Skip to content

Load on new versions via webhooks, render thumbnails from a per-version workspace, and document the architecture - #60

Merged
AlexKempen merged 89 commits into
certfrom
claude/zen-meitner-mk50cb
Sep 28, 2026
Merged

AlexKempen merged 89 commits into
certfrom
claude/zen-meitner-mk50cb

Conversation

@AlexKempen

@AlexKempen AlexKempen commented Sep 23, 2026 •

Copy link
Copy Markdown
Collaborator

This branch reworks most of the app: how documents load, how thumbnails render, how configurations are stored, auth and access, the stores behind the SPA, and the docs. docs/architecture/ now describes each area as it stands. It is the best place to review against.

Loading and webhooks (loading.md)

  • Loading:
    • One document loads at a time.
    • A group's running load is replaced rather than queued behind.
    • Search is rebuilt with every load.
    • Groups flagged mid-load are rerun.
  • Webhooks: each new Onshape version triggers a reload through a webhook.
    • The app registers the webhook on the owner's behalf (no button), pings it when it is created, and re-registers it if Onshape drops it.
    • Webhook urls come from APP_URL.
  • Held versions: a new version is held for an admin's approval. It is badged in the meantime and loads by itself after two days.
  • Background loads: these run as a remembered session (background-sessions.ts), tried in order: the owner, then team admins, then editors, then the dev access-level override.
  • Load thumbnails: retried at 30s, 1m, 2m, 4m, 4m, 4m before THUMBNAIL_FAILED.
  • Admin team: each library has an admin team, stored on its row. A change to the team re-asks users' access levels.
  • Migrations: folded into the single migration drizzle/0005_library_loading.sql, on top of cert's 0004_configuration_instance_key.

Thumbnails (thumbnails.md)

  • Thumbnail workspace: thumbnails are read from an app-owned workspace branched off each loaded version, named FRCDesignApp Thumbnails (DO NOT EDIT).
    • A workspace is deleted once no group in any library still names it.
  • Configuration renders:
    • They start only through POST /api/render-thumbnail/insertable/:insertableId, and return a RenderStatus of rendering or no-part.
    • They run in RenderThumbnailWorkflow by predictable thumbnail id, and are pushed to clients when stored.
    • Serving (GET /api/thumbnail/...) never starts a render.
  • Stale thumbnails: deleted per document after each load, instead of on a cron.
  • Reload thumbnail: is scoped to a library.

Configurations (configurations.md)

  • Two forms: a configuration exists only as a Selection (values as they were entered) or a ConfigurationKey (which only names thumbnails). Both are built in selection.ts.
  • Parameter roles:
    • Parameters have stored roles.
    • Derivation variables are filled in the insert menu only.
    • Assemblies are indexed too, and the app decides which parameters are indexed.
  • Indexing: gets its own settings section, with a switch per parameter.
  • Quantities: shown in their own units. Workspace units are cached in KV behind a transient webhook.
  • Favorites: the insert menu saves and resets a favorite's configuration.

Auth and access (auth.md)

  • Library-gated routes: every one names the library in its path, so the guard checks that library. libraryOf is gone.
  • OAuth: sends an explicit redirect_uri built from APP_URL.
  • Auth cookies: each has its own module.
  • Background work: tracking runs in the background.
  • Removed: SESSION_SECRET and VERBOSE_LOGGING.

Frontend

  • UI state: held in Zustand stores (ui-state, Onshape launch), persisted with Zustand's storage. Synced settings are gone.
  • Live updates: push (a Durable Object WebSocket) replaces polling for jobs, library versions, thumbnails and access.
  • Layout: the configuration panel is laid out on a grid.
  • Component defaults: move into the theme.
  • Section headers: sticky.
  • Hover cards: open on tap.
  • Company domain: Onshape links open on the caller's company domain.
  • Build checks: each has a title. A few carry a short description behind an info icon.
  • Search: reads terms as spans. Matching and highlighting are simplified (search.md).
  • Analytics dashboard: headline tiles total over the selected range (analytics.md).

