Skip to content

Store rich configuration records and auto-index by heuristic - #41

Open
AlexKempen wants to merge 17 commits into
certfrom
claude/auto-indexing-rebase-9vw1ca
Open

Store rich configuration records and auto-index by heuristic#41
AlexKempen wants to merge 17 commits into
certfrom
claude/auto-indexing-rebase-9vw1ca

Conversation

@AlexKempen

Copy link
Copy Markdown
Collaborator

Replace the part-number map with a ConfigurationRecord per probed
configuration (part number, name, description, material, vendor), so
search, the UI, and future build checks can read what each configuration
produces without re-querying Onshape.

  • Unify part-studio (/parts) and assembly (element metadata) probing into
    a single record parser; assemblies now read the metadata property bag
    in one call instead of the assembly definition.
  • Enumeration skips isCosmetic ("exclude from properties") parameters.
  • Index automatically when an insertable has a non-custom vendor and fewer
    than 100 non-cosmetic configurations; the former search toggle becomes a
    force-index override. Vendor parts over the auto line raise a new
    MANY_CONFIGURATIONS warning; 512 remains the hard cap.
  • Search derives its part-number map from records (first-wins over
    enumeration order keeps the latest revision).
  • Migration 0004 renames search_part_numbers -> force_index, drops
    default_part_number, and replaces configurations.part_numbers with
    records.

Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01XT52xco8DfQnh1iMGff1gF

@AlexKempen
AlexKempen force-pushed the claude/auto-indexing-rebase-9vw1ca branch 2 times, most recently from d95f01f to 7733bf6 Compare August 17, 2026 02:44
claude and others added 17 commits August 18, 2026 23:42
Replace the part-number map with a ConfigurationRecord per probed
configuration (part number, name, description, material, vendor), so
search, the UI, and future build checks can read what each configuration
produces without re-querying Onshape.

- Unify part-studio (/parts) and assembly (element metadata) probing into
  a single record parser; assemblies now read the metadata property bag
  in one call instead of the assembly definition.
- Enumeration skips isCosmetic ("exclude from properties") parameters.
- Index automatically when an insertable has a non-custom vendor and fewer
  than 100 non-cosmetic configurations; the former search toggle becomes a
  force-index override. Vendor parts over the auto line raise a new
  MANY_CONFIGURATIONS warning; 512 remains the hard cap.
- Search derives its part-number map from records (first-wins over
  enumeration order keeps the latest revision).
- Migration 0004 renames search_part_numbers -> force_index, drops
  default_part_number, and replaces configurations.part_numbers with
  records.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XT52xco8DfQnh1iMGff1gF
Add a Configuration section to the editor build-status hover card that
lists each configuration parameter with its type and whether it is
excluded from properties (isCosmetic) — the flag that keeps a parameter
from multiplying the indexed configuration count, so editors can see at a
glance which parameters to mark cosmetic to bring a part under the
auto-index line.

Also fix the "Configurable" state row to key on the parameter count rather
than on a configuration row existing: an indexed non-configurable
insertable now carries a row that holds only records, and would otherwise
report as configurable.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XT52xco8DfQnh1iMGff1gF
Extend search beyond part numbers to the configuration records' part
names, surface the matched part number + name in results and (live) in
the insert menu, normalize numbers/fractions, and move the search index
out of D1 into R2.

- Index part names alongside part numbers. `SearchDocument` now carries
  a `partNames` field and a stored `records` list (`toSearchRecords`
  dedupes to distinct (part number, name) pairs). A hit picks its single
  best-matching record (`findBestRecord`, exact -> prefix -> substring)
  by part number or name, falling back to the default record for a plain
  title match, and annotates the hit with that record's number/name/config.
- Canonicalize numbers in `tokenize`: fractions, mixed numbers, and
  decimals collapse to a 2-dp decimal at both index and query time, so
  `.5`, `1/2`, and `0.50` all match. Thread specs (`10-32`) and part
  numbers (`217-2600`) are left untouched. `normalizeForMatch` keeps the
  record lookup in step with what the index matched.
- Show the part number + name on result rows (a dimmed secondary line)
  and live in the insert menu, where `findRecordForConfiguration` maps the
  selected parameters to their record. `/configuration/:id` now returns
  the insertable's records for this.
- Move the serialized MiniSearch index from the D1 `libraries.search_db`
  column to R2 (`SEARCH_INDEX` bucket), stored gzipped and streamed with
  `Content-Encoding: gzip`; removes the D1 per-value size ceiling as the
  index grows with record count. Migration 0003 drops the column.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ATK594PtgVSufvSHa24VVp
