Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
c8789b3
refactor: split oversized modules into focused ones
claude Aug 20, 2026
b1a40a7
refactor: organize backend and frontend by feature, drop shared/
claude Aug 20, 2026
4d59d9b
refactor: alias frontend imports of the backend contract
claude Aug 20, 2026
93ddd5e
docs: describe the feature layout
claude Aug 20, 2026
9c35cd4
refactor: point a stale comment at the moved module
claude Aug 20, 2026
9d5423d
refactor: consolidate Onshape parsing into a load feature
claude Aug 20, 2026
afb728b
refactor: split auth by role, and reduce app.ts to assembly
claude Aug 20, 2026
ba50b44
refactor: rename POST /api/user-data to /api/settings
claude Aug 20, 2026
e25293d
refactor: express configurability as a boolean, not a duplicated id
claude Aug 20, 2026
cdbc703
refactor: store an element's own part data on the insertable
claude Aug 20, 2026
3f3d5ef
refactor: drop the part_data backfill, matching 0004
claude Aug 21, 2026
3648264
refactor: swap Tabler for Phosphor icons, and untangle the home accor…
claude Aug 21, 2026
7aa07e7
fix: stop flashing the sign-in button, and clipping the toast's action
claude Aug 21, 2026
83cd492
fix: let a toast size to its content instead of wrapping at 440px
claude Aug 21, 2026
fa28b99
refactor: use the query's own isPending, and resolve access at save time
claude Aug 21, 2026
287815b
refactor: standardize route validation, and tighten the editor gate
claude Aug 21, 2026
2ec6176
refactor: clearer contracts, names, and favorite ownership
claude Aug 21, 2026
3f104ab
refactor: one error shape for every failed api response
claude Aug 21, 2026
63b0b79
refactor: address review - errors, theming, toasts, icon colors
claude Aug 21, 2026
5e01977
refactor: name the placement-specific icon sizes for their placement
claude Aug 21, 2026
abe0183
refactor: dissolve lib/utils, and let the menu files fast-refresh
claude Aug 21, 2026
dbd9eaa
Dedupe the restore toast, log requests, and add a quick-insert tip
claude Aug 21, 2026
e873ee5
Quick insert a search hit's configuration, not the element defaults
claude Aug 21, 2026
7a9d82e
Rebuild the navbar as library tabs over a search row
claude Aug 22, 2026
77a1567
Give each library its accent color back
claude Aug 22, 2026
620b6a0
Remove console.log
AlexKempen Aug 22, 2026
6951183
Polish the navbar: brand tile, status badges, distinct rows
claude Aug 22, 2026
43d99f9
Frame the insert menu, and stop the favorite icon shrinking
claude Aug 22, 2026
54b279d
Share one modal chrome, and spell library names out in full
claude Aug 22, 2026
998526b
Square the corners off and tighten the modal card
claude Aug 22, 2026
7ec1689
Stop the preview card double-insetting itself
claude Aug 22, 2026
32fff23
Give every icon-and-text heading one component
claude Aug 22, 2026
aa65ab8
Sit title icons on the text's optical centre
claude Aug 22, 2026
416c385
Balance the modal chrome, and stop refetching job status per tab
claude Aug 22, 2026
ed9a842
Fix the title icon wrapper, and give section headers one height
claude Aug 22, 2026
c6f32c3
Even up the derive button, parameters, and group header
claude Aug 22, 2026
b55647d
Inset the footer's buttons equally, and drop the button's own tuning
claude Aug 22, 2026
7f86d9e
Give the modal header the standard inset, and trim what was overbaked
claude Aug 22, 2026
71a0c43
Reword quick insert tip
AlexKempen Aug 22, 2026
3478a50
Let the header's padding set its height
claude Aug 22, 2026
445f531
Link a part number to the vendor's page for it
claude Aug 22, 2026
6a3d906
Resolve a vendor from the insertable, and show the part in every menu
claude Aug 22, 2026
730508b
Parse a record's vendor at load, where nothing sets one today
claude Aug 22, 2026
8226ba3
Give the title's second line its own leading
claude Aug 23, 2026
9c3ffe9
Drop the external link icon beside the part number
claude Aug 23, 2026
0a119d3
Search REV and TTB, and put the launch icon inside the link
claude Aug 23, 2026
92208ef
Reload thumbnails through the load alone, and read NO_PARTS as an issue
claude Aug 23, 2026
c862756
Keep the userId on the session and tidy part numbers
claude Aug 23, 2026
54906a6
Hide placeholder part numbers instead of showing them
claude Aug 23, 2026
8dc4fe8
Revert "Hide placeholder part numbers instead of showing them"
claude Aug 23, 2026
5d27a1d
Score search records by term so a query can name a configuration
claude Aug 23, 2026
d3496d5
Index part number segments by value, and dress up the filter callout
claude Aug 23, 2026
bc95259
Stop the copy button from shifting the menu title
claude Aug 23, 2026
b3bb346
Identify a part by its number, in the menu and in search results
claude Aug 23, 2026
03842fa
Hide an N/A part number where it is displayed
claude Aug 23, 2026
c1800e7
Trim the comments and tests that had grown past their worth
claude Aug 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,24 @@ doc comment when the signature already says it (e.g. write "returns the access
level, respecting the cache" — not a paragraph re-deriving the caching).
Aggressively delete comments that narrate obvious implementation details.

## Layout

`src/` has two sides, `backend/` (the Worker) and `frontend/` (the SPA). There
is no shared directory: the backend owns the contract, and the frontend imports
it through the `@backend/*` alias. Imports within a side stay relative.

Both sides are organized the same way:

- `features/<feature>/` — everything one feature owns. Backend features hold
`routes.ts` plus their storage, models and DTOs; frontend features hold
`queries.ts` and `components/`.
- `lib/` — cross-cutting plumbing that belongs to no single feature.
- `components/` (frontend only) — UI used by more than one feature.

Anything the frontend imports from a backend feature must be a leaf module —
pure types and functions, no Worker-only imports — or it lands in the client
bundle.

# Cloudflare Workers

STOP. Your knowledge of Cloudflare Workers APIs and limits may be outdated. Always retrieve current documentation before any Workers, KV, R2, D1, Durable Objects, Queues, Vectorize, AI, or Agents SDK task.
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,9 @@ To exercise the signed-in-only UI (favorites, insert button) without a real
Onshape session, set `FORCE_SIGNED_IN=true` in your `.env`. This is a
testing-only escape hatch — it uses a fake user id and Onshape calls it reveals
won't actually work, so leave it unset normally. Combine with
`ACCESS_LEVEL_OVERRIDE=admin` to also show editor/admin controls.
`ACCESS_LEVEL_OVERRIDE=admin` to also show editor/admin controls — editor
routes require a session as well as the access level, so the override alone
does not reach them.

# Troubleshooting

Expand Down
33 changes: 15 additions & 18 deletions docs/GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ In the database, Groups correspond to Documents (or more specifically Versions o

### Path types in the codebase

These are defined in `src/shared/onshape-path.ts`:
These are defined in `src/backend/lib/onshape/path.ts`:

```ts
// Just a document
Expand Down Expand Up @@ -78,10 +78,7 @@ The `apiPath()` function in `src/backend/onshape-api/api-path.ts` assembles thes

```ts
import { apiPath } from "../api-path";
import {
toInstanceApiPath,
toElementApiPath
} from "../../../shared/onshape-path";
import { toInstanceApiPath, toElementApiPath } from "./path";

// Produces: /assemblies/d/{did}/w/{wid}/e/{eid}/features
apiPath("assemblies", elementPath, toElementApiPath, { endRoute: "features" });
Expand All @@ -90,7 +87,7 @@ apiPath("assemblies", elementPath, toElementApiPath, { endRoute: "features" });
apiPath("documents", instancePath, toInstanceApiPath, { endRoute: "elements" });
```

The serializer functions (`toDocumentApiPath`, `toInstanceApiPath`, `toElementApiPath`) are all defined in `src/shared/onshape-path.ts` and convert a path object into its URL segment string.
The serializer functions (`toDocumentApiPath`, `toInstanceApiPath`, `toElementApiPath`) are all defined in `src/backend/lib/onshape/path.ts` and convert a path object into its URL segment string.

### Calling the Onshape API

Expand All @@ -116,7 +113,7 @@ Use this when you need to expose new functionality to the frontend via a new API

### 1. Add the handler to a routes file

Open the relevant file in `src/backend/routes/` (or create a new one if the functionality is in a new area). Each file creates a Hono sub-app and registers handlers on it:
Open the `routes.ts` of the feature that owns the functionality, under `src/backend/features/` (or add a new feature directory if it belongs to none of them). Each `routes.ts` creates a Hono sub-app and registers handlers on it:

```ts
export const myRoutes = getApp();
Expand Down Expand Up @@ -160,9 +157,9 @@ import { myRoutes } from "./routes/my-routes";
app.route("/api", myRoutes);
```

### 3. Define the response type in shared
### 3. Define the response type

If the frontend needs to consume this endpoint, define a TypeScript interface for the response in `src/shared/api-models.ts` so both sides agree on the shape.
If the frontend needs to consume this endpoint, define a TypeScript interface for the response in the feature's `dto.ts` (e.g. `src/backend/features/library/dto.ts`). The backend owns the contract; the frontend imports it through `@backend/features/<feature>/dto`, so both sides agree on the shape.

---

Expand Down Expand Up @@ -230,7 +227,7 @@ The schema is the source of truth for what's stored in D1. Drizzle ORM reads it

### 1. Edit the schema

Open `src/shared/schema.ts`. Tables are defined using Drizzle's SQLite helpers:
Open `src/backend/db/schema.ts`. Tables are defined using Drizzle's SQLite helpers:

```ts
import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";
Expand Down Expand Up @@ -266,7 +263,7 @@ This runs all pending migrations against your local D1 database (used by `npx wr

### 4. Update API response types if needed

If the new data needs to be returned to the frontend, update the relevant interface in `src/shared/api-models.ts` and modify the query in `src/backend/library-data.ts` (if it's part of the main library response) or in the appropriate route handler.
If the new data needs to be returned to the frontend, update the relevant interface in the feature's `dto.ts` and modify the query in `src/backend/features/library/db.ts` (if it's part of the main library response) or in the appropriate route handler.

## Adding a Frontend Route

Expand Down Expand Up @@ -349,20 +346,20 @@ function GroupPage() {

If you just need to fetch data inside an existing component (without creating a new route), follow this pattern.

### 1. Define the query in `queries.ts`
### 1. Define the query in the feature's `queries.ts`

Open `src/frontend/queries.ts` and add a query definition:
Add the key to `src/frontend/lib/query-keys.ts`, then add the query to `src/frontend/features/<feature>/queries.ts`:

```ts
export function getMyDataQuery(someId: string) {
return queryOptions({
queryKey: ["my-data", someId],
queryKey: myDataQueryKey(someId),
queryFn: () => apiGet("/my-thing/" + someId)
});
}
```

Keeping query definitions in `queries.ts` means the same query can be used in multiple components and they'll all share the same cache.
Keeping query definitions in the feature's `queries.ts` means the same query can be used in multiple components and they'll all share the same cache. Keys live in `lib/query-keys.ts` so the refresh flows can invalidate across features.

### 2. Use it in a component

Expand Down Expand Up @@ -426,7 +423,7 @@ function useMyMutation(someId: string) {
}
```

`getQueryUpdater` (from `src/frontend/common/utils.ts`) wraps Immer's `produce()` into a function that React Query's `setQueryData` accepts. Immer allows you to mutate query results directly rather than mutating an original cache value, which is much cleaner for nested data.
`getQueryUpdater` (from `src/frontend/lib/utils.ts`) wraps Immer's `produce()` into a function that React Query's `setQueryData` accepts. Immer allows you to mutate query results directly rather than mutating an original cache value, which is much cleaner for nested data.

**Why cancel queries in `onMutate`?** If a background refetch lands after the optimistic update, it will overwrite the cache with stale data. Canceling outstanding queries for that key prevents this race condition.

Expand Down Expand Up @@ -602,7 +599,7 @@ If rule 2 tempts you to call a hook from a utility function, make the utility fu

A custom hook is just a regular TypeScript function that starts with `use` and calls other hooks inside. You write them to extract repeated stateful logic out of components so it can be shared and tested independently.

Example from this codebase — `useUiState()` in `src/frontend/api-utils/ui-state.ts`:
Example from this codebase — `useUiState()` in `src/frontend/lib/ui-state.ts`:

```tsx
// In ui-state.ts (a .ts file — no JSX, so no .tsx needed)
Expand Down Expand Up @@ -656,7 +653,7 @@ Prefer Mantine component props (`c=`, `bg=`, `p=`, `radius=`) over adding new SC

## The `apiGet` / `apiPost` / `apiDelete` Helpers

These are thin wrappers around `fetch` defined in `src/frontend/api-utils/api.ts`. They:
These are thin wrappers around `fetch` defined in `src/frontend/lib/api-client.ts`. They:

- Automatically prepend `/api` to the path (so you write `"/context-data"` not `"/api/context-data"`)
- Serialize query parameters via `URLSearchParams`
Expand Down
Loading
Loading