DUNIN7 · LOOMWORKS · RECORD
record.dunin7.com
Status Current
Path phases/companion-surface-phase1/loomworks-companion-inengagement-conformance-checklist-v0_1.md

Companion in-engagement surface — conformance checklist (mockups 1 & 2) — v0_1

Version. v0.1 Date. 2026-06-26 Build target. loomworks Operator-Layer frontend, branch feat-companion-surface (off cr-2026-125-companion-surface-phase1, the known-good focus-integration foundation — no drift). Against. loomworks-companion-build-spec-v0_1.md + mockups …-surface-desktop-… (1) and …-surface-mobile-… (2), read as a specification. Verification. Vitest suite 438 / 76 files green (32 new conformance tests for this surface) + live render of the real engagement E0007 "Goosey the Moose" (held=2, settled=13, renders=13) at desktop (1280) and mobile (390) widths via Playwright + the dev session. Screenshots in ~/Downloads/companion-inengagement-*-render-v0_1.png.

Legend: ✅ CONFORMS · ◐ CONFORMS w/ noted real-data or honest-empty fill · ⚠ DEVIATION (with reason)


DESKTOP (mockup 1) — elements

| # | Element (spec) | Status | Evidence / note | |---|---|---|---| | D1 | Dual-pane frame: left conversation 46%, right workspace 54%, divider between | ✅ | InEngagementSurface grid minmax(0,46fr) minmax(0,54fr); left pane border-r. Live render shows the split + divider. | | D2 | Header left: "Loomworks · {engagement name}" (no "Engagement:" label) | ✅ | InEngagementHeader: {BRAND.productName} · {engagementName}. Live: "Loomworks · Goosey the Moose". | | D3 | Header right: focus chip "here: {n} · {name} · {E####}" | ◐ | Live: "here: 7 · Goosey the Moose · E0007". Name is the engagement's real title (mockup's "Goosey" was illustrative shorthand) — real-content fill per spec. Never the UUID (verified: no uuid-secret in DOM). | | D4 | Header right: sort-order toggle (CONFIRMED KEEP, not in mockup) | ✅ | data-testid=sort-order-toggle, Icon flips with order; click → PUT /me/settings (optimistic). | | D5 | Header right: Operator avatar | ✅ | "MP" initials from person.display_name, info-wash circle. | | D6 | NO "Speak to Companion" pill | ✅ | Absent by construction. | | D7 | NO big engagement-name title inside the left pane | ✅ | Left pane is conversation only; name lives in header + chip. | | D8 | Left pane = vertical column, two children | ✅ | ConversationPane: scroll child + input-zone child. | | D9 | Child 1 conversation: flex:1; overflow-y:auto (THIS scrolls) | ✅ | conversation-scroll = flex-1 overflow-y-auto. | | D10 | Operator messages right-aligned, info-blue bubbles | ✅ | OperatorBubble: self-end bg-info-wash text-info. Live confirms. | | D11 | Companion messages left-aligned, "Companion" label above, secondary-bg bubble | ✅ | CompanionBubble: self-start, label text-content-3, bubble bg-surface-2. | | D12 | Inline action buttons on Companion messages where applicable | ◐ | Wired to the engine's real suggested_actions (the API-supported per-turn affordance). The mockup's literal "Yes, commit ↗ / Not yet" is illustrative — see DEV-1. | | D13 | Child 2 input zone: border-top, secondary-bg, NO flex-grow, FIXED SIBLING that does NOT scroll | ✅ | input-zone = shrink-0 border-t bg-surface-2, sibling of (not inside) the scroll area. Test asserts scroll.contains(input)===false. | | D14 | Round mic on the LEFT (●, 38px, info fill/border/glyph) | ✅ | 38×38 bg-info-wash border-info-line text-info. Live confirms purple/info circle. | | D15 | One field filling the rest, placeholder "Speak, or type a note or command…" | ✅ | composer textarea, SURFACE.inputPlaceholder. | | D16 | NO Send button (Enter sends) | ✅ | Test: queryByRole("button",{name:/send/i}) is null. | | D17 | Suggestion chips below the input row | ✅ | SURFACE.suggestionChips(...); live shows three chips. | | D18 | Right pane: tab strip (Memory · Manifestation · Shaping · Rendering), click switches room | ✅ | WorkspacePane role=tablist, 4 tabs, onRoomChange. | | D19 | Active room indicated | ⚠→✅ | The mockup CSS marks active as a bordered pill on surface-1 (its .room.active), NOT "underlined" as the spec prose says. Conformed to the mockup CSS (authoritative artifact). See DEV-2. | | D20 | Workspace body flex:1; overflow-y:auto rendering per-room cards | ✅ | workspace-body = flex-1 overflow-y-auto. |

DESKTOP — workspace room content

