Version. 0.1 Date. 2026-05-29 Status. Working draft. Closes the composer-upload-port work (item 3) and the 2026-05-28/29 arc.
This session opened on the 2026-05-28 evening handoff's five-item list and closes it. The headline result: item 3 — file upload through the in-engagement composer — is complete and verified on the live system, on both the new surface (CenterPane) and the old one (ChatView), across upload success and fallback paths. The work shipped as Shape 2 (a shared upload hook both surfaces consume) in five build steps plus one gap-closing test, each its own commit, with frontend behaviour preserved and proven.
Along the way the session paid down record debt that had accumulated: it filed queued-directions §15.3 (the upload assertion-grain gap plus two documentation corrections), and it corrected the current-status manifest, which had mislabeled Phase 60 as "Upload pathway v1" when Phase 60 is actually Companion-as-Operator-system-interface. Both landed and pushed before the build began.
What is left: a short tracked follow-up list (below) — none blocking, all named so they survive the session. The decision this handoff records is that item 3 is done; the action it carries forward is the batch push of the build commits and this handoff, and the tag composer-upload-port-v0_1.
The list from the 2026-05-28 evening handoff, closed:
b793aef, 427c446). Prior session.d5d57ee). Prior session.Items 1, 2, 4 needed nothing. Item 5 remains parked by design. Item 3 was the session's work and is now done.
Shape 2. The upload orchestration (~100 lines, formerly inlined in ChatView's handleUpload) was extracted into a shared, turn-type-agnostic hook, src/hooks/useEngagementUpload.ts. Both ChatView and CenterPane consume it. The leaf components (UploadAttachmentButton, UploadResultCard, postUpload, filterPlatformSystemFiles) were already shared; only the orchestration needed extracting.
The seam, resolved on evidence. The composer's upload goes direct to the engine (/engagements/{id}/uploads) via the existing postUpload, because the Operator Layer has no backend tier — it is a pure frontend; every call already terminates at the engine. The "gateway" alternative had no spec or code basis. This was verified by reading architecture spec v0.4 (no "gateway"/"chokepoint"; OVA/FORAY are the governed boundary, both engine-side and seam-and-stub) and the Operator Layer call-pattern survey.
Draft persistence added (CR v0.2 amendment). Step 0 surfaced that CenterPane lost typed drafts on navigation while ChatView preserved them. Rather than defer, the parity gap was closed in-scope: CenterPane now uses the same proven composer-draft.ts mechanism (restore-on-mount, 300ms debounced write, clear-on-commit), keyed per engagement. The two surfaces are now symmetric.
loomworks)DisplayTurn types under one name (handled by the hook staying turn-type-agnostic); CenterPane has no persisted-draft (→ draft-persistence scope added); src/hooks/ did not exist (→ created).d67173c) — extract useEngagementUpload; ChatView consumes it. Behaviour preserved exactly; 388 tests unchanged, none modified.002be2e) — draft persistence on CenterPane, alone. Mechanism note: used ChatView's own render-phase-reset idiom rather than its setState-in-effect restore, to avoid propagating a pre-existing lint defect into clean code. Same behaviour; lint baseline held. +4 tests.74e1d26) — mount the live upload affordance on CenterPane; remove the disabled "Attachments coming soon" placeholder. +4 tests. Draft persistence and upload deliberately separated into Steps 2 and 3 so a regression would be attributable to one.2e938a7) — consolidated test pass; filled CenterPane upload error and 401 coverage. +2 tests.c2ca4db) — added a ChatView upload 401-silent test (a pre-existing coverage gap, not a regression). The test passes, confirming ChatView's 401 handling is correct. +1 test.
Final code head: c2ca4db. Test suite: 399 passing, typecheck and lint at baseline throughout.
Run against the live engine + MinIO + browser stack (the exercise that originally surfaced the transaction bug; green tests alone are not sufficient evidence). All passed:
.md rendered its extracted content) and the fallback path (.json/.html showed "couldn't read this file directly" with the manual-content affordance) rendered correctly..md extracted and rendered. ChatView's refactored path is unbroken.
Both pushed to loomworks-record origin before the build:
b3ec366) — added §15.3 (upload content does not reach assertion grain; assertions_drafted hardcoded empty, no downstream promoter), folding in two independently-timed documentation corrections (spec §13 "engine side not yet shipped" is stale; §15.1's "assertions written to Memory" is imprecise). Archived v0.19, v0.20.8f57ff3) — corrected Entry 101's Phase 60 mislabel (Phase 60 is Companion-as-Operator-system-interface, not Upload pathway v1), fixed stale upload-pathway framings, added Entry 105 absorbing this arc, recorded the upload-pathway phase-identifier as unreconciled in §5. Archived v0.42.The CR documents are in the record but not yet pushed (see batch push):
822a75a) and Change Request v0.2 (09876d0), both in change-requests/.Named here so they survive the session. None gate item 3's completion.
.md is canonical and correct.onClarificationPick not wired. On CenterPane, an upload that returns a clarification card (e.g. an image asking which extraction skill to run) renders the card, but its buttons have no wired action — CenterPane has no chip-to-reconverse path like ChatView's. Observed live (the .png upload). A small follow-up; the manual-content fallback (for unrecognized files) does work..json and .html. Surfaced clearly by live testing: the engine has extraction skills for .md, images, and others, but not .json or .html, which fall back to manual-content. This is engine work (upload-facility-extension territory, queued-directions §4), not a frontend defect.record.dunin7.com/operations/discipline-mechanisms/ editor page serves editor.js and site.css as text/html, so the browser refuses them. A record-site serving issue, unrelated to this work, surfaced incidentally during testing.
Recorded in full at queued-directions §15.3. Upload writes a UploadEventReceived Memory event with full provenance, but drafts no individually-affirmable Assertions (assertions_drafted is hardcoded empty; no downstream promoter exists). Upload content is searchable and provenance-attributable but does not reach the assertion grain the rest of the Memory pipeline operates on. Independent of item 3; present on the path ChatView used before this work; a real engine work item, unsequenced.
loomworks (code): push c2ca4db and the tag composer-upload-port-v0_1.loomworks-record: push 09876d0 (CR v0.2) plus this handoff.composer-upload-port-v0_1, Operator-Layer-only (no engine tag — no engine change). Annotated.
| Repository | Head | State |
|---|---|---|
| Engine (loomworks-engine) | d5d57ee | Unchanged this session. |
| Operator Layer (loomworks) | c2ca4db | Composer Upload Port Steps 1–4 + 401 test. Unpushed until batch push; tag composer-upload-port-v0_1 to apply. |
| Record (loomworks-record) | 09876d0 | v0.21, v0.43, scoping note, CR v0.2 — first two pushed; CR v0.2 and this handoff unpushed until batch push. |
DUNIN7 · Loomworks · Session Handoff · Composer Upload Port v0.1 · 2026-05-29