pinswap

Project architecture

How a pin becomes a wire: from source document to on-screen schematic.

1 · Browser — Next.js 14 App Router

Pages
Harness grid /, then per-harness schematic, connections, pins, layout, parts. Plus /library, /mapping, /diagram.
Canvas
SchematicCanvas.tsx renders nets and connectors with @xyflow/react. Net highlighting, wire/mating-face toggle.
State
HarnessProvider + SelectionProvider hold the loaded harness, selection, undo/redo.
↓HTTP · JSON

2 · Next.js server — API + repository

API routes
/api/edit/* (add/remove wire, revisions), /api/harness/* (share, export CSV/JSON/xlsx), /api/auth/*.
Repository
postgres-repository.ts — a thin, typed wrapper over the SQL functions. No business logic lives in TS; the schema is the source of truth.
↓postgres driver · SQL

3 · PostgreSQL 16 — hx schema

Query functions
pin_map() · validate() · harness_nets() · cut_list() · bom() · plugin_compare() — the views and rules are computed in SQL, not re-implemented in the app.
Edit functions
add_wire() · remove_wire() · add_claim() · waive() — mutations record a revision, so every change is undoable and auditable.
Provenance
source → device_pin_claim. Every pin cites a document and a reliability. Evidence status (measured / corroborated / single_source / conflict) is derived, never typed by hand.
Library + device
part · part_connector · part_connector_cavity — the physical connectors and faces. device_model · device_connector · device_pin — the ECU/OEM pinouts and their signal_class.
Harness + vehicle
project · harness · device_instance · component · wire · wire_end — the live wiring. vehicle · vehicle_variant · engine — the applications it fits.
↓migrate on start (entrypoint.sh)

4 · Seed data — sql/seed/*.sql

Migrations are ordered by directory
schema/* (run once, never edited) → functions/* (create-or-replace, re-applied when a checksum changes) → seed/* (run once, non-idempotent, each in a transaction). Seed files call the add_* helpers so data stays DRY with the schema. Tracked in hx.schema_migrations by full filename + checksum.

Functional architecture

How the frontend and backend talk, and how login and security are enforced.

A · Request flow — frontend ↔ backend

Read path (server components)
Pages are force-dynamic: on each request the server component calls getRepository(), which runs the hx SQL functions and returns JSON. No build-time prerender hits the database.
Write path (client → API)
The canvas and inspector fetch /api/edit/harness/:id/ops with an ops[] patch and a version. The route calls repository.applyOps() → hx.apply_ops() (or undo/redo). On success the version advances and the response feeds undo/redo state.
Request lifecycle
Browser → Cloudflare (TLS, Web Analytics) → Next.js middleware (write paths only) → API route / server component → repository → PostgreSQL, then JSON back down the same chain.

B · Login & authentication

Sign-in
POST /api/auth/login checks the email/password against a scrypt hash (per-user salt, constant-time timingSafeEqual) and sets a signed session cookie.
Session cookie
hx_session — an HS256 JWT signed with SESSION_SECRET, 30-day expiry, httpOnly + SameSite=Lax + Secure in production. Verified with jose (edge-safe).
Two auth modes
AUTH_MODE=session uses our own cookie; AUTH_MODE=cf-access verifies a Cloudflare Access JWT against the team JWKS. dev (non-prod) is the catalog owner. Unset fails closed.

C · Authorization (who can edit what)

Middleware gate
src/middleware.ts locks every /api/edit/* path in order: read-only (403) → auth (401) → JSON content-type (415) → same-origin check (403). It injects x-user-email for the next step.
Ownership
assertOwned() — the middleware proves someone is authenticated; this proves they own the harness. Catalog harnesses are owned by mikedoubleg and stay read-only to everyone else.
Read-only shares
share.ts signs a JWT {h: harnessId} (HS256, SHARE_SECRET, 30d). Sharing is read-only by construction — the token grants no edit capability.

D · Live updates & deployment

Live edits (SSE)
One shared Postgres LISTEN on the hx_harness channel. apply_ops, undo, redo and restore call pg_notify; /api/harness/:id/events fans each event to open Server-Sent-Event streams so every viewer sees edits live.
Deployment
docker-compose.yml runs db (postgres:16) + app (Next.js, host port 3100). entrypoint.sh runs npm run migrate then npm start, so the schema and seeds are applied on every start, tracked by filename + checksum. Cloudflare sits in front (APP_ORIGIN https://pinswap.app).

One principle runs through every layer: no guessed data. A pin with no source has no claim and surfaces as a DATASET_GAP finding rather than a fabricated value. A confidence label was replaced by per-pin evidence status.

Built by Michael Gnankou · LinkedIn