Dev setup and tooling

  • Dev server: served at dev.frcdesign.org through a shared named Cloudflare tunnel (npm run tunnel, token in .tunnel-token). This replaces mkcert.
  • Tests:
    • Only tests that need bindings (*.worker.test.ts) run in the Workers pool. The rest run in Node.
    • Component tests run in jsdom.
  • Docs:
    • docs/architecture/ has one document per area.
    • AGENTS.md sets the rules for keeping them current.
    • npm run check:docs, run in CI, catches stale paths.

Testing

npm test (Node, jsdom and Workers projects, 934 tests), npx tsc -b, lint and npm run check:docs pass on the merged head. Webhook-triggered loads were exercised against Onshape through the dev tunnel.

🤖 Generated with Claude Code

https://claude.ai/code/session_01HDekeqMAagMXf7o9zF2SxJ

Only a test that needs D1, R2, KV or a workflow pays for a Workers runtime
and a migrated database; it says so with a .worker.test.ts name. The suite
drops from about 88s to 53s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDekeqMAagMXf7o9zF2SxJ
A quantity now stays the expression that was typed, (2 + 3) in, through the
url, favorites and the insert, and reaches Onshape that way instead of as a
canonical base-unit value. Keys are derived from a selection only to address
its thumbnail: stored records carry their enumerated values, build issues the
values they blame, and the Onshape endpoints take a configuration map.

Also fixes:
- a favorite's Open document and Copy link dropped its configuration
- Restore after cancelling reopened the configuration the menu opened with
- favorites never showed the element's own part number, their records
  missing the insertable's part data
- the insert menu header fell back to the default part number when the
  matching record had been folded away as a search duplicate

Old rows upgrade on read (configurations/legacy.ts). The search index moves
to a versioned key and is rebuilt on a miss, and immutable API urls carry a
response-shape suffix so cached responses in the old shape are not reused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDekeqMAagMXf7o9zF2SxJ
The per-user Durable Object queue, with its preemption, poll schedule and
invalid-key memo, is gone. On a miss the route resolves Onshape's thumbnail
id once and starts a RenderThumbnailWorkflow named by a hash of what it
renders, handing it that id and both R2 keys; later polls find the same
instance and spend nothing. The workflow only asks for the bytes until they
land and stores them. A configuration Onshape cannot resolve answers 422 at
once, before any workflow starts.

wrangler.jsonc deletes the ThumbnailRenderer class in a v2 migration, which
discards its stored queue.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDekeqMAagMXf7o9zF2SxJ
On a narrow panel a long label squeezed its Select to a sliver, and the
dropdown, as wide as that sliver, wrapped every option onto several lines.
Labels now share a capped column, so controls line up and a long label wraps
instead; a dropdown is at least as wide as its input and at most as wide as
the screen. The get-app callout's button wraps under its text rather than
running off the edge, and clicking into a quantity keeps the whole
expression selected in browsers whose mouseup would drop it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDekeqMAagMXf7o9zF2SxJ
CSS values that were passed around as inline style objects (borders, the
frame background, flex-shrink, the title icon nudge, the modal frame) are
classes now: shared ones in lib/styles.module.css, one component's in its
own module. Tooltip, HoverCard and Card take their repeated props from the
theme, so a call site names only what differs; Menu and Select stop
restating Mantine's own portal default. style-constants.ts keeps only values
handed to Mantine props.

Also drops a wrong comment in AppMenu: Mantine fills in flip and shift
itself when middlewares names only size.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDekeqMAagMXf7o9zF2SxJ
A jsdom project runs *.test.tsx with Testing Library. The first tests drive
the configuration panel (a typed expression is kept and shown evaluated,
conditions show and hide rows, the record follows the selection) and the
document links a favorite's menu builds.

