From c4996c4bd3c6dc63086badd5df4b8412c210d995 Mon Sep 17 00:00:00 2001 From: bo Date: Mon, 10 Aug 2026 19:37:46 +0800 Subject: [PATCH 01/19] docs(design): polish prototype status semantics and shared chrome Unify running/attention/done/failed tones across Home, Sessions, Session, Todos, and Automations prototypes. Standardize product copy on "Needs you", share status-orbit and primary-button primitives, add a light static session sample cache, and include the prototype brand mark. --- .gitignore | 1 + design-system/prototypes/app.js | 235 +- design-system/prototypes/automations.html | 864 +++++- design-system/prototypes/dashboard.html | 339 ++- design-system/prototypes/logo.svg | 7 + design-system/prototypes/session.html | 2989 ++++++++++++++++----- design-system/prototypes/sessions.html | 322 ++- design-system/prototypes/styles.css | 799 ++++-- design-system/prototypes/todos.html | 2826 ++++++++++++++++--- 9 files changed, 6705 insertions(+), 1677 deletions(-) create mode 100644 design-system/prototypes/logo.svg diff --git a/.gitignore b/.gitignore index 588449fd..16af7d81 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ bun.lockb .archcode .specra .playwright-mcp +.codex/config.toml __test_tmp__/ __test__/ .turbo/ diff --git a/design-system/prototypes/app.js b/design-system/prototypes/app.js index b3f697de..f8f2f494 100644 --- a/design-system/prototypes/app.js +++ b/design-system/prototypes/app.js @@ -1,22 +1,37 @@ const icons = { app: "M5 5h14v14H5zM9 9h6v6H9z", + "arrow-up": "M12 19V5M5 12l7-7 7 7", bell: "M18 8a6 6 0 0 0-12 0c0 7-3 7-3 9h18c0-2-3-2-3-9ZM10 21h4", + board: "M4 5h4v14H4zM10 5h4v14h-4zM16 5h4v14h-4z", check: "m5 12 4 4L19 6", chevron: "m9 6 6 6-6 6", + "circle-check": "M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-12 0 2 2 4-4", + "circle-dot": "M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6 0a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z", + "circle-play": "M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-11-4 6 4-6 4Z", close: "M6 6l12 12M18 6 6 18", dashboard: "M4 4h6v6H4zm10 0h6v9h-6zM4 14h6v6H4zm10 3h6v3h-6z", + "file-plus": "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8zM14 2v6h6M12 18v-6M9 15h6", + list: "M8 6h13M8 12h13M8 18h13M3 6h.01M3 12h.01M3 18h.01", filter: "M4 6h16M7 12h10M10 18h4", grip: "M9 7h.01M15 7h.01M9 12h.01M15 12h.01M9 17h.01M15 17h.01", + hammer: "m15 12-8.5 8.5-3-3L12 9m2-6 7 7-4 4-7-7 4-4Z", + layers: "m12 2 9 5-9 5-9-5 9-5Zm-9 10 9 5 9-5M3 17l9 5 9-5", + lightbulb: "M9 18h6M10 22h4M8.2 14.6A7 7 0 1 1 15.8 14.6C15.2 15.1 15 15.7 15 16H9c0-.3-.2-.9-.8-1.4Z", menu: "M4 7h16M4 12h16M4 17h16", moon: "M20 15.5A8.5 8.5 0 0 1 8.5 4 8.5 8.5 0 1 0 20 15.5Z", more: "M6 12h.01M12 12h.01M18 12h.01", panel: "M4 4h16v16H4zM15 4v16", plus: "M12 5v14M5 12h14", search: "m20 20-4.3-4.3M18 11a7 7 0 1 1-14 0 7 7 0 0 1 14 0Z", + scan: "M3 3v5M3 3h5M21 3h-5M21 3v5M3 21v-5M3 21h5M21 21h-5M21 21v-5M8 15l3-3 2 2 4-5", settings: "M12 15.5a3.5 3.5 0 1 0 0-7 3.5 3.5 0 0 0 0 7ZM19 13.5l2 1-2 3-2-1.1a8 8 0 0 1-2 1.2L14.8 20h-3.6l-.2-2.4a8 8 0 0 1-2-1.2L7 17.5l-2-3 2-1a8 8 0 0 1 0-3L5 9.5l2-3 2 1.1a8 8 0 0 1 2-1.2l.2-2.4h3.6l.2 2.4a8 8 0 0 1 2 1.2L19 6.5l2 3-2 1a8 8 0 0 1 0 3Z", + square: "M6 6h12v12H6z", sun: "M12 4V2m0 20v-2M4 12H2m20 0h-2m-2.3-5.7 1.4-1.4M4.9 19.1l1.4-1.4m0-11.4L4.9 4.9m14.2 14.2-1.4-1.4M16 12a4 4 0 1 1-8 0 4 4 0 0 1 8 0Z", + sparkles: "m12 3-1.1 3.1L8 7.2l2.9 1.1L12 11l1.1-2.7L16 7.2l-2.9-1.1L12 3Zm6 9-.7 2-1.8.7 1.8.7.7 2 .7-2 1.8-.7-1.8-.7-.7-2ZM6 13l-1 2.6-2.5 1L5 17.5 6 20l1-2.5 2.5-.9-2.5-1L6 13Z", + telescope: "m10 6 8-3 2 4-8 3M10 6l2 4M8 12l-4 9M12 12l4 9", todo: "M8 6h12M8 12h12M8 18h12M3.5 6h.01M3.5 12h.01M3.5 18h.01", + workflow: "M3 5h6v6H3zM15 13h6v6h-6zM9 8h3a3 3 0 0 1 3 3v2", }; const baseWorkSearchItems = [ @@ -25,10 +40,10 @@ const baseWorkSearchItems = [ workKey: "todo-profile-defaults", type: "Session", tone: "attention", - state: "Permission", + state: "Needs you", title: "Implementation Session", meta: "Todo · Model profile defaults per project", - href: "./session.html?todo=todo-profile-defaults&session=sess-profile-implementation", + href: "./session.html?todo=todo-profile-defaults&session=sess-profile-implementation&state=hitl", }, { group: "Needs you", @@ -44,8 +59,8 @@ const baseWorkSearchItems = [ group: "Needs you", workKey: "automation-aut-health", type: "Automation", - tone: "attention", - state: "Failed run", + tone: "failed", + state: "Failed", title: "Daily project health check", meta: "Automation · latest run needs a decision", href: "./session.html?automation=aut-health&session=sess-aut-health-failed&title=Daily%20project%20health%20check", @@ -65,7 +80,7 @@ const baseWorkSearchItems = [ workKey: "todo-work-search", type: "Todo", tone: "review", - state: "Review", + state: "Ready to review", title: "Unify project work search", meta: "In Progress · Lead + Build · result ready", href: "./todos.html#todo=todo-work-search", @@ -134,6 +149,20 @@ document.querySelectorAll("[data-icon]").forEach((element) => { if (icons[name]) element.innerHTML = icon(name); }); +document.querySelectorAll("[data-filter-clear]").forEach((button) => { + const attribute = button.dataset.filterClear; + const input = attribute ? document.querySelector(`[${attribute}]`) : null; + if (!(input instanceof HTMLInputElement)) return; + const sync = () => { button.hidden = input.value.length === 0; }; + input.addEventListener("input", sync); + button.addEventListener("click", () => { + input.value = ""; + input.dispatchEvent(new Event("input", { bubbles: true })); + input.focus(); + }); + sync(); +}); + const root = document.documentElement; const toast = document.querySelector(".toast"); let toastTimer; @@ -166,6 +195,90 @@ function showToast(message) { window.showToast = showToast; +function createProjectContextMenu() { + const identities = [...document.querySelectorAll(".project-identity")]; + if (!identities.length) return; + + const menu = document.createElement("div"); + menu.id = "project-context-menu"; + menu.className = "project-context-menu"; + menu.hidden = true; + menu.setAttribute("role", "menu"); + menu.setAttribute("aria-label", "Project actions"); + menu.innerHTML = ` + + `; + document.body.append(menu); + + let activeIdentity = null; + + function projectName(identity) { + return identity.querySelector("h1, strong")?.textContent.trim() || "project"; + } + + function close({ restoreFocus = false } = {}) { + if (menu.hidden) return; + menu.hidden = true; + if (restoreFocus) activeIdentity?.focus(); + activeIdentity = null; + } + + function open(identity, clientX, clientY) { + activeIdentity = identity; + menu.hidden = false; + menu.style.visibility = "hidden"; + const menuRect = menu.getBoundingClientRect(); + const left = Math.max(8, Math.min(clientX, window.innerWidth - menuRect.width - 8)); + const top = Math.max(8, Math.min(clientY, window.innerHeight - menuRect.height - 8)); + menu.style.left = `${left}px`; + menu.style.top = `${top}px`; + menu.style.visibility = "visible"; + requestAnimationFrame(() => menu.querySelector("button")?.focus()); + } + + identities.forEach((identity) => { + const name = projectName(identity); + identity.tabIndex = 0; + identity.setAttribute("aria-haspopup", "menu"); + identity.setAttribute("aria-controls", menu.id); + identity.setAttribute("aria-label", `${name} project actions`); + identity.setAttribute("aria-keyshortcuts", "Enter Space Shift+F10"); + identity.setAttribute("role", "button"); + identity.setAttribute("title", "Right-click for project actions"); + identity.addEventListener("contextmenu", (event) => { + event.preventDefault(); + open(identity, event.clientX, event.clientY); + }); + identity.addEventListener("keydown", (event) => { + if (!(event.key === "Enter" || event.key === " " || event.key === "ContextMenu" || (event.shiftKey && event.key === "F10"))) return; + event.preventDefault(); + const rect = identity.getBoundingClientRect(); + open(identity, rect.left, rect.bottom + 4); + }); + }); + + menu.addEventListener("click", (event) => { + const action = event.target.closest("[data-project-context-action]"); + if (!action || !activeIdentity) return; + showToast(`${action.dataset.projectContextAction}: ${projectName(activeIdentity)}`); + close({ restoreFocus: true }); + }); + + document.addEventListener("pointerdown", (event) => { + if (!menu.hidden && !menu.contains(event.target)) close(); + }); + document.addEventListener("keydown", (event) => { + if (event.key === "Escape" && !menu.hidden) { + event.preventDefault(); + close({ restoreFocus: true }); + } + }); + window.addEventListener("resize", () => close()); + window.addEventListener("scroll", () => close(), true); +} + +createProjectContextMenu(); + function readPrototypeSessions() { try { const value = JSON.parse(localStorage.getItem("archcode-prototype-sessions") || "[]"); @@ -177,6 +290,99 @@ function readPrototypeSessions() { window.readArchcodePrototypeSessions = readPrototypeSessions; +/** Shared static Sessions catalog for list pages + Home Needs-you slice. */ +const archcodePrototypeStaticSessions = [ + { + group: "Needs you", + tone: "attention", + state: "Needs you", + source: "Todo", + title: "Implementation Session", + sessionId: "sess-profile-implementation", + parent: "Model profile defaults per project", + agent: "Lead → Build", + href: "./session.html?todo=todo-profile-defaults&session=sess-profile-implementation&state=hitl", + }, + { + group: "Needs you", + tone: "failed", + state: "Failed", + source: "Automation", + title: "Daily project health check", + sessionId: "sess-aut-health-failed", + parent: "Daily project health check", + agent: "Lead", + href: "./session.html?automation=aut-health&session=sess-aut-health-failed&title=Daily%20project%20health%20check", + }, + { + group: "Needs you", + tone: "attention", + state: "Needs you", + source: "Todo", + title: "Review recoverable worktree cleanup", + sessionId: "sess-worktree-permission", + parent: "Review recoverable worktree cleanup", + agent: "Lead", + href: "./session.html?todo=todo-worktree-permission&session=sess-worktree-permission", + }, + { + group: "Running", + tone: "running", + state: "12m", + source: "Todo", + title: "Live execution recovery", + sessionId: "sess-live-recovery", + parent: "Live execution recovery", + agent: "Lead → Explore", + href: "./session.html?todo=todo-live-recovery&session=sess-live-recovery", + }, + { + group: "Running", + tone: "running", + state: "34s", + source: "Direct", + title: "Fix incorrect model type", + sessionId: "sess-direct-model-type", + parent: "Lead", + agent: "Lead", + href: "./session.html?session=sess-direct-model-type&title=Fix%20incorrect%20model%20type", + }, + { + group: "Recent", + tone: "done", + state: "Completed · 1h", + source: "Todo", + title: "Review Session", + sessionId: "sess-profile-review", + parent: "Model profile defaults per project", + agent: "Analyst", + href: "./session.html?todo=todo-profile-defaults&session=sess-profile-review", + }, + { + group: "Recent", + tone: "done", + state: "Completed · Yesterday", + source: "Automation", + title: "Regression check · Aug 1", + sessionId: "sess-aut-regression-20260801", + parent: "Regression check", + agent: "Lead", + href: "./session.html?automation=aut-regression&session=sess-aut-regression-20260801&title=Regression%20check", + }, + { + group: "Recent", + tone: "done", + state: "Completed · Jul 30", + source: "Direct", + title: "Audit project context loading", + sessionId: "sess-legacy-context-audit", + parent: "Lead", + agent: "Lead", + href: "./session.html?session=sess-legacy-context-audit", + }, +]; +window.archcodePrototypeStaticSessions = archcodePrototypeStaticSessions; + function escapePrototypeHtml(value) { return String(value).replace(/[&<>'"]/g, (character) => ({ "&": "&", "<": "<", ">": ">", "'": "'", '"': """ })[character]); } @@ -361,7 +567,7 @@ function createSearchDialog({ triggerSelector, scope, title, description, placeh return { finder, open, close }; } -const currentProjectName = document.querySelector(".project-identity-copy h1")?.textContent.trim() || "project"; +const currentProjectName = document.querySelector(".project-identity h1")?.textContent.trim() || "project"; const globalSearch = createSearchDialog({ triggerSelector: "[data-open-global-search]", scope: "global", @@ -386,22 +592,25 @@ const attentionItems = [ { type: "Session", title: "Implementation Session", - meta: "Permission · Model profile defaults per project", - state: "Permission", - href: "./session.html?todo=todo-profile-defaults&session=sess-profile-implementation", + meta: "Permission gate · Model profile defaults per project", + state: "Needs you", + tone: "attention", + href: "./session.html?todo=todo-profile-defaults&session=sess-profile-implementation&state=hitl", }, { type: "Todo", title: "Review worktree deletion permission", meta: "Lead is waiting on one scoped decision", - state: "Decision", + state: "Needs you", + tone: "attention", href: "./todos.html#todo=todo-worktree-permission", }, { type: "Automation", title: "Daily project health check", meta: "Latest run stopped before the final report", - state: "Failed run", + state: "Failed", + tone: "failed", href: "./session.html?automation=aut-health&session=sess-aut-health-failed&title=Daily%20project%20health%20check", }, ]; @@ -421,9 +630,9 @@ function createAttentionInbox() {
${attentionItems.map((item) => ` - + ${escapePrototypeHtml(item.type)}${escapePrototypeHtml(item.title)}${escapePrototypeHtml(item.meta)} - ${escapePrototypeHtml(item.state)} + ${escapePrototypeHtml(item.state)} `).join("")}
`; document.body.append(inbox); diff --git a/design-system/prototypes/automations.html b/design-system/prototypes/automations.html index e47af650..5f490205 100644 --- a/design-system/prototypes/automations.html +++ b/design-system/prototypes/automations.html @@ -5,7 +5,10 @@ ArchCode — Automations Prototype - + + + + - +

Sessions

Discussion and execution history stays attached to this Todo.

@@ -1235,7 +2674,7 @@

Sessions

Plan DiscussionShaped the PRD and wrote the current Plan. Done - + Implementation SessionLead with Build and Explore agents · Goal active Running @@ -1268,15 +2707,57 @@

Lifecycle

+ + + - + + +
+
+

New Todo

+ +
+
+ + +

Markdown is supported. Save it for later, or start a bound Lead Session now.

+
+
+ + + +
+
+
+
From 4f53cc6995a7a52a6a8db05e699533907b3d4f6b Mon Sep 17 00:00:00 2001 From: bo Date: Mon, 10 Aug 2026 20:19:37 +0800 Subject: [PATCH 02/19] docs(design): sync inspector polish and status lexicon Absorb the Session inspector redesign, Needs you density rule, and Failed vs attention tones into MASTER and page overrides. Align prototypes so Failed Sessions no longer paint Composer as amber Needs you, and Todos show a Failed operational line with error tone. Co-Authored-By: Claude Fable 5 --- design-system/MASTER.md | 99 ++++- design-system/pages/automations.md | 31 +- design-system/pages/dashboard.md | 17 +- design-system/pages/session.md | 83 +++- design-system/pages/sessions.md | 20 +- design-system/pages/todos.md | 23 +- design-system/prototypes/session.html | 559 +++++++++++++++++++++----- design-system/prototypes/todos.html | 113 +++++- 8 files changed, 766 insertions(+), 179 deletions(-) diff --git a/design-system/MASTER.md b/design-system/MASTER.md index 7914606e..a4f2dd8c 100644 --- a/design-system/MASTER.md +++ b/design-system/MASTER.md @@ -1,8 +1,10 @@ # Signal Workbench Design System > Target UI specification synchronized with the current effective prototypes on -> 2026-08-03. Until product implementation catches up, current product code +> 2026-08-10. Until product implementation catches up, current product code > remains authoritative for existing runtime behavior and state mechanics. +> Prototypes under `design-system/prototypes/` lead visual and copy decisions for +> surfaces they cover; page files record those decisions for implementation. > > When designing or implementing a page, read this file first and then read > `pages/[page-name].md`. A page file overrides this Master only where it says @@ -355,10 +357,12 @@ Narrow-screen rules: generic `EntityFilter` component. Page-specific source or view controls may sit beside it without restyling the field. - Session and Automation pages use one compact row per item: - `state icon → single-line title → optional goal/attention/time marker`. -- Use icons for running, completed, Goal, permission, question, and Automation - states. Reserve short text tags for states that require immediate user action, - such as `Permission` or `Question`. + `status orbit → single-line title → optional goal/attention/time marker`. +- Use icons/orbits for running, completed, Goal, permission, question, Automation, + failed, and review states. Inventory and Home surfaces use the product phrase + **`Needs you`** for human action groups; reserve short mechanism tags + (`Permission`, `Question`) for tool/HITL detail and accessible names when + density requires it. **`Failed`** always uses error tone. - Do not repeat ordinary state and time in a second descriptive line. Put the full state explanation in the accessible name and tooltip. - Organize Sessions by decision value: `Needs you`, `Running`, then `Recent`. @@ -392,29 +396,58 @@ Narrow-screen rules: ## Status Language Use visible text or a recognizable icon in addition to color. Always provide -the complete state in the accessible name: +the complete state in the accessible name. -| State | Visual | -|---|---| -| Running/live | Lime-accented spinner/orbit or live pulse plus accessible state | -| Completed | Green check/status text | -| Needs attention | Amber icon plus short `Permission` or `Question` tag | -| Selected/active | Indigo field or inset rule | -| Idle/neutral | Outline neutral orbit | -| Error/destructive | Red icon/field and recovery wording | +### Product lexicon vs mechanism detail + +| Layer | Label | Where it appears | +|---|---|---| +| Product decision | **`Needs you`** | Home sections, Sessions/Automations inventory groups and row states, Session header badge, Composer state, Todo operational line, rail attention inbox | +| Mechanism detail | **`Permission`**, **`Question`**, tool/HITL tab copy | Tool rows, HITL request family, Context Inspector Execution binding, agent-tree trailing state when mirroring a gate | +| Terminal failure | **`Failed`** | Own red tone — never restyled as amber `Needs you` | +| Live work | **`Running`** / elapsed | Inventories, header, Composer, agent tree | +| Success | **`Done`** / **`Completed`** / **`Ready to review`** | Inventories and review surfaces | + +Rules: + +- Prefer **`Needs you`** as the cross-surface product phrase for “user action required.” Do not compete with **`Needs attention`**, **`Waiting`**, or bare **`Permission`** as the primary inventory/header label. +- **`Permission`** remains correct for the *kind* of gate (tool status, HITL family, Execution `Suspended · Permission`). It is not a substitute for the product group name `Needs you`. +- On a single Session canvas, do **not** repeat `Needs you` on every chrome layer. Strong product labels stay on **header + Composer**; Work folds, agent tree, and inspector summary use mechanism or time wording (see [`pages/session.md`](pages/session.md)). +- Failed runs use **`--error`** tone and the word **`Failed`**. Do not map failure onto the attention/amber channel. + +### Status visual map + +Core decision triad is **Running / Needs you / Done**. The full map also covers +failure, review, selection, and idle so implementers do not invent competing +colors. + +| State | Token / field | Visual | +|---|---|---| +| Running / live | `--signal` / `--signal-foreground` / `--running-field` | Lime orbit or live pulse + accessible state | +| Needs you / HITL attention | `--warning` / `--attention-field` | Amber icon/orbit + `Needs you` (or mechanism tag where density rules allow) | +| Done / completed | `--success` / `--success-field` | Green check or completed text | +| Failed / error | `--error` / `--error-field` | Red icon/orbit + `Failed` or recovery wording | +| Ready to review | brand-tinted quiet marker (not lime) | Review-ready inventory cue | +| Selected / active | `--brand` / `--selection-field` | Indigo field or inset rule | +| Idle / stopped / neutral | `--neutral` / outline orbit | Outline neutral orbit; no lime | + +Shared **status-orbit** (and page aliases: home/session/automation/session-finder/session-picker) is one primitive: same sizes, tones, spin only while `.running`, and `prefers-reduced-motion` freezes spin. Do not fork per-page orbit CSS. Automation invocation state is not Session or Execution completion: a `dispatched` invocation remains visibly `Dispatched` and must never be labeled `Completed` without a terminal result from the relevant Session/Execution. Avoid decorative status animation. Only a running Session spinner, live Work -pulse, and terminal cursor may loop. +pulse, status-orbit spin while running, and terminal cursor may loop. ## Component Specifications ### Buttons -- Primary: indigo fill, 4px radius, 32px default height. +- Primary: shared `.primary-button` (or product equivalent) — indigo fill, + 4px radius, 32px default height, disabled state uses muted fill without a second + “fake primary” style. Inventory CTAs such as `New Session` and `New Automation` + use this primitive; do not invent page-local primary button classes. - Secondary: elevated neutral surface, 1px border, 4px radius. - Icon button: 32–40px visible control; expand the hit area to 44px on coarse pointers. @@ -458,8 +491,12 @@ Execution is a mandatory product entity, not an optional visual section. - Running Work is expanded so current progress remains visible. Completed Work collapses to `Worked for {duration}` with only a chevron and expansion affordance. -- A running summary reads `Working · {duration}` and may append one current - activity label after an em dash. +- A running summary reads `Working for {duration}` (or `Working · {duration}`) and + may append one current activity label after an em dash. +- When Work is suspended for the user (HITL / permission), the fold label uses + time/mechanism wording such as **`Paused · Worked for {duration}`** — not a + second `Needs you` slogan. Product urgency remains on the Session header and + Composer. - Do not show Execution number, model, message count, step count, Tool count, or Child count in the visible Work row. Preserve Execution identity in product data and stable DOM identity. @@ -528,16 +565,28 @@ Execution is a mandatory product entity, not an optional visual section. ### Inspector, Drawers, and Entity Detail -- Context Inspector owns Agents, Changes, and Context; do not remove or merge - these tabs. -- Inspector is a persistent right column on wide Session layouts and an overlay - below 1181px. +- Context Inspector owns **Agents**, **Changes**, and **Context**; do not remove + or merge these tabs, and do not add a fourth tab for HITL or tokens. +- Inspector is a persistent right column on wide Session layouts (**312px** default, + resizable 280–460px) and an overlay below 1181px. - Persistent desktop navigation and Inspector widths are user-resizable and restored after collapse or focus mode. +- **Role split:** main canvas owns narrative work (transcript, Work, HITL + decision UI, Composer). The inspector owns machine state — agent structure, + file artifacts, and session bindings — not a second chat or primary CTA strip. +- **Quiet IDE density:** list rows and property rows, not card stacks; type floor + **≥11px** (primary ~12–12.5px, meta ~11–11.5px). Prefer hover fields and a 2px + brand inset edge for selection over large brand washes. +- **Header vs inspector anti-duplication:** header is glance (title, product + status, `N tools · tokens`); inspector is structure/precise bindings. If a fact + appears in both, header stays one number or short badge; inspector holds the + full path, ratio, or objective. - Todo detail is an independent project route because it owns durable Markdown, Plan, linked Sessions and Automations, and lifecycle actions. The Todo Board never duplicates this surface in a drawer. - Overlays use a scrim and a visible close action. +- Detailed Agents / Changes / Context presentation lives in + [`pages/session.md`](pages/session.md). ### Todo References and Live Source Context @@ -640,6 +689,12 @@ Motion explains state changes; it is not decoration. and expanded ordinary rows show their targets. - [ ] Confirm the Session header preserves Tool count and Token usage without restoring Execution/model/message metadata at 390px, 760px, and desktop. +- [ ] Confirm product status copy uses `Needs you` on inventories/header/Composer + and does not paint `Failed` as amber attention. +- [ ] Confirm Session canvas does not stack `Needs you` on Work, agent tree, and + inspector summary in addition to header + Composer. +- [ ] Confirm Context Inspector remains three tabs, quiet list density, and no + hosted HITL primary actions. - [ ] Confirm keyboard focus and accessible expansion state. - [ ] Confirm `prefers-reduced-motion`. - [ ] Confirm browser console is clean. diff --git a/design-system/pages/automations.md b/design-system/pages/automations.md index b8e7de2c..f4f7e000 100644 --- a/design-system/pages/automations.md +++ b/design-system/pages/automations.md @@ -27,22 +27,27 @@ attention, and recover the exact Session associated with every run. ## Automation List -- Group rows by decision value: `Needs attention`, `Scheduled`, `Paused`, then - `Inactive`. Groups are mutually exclusive. -- A latest Invocation with `failed` or `missed` always belongs in `Needs - attention`, even when the Automation definition is disabled. When no failed or - missed Invocation overrides it, active definitions belong in `Scheduled`, - paused definitions in `Paused`, and disabled definitions in `Inactive`. -- Each row shows status orbit, name, concise schedule or latest-run context, and - the real definition/Invocation state. `dispatched` is not `Completed`; the - final execution result is read from the linked Session. Use a flat row with - separators, not a summary card. -- Selection uses the shared indigo selected treatment. Needs-attention state - keeps explicit text in addition to amber color. +- Group rows by decision value: **`Needs you`**, **`Scheduled`**, **`Paused`**, + then **`Inactive`**. Groups are mutually exclusive. Prefer the product phrase + `Needs you` over `Needs attention` for the attention group title and primary + row state (compat aliases may still parse older strings in prototypes). +- A latest Invocation with `failed` or `missed` always belongs in `Needs you`, + even when the Automation definition is disabled. When no failed or missed + Invocation overrides it, active definitions belong in `Scheduled`, paused + definitions in `Paused`, and disabled definitions in `Inactive`. +- Each row shows the shared status orbit, name, concise schedule or latest-run + context, and the real definition/Invocation state. Terminal failures show + **`Failed`** with error tone; human gates show **`Needs you`** with attention + tone. `dispatched` is not `Completed`; the final execution result is read from + the linked Session. Use a flat row with separators, not a summary card. +- Selection uses the shared indigo selected treatment. Needs-you state keeps + explicit text in addition to amber color. - `Filter Automations` matches stable ID, name, instruction, schedule, linked Todo canonical content, and visible run state. Show a helpful no-results state in place of the list without hiding the filter or New Automation action. +- `New Automation` / primary detail actions use the shared primary button + primitive where a single dominant CTA is required. ## Selected Detail @@ -79,6 +84,8 @@ attention, and recover the exact Session associated with every run. - treating an Automation definition as if it were the execution transcript; - hiding failed or missed invocations behind schedule state, including for disabled definitions; +- titling the attention group `Needs attention` instead of product `Needs you`; +- painting `Failed` invocations with amber attention styling; - deriving `Completed` from a `dispatched` Invocation instead of showing the real Invocation state and linked Session result; - sharing one Session identity across multiple runs; diff --git a/design-system/pages/dashboard.md b/design-system/pages/dashboard.md index 35a52f66..fbfb65e1 100644 --- a/design-system/pages/dashboard.md +++ b/design-system/pages/dashboard.md @@ -41,18 +41,25 @@ Do not add greetings, marketing copy, metrics, charts, or a global Todo board. ## Rows - Use one full-width row per actionable item: - `state icon → entity/title/project context → time or action state`. + `state orbit → entity/title/project context → time or action state`. - Every row identifies its project and entity type because Home crosses project boundaries. -- Needs-you rows use amber plus the exact actionable state, including - `Inspection`, `Permission`, `Question`, `Blocked`, `Budget limited`, - `Failed`, or `Timed out`. - Running rows use the live orbit plus elapsed time. Review rows say what result +- Section and chip labels use the shared product lexicon from Master: + **`Needs you`**, **`Running`**, **`Ready to review`**, **`Upcoming`**. +- Needs-you rows use the shared amber attention orbit plus product state text. + Prefer the inventory label **`Needs you`** for actionable human gates; use + **`Failed`** with the error (red) tone for terminal failures — never restyle + failure as amber attention. Mechanism detail (`Permission`, `Question`, etc.) + may appear in accessible names or secondary copy without replacing the section + title. +- Running rows use the live orbit plus elapsed time. Review rows say what result is awaiting review. Upcoming rows identify the Automation schedule. - Current-project rows use exact Todo, Automation, or Session deep links. Other project rows may remain explicit prototype actions rather than fake deep links. - Prefer a few decision-worthy rows over exhaustive recent activity. Home must never become a second Sessions, Automations, or Todos list. +- Home section footnote and “work lives in Todos” cue stay quiet; Home only + surfaces decisions and live execution. ## Search and Attention diff --git a/design-system/pages/session.md b/design-system/pages/session.md index 59d50492..ed75dfc1 100644 --- a/design-system/pages/session.md +++ b/design-system/pages/session.md @@ -102,7 +102,12 @@ running: user message → expanded Work - `Work` is the visual disclosure for one authoritative Execution. It does not merge multiple Executions or infer ownership from visual proximity. - A completed Work summary reads `Worked for {duration}`. A running summary - reads `Working · {duration}` and may append `— {current activity}`. + reads `Working for {duration}` (prototype) or `Working · {duration}` and may + append `— {current activity}`. +- When Work is waiting on the user, the fold uses **`Paused · Worked for + {duration}`** (or equivalent time/mechanism copy). Do **not** put product + `Needs you` on the Work chevron row; that slogan belongs on the Session header + badge and Composer state only. - Do not show `Execution {number}`, steps, Tool count, Child count, model, or binding metadata in the visible Work row. - Preserve the Execution identity in product data even though it is visually @@ -204,11 +209,68 @@ running: user message → expanded Work ## Context Inspector -- Keep the three tabs: Agents, Changes, Context. -- Agents uses a compact tree showing role, title, Skill, Profile, and status. -- Changes shows summary counts and a navigable file list. -- Context shows working directory, model, and execution counters. -- Do not move the dark-mode switch here; it remains on the project rail. +Keep the three tabs: **Agents**, **Changes**, **Context**. Do not host HITL +Approve/Reject, Goal editing, or Queue management here — those stay in the +Composer dock and main canvas. Do not move the dark-mode switch here; it remains +on the project rail. + +### Shell + +- Default width **312px** (resize 280–460px); collapse and focus mode preserve the + last expanded width. +- Optional one-line **summary strip** under the chrome close control: + mechanism-or-product-short status + `N agents · N files`. When the Session + product status is `Needs you`, this strip prefers the HITL **request family** + (**`Permission`** or **`Question`**) so the canvas does not shout `Needs you` + three times. Terminal **`Failed`** uses the error word, never amber attention. +- Tab bar may carry counts (`Agents 4`, `Changes 3`). Active tab uses a brand + underline, not a filled pill block. +- Type floor ≥11px; primary labels ~12–12.5px; meta ~11–11.5px. No sub-11px + operational text in the inspector. + +### Agents + +- Compact operational tree: Lead root, children indented with a quiet vertical + guide (depth padding), not heavy L-shaped chrome or per-row card borders. +- Each row: role mark · **Role** + profile · one-line objective · trailing state. + Skills/profile extras stay muted or hidden by default; do not force a third + equal-weight text line. +- Selection: quiet hover field and/or 2px brand inset edge — not a large brand + wash card. +- Trailing state uses the shared status map. When Lead is gated, show the HITL + request family (**`Permission`** or **`Question`**) rather than repeating + product `Needs you`. Use **`Failed`**, **`Running`**, or **`Completed`** for + non-gate states. +- Activating a node selects it in the tree. When the product supports it, also + focus or scroll the main transcript to that agent’s turns; do not open a second + chat surface. + +### Changes + +- Summary line first: `N files` plus aggregate `+additions −deletions` when known. +- Flat navigable file rows: kind (M/A/D) · mono path · optional per-file diffstat. + No bordered mini-cards per file. +- Empty state: one muted line such as `No file changes yet.` +- Optional footer action: **Open full diff** into the Session canvas. Row click + opens that path’s diff in canvas when the product supports it. + +### Context + +- Property list (label left / value right), not a stack of bordered cells. +- Priority rows first: **Goal**, **Execution** (binding such as + `Suspended · Permission`, `Running`, `Completed`). +- Supporting rows: Model/Profile, Tokens (`used / limit` without vanity charts), + Working directory. +- Goal full prose control remains in Composer; Context only mirrors the binding. + +### Density and anti-patterns + +- Prefer rows and rules over cards; semantic color only on state dots, short + labels, and diffstat ±. +- Do not duplicate the transcript, auto-expand every agent/file, or place primary + Send/Approve actions in the inspector. +- Phase-aware default tab is allowed (Agents while multi-agent live work; + Changes when review/diff is the job; Context for bindings) without adding tabs. ## Composer @@ -217,6 +279,12 @@ running: user message → expanded Work - Pending HITL is rendered first and receives the strongest semantic field in the dock. Its question or permission and response actions must be immediately visible; Goal and Queue never sit above it. +- Composer chrome may show product state **`Needs you`** while a real HITL + decision is pending (paired with the Session header badge). Tool-level copy + inside HITL may still say **Permission** / **Question** for the request family. +- Terminal **`Failed`** Sessions keep error tone on both header and Composer + chrome (`Failed`). Do not restyle failure as amber product `Needs you` merely + because recovery still needs a human. - Goal is one compact textual summary showing status and objective with an entry to its controls. Never add a Goal progress bar. - Queued messages remain visible as compact rows with their content and @@ -240,6 +308,9 @@ running: user message → expanded Work - an expandable Composer or Goal progress bar; - HITL placed below Goal, Queue, or ordinary input; - live lime used on completed rows; +- stacked product `Needs you` labels on Work, agent tree, and inspector summary + in addition to header + Composer; +- inspector card stacks, sub-10px type, or brand-wash agent selection; - collapsed state that hides the existence of Execution; - tool output that expands beyond the work canvas; - raw parameter tables for ordinary read/search tools; diff --git a/design-system/pages/sessions.md b/design-system/pages/sessions.md index 9db209e4..7156c28b 100644 --- a/design-system/pages/sessions.md +++ b/design-system/pages/sessions.md @@ -16,22 +16,28 @@ attention, and start work directly without manufacturing a Todo first. - Do not repeat a visible `Sessions` page title below the toolbar. - Use one compact command row: local Session filter, one source filter, and the single primary `New Session` action. -- The list is centered at a maximum width of 1080px and grouped as `Needs you`, - `Running`, then `Recent`. +- The list is centered at a maximum width of 1080px and grouped as **`Needs you`**, + **`Running`**, then **`Recent`**. Group titles match the product lexicon in + Master; do not title the first group `Needs attention`. - Use flat rows and thin separators. Do not add summary cards, charts, runtime metrics, or a second dashboard above the list. +- `New Session` uses the shared primary button primitive. ## Session Rows -- Row order is status orbit, Session title and source context, then action state - or elapsed time. +- Row order is shared status orbit, Session title and source context, then action + state or elapsed time. - Every row identifies one source: `Todo`, `Automation`, or `Direct`. - Todo and Automation sources include the durable parent name when it is still available, otherwise their stable parent ID. Direct Sessions show the root Agent identity instead of inventing a parent work item. -- `Needs you` uses an amber icon plus `Inspection`, `Permission`, `Question`, or `Failed` text; - `Running` uses the live orbit plus elapsed time; completed Recent rows use a - green check or explicit `Completed` text. +- **`Needs you`** group rows use the amber attention orbit plus product state + text (`Needs you`, or more specific gate copy in the accessible name). + **`Failed`** rows use the error orbit/tone and the word `Failed` — they may + still sort under the Needs you *decision group* but must not look like amber + HITL. +- **`Running`** uses the live orbit plus elapsed time; completed Recent rows use a + green check or explicit completed text. - The whole row opens the exact Session URL. Destructive actions remain in an overflow menu or the Session detail and never compete with row navigation. diff --git a/design-system/pages/todos.md b/design-system/pages/todos.md index 9d3d4dec..10fe085a 100644 --- a/design-system/pages/todos.md +++ b/design-system/pages/todos.md @@ -87,16 +87,19 @@ Lane rules: Execution, and Automation inventory already loaded by the page. - Do not render a provisional operational line until Session and Automation inventory plus runtime and HITL snapshots are authoritative. Once ready, use - this precedence: `Needs you` for unresolved HITL or blocked/budget-limited - Goals; `Working` for live work; `Needs attention` for the latest stopped or - failed result; `Ready to review` for the latest completed result; `Scheduled` - for a future active Automation; otherwise `Idle`. A newer active or terminal - attempt supersedes an older failure, while an Automation dispatch alone is - never completion. -- The operational line uses one icon plus visible text and an optional short - detail after a separator. Keep it inside the existing card boundary without - a badge stack, nested card, action, lifecycle control, or full-width internal - divider. + this precedence: **`Needs you`** for unresolved HITL or blocked/budget-limited + Goals (product phrase; pair with attention tone); **`Failed`** for the latest + terminal failed/stopped attempt with **error** tone — never amber attention; + **`Working`** for live work (signal tone); **`Ready to review`** for the latest + completed result awaiting acceptance; **`Scheduled`** for a future active + Automation; otherwise **`Idle`**. A newer active or terminal attempt supersedes + an older failure, while an Automation dispatch alone is never completion. The + Board prototype demonstrates Needs you, Failed, Working, and Ready to review + operational lines. +- The operational line uses one shared status cue (orbit or icon) plus visible + text and an optional short detail after a separator. Keep it inside the existing + card boundary without a badge stack, nested card, action, lifecycle control, or + full-width internal divider. - Empty-lane guidance stays close to the lane header and aligns with card text; do not center it inside the full desktop drop target. - Pointer and touch dragging target the lane under the pointer rather than the diff --git a/design-system/prototypes/session.html b/design-system/prototypes/session.html index 4e1ca78b..8e11c497 100644 --- a/design-system/prototypes/session.html +++ b/design-system/prototypes/session.html @@ -11,10 +11,10 @@ - + - +
@@ -2432,12 +2628,12 @@

Direct Session ready

class="work-toggle" aria-expanded="true" aria-controls="current-work" - aria-label="Work segment needs user input, Needs you · Worked for 38s" + aria-label="Work segment needs user input, Paused · Worked for 38s" data-work-toggle data-current-work-toggle > - Needs you · Worked for 38s + Paused · Worked for 38s
@@ -2697,30 +2893,35 @@

Run focused Profile verification

+
+ Permission + + 4 agents · 3 files +
- - + +
@@ -2817,7 +3023,78 @@

Run focused Profile verification

const contextGoal = document.querySelector("[data-context-goal]"); const contextTokens = document.querySelector("[data-context-tokens]"); const contextExecution = document.querySelector("[data-context-execution]"); + const contextCwd = document.querySelector("[data-context-cwd]"); const inspectorChanges = document.querySelector("[data-inspector-changes]"); + const inspectorSummaryStatus = document.querySelector("[data-inspector-summary-status]"); + const inspectorSummaryMeta = document.querySelector("[data-inspector-summary-meta]"); + const agentsCountEl = document.querySelector("[data-agents-count]"); + const changesCountEl = document.querySelector("[data-changes-count]"); + const changesSummary = document.querySelector("[data-changes-summary]"); + const changesSummaryCount = document.querySelector("[data-changes-summary-count]"); + const changesSummaryStat = document.querySelector("[data-changes-summary-stat]"); + const changesFullDiff = document.querySelector('[data-action="在画布打开完整 diff"]'); + + const changeDiffHints = { + "project-session-profile.test.ts": { plus: 18, minus: 6 }, + "projects/registry.ts": { plus: 9, minus: 2 }, + "routes/sessions.ts": { plus: 4, minus: 1 }, + "session.tsx": { plus: 12, minus: 4 }, + "global-sse.tsx": { plus: 7, minus: 2 }, + "session.interaction.tsx": { plus: 9, minus: 1 }, + "app.js": { plus: 14, minus: 5 }, + "todos.html": { plus: 11, minus: 3 }, + "styles.css": { plus: 8, minus: 2 }, + "types.ts": { plus: 3, minus: 1 }, + "session-cwd-migration.ts": { plus: 16, minus: 4 }, + "session-cwd-migration.test.ts": { plus: 10, minus: 0 }, + }; + + function changeDiffFor(path) { + return changeDiffHints[path] || { plus: 4, minus: 1 }; + } + + function renderInspectorChanges(paths) { + const list = Array.isArray(paths) ? paths : []; + if (changesCountEl) changesCountEl.textContent = String(list.length); + if (changesSummary) changesSummary.hidden = list.length === 0; + if (changesFullDiff) changesFullDiff.hidden = list.length === 0; + if (!list.length) { + if (inspectorChanges) { + inspectorChanges.innerHTML = '

No file changes yet.

'; + } + if (changesSummaryCount) changesSummaryCount.textContent = "0"; + if (changesSummaryStat) changesSummaryStat.innerHTML = ""; + return; + } + let plusTotal = 0; + let minusTotal = 0; + const rows = list.map((path) => { + const stat = changeDiffFor(path); + plusTotal += stat.plus; + minusTotal += stat.minus; + return ``; + }); + if (inspectorChanges) inspectorChanges.innerHTML = rows.join(""); + if (changesSummaryCount) changesSummaryCount.textContent = String(list.length); + if (changesSummaryStat) { + changesSummaryStat.innerHTML = `+${plusTotal} −${minusTotal}`; + } + } + + function syncInspectorSummary(sample, leadTone) { + const visibleAgents = 1 + document.querySelectorAll("[data-profile-agent]:not([hidden])").length; + const changeCount = Array.isArray(sample.changes) ? sample.changes.length : 0; + if (agentsCountEl) agentsCountEl.textContent = String(visibleAgents); + if (inspectorSummaryStatus) { + inspectorSummaryStatus.textContent = presentInspectorSummaryStatus(sample, leadTone); + inspectorSummaryStatus.className = `inspector-summary-status ${leadTone}`; + } + if (inspectorSummaryMeta) { + const agentLabel = visibleAgents === 1 ? "1 agent" : `${visibleAgents} agents`; + const fileLabel = changeCount === 1 ? "1 file" : `${changeCount} files`; + inspectorSummaryMeta.textContent = `${agentLabel} · ${fileLabel}`; + } + } const hitlTitle = document.querySelector("[data-hitl-title]"); const hitlSummary = document.querySelector("[data-hitl-summary]"); const hitlDetail = document.querySelector("[data-hitl-detail]"); @@ -2871,7 +3148,7 @@

Run focused Profile verification

{ title: "Request recoverable deletion permission", state: "current" }, { title: "Move only the resolved worktree to Trash", state: "pending" }, ], - primaryMessage: "Resolve the exact managed worktree and stop before deleting anything that cannot be recovered.", primaryWorkLabel: "Worked for 54s", primaryCommentary: "Resolved the managed worktree record and verified it is not the project root or an active Session directory.", primaryReasoning: "Only the exact stale worktree path is eligible; the parent workspace and runtime state remain outside the requested operation.", primaryTools: "git_status, file_read", primaryTarget: "managed worktree registry", primaryResponse: "Target resolved. Cleanup is paused at the recoverable permission boundary.", showSecondary: true, secondaryMessage: "Remove only the resolved stale worktree and leave all other project data untouched.", currentWorkLabel: "Needs you · Worked for 22s", currentCommentary: "The exact stale worktree is ready to move to Trash after your approval.", currentReasoning: "The target passed project-root, active-Session, and runtime-path guards.", needsHitl: true, showQueue: false, composerState: "Needs you", objective: "Review recoverable worktree cleanup", execution: "Suspended · Permission", goal: "None · ordinary work", tokens: "21,804 / 200,000 tokens", changes: [], + primaryMessage: "Resolve the exact managed worktree and stop before deleting anything that cannot be recovered.", primaryWorkLabel: "Worked for 54s", primaryCommentary: "Resolved the managed worktree record and verified it is not the project root or an active Session directory.", primaryReasoning: "Only the exact stale worktree path is eligible; the parent workspace and runtime state remain outside the requested operation.", primaryTools: "git_status, file_read", primaryTarget: "managed worktree registry", primaryResponse: "Target resolved. Cleanup is paused at the recoverable permission boundary.", showSecondary: true, secondaryMessage: "Remove only the resolved stale worktree and leave all other project data untouched.", currentWorkLabel: "Paused · Worked for 22s", currentCommentary: "The exact stale worktree is ready to move to Trash after your approval.", currentReasoning: "The target passed project-root, active-Session, and runtime-path guards.", needsHitl: true, showQueue: false, composerState: "Needs you", objective: "Review recoverable worktree cleanup", execution: "Suspended · Permission", goal: "None · ordinary work", tokens: "21,804 / 200,000 tokens", changes: [], hitlTitle: "Move one stale worktree to Trash", hitlSummary: "The exact managed worktree has been resolved and can be recovered from Trash.", hitlDetail: "/workspace/.archcode-worktrees/stale-profile-review", questionTitle: "What should happen to the stale worktree?", questionSummary: "Choose one recovery-safe option. No project source has been changed.", }, "sess-work-search-discussion": { @@ -2985,7 +3262,7 @@

Run focused Profile verification

{ title: "Run focused contract verification", state: "current" }, { title: "Return Todo for review", state: "pending" }, ], - primaryWorkLabel: "Needs you · Worked for 3m 41s", + primaryWorkLabel: "Paused · Worked for 3m 41s", primaryCommentary: "The implementation is ready. One scoped verification command is waiting for permission.", primaryReasoning: "The command is limited to the new Session-admission contract and does not mutate project data.", primaryTools: "bash", @@ -3022,7 +3299,7 @@

Run focused Profile verification

primaryResponse: "First pass complete. The recommendation can remain project-owned and apply only when a new root Session has no explicit Profile override.", showSecondary: true, secondaryMessage: "Add the focused contract coverage. Stop if this requires changing existing Session bindings.", - currentWorkLabel: "Needs you · Worked for 38s", + currentWorkLabel: "Paused · Worked for 38s", preserveRepresentative: true, needsHitl: true, showQueue: true, @@ -3097,7 +3374,7 @@

Run focused Profile verification

showSecondary: false, needsHitl: false, showQueue: false, - composerState: failed ? "Needs you" : running ? "Running" : "Idle", + composerState: failed ? "Failed" : running ? "Running" : "Idle", objective: `Run ${title}`, execution: failed ? "Failed · Needs decision" : running ? "Running" : "Completed", goal: "None · Automation invocation", @@ -3164,12 +3441,45 @@

Run focused Profile verification

} function presentWorkLabel({ running = false, needsYou = false, duration = "0s", explicit } = {}) { - if (explicit) return explicit; + // Work fold mirrors mechanism/time — product "Needs you" lives on header + composer only. + if (explicit) { + return String(explicit).replace(/^Needs you\s*·\s*/i, "Paused · "); + } if (running) return `Working for ${duration}`; - if (needsYou) return `Needs you · Worked for ${duration}`; + if (needsYou) return `Paused · Worked for ${duration}`; return `Worked for ${duration}`; } + function presentGateMechanism(sample) { + const family = String(sample.hitlFamily || sample.hitlKind || "").toLowerCase(); + if (family === "question" || family === "ask_user" || family === "ask-user") return "Question"; + const execution = String(sample.execution || ""); + if (/\bquestion\b/i.test(execution) || /ask user/i.test(execution)) return "Question"; + return "Permission"; + } + + function presentAgentStateLabel(sample, leadTone) { + if (leadTone === "failed" || sample.statusTone === "failed" || sample.status === "Failed") return "Failed"; + if (leadTone === "needs-you" || leadTone === "attention" || sample.needsHitl || sample.status === "Needs you") { + return presentGateMechanism(sample); + } + if (leadTone === "completed" || leadTone === "done") return "Completed"; + if (leadTone === "running") return "Running"; + if (leadTone === "idle") return "Idle"; + return sample.status || "Stopped"; + } + + function presentInspectorSummaryStatus(sample, leadTone) { + if (leadTone === "failed" || sample.statusTone === "failed" || sample.status === "Failed") return "Failed"; + if (leadTone === "needs-you" || leadTone === "attention" || sample.needsHitl || sample.status === "Needs you") { + return presentGateMechanism(sample); + } + if (leadTone === "running") return "Running"; + if (leadTone === "completed" || leadTone === "done") return "Done"; + if (leadTone === "idle") return "Idle"; + return sample.status || "Session"; + } + function syncWorkToggle({ toggle, labelNode, liveNode, label, running = false, needsYou = false }) { if (labelNode) { labelNode.textContent = label; @@ -3236,7 +3546,18 @@

Run focused Profile verification

const unavailable = sample.composerState === "Unavailable"; const idle = sample.composerState === "Idle"; const running = sample.composerState === "Running"; - const attention = sample.composerState === "Needs attention" || sample.composerState === "Needs you"; + const failed = + sample.composerState === "Failed" || + sample.statusTone === "failed" || + sample.statusTone === "error" || + sample.status === "Failed"; + // Product "Needs you" is only for real attention/HITL — never for terminal Failed. + const attention = + !failed && + (sample.composerState === "Needs attention" || + sample.composerState === "Needs you" || + sample.needsHitl || + sample.statusTone === "attention"); const queueing = running || attention; const composerPrompt = sample.empty ? "What should the Agent do?" @@ -3244,7 +3565,9 @@

Run focused Profile verification

? "Send a follow-up" : unavailable ? "Session unavailable" - : "Queue a message"; + : failed + ? "Send a recovery follow-up" + : "Queue a message"; composer?.setAttribute("aria-label", composerPrompt); collapsedComposer?.setAttribute("aria-label", composerPrompt); @@ -3255,7 +3578,15 @@

Run focused Profile verification

} composerStates.forEach((state) => { - const tone = running ? " running" : attention ? " attention" : idle || unavailable ? " idle" : ""; + const tone = running + ? " running" + : failed + ? " failed" + : attention + ? " attention" + : idle || unavailable + ? " idle" + : ""; state.className = `composer-state${tone}`; state.hidden = false; }); @@ -3266,9 +3597,11 @@

Run focused Profile verification

? "Ready" : running ? "Running" - : attention - ? "Needs you" - : sample.composerState; + : failed + ? "Failed" + : attention + ? "Needs you" + : sample.composerState; }); if (composerCollapse) composerCollapse.hidden = !sample.needsHitl; @@ -3347,7 +3680,12 @@

Run focused Profile verification

? `automation=${encodeURIComponent(sample.automationId)}&` : ""; const href = item.href || `./session.html?${todoParam}session=${encodeURIComponent(item.id)}`; - const tone = item.tone === "attention" ? "attention" : item.tone === "running" ? "running" : item.tone === "done" ? "done" : ""; + const tone = + item.tone === "attention" ? "attention" : + item.tone === "running" ? "running" : + item.tone === "done" ? "done" : + item.tone === "failed" || item.tone === "error" ? "failed" : + ""; return `${escapeSessionHtml(item.title)}${escapeSessionHtml(item.meta || "")}${escapeSessionHtml(item.status)}`; }).join("")}`; closeSessionPicker(); @@ -3526,13 +3864,17 @@

Run focused Profile verification

? "idle" : "stopped"; leadState.className = `agent-state ${leadTone}`; - leadStateText.textContent = sample.status; + leadStateText.textContent = presentAgentStateLabel(sample, leadTone); contextGoal.textContent = sample.goal; - contextTokens.textContent = sample.tokens; + contextTokens.textContent = String(sample.tokens || "").replace(/\s*tokens$/i, ""); contextExecution.textContent = sample.execution; + if (contextCwd) contextCwd.textContent = sample.cwd || "/workspace/archcode"; const supportingAgents = new Set(sample.supportingAgents || []); - document.querySelectorAll("[data-profile-agent]").forEach((node) => { node.hidden = !(sample.preserveRepresentative || supportingAgents.has(node.dataset.profileAgent)); }); - inspectorChanges.innerHTML = sample.changes.length ? sample.changes.map((path) => `
M${escapeSessionHtml(path)}Changed
`).join("") : '
No uncommitted changes in this Session.
'; + document.querySelectorAll("[data-profile-agent]").forEach((node) => { + node.hidden = !(sample.preserveRepresentative || supportingAgents.has(node.dataset.profileAgent)); + }); + renderInspectorChanges(sample.changes || []); + syncInspectorSummary(sample, leadTone); const projectLink = document.querySelector("[data-session-project-link]"); if (projectLink) { @@ -3709,6 +4051,21 @@

Run focused Profile verification

}); }); + // Prototype affordance only: selection chrome + toast for transcript focus. + // Product may scroll the canvas to that agent's turns when it supports it. + document.querySelectorAll(".agent-node").forEach((node) => { + node.addEventListener("click", () => { + document.querySelectorAll(".agent-node").forEach((other) => { + const active = other === node; + other.classList.toggle("active", active); + if (active) other.setAttribute("aria-current", "true"); + else other.removeAttribute("aria-current"); + }); + const role = node.querySelector(".agent-line strong")?.textContent?.trim() || "Agent"; + window.showToast?.(`Focus ${role} turns in transcript when product supports it`); + }); + }); + const hitlOptions = document.querySelectorAll("[data-hitl-option]"); const hitlCustomAnswer = document.querySelector("[data-hitl-custom-answer]"); const hitlQuestionSubmit = document.querySelector("[data-question-submit]"); diff --git a/design-system/prototypes/todos.html b/design-system/prototypes/todos.html index 318b6d3d..45df2eb3 100644 --- a/design-system/prototypes/todos.html +++ b/design-system/prototypes/todos.html @@ -615,6 +615,7 @@ .todo-operational.running { color: var(--signal-foreground); } .todo-operational.attention { color: var(--warning); } + .todo-operational.failed { color: var(--error); } .todo-operational.review { color: var(--brand); } .lane-orbit { @@ -951,6 +952,11 @@ background: var(--brand-field); } + .peek-runtime.is-failed { + border-color: color-mix(in srgb, var(--error) 26%, var(--border-subtle)); + background: var(--error-field); + } + .peek-runtime-mark { flex: 0 0 auto; width: 8px; @@ -972,6 +978,11 @@ box-shadow: 0 0 0 3px color-mix(in srgb, var(--brand) 18%, transparent); } + .peek-runtime.is-failed .peek-runtime-mark { + background: var(--error); + box-shadow: 0 0 0 3px color-mix(in srgb, var(--error) 18%, transparent); + } + .peek-runtime strong, .peek-runtime span { display: block; @@ -1052,6 +1063,7 @@ } .peek-orbit.attention { background: var(--warning); } + .peek-orbit.failed { background: var(--error); } .peek-orbit.review { background: var(--brand); } .peek-orbit.automation, .peek-orbit.discussion { background: var(--text-tertiary); } @@ -1084,6 +1096,7 @@ .peek-link-state.running { color: var(--signal-foreground); } .peek-link-state.attention { color: var(--warning); } + .peek-link-state.failed { color: var(--error); } .peek-link-state.review { color: var(--brand); } .peek-footnote { @@ -1659,6 +1672,7 @@ .row-operational.running { color: var(--signal-foreground); } .row-operational.attention { color: var(--warning); } + .row-operational.failed { color: var(--error); } .row-operational.review { color: var(--brand); } .row-actions { @@ -1983,6 +1997,11 @@ background: var(--success); } + .work-orbit.failed { + border-color: var(--error); + background: var(--error); + } + .work-row strong { display: block; font-size: 12px; @@ -2012,6 +2031,10 @@ color: var(--warning); } + .work-row-state.failed { + color: var(--error); + } + .work-row-state.review { color: var(--brand); } @@ -2389,7 +2412,7 @@

Ready

In Progress

Execution is attached here

- 3 + 4
@@ -2435,17 +2458,9 @@

Done

-

In Progress

Execution is attached here

3 +

In Progress

Execution is attached here

4
-
- - -
-
+
+ + +
+ +
+ + +
+
@@ -3100,7 +3140,7 @@

Run focused Profile verification

if (changesFullDiff) changesFullDiff.hidden = list.length === 0; if (!list.length) { if (inspectorChanges) { - inspectorChanges.innerHTML = '

No file changes yet.

'; + inspectorChanges.innerHTML = '
No file changes yet.
'; } if (changesSummaryCount) changesSummaryCount.textContent = "0"; if (changesSummaryStat) changesSummaryStat.innerHTML = ""; diff --git a/design-system/prototypes/sessions.html b/design-system/prototypes/sessions.html index c34af4f3..c467d53a 100644 --- a/design-system/prototypes/sessions.html +++ b/design-system/prototypes/sessions.html @@ -281,6 +281,21 @@ text-align: center; } + .sessions-empty-icon { + display: grid; + place-items: center; + width: 56px; + height: 56px; + margin: 0 auto 16px; + border-radius: 50%; + background: color-mix(in srgb, var(--brand) 8%, var(--bg-surface)); + color: var(--brand); + } + + .sessions-empty-icon [data-icon] { + font-size: 24px; + } + .sessions-empty strong, .sessions-empty span { display: block; } .sessions-empty strong { color: var(--text-secondary); font-size: 13px; } @@ -380,6 +395,7 @@
diff --git a/design-system/prototypes/styles.css b/design-system/prototypes/styles.css index 2fa49630..2878bd88 100644 --- a/design-system/prototypes/styles.css +++ b/design-system/prototypes/styles.css @@ -815,9 +815,11 @@ p { } .primary-button { + position: relative; border: 1px solid var(--brand); background: var(--brand); color: var(--brand-ink); + overflow: hidden; box-shadow: 0 1px 3px rgba(99, 102, 241, 0.3), inset 0 1px 0 rgba(255, 255, 255, 0.1); @@ -828,6 +830,27 @@ p { transform var(--motion-hover) var(--ease-smooth); } +.primary-button::after { + content: ""; + position: absolute; + top: 50%; + left: 50%; + width: 0; + height: 0; + border-radius: 50%; + background: rgba(255, 255, 255, 0.3); + transform: translate(-50%, -50%); + pointer-events: none; + opacity: 0; +} + +.primary-button:active::after { + width: 300px; + height: 300px; + opacity: 1; + transition: width 400ms var(--ease-smooth), height 400ms var(--ease-smooth), opacity 300ms var(--ease-smooth); +} + .primary-button:hover { border-color: var(--brand-hover); background: var(--brand-hover); diff --git a/design-system/prototypes/todos.html b/design-system/prototypes/todos.html index ce16530a..2a01ef1f 100644 --- a/design-system/prototypes/todos.html +++ b/design-system/prototypes/todos.html @@ -315,10 +315,32 @@ background: var(--bg-overlay); color: var(--text-primary); box-shadow: var(--elevation-lg); + animation: modal-in 200ms var(--ease-smooth); + } + + @keyframes modal-in { + from { + opacity: 0; + transform: scale(0.96); + } + to { + opacity: 1; + transform: scale(1); + } } .todo-create-dialog::backdrop { background: rgb(0 0 0 / 58%); + animation: backdrop-in 200ms var(--ease-standard); + } + + @keyframes backdrop-in { + from { + opacity: 0; + } + to { + opacity: 1; + } } .todo-create-form { @@ -658,6 +680,19 @@ text-align: left; } + .lane-empty-icon { + display: inline-flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + margin-bottom: 8px; + border-radius: 50%; + background: color-mix(in srgb, var(--neutral) 8%, transparent); + color: var(--neutral); + font-size: 16px; + } + .lane-empty strong { display: block; color: var(--text-secondary); @@ -689,6 +724,26 @@ text-align: center; } + .flat-empty-icon { + display: grid; + place-items: center; + width: 56px; + height: 56px; + margin: 0 auto 16px; + border-radius: 50%; + background: color-mix(in srgb, var(--brand) 8%, var(--bg-surface)); + color: var(--brand); + } + + .flat-empty-icon [data-icon] { + font-size: 24px; + } + + .flat-empty strong, + .flat-empty span { display: block; } + .flat-empty strong { color: var(--text-secondary); font-size: 13px; margin-bottom: 6px; } + .flat-empty span { font-size: 11px; } + .sidebar-toolbar.sessions-only { grid-template-columns: minmax(0, 1fr); } From 893ee339d6373738208286e2503464372565e22f Mon Sep 17 00:00:00 2001 From: bo Date: Tue, 11 Aug 2026 02:13:24 +0800 Subject: [PATCH 06/19] polish: comprehensive UI refinements across all prototypes - Queue messages: Complete redesign with card-based layout, proper hierarchy, better spacing - Typography: Increased font sizes and weights for better readability (13-14px body, 600-700 weight) - Spacing: Increased padding across todos, sessions, automations, dashboard rows (10-16px) - Hover effects: Enhanced with translateY(-2px) and --shadow-hover token for consistency - Count badges: Stronger contrast with --bg-elevated and --text-secondary - Drag handles: Visible by default (opacity 0.35) instead of hidden - Dashboard: Larger heading (24px), better row typography (14px/12px) - Shadow tokens: Added --shadow-base and --shadow-hover for consistent depth system All prototypes now have cohesive visual density, readable typography, and polished micro-interactions. --- design-system/prototypes/automations.html | 20 +++---- design-system/prototypes/dashboard.html | 34 +++++------ design-system/prototypes/session.html | 71 ++++++++++++++--------- design-system/prototypes/sessions.html | 10 ++-- design-system/prototypes/styles.css | 2 + design-system/prototypes/todos.html | 31 +++++----- 6 files changed, 92 insertions(+), 76 deletions(-) diff --git a/design-system/prototypes/automations.html b/design-system/prototypes/automations.html index bad8ff18..570d20e0 100644 --- a/design-system/prototypes/automations.html +++ b/design-system/prototypes/automations.html @@ -122,8 +122,8 @@ align-items: start; gap: 10px; width: 100%; - min-height: 70px; - padding: 13px 8px; + min-height: 74px; + padding: 15px 12px; border-bottom: 1px solid var(--border-subtle); background: transparent; color: var(--text-secondary); @@ -139,8 +139,8 @@ .automation-row[aria-selected="true"] { background: var(--bg-hover); color: var(--text-primary); - transform: translateY(-1px); - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04); + transform: translateY(-2px); + box-shadow: var(--shadow-hover); } .automation-row:active { @@ -148,7 +148,7 @@ } .automation-row[aria-selected="true"] { - box-shadow: inset 2px 0 0 var(--brand), 0 1px 2px rgba(0, 0, 0, 0.04); + box-shadow: inset 2px 0 0 var(--brand), var(--shadow-hover); } /* automation-orbit tones come from shared styles.css */ @@ -158,21 +158,21 @@ .automation-row small { display: block; } .automation-row strong { - font-size: 12px; - font-weight: 650; + font-size: 13px; + font-weight: 600; } .automation-row small { margin-top: 5px; color: var(--text-tertiary); - font-size: 10px; + font-size: 11px; line-height: 1.45; } .automation-row-state { color: var(--text-tertiary); - font-size: 9px; - font-weight: 650; + font-size: 10.5px; + font-weight: 600; white-space: nowrap; } diff --git a/design-system/prototypes/dashboard.html b/design-system/prototypes/dashboard.html index fcc8f47c..d5058bcf 100644 --- a/design-system/prototypes/dashboard.html +++ b/design-system/prototypes/dashboard.html @@ -51,8 +51,8 @@ } .home-header h1 { - font-size: 22px; - font-weight: 680; + font-size: 24px; + font-weight: 700; line-height: 1.15; letter-spacing: -0.03em; } @@ -125,7 +125,7 @@ border: 1px solid var(--border-subtle); border-radius: var(--shape-card); background: var(--bg-surface); - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04); + box-shadow: var(--shadow-base); } .home-section.attention { @@ -172,12 +172,12 @@ .home-section-count { min-width: 22px; padding: 2px 7px; - border: 1px solid var(--border-subtle); + border: 1px solid var(--border-default); border-radius: 999px; - background: var(--bg-muted); - color: var(--text-tertiary); + background: var(--bg-elevated); + color: var(--text-secondary); font-size: 11px; - font-weight: 650; + font-weight: 700; text-align: center; font-variant-numeric: tabular-nums; } @@ -196,8 +196,8 @@ grid-template-columns: 14px minmax(0, 1fr) auto; align-items: center; gap: 12px; - min-height: 64px; - padding: 12px 14px; + min-height: 68px; + padding: 14px 16px; border: 0; border-bottom: 1px solid var(--border-subtle); background: transparent; @@ -216,8 +216,8 @@ .home-row:hover { background: var(--bg-hover); - transform: translateY(-1px); - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04); + transform: translateY(-2px); + box-shadow: var(--shadow-hover); } .home-row:active { @@ -234,23 +234,23 @@ .home-row-copy strong { overflow: hidden; color: var(--text-primary); - font-size: 13.5px; - font-weight: 620; + font-size: 14px; + font-weight: 600; letter-spacing: -0.015em; text-overflow: ellipsis; white-space: nowrap; } .home-row-copy small { - margin-top: 4px; + margin-top: 5px; color: var(--text-tertiary); - font-size: 11.5px; + font-size: 12px; } .home-row-state { color: var(--text-tertiary); - font-size: 11.5px; - font-weight: 640; + font-size: 12px; + font-weight: 600; font-variant-numeric: tabular-nums; white-space: nowrap; } diff --git a/design-system/prototypes/session.html b/design-system/prototypes/session.html index 31fb479f..784bac87 100644 --- a/design-system/prototypes/session.html +++ b/design-system/prototypes/session.html @@ -1408,23 +1408,22 @@ } .queue-list { + display: grid; + gap: 8px; + padding: 12px; border-top: 1px solid var(--border-subtle); - border-bottom: 1px solid var(--border-subtle); + background: var(--bg-muted); } .queue-row { display: grid; - grid-template-columns: 58px minmax(0, 1fr) auto auto; - align-items: center; + grid-template-columns: minmax(0, 1fr); gap: 8px; - min-height: 36px; - padding: 5px 8px; - color: var(--text-secondary); - font-size: 10px; - } - - .queue-row + .queue-row { - border-top: 1px solid var(--border-subtle); + padding: 12px 14px; + border: 1px solid var(--border-subtle); + border-radius: 8px; + background: var(--bg-surface); + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04); } body[data-prototype-state="hitl"] .queue-row:nth-child(n + 2) { @@ -1434,9 +1433,13 @@ .queue-status { display: inline-flex; align-items: center; - gap: 5px; + gap: 6px; + margin-bottom: 6px; color: var(--text-tertiary); - font-weight: 650; + font-size: 10px; + font-weight: 680; + letter-spacing: 0.02em; + text-transform: uppercase; } .queue-status.steering { @@ -1444,34 +1447,44 @@ } .queue-copy { - overflow: hidden; + margin-bottom: 8px; color: var(--text-primary); - font-size: 11px; - text-overflow: ellipsis; - white-space: nowrap; + font-size: 13px; + font-weight: 500; + line-height: 1.5; } .queue-model { + margin-bottom: 10px; color: var(--text-tertiary); font-family: var(--font-stack-mono); - font-size: 9px; - white-space: nowrap; + font-size: 11px; } .queue-actions { display: flex; - gap: 2px; + gap: 6px; + padding-top: 8px; + border-top: 1px solid var(--border-subtle); } .queue-actions button { - min-height: 26px; - padding: 0 5px; - border-radius: 3px; - color: var(--text-tertiary); - font-size: 9px; + min-height: 28px; + padding: 0 10px; + border: 1px solid var(--border-default); + border-radius: 6px; + background: transparent; + color: var(--text-secondary); + font-size: 11px; + font-weight: 600; + transition: + background var(--motion-hover) var(--ease-smooth), + border-color var(--motion-hover) var(--ease-smooth), + color var(--motion-hover) var(--ease-smooth); } .queue-actions button:hover { + border-color: var(--border-strong); background: var(--bg-hover); color: var(--text-primary); } @@ -2892,15 +2905,15 @@

Run focused Profile verification

Queued - After verification, summarize Profile precedence and remaining risk. +

After verification, summarize Profile precedence and remaining risk.

GPT-5.6 Luna · deep - +
Steering - Also verify the recommendation is visible before Start Work. +

Also verify the recommendation is visible before Start Work.

GPT-5.6 Luna · deep - +
diff --git a/design-system/prototypes/sessions.html b/design-system/prototypes/sessions.html index c467d53a..80d75666 100644 --- a/design-system/prototypes/sessions.html +++ b/design-system/prototypes/sessions.html @@ -200,8 +200,8 @@ grid-template-columns: 14px minmax(0, 1fr) auto; align-items: center; gap: 11px; - min-height: 56px; - padding: 7px 9px; + min-height: 60px; + padding: 12px; border-bottom: 1px solid var(--border-subtle); color: var(--text-secondary); transition: @@ -214,8 +214,8 @@ .session-row:hover { background: var(--bg-hover); color: var(--text-primary); - transform: translateY(-1px); - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04); + transform: translateY(-2px); + box-shadow: var(--shadow-hover); } .session-row:active { @@ -233,7 +233,7 @@ overflow: hidden; color: var(--text-primary); font-size: 13px; - font-weight: 650; + font-weight: 600; text-overflow: ellipsis; white-space: nowrap; } diff --git a/design-system/prototypes/styles.css b/design-system/prototypes/styles.css index 2878bd88..bb3e9a0d 100644 --- a/design-system/prototypes/styles.css +++ b/design-system/prototypes/styles.css @@ -46,6 +46,8 @@ --elevation-sm: 0 10px 30px rgb(26 31 25 / 9%); --elevation-md: 0 14px 34px rgb(0 0 0 / 10%); --elevation-lg: 0 22px 56px rgb(25 28 22 / 18%); + --shadow-base: 0 1px 3px rgba(0, 0, 0, 0.08), 0 1px 2px rgba(0, 0, 0, 0.06); + --shadow-hover: 0 4px 12px rgba(0, 0, 0, 0.12), 0 2px 4px rgba(0, 0, 0, 0.08); --shape-control: 6px; --shape-card: 8px; --shape-popover: 8px; diff --git a/design-system/prototypes/todos.html b/design-system/prototypes/todos.html index 2a01ef1f..5fbf3d6b 100644 --- a/design-system/prototypes/todos.html +++ b/design-system/prototypes/todos.html @@ -461,12 +461,12 @@ min-width: 22px; margin-top: -2px; padding: 2px 7px; - border: 1px solid var(--border-subtle); + border: 1px solid var(--border-default); border-radius: 999px; - background: color-mix(in srgb, var(--bg-muted) 70%, transparent); - color: var(--text-tertiary); + background: var(--bg-elevated); + color: var(--text-secondary); font-size: 11px; - font-weight: 650; + font-weight: 700; text-align: center; font-variant-numeric: tabular-nums; } @@ -480,13 +480,13 @@ .todo-card { position: relative; display: grid; - grid-template-columns: 30px minmax(0, 1fr); + grid-template-columns: 36px minmax(0, 1fr); min-height: 56px; overflow: hidden; border: 1px solid var(--border-subtle); border-radius: var(--shape-card); background: var(--bg-surface); - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04); + box-shadow: var(--shadow-base); transition: background var(--motion-hover) var(--ease-smooth), border-color var(--motion-hover) var(--ease-smooth), @@ -497,8 +497,8 @@ .todo-card:hover { border-color: var(--border-default); background: var(--bg-hover); - transform: translateY(-1px); - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.06); + transform: translateY(-2px); + box-shadow: var(--shadow-hover); } .todo-card:active { @@ -526,7 +526,7 @@ background: transparent; color: var(--text-muted); cursor: grab; - opacity: 0; + opacity: 0.35; transition: opacity var(--motion-hover) var(--ease-standard); } @@ -546,7 +546,7 @@ flex-direction: column; justify-content: center; min-width: 0; - padding: 8px 12px 8px 0; + padding: 10px 14px 10px 0; background: transparent; text-align: left; } @@ -620,10 +620,10 @@ display: -webkit-box; overflow: hidden; color: var(--text-secondary); - font-size: 13.75px; + font-size: 14px; font-weight: 500; letter-spacing: -0.012em; - line-height: 1.48; + line-height: 1.5; -webkit-box-orient: vertical; -webkit-line-clamp: 2; transition: color var(--motion-hover) var(--ease-standard); @@ -1642,7 +1642,8 @@ font-family: var(--font-stack-mono); font-size: 11px; font-variant-numeric: tabular-nums; - color: var(--text-muted); + font-weight: 600; + color: var(--text-secondary); } .list-rows { @@ -1654,8 +1655,8 @@ grid-template-columns: 18px minmax(0, 1fr) auto; align-items: center; gap: 12px; - min-height: 52px; - padding: 8px 6px; + min-height: 56px; + padding: 10px 12px; border-bottom: 1px solid var(--border-subtle); background: transparent; transition: background var(--motion-hover) var(--ease-standard); From cb98acdc576c66455a38e6ba67be0d10bfa64979 Mon Sep 17 00:00:00 2001 From: bo Date: Tue, 11 Aug 2026 02:17:01 +0800 Subject: [PATCH 07/19] polish: enhance session.html UI components - Queue messages: Complete card-based redesign with proper spacing and hierarchy - Tool runs: Increased height (36px), added hover effects with translateX(2px) - HITL options: Card-style with borders, better spacing (48px height, 10-12px padding) - Composer controls: Enhanced model/variant pickers with borders and elevated bg - Composer action button: Larger (36px), added hover lift and active scale - Menu items: Increased to 36px height with 13px font size - Typography: Improved readability across all interactive components All interactive elements now have consistent hover states, proper spacing, and cohesive visual treatment. --- design-system/prototypes/session.html | 105 +++++++++++++++++--------- 1 file changed, 70 insertions(+), 35 deletions(-) diff --git a/design-system/prototypes/session.html b/design-system/prototypes/session.html index 784bac87..208c2712 100644 --- a/design-system/prototypes/session.html +++ b/design-system/prototypes/session.html @@ -778,11 +778,20 @@ grid-template-columns: minmax(98px, auto) minmax(0, 1fr) auto; align-items: center; gap: 9px; - min-height: 32px; - padding: 5px 7px; - border-radius: 4px; + min-height: 36px; + padding: 7px 9px; + border-radius: 5px; color: var(--text-secondary); text-align: left; + transition: + background var(--motion-hover) var(--ease-smooth), + transform var(--motion-hover) var(--ease-smooth); + } + + .tool-child:hover, + .tool-row:hover { + background: var(--bg-hover); + transform: translateX(2px); } .tool-child + .tool-child { @@ -793,14 +802,15 @@ .tool-target { overflow: hidden; color: var(--text-secondary); - font-size: 12px; + font-size: 12.5px; text-overflow: ellipsis; white-space: nowrap; } .tool-status { color: var(--text-tertiary); - font-size: 10px; + font-size: 11px; + font-weight: 600; white-space: nowrap; } @@ -1144,15 +1154,15 @@ .hitl-panel h2, .hitl-question { - font-size: 13px; - font-weight: 500; - line-height: 1.55; + font-size: 14px; + font-weight: 600; + line-height: 1.5; } .hitl-summary { margin-top: 4px; color: var(--text-secondary); - font-size: 12px; + font-size: 13px; line-height: 1.55; } @@ -1198,30 +1208,36 @@ position: relative; display: flex; min-width: 0; - min-height: 44px; + min-height: 48px; align-items: flex-start; - gap: 8px; - padding: 6px 8px; - border-radius: 4px; + gap: 10px; + padding: 10px 12px; + border: 1px solid var(--border-subtle); + border-radius: 6px; + background: var(--bg-surface); color: var(--text-secondary); - font-size: 12px; + font-size: 13px; text-align: left; cursor: pointer; transition: background var(--motion-hover) var(--ease-smooth), + border-color var(--motion-hover) var(--ease-smooth), color var(--motion-hover) var(--ease-smooth); } .hitl-option:hover { + border-color: var(--border-default); background: var(--bg-hover); color: var(--text-primary); } .hitl-option:focus-within { + border-color: var(--brand); box-shadow: var(--focus); } .hitl-option.selected { + border-color: var(--brand); background: var(--brand-field); color: var(--text-primary); } @@ -1734,21 +1750,34 @@ display: inline-flex; max-width: 190px; min-width: 0; - height: 28px; + height: 32px; align-items: center; - gap: 4px; - padding: 0 6px; - border-radius: 4px; + gap: 5px; + padding: 0 9px; + border: 1px solid var(--border-subtle); + border-radius: 6px; + background: var(--bg-elevated); color: var(--text-secondary); - font-size: 11px; - font-weight: 560; + font-size: 12px; + font-weight: 600; text-align: left; + transition: + background var(--motion-hover) var(--ease-smooth), + border-color var(--motion-hover) var(--ease-smooth), + color var(--motion-hover) var(--ease-smooth); + } + + .composer-model-trigger:hover, + .composer-variant-trigger:hover { + border-color: var(--border-default); + background: var(--bg-hover); + color: var(--text-primary); } .composer-variant-trigger { - max-width: 88px; + max-width: 100px; color: var(--text-tertiary); - font-weight: 500; + font-weight: 600; } .composer-model-trigger:hover, @@ -1822,25 +1851,23 @@ .composer-model-menu button, .composer-variant-menu button { display: flex; - min-height: 32px; + min-height: 36px; width: 100%; align-items: center; gap: 8px; padding: 0 10px; - border-radius: 4px; + border-radius: 5px; color: var(--text-secondary); - font-size: 12px; + font-size: 13px; + font-weight: 500; text-align: left; + transition: + background var(--motion-hover) var(--ease-smooth), + color var(--motion-hover) var(--ease-smooth); } .composer-model-menu button:hover, .composer-variant-menu button:hover, - .composer-model-menu button[aria-pressed="true"], - .composer-variant-menu button[aria-pressed="true"] { - background: var(--bg-hover); - color: var(--text-primary); - } - .composer-model-menu button[aria-pressed="true"], .composer-variant-menu button[aria-pressed="true"] { font-weight: 600; @@ -1848,7 +1875,7 @@ .composer-model-menu .composer-model-reset { color: var(--text-tertiary); - font-size: 11px; + font-size: 12px; } .composer-model-menu .composer-model-divider { @@ -1871,16 +1898,24 @@ .composer-action { display: grid; place-items: center; - width: 32px; - height: 32px; - border-radius: 4px; + width: 36px; + height: 36px; + border-radius: 6px; background: var(--text-primary); color: var(--bg-base); + transition: + background var(--motion-hover) var(--ease-smooth), + transform var(--motion-hover) var(--ease-smooth); } .composer-action:hover { background: var(--brand); color: var(--brand-ink, var(--bg-overlay, white)); + transform: translateY(-1px); + } + + .composer-action:active { + transform: translateY(0) scale(0.96); } .composer-action.stop:hover { From 6beb8c9b30de5f08372e14331e7d9d2936063b6a Mon Sep 17 00:00:00 2001 From: bo Date: Tue, 11 Aug 2026 02:21:40 +0800 Subject: [PATCH 08/19] polish: unify typography across all prototypes - Standardized heading sizes: h1 26px (weight 680), h2 24-26px - Unified body text: primary 13-13.5px (weight 620), secondary 11.5-12px - Consistent metadata: 11-11.5px (weight 640-680) - All pages now use matching font sizes and weights for equivalent elements - Better visual hierarchy and readability throughout --- design-system/prototypes/automations.html | 12 +++++++----- design-system/prototypes/dashboard.html | 16 ++++++++-------- design-system/prototypes/sessions.html | 11 ++++++----- 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/design-system/prototypes/automations.html b/design-system/prototypes/automations.html index 570d20e0..cc04cf46 100644 --- a/design-system/prototypes/automations.html +++ b/design-system/prototypes/automations.html @@ -225,7 +225,8 @@ } .detail-title-row h2 { - font-size: 24px; + font-size: 26px; + font-weight: 680; line-height: 1.2; letter-spacing: -0.03em; } @@ -234,7 +235,7 @@ max-width: 600px; margin-top: 7px; color: var(--text-secondary); - font-size: 12px; + font-size: 13px; line-height: 1.55; } @@ -252,15 +253,16 @@ .detail-section h3 { font-size: 11px; - font-weight: 700; - letter-spacing: 0.05em; + font-weight: 680; + letter-spacing: 0.06em; text-transform: uppercase; + color: var(--text-tertiary); } .detail-section-copy { margin-top: 10px; color: var(--text-secondary); - font-size: 12px; + font-size: 13px; line-height: 1.65; } diff --git a/design-system/prototypes/dashboard.html b/design-system/prototypes/dashboard.html index d5058bcf..f30e7c5e 100644 --- a/design-system/prototypes/dashboard.html +++ b/design-system/prototypes/dashboard.html @@ -51,8 +51,8 @@ } .home-header h1 { - font-size: 24px; - font-weight: 700; + font-size: 26px; + font-weight: 680; line-height: 1.15; letter-spacing: -0.03em; } @@ -234,23 +234,23 @@ .home-row-copy strong { overflow: hidden; color: var(--text-primary); - font-size: 14px; - font-weight: 600; + font-size: 13.5px; + font-weight: 620; letter-spacing: -0.015em; text-overflow: ellipsis; white-space: nowrap; } .home-row-copy small { - margin-top: 5px; + margin-top: 4px; color: var(--text-tertiary); - font-size: 12px; + font-size: 11.5px; } .home-row-state { color: var(--text-tertiary); - font-size: 12px; - font-weight: 600; + font-size: 11.5px; + font-weight: 640; font-variant-numeric: tabular-nums; white-space: nowrap; } diff --git a/design-system/prototypes/sessions.html b/design-system/prototypes/sessions.html index 80d75666..1f47dd35 100644 --- a/design-system/prototypes/sessions.html +++ b/design-system/prototypes/sessions.html @@ -232,17 +232,17 @@ .session-row-copy strong { overflow: hidden; color: var(--text-primary); - font-size: 13px; - font-weight: 600; + font-size: 13.5px; + font-weight: 620; text-overflow: ellipsis; white-space: nowrap; } .session-row-copy small { - margin-top: 2px; + margin-top: 3px; overflow: hidden; color: var(--text-tertiary); - font-size: 11px; + font-size: 11.5px; text-overflow: ellipsis; white-space: nowrap; } @@ -262,7 +262,8 @@ .session-row-state { justify-self: end; color: var(--text-tertiary); - font-size: 10px; + font-size: 11px; + font-weight: 640; font-variant-numeric: tabular-nums; white-space: nowrap; } From f73d240760e36dadb36472bdc26d873ec3b184a6 Mon Sep 17 00:00:00 2001 From: bo Date: Tue, 11 Aug 2026 16:58:14 +0800 Subject: [PATCH 09/19] docs(design): sync MASTER.md + pages/*.md with prototype polish (model menu, attachments, inspector summary, status orbits, rail monograms) --- design-system/MASTER.md | 41 ++++++++++++++++++++++-------- design-system/pages/automations.md | 6 +++-- design-system/pages/dashboard.md | 11 +++++--- design-system/pages/session.md | 38 +++++++++++++++++++-------- design-system/pages/sessions.md | 5 ++-- design-system/pages/todos.md | 4 ++- 6 files changed, 76 insertions(+), 29 deletions(-) diff --git a/design-system/MASTER.md b/design-system/MASTER.md index 5a6680df..015f1b95 100644 --- a/design-system/MASTER.md +++ b/design-system/MASTER.md @@ -1,7 +1,7 @@ # Signal Workbench Design System > Target UI specification synchronized with the current effective prototypes on -> 2026-08-10. Until product implementation catches up, current product code +> 2026-08-11. Until product implementation catches up, current product code > remains authoritative for existing runtime behavior and state mechanics. > Prototypes under `design-system/prototypes/` lead visual and copy decisions for > surfaces they cover; page files record those decisions for implementation. @@ -321,7 +321,10 @@ Narrow-screen rules: ## Navigation -- Project rail is the stable global anchor and uses initials or consistent +- Project rail is the stable global anchor. Project entries use the **Quiet + monogram spine**: two-letter lowercase initials derived from the project name + (e.g. `ar`, `sp`), not single-letter marks, Discord-style avatars, or per-project + icon inventiveness. Utility controls (search, Needs you, Settings, theme) stay outline SVG icons. - Active project uses a lime edge marker because it represents the live workspace; active in-project navigation uses indigo. @@ -415,26 +418,27 @@ Rules: - Prefer **`Needs you`** as the cross-surface product phrase for “user action required.” Do not compete with **`Needs attention`**, **`Waiting`**, or bare **`Permission`** as the primary inventory/header label. - **`Permission`** remains correct for the *kind* of gate (tool status, HITL family, Execution `Suspended · Permission`). It is not a substitute for the product group name `Needs you`. -- On a single Session canvas, do **not** repeat `Needs you` on every chrome layer. Strong product labels stay on **header + Composer**; Work folds, agent tree, and inspector summary use mechanism or time wording (see [`pages/session.md`](pages/session.md)). +- On a single Session canvas, do **not** repeat `Needs you` on every chrome layer. Strong product labels stay on **header + Composer**; Work folds and agent-tree trailing state use mechanism or time wording (see [`pages/session.md`](pages/session.md)). Do not invent an inspector summary strip to restate the same status. - Failed runs use **`--error`** tone and the word **`Failed`**. Do not map failure onto the attention/amber channel. ### Status visual map Core decision triad is **Running / Needs you / Done**. The full map also covers failure, review, selection, and idle so implementers do not invent competing -colors. +colors. **`Ready to review`** is a real product decision state (Home section, +Todo operational line, inventory cue) — not decorative copy. | State | Token / field | Visual | |---|---|---| | Running / live | `--signal` / `--signal-foreground` / `--running-field` | Lime orbit or live pulse + accessible state; running orbits use green glow (`0 0 12px rgba(101, 163, 13, 0.25)`) | | Needs you / HITL attention | `--warning` / `--attention-field` | Amber icon/orbit + `Needs you` (or mechanism tag where density rules allow); attention orbits use amber glow (`0 0 12px rgba(251, 146, 60, 0.3)`) | -| Done / completed | `--success` / `--success-field` | Green check or completed text | +| Done / completed | `--success` / `--success-field` | Shared **circle-check** SVG (same path as Todos Done `data-icon="circle-check"`) or completed text — never a freehand CSS border-hack check | | Failed / error | `--error` / `--error-field` | Red icon/orbit + `Failed` or recovery wording | | Ready to review | brand-tinted quiet marker (not lime) | Review-ready inventory cue with brand color glow on focus | | Selected / active | `--brand` / `--selection-field` | Indigo field or inset rule | | Idle / stopped / neutral | `--neutral` / outline orbit | Outline neutral orbit; no lime | -Shared **status-orbit** (and page aliases: home/session/automation/session-finder/session-picker) is one primitive: same sizes, tones, spin only while `.running`, and `prefers-reduced-motion` freezes spin. Do not fork per-page orbit CSS. +Shared **status-orbit** (and page aliases: home/session/automation/session-finder/session-picker) is one primitive: same sizes, tones, spin only while `.running`, and `prefers-reduced-motion` freezes spin. Done/completed orbits use the same SVG glyph language as Todos lane Done — do not fork a CSS pseudo-element check. Do not fork per-page orbit CSS. Automation invocation state is not Session or Execution completion: a `dispatched` invocation remains visibly `Dispatched` and must never be labeled @@ -564,8 +568,17 @@ Execution is a mandatory product entity, not an optional visual section. - Queued messages remain directly visible as compact rows with their message text and management actions. Do not reduce them to only a count or a `View` disclosure. -- Input, Agent, Profile, next-model selection, Queue/Send, and Stop remain in - one quiet surface below the priority band and compact summaries. +- Input, next-model selection, Queue/Send, and Stop remain in one quiet surface + below the priority band and compact summaries. +- **Next model control** is a single quiet trigger (`Model · effort`) opening one + menu. Models are user-configured catalog entries — show **display names only**, + with no marketing blurbs. Mark the project/principal default model with a small + **Default** badge on that row; do **not** add a separate “Use Principal default” + menu item for ordinary selection. Effort (variant) options are free-form config + keys from the model definition (plus **Default** when no variant is selected); + label the section **Effort**, not Thinking, and do not invent effort descriptions. +- **Attachments** already on the draft are compact chips: file glyph + name + one + remove control. No reorder up/down affordances — order is attach order. - A running Session keeps ordinary queue composition available and exposes Stop clearly. - The dock consumes layout space and never floats over Work or conversation @@ -579,6 +592,9 @@ Execution is a mandatory product entity, not an optional visual section. resizable 280–460px) and an overlay below 1181px. - Persistent desktop navigation and Inspector widths are user-resizable and restored after collapse or focus mode. +- **No inspector summary strip** above the tab bar. Status, agent count, and file + count already live on header/Composer, Agents/Changes tab badges, and Context + rows — restating them wastes chrome and duplicates product urgency. - **Role split:** main canvas owns narrative work (transcript, Work, HITL decision UI, Composer). The inspector owns machine state — agent structure, file artifacts, and session bindings — not a second chat or primary CTA strip. @@ -699,10 +715,15 @@ Motion explains state changes; it is not decoration. restoring Execution/model/message metadata at 390px, 760px, and desktop. - [ ] Confirm product status copy uses `Needs you` on inventories/header/Composer and does not paint `Failed` as amber attention. -- [ ] Confirm Session canvas does not stack `Needs you` on Work, agent tree, and - inspector summary in addition to header + Composer. +- [ ] Confirm Session canvas does not stack `Needs you` on Work or agent tree in + addition to header + Composer, and that the inspector has no summary strip. - [ ] Confirm Context Inspector remains three tabs, quiet list density, and no hosted HITL primary actions. +- [ ] Confirm composer model menu shows bare model names + Default badge, Effort + (not Thinking) without marketing blurbs, and attachments are remove-only chips. +- [ ] Confirm shared done/completed status-orbit uses the same circle-check glyph + as Todos Done. +- [ ] Confirm project rail uses Quiet two-letter monograms for projects. - [ ] Confirm keyboard focus and accessible expansion state. - [ ] Confirm `prefers-reduced-motion`. - [ ] Confirm browser console is clean. diff --git a/design-system/pages/automations.md b/design-system/pages/automations.md index dc7a9da7..33387b18 100644 --- a/design-system/pages/automations.md +++ b/design-system/pages/automations.md @@ -38,8 +38,10 @@ attention, and recover the exact Session associated with every run. - Each row shows the shared status orbit, name, concise schedule or latest-run context, and the real definition/Invocation state. Terminal failures show **`Failed`** with error tone; human gates show **`Needs you`** with attention - tone. `dispatched` is not `Completed`; the final execution result is read from - the linked Session. Use a flat row with separators, not a summary card. + tone. Completed/successful recent cues use the shared circle-check done orbit + when an orbit is shown. `dispatched` is not `Completed`; the final execution + result is read from the linked Session. Use a flat row with separators, not a + summary card. - Rows use subtle hover micro-interactions (0.5px `translateY` lift) with background color change for perceived responsiveness without raised shadows. - Selection uses the shared indigo selected treatment. Needs-you state keeps diff --git a/design-system/pages/dashboard.md b/design-system/pages/dashboard.md index c5d53a3c..36a73a8e 100644 --- a/design-system/pages/dashboard.md +++ b/design-system/pages/dashboard.md @@ -20,8 +20,9 @@ analytics dashboard. - Keep only the theme-adaptive project rail and global work canvas. Do not render a project toolbar, persistent project sidebar, or project-level New Session. -- The ArchCode brand mark represents the current Home destination. Project icons - open each project's Todos page. +- The ArchCode brand mark represents the current Home destination. Project + entries on the rail use Quiet two-letter monograms and open each project's + Todos page. - Main content width is at most 1180px with 40px desktop gutters and 18px narrow gutters. - Pair `Needs you` with `Running` and `Ready to review` with `Upcoming` on wide @@ -52,8 +53,10 @@ Do not add greetings, marketing copy, metrics, charts, or a global Todo board. failure as amber attention. Mechanism detail (`Permission`, `Question`, etc.) may appear in accessible names or secondary copy without replacing the section title. -- Running rows use the live orbit plus elapsed time. Review rows say what result - is awaiting review. Upcoming rows identify the Automation schedule. +- Running rows use the live orbit plus elapsed time. **Ready to review** is a + real Home decision section and row state (brand-tinted review cue), not a + synonym for Done. Review rows say what result is awaiting review. Upcoming + rows identify the Automation schedule. - Current-project rows use exact Todo, Automation, or Session deep links. Other project rows may remain explicit prototype actions rather than fake deep links. - Prefer a few decision-worthy rows over exhaustive recent activity. Home must diff --git a/design-system/pages/session.md b/design-system/pages/session.md index ed75dfc1..982d895b 100644 --- a/design-system/pages/session.md +++ b/design-system/pages/session.md @@ -218,11 +218,9 @@ on the project rail. - Default width **312px** (resize 280–460px); collapse and focus mode preserve the last expanded width. -- Optional one-line **summary strip** under the chrome close control: - mechanism-or-product-short status + `N agents · N files`. When the Session - product status is `Needs you`, this strip prefers the HITL **request family** - (**`Permission`** or **`Question`**) so the canvas does not shout `Needs you` - three times. Terminal **`Failed`** uses the error word, never amber attention. +- **No summary strip** above the tab bar. Do not restate Session status, agent + count, or file count as a chrome line — those already live on the Session + header/Composer, Agents/Changes tab badges, and Context property rows. - Tab bar may carry counts (`Agents 4`, `Changes 3`). Active tab uses a brand underline, not a filled pill block. - Type floor ≥11px; primary labels ~12–12.5px; meta ~11–11.5px. No sub-11px @@ -290,11 +288,27 @@ on the project rail. - Queued messages remain visible as compact rows with their content and management actions. Never collapse the queue to only a count or `View` control. -- Agent, Profile, next model, and the current Send/Queue/Stop actions remain in - the quiet input surface below those priority cues. +- **Next model picker** (prototype `composer-model-picker`): + - Trigger shows `Model display name · effort` (effort omitted only when the + catalog model has no variants). + - Menu lists **model display names only** — no marketing descriptions + (“Balanced default…”, etc.). Models are user-added configuration. + - The project/principal default model, if known, carries a small **Default** + badge on that row. Do not add a separate “Use Principal default” option for + ordinary next-run selection in the prototype; product may still expose + profile-default reset only when the session already holds an explicit + override (see product `ModelPicker`). + - Effort section label is **Effort** (not Thinking). Options are free-form + variant keys from model config (e.g. `fast`, `deep`) plus **Default** when + no variant is selected. No effort description hints. +- **Draft attachments** are chips: file glyph + truncated name + one remove (×) + control. Size/ready secondary text is optional and may stay hidden at this + density. No up/down reorder controls — attach order is enough. +- Agent identity and the current Send/Queue/Stop actions remain in the quiet + input surface below those priority cues. - A running Session may queue ordinary messages while Stop remains a separate, unmistakable action. -- On very narrow layouts, hide secondary Profile/model metadata before removing +- On very narrow layouts, hide secondary model/effort metadata before removing Agent identity or the primary Queue/Send/Stop controls. ## Session-Specific Avoidances @@ -308,8 +322,12 @@ on the project rail. - an expandable Composer or Goal progress bar; - HITL placed below Goal, Queue, or ordinary input; - live lime used on completed rows; -- stacked product `Needs you` labels on Work, agent tree, and inspector summary - in addition to header + Composer; +- stacked product `Needs you` labels on Work or agent tree in addition to + header + Composer; +- an inspector summary strip that restates status or counts already on tabs; +- model marketing blurbs, a separate Principal-default menu row, or Thinking + intensity copy with invented effort descriptions; +- attachment reorder arrows on already-attached draft chips; - inspector card stacks, sub-10px type, or brand-wash agent selection; - collapsed state that hides the existence of Execution; - tool output that expands beyond the work canvas; diff --git a/design-system/pages/sessions.md b/design-system/pages/sessions.md index ebb66185..be71d020 100644 --- a/design-system/pages/sessions.md +++ b/design-system/pages/sessions.md @@ -38,8 +38,9 @@ attention, and start work directly without manufacturing a Todo first. **`Failed`** rows use the error orbit/tone and the word `Failed` — they may still sort under the Needs you *decision group* but must not look like amber HITL. -- **`Running`** uses the live orbit plus elapsed time; completed Recent rows use a - green check or explicit completed text. +- **`Running`** uses the live orbit plus elapsed time; completed Recent rows use + the shared **circle-check** done orbit (same glyph as Todos Done) or explicit + completed text — not a CSS border-hack check. - The whole row opens the exact Session URL. Destructive actions remain in an overflow menu or the Session detail and never compete with row navigation. diff --git a/design-system/pages/todos.md b/design-system/pages/todos.md index 6e5e7d9d..3ccb00c7 100644 --- a/design-system/pages/todos.md +++ b/design-system/pages/todos.md @@ -68,7 +68,9 @@ Lane rules: large filled empty box. A current drag target may use one temporary quiet field. - Lane headers use a status orbit, title, short explanation, one bottom rule, - and a plain tabular count. Do not box the count as a badge. + and a plain tabular count. Do not box the count as a badge. The Done lane + orbit uses the shared **circle-check** glyph (`data-icon="circle-check"`); + other inventory done orbits must match this glyph, not invent a CSS check. - Todo cards are the Board's only persistent card layer. Use one quiet border, 6px radius, and no elevation. - Cards use subtle hover micro-interactions (1px `translateY` lift) with From c527b28f933cc904718a26d33014aefbd387e1c5 Mon Sep 17 00:00:00 2001 From: bo Date: Tue, 11 Aug 2026 16:59:59 +0800 Subject: [PATCH 10/19] polish(prototypes): simplify model menu, attachments, and inspector chrome - Model picker: bare names + Default badge, Effort (no Thinking/marketing copy) - Attachments: remove-only chips (no reorder arrows) - Drop inspector summary strip; keep Agents/Changes tab counts - Align done status-orbit with Todos circle-check SVG - Quiet two-letter rail monograms; cache bust prototype-polish-16 Co-Authored-By: Claude Fable 5 --- design-system/prototypes/app.js | 11 +- design-system/prototypes/automations.html | 32 +- design-system/prototypes/dashboard.html | 32 +- design-system/prototypes/session.html | 1100 +++++++++++++-------- design-system/prototypes/sessions.html | 32 +- design-system/prototypes/styles.css | 71 +- design-system/prototypes/todos.html | 78 +- 7 files changed, 845 insertions(+), 511 deletions(-) diff --git a/design-system/prototypes/app.js b/design-system/prototypes/app.js index f8f2f494..870d136e 100644 --- a/design-system/prototypes/app.js +++ b/design-system/prototypes/app.js @@ -8,8 +8,13 @@ const icons = { "circle-check": "M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-12 0 2 2 4-4", "circle-dot": "M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6 0a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z", "circle-play": "M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-11-4 6 4-6 4Z", + clock: "M12 21a9 9 0 1 0 0-18 9 9 0 0 0 0 18ZM12 7v5l3 3", close: "M6 6l12 12M18 6 6 18", + // Lucide corner-down-right — standard “inject / reply into flow” glyph for Steer + "corner-down-right": "M15 10l5 5-5 5M4 4v7a4 4 0 0 0 4 4h12", dashboard: "M4 4h6v6H4zm10 0h6v9h-6zM4 14h6v6H4zm10 3h6v3h-6z", + edit: "M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7M18.5 2.5a2.1 2.1 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5Z", + file: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8zM14 2v6h6", "file-plus": "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8zM14 2v6h6M12 18v-6M9 15h6", list: "M8 6h13M8 12h13M8 18h13M3 6h.01M3 12h.01M3 18h.01", filter: "M4 6h16M7 12h10M10 18h4", @@ -31,7 +36,9 @@ const icons = { sparkles: "m12 3-1.1 3.1L8 7.2l2.9 1.1L12 11l1.1-2.7L16 7.2l-2.9-1.1L12 3Zm6 9-.7 2-1.8.7 1.8.7.7 2 .7-2 1.8-.7-1.8-.7-.7-2ZM6 13l-1 2.6-2.5 1L5 17.5 6 20l1-2.5 2.5-.9-2.5-1L6 13Z", telescope: "m10 6 8-3 2 4-8 3M10 6l2 4M8 12l-4 9M12 12l4 9", todo: "M8 6h12M8 12h12M8 18h12M3.5 6h.01M3.5 12h.01M3.5 18h.01", + trash: "M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6M10 11v6M14 11v6", workflow: "M3 5h6v6H3zM15 13h6v6h-6zM9 8h3a3 3 0 0 1 3 3v2", + zap: "M13 2 3 14h8l-1 8 10-12h-8l1-8Z", }; const baseWorkSearchItems = [ @@ -187,7 +194,7 @@ document.querySelector("[data-theme-toggle]")?.addEventListener("click", () => { function showToast(message) { if (!toast) return; - toast.textContent = `${message} · 原型演示`; + toast.textContent = `${message} · prototype demo`; toast.classList.add("visible"); clearTimeout(toastTimer); toastTimer = setTimeout(() => toast.classList.remove("visible"), 2200); @@ -288,7 +295,7 @@ function readPrototypeSessions() { } } -window.readArchcodePrototypeSessions = readPrototypeSessions; +window.readPrototypeSessions = readPrototypeSessions; /** Shared static Sessions catalog for list pages + Home Needs-you slice. */ const archcodePrototypeStaticSessions = [ diff --git a/design-system/prototypes/automations.html b/design-system/prototypes/automations.html index cc04cf46..d0a211db 100644 --- a/design-system/prototypes/automations.html +++ b/design-system/prototypes/automations.html @@ -2,13 +2,21 @@ + ArchCode — Automations Prototype - + - + - +
diff --git a/design-system/prototypes/dashboard.html b/design-system/prototypes/dashboard.html index f30e7c5e..abc7b3c7 100644 --- a/design-system/prototypes/dashboard.html +++ b/design-system/prototypes/dashboard.html @@ -2,13 +2,21 @@ + - + ArchCode — Home Prototype - + - + - +
diff --git a/design-system/prototypes/session.html b/design-system/prototypes/session.html index 208c2712..700a3225 100644 --- a/design-system/prototypes/session.html +++ b/design-system/prototypes/session.html @@ -2,16 +2,24 @@ + ArchCode — Session Prototype - + - + - +
@@ -2802,7 +3018,7 @@

Direct Session ready

↳ delegate Review Profile precedenceAnalyst · deep · Completed · 31s - +
@@ -2842,7 +3058,7 @@

Direct Session ready

protected6 refs

Preserved the current and last two rounds; delegated work and tool artifacts remain recoverable.

- +
@@ -2863,15 +3079,16 @@

Direct Session ready

-
- Prototype preview · HITL state -
- - +
+
+ Prototype preview · HITL state +
+ + +
-
-
+

Run focused Profile verification

@@ -2889,15 +3106,15 @@

Run focused Profile verification

1/2 - +
- - + +
- - - + + +
@@ -2929,86 +3146,129 @@

Run focused Profile verification

- - + +
- +
- Queued +

After verification, summarize Profile precedence and remaining risk.

- GPT-5.6 Luna · deep -
+
+ + + +
- Steering +

Also verify the recommendation is visible before Start Work.

- GPT-5.6 Luna · deep -
+
+ + +
+
-
@@ -3378,7 +3386,7 @@

New Todo

const excerpt = contentExcerpt(sample.content); const runtime = sampleRuntime(sample); return `
- + `; + }).join("")} + +
`; + document.body.append(picker); + + const filter = picker.querySelector("[data-project-picker-filter]"); + const rows = [...picker.querySelectorAll("[data-project-picker-key]")]; + const empty = picker.querySelector(".project-picker-empty"); + + function close({ restoreFocus = true } = {}) { + if (picker.hidden) return; + picker.hidden = true; + picker.setAttribute("aria-hidden", "true"); + trigger.setAttribute("aria-expanded", "false"); + if (restoreFocus) requestAnimationFrame(() => trigger.focus()); + } + + function open() { + picker.hidden = false; + picker.setAttribute("aria-hidden", "false"); + trigger.setAttribute("aria-expanded", "true"); + if (filter instanceof HTMLInputElement) { + filter.value = ""; + rows.forEach((row) => { row.hidden = false; }); + if (empty) empty.hidden = true; + requestAnimationFrame(() => filter.focus()); + } + } + + trigger.addEventListener("click", () => { + if (picker.hidden) open(); + else close(); + }); + + filter?.addEventListener("input", () => { + const query = filter instanceof HTMLInputElement ? filter.value.trim().toLowerCase() : ""; + let visible = 0; + rows.forEach((row) => { + const match = query.length === 0 || row.dataset.projectPickerSearch?.includes(query); + row.hidden = !match; + if (match) visible += 1; + }); + if (empty) empty.hidden = visible !== 0; + }); + + picker.addEventListener("click", (event) => { + const row = event.target.closest("[data-project-picker-key]"); + if (!(row instanceof HTMLButtonElement)) return; + const source = projects.find((project) => project.dataset.projectKey === row.dataset.projectPickerKey); + close({ restoreFocus: false }); + source?.click(); + }); + + document.addEventListener("pointerdown", (event) => { + if (picker.hidden || picker.contains(event.target) || trigger.contains(event.target)) return; + close({ restoreFocus: false }); + }); + + document.addEventListener("keydown", (event) => { + if (event.key === "Escape" && !picker.hidden) { + event.preventDefault(); + close(); + } + }); +} + +createProjectPicker(); + const sidebar = document.querySelector(".project-sidebar"); function closeOverlays() { diff --git a/design-system/prototypes/automations.html b/design-system/prototypes/automations.html index d0a211db..9af4e66f 100644 --- a/design-system/prototypes/automations.html +++ b/design-system/prototypes/automations.html @@ -16,7 +16,7 @@ - + - +
+ handleProjectClick(slug)} + open={pickerOpen} + projects={allProjects} + returnFocusRef={moreProjectsTriggerRef} + runningCounts={runningCounts} + /> + {editingProject && ( | null; +} + +function isElement(value: unknown): value is ElementLike { + return typeof value === "object" && value !== null && "props" in value; +} + +function childrenOf(value: unknown): unknown[] { + if (!isElement(value)) return []; + const children = value.props?.children; + return children === undefined || children === null ? [] : Array.isArray(children) ? children : [children]; +} + +function findAll(value: unknown, predicate: (element: ElementLike) => boolean): ElementLike[] { + const matches: ElementLike[] = []; + const visit = (node: unknown): void => { + if (Array.isArray(node)) return void node.forEach(visit); + if (!isElement(node)) return; + if (predicate(node)) matches.push(node); + childrenOf(node).forEach(visit); + }; + visit(value); + return matches; +} + +function textContent(value: unknown): string { + if (typeof value === "string" || typeof value === "number") return String(value); + if (Array.isArray(value)) return value.map(textContent).join(""); + return isElement(value) ? textContent(value.props?.children) : ""; +} + +const Fragment = Symbol.for("react.fragment"); +const jsx = (type: unknown, props: Record | null, key?: unknown) => ({ type, props: props ?? {}, key }); +mock.module("react/jsx-dev-runtime", () => ({ Fragment, jsx, jsxs: jsx, jsxDEV: jsx })); +mock.module("react", () => ({ + useMemo: (factory: () => T) => factory(), + useState: (initial: T) => [initial, () => {}], +})); +mock.module("lucide-react", () => ({ Search: "Search", X: "X" })); +mock.module("../primitives/StatusGlyph", () => ({ StatusGlyph: "StatusGlyph" })); +mock.module("../ui/Dialog", () => ({ + DialogRoot: "DialogRoot", + DialogClose: "DialogClose", + DialogContent: "DialogContent", + DialogTitle: "DialogTitle", + DialogDescription: "DialogDescription", +})); + +const { ProjectPickerDialog, filterProjectPickerItems } = await import("./ProjectPickerDialog"); + +const projects: Project[] = [ + { slug: "archcode", name: "ArchCode", workspaceRoot: "/workspace/archcode", addedAt: "2026-01-01T00:00:00.000Z" }, + { slug: "montage", name: "OpenMontage", workspaceRoot: "/workspace/montage", addedAt: "2026-01-02T00:00:00.000Z" }, +]; + +describe("ProjectPickerDialog", () => { + test("filters the complete inventory by name, slug, or workspace path", () => { + expect(filterProjectPickerItems(projects, "open").map((project) => project.slug)).toEqual(["montage"]); + expect(filterProjectPickerItems(projects, "archcode").map((project) => project.slug)).toEqual(["archcode"]); + expect(filterProjectPickerItems(projects, "/workspace/montage").map((project) => project.slug)).toEqual(["montage"]); + }); + + test("shows every project with mark, name, path, current state, running count, and attention count", () => { + const tree = ProjectPickerDialog({ + activeSlug: "archcode", + attentionCounts: { archcode: 2 }, + marks: { archcode: "ac", montage: "om" }, + onOpenChange: () => {}, + onSelect: () => {}, + open: true, + projects, + returnFocusRef: { current: null }, + runningCounts: { archcode: 1, montage: 0 }, + }); + const rows = findAll(tree, (element) => element.type === "button" && String(element.props?.["aria-label"]).includes("running")); + + expect(rows).toHaveLength(2); + expect(rows[0]?.props?.["aria-current"]).toBe("page"); + expect(rows[0]?.props?.["aria-label"]).toBe("ArchCode, current project, 1 running, 2 need you"); + expect(textContent(rows[0])).toContain("acArchCodeCurrent/workspace/archcode1 running2 need you"); + expect(textContent(rows[1])).toContain("omOpenMontage/workspace/montage0 running0 need you"); + expect(String(findAll(rows[0], (element) => textContent(element) === "2 need you")[0]?.props?.className)).toContain("text-warning"); + expect(String(findAll(rows[1], (element) => textContent(element) === "0 need you")[0]?.props?.className)).toContain("text-text-tertiary"); + }); +}); diff --git a/apps/web/src/components/features/ProjectPickerDialog.tsx b/apps/web/src/components/features/ProjectPickerDialog.tsx new file mode 100644 index 00000000..7546f705 --- /dev/null +++ b/apps/web/src/components/features/ProjectPickerDialog.tsx @@ -0,0 +1,140 @@ +import { Search, X } from "lucide-react"; +import { useMemo, useState, type RefObject } from "react"; +import type { Project } from "../../api/types"; +import { StatusGlyph } from "../primitives/StatusGlyph"; +import { + DialogContent, + DialogClose, + DialogDescription, + DialogRoot, + DialogTitle, +} from "../ui/Dialog"; + +interface ProjectPickerDialogProps { + activeSlug?: string; + attentionCounts: Readonly>; + marks: Readonly>; + onOpenChange: (open: boolean) => void; + onSelect: (slug: string) => void; + open: boolean; + projects: readonly Project[]; + returnFocusRef: RefObject; + runningCounts: Readonly>; +} + +export function filterProjectPickerItems(projects: readonly Project[], query: string): readonly Project[] { + const needle = query.trim().toLocaleLowerCase(); + if (needle.length === 0) return projects; + return projects.filter((project) => ( + project.name.toLocaleLowerCase().includes(needle) + || project.slug.toLocaleLowerCase().includes(needle) + || project.workspaceRoot.toLocaleLowerCase().includes(needle) + )); +} + +export function ProjectPickerDialog({ + activeSlug, + attentionCounts, + marks, + onOpenChange, + onSelect, + open, + projects, + returnFocusRef, + runningCounts, +}: ProjectPickerDialogProps) { + const [query, setQuery] = useState(""); + const filteredProjects = useMemo(() => filterProjectPickerItems(projects, query), [projects, query]); + + return ( + { + onOpenChange(nextOpen); + if (!nextOpen) setQuery(""); + }}> + { + event.preventDefault(); + returnFocusRef.current?.focus(); + }} + > +
+ Projects + + + + + Open any registered workspace. + + +
+ +
+ {filteredProjects.length === 0 ? ( +

No projects match this filter.

+ ) : ( +
+ {filteredProjects.map((project) => { + const isCurrent = project.slug === activeSlug; + const runningCount = runningCounts[project.slug] ?? 0; + const attentionCount = attentionCounts[project.slug] ?? 0; + return ( + + ); + })} +
+ )} +
+
+
+ ); +} diff --git a/apps/web/src/components/features/ProjectToolbar.test.tsx b/apps/web/src/components/features/ProjectToolbar.test.tsx index 92b4cf0d..5170a6fc 100644 --- a/apps/web/src/components/features/ProjectToolbar.test.tsx +++ b/apps/web/src/components/features/ProjectToolbar.test.tsx @@ -57,7 +57,6 @@ mock.module("../../api/queries", () => ({ useProjects: () => ({ data: [project] mock.module("./ProjectActionMenu", () => ({ ProjectActionDropdown: "ProjectActionDropdown" })); mock.module("./EditProjectDialog", () => ({ EditProjectDialog: "EditProjectDialog" })); mock.module("./CloseProjectDialog", () => ({ CloseProjectDialog: "CloseProjectDialog" })); -mock.module("lucide-react", () => ({ MoreHorizontal: "MoreHorizontal" })); const { ProjectToolbar } = await import("./ProjectToolbar"); @@ -70,7 +69,9 @@ describe("ProjectToolbar", () => { test("shows project identity and the stable project navigation order", () => { const tree = ProjectToolbar(); - const heading = findAll(tree, (element) => element.type === "h1")[0]; + const menu = findAll(tree, (element) => element.type === "ProjectActionDropdown")[0]; + const trigger = menu?.props?.trigger as ElementLike; + const heading = findAll(trigger, (element) => element.type === "h1")[0]; const links = findAll(tree, (element) => element.type === "NavLink"); const navigation = findAll(tree, (element) => element.type === "nav")[0]; @@ -82,13 +83,25 @@ describe("ProjectToolbar", () => { "/projects/archcode/sessions", ]); expect(navigation?.props?.className).toContain("[@media(pointer:coarse)]:h-11"); + expect(navigation?.props?.className).toContain("h-10"); + expect((tree as ElementLike).props?.className).toContain("min-h-12"); + expect((tree as ElementLike).props?.className).not.toContain("h-[88px]"); + expect(links.every((link) => String(link.props?.className).includes("min-h-11"))).toBe(true); + }); + + test("keeps the Session detail toolbar at the prototype's fixed mobile height", () => { + routeParams = { slug: "archcode", sessionId: "session-1" }; + const tree = ProjectToolbar(); + expect((tree as ElementLike).props?.className).toContain("h-[88px]"); }); - test("keeps project actions in the toolbar", () => { + test("uses the project identity itself as the project-actions trigger", () => { const tree = ProjectToolbar(); const menu = findAll(tree, (element) => element.type === "ProjectActionDropdown")[0]; expect(menu?.props?.project).toBe(project); - expect((menu?.props?.trigger as ElementLike).props?.["aria-label"]).toBe("Project actions"); + const trigger = menu?.props?.trigger as ElementLike; + expect(trigger.props?.["aria-label"]).toBe("Project actions for ArchCode"); + expect(textContent(trigger)).toContain("ArchCode/workspace/archcode"); }); test("leaves the page h1 to Todo, Session, and Automation detail headers", () => { @@ -99,9 +112,11 @@ describe("ProjectToolbar", () => { ]) { routeParams = { slug: "archcode", ...detailParams }; const tree = ProjectToolbar(); + const menu = findAll(tree, (element) => element.type === "ProjectActionDropdown")[0]; + const trigger = menu?.props?.trigger as ElementLike; - expect(findAll(tree, (element) => element.type === "h1")).toEqual([]); - expect(findAll(tree, (element) => element.type === "p").some((element) => textContent(element) === "ArchCode")).toBe(true); + expect(findAll(trigger, (element) => element.type === "h1")).toEqual([]); + expect(findAll(trigger, (element) => element.type === "p").some((element) => textContent(element) === "ArchCode")).toBe(true); } }); }); diff --git a/apps/web/src/components/features/ProjectToolbar.tsx b/apps/web/src/components/features/ProjectToolbar.tsx index a8325919..03d3f330 100644 --- a/apps/web/src/components/features/ProjectToolbar.tsx +++ b/apps/web/src/components/features/ProjectToolbar.tsx @@ -1,5 +1,4 @@ import { useCallback, useState } from "react"; -import { MoreHorizontal } from "lucide-react"; import { NavLink, useNavigate, useParams } from "react-router-dom"; import { useProjects } from "../../api/queries"; import type { Project } from "../../api/types"; @@ -26,6 +25,7 @@ export function ProjectToolbar() { const [editingProject, setEditingProject] = useState(null); const [closingProject, setClosingProject] = useState(null); const ProjectName = automationId === undefined && sessionId === undefined && todoId === undefined ? "h1" : "p"; + const mobileHeight = sessionId === undefined ? "min-h-12" : "h-[88px]"; const handleProjectClosed = useCallback((closedProject: Project) => { const remaining = projects?.filter((candidate) => candidate.slug !== closedProject.slug) ?? []; @@ -33,23 +33,37 @@ export function ProjectToolbar() { }, [navigate, projects]); return ( -
-
- -
- {project?.name ?? slug} - {project &&

{project.workspaceRoot}

} -
-
+
+ {project ? + + + {project.name} + {project.workspaceRoot} + + + )} + /> :
+ + {slug} +
} -