As of paul/interactions, 2026-09-02. Companion to FEATURE-LIST.md, which is
the part page in full detail; this is everything else, plus the open questions.
The whole application is one sentence. As a user, I want to find the right tool or tools for a feature or a group of features, and save the tool assembly to a list. Everything below either serves that sentence or is in the way of it.
| § | Open questions |
|---|---|
| How I use it | 1 |
| The screen | — |
| 1 Tool filters | 3 |
| 2 The feature list | 2 |
| 3 The tool table | 3 |
| 4 The 2D tool viewer | 2 |
| 5 The order list | 4 |
| 6 Threaded holes | 4 |
| 7 Tool matching | 3 |
Seven walkthroughs, in the order a shop meets them. Button names are exactly what is on screen.
The fast path, and the one most jobs are made of.
- Upload the part and wait for the analysis.
- Click a face. The table below fills with what cuts it, best first, and the panel on the right assembles the top row.
- Don't like the top row? Click another. The panel follows.
- Press Add tool. The feature appears on the list with that tool under it, and the tool is on the bill.
Two tools, chosen on different numbers.
- Click the hole. The panel offers the threads its diameter reads as, closest first.
- Pick M3×0.5. That is the whole of the question the panel asks: which thread. How the thread is made is asked over the drills it decides.
- The table splits into Taps and Drills. Taps first, because the thread is the decision. Over both sits Cut Tap / Form Tap — two words, no figures; red means no standard drill makes that tap's predrill from the hole as modelled.
- Pick a tap, press Add tool.
- Switch to Drills — already judged against the tap drill, not the hole as modelled. Pick one and press Add this tool.
- The row now holds both, and the bill has two lines.
"Can one end mill do all of these?" — the question worth answering before quoting.
- Press Add group.
- Click the faces on the part. Click a face again to take it out. Every group asks the one question — one tool for all of them — so the box says so rather than offering it (Paul, 2026-09-08).
- The table below is already showing only what cuts every feature in the group. If it is empty, no single tool does it.
- Pick the tool from the table, then press Add to order list under the stack. That one press makes the group and orders the assembly together (Paul, 2026-09-09) — the box itself has no confirm, and the X in its top right is the way out.
Six different features, six different answers, one press.
- Add group, pick the features, then choose The best tool for each.
- The table stops listing tools and says why: the question is one per feature.
- Press Add to order list under the stack.
- Open the folder in the list. Every feature has its own tool underneath it.
- Press any of those tools to see the full offer for that one feature, if you want to overrule it.
A second thought about a feature that already has a tool.
- Press the tool under a row. It opens in the panel, which says what it is on the list for.
- Click a different row in the table. The panel now offers two things:
- Replace B976Z02500 — swap it out, one tool where there was one.
- Add this tool — keep both. This is how a hole gets a spot drill and a drill.
- Remove tool takes one off. Removing the last one takes the row off the list too.
Deciding the cutter now and the holding when the job is real.
- Open a tool that is already on the list.
- Choose a holder, then a collet. Only holders that grip the shank and clear the part at the stickout this feature needs are offered.
- The drawing updates, with the clearance around it.
- Press Update tool assembly. Every feature that tool cuts gets the same holding.
What all of this was for.
- Open the Order list tab.
- Every component is its own row — tool, holder, collet — with its own quantity and its own way to the vendor's page.
- Type the quantities.
- Press Export Fusion library, give the library a name, then save the
whole bill as a
.jsonlibrary Fusion can import.
Open question — there is no walkthrough for "I already know the tool". Every path above starts from the part. A shop that wants to check whether a tool it already owns will cut a feature has to find it in the table by number and read the marks — which works, but nobody designed it.
One page does nearly all of it: the part, the questions you ask about it, and the tools that answer them.
┌──────────────────────────────────────────────────────────────────────────┐
│ Tool catalog (17470) Part | Order list ⑤ mm/in │
├────────────────────────────────────────────────────┬─────────────────────┤
│ ┌────────┐ ┌──────────────┐ ┌───────────────┐ │ TDMX1200 │
│ │FILTERS │ │ FEATURES ② │ │ NEW GROUP │ │ On the list for │
│ │ vendor │ │ 4 × Through │ │ [chips…] │ ▟▙ │ Pocket │
│ │ family │ │ B976Z02500 │ │ ( ) one/all │ ▟███▙│ [ Remove tool ] │
│ │ type ① │ │ Pocket │ │ (•) one each │ ▜███▛│ ┌───────────────┐ │
│ │ shank │ │ TDMX1200 │ │ [Create group]│ ▜▛ │ │ holder/collet │ │
│ │ … │ │ [+feature] │ └───────────────┘ │ │ the drawing │ │
│ │ │ │ [+group] │ ← the part │ │ + clearance ④│ │
│ │ │ │ M3×0.5 ⑥ │ │ └───────────────┘ │
│ └────────┘ └──────────────┘ │ │
├────────────────────────────────────────────────────┤ │
│ Cuts every feature in the group 9490 removed ③ │ │
│ CATALOG NO. VENDOR DIAMETER ▾ FLUTE LENGTH ▾ │ │
│ TDMX1200 WIDIA 12.00 mm ✓ 26.00 mm ✓ │ ⑦ rules + crib │
│ TDMX0800 WIDIA 8.00 mm ✓ 19.00 mm ✓ │ decide these │
│ TDMX0500 WIDIA 5.00 mm ✓ 13.00 mm ✓ │ rows │
└────────────────────────────────────────────────────┴─────────────────────┘
| # | Region | Its job | Reads | Writes |
|---|---|---|---|---|
| 1 | Filter rail | Narrow the catalog to what a shop would buy | the URL | the URL |
| 2 | Feature list | Hold what has been asked about, and its answers | the part, the sheet | the list, the sheet |
| 3 | Tool table | Offer every tool that answers the current question | rules, filters, crib | nothing |
| 4 | Tool panel | Assemble a tool, and decide what happens to it | the selected tool | the sheet, the list |
| 5 | Order list | Show what has been decided, as a thing to buy | the sheet | quantities |
| 6 | Thread picker | Say what a hole is threaded for (how it is made is asked over the lists) | the hole's diameter | the predrill, filters |
| 7 | Matching | Decide which tools fit, and in what order | three CSVs, datasheet | nothing |
a click THE QUESTION the tool table the panel the bill
or a list ─asks→ these features, ─judged→ ranked, ─picked→ add · ─writes→ one line
row all or each marked replace · each
▲ ▲ remove │
│ │ │
└───────── and answers the row it came from ─────────────────────┘
rules · filters · crib
The return path is what makes the list the source of truth: once a tool is on the bill for a feature, that is what the row says — the decision, not the recommendation.
- Upload a part, watch it analyse, and click a face to see what cuts it.
- Build a list of features and groups — the things you are asking about.
- Narrow the catalog by vendor, family, type, material, shank, and any number column.
- Read the offer: what fits, in the rules' order, with a mark on every number they read — and the near misses when nothing fits.
- Assemble a tool with a holder and a collet, and see it drawn with its clearance.
- Save it to the list: add, replace, add another, or remove. Removing the last one takes the row with it.
- Say a hole is threaded and get taps and drills as two tabs; the predrill control over the drills says whether the thread is cut or rolled, and the drills are judged against whichever it is.
- Take the bill away as an order list, or as a Fusion tool library.
Where it lives
apps/catalog/app/routes/part.tsx— the whole screen, wired together — the last place to lookapps/catalog/app/shared/— the rules, each in its own module and tested thereapps/catalog/app/components/— what draws themapps/catalog/app/client/— the matching worker, off the UI thread
Paths below are relative to apps/catalog/ unless they say otherwise.
Filters narrow the catalog to the tools a shop could actually buy and hold. They never decide whether a tool fits — that is the rules' job — so a filter is always somebody's preference, and always visible and reversible.
- One query object — free text, discrete terms (vendor, type, family, material, flutes), and continuous ranges in millimetres. It is the only thing that decides which tools are on screen.
- Two of those terms are phrases this catalog builds, not facets a vendor
publishes (2026-09-08).
typeis the form with what is behind the cut in its words —Reduced shank bull nose end mill,Necked flat end mill— andfamilyis the vendor's product line, or the family under it where the vendor names no line. Each replaced a pair of axes a shop had to answer twice. A holder and a collet have atypeof the same kind:BT30 ER11 collet chuck, the shortcut through taper, clamping and series at once. - A reduced shank is
SFDM < DCand nothing else (Paul, 2026-09-08) — the shank behind the cut is thinner than the cut. It is notshankOf, the package's reading of a neck standing back from the flutes: over the scrape those two find 7,499 tools and 6,378 tools with exactly one tool in both, so which one the words are built from is a decision rather than a detail. Taps and slot mills are left out of the phrase — a tap's shank is sized to the chuck, and a slot mill has no full-shank version to be told apart from — which leaves 574 tools, all of them mills, readingReduced shank …. - A neck is a second phrase, not the same one (Paul, 2026-09-09, on a
Kennametal
MaxiMet™ … Neckedend mill reading as a plainFlat end mill: "shouldn't this tool be showing as a reduced shank flat end mill based on our rules?"). It is not, by the rule above —SFDMandDCare both ⌀9.525 and what is thin is the 28.575 mm shoulder at ⌀8.920 below a full-width shank. SohasNeckgets a word of its own,Necked flat end mill, on the 9,919 tools in the scrape that have one. The two readings are disjoint — 8,079 against 9,919, none in both — which is why one word could not carry both: a thin shank is clamped thin, a neck is clamped full and reaches past a wall. Taps and slot mills are left out of this phrase for the same reason they are left out of the other, and for the slot mill more literally: 1,791 of its 2,261 state the neck, and the other 470 only because no shoulder was published (Paul, 2026-09-09: "we just shouldn't touch any of the slot mills"). The shank leads where a tool is somehow both; nothing in the scrape is. - What is behind them still runs.
form,shank,familyId,productLine,taperandcolletSeriesare all still matched, still read off a URL, and two of them are still written by the app itself — they simply have no control on the page.AXES_PARKEDis the list, with a reason each. - It lives in the URL. A filtered view is a view you can send to a colleague; that only works if it round-trips through the query string without loss.
- Free text is identity, never geometry — catalog number, material number, brand, family, and the vendor's own product line (GOdrill, Viper). Nobody types "12.7" meaning a diameter into a search box.
- Material is "any of". A tool indexed for steel and stainless answers a question about either. A tool nobody rated carries no values and drops out of a filtered view — that is the vendor's silence, not a claim about the tool.
- Every axis is counted against every other filter but its own. Choosing one vendor narrows the family and product-line lists to that vendor's — but does not hide the other vendors, because then you could never change your mind.
- An empty answer stays and is greyed; another vendor's family or product line comes off the list entirely. The difference is "this exists and matches nothing" versus "this was never yours to pick".
- A chosen feature fills the blanks — the tool types the feature considers,
and the
mustbounds that apply to every type. Written into the same controls somebody can change. - Suggested, never enforced. Applying a new feature's suggestions replaces only what the last feature suggested, so an answer somebody gave themselves stands.
- Choosing a thread writes the type filter — drills and taps — and "show compatible endmills" adds the two milling forms to the same place. The filter rail is the last word, so anything the app decides for you is somewhere you can undo it.
- A question about a column is asked on that column's heading. The vendor, the type, the catalog number and every geometry code are narrowed from the header that shows them, with a funnel that is drawn whether or not it is set and filled when it is. A filter written anywhere else has to name the thing it narrows; a filter on the header is already pointing at it.
- One button is left: the part's material, in the top right beside the column picker rather than on a row of its own (Paul, 2026-09-08). It is not a column and is not going to be one — it is a property of the part, which is why it both narrows the list and orders it. Everything else went onto a column or off the page (Paul, 2026-09-08: "I don't love how the filters are hidden behind the button right now, especially with so many also being column headers", then "we can remove the remaining clamping, floor radius, collet, and holder filters. Keep the rule on the back end but hide them for now").
- Rolled in, not dropped. The shank became part of the type's words and the product line became part of the family's, so two pairs of controls became two columns. The holder, the collet, the clamping length and the floor allowance are hidden with their rules intact: the crib still holds with its own tapers, and the sheet's clamping and floor values still decide what fits.
- One list decides which is which.
app/shared/column-filters.tssays what each column asks;BUTTON_FILTERSis every quick filter that list does not claim. A question asked in a header and on a button is the defect, andcomponents/filter-panel.test.tsxis the sensor against it. - A header narrows; the buttons can widen. The Type heading offers the forms the list currently holds, so it cannot offer a value that empties the list. Asking for a form this catalog does not hold is not a question a header can ask — and was not worth keeping a second Type control to answer.
- The count is beside the Clear. Every narrowing is counted in the chrome, the headers' included, because a filter set on a column somebody has since hidden is otherwise a short list with nothing pointing at why.
- The menu is drawn over the page, not inside the table. The kit's table is a scroll container on both axes, so a menu positioned inside a header cell is clipped at the header's own edge — which is why the first pair of header filters here was written and never wired up.
- It stays up until somebody presses off it (Paul, 2026-09-08: "the filter
options clear immediately when I make a selection … I'll often want to
multi-select in these dialogs"). Ticking a value, choosing an operator and
typing a number all leave the menu where it is; a press anywhere else, or
Escape, puts it away. The menu is therefore the table's rather than the
heading's: a virtualized header is thrown away and built again on every render
of the list it stands over — the kit hands
react-windowaninnerElementTypeit builds inline — so a menu a heading held open closed itself on the one press it exists for.components/column-filter.tsxholds the rule andtests/on-the-part.spec.tsis the sensor. The funnel opens on the press rather than on the click for the same reason: aclickneeds the press and the release on one element, and the header does not survive between them while the matcher is answering. - A list of words has a box over it, and a button beside the box (Paul,
2026-09-08). Every filter that narrows on words — the tool list's Vendor, Type
and Family; a holder's Vendor, Type, Family, Taper, Clamping and Collet
series; a collet's Vendor, Type, Family and Series — opens with a search over
its own options and a Select shown beside it. Family runs to hundreds of
values, so a list of ticks alone is a list nobody reaches the bottom of; the
search narrows the options and the button ticks every option it is showing, so
one vendor's whole product line is one press rather than eleven. Searching
is not choosing: a value ticked and then hidden by the next word typed is
still a value the shop asked for, and comes back ticked when the box is
cleared. The button turns into Clear shown once everything on screen is
ticked, and takes back exactly what it gave — nothing chosen off-screen moves.
components/column-filter.tsx§optionsMatchingis the rule andcomponents/column-filter.test.tsxthe sensor. The catalog number stays a plain text box: it narrows the rows, not a list of options. - An axis that has been narrowed is counted over a pool judged without it
(2026-09-10). With a feature on screen the rows are what the matcher answered
with, and the matcher only judges what the terms already admit — so the moment
one vendor is ticked, no other vendor's tools have been judged for that
feature and counting the rows reports every other vendor at nought. Which is
what it did, over a list of vendors that plainly had tools for the job (Paul,
2026-09-10: filter to Kennametal, drop the family filter, and every other
vendor reads zero until you tick one). So the worker judges the same question
a second time with every facet cleared and sends the counts back —
shared/catalog-matcher.ts§facetCounts, measured per axis against every filter but that axis's own. It is the whole pipeline again, ~135 ms against ~50 ms on the scraped catalog, so it runs only while a facet is actually narrowing and the widened pool is kept across every tick of one: the pool is the question with the facets off, which is the one thing ticking a facet does not change. - What is left when the worker cannot answer. No facet narrowing yet, or a
question nothing fits at all, and the counts are over the rows the list is
holding, near misses included — a picker saying "nothing to narrow by" over
four rows on screen was the same rule broken at the other end.
shared/filter.ts§stillOfferedstill carries that half: it remembers the list from the last moment the question could be answered, so a second value stays one press away while nothing has been judged for it.
Open questions
- Does a feature's suggestion still announce itself? The buttons say what they are set to, so a suggestion that fills one is readable. A suggestion that fills a header is a funnel on a column that may be hidden; the count beside Clear is the only sign, which may not be enough.
- Part material is a property of the part, not of the list. It is still the first of the buttons. Should it move next to the part, where it is asked once?
Where it lives
app/shared/filter.ts— the query, what it matches, and the per-axis countsapp/shared/filter.ts—FACET_AXES, the axes a count is measured overapp/shared/catalog-matcher.ts—facetPool/facetCounts, the counts with a feature on screenapp/components/filter-panel.tsx—QUICK_FILTERS— every axis and its valuesapp/shared/column-filters.ts— which column asks which filter, and what is parkedapp/shared/tool-type.ts— what a tool is in one phrase, shank and allapp/components/column-filter.tsx— the header filters, their menu, and the compare operatorsapp/components/column-heading.tsx— a heading's two marks: it sorts, and it narrowsapp/components/filter-rail.tsx— the bubbles down the left of the part (unused)app/shared/suggest-filters.ts— what a chosen feature and material fill inapp/shared/holding.ts— the crib axes — taper and collet series
The list is what has been asked about, and it drives everything: what the part
paints, what the tool table is for, and what reaches the bill.
FEATURE-LIST.md is the full spec; this is the shape of it.
- A feature — one decision. Eight identical holes are one row.
- A group — several features chosen together, plus what it wants back: one tool for all of them, or the best tool for each.
- Either can hold several tools. A hole is a spot drill and a drill.
- Groups are named from their contents — 4 × Through Hole — never typed.
- Add feature — click a face, then confirm. Add group — click the faces (a click is a toggle while building; the arrows choose which reading you meant), or add every feature of a kind at once.
- Confirming is what writes the bill, one line per distinct feature.
- Right-click a row to edit or remove it. Removing takes its tools off the bill.
What it does to the tool table:
| Selected | Table shows |
|---|---|
| nothing | the whole catalog, narrowed by the filters |
| a feature | what cuts it, then the near misses |
| a group, one-for-all | what cuts every feature in it |
| a group, one-each | no list — the tools are chosen per feature |
| a tool under a row | that row's question in full, with the tool in the panel |
Open questions
- Threads on groups of holes. Holes in a group should list as sets by diameter and depth, each set taking the thread picker as a single feature does. The thread choice is stored per feature today, which is what stands in the way. See §6.
- Ordering and operations. The list is in the order things were added. A shop reads a job as a sequence — spot, drill, tap — and nothing on the row says which pass a tool is for. Does the list need an order, or is that the order list's job?
Where it lives
app/shared/feature-list.ts— what the list holds, its names, ids, storage — andasked()app/shared/recommendations.ts— a row's answers, and what opensapp/shared/part-interaction.ts— what a click on the part meansapp/components/feature-list-panel.tsx— the list, its answers, its right-clickapp/components/group-editor.tsx— building a groupapp/components/selection-panel.tsx— the reading, its numbers and its threaddocs/FEATURE-LIST.md— the full spec
The offer. Every tool that could answer the question currently being asked, in the order the rules rank them, with a mark on every number the rules read.
| What is asked | Heading | Rows |
|---|---|---|
| Nothing | Every tool in the catalog | the filtered catalog |
| A previewed face | Cuts the pocket | what fits it |
| A selected feature | Cuts the pocket | what fits it |
| A group, all | Cuts every feature in the group | what fits all of them |
| A group, each | One tool per feature | a notice, not a list |
| A threaded hole | Taps · Drills | two tabs, taps first |
-
Nothing fits is never an empty table. The closest misses are shown instead, each with the number that stopped it painted red — and never a tool a filter turned down. A near miss may be outside the one bound it missed on, and no other:
- A bound somebody typed themselves is the question rather than a tolerance on it, so at most three flutes is at most three flutes and the table says so when nothing answers it (Paul, 2026-09-10).
- The bound the geometry wrote holds on every column but the one that removed the tool. A group capped at ⌀0.286 in listed ⌀0.438 in to ⌀0.750 in cutters (Paul, 2026-09-11): they were removed for reach, so the diameter question was never put to them — judging stops at the first feature that rules a tool out — and they came back as "the closest" under a filter no part of them was near.
- So a face nothing reaches still lists the deepest cutters in the crib: they miss on flute length, which is the bound they are allowed to be outside.
nearEnoughinapp/shared/tool-fit.tsis the rule, applied a tool at a time in the worker before the fifty nearest are taken. -
The first row is always highlighted and the panel is already assembling that tool, so confirming takes it without a second click.
-
Notes beside the heading say what the rules removed, what the filters hid, and how many have no holder that clears — the difference between "no such tool" and "you filtered it out".
| Mark | Means |
|---|---|
| green tick | the rules read this number and it passed |
grey i |
a figure worth reading, inside tolerance — hover for it |
| amber warning | a caution: allowed, and here is what you allowed |
red ✗ |
the rule that took the tool off the list |
| nothing | the rules never read this column |
- Every column header filters its own column — the geometry codes as ranges, vendor, type and family as checkbox lists, the catalog number as a search. The two holding cells ask nothing: they set the holder and collet on that row rather than holding a value to narrow on.
- Four columns say which tool a row is: catalog number, vendor, type, family — the same four every holder and collet row carries, asking the same questions on all three lists. Type says the shank where the shank is narrower than the cut; family says the vendor's line where it names one; the catalog number is a search over the number and the vendor together.
- They are in the column picker with the rest (Paul, 2026-09-08: "I should also see ALL the columns in the list"). Nothing is drawn outside the column set any more, so every column can be hidden and dragged — which is also what keeps the picker honest about what a list holds.
- A heading shows both of the things it can do: a pair of faint chevrons saying it sorts, a funnel saying it narrows. The kit draws an arrow only on the column already sorted, so every other heading used to be a word with no sign that pressing it did anything.
- The checkbox lists narrow, they do not widen. They offer what the table currently holds, counted, so a value that would empty the list can be told from a rare one before it is pressed.
- The catalog-number box narrows on number and brand together.
- A press inside a filter is not a press on the header. Opening or using a filter must not also re-sort the column it is standing on.
- Columns are yours — hidden, shown and reordered per list; taps and drills keep separate sets.
- The list opens in the order a row is read (Paul, 2026-09-10): catalog number, vendor, family, type, then flutes and diameter, then flute length, length below holder, L/D and overall length. The column picker lists every column in that same order top to bottom, because the page seeds the order from the table's own column list, and the shank, the holder and the collet wait at the end of it to be asked for.
- Two columns follow what is on the list. Corner radius is the number a
mill is chosen on and a dash beside a drill; tip angle is the reverse. So a
mill on the list brings the radius out, a drill brings the angle, and a list
holding both draws both. Toggled by hand either one stays where it was put.
app/shared/auto-columns.tsis the rule. - The table scrolls sideways rather than squeezing its columns into the panel. Fitting them was tried on 2026-09-10 and taken straight back out: a dozen columns share a panel with fifty pixels of chrome each, so what fits is headings nobody can read over cells nobody can read.
Open questions
- Should a near miss be selectable? Today it is: the first row is highlighted even when nothing fits, so Add tool will put a tool the rules refused onto the bill. That may be right — shops overrule rules — but nothing warns them.
- Sorting versus ranking. Sorting by a column throws away the rules' order, which is the app's actual recommendation.
Where it lives
app/components/part-tool-table.tsx— the table, its columns, its header filtersapp/shared/tool-marks.ts— the tick, thei, the warning and the redxapp/shared/column-order.ts— which columns are shown, and in what orderapp/shared/auto-columns.ts— the two columns the list turns on for itselfapp/shared/tool-order.ts— kept rows first, then the sheet's orderapp/shared/geometry.ts— how a geometry value is printedapp/shared/catalog-matcher.ts— the one matching truth: what fills these rowsdocs/PART-TOOL-TABLE-PLAN.md— replacing this table with the kit'sTable
The drawing of the tool, its holder and the clearance around it. The component
lives in @toolpath/tool-drawing and is Justin's; this catalog only wires it up
through catalog-drawing.tsx, with tool-drawing-input.ts as the whole adapter.
The package draws the verdict; it does not decide it — whether an assembly
clears a feature is answered in @toolpath/catalog-data. Do not write a second
drawing here.
What the panel around it offers is the part that matters:
| State | Buttons |
|---|---|
| Nothing selected | none — there is nothing to add it to |
| Feature or group with no tools yet | Add tool |
| This tool is one of its tools | Update tool assembly*, Remove tool |
| It has tools, this is not one | Replace B976Z02500, Add this tool |
* Only when the holding has changed. A button that saves what is already saved is one somebody presses to find out whether it did anything.
- Replace names the tool it drops, because with several mapped there is otherwise no telling which one goes. Where several go it says Replace all tools.
- Remove takes the row with it if that was its last tool.
- The panel always says what the tool is on the list for.
Open questions
- Stickout is carried but not editable. A bill line holds it and Update tool assembly is written to save it; there is no control yet.
Should the drawing show the feature?Answered 2026-09-03 by Justin: the panel draws the part the tool is read for. The page had the feature's reach curve in hand the whole time and spent it only on the holder list, so the one place a shop reads a tool showed no reason for the stickout the list beside it had settled on.
Where it lives
app/components/tool-details.tsx— the panel: the drawing, the holding, the buttonsapp/shared/tool-actions.ts— which buttons, given what is being askedapp/components/catalog-drawing.tsx— the one file that wires the drawing package upapp/shared/tool-drawing-input.ts— the whole adapter into its input contractapp/shared/holder-choice.ts— which holders and collets are offered, and the stickoutpackages/catalog-data/src/clearance.ts— whether an assembly clears — the verdict the drawing drawspackages/catalog-data/src/stickout.ts— one module owns how far a tool stands outdocs/HOLDER-PROFILES.md— holders drawn from their own CAD rather than nine published numbers@toolpath/tool-drawing— the component itself — another repository, Justin's
The setup sheet read the other way round: what has been decided for this part, as a thing to buy. It stores guids and resolves them through the catalog on every render, so it can never disagree with the catalog about a diameter.
-
A row is a component, not an assembly. Three cutters usually go in one holder, so tool, holder and collet each get their own row, quantity and vendor link, with a heavier rule between assemblies.
-
A tool on its own is a legal line. Deciding the cutter and leaving the holder for later is a real state of a job.
-
The vendor's page hangs off the catalog number, not a column of its own.
-
Quantity is typed, not spun.
-
A line whose tool has left the catalog shows as gone, not as a stale number.
-
Export Fusion library — the whole bill as a Fusion
.jsonlibrary. The export asks for a name and nothing else. It writes one record per distinct assembly, carrying its selected setout and its holder — the silhouette measured off the vendor's CAD model where the catalog has a complete one, and the published dimensions otherwise — and names every omitted tool or holder shape rather than inventing vendor geometry.The exporter is not this application's. It is
@toolpath/tool-support/export/fusion, whose per-type rules are derived from Autodesk's own published JSON Schema and held against it by a test upstream.app/shared/fusion-input.tsis the whole seam: it turns this catalog's records into what that package takes and reads its notes back into the dialog. Do not write a second exporter here.
Open questions — this page has had the least attention
- Do the vendor links actually work? Unverified against real scraped data. Worth checking before anyone demos it.
- It does not know about groups. A bill that said "these four faces, one end mill" would match how the work was decided.
- Nothing says what a tool is for. A line does not name the feature it was chosen for, so the reasoning is lost the moment you leave the part page.
- The export states no real feeds and speeds. Every record carries one placeholder preset called
Default Presetwhose every number is 1. It is there because a tool with no preset at all is a tool Fusion will not load: the export went out asstart-values: { presets: [] }until 2026-09-11, on the reading that the schema wants the key and not a preset in it, and the libraries were refused. Autodesk does ask for one — everystart-valuesbranch declarespresets: { type: 'array', minLength: 1 }— butminLengthis a string keyword and a no-op on an array, so no validator enforces it and nothing here caught it. Of 727 tools across seventeen libraries Fusion itself wrote, every one carries a preset and none carries zero.app/shared/pretool-presets.tsis still here and is deliberately unreferenced: it is the whole PreTool model, and the package takes presets back throughToolRequest.presetswhen this application is ready to answer for them. Three things need fixing first, each found by checking PreTool's output against the five preset shapes the package pins: spot and centre drills route through the drill generator and are short of the five feedrates aspottingpreset requires; a tap states four fields atappingpreset does not model; and PreTool computes in millimetres while the export now states each record in the vendor's own unit system.- A face mill no longer exports. Fusion requires
DCX,RE,TAandupper-radiuson one, all four of them measurements, and the catalog states none. It used to export with a fabricatedupper-radius: 0— a record that loaded and stated dimensions nobody measured.
Where it lives
app/routes/order-list.tsx— the pageapp/shared/setup-sheet.ts— the sheet itself — lines, quantities, storageapp/shared/fusion-input.ts— the bill as@toolpath/tool-support/export/fusiontakes it, and what its notes meanapp/shared/save-file.ts— saving it from the browser
A threaded hole is drawn as a hole. Which thread it is for is not in the model, so the app guesses from the diameter and lets somebody say otherwise — and then it is two tools, chosen on different numbers.
- The guess says what it read. A hole is usually modelled at the tap drill, sometimes at the minor diameter, occasionally at the nominal size; all three are tried, and the list is headed Closest match to modeled diameter.
- The list ranks; it does not argue. The order says which is closest; the words say only which diameter it matched.
- It is asked over the lists, not on the feature dialog (Paul,
2026-09-07). Saying what thread a hole is for and saying whether it will be
cut or rolled are two decisions, and only the second is about the lists.
Two rows of figures under the thread put it in front of somebody answering
the first.
components/predrill-choice.tsxis the control; the dialog iscomponents/thread-picker.tsxand asks one thing. - Over the taps as well, and named after the tap (Paul, 2026-09-09: "the 'form drill' term doesn't make a lot of sense, it's more that the appropriate drill is defined by the type of tap"). One mode, two consequences: the tap list holds only that kind of tap, and the drills are judged against that tap's predrill. Picking a tap writes the mode back, so choosing a form tap first puts the drill list on the form drill without a second press.
- A form tap wants a bigger hole than a cut tap — ⌀0.201 in against ⌀0.2244 in on a 1/4-20 — because it displaces metal rather than cutting it. Starting a form tap at a cut-tap size snaps the tap.
- The drill sizes are the Engine's charts, copied from
tap.jl, with the Jarvis formula only where the published chart is silent. - Two words, not two figures (Paul, 2026-09-07: "don't show the numbers,
just tap or form drill"). The chart size and the difference from the model
were printed on the control and again, per row, in the list underneath — which
is where a shop reads a drill's deviation, in the table's own columns. They are
on the hover now. What survives on the control is the one thing the list cannot
say: red, with an
✗, where the predrill is further from the model than the shop's max drill deviation allows, meaning no standard drill makes both. - Choosing a mode writes the type filter, where it can be seen and undone.
- The tap says which it is, since 2026-09-09 (Paul, 2026-09-07: "ideally it
should be pulled from the tap itself, but I don't think we have that data
yet"). It was true that a tool's
formis onlytap left handortap right handand that a vendor's product line names the material rather than the method. The fact went upstream where it belonged:@toolpath/tool-scraper2.4.0 recordsthreadMethodoff each vendor's own category, which is also what reaches EMUGE'sFG02— the catalog's only forming taps, 1,432 of them.CatalogTool.threadMethodcarries it, a tap stating none stays in both lists, and catalog version 10 is the store that holds the labels.
- Taps first. The thread is the decision; the drill follows from it.
- Drills are judged against the predrill, not the modelled bore — and against the whole group of identical holes. The kernel's drill and end-mill limits move with it, or every drill between the model and the predrill comes back "too large".
- Taps bypass the rules sheet. A tap is wider than the hole it threads, so the sheet's hole rules would refuse every one on diameter. The two questions that matter are asked directly: does the threaded length reach the bottom, and does the tool clear the part on the way down — swept against the same reach curve a drill uses.
- Taps go through the same holder question as everything else.
- When none reach, the nearest misses are shown with the failing length in red rather than an empty list.
- Show compatible endmills adds the flat and bull-nose forms to the type filter, for interpolating the predrill. Drills always lead.
- The tap list has its own columns:
DCis the thread's nominal diameter andLCFthe threaded length, with no corner radius or point angle.
Open questions
- This catalog holds no pitch. A tap's nominal size is stated by every vendor; the pitch is in the catalog number and the family name, differently for every brand. So M8×1.25 and M8×1 are both offered for an M8 and the panel says to check it. Fixing this is a scraper change, upstream.
- Threads on groups of holes — the big one. Blocked on the thread choice being stored per feature tag.
- Thread milling is coded but not offered. One line away from coming back.
- Spot drills and chamfers are not modelled. A threaded hole is really three or four operations; the app knows about two.
Where it lives
app/shared/threads.ts— the thread table, the Engine's tap-drill charts, reading a holeapp/components/predrill-choice.tsx— cut tap or form tap, over both listsapp/shared/hole-mode.ts— standing a hole in at its predrill; which taps reachapp/components/thread-picker.tsx— the picker, and the deviation marksapp/shared/thread-panes.ts— which tool leads each tab
What decides whether a tool is offered for a feature. This is the whole of it —
the Engine's wider rule set was read once for reference (ENGINE-TOOL-MATCHING.md)
and is not what runs here.
All three sit beside the code that reads them, in apps/catalog/app/shared/.
They are the files to change when the matching is wrong — not the
TypeScript.
| File | Rows | What it says | Guide |
|---|---|---|---|
app/shared/rules.csv |
54 | feature · when · tool types · rule · level · note | docs/RULES.md |
app/shared/knobs.csv |
16 | the numbers the rules are written in terms of | docs/RULES.md |
app/shared/feature-defaults.csv |
38 | per feature: what to show, and what filters the list | docs/FEATURE-DEFAULTS.md |
- A rule is a sentence about a number — diameter <= largest tool diameter, flute length past the corner >= feature depth + through overcut — written in knob names, never in literals.
- Rules are scoped by feature (with wildcards:
*Hole,Through*,*), by a condition, and by which tool types they apply to. A rule for one kind of tool is not a filter over all of them. - New fields, conditions and knobs are declared in the sheet, never
hard-coded into a panel. That is the whole reason the sheets exist. See
RULES.mdandFEATURE-DEFAULTS.md.
| Level | Count | Effect |
|---|---|---|
must |
28 | Removed. A fact about the geometry — the tool cannot cut it. |
should |
5 | Warned. Shown, marked amber, ranked below what fits. |
prefer |
4 | Demoted. A preference — a hole up to an inch is drilled. |
rank |
17 | Orders what is left: closest to the bore, shortest that reaches. |
- A warning outranks a demotion. A
shouldis a geometric fact somebody has to override; apreferis a taste. Read the other way round for a day, and tools over the tightest corner sat among the merely unpreferred. - Removed tools are kept, not discarded. "Nothing fits" is only actionable when it says which rule did the excluding and by how much.
Every number a rule is written in terms of. Four are also controls on screen, marked · — a shop changes those without touching a file, and raising one admits tools rather than hiding any.
| Knob | Value | What it decides |
|---|---|---|
| drill oversize · | 0.1016 mm | how far over the hole a drill may be — 0.004 in |
| drill undersize · | 0.1016 mm | and how far under; asked separately, because shops differ |
| drill angle tolerance shallower | 35° | how much shallower a drill point may be than a blind bottom |
| drill angle tolerance sharper | 0° | and sharper — zero, because a sharper point leaves a step |
| finishing radius limit · | 0.025 mm | the largest bull-nose radius allowed to stand in for a flat |
| through overcut | 0.127 mm | how far a through cut runs past the bottom |
| chamfer angle tolerance | 0.15° | how far a chamfer tool's angle may differ from the feature's |
| thread mill margin | 2 % | how far under the minor diameter a thread mill stays |
| good hold | 33 % | share of the overall length that stays in the holder |
| least hold | 25 % | below which an assembly is not offered at all |
| radial holder clearance | 0.508 mm | room between the holder and the part, sideways |
| axial holder clearance | 0.508 mm | room between the holder nose and the part top |
| least stickout | 12.7 mm | the shortest stickout worth setting a tool up at |
| stickout step | 3.175 mm | the increment a default stickout lands on, inch |
| metric stickout step | 3 mm | and metric |
| minimum clamping length · | 3 ×D | shank kept in the holder where the maker does not say |
- Read straight off the kernel's datasheet, never inferred: the widest cutter that reaches the tightest corner, a hole's drill and end-mill limits kept apart, depth below the part top, floor fillet, the cone at the bottom.
- A measurement the kernel does not state is never checked. An absent number must not become a demand of zero.
- Several features fold into one verdict — removed by any, warned by any, ranked by the first. That is what makes "one tool for all of them" a real question.
- A tool nothing can hold is not offered. Every candidate is asked of the holders and collets: does one grip the shank, clear the part at the stickout this feature needs, and keep enough of the tool in the collet.
- The stack is swept against the feature's reach curve, the same question the drawing draws. A hole at the bottom of an open pocket has fresh air beside the shank, which a length-below-holder number cannot know.
- Four knobs are the shop's, on screen: floor radius allowed, max drill deviation (over and under, separately), minimum clamping length, and the holder clearances. Raising one admits tools rather than hiding any.
Open questions
- There is no way to see why a tool is ranked where it is. The marks say what passed and what failed; nothing says "this is third because the two above it are closer to the bore".
- Nobody can edit the sheets from the app. They are files in the repo, which means a shop's own preferences need a developer. Is a rules screen wanted, or is a CSV a shop actually edits good enough?
- Coverage is uneven. Twenty feature kinds have rules; the kernel emits more. A feature with no rows falls back to the wildcards, which is safe but silent — a feature nobody wrote rules for looks exactly like one that passed them.
Where it lives
app/shared/rules.csv·knobs.csv— the rules and their numbers; edit these, not the codeapp/shared/feature-defaults.csv— what each feature shows, and what filters its listapp/shared/rules.ts— reads the sheets; where a new field or condition is declaredapp/shared/feature-defaults.ts— reads the datasheet sheetapp/shared/judge.ts— verdicts, the four standings, folding several features into oneapp/shared/tool-fit.ts— the catalog judged against a selectionapp/shared/catalog-matcher.ts— the one matching truth, shared by the table and the rowsapp/client/catalog-matcher.ts·.worker.ts— the same work, off the UI threaddocs/CATALOG-MATCHING-PERFORMANCE-PLAN.md— why it is a workerapp/shared/holder-choice.ts— whether anything can hold it, and at what stickoutapp/shared/clamping-length.ts— the minimum clamping length rulepackages/catalog-data/src/fit.ts— what a feature demands, read off its datasheetpackages/catalog-data/src/clearance.ts— whether a stack clears the partapp/components/floor-allowance.tsx·drill-deviation.tsx·clamping-length.tsx— the knobs a shop can change on screendocs/RULES.md·docs/FEATURE-DEFAULTS.md— how to edit the sheets without writing codedocs/ENGINE-TOOL-MATCHING.md— the Engine's wider rule set; reference only, not what runs