Skip to content

feat(dashboard): denser workspace with stage bar and run band (intermediate) - #7730

Open
xiaoan17 wants to merge 21 commits into
loopx-project:mainfrom
xiaoan17:codex/local-dashboard-baseline
Open

xiaoan17 wants to merge 21 commits into
loopx-project:mainfrom
xiaoan17:codex/local-dashboard-baseline

Conversation

@xiaoan17

@xiaoan17 xiaoan17 commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

What this changes

A presentation pass over the local dashboard. Most of this PR is about the board (看板) and the Goal workspace surface — layout, density, and what the run flow shows. No backend or data-model changes.

Status: intermediate. This is a first step, not the final information architecture. The layout, the stage bar and the run band will keep changing in follow-ups; treat it as a baseline to iterate on.

Asking for design review

Please judge the details, not just the direction:

  • Row density — is one 40px line per Goal readable, or too tight?
  • Short labels — goalShortLabel() cuts long objectives at the first separator. Does the result stay recognisable?
  • Stage bar — Plan → Run → Accept with the current stage filled: does it read as progress, or as decoration?
  • Run band — 28 segments, colour is outcome only. Useful, or just a barcode?
  • Empty states — one dashed line instead of a panel. Enough information?
  • Board cards — title first, two lines max, status pill + age. Anything missing that you would want at a glance?

The rules behind these choices are written down in
docs/design/dashboard-design-system.md,
added by this PR so the next screen starts from the same numbers.

Density

  • Sidebar Goal rows: two lines (title + activity sentence) → one line, short label + state dot
  • New goalShortLabel() keeps long objectives out of lists; the full title stays in the tooltip and the drawer
  • Empty sections collapse to one dashed line instead of a third of the screen

Flow visibility

  • Six-cell vitals strip → Plan → Run → Accept stage bar (43px), current stage filled, others hollow
  • Attention moves into the document column, so the Goal page now reads 01 → 02 → 03 → 04
  • "All work" stops being a 320px right rail and becomes the last row of the document
  • Home: run band — the 28 most recent runs, one segment per run, colour is outcome only
  • Data path added: status.json run_history.recent_runs → WorkspaceModel.recentRuns → home

Noise removal

  • 5 help icons dropped from the vitals strip; missing data now simply says "not measured"
  • Lark settings: lone ⓘ trigger → the scope sentence rendered inline
  • Desktop notifications: settings block → sidebar bell with a status dot
  • Capability settings: middle column from bordered cards → 168px text index
  • Radius tokens replace 9/10/11/13/14/99px literals; settings went from six radii to two

Also

  • Task board toolbar: two rows → one (title · search · one filter select · view switch)
  • Task lanes: no tinted panel, hairline header, inline empty state; cards start with the title
  • Duplicate "Delivery & evidence" heading removed; drawer Goal titles clamped to two lines
  • Developer cockpit: projection diff rows render as cards, commands wrap at word boundaries

Verification

  • tsc --noEmit, build:desktop, smoke:home-route, smoke:workspace-locale, design-route smoke — pass locally
  • node scripts/check-css-custom-properties.mjs dashboard — 3809 references, no undefined properties
  • Five Goal views render with zero page errors; home main column 1055 → 609 characters
  • CI is currently red on dashboard-browser, chat-bundle-browser, windows-powershell and checks.
    conversation-activity also fails locally on an unmodified baseline, so at least part of this predates the branch;
    the remaining failures need the job logs to separate from the baseline.

Not included

  • /design/local-dashboard is a temporary design entry and should be removed or redirected before merge
  • Short labels are heuristic (text before the first separator); a real short_name belongs in the registry

Follow-ups

  • Task-level run data (execution sessions) so the band reflects work, not only Goal state refreshes
  • Board cards do not show the owner yet; the design board does
  • Research / benchmark surfaces and the docs showcase site have not had this pass

Review follow-up (head 6a936eaae)

Addresses the four P2 findings on the previous head, and finishes the
acceptance migration the density pass left behind.

P2-1 — non-empty results were hidden

:not(:has([data-result-catalog] li)) never matched the catalog (every row is a
<button>), so the empty treatment applied to populated Team/Research/Managed
sections and hid the reader. Emptiness is now declared by the section that owns
the read — goal-team-results.tsx, goal-research-results.tsx,
goal-managed-results.tsx set data-result-empty only when the page is final
and empty. A page with a next cursor keeps its catalog and controls; Explore's
different DOM is untouched.

P2-2 — completion was inferred from a word list

RunBand printed recent.length - failed as 完成, so quota_monitor_poll
records and runs with a null classification read as completed. Completion and
failure now come only from the closed vocabularies the writers already validate:
ProgressResultClass (advanced / blocked, and the neutral
unchanged / exploration_exhausted / no_followup) and DeliveryOutcome
through the shipped deliveryOutcomeTone. Both are decoded additively in
data/status.ts. Everything unproven renders 观察 or 状态未知 — a monitor poll is
no longer 完成, and the free classification string never decides.

P2-3 — unknown attention was stated as "nothing waiting"

The region claimed 没有等你决定的事 whenever the list was empty. The affirmative
claim now requires availability ok and coverage complete and a known
zero; unavailable, partial or null counts say the region cannot confirm and keep
the task entry. Covered by the migrated scenarios (mode === "empty" now asserts
the unknown copy).

P2-4 — a normal Goal lost its sidebar state word

