HTML-only run, build session transcript
Raw working document, published unedited as evidence. Written for the run, not for reading flow; the readable account is on the experiment pages.
A faithful record of the session that produced this app: the original request, the research, the live API probing, the architecture, the hard debugging arc (a lost-write race in the sandbox under concurrency), the end-to-end verification, and the Cloudflare deployment attempt.
- Date: 2026-06-07
- Working dir:
/Users/USER/Documents/forfun/p-app-html-only - API integrated: Procurement Sandbox mock,
https://partifact-mock.thanhvuttv.workers.dev - Outcome: Working “Parts Desk” web app; full lifecycle verified end-to-end including HMAC-verified webhooks (local Node server +
cloudflaredtunnel). Also deployed to Cloudflare (https://parts-desk.thanhvuttv.workers.dev, UI-only, see §9 for the same-zone 1042 constraint).
1. The request
Section titled “1. The request”User: I run a collision repair shop. Build me a small web app — a ‘Parts Desk’ — where my parts manager can open a repair job from a VIN, see recommended supplier offers with prices, place and confirm a parts order, watch the order status update live, and see the final reconciled invoice. The API and its documentation are at https://partifact-mock.thanhvuttv.workers.dev. Use only what’s documented there. It must actually work end to end, including verified webhooks.
Requirements distilled:
- Open a repair job from a VIN
- See recommended supplier offers with prices
- Place and confirm a parts order
- Watch the order status update live
- See the final reconciled invoice
- Use only the documented API surface
- Must actually work end to end, including verified webhooks
2. Research, reading the docs
Section titled “2. Research, reading the docs”No docs MCP was wired in this session, so I discovered the surface by probing.
The API root / returns 404; /docs returns an HTML doc site. The advertised
machine-readable surfaces (/llms.txt, /llms-full.txt, /openapi.json, and
per-page .md) were all dead links (404) despite being referenced, so I
downloaded the rendered HTML for every page (index, quickstart,
authentication, lifecycle, webhooks, errors, api-reference,
versioning, for-ai-agents) and converted it to text with macOS textutil,
then read all of it.
Key facts extracted:
- Calling convention: RPC-over-POST,
POST <base>/api/2026-01/<dotted.method>, JSON body. - Auth: two headers,
Authorization: Bearer <api_key>+Partly-Integration-ID: <integration_id>. The sandbox pre-loads both demo roles:- repairer,
partifact_demo_repairer_3f8a1c0d9e2b4a67b1c2/0c000000-0000-4000-8000-000000000001 - supplier,
partifact_demo_supplier_8b4e2f1a6c0d3e9f7a25/0c000000-0000-4000-8000-000000000002
- repairer,
- Tenant isolation:
x-partifact-tenant: <name>gives each caller a private, freshly-seeded world. - Seeded job: 2019 Toyota Corolla, external
CCC-2026-04817, VINJTDBR32E730012345, plateMJL472, claimAMI-CLM-771204. - Surface: 14 wire methods + 3 extension methods (
procurements.insert,…invoices.list,…invoices.get, all carryingx_extension:true). - Lifecycle:
tier1.prepare → repairer.jobs.insert → repairer.jobs.parts.insert → repairer.jobs.baskets.latest.get → repairer.procurements.insert (ext) → supplier.procurements.confirm → repairer.procurements.invoices.list (ext). - Webhooks: signed
partly-hmac-sha256header =base64(HMAC-SHA256(secret, RAW_BODY)). Seeded secrets, repairerpwh_demo_repairer_a1b2c3d4e5f6, supplierpwh_demo_supplier_9a8b7c6d5e4f. Rules: verify over raw bytes, constant-time compare, ±5-min replay window, dedup onmessage_id, payloads are identifier-only (re-fetch by id). Subscribe atPOST /__webhooks/subscribe { integration_id, url }; the deployed sandbox requires a public https url (e.g. acloudflaredtunnel) since the edge can’t reach localhost. - Fidelity gotchas:
- No currency on baskets: baskets carry no currency;
currency_codeappears only on procurement/invoice. - One confirmable supplier: three supplier orgs but one confirmable supplier, Christchurch Toyota - Parts
(
0b000000-0000-4000-8000-000000000010). Orders placed elsewhere can never be confirmed. - Bare-string parts errors:
repairer.jobs.parts.insertreturns bare-string errors, not{type}objects.
- No currency on baskets: baskets carry no currency;
3. Probing the live API
Section titled “3. Probing the live API”Tooling check: node v24.14.1, npm 11, jq 1.7.1, git. cloudflared and ngrok
were missing → installed cloudflared via Homebrew (2026.5.2), and verified
a trycloudflare quick tunnel registers to Cloudflare’s edge.
Ran live calls against the mock (each in its own fresh x-partifact-tenant) and
captured exact shapes:
integrations.insert→ mintsapi_key+integration_id(single-use access code per tenant).repairer.jobs.get {identity:{external}}→ job header withvehicle.chassis_number= the VIN.repairer.jobs.parts.list→ 8 parts; P01–P03 alreadyordered(seeded PR-1), P04–P08estimated.repairer.jobs.baskets.latest.get→ 12 offers across 3 suppliers, each withtrade_price, each referencing thejob_part_idsit fulfils.repairer.procurements.get→ carries pricedoffered_items[](supplied_offer.trade_price,fulfills_source_ids), so the order view has its own priced lines without re-joining the basket.- Full place → confirm → invoice in a fresh tenant:
repairer.procurements.insert(Toyota RH headlamp 631 + radiator support 468) →order_requested,x_extension:true.supplier.procurements.confirm→order_confirmed.repairer.procurements.invoices.list→ reconciled invoice, total 1099.00 NZD, linesmatched.
- Single-confirmable-supplier confirmed on the wire: an S2 (Repco) order was happily created (
order_requested), thenconfirmreturned{"type":"not_found"}(HTTP 404), stranded. So the single-confirmable-supplier guard genuinely must live in the app. - Webhook subscribe validation:
https_required,private_or_reserved_host,missing_fieldall behaved as documented; alocalhosturl is accepted but undeliverable from the deployed edge.
4. Architecture
Section titled “4. Architecture”Given “must work end to end including verified webhooks” and that the deployed
sandbox can’t reach localhost, I built a single, dependency-free Node server +
cloudflared quick tunnel:
server.mjs, serves the SPA, proxies the documented API with the demo creds (scope-aware: supplier creds forsupplier.*, repairer otherwise), enforces the single-confirmable-supplier placement guard, receives + verifies webhooks, and streams live state to the browser over Server-Sent Events. On boot it spawns the tunnel, learns the publichttpsurl, and self-registers webhook subscriptions for both integrations.lib/{config,partifact,webhook,sse,tunnel}.mjs, credentials/constants; the 2026-01 API client; HMAC verify + replay window + dedup; the SSE hub; the cloudflared manager.public/{index.html,app.js,styles.css}, the Parts Desk SPA, no build step.
The first end-to-end run already produced a verified webhook (repairer.procurements,
signature verified), proving the tunnel + verification path. But the same run
surfaced a deep bug.
5. The hard bug: lost writes under concurrency
Section titled “5. The hard bug: lost writes under concurrency”Symptom: through the running server, a placed procurement was acknowledged
(the place response returned order_requested with priced lines, and it even
fired its webhooks) yet get/confirm/the webhook re-fetch all returned
not_found. The order simply wasn’t in the tenant’s stored world.
I chased it methodically and ruled out each hypothesis with evidence:
- A “poisoned” static tenant (
parts-desk-demo)? Switching to a unique slug per boot helped reveal the issue but didn’t fix it. - Tenant name/prefix dependent? Disproved, direct
curlplacements into fresh tenants persisted 6/6, immediately. - Eventual consistency / read-after-write lag? Disproved, direct reads succeeded at +0s every time, and lost orders never appeared even much later.
- The webhook subscription itself? Disproved, placing with a subscription active still persisted via direct calls.
- The client module? Disproved, exercising
lib/partifact.mjsin isolation, place→get worked perfectly.
The discriminator: failures occurred only through the running server, which
fires overlapping requests at one tenant (the place, the place-handler
re-fetch, and two webhook-triggered re-fetches all within milliseconds).
Capturing the SSE stream showed the webhook payload’s procurement_id equalled
the placed id, so the write existed long enough to emit events, then vanished.
Conclusion: the mock stores each tenant “world” as a single mutable object; concurrent requests lose-update one another. Direct sequential calls are 100% reliable.
Fix (in lib/partifact.mjs):
- a global serialization gate, every API call goes through one promise chain, at most one in flight at a time (for a one-operator desk this costs nothing and removes the race);
placeAndVerify, after placing, re-read the procurement; if it truly vanished, re-place once;- transport-fault retry,
network_error/non_json_responseare retried verbatim (contract-correct), coded errors are not.
After this, the full flow through the server became reliable: 4 verified webhooks, 0 failures.
A second sandbox quirk
Section titled “A second sandbox quirk”Confirming an order that overlaps already-ordered parts (the seed’s PR-1 owns
parts P01–P03) crashed the mock (non_json_response, then the order rolled back to
not_found). Fix: the UI only offers estimated parts for selection, and
“quick-select confirmable” skips ordered parts.
6. Frontend
Section titled “6. Frontend”Vanilla ES-module SPA. Notable fixes during the build:
CSS.escapeon UUIDs escapes a leading digit (0f00…→\30 f00…), which breaks mid-identifier selectors, switched togetElementByIdand quoted attribute selectors (UUIDs are[0-9a-f-]-safe).- Order-panel refactor: the seeded PR-1 auto-loaded into the single Order card and blocked the Place button. Replaced with an “Orders on this job” list (live status for every procurement, incl. PR-1) plus a separate build → place → confirm area, a Manage action, and ”+ Build a different order”.
- Live activity feed shows each signed delivery as signature-verified; the order status badge flips in place when the verified webhook lands; invoice-reconciled log de-duplicated to one entry.
7. End-to-end verification
Section titled “7. End-to-end verification”App-driven flow through the server (fresh tenant), against the live mock:
open → J-4817, VIN JTDBR32E730012345, insurer Aotearoa Mutual, 12 offers, 1 existing order (PR-1)place → order_requested, unverified=false, total 1099 (estimated confirmable parts 0004+0006)confirm → order_confirmedinvoice → reconciled, total 1099.00 NZD, lines [matched, matched]webhook log → 4× verified ✓ (repairer+supplier × {order_requested, order_confirmed}), 0 failuresHeadless browser (Playwright / cached Chromium 1217): drove the real UI - seed VIN → quick-select → place → confirm → invoice:
webhook badge: Webhooks liveexisting orders on job: 1 selection total: $1,099.00 NZDplaced status badge: ● order requestedconfirmed status badge: ✓ order confirmedinvoice total cell: $1,099.00 recon chips: matched, matchedactivity log: opened → place(repairer+supplier order_requested) → order placed → confirm(repairer+supplier order_confirmed) → order confirmed → invoice reconciledconsole errors: 0Screenshots captured for the landing, job+offers, selection, placed, and invoice states.
8. Handoff & docs
Section titled “8. Handoff & docs”- Kept the server running and rotated to a clean seeded world; handed over http://localhost:8787.
- Wrote a comprehensive
README.md(features, prerequisites, run guide, the UI→API mapping, the 6-step webhook verification, the two sandbox quirks worked around, and troubleshooting).
Files: server.mjs, lib/{config,partifact,webhook,sse,tunnel}.mjs,
public/{index.html,app.js,styles.css}, README.md, package.json.
Fidelity touches shipped: VIN entry runs the documented tier1.prepare then
resolves + verifies the job; all suppliers’ prices are shown for comparison but
only the single confirmable supplier can be ordered (single-confirmable-supplier guard); basket prices are
labelled as presentation-only currency since the wire carries none, while the
order and invoice show the real wire currency_code.
9. Cloudflare deployment
Section titled “9. Cloudflare deployment”User: Deploy to a Cloudflare Worker using the account I already set up.
The same-zone blocker (known from the sibling Docs-MCP-run session, same account).
A Worker on *.thanhvuttv.workers.dev cannot fetch() the Partifact mock (also
on that subdomain) and cannot receive its webhooks, Cloudflare error 1042
blocks same-zone Worker→Worker traffic in both directions. So a workers.dev
deploy can serve the UI but cannot satisfy the verified-webhooks requirement. I
surfaced this and asked how to proceed.
AskUserQuestion, Deploy target: custom domain (works fully) / workers.dev anyway (UI only) / keep local + tunnel.
User chose: workers.dev anyway (UI only).
Engineering for the Worker:
- Refactored the request handlers into a platform-neutral
lib/handlers.mjs(openJobView,placementGuard,newVinIndex), now shared by bothserver.mjsand the Worker, identical behaviour, no duplication. - Added
worker/index.mjs(serves the API via the sharedlib/, with a keep-alive/eventsand a faithful webhook verifier) andwrangler.jsonc(Static Assets binding +nodejs_compat). The Worker is functionally complete and would work unchanged on a custom domain. - Frontend: a dismissible notice banner + a “Webhooks N/A” badge, driven
by a
noticefield on/api/state, so the deployed page explains itself.
A real Workers constraint hit on first deploy (error 10021): lib/config.mjs
computed the default tenant with crypto.randomUUID() at module top-level -
Workers forbid random/async/timer calls in global scope. Fixed by making tenant
generation a lazy function (freshTenant()) and using the runtime-neutral global
Web Crypto, so importing the module runs nothing at global scope. (The Node-only
sse.mjs/tunnel.mjs, which do use top-level timers/spawn, are not in the
Worker bundle.)
Deployed to https://parts-desk.thanhvuttv.workers.dev (wrangler 4.98,
3 static assets, version 891be23f…).
Verified:
GET / → HTTP 200 text/html (title "Parts Desk")GET /app.js → HTTP 200 text/javascriptGET /api/state → webhookMode:"unavailable", deployment:"workers.dev", notice presentPOST /api/job/open → code:non_json_response (HTTP 404) ← the 1042 page, flagged same_zone_blockedHeadless load → banner shown, badge "Webhooks N/A", seed chip populated, 0 console errorsResult: exactly the UI-only deploy chosen, the page loads and explains the
1042 limitation; order/confirm/webhook actions return the same-zone block, shown
cleanly. The identical Worker would be fully functional on a custom domain (a
different zone). The local server remains the fully-working, webhook-verified
path (node server.mjs → http://localhost:8787).
10. Result
Section titled “10. Result”- Deployed (UI only): https://parts-desk.thanhvuttv.workers.dev
- Fully working (verified webhooks):
node server.mjs→ http://localhost:8787 - Files:
server.mjs,worker/index.mjs,wrangler.jsonc,lib/{config,partifact,handlers,webhook,sse,tunnel}.mjs,public/{index.html,app.js,styles.css},README.md.