Create a fresh local consultation for explicit new-chat navigation and keep reserved recovery from taking over its landing. Add regression tests and record validation gaps for remote review. Co-Authored-By: Claude Code <noreply@anthropic.com>
11 KiB
TASK · 次级页「新建对话」回首页落到最近一次对话(2026-09-23)
基线
origin/staging = 6b53d9bb(docs: record BUG-1011 green gate and staging deployment)。- staging 线上
deployment.gitCommit = 018b2b48;deploy/is-docs-only-range.sh 018b2b48… 6b53d9bb…退出 0,线上即基线代码。 - 执行分支:
codex/secondary-new-chat-intent-20260923,worktree.worktrees/secondary-new-chat-intent-20260923。 - 与本单同期待领取的前端单:
TASK-rectification-session-title-result-20260922.md(改标题算式,不碰本单四个文件);TASK-console-noise-20260916.md(app-sidebar.tsx不在其范围)。本单无串行依赖。
事故实证
产品在真实环境复现:在 /chart、/ephemeris、/reports 任一页点侧栏「新建对话」,回到 / 后打开的是最近一次对话,不是新会话。每次都如此,不是偶发。
代码链(全部按 origin/staging 6b53d9bb):
frontend/src/components/app-sidebar.tsx,AppSidebar的<SidebarMenuItem>第一项:controls存在时渲染<SidebarMenuButton onClick={handleNewChat}>(走onNewChat→startNewChat());controls缺省时渲染<SidebarMenuLink className="new-chat" href="/">。frontend/src/app/(app)/layout.tsxAppShell:controls={registration?.controls}。只有/的Home()调registerShellControls(page.tsx中registerShellControls两处),三个次级页从不注册,所以次级页的「新建对话」= 纯链接/,URL 上没有任何"新建"意图。frontend/src/lib/chat-session-url.tsresolveBootstrapSessionSelection:search无c、storedReturnId为空 →{ sessionId: defaultSessionId, urlAction: "none" }。frontend/src/app/(app)/page.tsxbootstrap 内resolveLookupBootstrap({ …, defaultSessionId: nextSessions[0].id }):默认就是列表第一条=最近更新的会话。frontend/src/lib/home-bootstrap.tsresolveStarterHomeLandingSessionId/starterHomeLandingNeedsConsultation:urlAction === "none"时只有"最近一条是生时校正"才改落空咨询或新建;最近一条是普通咨询就直接打开它。
同一入口在 BUG-745 已被记过一次("从次级页点「新建对话」回首页要等很久"),当时修的是慢(整页刷新→客户端跳转),"回到哪"没有动。BUG-989 又把新建会话定为"第一问之前不落库、不写 ?c=",所以也不存在"先建好再带 id 回来"的路径。
根因
次级页的「新建对话」只是一条回首页链接,而首页无参数打开的落点规则是"最近一次对话"。两条规则各自都对,缺的是**"新建"这个意图在 URL 里没有表达方式**。不是启动链算错,不是缓存串会话。
决策记录
- D1 产品 2026-09-23 确认:次级页「新建对话」必须开一个新会话,与首页按钮行为一致。
- D2 表达方式定为 URL 意图参数:次级页链接指向
/?new=1;首页启动看到new=1就本地建一个空咨询会话并落到它,随后用replaceState把new(连同c)从地址栏抹掉。 - D3 沿用 BUG-989:新会话只在本地建,不
POST /api/sessions、不写?c=,第一问时由既有send()→persistSession(…, "create")落库。isUnsavedEmptyConsultation()已覆盖启动期用createSession()建的会话(page.tsx中starterHomeLandingNeedsConsultation分支就是先例),不需要新的落库路径。 - D4 优先级:
new胜过c与登录返回存根(storedReturnId)。带new时忽略两者,并清掉存根(它只服务于登录后回到原会话,用户既然点了新建,存根已过时)。 - D5 不改的:侧栏页脚
.profile-trigger与品牌行两个href="/"仍是"回首页",落点规则不变;首页自身按钮不变;BUG-989的三条防复发不变。 - D6 不在本单:刷新
/(无参数)仍落最近对话——这是既有设计,不是本单事故。 - D7(执行中追加授权,2026-09-23) 独立验收发现:存在旧
reserved咨询时,restoreConsultationRecovery()会把新建落点改回旧咨询。产品明确选择「一并修复」,授权首页在恢复后增加new-chat落点保护并清除此次恢复提示,保留旧任务的 pending/recovering 能力,不取消任务、不改恢复 hook。本授权有限扩展红线 5 / T2「首页只加一行」:允许额外一个四行保护块(首页合计 +5 行、state/ref 不增),必须补动态执行真实恢复函数与首页装配段的回归测试;无new的恢复行为不变。 - 执行勘误:当前品牌行是
div+span/strong,没有 D5 所述链接;保持现状,不补造入口。首页无参数还受收藏顺序及登录存根影响,本单不更改这些规则。URL 清理优先满足 T2「激活之前」时序,不拘泥于位于激活之后的replace-clear邻行。
硬红线
/必须保持○ Static:读取new只能在客户端(与preview参数、parseSessionUrlQuery同一方式),不得引入useSearchParams(chat-session-url.test.ts的 "home stays a client-read query on a static route" 已锁)。- 第一问之前不得
POST /api/sessions;未落库会话不得写入?c=;列表响应不得带draft(BUG-989 防复发三条)。 - 解析与生成
new的逻辑只在frontend/src/lib/chat-session-url.ts一处;app-sidebar.tsx与page.tsx只调用,不得各写一份字符串拼接。 - 产品里只有一个侧栏组件、只读模式只少"菜单按钮、chevron、账户菜单"三样(BUG-744);不得为次级页再加第二个按钮或第二套类名。
- 不得改
frontend/src/app/(app)/page.tsx之外的会话状态流;page.tsx不得增行超过本单需要的一个分支(§6 增长冻结)。 - 既有断言改动逐条写"原值 / 新值 / 原因",测试总数不得低于开工时
origin/staging实测。 - 不顺手修 lint warning、不升级依赖、不动迁移、不改 workflow。
任务分解
T1 · URL 意图(chat-session-url.ts)
- 新增导出:
NEW_CHAT_QUERY_KEY = "new"、newChatHref(): string(返回/?new=1)、parseNewChatIntent(search): boolean。 sessionHref()生成任何地址时都删掉new(这样首问后writeSessionUrl(id, "push")与启动期writeSessionUrl(null, "replace")都能顺带抹掉它)。resolveBootstrapSessionSelection新增urlAction: "new-chat":search含new时直接返回{ sessionId: defaultSessionId, urlAction: "new-chat", missing: false, clearStoredReturn: true },不看c、不看storedReturnId。BootstrapSessionSelection.urlAction联合类型同步加"new-chat"。
验收:frontend/tests/chat-session-url.test.ts 新增 ≥4 条:parseNewChatIntent 对 ?new=1 / ?new= / 无参 的判定;new 同时带 c 与存根时胜出且 clearStoredReturn 为真;sessionHref("?new=1&x=1", null) 结果不含 new 但保留 x;sessionHref("?new=1", id) 结果只有 c。"session href keeps sibling query keys and can drop a dead c" 若改断言,写三栏说明。
T2 · 首页落点(home-bootstrap.ts + page.tsx)
starterHomeLandingNeedsConsultation(…, "new-chat")恒返回true;resolveStarterHomeLandingSessionId对"new-chat"返回传入的selectedId(随后会被新建会话覆盖)。LandingUrlAction类型同步。page.tsxbootstrap 内复用现有starterHomeLandingNeedsConsultation分支建会话(不要再写一段createSession拼装),urlAction === "new-chat"时在setActiveSessionId之前调writeSessionUrl(null, "replace")抹掉new(该分支在bootstrapSelection.urlAction === "replace-clear"那一行旁边加,最多 1 行)。- 这条路径不得触发
SESSION_MISSING_NOTICE(missing: false)。
验收:frontend/tests/home-bootstrap-reveal.test.ts(或同目录新文件,纯函数测试)新增 ≥2 条:最近一条是普通咨询、urlAction="new-chat" 时 starterHomeLandingNeedsConsultation 为真;urlAction="none" 时行为与基线相同(回归锁)。
T3 · 次级页链接(app-sidebar.tsx)
- 只读模式那条
<SidebarMenuLink className="new-chat" href="/">改为href={newChatHref()}。页脚.profile-trigger与品牌行AppLink href="/"不改。
验收:frontend/tests/sidebar-contract.test.ts 在只读模式那条测试里加断言:.new-chat 只读链接的 href 引用 newChatHref(不是字面量 /),页脚仍是 href="/";grep -n '"/?new=1"' frontend/src 只命中 chat-session-url.ts 一处。
T4 · 记录
docs/BUG_HISTORY.md新增 BUG-1015(状态resolved,现象、触发条件、根因、修复、验证、防复发;关联 BUG-745、BUG-744、BUG-989;复发自:无)。CHANGELOG.md一条:星盘 / 星历 / 我的报告页的「新建对话」现在真的新建。Skill 版本不 bump。docs/tasks/PROGRESS-secondary-new-chat-intent-20260923.md:测试数字(开工基线 / 完成后)、既有断言三栏表、环境缺口。docs/testing/secondary-new-chat-20260923.md真机清单:①/chart点「新建对话」→ 首页是空会话、地址栏是/(无?new、无?c)、侧栏列表没有多出一行;② 在该空会话发第一问 → 地址栏变/?c=<id>、列表顶部出现新会话;③ 在 ① 之后直接刷新 → 落最近对话(D6,预期行为,用来确认没有重复建会话);④/reports/<id>页同 ①;⑤ 移动端抽屉内同 ①,抽屉关闭。
让步顺序
- T2 若发现
resolveStarterHomeLandingSessionId加分支会让home-bootstrap.ts现有测试语义混乱,可把"new-chat"的判断留在starterHomeLandingNeedsConsultation一处,resolveStarterHomeLandingSessionId原样透传——写进进度记录。 sessionHref()删new若影响frontend/tests/chat-session-url.test.ts的既有 sibling 断言,允许改该断言(三栏说明),不允许改成"保留new"。- 不得让步:D3(不落库、不写
?c=)、红线 1(Static)、红线 3(一处实现)。
开工前置命令
git fetch origin --prune
git worktree add -b codex/secondary-new-chat-intent-20260923 .worktrees/secondary-new-chat-intent-20260923 origin/staging
cd .worktrees/secondary-new-chat-intent-20260923/frontend
./node_modules/.bin/tsc --noEmit
npm run lint
npm test 2>&1 | tail -5 # 记下开工时的总数与失败清单
完成后:tsc --noEmit 0 错;npm run lint 0 error;npm test 失败清单与开工基线逐条一致、总数 ≥ 基线 + 新增;next build 后 / 仍 ○ Static,首屏 gzip ±2%。纯前端改动,不要求 pre_work_check.py。
BUG 编号起点
开工时核对 docs/BUG_HISTORY.md 最大号(写单时为 BUG-1014),本单用 BUG-1015;若被他单占用则顺延并在进度记录里写明。