GoalRowActivity was gated behind goal.loadState, so a loaded running / queued
/ claimed Goal rendered no state at all (the mark is aria-hidden). Activity is
unconditional again. The row keeps one visible state word plus the gap badge
(the density goal), and the owner's full sentence stays on the same <small> as
title and aria-label, so the fact is not hover-only. When the line is
tight the badge yields before the word. Assertions were moved, not deleted:
goal-activity.mjs still asserts the leading word (执行中 / 已安排 /
宿主已领取) and additionally reads the full sentence from the accessible name.

While fixing this, the one-line layout was found to squeeze the state word to
nothing when a window badge was present (执行中 → 1px, screenshot
goal-activity-observation-first.png); the copy slot no longer shrinks.

Design follow-ups from the approved boards

  • Cost and Agent 用时 are back in the stage bar (成本 $4.12 · Agent 用时 3.2h,
    one clickable group after a hairline, both windows in its title). The density
    pass dropped them and, because the only drawer trigger is hidden above 720px,
    they became unreachable on desktop. Unmeasured stays 未采集, never $0.00;
    the group takes its own line below 640px. This resolves the open doc/code
    divergence: docs/development/design.md now describes the stage bar instead of
    "at most six stats in one row".
  • Home Goal card regains its second row. After e626fbbd5 re-added the reason
    line, the card had five grid tracks for six children, so .personal-home-goal-time
    fell into an implicit second row — which is also what the confirmation geometry
    contract measures. The mark spans both rows, the reason spans 2..-1, the meta
    slot names the bound host (not a raw agent id), and the dead footer/activity-chip
    rules and the unused GoalActivityChip are removed.
  • Dead strip CSS removed (35 rules); only the first-read skeleton keeps
    .goal-vital*. The state sentence and the last activity's actor, which the strip
    printed beneath its values, now live on the bar spans' titles.

Acceptance migration (11 scenarios, 63 old references)

old fact new owner read by the tests
six vitals cells .goal-stagebar-meta spans (state / wait / work / last activity)
state sub-line, last activity actor the two spans' title
cost, Agent time (+ both windows) .goal-stagebar-usage group and its title; 未采集 read back from the drawer
needs-you count / oldest / optional .goal-overview-attention (count, row <time>, header <small>)
brief running / claimed tiles the home lanes (personal-home-lane-running / -claimed)
home card geometry kept, plus state/time same-row and reason-below proofs

research-mode's two .goal-vitals negatives were vacuous (the class is gone) and
now assert the testid/class is absent; goal-activity.mjs is self-consistent
(every removed-tile lookup asserts absence).

Verification

  • tsc --noEmit (dashboard, tsc 6.0.3): pass.
  • check-css-custom-properties.mjs dashboard: no undefined references.
  • Source dev-server scenarios, all pass: goal-masthead, goal-showcase,
    goal-suggestions, goal-suggestions-live, since-you-looked,
    since-you-looked-live, navigation-sorting, research-mode,
    runtime-readiness, confirmed-operations, goal-activity.
  • Screenshots for each scenario are in the ignored
    output/playwright/personal-workspace/; no binaries are committed, per
    docs/development/documentation-layout.md retention rules.
  • Not run locally: the packaged (LOOPX_PERSONAL_WORKSPACE_PACKAGED=1) craft
    smoke and the full packaged build; CI owns those.

@mergify

mergify Bot commented Oct 30, 2026

Copy link
Copy Markdown

Hi @xiaoan17, the DCO Sign-off check did not pass. Please inspect
its details first: checkout, fetch, timeout or infrastructure errors
need their own recovery, not a rewrite of otherwise signed commits.

If the log confirms a missing Signed-off-by trailer, amend the
affected commit with git commit --amend -s; for multiple commits,
use an interactive rebase against the current base from the correct
base-repository remote and sign off each affected commit. Push the
rewritten PR branch with git push --force-with-lease origin HEAD.

5 similar comments
@mergify

mergify Bot commented Oct 30, 2026

Copy link
Copy Markdown

Hi @xiaoan17, the DCO Sign-off check did not pass. Please inspect
its details first: checkout, fetch, timeout or infrastructure errors
need their own recovery, not a rewrite of otherwise signed commits.

If the log confirms a missing Signed-off-by trailer, amend the
affected commit with git commit --amend -s; for multiple commits,
use an interactive rebase against the current base from the correct
base-repository remote and sign off each affected commit. Push the
rewritten PR branch with git push --force-with-lease origin HEAD.

@mergify

mergify Bot commented Oct 30, 2026

Copy link
Copy Markdown

Hi @xiaoan17, the DCO Sign-off check did not pass. Please inspect
its details first: checkout, fetch, timeout or infrastructure errors
need their own recovery, not a rewrite of otherwise signed commits.

If the log confirms a missing Signed-off-by trailer, amend the
affected commit with git commit --amend -s; for multiple commits,
use an interactive rebase against the current base from the correct
base-repository remote and sign off each affected commit. Push the
rewritten PR branch with git push --force-with-lease origin HEAD.

@mergify

mergify Bot commented Oct 30, 2026

Copy link
Copy Markdown

Hi @xiaoan17, the DCO Sign-off check did not pass. Please inspect
its details first: checkout, fetch, timeout or infrastructure errors
need their own recovery, not a rewrite of otherwise signed commits.

If the log confirms a missing Signed-off-by trailer, amend the
affected commit with git commit --amend -s; for multiple commits,
use an interactive rebase against the current base from the correct
base-repository remote and sign off each affected commit. Push the
rewritten PR branch with git push --force-with-lease origin HEAD.

@mergify

mergify Bot commented Oct 30, 2026

Copy link
Copy Markdown

