feat/openmerch-sdk · last
update
2026-07-11, print technique is now a saved design choice — picker
shipped, gates 5/5
@openmerch/*), building
on the existing types package. Tilpas stays the consumer brand.
Step 4 of print realism is live. The technique a design is meant for
(DTG, DTF, embroidery) is no longer just a render setting — it lives in
the design document itself. The placement dock grew a technique picker
that only offers what the garment zone and colorway physically allow;
picking embroidery re-renders the print as stitches on the spot, marks
the document dirty, saves, and survives reload, undo and redo like any
other placement edit. If a saved choice later becomes unavailable (say
the colorway flips dark and excludes sublimation) the document keeps
the user's intent untouched and the UI shows the resolved fallback
with a note — nothing is silently rewritten. Export now downloads a
versioned .composition.json alongside the PNG carrying
the mm placement frame, technique and print-rect — the manufacturing
contract, no renderer internals. The document format bumped to v3 with
a hard rule for the future: a document written by a newer app version
shows "needs a newer app version" and disables saving everywhere
(including the proof route), and the save path is now a single locked
transaction so a stale open tab can never overwrite newer work. Two
adversarial reviews drove eight fixes before commit — among them that
save race, a proof loader that quietly degraded instead of failing,
and a render-path crash for documents carrying a technique the zone
never declared. Gates: 513 core tests, full golden E2E 5/5 (the
placement gate now also proves picker persistence and both export
downloads), and live visual QA on the twin.
Step 3 of print realism is live. Committed prints now render through a real ink model instead of a flat decal: linear-light compositing with a white underbase on dark garments (visible as a physically plausible halo on jet black), per-technique film coverage and roughness, embroidery as analytic 0.4mm stitch rows whose thread direction and specular follow the artwork rotation, and weave that continues under thin ink instead of stopping at the print edge. A 36-shot evidence matrix (5 techniques × light/dark × 4 camera angles) was captured through the production preview page and inspected shot by shot — zero blocking defects; measured pixels match the spec equations by hand. The heavier material compile exposed a hidden timing assumption in the placement E2E gate (it probed before the twin was drag-ready and the camera orbited away) — the gate now waits on the viewer's real ready signal, and the full golden suite is 5/5 again. The tank's receiver-overlay art path was restored and smoke-verified live.
The print-process realism spec survived two adversarial reviews plus an
independent verification pass and is now binding
(docs/specs/openmerch-print-process-realism.md). Steps 1–2
of six are shipped. Color foundation: OKLCH utilities with branded
encoded/linear types, the faithful Khronos PBR Neutral curve with
an analytic inverse, and one shared lightness source — the renderer,
colorways, and stage selection all classify dark/light garments through
the same OKLCH-L thresholds now (the old loader disagreed with the app
about forest and wash-slate). A live emissive color-checker probe
renders 16 swatches through the real WebGPU pipeline and round-trips
within deltaE 0.0014 (gate: 0.02). Technique contract v2: every
print technique (DTG, screen/plastisol, DTF, embroidery, sublimation)
is a measured, provenance-commented variant — cured film height in mm,
weave show-through, ink roughness, edge profile, underbase behavior —
and a shared resolver picks legal techniques per zone with real
garment-darkness context (sublimation is excluded on darks). A
characterization test pins the render output byte-identical until the
step-3 ink core swaps in; a Proxy-based consumption test proves no
contract field is decorative. Step 3 (the physically honest ink render
path: linear underbase compositing, physical stitch pitch, embroidery
anisotropy with a real thread-direction field, weave continuity across
the print edge) is implementing now.
All seven steps landed. Users drag, resize, and rotate the committed
print directly on the 3D garment with a live mm HUD; the HUD, the
WebGPU decal, and the manufacturing export all derive from one
panel-space mm frame (derivePanelSpaceExportFrame +
compileAreaPlacement), so what you place is what gets
printed — parity is asserted by unit fixtures at 0°/30°/90° and
non-uniform scales, and out-of-raster placements are refused before
Canvas can silently crop. Committed decals now project with true
rotation (the old axis-aligned bounds projection stretched rotated
art). QA evidence: a new golden gate
(openmerch-placement-parity.test.ts) drags on the twin,
reads the dock mm, saves, reloads, and requires exact restore; the
placement-preview benchmark holds p95 0.5ms / max 0.6ms across 120
frames (4ms budget). The pass also root-caused the flaky Gate 1: the
save indicator's exit animation kept the stale "Saved" label visible
for ~200ms after an edit, so the E2E's text poll reloaded before the
debounced autosave fired and the back-area edit was lost. Save gating
now asserts the raw machine via data-save-status — Gate 1
went 5/5 after three consecutive suite failures before the fix.
The ~18k-LOC WebGPU garment twin now lives in
@openmerch/garment-3d: viewer, loader, surface picker
(panel-space mm), print-surface contracts, technique appearance, and
the product-neutral color pipeline. The licensing boundary holds —
Printify-derived data (asset URLs, receiver-surface registry, print
areas, fabric textures) stays in next-app and is injected through four
required loader dependencies on the viewer; a boundary scan in the
package typecheck keeps it that way.
packages/next-app/src/components/3d/webgpu is deleted and
all consumers (/customize, /garment-preview, /design-studio, studio
mockup scenes) import the package. Verified: package tests 52/52,
scoped app suites 36/36, typecheck + boundary scans clean, golden gates
4/4, and hands-on browser QA of all four surfaces with zero page
errors — including the authed studio in both 3D modes ("On the item"
and the "In use" live scene). Along the way the pre-push preflight got
a real fix: a lint chunk containing only biome-ignored markdown files
no longer aborts the push. Next: direct-on-garment mm editing on top
of the extracted surface picker.
Phase 8 shipped in five commits. 9e2baba368 finishes the
interrupted ColorField repair (hex input keeps a focused draft; 3-digit
shorthand expands only on blur/Enter — the studio baseline defect — with
7 regression tests). 742137992 lands the golden E2E merge
gates (save→reload parity across both print areas, publish gating, tee +
cap add-to-cart with metadata) and deletes the five Yjs-era Playwright
suites; 4/4 green on three consecutive runs.
4ea5f9f214 fixes three Major /customize baseline defects
(hardcoded “tee” CTA, order-summary sliver, artwork/currency honesty on
the cart line). 7aaef82af2 fixes a live studio regression
the QA pass caught: fill edits updated the panel but never the canvas,
because the resolver didn't know EditorProvider-mounted engines —
@openmerch/react now exports
useOptionalEngine and the resolver checks it. Final-QA
evidence (30 shots + defect-disposition matrix vs the 2026-07-05
baseline) lives in
harness/evidence/openmerch-final-qa/REPORT.md; SDK docs
landed as README.md in @openmerch/core and @openmerch/react. Every
Major+ baseline defect in scope is fixed or architecturally eliminated;
open remainder is cosmetic polish + the env-lane studio org-drift.
Three commits: 28c4ecb892 rewires the bare
useCanvas hook through the canvas-service registry (it read
a provider no production surface mounts, so shadow/matting/style
controls silently no-oped — shadow toggle now verified live on /design);
d526f3c291 drops the dead segmentation hook and the
CanvasDO-era JWT draft scope; 74ada30b38 deletes 147
orphaned files (29.3k lines): old agent chat panels, fabric-canvas
services, mobile tree, template gallery, layers panels, 46
components/editor orphans, 27 ai-elements files, dead canvas hooks and
design config. The set was cross-verified by an independent import-graph
audit and a clean-config knip scan, and A/B-tested: the full vitest
suite is bit-identical with and without the deleted files (56
pre-existing environment failures, logged for final QA). All three
surfaces smoke clean. Deferred: ~160 unused files outside the editor
scope (landing pages, explore, ecom, social, dev tools) — dead but not
editor legacy; separate cleanup decision.
Commit 94a12d6f62. The buyer studio's workplane dropped its
bespoke snapshot-array undo/redo and now mounts a per-zone
CanvasService: object add/delete/duplicate, z-reorder, clear-all, and
history all go through the engine managers, with
history:update driving both the button state and the
debounced commit back to the garment (that last part covers
undo-to-empty, where no object events fire). The port surfaced two
engine-level findings. One in the app: fabric v6 IText measures ~2x too
wide before it's attached to a canvas, so computing a centered position
up front mis-placed text — placement now adds first, then centers. One
real bug in core: history restore passed a completion callback as
loadFromJSON's second argument, which fabric v6 treats as a
per-object reviver — the final render never ran, leaving the visible
canvas stale after every undo/redo (and permanently blank on
undo-to-empty). Completion is the returned promise; fixed at the source
with tests moved to the promise contract. Live QA on :3005, zero page
errors throughout: add text/shape, ink recolor, font swap, duplicate,
reorder, delete, clear-all, undo/redo round-trips (both workplane and
garment texture), Front↔Back zone switching with design restore, and the
Done handoff — the Art panel picks up the studio artwork with placement
controls live.
Commit cb30fe6eb2 deletes multiplayer end to end. next-app
lost src/sync (orchestrator, yjs adapter, awareness,
predictive sync, telemetry), the collaboration components/hooks, the
proof-snapshot JWT plumbing, the /api/canvasdo routes, and
the whole orchestrator/awareness/collaborator slice of the editor store
— undo/redo and the history timeline now run purely on the SDK
CanvasService registry. shared dropped its yjs mapping modules. In
worker-editor the 1,800-line CanvasDOYjs durable object is deleted; the
MCP session/actions API it secretly hosted was re-homed verbatim into a
dedicated McpSessionStore DO (wrangler migration
v8-mcp-session-store), and jwt-auth lost its proof-scope
and DO-name path sniffing. yjs, y-protocols,
y-websocket, and lib0 are out of every
package.json. Verified live: worker boots, MCP session
create→action→readback round-trips on :5173, /design renders and edits
with zero console errors on :3005. next-app + shared + worker typechecks
clean; 155/155 worker tests and 25/25 touched next-app tests green,
including new McpSessionStore coverage.
The studio surface is the first consumer of the full SDK stack:
@openmerch/react is real (EditorProvider + hooks, its own
boundary rules and tests), and studio's save path now flows through the
DocumentController — DesignDocument in/out, a rolling
design_versions row per draft, thumbnails to Cloudflare
Images, old payload shapes folded forward on read with their writer
deleted. Live parity gate caught two real engine bugs before sign-off:
the mount baseline marked every freshly opened document dirty (phantom
autosave on open), and undo was off-by-one — the first click restored
the state you were already in. Both fixed at the source: history is now
an explicit checkpoint model where mount re-baselines and a lone
baseline is not undoable. Verified in the browser: open→idle,
edit→Unsaved changes→Saving…→Saved, reload persists the design with
undo/redo correctly disabled, single-click undo/redo round-trips. 497
core + 3 react tests green.
The whole canvas engine — Fabric object model, CanvasService with all
its managers, event bridge, snapshot/export, guides — moved out of the
app into @openmerch/core. ~40k lines, originals deleted,
168 app files rewritten to import the SDK, Tilpas-branded engine names
neutralized. The engine's last app couplings (zustand stores, the dead
multiplayer seam, a server action) were severed first in a separate
surgical pass. Verified end to end: core's boundary check + 483 unit
tests green, app typecheck green, and a live browser run — editor boots,
draft creates, a rectangle lands on the canvas and in the layer list
with zero page errors (screenshot below). Test debt went down, not up:
26 previously-failing fabric tests now pass under core's test setup.
First SDK code landed. @openmerch/core now holds the
versioned document schema (one document owning N print-area subtrees),
the save-state machine where "Saved" can only ever mean an acknowledged
write (unit-tested), typed redacted error envelopes, and all five
provider seams: persistence, asset storage, AI image ops, fonts, POD.
Import boundaries are enforced in CI from the first commit — core
physically cannot import React, Next, or app code. garment-ir is renamed
to @openmerch/garment-ir. Now extracting the ~40k-line
canvas engine out of the app: a surgical pass is severing its store/sync
couplings in place, then the wholesale move into core.
Adversarial review done: an independent gpt-5.5 pass produced 20 evidence-backed findings; all folded into the spec. Biggest upgrades: a save-state machine where "Saved" only ever means an acknowledged write, versioned document schema with migrations, full undo/history semantics (batching, branch-aware, selection excluded), typed + redacted error envelopes on every provider seam, and a hard ordering rule — the SDK save path must prove save→reload parity before Yjs gets deleted. Now scaffolding @openmerch/* and the headless core.
All three surfaces baselined with reports + ~70 screenshots (committed +
pushed). Headline finds:
/design autosave is silently broken — saves ride the
Yjs websocket, the client falls back to an anonymous token before the
user session hydrates, and the worker rejects every connection; the UI
shows "Saving…" forever and a reload loses your edits. Strongest
possible validation of the "delete Yjs, save through an explicit
persistence adapter" decision. Also root-caused the editor boot hang:
worker-editor was missing its Printify API key (env drift — fixed, boot
passes now). /customize has a squeezed order-summary line + hardcoded
"tee" CTA; studio's hex input mangles 3-digit colors; /design's crop
inputs render garbled digits. Full defect tables live in
harness/evidence/openmerch-baseline/.
Found your pnpm dev next-app had died (port 3000 is your
MedicalTextbook dev server); tilpas now runs on :3005
against the hosted dev Supabase. Provisioned QA fixture user
qa-openmerch@tilpas.test via admin API. Three parallel QA
agents are now exercising /customize, /tilpas/studio, and /design end to
end with screenshot evidence, while a fourth maps all editor code for
the SDK spec.
Repo cleaned: recovered the uncommitted buyer-pack task, deploy hygiene,
and docs into three grouped commits on
codex/sota-garment-twin-plan, then branched
feat/openmerch-sdk. Code-map agent is sweeping all editor
code; baseline QA of the three surfaces starts now.