They caught a crash: an angle typed into a length box threw out of the range
check instead of being reported. The parser now rejects a result whose kind
is not the quantity's, with a message saying which kind it wanted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDekeqMAagMXf7o9zF2SxJ
…e trip

The probe tests needed the Workers runtime only because the module they test
also held its workflow-step variant; that lives with its one caller now, and
the limiter in lib/, so both test files run in Node. resetDb, which runs
before nearly every worker test, deletes in one batch instead of sixteen
round trips.

Adds end-to-end checks that an insert hands Onshape the expression that was
typed, in a derived feature and in an assembly's configuration.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDekeqMAagMXf7o9zF2SxJ
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDekeqMAagMXf7o9zF2SxJ
Onshape sometimes never renders an element's thumbnail in a version, and the
document's own workspace, which the load fell back to, drifts from the
version the library shows. A load now finds or branches a workspace off the
version (named by it, so a retried step or forced reload reuses it), reads
every thumbnail from there with no fallback, and stores its id on the group
beside the version. Configured renders and the on-demand reload read from the
same branch; a group no load has branched yet falls back to the version for
renders, and a reload branches one.

A fresh branch takes minutes to render, so thumbnail steps retry for about a
quarter of an hour, under a limiter of their own so the wait never holds a
probe's slot. Once a group moves to a new version, the branches of older
versions are deleted, best effort.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDekeqMAagMXf7o9zF2SxJ
Every branch now shares that name, so the version it came from is recorded
in its description, which is what finding it again matches. Also tightens
this change's comments: the thumbnail limiter's was wrong (a waiting step
holds its slot), the branch sharing the version's microversion is marked as
an assumption, and comments restating the code are gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDekeqMAagMXf7o9zF2SxJ
A load recomputes every build issue, so an issue that named its offending
configuration by key only needs to last until the next one. It still reads
and renders; it just has no configuration to link to until then.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDekeqMAagMXf7o9zF2SxJ
Onshape's exclude-from-properties flag no longer decides what indexing varies.
A parameter is indexed unless its name marks it as never worth varying (a
derivation variable, a color or its R, G and B channels together, a
tessellation setting) or an admin excluded it with the new per-parameter
switch on the build card. Exclusions are user-owned, kept across reloads, and
part studios only: Onshape lets no assembly exclude parameters from its
properties either, so an assembly indexes every one it can.

