ClientsFlow · Email-System Overhaul · W8 · EBO

EBO — W8 · Board Experience & Design System

FINAL · owner decisions CLOSED · awaiting signature · 2026-07-12. The target pipeline-board experience after W8 ships — a live push board, entrance/status animations, on-card AI reply, collapse arrow, a hamburger menu that now holds everything except the pipeline (owner ruling R1), and a consistency pass (not a redesign) over the existing board, light theme only. DECISIONS_FINAL.md (2026-07-12) outranks the owner comments, which outrank ANSWERS_ROUND3 and the handoff. Scenarios S1 … S10. The AMBER register is CLOSED — nothing in W8 may be guessed, defaulted or flagged amber.

What W8 owns
The board's look, layout, and every animation: live push, entrance bounce, needs-action pulse, dim overlay, clockwise dashed sequence ring, uniform English next-step/status tags, the on-card Draft-reply UI, the collapse arrow, the hamburger's visuals, and the design-system consistency pass (spacing · tags · fonts · radii · borders · shadows).
What W8 only reflects (owned elsewhere)
W1 owns whether/what actually sends (the failed-send red ring is triggered by W1; W8 only styles it) — and it is W1's on-board failure surfacing that makes R1 safe: a failed send shouts on the card, so nothing important lives only inside a relocated tab. W2 owns touchpoint-history content + the frozen scheduled copy the AI reply draws on. W4 owns raw speed. W7 owns which tabs move into the hamburger (under R1: all of them) and that each still works; W8 draws the ☰ itself. W8 never re-specs those — it renders their state.
You do → click/action/hover You should see → on-screen result Element changes → copy · look · where What changes underneath → data/state Must NOT happen → bug guard 🕓 Touchpoint history → impact on the card's history (or —)
Owner rulings folded in (comment round 2026-07-12 — these outrank ANSWERS_ROUND3)
① Not a redesign — a consistency pass. Do NOT change the current board UI dramatically. Make it consistent: uniform spacing · tags · fonts & font styles · corner radii · borders · shadows. One shadow style is explicitly required: Material "elevation 3 / light". The ClickUp-colorful ruling (Q17=C) survives only as the status-colour layer on top of today's layout — not as a re-skin.
② No reduced-motion suppression. Owner: "I need nice attention-grabbing animations but still elegant and classy, and good visual hierarchy." The animations ALWAYS play. Every existing prefers-reduced-motion suppression that touches these four animations is REMOVED (app/dash_styles.py:177 and the @media (prefers-reduced-motion) block in static/design/TOKENS.css). Elegance + hierarchy are the constraint, not motion-off.
③ English-only operator UI — global rule. Every tag, button, banner, tooltip, spinner label on the board is English. Hungarian appears ONLY inside client-facing email copy (the AI draft's body/greeting/sign-off). The reply button reads "Draft reply"; the send button reads "Send".
④ No mobile optimisation. Owner does not use the board on a phone. Desktop (1280) is the target; tablet (768) must merely not break. No 390px work item, no touch-affordance work.
⑤ Colour restraint. Saturated colour is reserved for decision states (needs-action · failed · approved); structural chrome uses quieter tints of the same hues. Bold, never a rainbow.
Standing invariants — light theme ONLY (never emit a dark surface); the board reflects state, it never invents it (W8 never sends, never advances a stage, never edits copy on its own); ≤ $30/month hosting cap (no new always-on service for the push channel); a build agent never hand-picks a hex colour — every status colour comes from one data-status → token map (static/design/TOKENS.css).
✅ Owner decisions — CLOSED (DECISIONS_FINAL.md, 2026-07-12). No AMBER left in W8.
D6 = W7-D2 · R1 TOP-LEVEL NAV = PIPELINE ONLY (option a — the most aggressive reading, and the literal one). Today/Focus, Done, Calls, Outbox and every other panel move into the ☰. The nav bar holds exactly one item: Pipeline. Rationale (owner): failed sends still surface on the board itself (red ring + banner, S9), so burying Outbox hides nothing. Consequence for W8: the ☰ is no longer a junk drawer — it holds his daily tools. Its findability, ordering, one-click reach and keyboard access are now first-class quality bars (S8), not chrome.
D1 A live push into a card with an OPEN editor is QUEUED and applied on close, with a subtle "updated" flash. Never repaint over typing (S1·3, S5·2).
D2 The pulse outranks the ring. A card that both needs action today and has an armed sequence pulses at full loudness; the ring stays but calms (slower/thinner). One card never runs two loud animations (S3·2, S4·1).
D5 A card's collapsed state persists across reload (per card); newly pushed cards arrive expanded (S7·2).
🌱 Seeded test identities (stable — re-created VERBATIM on every replay)
Every scenario is proven against its own ZZ-sentinel lead. The seed must recreate these names byte-for-byte on each reseed — the judge ties each frame to the seeded card, so a right-looking WRONG card fails. Never mint a fresh name per replay; values that genuinely change per run (ids, dates) live in the probe, never in the pixel checklist.
ZZ-S1 Design Kft (design-system card) · ZZ-S2 Bounce Kft (arrives live) · ZZ-S3 Due Kft (needs action today) · ZZ-S3 Quiet Kft (no action today) · ZZ-S4 Seq Kft (armed sequence) · ZZ-S5 Reply Kft (unreplied inbound) · ZZ-S6 Overdue Kft · ZZ-S6 Today Kft · ZZ-S6 Future Kft (next-step proximity trio) · ZZ-S7 Fold Kft (collapse) · ZZ-S8 Menu Kft (nav/board card) · ZZ-S9 Fail Kft (failed send) · ZZ-S10 Wide Kft (responsive).
Probe environment: probes run in a shell with $BASE (the staging board's base URL) and $DASH_TOKEN (the CURRENT live dash token — never computed from local flows.form_token("dash")) exported. The only curly placeholder bound by the walker is {lead_id}.

S1 — Board loads with the consistency pass applied (light theme, English tags) (happy path / foundation)

Who: Mátyás  ·  When: He opens the pipeline board in the morning — his one and only working surface. He is NOT looking at a new app: the board he knows is still there, just tidied — one spacing scale, one tag style, one shadow, one radius, one colour language.

#You doYou should see Element that changes
copy · look · where
What changes underneathMust NOT happen 🕓 Touchpoint history
1 Mátyás opens the pipeline board (loads the dashboard URL, Pipeline tab) — the seeded card ZZ-S1 Design Kft is on it The board he already knows — same columns, same card layout, same place for everything — but visually consistent: one spacing scale, one card corner radius, one border weight, one font/weight ladder, and every card carrying the same Material elevation-3 (light) shadow. Colour is disciplined: saturated only on decision states (needs-action · failed · approved), quiet tints on structural chrome Copy: column titles and card fields unchanged · Look: uniform 8px spacing scale, one radius (14px cards), one border weight, one type ladder, elevation-3/light shadow on every card, quiet accent tints on column headers · Where: whole board The dash loads the ClientsFlow token layer (static/design/TOKENS.css: status palette + --elev-3 + spacing/radius vars); card/tag/column styles read those tokens instead of the per-element ad-hoc values in app/dash_styles.py. No per-element inline colours anywhere Must NOT be a redesign — cards, columns and controls must NOT move, change shape, or be re-skinned beyond spacing/tags/fonts/radii/borders/shadows; must NOT paint any dark-theme surface even briefly; must NOT give structural chrome the same saturation as decision states (rainbow board); must NOT leave two shadow styles side by side
2 Mátyás scans across the columns The same status always looks the same everywhere — a "Scheduled" tag, a "Due today" tag, a "Rejected" tag read identically in New leads and in Contract out. Every tag is colour + icon + English word, never colour alone Copy (English only): "Scheduled" · "Due today" · "No action" · "Rejected" · "Failed" · "Approved" · Look: one pill style (.status-tag[data-status]), one colour + one Lucide icon per status, board-wide · Where: the card's tag row, every column Every status renders from a single state → token map (one data-status attribute per state); colours defined once in TOKENS.css, never per column, never inline Must NOT show ANY Hungarian text in the operator UI (no "Ütemezve" / "Ma teendő" / "Nincs teendő" / "Küldés"); must NOT let one status appear in two different colours in two columns; must NOT convey a status by colour alone (no icon, no word)
3 Mátyás keeps the board open while working (never presses Refresh) — and while a push lands, he has an editor OPEN on one card (a Draft-reply field with half-typed text) The board stays current on its own — new leads, status flips, and sequence start/stop appear live, within seconds, with no refresh and no full-page reload. The card he is typing in is left alone: its update is held back and applied the moment he closes the editor, announced by a subtle "updated" flash on the card. His text is never touched (owner ruling D1) Copy: unchanged; a brief "updated" flash on a card whose queued update just applied · Look: cards appear/update in place; the card with the open editor visibly does NOT change while he types · Where: live board A server-push channel (SSE from the existing Modal web app — no new always-on service, $30 cap intact) replaces/supersedes the 12s pollEvents loop (app/dash_js.py:2449) as the update path; updates are applied per card, never by re-rendering the page. An update targeting a card with an open editor (Draft-reply field, any open inline panel, or window._gdirty set) is queued and flushed on editor close — settled behaviour, not a default Must NOT trigger a full-page auto-reload (it can discard an in-progress edit — the standing 2026-06-12 rule); must NOT repaint, re-render or reorder a card that currently has an open editor — a single character of his typing lost to a live push is the canonical fail; must NOT silently drop the queued update either (it applies on close, with the flash); must NOT go silently stale for minutes

S2 — New lead pushed to the board → funky bounce-to-place entrance (~2s), no refresh (happy path)

Who: System (a new lead arrives) + Mátyás (watching)  ·  When: A fresh lead enters the pipeline while Mátyás already has the board open. He should notice it land — the arrival is the signal. Attention-grabbing, but elegant: one card, one bounce, then stillness.

#You doYou should see Element that changes
copy · look · where
What changes underneathMust NOT happen 🕓 Touchpoint history
1 Nothing — Mátyás is just watching the board when a new lead (ZZ-S2 Bounce Kft) arrives A new card drops into the New-leads column and bounces to settle into place over about 2 seconds — playful, impossible to miss, and it lands exactly where it belongs. Then it is completely still Copy: the lead's name + its first status tag · Look: bounce-to-place entrance (springy overshoot that settles), ~2s, .anim-entrance · Where: top of the New-leads column The push channel delivers the new lead to every open board; the card is inserted and the entrance class applied at insert time — transform-only (never opacity: an opacity fill pins opacity and defeats the dim/pulse overlays — the D6 bug in dash_styles.py:31) Must NOT require a refresh to see the lead; must NOT animate by fading opacity; must NOT drop the card into the wrong column; must NOT keep animating after it has landed; must NOT be suppressed by an OS reduced-motion preference (the animation always plays) The new lead's history begins per W2's rules (first-contact entry). W8 draws attention to the card; it does not author the history entry.

S3 — Cards needing action today pulse; cards needing none get a uniform dim overlay (auditable on hover) (happy path)

Who: Mátyás  ·  When: Mid-morning, the board holds a mix — some leads need a reply/call/decision today, most don't. His eye must be pulled only to the ones that need him, and the rest pushed back — but he must be able to audit the dim instantly, or he'll stop trusting it.

#You doYou should see Element that changes
copy · look · where
What changes underneathMust NOT happen 🕓 Touchpoint history
1 Mátyás scans the board for what needs him today (seeded: ZZ-S3 Due Kft needs action, ZZ-S3 Quiet Kft does not) ZZ-S3 Due Kft stands out — full colour, a gentle pulse, a "Due today" tag. ZZ-S3 Quiet Kft is pushed back under a uniform darker dim overlay — the same darkness on every dimmed card, board-wide Copy: "Due today" tag on the active card · Look: needs-action = full colour + soft pulse (.anim-pulse); no-action = one consistent dark wash (.dim-today / today's .ucard.passive::before, dash_styles.py:420) · Where: every card, every column Each card's server-side "needs action today?" flag (d.dim, consumed at dash_js.py:1340) decides pulse vs dim; the dim is a non-opacity darkening wash (composes with the other layers), applied via one reusable class — the JS derives nothing Must NOT dim a card that actually needs action today; must NOT vary the dim darkness card to card (it must read as one uniform layer); must NOT use an opacity fade to dim (it breaks the other overlays)
2 Mátyás hovers a dimmed card, then moves away; later, a dimmed lead replies so the card becomes actionable On hover the dim lifts instantly and the card reads at full strength, with a one-line reason ("No action today — next action: Jul 18") so he can audit why it was dimmed; moving away restores the dim. When the lead replies, the dim lifts for good and the card starts pulsing — live, without a refresh Copy: hover reason line "No action today — next action: {date}" (English); card may gain a "Due today" tag · Look: hover → wash removed; on reply → wash removed permanently + pulse begins · Where: the card, in place Hover only toggles the wash (no state write). The reply arrives through the push channel, flips the card's needs-action flag server-side; the dim class is removed and the pulse class added — the card is not re-rendered from scratch Must NOT keep a card dimmed after it becomes actionable; must NOT require a refresh for the transition; must NOT hide the "why dimmed" reason behind a click; must NOT run pulse AND the rotating ring at full loudness on one card — owner ruling D2: the pulse outranks the ring (the pulse plays at full strength, the ring stays but calms — slower and thinner); must NOT drop the ring entirely just because the card pulses (the sequence is still armed and must still read as armed) The underlying event (reply received) is logged per W2. W8 only flips the visual state.

S4 — Active scheduled sequence → clockwise rotating dashed ring (cool, moving); clears into the pulse when it ends (happy path)

Who: Mátyás  ·  When: A lead has an armed email sequence quietly running (emails scheduled over days). He wants to see at a glance which cards are "on autopilot" — and, crucially, to never confuse that with a card whose send FAILED.

#You doYou should see Element that changes
copy · look · where
What changes underneathMust NOT happen 🕓 Touchpoint history
1 Mátyás looks at ZZ-S4 Seq Kft, which has a scheduled sequence armed, and hovers its border The card wears a dashed border travelling clockwise around it in a calm cool colour (indigo) — a living "sequence running" halo. Hovering shows a tooltip: "Next scheduled: Jul 14, 09:00". It is unmistakably different from the failed-send ring, which is red and static — the two differ on BOTH colour and motion. If that same card ALSO needs action today, the pulse wins the loudness budget (owner ruling D2): the pulse plays at full strength and the ring keeps turning, but calmer — slower and thinner — so the card shouts once, not twice Copy: "Scheduled" tag + hover tooltip "Next scheduled: {date}, {time}" (English) · Look: indigo (--status-scheduled) dashed ring rotating clockwise (.anim-sequence-ring); on a card that also pulses, the ring switches to its calm variant · Where: the card's outer border The card carries the "active sequence" flag (today's autoflow class, dash_js.py:1344); the rotating ring is layered additively over the static dashed border, so a browser without the animation still shows a dashed indigo border. Animation precedence is one settled rule in one place: needs-action pulse > sequence ring (D2) — never a per-card judgment call Must NOT show the ring on a card with no armed sequence; must NOT replace the static dashed fallback; must NOT be red, warm, or static (that vocabulary belongs to failure — S9); must NOT rotate counter-clockwise; must NOT hide the "next scheduled" readout behind a click; must NOT run the ring at full loudness alongside the pulse on one card, and must NOT suppress the ring altogether when the pulse takes precedence (D2: it calms, it does not vanish) The sequence's scheduled steps live in the touchpoint history / outbox (W2). The ring reflects that a sequence is armed; it adds no history of its own.
2 The sequence ends because the lead replies (or it is stopped / completes) The rotating ring disappears — and in the same beat the card picks up the needs-action pulse (a reply means it's his turn now). "Autopilot ended" reads visually as "your turn", one continuous story, not two disconnected changes Copy: "Scheduled" tag removed; "Due today" tag appears · Look: ring gone → pulse begins on the same card, no gap · Where: the card The push channel clears the "active sequence" flag and (for a reply-stop) sets the needs-action flag in the same update; the ring class is removed and the pulse class added in one paint Must NOT keep spinning after the sequence stopped (a false "still sending" signal); must NOT require a refresh to clear; must NOT let a reply-stopped card go silent (ring gone, nothing gained) — the hand-off to the pulse is mandatory The stop/complete/reply event is logged by W1/W2. W8 only swaps the ring for the pulse.

S5 — Unreplied latest email is always shown on the card + the "Draft reply" flow (happy path / new feature)

Who: Mátyás  ·  When: A lead has sent an email and it hasn't been replied to. He wants to read what they said and fire back a good reply without opening anything — right on the card, with the AI writing the first draft.

#You doYou should see Element that changes
copy · look · where
What changes underneathMust NOT happen 🕓 Touchpoint history
1 Mátyás looks at ZZ-S5 Reply Kft, whose latest email from the lead is unreplied The lead's latest email is right there on the card face — not behind a click: the first ~2 lines are printed, with a "more" expander that opens the full text inline. Under it sits one primary button: "Draft reply" Copy: the lead's email text (2-line preview + "more") + button "Draft reply" (English) · Look: quoted email block on the card + a primary button · Where: card body, only while the latest email is unreplied The card reads the latest-inbound + reply-status from the same lead state W2 maintains; nothing is generated and nothing is sent at this point Must NOT hide the unreplied email behind a click/hover; must NOT print the full email by default (a long inbound would blow up the card height and wreck the column scan); must NOT show the block after a reply has been sent; must NOT show a stale/older email as "latest"; must NOT label the button anything other than "Draft reply" The inbound email is already a touchpoint (W2). W8 surfaces its text on the card face; showing it creates no new entry.
2 Mátyás clicks "Draft reply" (and mashes it a second time to be sure) Within a blink the button disables and shows a spinning loader with the label "Drafting…" — so the click clearly registered. Then an editable reply field appears right on the card, pre-filled with the AI's draft: proper Hungarian greeting on top (client-facing copy), the body written from this lead's history, the canonical sign-off at the bottom. The second click did nothing — no second draft Copy: button → "Drafting…" (English) while working; draft body = Hungarian client copy, {{greeting}} resolved (e.g. "Kedves János,") + canonical sign-off · Look: Lucide loader-circle spinning in the disabled button (visible <300ms), then an inline textarea + "Send" / "Cancel" buttons · Where: in place, on the card One AI generation request fires (the button is disabled synchronously on the first click — a double-fire cannot happen and cannot double-bill Gemini); the draft is generated from the touchpoint history, with greeting + sign-off applied by the existing copy rules (W2/W6 own copy correctness — W8 renders the field). Nothing sends Must NOT auto-send the draft; must NOT fire two generations on a double-click; must NOT sit visually dead for 1–2s with no spinner; must NOT show a raw {{token}} in the draft; must NOT drop the canonical sign-off; must NOT lose the draft if a live update lands on the card underneath; must NOT put Hungarian on any BUTTON or label (only inside the draft body) No entry yet — a draft is not a send. The touchpoint is written only when he actually sends (step 3), owned by W1/W2.
3 Mátyás edits the draft and clicks "Send" The reply goes out and the card frees immediately: the unreplied block + draft field disappear (it's answered now) and the sent reply lands in the card's history. If the send FAILS: the field stays open with his text intact, the failure is shown inline in the field, and the card gains the red dashed failed-send ring — a failure can never look like a success Copy: on success — card returns to normal (no unreplied block); on failure — inline English failure line + "Failed" tag · Look: success = field closes; failure = field stays open + red dashed ring (S9) · Where: the card The send fires through the W1 send path (the click IS the human gate); the touchpoint history gains the sent reply (W2) with the exact text he sent — 100% parity. On failure W1 raises the failed-send state and W8 renders it; nothing is silently swallowed Must NOT send without the explicit "Send" click; must NOT keep showing the unreplied block after a successful send; must NOT close the field or clear his text on a failure; must NOT report success when the send failed; the recorded history copy must match what was sent exactly (parity) NEW ENTRY (owned by W2)
The sent reply is logged as an outbound touchpoint with the resolved greeting + sign-off — 100% parity with what left. W8 triggers the send; W1/W2 own the record.

S6 — Next step shown as one uniform tag, tinted by urgency (happy path)

Who: Mátyás  ·  When: Scanning any column, he wants every card to tell him its next step in the same visual grammar — one tag, same slot, and he should feel when without reading the date.

#You doYou should see Element that changes
copy · look · where
What changes underneathMust NOT happen 🕓 Touchpoint history
1 Mátyás reads the three seeded cards — ZZ-S6 Overdue Kft, ZZ-S6 Today Kft, ZZ-S6 Future Kft Each card carries a single uniform "next step" tag in the same slot — "Next: call Jul 14" · "Next: scheduled email Jul 15" · "Next: awaiting reply" — and the tag's tint tells him the urgency: overdue = red, today = bright, future = calm. He knows when before he reads the date Copy (English): "Next: {step} {date}" · Look: one tag style, colour by proximity from the shared token scale (overdue → --status-failed, today → saturated, future → quiet tint) · Where: the card's tag row, same slot on every card The next step is derived from the lead's scheduled/expected next action (the same data feeding the outbox/history, W2); tag colour comes from the state→token map (S1) with a proximity modifier — the same source that drives needs-action/dim, never a second rule Must NOT render the next step as free prose that varies card to card; must NOT use a different colour for the same step-type in different columns; must NOT show a next-step tag that contradicts the card's status tag (e.g. "No action" + an overdue red next-step); must NOT invent a second urgency rule that disagrees with the dim/pulse logic The next scheduled step lives in W2's data. The tag reflects it; it authors no history.

S7 — Collapse arrow → card folds to name-only (bouncy, ~1s); expand restores it (happy path / EXTRA feature)

Who: Mátyás  ·  When: A busy column has grown tall and he wants to compress the cards he's parked so he can see more board at once — without losing the one or two facts that matter on a folded card.

#You doYou should see Element that changes
copy · look · where
What changes underneathMust NOT happen 🕓 Touchpoint history
1 Mátyás clicks the small arrow at the top-right of ZZ-S7 Fold Kft The card folds to a slim name-only row with a quick, cool bouncy animation (~1s) — a springy shrink that feels professional, not a jerky snap. What survives the fold: the lead name · the status colour dot · the next-scheduled-contact date · the sequence icon (if armed) · the failed-send marker (if failed). The arrow now points the other way Copy: name + next-contact date survive · Look: full card → one-line strip in ~1s using the proven spring easing (cubic-bezier(.34,1.56,.64,1), easeOutBack — the same easing the entrance uses, already in TOKENS.css); arrow flips · Where: the card, top-right arrow The card's collapsed flag is set; the collapsed layout renders only the survivor field set. Height animates via the shared easing token — no bespoke hand-rolled curve per card Must NOT drop the name or any "needs attention" signal (a collapsed card that is failing or needs action today must STILL show it — a collapsed card must still be able to shout); must NOT reflow the whole column jarringly; must NOT take noticeably longer than ~1s; must NOT look janky/linear (it must use the spring easing)
2 Mátyás clicks the (now-flipped) arrow on the collapsed card, then reloads the page The card bounces back open to its full self (same ~1s spring) — unreplied email block, tags, buttons all return exactly as before, including a draft he had in progress. After the reload, the cards he had collapsed are still collapsed (owner ruling D5 — the fold is a preference, not a session accident); newly pushed cards arrive expanded Copy: full card content returns · Look: strip → full card, spring easing; arrow flips back; after a reload the previously folded cards render folded immediately (no expand-then-collapse flicker) · Where: the card The collapsed flag is cleared and the full layout renders. Settled (D5): the collapsed flag is persisted per card (client-side, keyed by lead id) so it survives a reload and is applied at first paint; a live-pushed new card has no stored flag and therefore renders expanded Must NOT lose card content or an in-progress draft across a collapse/expand; must NOT expand a different card than the one clicked; must NOT re-expand everything on reload (D5 is settled, not a default); must NOT flash the full card before folding it on reload; must NOT collapse a newly arrived card

S8 — Hamburger menu (W8 draws it) holds EVERYTHING but the pipeline; Create-Task button gone (happy path / declutter)

Who: Mátyás  ·  When: He works almost entirely on the pipeline board. The dashboard today shows a primary trio (Focus · Pipeline · Done) plus a "Tools" overflow of fourteen more. Owner ruling R1 (= W8-D6 = W7-D2): the top bar keeps PIPELINE and nothing else — Today/Focus, Done, Calls, Outbox and all the rest go into the ☰. That changes what this menu is. It is no longer a junk drawer of tabs he never opens; it is the door to his daily tools. So its quality is a first-class W8 bar: the daily items sit at the top in a stable order, every item is one click away, the menu opens from the keyboard and shows a visible focus ring, and Esc always closes it. W7 supplies the item list and guarantees each tab still works; W8 owns how the ☰ looks, orders itself, and behaves.

#You doYou should see Element that changes
copy · look · where
What changes underneathMust NOT happen 🕓 Touchpoint history
1 Mátyás loads the dashboard (the board behind it holds ZZ-S8 Menu Kft) The top bar holds exactly one tab — Pipeline — plus a single ☰ hamburger drawn in the same design language as the rest of the board (same radius, border, elevation-3 shadow, Lucide menu icon). Everything else — including Today/Focus, Calls and Outbox — lives behind the ☰. The board is the page Copy (English): "Pipeline" + ☰, nothing else · Look: a one-item nav; one ☰ button styled from the token set, unmistakably a menu (icon + hit area, not a hidden gesture) · Where: dashboard top nav The nav's split is re-declared (W7 owns the list; dash_js.py:31-35 PRIMARY/TOOLS): PRIMARY becomes [pipeline], every other panel is a ☰ item — relocated, never deleted. Settled by R1 — not a default Must NOT leave a second tab in the top bar (Focus/Today, Calls, Outbox included — R1 is literal); must NOT delete or break any relocated tab; must NOT leave a tab reachable from two places (old Tools overflow AND the new ☰); must NOT make the ☰ itself easy to miss now that his daily tools are inside it; must NOT style the ☰ off-system (different radius/shadow/colour than the board)
2 Mátyás clicks ☰ and looks for the tools he uses every day (Today/Focus, Calls, Outbox), then clicks one, then presses Esc and returns to the board (ZZ-S8 Menu Kft still there) The menu opens as a clean panel listing every relocated tab — his daily items first, in a stable order, visually separated from the rarely-used rest (a "Daily" group above the others, each with its Lucide icon + English name). One click opens the tab exactly as before; Esc (or a click outside) closes the menu cleanly. He finds Outbox at a glance, without reading a list of eighteen Copy: the relocated tab names (English), grouped: Today · Calls · Outbox · Done first, then the rest · Look: dropdown panel under the ☰ — same radius/border/elevation-3 as the board, one icon + one label per row, a hairline separator between the daily group and the rest, clear hover highlight · Where: under the ☰ button Each item deep-links to its existing tab/panel — no behaviour of those tabs changes, only their entry point moved (W7 verifies each still works). The ordering is declared once beside the item list; the menu never reorders itself between opens Must NOT change what a relocated tab does; must NOT bury the daily items among the dormant ones or below the fold (they are the reason the menu now matters); must NOT reorder the items between opens (his muscle memory is the whole point); must NOT require more than one click to reach any tab; must NOT trap focus so the menu can't be closed with Esc or an outside click
3 Mátyás looks for the old "Create Task" button on a deal card It's gone — removed from the interface entirely (Q15=A: this is the ONLY removal; every other control merely relocates) Copy: — · Look: the Create-Task button no longer rendered · Where: the deal-card action row (today dash_js.py:1279 / 1283 / 1287) The Create-Task control is removed from the UI (coordinated with W7 — removed ONCE, cleanly; not guarded in one workstream and hidden in the other) Must NOT leave a dead/stub Create-Task button that does nothing; must NOT leave any link or hint pointing at a control that no longer exists; must NOT accidentally remove any OTHER control
4 Mátyás reaches the menu from the keyboard: Tab to the ☰, press Enter, arrow down through the items, press Enter on one; then reopens it and presses Esc The ☰ takes focus with a clearly visible focus ring, Enter opens it, the arrow keys walk the items with the highlighted item plainly visible on screen, Enter opens the highlighted tab, and Esc closes the menu and hands focus back to the ☰ — his daily tools stay a two-keystroke reach even though they left the top bar Copy: unchanged · Look: a visible focus ring on the ☰ and a visible highlight on the active menu item (token colour; the ring is never styled away) · Where: the ☰ + its panel The ☰ is a real focusable control (Basecoat's dropdown vocabulary): Enter/Space opens, arrows move the active item, Enter activates, Esc closes and restores focus. No new routing — the same deep links the mouse uses Must NOT have an invisible focus state (a keyboard user who can't see where he is has no quick access at all); must NOT leave focus stranded inside a closed menu; must NOT open a different tab than the highlighted one; must NOT require the mouse to reach any item

S9 — Failed send → static red dashed ring on the card (styled by W8, triggered by W1) (failure)

Who: System (W1 detects a send failure) + Mátyás  ·  When: An email that should have gone out failed. W1 owns detection and recovery; W8 owns only how the card LOOKS — and the one thing that must never happen is confusing it with the sequence ring.

#You doYou should see Element that changes
copy · look · where
What changes underneathMust NOT happen 🕓 Touchpoint history
1 Nothing — a scheduled send on ZZ-S9 Fail Kft fails behind the scenes (W1 detects it) The card gains a static red dashed ring and a "Failed" tag — loud, urgent, and unmistakably different from the moving indigo sequence ring (red + still vs cool + moving). Clicking the ring / the "Failed" tag takes him straight to W1's failure detail for that card Copy: "Failed" status tag (English) · Look: static red dashed ring (.anim-failed-ring, --status-failed) — no rotation · Where: card border W1 raises the failed-send state; W8 renders the ring from that state (styling only, never suppressible by a later cosmetic override). The click-through wires the card affordance to W1's failure entry — the behaviour behind it is W1's Must NOT let the red ring be silently overridden by another card style; must NOT rotate or look cool-coloured (that vocabulary means "sequence running"); must NOT clear the ring until W1 says the failure is resolved; must NOT be the only failure signal on a collapsed card (the failed marker survives the fold — S7) The failure is logged by W1/W2. W8 styles the card; it authors no failure record.

S10 — Desktop (1280) is the target; tablet (768) must not break (guardrail)

Who: Mátyás  ·  When: He works on the board from his laptop, occasionally glances from a tablet. Mobile (390) is explicitly OUT of scope — owner ruling 2026-07-12: "Don't optimize for mobile view at all." No phone work item, no touch-affordance work.

#You doYou should see Element that changes
copy · look · where
What changes underneathMust NOT happen 🕓 Touchpoint history
1 Mátyás works the board at desktop width (1280) — the real target; ZZ-S10 Wide Kft is on screen All columns side by side; cards at full richness — entrance bounce, pulse, dim, rotating ring, tags, the Draft-reply field and the collapse arrow all render cleanly and legibly, with the uniform elevation-3 shadow and one spacing rhythm Copy: full, English · Look: multi-column board, full card chrome · Where: whole board Full layout; no horizontal page scroll (the column strip scrolls inside its own container — the existing .kanban scroll-lock at dash_js.py:687-692 must keep working) Must NOT overflow the page horizontally; must NOT overlap cards or clip tags; must NOT lose the column scroll position when a live update lands
2 Mátyás glances at the board at tablet width (768) — ZZ-S10 Wide Kft still on screen Nothing is broken: columns scroll horizontally, cards stay legible, tags don't clip, the ☰ and the collapse arrow are still there and still work. It doesn't have to be optimised — it has to not be embarrassing Copy: full · Look: horizontally scrollable columns, same card chrome · Where: board + nav The existing responsive breakpoints reflow the column strip; no new mobile-specific layout is introduced Must NOT clip or overlap any tag/button; must NOT hide the ☰; must NOT push content off-screen; must NOT introduce a phone-specific layout (out of scope by owner ruling)
🕓 Touchpoint-history note for W8:
W8 is a presentation workstream — it almost never authors a touchpoint. The board reflects history that W2 owns (inbound emails, scheduled steps, sent replies) and state that W1 owns (sends, failures). The only place a W8 interaction causes a history entry is the Draft-reply send (S5·3), and even there the entry is authored by W1/W2 with exact copy parity — W8 merely triggers it via the human-gated "Send" click. Every other W8 row's history column is "—" by design: animating, dimming, collapsing, and menu-relocating change how the board looks, never what happened to the lead.

Work-item → scenario-step mapping

Each build work item derived for W8, and the scenario steps that prove it works when clicked through on the staging board. This table is the acceptance contract: /ebo-live-build walks exactly these steps after each item ships to staging, and every step again in the full pass before merge.

Work itemWhat it deliversProven by (scenario · step)
WI-1 · Live push channelThe board updates live (new leads, needs-action flips, sequence start/stop, failures) with no manual refresh and no full-page reload — SSE off the existing Modal web app, no new service (≤$30 cap).S1·3 · S2·1 · S3·2 · S4·2
WI-2 · Entrance animationNew-lead bounce-to-place, ~2s, transform-only, always plays (no reduced-motion suppression).S2·1
WI-3 · Card state visualsNeeds-action pulse · uniform dim overlay with hover-audit ("why dimmed") · clockwise indigo sequence ring with a "next scheduled" tooltip · the ring→pulse hand-off when a sequence ends.S3·1 · S3·2 · S4·1 · S4·2
WI-4 · Draft-reply on cardAlways-visible unreplied email (2-line preview + "more") → "Draft reply" button with a disabled spinner state → editable AI draft (Hungarian greeting + canonical sign-off) → human-gated "Send" with copy parity and a non-swallowing failure path.S5·1 · S5·2 · S5·3
WI-5 · Next-step tagsOne uniform, token-coloured next-step tag on every card in every column, tinted by proximity (overdue/today/future).S6·1
WI-6 · Collapse arrowTop-right arrow folds a card to name + status dot + next-contact date + sequence icon + failed marker, and expands it back — ~1s spring easing, preserving in-progress drafts.S7·1 · S7·2
WI-7 · Hamburger visuals + Create-Task removalTop-level nav reduced to Pipeline only (R1) with the ☰ holding everything else — including his daily tools: the button + menu panel drawn in the board's design language, daily items grouped first in a stable order, one click to any tab, keyboard-openable with a visible focus ring, Esc-closable (W7 supplies the item list and proves each tab still works); the Create-Task button removed entirely.S8·1 · S8·2 · S8·3 · S8·4
WI-8 · Design-system consistency passUniform spacing · tags · fonts · radii · borders · Material elevation-3/light shadow; one data-status→token colour map; English-only operator strings; light theme only. NOT a redesign.S1·1 · S1·2 · S10·1 · S10·2
WI-9 · Failed-send ring stylingRender W1's failed-send state as a static red dashed ring + "Failed" tag, click-through to W1's failure detail; unmistakably distinct from the sequence ring.S9·1
Sign-off — acceptance oracle. By signing, Sarudi Mátyás locks scenarios S1 … S10 and the invariants above as the acceptance answer key for W8 — Board Experience & Design System. All owner decisions are closed (D6 = R1 pipeline-only nav · D1 = queue the push, apply on editor close · D2 = pulse outranks ring · D5 = collapsed state persists) — there is no AMBER allowlist in W8 and nothing here may be guessed or defaulted. No W8 code is built or deployed before this signature.
Sarudi Mátyás  ✔ FINAL · 2026-07-12 · awaiting sign-off