fix(dashboard): report what a confirmed team plan actually materialized - #4598
huangruiteng wants to merge 2 commits into
Conversation
A confirmation can create every lane, some of them, or find them already
present, and the receipt is the only surface that knows which happened. The
confirmation card ignored it: every applied `team.plan` rendered the same
"Applied. LoopX state will refresh." line, so a plan whose lanes were partly
left unstaffed read exactly like a plan that was fully staffed. That is the
last R1 exit requirement the roadmap still lists for this path ("the initiating
surface displays the exact outcome").
Read the receipt instead of assuming success:
* `teamPlanAppliedOutcome` classifies the receipt as `applied`,
`partially_applied` or `already_present` and carries the created-lane count
and the gap count; `teamPlanAppliedLine` renders it, falling back to the
surface's own applied sentence for a receipt that records no team-plan
outcome (and never treating a typed failure as an applied outcome).
* The card model carries the outcome from the apply response, and the drawer
shows it for an applied plan.
* A partial application now says how many lanes exist and how many were left
unstaffed; a replayed plan says the lanes already existed instead of implying
a new creation. Both locales are updated.
* The browser fixture answers an apply with the shape the product writes (lanes
created, gap count) read from the stored proposal, so the steward-journey
scenario confirms a two-lane plan whose second lane is unstaffed and now
asserts the partial sentence instead of the generic one.
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
huangruiteng
left a comment
There was a problem hiding this comment.
Approval conclusion (author-owned PR; GitHub blocks formal self-approval)
Reviewed exact head 5f19aa591e3c2a386350dea5c5b2fa7965be5bdd (re-read immediately before publication; unchanged). Policy revision 6. loopx pr-review --check-result returned ok: true, verdict APPROVE, for this exact head before publication.
动机
这条 PR 修的是本 lane R1 交付链上最后一条 exit 项:"发起面必须显示这次确认到底做了什么"。团队计划(team.plan)被确认后可能建了全部 lane、只建了一部分、或发现 lane 已存在,而只有 apply receipt 知道是哪种。但确认卡片没读它:任何 applied 的 team.plan 都渲染同一句 drawer.proposalApplied("已应用,LoopX 状态将刷新。"),于是一个只组建了一半的计划读起来和完全组建成功一模一样。
- 影响面:凡是带"无法组建 lane"的计划都会命中;失败是静默的,业主会以为那条 lane 已经在跑。
- 之后的代价:缺失的工作直到有人去翻 Goal 的 todo 才会暴露——而 receipt 里早就写着真相。
- 这是 receipt 字段(
team_plan_partially_applied/gap_count/lanes,已在 #4582 合入)在消费者侧的收口,不是再造一份事实。 - 判定:
justified_increment——补齐一个已被 roadmap 点名的可观测缺口,不是"代码能跑"式的辩解。
改动思路
读已经落盘的权威 receipt,而不是新增第二个写入者或第二份事实:
- 入口:业主确认卡片 →
applyTypedAction返回 store 里持久化的 proposal(含 receipt)。 - 决策归属:
team-plan-preview.ts本来就是该计划所有读法的 owner(字段、goal id、lane 数、以及同族分类),所以"这次 apply 是什么结果"也放在这里;context-drawer.tsx只负责把这一行显示出来。 - 复用而非新建:分类复用 receipt 既有的 outcome 词表,沿用已有的 applied 句子作为 fallback,没有引入新 outcome enum,也没有新增 server 侧投影。
- 纯读:读 receipt 是纯粹的,apply 路径、preview、结算全部未动;卡片不可能与 receipt 不一致,因为它就是从 receipt 派生的。
具体改动
11 个文件、+181/-19:production 130/8 分布在五个文件(helper 对、卡片模型字段、页面接线、drawer 分支、i18n 一对);tests/fixtures 36/3 分布在三个文件(单测 smoke、浏览器场景及其 fixture);generated 24/12 分布在三个 bundle 文件(loopx/web/chat 资产、index.html、retention 清单);docs 0;mechanical moves 0。
关键代码讲解
apps/presentation/dashboard/src/features/personal-workspace/team-plan-preview.ts:140teamPlanAppliedOutcome(receipt)—— 把 applied 计划分类为team_plan_applied/team_plan_partially_applied/team_plan_lanes_already_present,并带上 created-lane 与 gap 计数。只读team_plan_*系列;receipt 没有该 outcome(或属于 typed failure)时返回null,让调用方保留自己那句话。纯读,无副作用。apps/presentation/dashboard/src/features/personal-workspace/team-plan-preview.ts:168teamPlanAppliedLine(outcome, t)—— 部分组建说清"建了几条、几条未组建";重放计划说明"已存在的 lane 没有被重复创建";其它情况 fallback 到drawer.proposalApplied。这个 fallback 保证其它 action kind 与"无 outcome 的旧 receipt"逐字节不变。typed failure 返回失败语义而不是被读成成功。apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-page.tsx:636workspaceProposal—— 卡片构建处,从 apply 响应里取出 outcome。只为team.plan设置,所以其它卡片不会多出字段;缺 receipt 时字段留空,drawer 走默认句。apps/presentation/dashboard/src/features/personal-workspace/context-drawer.tsx:974applied 分支 —— 唯一改动点是 applied 分支;failure / stale / gated / rejected 渲染完全未动。examples/personal-workspace-browser/fixture.mjs:1565teamPlanReceipt—— 让 apply 返回产品真正会写的 receipt 形状(outcome / gap_count / lanes),并且从 action store 里读计划(注入的 proposal 不在 session preview 里)。第一次实现正是错在这里、被 dev 场景抓出来后修的——这也是本次唯一的返工点。
语义与 CI 对齐
semantic_alignment:aligned / reuse_existing。改动读的是规范 apply owner 写的共享 receipt 契约,只消费、不重解释、不扩展;本地校验(单测 smoke、dev + packaged 浏览器场景、38 条 team-plan Python 测试、loopx canary premerge --from-git-diff 0 failure)都在该 head 上跑过,bundle 重建是幂等的,所以 packaged-asset 检查对同一 head 依然满足。
对主干的风险
- 爆炸半径:只有
team.plan的 applied 行。其它 action kind、其它 status、receipt 契约、apply 路径都未变;其它情况有断言钉住 fallback。 - 反向场景(最容易出事的那个):业主确认了一个留有未组建 lane 的计划、却读到"完全成功",从此没人跟进。这个 PR 的浏览器场景之前断言的正是那句通用句——也就是那个假成功;现在改成断言部分组建句。
- 未验证面:没有跑真实 manager 通道的 Lark 卡片(消费
review_card_frame_v0的那条面);Python apply 路径未变;仓库必修 jobdashboard-acceptance在 main 上独立为红(自有 todo 跟踪),与本改动无关。 - 推送前 CI 状态:本 head 上必需检查仍在跑(
Windows desktop/build/dashboard-acceptance/kernel-static-checks/ 兼容性矩阵 /stage2c/test-shard等 pending),已通过的只有Sign-off、changes、dependency-review与一条build。我没有等到全绿就发布本 review;合并前的 gate 必须在 head 不变的前提下重跑。 - 残留(PR 已明写):只有 applied 族会渲染具体句;确认后一条 lane 都没组建的计划仍走卡片既有的 failure 句;R1 的 F4(自动恢复 + 执行屏障)未触及。
- 回滚:revert 即恢复单句,没有任何持久状态变化。
我的整体评价
同意合并(待 owner 决定;本 PR 属于 apps/** 行为面,按现行规则只提 PR、不自合并、不 admin-bypass)。
这是一个成正向、且 proportional 的切片:它关掉的是一个已被 roadmap 点名的可观测缺口("把部分组建渲染成部分组建"),机制成本只有一个 helper 对 + 一个 drawer 分支 + 两处 i18n,没有新契约、没有新投影、没有新模块。它把"业主对已承诺工作的认知"对齐到 receipt 里的既有事实,而不是再加一层需要同步的第二真相。最有力的反对意见("卡片只该显示失败,给出计数会诱导业主相信一个自己无法核验的数字")不成立的原因是:这些计数来自 store 持久化的规范 receipt,和 Goal 的 todo 显示的是同一批事实;把它们藏起来不是更保守,而是把假成功留给业主。
English verdict
APPROVE. Exact head 5f19aa59 of #4598 is a proportionate, well-owned fix: the confirmation card now reads the canonical apply receipt's recorded outcome instead of printing one generic "Applied" line, so a partially staffed team plan can no longer present itself as a full success. The change is a pure read in the existing team-plan presentation owner plus one drawer branch, with the previous sentence kept as the fallback (pinned by a unit smoke and by both dev and packaged browser scenarios; 38 team-plan Python tests and canary premerge pass). Residual scope is declared: only the applied family renders a specific line, the Lark card surface was not exercised, and the repository's required dashboard-acceptance job is red on main independently of this head. Remote required checks are still pending on this head, so merge readiness must be re-established before any merge. This is a frontend/apps/** behavior change, so it is proposed for review only — no self-merge and no admin bypass.
huangruiteng
left a comment
There was a problem hiding this comment.
Approval conclusion (author-owned PR; GitHub blocks formal self-approval)
Reviewed exact head 5f19aa591e3c2a386350dea5c5b2fa7965be5bdd (re-read immediately before publication; unchanged). Policy revision 6. loopx pr-review --check-result returned ok: true, verdict APPROVE, for this exact head before publication.
Format correction: this supersedes my earlier record on the same exact head (pullrequestreview-5227634323), whose verdict line used a heading instead of the repository's required English verdict: line form, so it could not be read as an approval conclusion. The findings, evidence and verdict are unchanged.
动机
这条 PR 修的是本 lane R1 交付链上最后一条 exit 项:"发起面必须显示这次确认到底做了什么"。团队计划(team.plan)被确认后可能建了全部 lane、只建了一部分、或发现 lane 已存在,而只有 apply receipt 知道是哪种。但确认卡片没读它:任何 applied 的 team.plan 都渲染同一句 drawer.proposalApplied("已应用,LoopX 状态将刷新。"),于是一个只组建了一半的计划读起来和完全组建成功一模一样。
- 影响面:凡是带"无法组建 lane"的计划都会命中;失败是静默的,业主会以为那条 lane 已经在跑。
- 之后的代价:缺失的工作直到有人去翻 Goal 的 todo 才会暴露——而 receipt 里早就写着真相。
- 这是 receipt 字段(
team_plan_partially_applied/gap_count/lanes,已在 #4582 合入)在消费者侧的收口,不是再造一份事实。 - 判定:
justified_increment——补齐一个已被 roadmap 点名的可观测缺口,不是"代码能跑"式的辩解。
改动思路
读已经落盘的权威 receipt,而不是新增第二个写入者或第二份事实:
- 入口:业主确认卡片 →
applyTypedAction返回 store 里持久化的 proposal(含 receipt)。 - 决策归属:
team-plan-preview.ts本来就是该计划所有读法的 owner(字段、goal id、lane 数、以及同族分类),所以"这次 apply 是什么结果"也放在这里;context-drawer.tsx只负责把这一行显示出来。 - 复用而非新建:分类复用 receipt 既有的 outcome 词表,沿用已有的 applied 句子作为 fallback,没有引入新 outcome enum,也没有新增 server 侧投影。
- 纯读:读 receipt 是纯粹的,apply 路径、preview、结算全部未动;卡片不可能与 receipt 不一致,因为它就是从 receipt 派生的。
具体改动
11 个文件、+181/-19:production 130/8 分布在五个文件(helper 对、卡片模型字段、页面接线、drawer 分支、i18n 一对);tests/fixtures 36/3 分布在三个文件(单测 smoke、浏览器场景及其 fixture);generated 24/12 分布在三个 bundle 文件(loopx/web/chat 资产、index.html、retention 清单);docs 0;mechanical moves 0。
关键代码讲解
apps/presentation/dashboard/src/features/personal-workspace/team-plan-preview.ts:140teamPlanAppliedOutcome(receipt)—— 把 applied 计划分类为team_plan_applied/team_plan_partially_applied/team_plan_lanes_already_present,并带上 created-lane 与 gap 计数。只读team_plan_*系列;receipt 没有该 outcome(或属于 typed failure)时返回null,让调用方保留自己那句话。纯读,无副作用。apps/presentation/dashboard/src/features/personal-workspace/team-plan-preview.ts:168teamPlanAppliedLine(outcome, t)—— 部分组建说清"建了几条、几条未组建";重放计划说明"已存在的 lane 没有被重复创建";其它情况 fallback 到drawer.proposalApplied。这个 fallback 保证其它 action kind 与"无 outcome 的旧 receipt"逐字节不变。typed failure 返回失败语义而不是被读成成功。apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-page.tsx:636workspaceProposal—— 卡片构建处,从 apply 响应里取出 outcome。只为team.plan设置,所以其它卡片不会多出字段;缺 receipt 时字段留空,drawer 走默认句。apps/presentation/dashboard/src/features/personal-workspace/context-drawer.tsx:974applied 分支 —— 唯一改动点是 applied 分支;failure / stale / gated / rejected 渲染完全未动。examples/personal-workspace-browser/fixture.mjs:1565teamPlanReceipt—— 让 apply 返回产品真正会写的 receipt 形状(outcome / gap_count / lanes),并且从 action store 里读计划(注入的 proposal 不在 session preview 里)。第一次实现正是错在这里、被 dev 场景抓出来后修的——这也是本次唯一的返工点。
语义与 CI 对齐
semantic_alignment:aligned / reuse_existing。改动读的是规范 apply owner 写的共享 receipt 契约,只消费、不重解释、不扩展;本地校验(单测 smoke、dev + packaged 浏览器场景、38 条 team-plan Python 测试、loopx canary premerge --from-git-diff 0 failure)都在该 head 上跑过,bundle 重建是幂等的,所以 packaged-asset 检查对同一 head 依然满足。
对主干的风险
- 爆炸半径:只有
team.plan的 applied 行。其它 action kind、其它 status、receipt 契约、apply 路径都未变;其它情况有断言钉住 fallback。 - 反向场景(最容易出事的那个):业主确认了一个留有未组建 lane 的计划、却读到"完全成功",从此没人跟进。这个 PR 的浏览器场景之前断言的正是那句通用句——也就是那个假成功;现在改成断言部分组建句。
- 未验证面:没有跑真实 manager 通道的 Lark 卡片(消费
review_card_frame_v0的那条面);Python apply 路径未变;仓库必修 jobdashboard-acceptance在 main 上独立为红(自有 todo 跟踪),与本改动无关。 - 推送前 CI 状态:本 head 上必需检查仍在跑(
Windows desktop/build/dashboard-acceptance/kernel-static-checks/ 兼容性矩阵 /stage2c/test-shard等 pending),已通过的只有Sign-off、changes、dependency-review与一条build。我没有等到全绿就发布本 review;合并前的 gate 必须在 head 不变的前提下重跑。 - 残留(PR 已明写):只有 applied 族会渲染具体句;确认后一条 lane 都没组建的计划仍走卡片既有的 failure 句;R1 的 F4(自动恢复 + 执行屏障)未触及。
- 回滚:revert 即恢复单句,没有任何持久状态变化。
我的整体评价
同意合并(待 owner 决定;本 PR 属于 apps/** 行为面,按现行规则只提 PR、不自合并、不 admin-bypass)。
这是一个成正向、且 proportional 的切片:它关掉的是一个已被 roadmap 点名的可观测缺口("把部分组建渲染成部分组建"),机制成本只有一个 helper 对 + 一个 drawer 分支 + 两处 i18n,没有新契约、没有新投影、没有新模块。它把"业主对已承诺工作的认知"对齐到 receipt 里的既有事实,而不是再加一层需要同步的第二真相。最有力的反对意见("卡片只该显示失败,给出计数会诱导业主相信一个自己无法核验的数字")不成立的原因是:这些计数来自 store 持久化的规范 receipt,和 Goal 的 todo 显示的是同一批事实;把它们藏起来不是更保守,而是把假成功留给业主。
English verdict: APPROVE - exact head 5f19aa591e3c2a386350dea5c5b2fa7965be5bdd of #4598 is a proportionate, well-owned fix: the confirmation card now reads the canonical apply receipt's recorded outcome instead of printing one generic "Applied" line, so a partially staffed team plan can no longer present itself as a full success. The change is a pure read in the existing team-plan presentation owner plus one drawer branch, with the previous sentence kept as the fallback (pinned by a unit smoke and by both dev and packaged browser scenarios; 38 team-plan Python tests and canary premerge pass). Residual scope is declared: only the applied family renders a specific line, the Lark card surface was not exercised, and the repository's required dashboard-acceptance job is red on main independently of this head. Remote required checks were still pending on this head at publication, so merge readiness must be re-established on the unchanged head before any merge. This is a frontend/apps/** behavior change, so it is proposed for review only: no self-merge and no admin bypass.
…-team-plan-outcome Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com> # Conflicts: # loopx/web/chat/asset-retention.json # loopx/web/chat/assets/index-BoXxJXKW.js # loopx/web/chat/assets/index-CFOC0-T9.js # loopx/web/chat/assets/index-CXvjZarX.js # loopx/web/chat/index.html
huangruiteng
left a comment
There was a problem hiding this comment.
Approval conclusion (author-owned PR; GitHub blocks formal self-approval)
Reviewed exact head: 6f5d936088c0fe0c5619252e64654855b4dd5c68 (re-review after merging origin/main and rebuilding the packaged chat bundle).
loopx pr-review --check-result on the matching packet returned ok: true with no approval_blockers; all 19 evidence rows for this control-plane plan are verified.
动机
确认一张团队计划之后,卡片对任何已应用的计划都只说一句「已应用」。但回执其实已经区分了三种真实结果:全部建成(team_plan_applied)、部分建成(team_plan_partially_applied + gap_count)、本来就存在(team_plan_lanes_already_present)。前两次误读尤其伤人——部分落地被读成完整成功,会让 owner 以为整个团队都配齐了;复用被读成新建,会让 owner 以为刚创建了并不存在的 lane。
这个 PR 的价值:卡片停止忽略已经存在的权威事实,三种结果各自说清楚,且失败永远不会被读成成功。
改动思路
关键判断是不新增判定源。回执的 outcome 就是唯一知道「实际落地了什么」的面,前端拿不到这个事实,所以从 plan 反推会产生第二权威;在回执里再加一个「显示用」字段则会让两边可能分叉。因此只读既有 outcome(配合 gap_count 与 lanes),按三值渲染。
读取器用前缀收窄 + 三个精确取值判定,未知 outcome 返回 null,卡片随后落到既有文案。这条 null 分界是刻意的:写入失败的 typed outcome 绝不能被兜底成「已应用」。
具体改动
team-plan-preview.ts(+57):TeamPlanAppliedOutcome类型、teamPlanAppliedOutcome(receipt)读取器、teamPlanAppliedLine(outcome, t)文案函数。personal-workspace-model.ts/personal-workspace-page.tsx:预览对象新增可选teamPlanOutcome,仅在action_kind === "team.plan"时从proposal.receipt计算。context-drawer.tsx:已应用行对team.plan走新文案函数,其余分支(含operation.execute的 primaryLabel 与 gated 样式)保持不变。i18n.tsx:中英双语各两条文案。team-plan-proposal-smoke.ts(+56)与examples/personal-workspace-browser/*:断言三种结果的可见文本与未知 outcome 的回退。loopx/web/chat/*:打包产物在最新 main 上重跑npm run build:chat生成。
关键代码讲解
const outcome = asText(record.outcome);
if (!outcome.startsWith("team_plan_")) return null;
if (outcome === "team_plan_partially_applied") return { kind: "partially_applied", created: lanes, gaps };
if (outcome === "team_plan_lanes_already_present") return { kind: "already_present", created: lanes, gaps };
if (outcome === "team_plan_applied") return { kind: "applied", created: lanes, gaps };
return null;先用前缀收窄,再只认三个已知取值;未知取值一律 null。若把最后一行改成兜底「已应用」,一次写入失败或未来新增的 outcome 就会被显示成成功——这正是被 smoke 断言锁住的行为。
teamPlanAppliedLine 对 null 回落到既有的 drawer.proposalApplied,因此其他 action kind、其他状态、以及缺少回执的场景,呈现与改动前逐字一致。
对主干的风险
风险面只有一个:未知 outcome 是否会被读成成功。该分界由读取器的早返回保证,并有 smoke 断言;另外 action_kind === "team.plan" 的收窄保证其他动作的回执不会被误读。后端契约、apply 结果、Todo 与 quota 均未改动。
本次同时解决了该分支长期 DIRTY 的根因:与 main 的冲突全部在打包 frontend 产物(hashed 入口文件 rename/rename)。源码文件本身全部 clean merge;我按「把该目录重置到 main → 重跑 npm run build:chat → git add -A」重建,而不是手工合并压缩过的 bundle。重建后相对 main 的差异是 11 文件 +190/-22,其中打包产物 42 行,与前端源码改动相称。
在更新后的 head 上实测:
cd apps/presentation/dashboard && npm run smoke:team-plan-proposal→ ok(三值 + 未知回退断言)env -u PYTHONPATH uv run --extra test python examples/dashboard-pwa-bundle-smoke.py→ ok(bundle 与 asset-retention 一致)- 在打包 bundle 上运行 team-plan 浏览器示例 → 三种文案可见(含中文,无乱码)
env -u PYTHONPATH uv run --extra test loopx canary premerge --from-git-diff→ 0 failures / 0 advisories
未验证(如实标注,不当通过):未做真实浏览器像素级视觉审查;未在 Lark 侧同一回执的展示上复核(载荷相同、展示层不同)。本机验证依赖未跟踪的 npm 依赖(dashboard node_modules 与根 node_modules)。
边界声明:本 PR 改动 apps/** 与打包前端产物,按仓库规则属控制面改动,只提 PR、由维护者合并,作者不做自合并。
我的整体评价
让卡片读回执而不是继续忽略它,方向与边界都对:判定仍留在唯一所有者手里,前端只做映射。无阻断性发现。
残余风险是卡片目前只报数量(已创建 N 条、未组建 M 条),不逐条列出是哪些 lane——逐条命名由同栈的缺口命名切片承载,两者在同一条回归路径上互补。
English verdict: APPROVE - re-verified on exact head 6f5d936: the team-plan card no longer reports every applied plan as one success sentence. It reads the existing receipt outcome (which already distinguishes applied / partially applied / lanes already present) and renders each of the three, while an unknown outcome returns null so a typed failure can never be shown as success. No backend field or contract changed. The long-standing DIRTY state was only the packaged frontend bundle and was resolved by rebuilding it on the new main. The dashboard team-plan smoke, the packaged-bundle smoke, the on-bundle browser example, and premerge canary (0 failures) all pass. No blocking finding.
|
区分完整分配、部分分配和历史结果恢复有价值,已纳入 #4633。完成态现在直接列出任务与待安排原因,原计划折叠,并移除失效的确认按钮。结果只表示任务分配,不表示接收方已采纳或开始执行。 本 PR 关闭并保留分支,避免与 #4633 重复推进。替代 PR 尚待维护者审阅合并。 Superseded by #4633: retained the useful contract, consolidated implementation and validation, and kept assignment separate from collaboration/execution authority. |
A team-plan confirmation can create every lane, some of them, or find them already present, and the apply receipt is the only surface that knows which happened. The confirmation card ignored it: every applied
team.planrendered the sameApplied. LoopX state will refresh.line (drawer.proposalApplied), so a plan whose lanes were partly left unstaffed read exactly like a plan that was fully staffed. This is the last R1 exit item the roadmap still lists for this path — "the initiating surface displays the exact outcome" — and the sibling of the already-mergedteam_plan_partially_applied/gap_count/lanesreceipt fields.Change
team-plan-preview.tsgainsteamPlanAppliedOutcome(receipt)(classifiesteam_plan_applied/team_plan_partially_applied/team_plan_lanes_already_present, with created-lane and gap counts) andteamPlanAppliedLine(outcome, t), which falls back to the surface's own applied sentence when the receipt records no team-plan outcome and returns the failure for a typed failure rather than reading it as success.gap_count), read from the stored proposal rather than from the session previews — injected proposals live in the action store, so the earlier lookup missed the plan. The steward team-plan scenario confirms its two-lane plan (one ready, one unstaffed) and now asserts the partial sentence instead of the generic one.Validation
smoke:team-plan-proposal: asserts the classification, the counts, the partial sentence, the already-present sentence, the unchanged fallback for a receipt without a team-plan outcome, and that a typed failure is not read as applied.team-planscenario: passes with the new assertion after confirming the plan (it failed before the fixture fix, which is how the wrong lookup was found).build:chat(typecheck + bundle rebuild) andsmoke:personal-workspace-packaged(all six scenarios) pass;loopx/web/chatis rebuilt and current, which the Frontstage Pages job also re-checks.loopx canary premerge --from-git-diff: ok, 0 failures (direct checks, 7 catalog canaries, 8 risk-profile smokes, public boundary).Residual
Boundary
Frontend presentation plus its packaged bundle and the browser fixture (
apps/presentation/dashboard/**,examples/personal-workspace-browser/**, generatedloopx/web/chat). Proposed for review; not self-merged and no admin bypass.