Assemblies now index like part studios: automatically under the threshold,
and past it once an admin enables indexing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDekeqMAagMXf7o9zF2SxJ
Parameters recognized by name (derivation variable, color and its R/G/B
channels, tessellation) now carry a ParameterRole, shown with an icon in the
build status tooltip. A text derivation variable is read-only in the insert
menu and filled with a fresh UUID on every derive, since Onshape refuses to
derive the same part and configuration twice. It is left out of thumbnail
keys, analytics values and saved favorites.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDekeqMAagMXf7o9zF2SxJ
The role and the fill-in behavior now come from one check, so a
non-text parameter named for derivation is an ordinary parameter
everywhere rather than never indexed yet still editable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDekeqMAagMXf7o9zF2SxJ
Links are built on the launch's server origin, so a company session opens
frcdesign.onshape.com rather than cad.onshape.com. Only an https onshape.com
origin is accepted, since the launch is a url anyone can write.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDekeqMAagMXf7o9zF2SxJ
A verbose row's thumbnail and status badge gave up width to its title, so
they drew smaller than the next row's, most visibly when zoomed in. Only
the text shrinks now.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDekeqMAagMXf7o9zF2SxJ
Each header sticks below the ones above it, so favorites and the library
stay in reach however far the list is scrolled.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDekeqMAagMXf7o9zF2SxJ
Its options (skipDocumentD, endRoute, endId, featureId) obscured what each
call site asked for. addAssemblyFeature loses the update-by-id parameter
nobody passed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDekeqMAagMXf7o9zF2SxJ
parse.ts exports only what routes use, and its tests go through those.
readThumbnailUrls had no callers and is gone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDekeqMAagMXf7o9zF2SxJ
Headers now stick within their own section, so the next one pushes the
last off rather than stacking beneath it, and a section showing a zero
state does not stick at all: a header stuck over one only half-covered
it on the way out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDekeqMAagMXf7o9zF2SxJ
Mantine's HoverCard opens only on the mouse events a tap emulates, and
lets the tap through, so on a phone the same tap opened the build-status
card and the row's insert menu. AppHoverCard opens on hover and on a
click or tap, which pins it; the click, the card's own clicks and the
tap that dismisses it (which lands on an invisible overlay) are kept
from the row. It replaces every HoverCard: build status, thumbnails and
the insert location. Tooltips now show on touch as well.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDekeqMAagMXf7o9zF2SxJ
Adds an owner access level above admin, granted to the Onshape user
OWNER_USER_ID names; the owner's latest session is kept for work the
server starts on its own. The owner registers a company webhook from
settings. A new version of a library document reloads just that
document's groups under the owner's session, queued behind any reload
already running; a member added to or removed from the admin team
clears every cached access level. Deliveries are checked against a
token minted at registration.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDekeqMAagMXf7o9zF2SxJ
A LiveUpdates Durable Object holds every open client's WebSocket,
hibernating, tagged by the library it shows. Jobs starting and
finishing, a background load bumping a library's version, a
configuration's render landing and an admin team change are pushed
through it. Clients apply them to the query cache: job status is no
longer polled, and a render waits for its push instead of asking every
two seconds. While the socket is down, both fall back to polling as
before, and a reconnect refreshes the library for what it missed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDekeqMAagMXf7o9zF2SxJ
Whenever the owner's access level is resolved, the registration on
record is checked in the background and replaced if Onshape no longer
has it; an unregister notification forgets it, so the owner's next
visit registers anew. The register button, its route and the owner-only
guard it needed are gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDekeqMAagMXf7o9zF2SxJ
Loads:
- LoadDocumentWorkflow replaces LoadLibraryWorkflow and AddGroupWorkflow.
  Adding a document writes its shell group and asks for its load; the
  owner's "reload everything" asks for one per group.
- load_jobs in D1 keeps one load per group running; one asked for
  meanwhile is marked on the row and started as the running one ends.
  The last load in a library rebuilds its search index.
- Each load registers its document's version webhook (isTransient
  false), removed with the document's last group. A new version reloads
  that document's groups under the owner's session.
- Thumbnail reconciliation moves to a daily cron.

Access:
- The owner sets each library's admin team. Its members are stored in
  admin_team_members and pulled again on the team's webhook, so access
  is a per-library lookup; ADMIN_TEAM and the KV access cache are gone.
- A membership sync bumps the library version, which is how clients
  learn their access changed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDekeqMAagMXf7o9zF2SxJ
Each load that wrote to its group now rebuilds its library's search index
before bumping the version, rather than leaving that to the library's
last load, so every load stands alone. Reload everything starts every
load at once and returns. OWNER_USER_ID is set in every environment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDekeqMAagMXf7o9zF2SxJ
- Roles are recognized once as a document is loaded and stored on the
  parameter; indexing, keys and the UI read parameter.role instead of
  matching names again. The name folding lives with the one detector.
- legacy.ts is gone: a force reload rewrites records and parameters in
  the current shape. A favorite saved in base units stays valid, shown
  in meters until saved again.
- searchRecordsOf(row) replaces toRecords then toSearchRecords at every
  call site, and the search index is built from those records.
- UnitInfo's fields are required. useUnitInfo resolves the document's
  units, or a placeholder (inches, degrees) outside a document or when
  they fail to load, so nothing downstream merges defaults.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDekeqMAagMXf7o9zF2SxJ
…eir own units

- Job status is the set of groups loading, pushed as it changes; a
  group's row, its parts and its card spin only while it loads. The
  navbar still shows anything loading in the library.