Hi @xiaoan17, the DCO Sign-off check did not pass. Please inspect
its details first: checkout, fetch, timeout or infrastructure errors
need their own recovery, not a rewrite of otherwise signed commits.

If the log confirms a missing Signed-off-by trailer, amend the
affected commit with git commit --amend -s; for multiple commits,
use an interactive rebase against the current base from the correct
base-repository remote and sign off each affected commit. Push the
rewritten PR branch with git push --force-with-lease origin HEAD.

@mergify

mergify Bot commented Oct 30, 2026

Copy link
Copy Markdown

Hi @xiaoan17, the DCO Sign-off check did not pass. Please inspect
its details first: checkout, fetch, timeout or infrastructure errors
need their own recovery, not a rewrite of otherwise signed commits.

If the log confirms a missing Signed-off-by trailer, amend the
affected commit with git commit --amend -s; for multiple commits,
use an interactive rebase against the current base from the correct
base-repository remote and sign off each affected commit. Push the
rewritten PR branch with git push --force-with-lease origin HEAD.

@xiaoan17
xiaoan17 force-pushed the codex/local-dashboard-baseline branch from e58fc0d to 1828b80 Compare October 30, 2026 10:47
@xiaoan17

Copy link
Copy Markdown
Contributor Author

Browser-lane diagnosis: the redesign moved three shipped acceptance contracts

All three dashboard-browser shards failed, each on a different scenario, and
each failure traces to the same cause: this branch deliberately moved the
first screen's DOM and text, and three shipped scenarios still assert the
previous contract.

Shard Scenario Failure What it asserts
1 confirmed-operations page.waitForFunction timeout at confirmed-operations.mjs:98 the home brief returns to three tiles after the drawer closes
2 navigation-sorting First screen missing Codex; body=… the first screen names the agent/host
3 goal-activity locator.waitFor timeout at goal-activity.mjs:120 the Goal row shows its activity state and its owning host

What moved

The Goal row's activity sentence is gone. goal-sidebar.tsx now renders
the row's activity only when the Goal has a load state:

{goal.loadState ? <GoalRowActivity goal={goal} showLoad /> : null}

That <small> element was the only place on the first screen that named the
owning host: hostSurfaceLabel("codex-app") is "Codex App", and the row's
activity sentence is built from it (viaHost, hostClaimed). Shard 3 waits
for exactly that element (goalRow(...).locator("small", { hasText: "执行中" }))
and then asserts that a host-owned Goal reads 已安排 · 由 Codex App 线程负责,
and shard 2 asserts the first screen contains Codex for the same reason. The
agent id still appears on the board card (codex, from
goal.agentLabel ?? goal.agentId), but the host name no longer does.

The board card has no <footer> any more. The card is now identity mark,
short label, reason, meta, state and time:

<GoalIdentityMark goal={goal} />
<strong className="personal-home-goal-title" title={goal.title}>{goalShortLabel(goal)}</strong>
<p className="personal-home-goal-reason" title={…}>…</p>
<span className="personal-home-goal-meta">…</span>
<span className="personal-home-goal-state">…</span>
<small className="personal-home-goal-time" title={…}>…</small>

confirmed-operations.mjs reads that card's geometry through
card.querySelector("footer") and then footer.querySelector("small"), which
now has no footer to read. The scenario fails earlier than that, on the brief
tile count after the drawer closes; this branch also moves the brief out of
the right rail into the document column, so that part needs a local run to
separate from the layout change. The card-geometry read that follows is
independently broken by the removed <footer>.

The decision this needs

The design system this PR adds says a row is status dot or mark, primary text, secondary metadata, trailing time or count, and that the Goal page
carries the detail. That is a deliberate move of activity detail off the
overview, and it is the right shape — but it changes what the first screen
promises, so the shipped acceptances have to change with it. There are two
honest ways to close this:

  1. Update the three scenarios to the new contract. The activity state is
    still shown (the card's state slot and the Goal page's masthead), so the
    assertions can move to where the information now lives instead of being
    deleted. The host-ownership fact in particular still matters — a Goal whose
    work runs in an external host must not read as if LoopX were executing it —
    and that assertion should survive somewhere.
  2. Keep the owning host on the first screen. The card's secondary metadata
    slot already exists; naming the host there (Codex App) instead of the raw
    agent id (codex) keeps the fact visible and keeps the row at one line.

Recommendation: option 2 for the host name, option 1 for the rest. The
scenario bodies themselves were not changed in this branch, so the three
failures are the contract speaking, not a regression in the shipped behaviour.

This is left open deliberately rather than fixed in the branch: it is a
first-screen contract decision, and the PR asks the reviewer to judge exactly
this. Nothing here is fixed by widening a budget or relaxing an assertion.

@songoow songoow left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewer: model_agent | GPT-6 Astra Pro | OpenAI | self_reported

REQUEST_CHANGES — 4 项 P2。 审查 head e626fbbd575c39df8f6e4426517ab3a12852a266,merge-base 5c2ed9f6f2cf8a9bdc952f1c8dfceae92e062b0b;完整覆盖 27 文件(+752/-340)。前三项是实际源码/生产数据合同反例,第四项另有真实 CI 浏览器失败;没有把源码推导称作本地实跑。

动机 / Motivation

受影响的是在本地工作区跨 Goal 查看进度、处理待决事项、查阅成果的用户,包括正常加载和数据暂不可用时的日常浏览。

用户进入一个正在执行的 Goal,原先可在侧栏读到执行状态并进入成果阅读;这次把页面改为更紧凑的阶段条和分段文档,希望更快看清当前工作,但当前分支会隐藏正常状态及已有成果。

