Version. v0.2 (supersedes v0.1; v0.1 stays as the baseline element/behavior record).
Date. 2026-06-27
Branch. loomworks feat-companion-surface (commits: integration foundation → responsive surface → named type scale → upload restore).
Scope of v0.2. Carries forward all v0.1 element/behavior rows (mockups 1 & 2) unchanged, and records two deliberate post-conformance fixes. Conversation-content and the across-engagements home remain separately queued.
Verification. Vitest 441 / 76 files green (3 new upload tests + the type sweep) + live render of E0007 at desktop & mobile.
This is the principle both fixes share, stated once: the mockup is the floor for LAYOUT and BEHAVIOR, not a catalogue of which capabilities exist, and its literal px are illustration, not the type spec. Two places the fresh build took the mockup too literally are corrected here — neither is drift; both are baseline-exceeds the Operator directed.
What. The mockup's px (13px body, 11px labels) were sized for a small illustration frame; in the full-viewport surface they read too small. Introduced a small named type-scale as tokens and applied it across the in-engagement surface, replacing the literal px.
| Item | Status |
|---|---|
| Named role tokens --font-heading / --font-body / --font-label / --font-meta in tokens.css | ✅ roomy reading sizes (18 / 16 / 14 / 12, tunable on sight) |
| Wired to Tailwind utilities text-heading / text-body / text-label / text-meta | ✅ via @theme (Tailwind v4 font-size namespace is --text-; --font- is family) |
| Every reading-text literal in the surface swept onto the role utilities | ✅ ConversationPane, WorkCard, MemoryRoom, RenderingRoom, RoomView, WorkspacePane, InEngagementHeader, MobileSurface, InEngagementSurface |
| Hierarchy preserved (body > label > meta; heading above) | ✅ 18 > 16 > 14 > 12 |
| Type only — spacing untouched | ✅ spacing stays deferred |
| Template-clean (tokens, no new one-offs) | ✅ |
| Mic ● glyph sizes (18/20px) | left as icon sizing (not reading type) |
| NotFoundPanel (pre-existing serif error panel, not a mockup surface) | out of scope (its own non-token styling; error path only) |
Recorded position — type-deferral paid down deliberately. Type was previously deferred debt (the designed type-scale system was named "defer; its own pass later"). That deferral was waiting on a clean hierarchy to scale from. The fresh build provided exactly that clean hierarchy, so the debt is being paid down now — deliberately, not by drift. The prior "defer type" position is preserved as the reason this is the right moment: the deferral was correct then (no clean hierarchy yet), and paying it down is correct now (hierarchy exists). This is a small named scale, NOT the full designed type-scale system (which remains deferred); only the reading-role floor is set.
What. The fresh surface dropped the composer attach affordance to match mockup 1 (which shows none). Upload is a real built capability; dropping it to match the mockup was the wrong trade (the same error as stripping the kept sort-order toggle). Restored.
End-to-end verification (engine + frontend code):
| Pathway | Supported? | Wired? |
|---|---|---|
| Single-file | ✅ COMPLETE (engine: ObjectStore + purpose-classification + per-file result; frontend: the proven /chat postUpload chain) | ✅ |
| Multi-file | ✅ COMPLETE (engine loops files → parent + per_file) | ✅ |
| Folder (directory ingestion) | ⚠ NOT complete end-to-end | ❌ not surfaced |
Why folder is not surfaced. The engine is ready — POST /engagements/{id}/uploads builds a folder_manifest with a per-file relative_path derived from the multipart filename. But the frontend postUpload appends file.name (basename), never file.webkitRelativePath, so directory structure is silently flattened; and the Phase 60 OL folder "composer-completeness" piece is unwired. It would not 500 (it degrades to flat multi-file), but it is not true directory ingestion. Per only-show-what's-available, no folder button is shown. Folder upload waits for the Phase 60 OL work (frontend must transmit webkitRelativePath + the completeness piece must land).
| Item | Status |
|---|---|
| Attach (paperclip) affordance in the bottom-anchored input row, beside mic+field (desktop) | ✅ token-styled, file-only |
| Attach affordance in the mobile bottom bar | ✅ (size-40 variant) |
| Multi-file selection | ✅ <input multiple> |
| NO folder affordance | ✅ (gated off; documented above) |
| Upload runs the real pathway (useEngagementUpload → postUpload) | ✅ test asserts postUpload({engagementId, files}) |
| Per-file results render | ✅ desktop: UploadResultCard on the Companion turn (incl. clarification → sends the chosen reading, mirroring /chat). Mobile: no transcript (mockup) → Companion ack surfaces transiently in the hint line, consistent with spoken replies |
| useConversation extended with upload turn primitives (append turns, attach results, uploading gate) | ✅ |
Both fixes hold the surface to its purpose over a literal reading of the mockup: behavior/layout conform to the mockups (v0.1 rows unchanged), while real capability (upload) and readable type are restored/raised as the Operator directed. Suite green; halt before push.
DUNIN7 — Done In Seven LLC — Miami, Florida Companion in-engagement surface — conformance checklist — v0_2 — 2026-06-27