- No polling fallback: job status comes from pushes alone, and a render
  waits for its push, asking once more at its deadline. A reconnect
  still refreshes the library for what it missed.
- Each quantity box reads the document's units itself, from the one
  cached query, and shows its own unit until they arrive or outside a
  document, so the panel no longer waits on them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDekeqMAagMXf7o9zF2SxJ
Renders store both sizes in parallel, so RenderSource no longer orders
anything: insertableId alone asks for a render. The render workflow's
instance is named after Onshape's thumbnail id rather than a hash, and a
configuration Onshape has no part for throws a handled 422 from
requestRender instead of threading an outcome string through the route.

AGENTS.md now prefers undefined to null, with null kept at boundaries and
where it means "clear"; a pass converts the internal nulls. The settings
defaults shrink to DEFAULT_THEME, the only one read.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDekeqMAagMXf7o9zF2SxJ
AGENTS.md now defaults to no comment, one or two lines when there is one,
no history or alternatives not taken, and no hedging where the answer can
be checked. Comments across the codebase are cut to match, and stale ones
corrected.

AppHoverCard is Mantine's HoverCard where the device can hover and a
Popover the target opens on a tap where it cannot, replacing the hand-rolled
timers and pin state. useCloseHoverCard goes with it.

Also:
- The access-level KV cache nothing wrote is removed.
- InsertOut.featureId is optional, and a part-studio derive no longer
  reports a Fasten mate it didn't make.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDekeqMAagMXf7o9zF2SxJ
…iguration

Onshape answers a thumbnail it hasn't rendered with a stand-in unless told
not to, and the by-id endpoint renders used could answer with another
configuration's image; either way the default was stored, immutably, under
the configuration's key. Renders now fetch the documented configured
endpoint on the thumbnail workspace with skipDefaultImage, rejectEmpty and
requireConfigMatch, and element thumbnails ask for rendered images only. A
forced reload also drops a document's configuration renders, clearing any
bad ones.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDekeqMAagMXf7o9zF2SxJ
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDekeqMAagMXf7o9zF2SxJ
… included

The hover card's Indexing section holds the enable switch, the indexable
configuration count and a labeled switch for each enum or boolean
parameter. Assemblies are enumerated through the same probe as part
studios, so their parameters can be excluded too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDekeqMAagMXf7o9zF2SxJ
Resolves the id from the group's thumbnail workspace and fetches it from
the by-id thumbnail endpoint, with no query flags; element thumbnails
drop them too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDekeqMAagMXf7o9zF2SxJ
A group without a thumbnail workspace starts no render until its next load
branches one, rather than falling back to the version.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDekeqMAagMXf7o9zF2SxJ
… seconds

registered_at, last_delivery_at and last_event were missing from databases
that applied 0004 before they were folded in, which broke registration;
Onshape's own webhook info and the logs cover what they recorded.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDekeqMAagMXf7o9zF2SxJ
Restores the configured render's 5-second polling.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDekeqMAagMXf7o9zF2SxJ
…key's shape suffix

A local origin registered a webhook Onshape cancelled at once, so every load
registered another. A new webhook is pinged, so its delivery in the logs shows
Onshape can reach it. The ?v= cache key is the library version alone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDekeqMAagMXf7o9zF2SxJ
A tunnel gives the dev server a fixed public https hostname, so the Onshape
OAuth app's urls are set once and webhooks can reach it. DEV_HOSTNAME lets
Vite accept requests for it; npm run tunnel runs it beside npm run dev.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDekeqMAagMXf7o9zF2SxJ
One dashboard-managed tunnel serves the dev server at a fixed hostname, so
developers need its token, not access to the domain.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDekeqMAagMXf7o9zF2SxJ
…nnel token

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDekeqMAagMXf7o9zF2SxJ
The post-registration ping, the localhost skip and the per-registration and
per-delivery logs were for tracking down unreachable webhooks. Self-healing
stays, with a warning when it re-registers and one for unknown tokens.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDekeqMAagMXf7o9zF2SxJ
The access-level override answered before the admin's session was kept, and
an overridden admin is on no team, so a load nobody is signed in behind found
no session. The override now keeps it under a reserved id that the lookup
tries last.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDekeqMAagMXf7o9zF2SxJ
Titles state the problem alone, in one style; descriptions say why it matters
and how to fix it, naming the controls that do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDekeqMAagMXf7o9zF2SxJ
…keeping them current