源码确认 Manager 运行条、看板卡片、Goal 阶段头部和侧栏通知已接入真实路由;同时确认有成果被隐藏、未知被写成完成或无待办、普通侧栏状态缺失四项回归。整体阅读体验是否改善尚未经过完整视觉验证。

本次审查只评价这个展示改动的现有入口和数据语义,不要求完成作者后续信息架构路线图,也不把缺少本地浏览器或改动规模本身当成代码缺陷。

改动思路 / Approach

保持现有数据投影、活动组件和结果 reader 的职责边界最合适:布局可以调整层次,完成/未知/空态仍由原事实和 owner 决定,CSS不应成为业务状态分类器。 当前 PR 的可用切片是默认工作区展示重排、短标题、阶段头部、通知入口与对应路由/smoke;不要求它完成未来信息架构,也不以未来提案免除当前语义回归。

/ 和 /design/local-dashboard 共用 DashboardRouteContent、searchSchema 和 DashboardPage,所以设计路由不隔离默认行为。原 status/digest/activity/result owner 提供事实,React/CSS负责呈现。判断完成、未知或空态的依据不能在布局层丢失。

合法输入 新判断产生的错误结果 应保留的依据
非空成果目录使用 button 无 li,隐藏整个成果 layout 结果 owner 的真实空态
monitor poll / classification 缺值 不含错误词,计为完成 明确结果类型与完成事实
attention 空但总量/覆盖未知 显示没有等你决定的事 完整且已知零才能空态
普通 Goal 没有 loadState 不显示 activity 文本 原活动状态组件

此表是源码对照,不是浏览器执行记录。

具体改动 / Changes

审查包括 Manager 运行条与摘要、看板标题/指标/原因行/滚动布局、Goal 三阶段头部与概览分段、任务/工作/交付证据折叠、通知迁入侧栏及设置/开发入口调整、短标题、临时设计路由、静态 preview、新设计文档和两个 smoke。全部 TSX/数据链、六个非生成 CSS diff、preview HTML 与 smoke 已读。生成 preview CSS 为 486,588 bytes 单行;检查了引用与本地资产边界,未声称逐字人工审读或把 mock 当实际产品验收。新增 smoke 主要覆盖 route、copy、query 往返,未覆盖非空成果 CSS 或普通侧栏状态。

采用改动前已接受的 apps/presentation/dashboard/design.md,修订 5c2ed9f6f2cf8a9bdc952f1c8dfceae92e062b0b。本次映射 visible-claim-lineage、completion-durable-facts、single-sidebar-semantic-state、files-readable 均未满足:可见状态须有事实来源,侧栏须有语义状态及文本/图标表达,成果须能阅读。新增 docs/design/dashboard-design-system.md 是提案,不能反向授权回归。

关键代码讲解

  1. goal-loopx-mode.css:146–147 从 DOM 标签推断空态;静态 import 使规则进入默认页面和多个结果 owner。
  2. DashboardPage:2969–2977 -> RunBand:60–82 新增 recentRuns 投影并统计完成,已越过纯样式调整。
  3. GoalOverview:56–61/109 读取 typed digest,却在列表空时忽略 unknown/coverage 的含义。
  4. GoalSidebar:110 新 gate 删除普通 GoalRowActivity;加载状态不能代替活动状态。
  5. GoalVitals:169 改为计划/执行/验收与原导航回调,goalShortLabel:10 做局部标题缩短;这些路径已读,整页与窄屏表现仍未验证。

对主干的风险 / Risks

P2-1,goal-loopx-mode.css:146–147:非空成果被隐藏。 Team/Managed/Research 的 nav[data-result-catalog] 直接映射 ResultCatalogRow,后者在 result-catalog.tsx:13 返回 button,没有 li。新增 :not(:has([data-result-catalog] li)) 因此在有成果时也成立,隐藏整个目录和 reader;后续 display:grid 特异性更低,不能覆盖。Explore 复用 layout 且无该 catalog 标记,也受影响。实际 Files 入口在 PersonalWorkspacePage:392–399。请优先依据 owner 显式空态,只压缩确实空的部分;若使用 data-result-key,还须保留 Explore 的不同 DOM 和当前页为空但仍有 next 的情况,不能简单全局替换;覆盖非空、空、loading/error,以及 desktop/narrow 目录选择、阅读与返回。此项是 markup/CSS 的确定性推导,未做本地浏览器实跑。

P2-2,dashboard-page.tsx:2973:非错误分类被算作完成。 runRecordSchema 的 classification 是可空自由字符串;真实 quota/monitor_poll.py 将 quota_monitor_poll 记录写入 recent_runs(第584–587行)。监控观察和缺值都不匹配错误词 regex,因而进入 completed;RunBand 又用 recent.length - failed 显示“完成”。请复用 typed outcome/完成事实,保留 unknown/observation,不要扩充错误词表。回归应从实际 status 消费端覆盖 monitor、缺值、明确完成和失败。

P2-3,goal-overview.tsx:109:未知待决事项被写成无待办。 attention 空、totalAttention 为 null 时,showAttention 为 false;上游 goal_workspace_digest.ts:195–219 确实允许 unavailable、null counts 与 unknown coverage,partial 也不能证明完整零。新增肯定空文案会误导用户无需介入;旁边“查看”按钮不修正主声明。请只在 availability 正常、coverage 完整且总数已知为零时显示空态,其他分支明确未知/部分和恢复入口,并分别覆盖上述状态。