| # | Item | Status | Note | |---|---|---|---| | D21 | Memory: "Held for you ({n})" + "Settled ({n})" sections, real engine assertions | ✅ | Live: "Held for you (2)" + "Settled (13)" from real E0007 data. | | D22 | Held card: content + Definition/Rule type chip + Required/Recommended chip where applicable + contributor + Confirm action | ✅ | Live held card: "Held" + "Definition" + "Held 15" + "Confirm ↗" + content + "noted by Marvin Percival (Operator)". | | D23 | Confirm = Operator's reserved act (Held→Settled live, Operator-approves, not automatic) | ✅ | commitAssertion through the Operator's own session; test asserts call + refetch. | | D24 | Honest empty states ("Nothing held right now — the Companion isn't waiting on you") | ✅ | SURFACE.heldEmpty(companionName) / settledEmpty; test asserts + no dead buttons. | | D25 | Rendering: produced renders to download/view + candidates; badge treatments | ◐ | Live: real renders with "Render" + "HTML" badges + "View ↗" (opens the produced HTML). Candidate cards: none (no candidate endpoint) → honest. | | D26 | Manifestation / Shaping room shapes | ◐ | No engine endpoint exists for these rooms (confirmed). Honest empty states (composeEmpty / shapeEmpty) rather than fake cards — spec: "honest empty states where it doesn't [exist]". | | D27 | Badge treatments (Held/In record/Question/Shape/Type/Render/Candidate) | ✅ | WorkCard BadgeTone map = the mockup's badge() colour table as design-token classes. |


MOBILE (mockup 2) — elements + behavior

| # | Element/behavior (spec) | Status | Evidence / note | |---|---|---|---| | M1 | Single column; header: engagement name + compact context line "here · {n} · {E####}" | ✅ | Live: "Goosey the Moose" + "here · 7 · E0007" + MP avatar. | | M2 | NO desktop labels (minimal header) | ✅ | Compact header branch in InEngagementHeader. | | M3 | Room stack flex:1; overflow-y:auto, one room at a time | ✅ | mobile-stack = flex-1 overflow-y-auto; renders the active room only. | | M4 | Room name + dots indicator + chevron prev/next | ✅ | mobile-room-name, 4 dots (active wider, info), ‹ / › buttons. | | M5 | Bottom bar flex-shrink:0; border-top — FIXED SIBLING, does NOT scroll | ✅ | mobile-bottom-bar = shrink-0 border-t, sibling of stack. Test asserts stack.contains(bar)===false. | | M6 | Bottom bar: field (flex:1) on left + large round mic (48px) on RIGHT; hint line above | ✅ | Live: hint "swipe to move · hold to speak" + field + 48px info-blue mic on the right. | | M7 | The four rooms are the swipe plane (mockup has no conversation tab) | ◐ | Conformed to the mockup's actual arrangement: swipe plane = the four rooms, no transcript. See DEV-3. |

IN-ENGAGEMENT — stated behaviors (each a requirement)

| # | Behavior (spec) | Status | Evidence | |---|---|---|---| | B1 | Input zone never scrolls — fixed sibling, both desktop & mobile | ✅ | D13 + M5 (tests assert sibling-not-descendant on both). | | B2 | Room switching (tab desktop / chevron+swipe mobile) re-renders that room; active indicated | ✅ | Tests: tab click → onRoomChange; chevron + swipe → onRoomChange. | | B3 | Mobile swipe between rooms (\|dx\|>50 && \|dx\|>\|dy\|); vertical scroll = within room; gestures coexist | ✅ | MobileSurface touchend matches the mockup rule. Tests: left-swipe moves; vertical drag does not. | | B4 | Mic toggle: not-listening = info-blue; listening = iron-oxide + hint "listening… '{example}'" | ✅ | Live: idle = info-blue; listening = iron-oxide + placeholder listening… "show me my outputs". | | B5 | Suggestion chips send their text on click | ✅ | Test: chip click → send(chipText). | | B6 | Enter sends a typed message (no Send button) | ✅ | Tests (desktop + mobile): Enter → send; Shift+Enter does not. | | B7 | Conversation order: default + sort-order toggle flips it (CONFIRMED KEEP) | ✅ | Baseline oldest_first (newest at bottom near input); header toggle + Companion tune_setting both flip; stored pref honoured. |


DEVIATIONS (explicit, with reason)

Real-data / honest-empty fills (not deviations — spec-directed)

Global conformance requirements

| Requirement | Status | Note | |---|---|---| | Identity: number + name + E####, never UUID | ✅ | Chip + context line; DOM has no UUID (asserted). | | Plain-terms: Memory/Manifestation/Shaping/Rendering as room labels; wire-tokens forbidden outside adapters | ✅ | room-labels.ts (vocab-wall room-label exception); components dispatch on a wall-safe content discriminator, never the room-key literals. | | Only show what's available (no disabled/grayed) | ✅ | DEV-4. | | Responsive: layout switches on WIDTH | ✅ | useIsWide(900); verified at 1280 + 390. | | Template-clean: tokens (no raw hex), strings via registry, Companion name from data | ✅ | New semantic palette tokens; all strings in SURFACE/BRAND/FOCUS; companion name from person.companion_name. Vocab-wall + template-clean guards green. | | Companion is the entity; AI invisible | ✅ | No model disclosure anywhere. |


DUNIN7 — Done In Seven LLC — Miami, Florida Companion in-engagement surface — conformance checklist — v0_1 — 2026-06-26