docs/architecture/ documents thumbnails, configurations, loading, favorites
and auth: flows, storage, invariants, failure modes and decisions. AGENTS.md
says when a change must update them, and npm run check:docs, run in CI, fails
on a doc naming a file that no longer exists. REFERENCE.md now links to them
instead of repeating them, and the stale paths the check found in GUIDE.md and
REFERENCE.md are fixed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDekeqMAagMXf7o9zF2SxJ
Element and thumbnail editing routes take the library in the path, so the
editor and admin middlewares always check the path's library and each
handler scopes its query to it. The owner check always asks about the
default library, since the owner's access is the same in all.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDekeqMAagMXf7o9zF2SxJ
…default inline

Onshape takes a repeated derive, so the server no longer refills derivation
variables; the menu gives one still at its default a unique value. The one
case needing a non-empty configuration, a part studio on its defaults, now
names its first parameter directly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDekeqMAagMXf7o9zF2SxJ
…clude nothing yet

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDekeqMAagMXf7o9zF2SxJ
…rivation filling

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDekeqMAagMXf7o9zF2SxJ
Each environment declares the url it is served from. The authorize request
names its callback there, webhooks point there rather than at whichever
request happened to start the load, so loads no longer carry an origin, and
the dev server accepts its host. DEV_HOSTNAME is gone; APP_URL in .env
overrides the dev value.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDekeqMAagMXf7o9zF2SxJ
…thumbnails at 30s-4m

The stored thumbnail route only serves; a client that misses a configuration
and can wait asks POST /api/render-thumbnail/insertable/:id, which answers
rendering or no-part, rather than an optional query parameter on the image
url starting the render. A render is keyed by the insertable's current
microversion. Load thumbnails retry after 30 s, 1, 2, 4, 4 and 4 minutes.

Docs: insertable thumbnails, why stale workspaces are deleted after the save,
the session each load uses, and worked examples of how search reads and
scores a query.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDekeqMAagMXf7o9zF2SxJ
…s' sessions serve

Any admin team member's session can now carry a load nobody is signed in
behind: the owner's first, then team admins', then editors', then the dev
override's. Sessions saved under the old admin-session prefix are left to
expire; each member's is saved again the next time they open the app.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDekeqMAagMXf7o9zF2SxJ
…toasts, pushes, caching

A new platform.md gathers what every feature shares, and loading and
thumbnails gain per-step tables of the Onshape calls made and the retry policy
each runs under. The query client's comment now matches its two retries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDekeqMAagMXf7o9zF2SxJ
@AlexKempen AlexKempen changed the title Run tests without bindings in Node Load on new versions via webhooks, render thumbnails from a per-version workspace, and document the architecture Sep 28, 2026
A group in another library can stay on an older version, held for approval,
and still read renders from that version's workspace; cleanup deleted it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDekeqMAagMXf7o9zF2SxJ
cert's 0004_configuration_instance_key takes 0004, so this branch's migration
is regenerated on top of it as 0005_library_loading, with the same statements.
Tracking keeps running in the background and passes the part's parameters to
the rollups.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDekeqMAagMXf7o9zF2SxJ
@AlexKempen
AlexKempen merged commit e5977e5 into cert Sep 28, 2026
1 check passed
@AlexKempen
AlexKempen deleted the claude/zen-meitner-mk50cb branch September 29, 2026 03:13

This branch was successfully deployed

1 active deployment
cert — e5977e5a Deployed Sep 28, 2026 by AlexKempen via deploy-cert #113
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