P2-4,goal-sidebar.tsx:110:正常 Goal 失去侧栏状态文字。 普通 loaded Goal 没有 loadState,新条件移除 running/quiet/claimed 的 GoalRowActivity。身份标记是 aria-hidden;GoalActivityHelp 在缺少 hostThreadActivity、Goal 已停止,或存在 loadState 时返回 null,不能替代普通活动文本。实际 CI job 124053833801 的 goal-activity.mjs:120 等待 Progress Projection 行“执行中”15秒超时,与这次改动吻合。请恢复原 activity owner 的无条件显示,保留加载和补充说明;不要删断言。修复后重跑 LOOPX_PERSONAL_WORKSPACE_SCENARIO=goal-activity node examples/personal-workspace-browser-smoke.mjs,并覆盖其他正常状态。

语义与 CI 对齐

四项都触及现有状态来源、侧栏或成果阅读合同,不需要新增后端协议/权限。局部修复应复用原 typed digest/activity/result 词汇,保留现有 smoke。git diff --check PASS;上述实际 CI 活动断言 FAIL;本地 UI、同 fixture 的 base/head 浏览器对照和整页/default/expanded/narrow 视觉 UNVERIFIED。

独立核验了 CI bundle 的35个资产摘要、673个源码输入;24个 PR 改动输入全部匹配审查 head。manifest 源是 CI merge f38b5595c53ce30fe7c8fd002c379ada5dfe90f1(此 head 合入 main 0ad08b15dd7331051c93566075561a390a9cd7e2),其余20处差异全部等于该 main。因此可绑定受影响代码,不能称为整个未合并 head 的完整产物,更不能据此证明视觉通过。同 run 可读取 artifacts 无截图/trace;本地前端依赖/浏览器条件不齐,未渲染真实整页。

我的整体评价 / Verdict

REQUEST_CHANGES,固定 head e626fbbd575c39df8f6e4426517ab3a12852a266。 长程跟踪的可见信息与用户体验均为 regression;没有证据表明后端成果被删除或持久结算权威改变。阶段性布局调整是合理范围,但当前成果/状态回归不能延后处理。

路由复用和局部标题 helper 合理,修复无需新框架或兼容层。整体样式收益与体量是否相称仍缺完整视觉证据;改动规模本身不是 finding。本 PR 没有 opt-in/default-off 承诺,临时路由也不隔离默认页面。重新审查需要修正四项具体分支、保留既有断言、补正常/异常及成果阅读回归,并提供整页、默认、展开、窄屏证据。未完成视觉不是新增审美猜测,也没有声称完整 UI suite 或合并资格已通过。

未在本地浏览器渲染真实整页、默认/展开或窄屏状态;本地前端依赖/浏览器条件不齐。已核验的 CI bundle 不是截图,当前可读取的同 run artifacts 没有截图或 trace。 Manager、看板、Goal 默认/展开、成果阅读、侧栏通知、窄屏和键盘/焦点的完整视觉验收未完成;不把静态 board-preview 或 bundle 哈希当作渲染通过。

English verdict: REQUEST_CHANGES — e626fbb; four P2 regressions hide nonempty results, misreport completion and empty attention, and remove normal sidebar activity. Full browser parity and visual qualification remain unverified.

@mergify

mergify Bot commented Oct 30, 2026

Copy link
Copy Markdown

This pull request has merge conflicts with main and cannot be merged
until they are resolved. Please rebase or merge the base branch, @xiaoan17.

Choose the remote for the base repository, not an out-of-date fork.
For a fork clone, first inspect git remote -v; upstream must point
to https://github.com/loopx-project/loopx.git. If it is absent, add it
with git remote add upstream https://github.com/loopx-project/loopx.git.
Then run:

git fetch upstream
git rebase upstream/main
# Resolve each conflict, git add the resolved files, then git rebase --continue.
git push --force-with-lease origin HEAD

For a same-repository clone whose origin points to
https://github.com/loopx-project/loopx.git, use origin instead of
upstream for fetch/rebase. If you prefer merging the base, use
git merge <base-remote>/main and push normally.

Keep the DCO Signed-off-by trailer on every commit when you rebase.
https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork

@mergify mergify Bot added the needs-rebase Mergify: the pull request has merge conflicts with its base branch label Oct 30, 2026
- one-line Goal rows with short labels; full title moves to tooltip/drawer
- collapse the six-cell vitals strip into a Plan → Run → Accept stage bar
- wire status run history into a home run band (28 most recent runs)
- single-row task toolbar with one filter control; inline empty lanes
- desktop notifications become a sidebar bell instead of a settings block
- quiet the capability nav, inline scope hints, drop stray help icons
- dedupe the Delivery & evidence title; clamp drawer goal titles

Signed-off-by: xiaoan17 <xiaoan17@users.noreply.github.com>
Shape was ad hoc (7 radii in one screen). Introduce --pw-r-control/item/
card/pill and a 4-24px spacing scale, then replace 9/10/11/13/14/99px
literals with them. Settings now renders two radii instead of six.

Signed-off-by: xiaoan17 <xiaoan17@users.noreply.github.com>
- attention moves into the document column so the Goal page reads 01→04
- home tiles become flex-wrap so a tall tile stops stretching its row
- home spacing uses the token scale

Signed-off-by: xiaoan17 <xiaoan17@users.noreply.github.com>
Empty research history and empty report sections become one-row
statements instead of bordered panels.

Signed-off-by: xiaoan17 <xiaoan17@users.noreply.github.com>
break-all split npm command names mid-token; the diff table now gives
Axis a fixed width so the remaining columns stop stacking one word per
line.

Signed-off-by: xiaoan17 <xiaoan17@users.noreply.github.com>
A four-column table in a half-width panel stacked one word per line.
Each axis is now a card: title, then Current / Proposed / Gate.

Signed-off-by: xiaoan17 <xiaoan17@users.noreply.github.com>
The right rail held nothing but the inventory, so it cost a full column
for one collapsed row. The inventory is now a full-width disclosure after
the Now section; the Goal page is a single 1120px column.

Signed-off-by: xiaoan17 <xiaoan17@users.noreply.github.com>
The leading status glyph stole a column from every card, so titles
wrapped around it. Cards now match the design: two-line title, then a
status pill and age.

Signed-off-by: xiaoan17 <xiaoan17@users.noreply.github.com>
Radius and shadow already matched; padding, gutters and card type now
follow the same numbers as the design board.

Signed-off-by: xiaoan17 <xiaoan17@users.noreply.github.com>
Tokens, shape, spacing, type, status colour, component rules and the
stage/run-band flow, so the next screen starts from the same numbers.

Signed-off-by: xiaoan17 <xiaoan17@users.noreply.github.com>
Two browser contracts depended on what the density pass removed:
lanes must stretch so each owns a bounded scroll region, and a Goal card
still carries its reason sentence. The reason is now one clamped line.

Signed-off-by: xiaoan17 <xiaoan17@users.noreply.github.com>
@xiaoan17
xiaoan17 force-pushed the codex/local-dashboard-baseline branch from e626fbb to b3456e1 Compare October 30, 2026 18:07
@xiaoan17

Copy link
Copy Markdown
Contributor Author

Rebased onto main, and one more acceptance that moved with the design

The branch was rebased onto the current main (head b3456e110), so the PR is
no longer conflicting. The rebase touched one file,
apps/presentation/dashboard/src/features/personal-workspace/personal-workspace.css,
in two regions, and both were resolved mechanically:

  • Additive region — main added the .personal-document-attachment rules
    (the document-attachment feature) next to the block this branch adds. Both
    blocks are kept, main's first.
  • Same-rule region — main carried border-radius: 10px and
    .personal-composer-images figure > button; this branch's radius-token
    commit changed those to var(--pw-r-item) and
    .personal-composer-images button. The branch's side wins, since that is the
    tokenisation this branch introduces and --pw-r-item is defined in the same
    file.

No other file needed a decision: personal-workspace-model.ts,
personal-workspace-page.tsx and dashboard-page.tsx merged cleanly.

Local validation on the rebased head:

npx tsc --noEmit                                    clean
node scripts/check-css-custom-properties.test.mjs    ok
node scripts/check-css-custom-properties.mjs dashboard
    35 stylesheets, 70 defined tokens, 3813 bare references, no undefined references
npm run build:desktop                               built
npm run smoke:home-route                            home-route smoke ok

One more acceptance that moved with the design

examples/dashboard-home-browser-smoke.mjs is new in this branch, and it fails
at the branch's head — before and after the rebase, identically:

Error: Missing dashboard home text: 需要你, 询问全局待办, 汇总所有 Goal 进展, 创建新 Goal, Codex

The page it renders does show GOALS, LoopX 管家 and the showcase Goal
titles; what it no longer shows is the channel buttons (询问全局待办,
汇总所有 Goal 进展, 创建新 Goal) and the agent name (Codex) — the same
first-screen contents the three shipped scenarios assert. The branch's own new
acceptance therefore still encodes the previous first screen while the branch
implements the next one.

This file is not wired into CI, so it does not appear as a red check here; it
is the branch's own design acceptance, and it fails for the same reason the
shipped navigation-sorting, goal-activity and confirmed-operations
scenarios fail. It is left as it is rather than rewritten to match whatever
the page currently renders: pinning the current output would make the
acceptance vacuous, and what the first screen must show is the decision this
PR asks the reviewer to make.

The two options from the earlier diagnosis therefore cover four acceptances,
not three: either the first screen keeps naming the owning agent and its
channels, or those acceptances move to where the redesign puts that
information.

@mergify mergify Bot removed the needs-rebase Mergify: the pull request has merge conflicts with its base branch label Oct 30, 2026
… row

The density pass gated the sidebar activity line behind `goal.loadState`, so a
normal running, queued or claimed Goal showed no state word at all; the only
remaining glyph is aria-hidden, so the fact disappeared from the page (review
P2-4). Every loaded Goal renders its activity again, as one visible state word
plus the window badge, with the owner's full sentence kept on `title` and
`aria-label` so the host is never hover-only and never guessed. When the row is
tight the gap badge yields before the word: the copy slot no longer shrinks and
the badge truncates.

The home Goal card lost its second row when the reason line came back: five
tracks for six children put `.personal-home-goal-time` into an implicit row of
its own, which also broke the state/time geometry the confirmation contract
measures. The mark now spans both rows, the reason spans 2..-1, and the meta
slot names the bound host instead of a raw agent id. The dead footer and
activity-chip rules, and the unused `GoalActivityChip`, are removed.

Signed-off-by: xiaoan17 <xiaoan17@users.noreply.github.com>
Collapsing the six-cell vitals strip into the Plan -> Run -> Accept bar dropped
成本 and Agent 用时 with it. On desktop they became unreachable: the only
drawer trigger is hidden above 720px, so a Goal's spend and agent time had no
entry point at all.

The bar's meta row now ends with one clickable usage group, after a hairline:
`成本 $4.12 · Agent 用时 3.2h` in caption size, values at 500 weight with
tabular numerals, both 24h/7d windows in the button's title. It opens the
existing usage detail through the same `onFocusRegion("usage")` callback the
strip used, so no new surface is introduced. Unmeasured usage stays visible as
未采集 rather than a zero, and the group takes its own line below 640px so the
bar never wraps into the content area.

The strip's remaining CSS (35 rules: cells, values, sub-lines, ribbon, dots,
live arc) is deleted; only the first-read skeleton keeps `.goal-vital*`. The
state sentence (`Turn 收尾中`, activity-unknown, the host alsoKey) and the last
activity's actor, which the strip printed under its values, now live on the two
spans' titles so the facts survive the denser layout.

Signed-off-by: xiaoan17 <xiaoan17@users.noreply.github.com>
… unknown attention

Three regressions the review found in the density pass (P2-1..P2-3), each fixed
at the owner instead of by guessing from markup or words:

- Empty result sections were inferred with `:not(:has([data-result-catalog] li))`,
  but every catalog row renders a `<button>`, so the rule also fired when results
  existed and hid the whole catalog and reader. The three result owners now mark
  `data-result-empty` only when their own read is final and empty; a page that
  still has a next cursor keeps its catalog and controls, and Explore keeps its
  different DOM.

- RunBand subtracted failures from the slice length to print 完成, so monitor
  polls and runs with a null classification read as completed. Completion and
  failure now come only from the closed vocabularies the writers already
  validate: `ProgressResultClass` and `DeliveryOutcome`, decoded additively in
  `data/status.ts` and toned by the shipped `deliveryOutcomeTone`. Everything
  else stays 观察 or 状态未知; the free classification string never decides.

- The attention region printed "没有等你决定的事" whenever its list was empty,
  although the digest allows unavailable availability, null counts and partial
  coverage. The affirmative claim now needs availability ok, complete coverage
  and a known zero; every other branch says the region cannot confirm and keeps
  the task entry.

New copy is in both languages (`overview.attentionUnknown`, `runs.observation`,
`runs.recent`).

Signed-off-by: xiaoan17 <xiaoan17@users.noreply.github.com>
The redesign moved facts the workspace scenarios still read from the deleted
six-cell strip and brief tiles. Every assertion is migrated to the surface that
now owns the fact, not dropped:

- state, work count and last activity -> `.goal-stagebar-meta` spans; the state
  sentence and the last activity's actor -> their titles;
- cost and Agent time -> the `.goal-stagebar-usage` group, including both windows
  from its title, and the drawer's 未采集 for unmeasured usage (the old cells
  were non-interactive, so that contract is now a real readback);
- needs-you counts, oldest age and optional count -> `.goal-overview-attention`;
  needs-you is asserted as the attention tone with danger explicitly absent;
- running/claimed brief tiles -> the home lanes they moved to (`goal-activity`
  is now self-consistent: every removed-tile lookup asserts absence);
- home card geometry kept and strengthened: state and time share the first row
  and the reason sits below it.

`research-mode`'s vacuous `.goal-vitals` negatives now assert the testid and
class are absent; the loading skeleton keeps `.goal-vital*`.

Verified locally: goal-masthead, goal-showcase, goal-suggestions,
goal-suggestions-live, since-you-looked, since-you-looked-live,
navigation-sorting, research-mode, runtime-readiness, confirmed-operations and
goal-activity all pass against the source dev server.

Signed-off-by: xiaoan17 <xiaoan17@users.noreply.github.com>
The composition guide still required "at most six stats in one row" while the
Goal page renders a one-line stage bar, so the document and the product
disagreed about the same region. The paragraph now describes the shipped bar:
the stage track, the Goal's own facts as captions in reading order, and the
cost / Agent-time pair that opens the usage detail.

Signed-off-by: xiaoan17 <xiaoan17@users.noreply.github.com>
@xiaoan17

Copy link
Copy Markdown
Contributor Author

回应上一轮 review 的四项 P2,全部落在 owner 上修,没有靠 DOM 形状或词表推断;head 现在是 6a936eaae(在其上追加 5 个 commit,PR body 已补 "Review follow-up" 一节)。

P2-1 非空成果被藏 — 删掉 goal-loopx-mode.css 里 :not(:has([data-result-catalog] li)) 的 DOM 猜测(catalog 行是 <button>,所以规则对非空目录也成立)。改由三个结果 owner 自己声明:goal-team-results.tsx / goal-research-results.tsx / goal-managed-results.tsx 只在「本页已结束且为空」时写 data-result-empty;带 next cursor 的页保留目录与「继续查找」入口;Explore 的 DOM 不受影响。

P2-2 完成态靠词表推断 — RunBand 原来是 recent.length - failed 当「完成」,所以 quota_monitor_poll 和 classification 缺值的记录都读成完成。现在只认 writer 已经校验过的封闭词表:ProgressResultClass(advanced / blocked / 中性三值)与 DeliveryOutcome(复用已发布的 deliveryOutcomeTone),在 data/status.ts 里做加法式解码;其余一律 观察 或 状态未知,自由字符串不再决定完成。

P2-3 unknown 被写成"没有等你决定的事" — 肯定式空态现在要求 availability=ok、coverage=complete 且总数已知为零;unavailable / partial / null 计数一律显示"无法确认",并保留任务入口。迁移后的场景里 mode === "empty" 断言的就是 unknown 文案。

P2-4 普通 Goal 丢侧栏状态 — 去掉 goal.loadState 前置 gate,活动行恢复无条件显示;密度目标仍是一个可见状态词 + 缺口 badge,完整句子同时放在同一个 <small> 的 title 和 aria-label(不再是 hover-only)。断言是搬家不是删除:goal-activity.mjs 仍断言可见词(执行中 / 已安排 / 宿主已领取),再额外从可访问名读整句。修这一项时还发现单行布局在带窗口 badge 时会把状态词挤成 1px(截图 goal-activity-observation-first.png),已让 badge 先让位。

另外两项来自设计稿的修正:

  • 成本 / Agent 用时回到 stage bar(成本 $4.12 · Agent 用时 3.2h,竖线后一个可点分组,两个窗口进 title)。原改动删掉这两项后,桌面上唯一打开抽屉的入口在 >720px 被隐藏,两个数字实际不可达;未采集仍显示 未采集,不显示 $0.00。docs/development/design.md 同步改成描述 stage bar,消除文档与代码的分歧。
  • 首页 Goal 卡片补回第二行:e626fbbd5 加回 reason 后是 5 轨 6 子元素,time 掉进隐式第二行(也正是确认契约测量的位置);现在 mark 跨两行、reason 占 2..-1,meta 显示宿主名,并清掉死掉的 footer/chip CSS 与未使用的 GoalActivityChip。

验收侧:11 个场景 63 处旧引用全部迁到新 owner(state/work/last → .goal-stagebar-meta;state 子句与 actor → span title;成本/用时 → usage 分组 + 抽屉回读;needs-you 计数 → .goal-overview-attention;brief running/claimed → 首页泳道),research-mode 两处空断言改为真正的 testid/class 缺席断言。本地 tsc --noEmit 通过,以下场景在源码 dev server 上全绿:goal-masthead、goal-showcase、goal-suggestions、goal-suggestions-live、since-you-looked、since-you-looked-live、navigation-sorting、research-mode、runtime-readiness、confirmed-operations、goal-activity。打包版 craft smoke 与整包构建仍交给 CI。

The density pass moved the source-failure reason into a `<details>` next to the
diagnostics, so the banner only said that the source could not be read while the
actual cause (`home.health.sourceUnreadableDetail`) stayed behind a 诊断信息
disclosure. A consequential failure has to stay visible: the issues list is the
banner's own row again and only the deeper diagnostics fold.

The settings page also kept the removed notification block's props and its
support-copy map, and the workspace page still passed both; the bell is the only
notification surface now, so these leftovers and their call-site arguments are
deleted.

Signed-off-by: xiaoan17 <xiaoan17@users.noreply.github.com>
Two failures that only surfaced once the earlier shard failures cleared:

- `goal-activity` matched the sessions read with `**/api/chat/sessions?*`. A
  glob's `?` does not require a query string to be present for the match, but it
  does require one character after `sessions`, so the 503 fixture was not applied
  when the read carried no query and the row kept a host-owned sentence. The
  route now matches the path with or without a query, which makes the
  "unreadable session state" branch deterministic.

- `attention-notifications` and `attention-notifications-live` drove a settings
  checkbox that the density pass replaced with the sidebar bell, so they waited
  on a testid that no longer exists. Both now read the bell the way a person
  does — accessible name `桌面通知`, `aria-pressed`, `is-{off|on|blocked}`, the
  state sentence in `title`, the dot, and disabled for a blocking browser — and
  keep every permission-request count, stored preference, notification count,
  partial-read and silence assertion. The granted/denied/unsupported sentence the
  old block printed has no surface any more; the bell's disabled state is what is
  asserted instead.

Signed-off-by: xiaoan17 <xiaoan17@users.noreply.github.com>
…ings block

The user guide still pointed at a screenshot of 设置 → 外观 → 桌面通知 and told
people to switch notifications there. The control is the sidebar bell now, and
the permission sentence moved into its title/disabled state, so section 7.1 says
that instead. The superseded settings screenshot had no other consumer and is
removed with it.

Signed-off-by: xiaoan17 <xiaoan17@users.noreply.github.com>
…chment hit area

Two regressions the packaged smokes caught once the earlier failures cleared:

- Moving attention into the document column left `is-attention-first` as a pure
  CSS `order: -1`, so at a narrow width the blocking decision was painted above
  the Goal's direction while the DOM still read direction first. The document now
  renders its slots in the promoted order too, using keyed fragments so React
  moves the existing nodes instead of remounting them — a reflow keeps focus on
  the decision it moved, which is what the craft contract measures.

- The composer's remove button lost its `figure >` child scope in the radius
  token pass, so `.personal-composer-images button` also matched the expand
  button inside a document attachment. That button became absolutely positioned
  over the chip and swallowed its clicks. The rule is scoped to the figure's own
  child again.

Signed-off-by: xiaoan17 <xiaoan17@users.noreply.github.com>
…n owners

- The craft smoke addressed the inline rail by a testid that the density pass
  removed with the right rail; the inventory now lives inside the document as
  `.goal-workspace-rail-inline`, and the smoke reads that wrapper. Its narrow
  column assertions now match the promoted DOM order the app renders.

- The task toolbar is one filter control, so the inspector smoke selects
  受阻与延后 through the `任务状态筛选` combobox instead of a button group that no
  longer exists.

- `goal-activity`'s unreadable-session block asserted one exact sentence. Which
  sentence the owner composes depends on what the status snapshot already
  declared, so the block now waits for the state to be named and asserts the
  invariant it exists for: an unreadable session is never rendered as running.

Signed-off-by: xiaoan17 <xiaoan17@users.noreply.github.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants