From be7bf9d4267871dda900c7ec4d5de794997c5cb5 Mon Sep 17 00:00:00 2001 From: creatixchu Date: Fri, 31 Jul 2026 17:57:13 +0800 Subject: [PATCH] doc(agent-notes): record the blank-session settling exemption --- ...isible-while-blank-session-opens.i18n.yaml | 6 ++++ ...-hero-visible-while-blank-session-opens.md | 33 +++++++++++++++++++ ...ro-visible-while-blank-session-opens.zh.md | 33 +++++++++++++++++++ 3 files changed, 72 insertions(+) create mode 100644 .agents/notes/implemented/bug-fix/2026-07-31-hero-visible-while-blank-session-opens.i18n.yaml create mode 100644 .agents/notes/implemented/bug-fix/2026-07-31-hero-visible-while-blank-session-opens.md create mode 100644 .agents/notes/implemented/bug-fix/2026-07-31-hero-visible-while-blank-session-opens.zh.md diff --git a/.agents/notes/implemented/bug-fix/2026-07-31-hero-visible-while-blank-session-opens.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-07-31-hero-visible-while-blank-session-opens.i18n.yaml new file mode 100644 index 0000000000..1221753cb7 --- /dev/null +++ b/.agents/notes/implemented/bug-fix/2026-07-31-hero-visible-while-blank-session-opens.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# side as of the last confirmed-consistent state. Both languages carry equal authority; +# after editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-07-31-hero-visible-while-blank-session-opens.md +2026-07-31-hero-visible-while-blank-session-opens.md: 10339c5e6540daa84e691c01edf335aaa2fdf8aa +2026-07-31-hero-visible-while-blank-session-opens.zh.md: 571962804489112dcccd66d18317835bab45e30e diff --git a/.agents/notes/implemented/bug-fix/2026-07-31-hero-visible-while-blank-session-opens.md b/.agents/notes/implemented/bug-fix/2026-07-31-hero-visible-while-blank-session-opens.md new file mode 100644 index 0000000000..10339c5e65 --- /dev/null +++ b/.agents/notes/implemented/bug-fix/2026-07-31-hero-visible-while-blank-session-opens.md @@ -0,0 +1,33 @@ +# Agent Note: Hero stays visible while a blank session opens + +Status: implemented + +English | [中文](2026-07-31-hero-visible-while-blank-session-opens.zh.md) + +## Problem + +The conversation root has a `settling` phase for a session that is still opening while its composer reads `blank`: the hero-versus-docked outcome is unknowable until history arrives, so the composer seat is hidden (`visibility:hidden`) rather than flashing the centered hero and snapping to the docked bar. Startup auto-selection turned that guard into the defect it was meant to prevent. From the no-workspace hero, `WorkspacesService.startInitialSelection` connects the most recent workspace and opens its blank session; `openState` flips to `loading` the moment `open()` lands, so the center column went blank for the whole history round-trip and then repainted, which reads as a full-page refresh on every launch. + +## Decision + +`ConversationRoot` reads the session list summary's `blank` flag alongside the conversation snapshot and exempts summary-proven blank sessions from settling: `settling` additionally requires `summaryBlank !== true`, and `hero` accepts a blank composer while `openState === 'loading'` when the summary proves the session blank. A session the list already reports as blank can only land on the hero, so hiding buys nothing and costs the visible flash. When the summary row is absent — a session not yet listed — `summaryBlank` is `undefined` and the conservative settling hide is unchanged. + +The summary flag and the snapshot's own `blank` are distinct sources: the snapshot describes the session being opened, the summary is the list row that already exists before the open resolves. Only the latter is available early enough to decide the phase. + +## Alternatives considered + +**Drop the settling phase entirely.** Rejected because it still earns its keep for a session with no summary row: without a prior claim about emptiness, hero-versus-docked is genuinely unknowable and the flash it prevents is the worse one. + +**Delay the `loading` flip until history returns.** Rejected because `openState` is authoritative about the open operation; deferring it to suppress a presentation artifact would misreport the data state to every other consumer. + +**Cross-fade or otherwise animate the settling hide.** Rejected because the column has nothing to show during the round-trip either way — the fix is to not hide content whose outcome is already known, not to decorate the hiding. + +## Deferred + +The no-session→session tree relocation in `ConversationRoot` (the hero/composer subtree moves into the `conversation.session` outlet) still rebuilds the composer DOM on the same transition; removing it means moving `conversation.session` to `session-maybe` scope, a slot-contract change that needs its own proposal. + +Object-layer reference churn found while diagnosing this — no-op projections minting fresh snapshots, the create path projecting twice, `select()` using `notifyNow` from async continuations — is real but independent of the visible flash. + +## Consequences + +Startup auto-selection renders the hero immediately and keeps the composer seat and header visible through the history round-trip, so launching into a recent workspace no longer looks like a page reload. Sessions with no list summary keep the previous settling behavior, so the guard still covers the case it was written for. Skeleton tests pin both branches: an unlisted blank session settles, a summary-proven blank session opening under `loading` renders hero chrome and a live textarea. diff --git a/.agents/notes/implemented/bug-fix/2026-07-31-hero-visible-while-blank-session-opens.zh.md b/.agents/notes/implemented/bug-fix/2026-07-31-hero-visible-while-blank-session-opens.zh.md new file mode 100644 index 0000000000..5719628044 --- /dev/null +++ b/.agents/notes/implemented/bug-fix/2026-07-31-hero-visible-while-blank-session-opens.zh.md @@ -0,0 +1,33 @@ +# Agent Note: 空白会话打开期间保持 hero 可见 + +Status: implemented + +[English](2026-07-31-hero-visible-while-blank-session-opens.md) | 中文 + +## 问题 + +会话根节点为"正在打开且 composer 处于 `blank`"的会话保留了一个 `settling` 阶段:在历史记录返回之前,hero 与 docked 的归属不可知,因此宁可隐藏 composer 座位(`visibility:hidden`),也不要先闪出居中的 hero 再跳到底部输入条。启动时的自动选择把这道防护变成了它本要防止的缺陷。从无工作区的 hero 进入时,`WorkspacesService.startInitialSelection` 会连接最近的工作区并打开其空白会话;`open()` 一落地 `openState` 立即翻为 `loading`,中间栏因此在整个历史往返期间保持空白,随后重绘一次——每次启动看起来都像整页刷新。 + +## 决策 + +`ConversationRoot` 在读取会话快照的同时读取会话列表摘要的 `blank` 标志,并让"摘要已证明为空白"的会话豁免 settling:`settling` 额外要求 `summaryBlank !== true`,而 `hero` 在摘要证明会话为空白时,接受 `openState === 'loading'` 期间处于 blank 的 composer。列表已报告为空白的会话只可能落到 hero,因此隐藏毫无收益,只换来一次可见闪烁。当摘要行缺失时——会话尚未出现在列表中——`summaryBlank` 为 `undefined`,保守的 settling 隐藏行为保持不变。 + +摘要标志与快照自身的 `blank` 是两个不同来源:快照描述正在打开的这个会话,摘要则是在打开操作完成之前就已存在的列表行。只有后者足够早,可用于决定阶段。 + +## 备选方案 + +**彻底移除 settling 阶段。** 否决,因为对没有摘要行的会话它仍有价值:在缺少任何关于"是否为空"的先验断言时,hero 与 docked 的归属确实不可知,而它所防止的那种闪烁更糟糕。 + +**推迟 `loading` 的翻转,直到历史返回。** 否决,因为 `openState` 是打开操作的权威状态;为了压制一个呈现层瑕疵而推迟它,会向其他所有消费者误报数据状态。 + +**为 settling 的隐藏加交叉淡入或其他动画。** 否决,因为无论如何该栏在往返期间都没有内容可展示——正确的修复是不隐藏结局已知的内容,而不是把隐藏装饰得好看些。 + +## 推迟事项 + +`ConversationRoot` 中"无会话→有会话"的树位置迁移(hero/composer 子树移入 `conversation.session` 出口)仍会在同一次转换中重建 composer 的 DOM;消除它意味着把 `conversation.session` 移到 `session-maybe` 作用域,这是一次插槽契约变更,需要单独立项。 + +诊断期间发现的对象层引用抖动——空操作投影铸造出新的快照、创建路径重复投影一次、`select()` 在异步续体中使用 `notifyNow`——确实存在,但与这次可见闪烁相互独立。 + +## 影响 + +启动自动选择会立即渲染 hero,并在整个历史往返期间保持 composer 座位与 header 可见,因此启动进入最近工作区不再像页面重载。没有列表摘要的会话保持原有的 settling 行为,这道防护仍覆盖它当初针对的场景。骨架测试固定了两条分支:未列出的空白会话进入 settling;摘要已证明为空白的会话在 `loading` 期间渲染 hero 外壳与可用的文本框。