Prefix search matches a whole indexed term from a shorter query — typing
"mot" matches "motor" — and highlighting underlined that entire term, so
a match was all-or-nothing. Use MiniSearch's `queryTerms` (what was
typed) alongside `match` (the document terms that matched) to underline
just the longest query term each matched term starts with. Searching
"maxsp" now underlines "MAXSp" of "MAXSpline" rather than all of it; an
exact match still underlines the whole term, and overlapping ranges are
merged as before.

Also escape terms used to locate matches: they can now carry regex
metacharacters (a canonicalized "1.5" would otherwise also underline
the "125" in "1.5 x 125 Spacer").

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ATK594PtgVSufvSHa24VVp
Thumbnails are the slowest thing we ask Onshape for — a render can need
polling and take minutes — and until now only one existed per element,
its default configuration. A favorite pinned to a configuration and a
search hit that matched one both showed the default image.

Configuration thumbnails are now produced at runtime and stored in R2,
addressed by a canonical configuration so equivalent selections share
one object.

- Add canonicalizeConfiguration: drops parameters hidden by a visibility
  condition and values matching the parameter default (Onshape applies
  those anyway, so an all-defaults selection reduces to the default
  thumbnail), normalizes quantity expressions through the evaluator, and
  emits in declaration order rather than object-key order. input-parser
  moves to src/shared so both sides can evaluate expressions.
- New R2 key scheme splits `thumbnails/default/` (never expires — it is
  what everything falls back to) from `thumbnails/config/`, which carries
  a ~90-day lifecycle rule. Keys pin the microversion, so objects are
  immutable rather than overwritten in place.
- The serving route falls back to the default thumbnail for a
  configuration we haven't rendered, cached briefly so the real one can
  take over; `warm=1` also starts a ThumbnailWorkflow, whose instance id
  is derived from the configuration so concurrent requests for the same
  thumbnail collapse onto one render. The insert menu's live proxy stores
  what it already proxied, warming the cache at no added latency.
- Rows and hovers now render the same configuration. Favorites and the
  insert menu warm eagerly; search rows only serve what is cached, so a
  cold search can't start a render per row.
- Replace the generic thumbnail_urls JSON map with explicit
  small/large columns, and trim ThumbnailSize to the two stored sizes
  (the unused 600x340 is gone; the insert preview moves to the stored
  300x300). Migration 0004.
- Fix an unguarded items[0] in getThumbnailId (a configuration matching
  nothing threw a TypeError) and a per-size try/catch that never caught,
  since it returned the promise without awaiting it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ATK594PtgVSufvSHa24VVp
Follow-up on review. Three things.

Records now store their canonical configuration, computed at parse time
where the parameters are already in hand. Previously each surface encoded
its own configuration as-is, so a thumbnail warmed from the insert menu
was not the one a search row asked for. `enumerateConfigurations` omits
cosmetic, quantity, and string parameters while the insert menu holds the
user's whole selection; canonicalizing both drops the values left at their
default, and what remains differs only when the render genuinely does. The
insert menu and favorite editor get the canonical form from
ConfigurationWrapper, which is the only place with both the parameters and
the document's units, and a favorite now stores it — Onshape applies
defaults for whatever is omitted, so it inserts the same thing.

`canonicalizeConfiguration` takes `unitInfo` optionally, since it is only
needed to evaluate quantity expressions and enumerated records never carry
them.

Image fetches no longer mint object URLs. `handleImageResponse` created
one per fetch and nothing ever revoked it, so every thumbnail — and every
refetch of one — held a Blob until the page unloaded. The query now
validates the URL and returns it, which both keeps the existing loading,
error, and retry behavior and leaves the response in the browser cache for
the `<img>` that follows. Revoking was not an option: a cached query can
share a URL across rows, so freeing it on one unmount could blank another.

Also revert the REFERENCE.md edit; the R2 lifecycle note belongs in the PR.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ATK594PtgVSufvSHa24VVp
The card listed each parameter's type but not the thing an admin is
actually looking for: which parameters drive the configuration count, and
so which ones to exclude to bring an insertable under the auto-index
threshold.

- Each parameter now carries an Indexed / Not indexed badge whose tooltip
  gives the reason: it is varied, it is excluded from properties, or it is
  a quantity or text parameter, which are never varied.
- Extract `isIndexedParameter` and use it inside `enumerateConfigurations`
  rather than duplicating the rule, so what the card reports cannot drift
  from what enumeration varies. A test pins the two together.
- An enum's type badge shows its option count and lists the options on
  hover, which is where its share of that count comes from.
- Rename the section to Configurations and give both badges the same
  size/variant; the type badge was the odd one out.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ATK594PtgVSufvSHa24VVp
**Insert menu header.** The part number and name the selected configuration
produces moved out from under the preview image and into the modal's
header, under the element's name — all three are shown, since the element
name is how the part was found and the part number and name are what gets
inserted. The header is updated rather than rendered, so it follows the
selection as the configuration changes. ConfigurationWrapper reports the
matched record upward instead of rendering it, and the favorite editor
gets the same header rather than losing the display.

**Insert target.** Both insert routes took the tab being inserted into as
URL path segments, reassembled with non-null assertions and an unchecked
cast to the instance type. A missing or unrecognized piece became a
nonsense Onshape URL and an opaque failure rather than a complaint. They
now take the target as one typed object in the body, validated at the
boundary, and the path guards check the instance type against its literals
rather than trusting whatever arrived.

**Stale version ids.** A tab whose microversion hasn't changed is skipped
by a reload, so it never reaches saveInsertable and kept the version id it
was last loaded at, while the group row advanced to the new one. The
geometry is identical, but that id is what insertion and every document
link are built from, so a skipped insertable now moves forward with its
group.

Also give the stored search index its content type back: it is served
straight from R2 via writeHttpMetadata, so dropping it from the put left
the response untyped.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ATK594PtgVSufvSHa24VVp
Replaces the hand-rolled target-path guard with a zod schema on each route,
matching how the group and thumbnail routes already validate. The whole
body is checked, not just the path: the flags get explicit defaults rather
than arriving as undefined, and a bad request is rejected before any of
the handler runs.

`InstanceType` and the runtime list validators check against are now one
`as const` definition, so the schema's enum can't drift from the type.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ATK594PtgVSufvSHa24VVp
The row shows the matched configuration's part number and name under the
title, but only the title was underlined — a query that hit a part number
gave no indication of where.

`generateHighlightPositions` now takes the text and field to search rather
than assuming the document's name, so a hit carries positions for the part
number and name alongside the title's. `HighlightedText` is split out of
`SearchHitTitle` so the detail line can render each part with its own
matches, and the two are joined as elements rather than a string.

A title-only match underlines nothing in the detail line: the record shown
there is the default one, which the query never matched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ATK594PtgVSufvSHa24VVp
The two buckets held disjoint key prefixes and were never addressed
together, so a single BLOB binding costs nothing and halves the
provisioning. Lifecycle rules are per-prefix, so thumbnails/config/
can still expire while thumbnails/default/ and search-index/ do not.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ATK594PtgVSufvSHa24VVp
The hardcoded urls predated the microversion query param, so the fixture
no longer looked like anything uploadThumbnails returns.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ATK594PtgVSufvSHa24VVp
The thumbnail workflow authenticated with an empty session id, so every
warm render 401'd before reaching Onshape; it now carries the requesting
session like the other two workflows do.

Boolean and string inputs read the raw configuration value, which
canonicalization now omits whenever it equals the default, so a
default-true checkbox rendered unchecked. Both fall back to the
parameter default, as the enum input already did.

Also gates the favorite save on a reported canonical configuration,
rebuilds the search index before bumping the version so an immutable
url cannot pin a stale index, and restores includeComputedProperties
on the metadata probe indexing runs per configuration.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ATK594PtgVSufvSHa24VVp
Instance ids are unique for the lifetime of the account, so deriving one
from the configuration meant a single failed render blocked that
configuration forever. Runs now take a generated id and skip a render
that is already stored — checked before calling Onshape and again before
writing, since a render takes minutes. Two concurrent runs for one
configuration are possible and cost a duplicate render at worst.

Element resolution now matches on the microversion as well, so it stays
correct if a tab ever backs more than one insertable.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ATK594PtgVSufvSHa24VVp
Matching the microversion as well was wrong: the stored value is the
current one and the url carries whatever the client last loaded, so any
reload made them diverge and resolution found nothing. The microversion
is a reload marker, not part of the element's identity.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ATK594PtgVSufvSHa24VVp
@AlexKempen
AlexKempen force-pushed the claude/auto-indexing-rebase-9vw1ca branch from b51f684 to f572cea Compare August 18, 2026 23:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants