diff --git a/docs/tasks/README.md b/docs/tasks/README.md
index 43998b0d..866519e4 100644
--- a/docs/tasks/README.md
+++ b/docs/tasks/README.md
@@ -252,6 +252,7 @@
| `TASK-api-server-backdoor-close-20260916.md` | — | **取代 decomposition 单的阶段 1** · **第二版(2026-09-16 改写)**:第一版的「8 方法 / 314 行」**错了一个数量级**——闭包只跟了 `self._x()`,漏掉 `_compute_consultation_workflow` 那 6 行委托转给模块级 `execute_consultation_workflow(self, ...)` 的一跳。执行方重算实测 **115 方法 / 4,104 行(占全类 51% 方法),但碰 HTTP 上下文的仍是 0 个**。产品在 A/B/C 中**选定 B(mixin 抽取)**:方法体原样搬进 `ConsultationComputeMixin`,`JyotishAPIHandler(BaseHTTPRequestHandler, Mixin)` 靠 MRO 解析,离线调用方直接实例化 mixin——四个伪造点全关。**产品同时授权:§4 那条「不得搬 ≥150 行业务方法」按本意解释(本意是防逐行改写的 C 案),mixin 放行。** 必须先过 **spike 闸门**:搬完后 `test_api_server_security.py` 一字不改直接跑,绿则继续、红则退回 A(只搬 7 方法/300 行、关 2 个伪造点)。真风险是循环 import——闭包引用同文件 44 个模块级函数 + 21 个常量。`__new__` 归零只针对 scripts 侧 4→0(tests 侧 29 处含不许改的 security 测试,保持不增长)。阶段 3 仍不做。不占 BUG 号 | **已执行(spike 红 → 退回 A)** | spike 实测:整体 mixin 化后 `test_api_server_security.py` **1 failed / 128 passed**——`test_chart_async_job_executes_in_background` 挂在 `monkeypatch.setattr(jyotish_api_server, '_write_async_job_record', ...)`:调用方法随 mixin 搬走后从新模块 globals 解析,补丁落在旧模块上不生效(已证明把同一 fake 打到 mixin 模块即恢复原行为,非搬坏)。循环 import 不是障碍(移动集不引用 `JyotishAPIHandler`)。按 §6.3 退回 A:`scripts/offline_compute_mixins.py` 收 7 方法 / 300 行 + 3 助手 + `BadRequest`,关掉 `consultation_workflow_service:27` 与 `local_accuracy_report:141` 两处伪造,scripts 侧 `__new__` **4 → 2**;类方法 225 → 218、行数 11,291 → 10,924;security 测试一字未改 **129 passed**。剩余 2 处伪造都在咨询工作流链上,需另案解决 monkeypatch 落点问题 |
| `RECONCILE-20260916.md` | — | **对账清单(不是任务书)**:状态板与现实脱节(09-15~16 那 10 份早已合入却仍写「待领取/待验收」,本轮已修正),所以另外 7 份 09-10~14 的单**不能拿 README 当证据**——它们没有 `PROGRESS-*.md`,但引用的 BUG 号都是 `resolved`。要么被别的单顺带修了没留记录,要么压根没做。每行回一个「做了/没做」即可。另附三条我已查到确定没做的证据、两条状态未闭环的、以及 BLK-001 仍红(2026-09-16 在 `4f643aa0` 复跑确认) | 待产品负责人 / 执行方回填 | — |
| `TASK-rectification-precision-gate-guided-collect-20260916.md` | `PROGRESS-rectification-precision-gate-guided-collect-20260916.md` | **出卡加精度门槛 + 引导式补经历**:真机 30 分钟窗给 5 件事 / 3 领域就出卡,区间 20 分钟、5 候选并列 26/26/20%,卡下只有自由文本邀请。产品拍板 D1 出卡门槛「宽度 ≤10 分钟且前两名差 >3 个百分点且不并列」;D2 用户说没有了仍按现行规则出卡(保留永远给结果);D3 门槛未达改为系统点名逐题问 + 类型芯片 + 年/月选择器录入,删自由文本邀请;D4 跳过的线换问法再问一次(拒绝不重问,改 BUG-687 口径);D5 时间点题答「没发生」不关领域、存在性题问整个领域、七条线一张表不许按领域写死。引擎新增 `guided_collect_windows`(边界不设 45 天闸门、含小运),不改打分与 lead。Skill 10.0.27。附 T6 两条核实:B/C/D 回执文案对调、性格题后范围回弹 8 分钟。BUG-740~743 | **已验收:未通过(带修复单)** | `cfb41daf`;tsc/lint/Static/gzip(+0.44%) 通过,D1~D5 单测通过;但 `npm test` 新增 16 红(校正既有断言未跟上)+ memoization golden 因 receipt 新键红(门禁级,staging 仍部署 317e9f18);引导窗口领域轮询分配、回放未按真值注入、录入卡走模型轮 → `TASK-rectification-precision-gate-guided-collect-fix-20260916.md` |
+| `TASK-sidebar-unify-20260916.md` | — | **侧栏统一成一个组件 + 次级页共享外壳 + 跳转不整页刷新(纯前端)**:`/` 用 `AppSidebar`、四个次级页用另写的 `AppNavRail`,两套标记结构共用一份 CSS,会话行少 `.session-main` 一层(无内边距 / 44px 高 / 当前项标记、留 44px 空列)、页脚 56px 菜单 vs 44px 文字;首页进次级页是 `window.location.assign` 整页刷新,次级页各自在组件里挂 `SecondaryShell`、无共享 layout 无缓存,每次跳转重拉 `/api/sessions` + `/api/account`;折叠状态不持久。产品拍板 D1 只留一个 `AppSidebar`(操作回调可选 = 只读模式,D9「不带写操作」维持)、D2 路由组 `app/(secondary)/` layout 承载外壳、D3 改 ``、D4 模块级 60 s 缓存 + 首页写操作失效、D5 折叠状态存 cookie、D6 回首页慢的启动链另开一单。实测:接口 TTFB 0.63~0.90 s,回 `/` 启动链串行 ≥4 次往返 + 揭幕闸门 4 s。BUG-744~746 | 待领取 | — |
| `TASK-rectification-precision-gate-guided-collect-fix-20260916.md` | — | **验收修复单**:F1 `decision_receipt` 新增 `guided_collect_windows` 让 `test_rectification_engine_memoization` golden 红(在 CORE_PYTEST_TARGETS,staging 未部署 cfb41daf);F2 16 条既有校正断言红未按三栏改;F3 `mayDeliverOnPrecision` 缺省即放行、短路 BUG-654 且 `guidedCollectExhausted` 不含 refresh;F4 无领域轨道窗口轮询贴领域、一窗只问一领域;F5 离线回放注入 month_lo 非真值方向、0/20 不作数;F6 录入卡合成「或」列表句走模型轮;F7 page.tsx +1、手造 fixture。D6 门槛达标即出卡待产品确认。BUG 段 744 起 | 待领取 | 与主单同文件;开工基线 3391 / 47 |
## 命名与归档
diff --git a/docs/tasks/TASK-sidebar-unify-20260916.md b/docs/tasks/TASK-sidebar-unify-20260916.md
new file mode 100644
index 00000000..b352615d
--- /dev/null
+++ b/docs/tasks/TASK-sidebar-unify-20260916.md
@@ -0,0 +1,138 @@
+# TASK · 侧栏统一成一个组件 + 次级页共享外壳 + 跳转不再整页刷新(2026-09-16)
+
+分支:`codex/sidebar-unify-20260916`。纯前端,不动数据库、不动 Skill、不动 Python。
+
+## 基线 commit
+
+`origin/staging` = `cfb41daf`(feat(rectification): 出卡加精度门槛,补经历改成系统点名)。本单涉及的文件自 `317e9f18` 起没有变化,进度记录里按开工时的 `git rev-parse origin/staging` 重新写一次。
+
+## 事故实证(行号按符号定位,会漂)
+
+产品负责人在 staging 真机上的三条观察,逐条对到代码:
+
+1. **首页与三个次级页的侧栏长得不一样。**
+ - `/` 用 `AppSidebar`(`frontend/src/components/app-sidebar.tsx`),会话行是 `SidebarSessionRow`(`sidebar-session-row.tsx`:`.session-row > .session-main` + 菜单按钮 + 副标题),页脚是 `.profile-trigger`(56px、带 chevron、打开账户菜单)。
+ - `/chart`、`/ephemeris`、`/reports`、`/reports/[reportId]` 用 `AppNavRail`(`app-nav-rail.tsx`),会话行在 `renderRow` 里是 `` 直接套 `.session-title`,**没有 `.session-main` 这一层**:丢了 `padding: var(--space-2)`、`min-height: 44px`、当前项 2px 左标记(`globals.css` 的 `.session-main[data-active="true"]::before`),而 `.session-row` 的 `grid-template-columns: minmax(0, 1fr) 44px` 还给不存在的菜单按钮留着一列 44px 空位。页脚是 `.nav-rail-identity`(44px、flex、文字「N 点」)。
+ - 两个组件共用一份 `globals.css`,所以「四个导航项」本身一致(`.new-chat` 强调色、`.report-nav-button` 文字色,`DESIGN.md` §Scarce 第 1 条写明这是设计);分叉全在会话行与页脚。
+ - `SidebarProvider`(`ui/sidebar.tsx`,`defaultOpen = true`)不存 cookie / localStorage,折叠状态只活在内存里。
+2. **每次进次级页都重新读会话列表。**
+ - `app-sidebar.tsx` 的 `leaveChat()` 用 `window.location.assign(path)`:首页 → 次级页是**整页刷新**,React 树与内存全部清零。
+ - 三个次级页各自在组件内部渲染 `SecondaryShell`(`chart-page-view.tsx`、`ephemeris-page.tsx`、`personal-report-center.tsx`、`personal-report-page.tsx` 共 8 处调用),每个 `SecondaryShell` 挂一份 `SidebarProvider + AppNavRail`。`app/` 下没有把这四个路由包起来的 layout,所以次级页之间即便是客户端跳转,外壳也整个重挂,`useNavRail()`(`hooks/use-nav-rail.ts`)的 effect 重新 `GET /api/sessions?limit=40` + `GET /api/account`,且无任何缓存。
+3. **从次级页回「新建对话」等很久。**
+ - `AppNavRail` 里「新建对话」是 ``,客户端跳转本身没问题;慢在 `Home()` 的启动链每次都从零跑:`page.tsx` 的 `loadCloudData()` 先 `Promise.all(账户, 模型目录, 会话列表)` → `fetchActiveConsultationStatus` → `resolveLookupBootstrap` → 当前会话 `fetchSessionDetail` → `setBootstrapPhase("prepare")` → 「准备」阶段并行拉 `/api/rectification/cases/entry-summary` 与 `POST /api/daily-starlanguage` → `bootstrapRevealDelayMs`(`lib/home-bootstrap.ts`,上限 `BOOTSTRAP_PREPARE_TIMEOUT_MS = 4000`)→ `setHydrated(true)` 揭幕。串行至少 4 次往返。
+ - 2026-09-16 从验收机测 staging:接口单次 TTFB 0.63~0.90 s;`/` 首屏脚本 23 个 gzip 604 KB,`/chart` 17 个 gzip 283 KB,从次级页回 `/` 要新下载 8 个共 gzip 339 KB(`` 默认会预取,但未在浏览器里证实)。
+
+## 根因
+
+- 样式分叉:`TASK-cend-surfaces-claude-alignment-20260916` D9 决定次级页侧栏只读,T4.1 为了不把 `Home()` 的会话管理层上提而**另写了第二个组件**。D9 只说「不带操作」,没说「长得不一样」;分叉是实现手段的副作用,不是设计。
+- 重复读取:没有共享 layout、没有缓存、首页出去是整页刷新,三件事叠加。
+- 回首页慢:启动链串行且不复用侧栏已经拿到的账户与会话列表;「准备」阶段两个请求每次现拉。
+
+## 决策记录(产品负责人 2026-09-16 拍板)
+
+- **D1|侧栏只保留一个组件。** `AppSidebar` 收编 `AppNavRail`:会话操作回调与账户菜单改成**可选**,不传就渲染成只读(行是纯链接、页脚是去 `/` 的链接)。`app-nav-rail.tsx` 删除。D9「次级页不带写操作」**维持**,只是实现方式从「第二个组件」改成「同一组件的只读模式」。
+- **D2|四个次级路由共享一个外壳实例。** 用路由组 `app/(secondary)/` 的 layout 挂 `SidebarProvider + AppSidebar`,页面只保留 46px 顶栏与正文。路由之间跳转不重挂侧栏、不重拉列表。
+- **D3|首页进次级页改客户端跳转。** `leaveChat()` 的 `window.location.assign` 换成 ``(保留 `persistLoginSessionReturn()`)。`/login` 维持硬跳转。
+- **D4|会话列表与账户在内存里缓存,先用旧数据再后台刷新。** 缓存是模块级、按账户 id 键、同一标签页内有效、过期时间 60 s;`/` 上的会话写操作(新建 / 重命名 / 删除 / 归档 / 收藏)成功后写穿缓存或使其失效。不落 localStorage。
+- **D5|折叠状态跨页记住。** 存 cookie(shadcn 的 `sidebar_state` 惯例)或 localStorage 二选一,进度记录写明选哪个;移动端抽屉状态不记。
+- **D6|本单不动 `Home()` 的启动链。** 回首页的等待另开一单(`TASK-home-bootstrap-reuse`),前提是本单先落地让内存缓存能活过跳转。本单只要求「新建对话」链接保持 `` 且预取不被关掉。
+
+## 硬红线
+
+1. `./node_modules/.bin/tsc --noEmit` 0 错;`npm run lint` **0 error**。
+2. 测试总数不得低于开工时 `origin/staging` 的实测;改既有断言必须写「原值 / 新值 / 原因」三栏。`tests/sidebar-contract.test.ts`(43 条)与 `tests/sidebar-state.test.ts`(5 条)是侧栏 CSS 与状态合同,不得删条。
+3. `next build` 后 `/` 保持 `○ Static`;`/chart`、`/ephemeris` 的渲染标记改前改后不变;`/reports`、`/reports/[reportId]` 维持 `force-dynamic`。路由组不得改变任何 URL。四个路由的标记逐个列进进度记录。
+4. `/` 首屏 gzip ±2%(口径沿用 `PROGRESS-cend-ui-r1-20260916.md`:`.next/static` 下 css chunk gzip 合计,另附 `/` HTML 引用的 js chunk gzip 合计)。次级页体积改前改后列出。
+5. `frontend/src/app/page.tsx` 不得增长;`Home()` 的 `useState` / `useRef` 数不得增长(`tests/home-shell-growth-contract.test.ts`)。本单对 `page.tsx` 的改动只允许:传给 `AppSidebar` 的 props 变化、写操作后调用缓存失效。
+6. 次级页上的侧栏**不得**发起任何写操作接口(D9 维持)。只读模式下 `AppSidebar` 不得 import `page.tsx` 的任何 hook。
+7. 揭幕后不得出现 spinner / 骨架 /「正在加载」。缓存未命中时侧栏显示现有静态空态文案(`sidebar-empty`),不是骨架。
+8. 不改 `useConversationScrollAnchor`、`ChatComposer`;不动 BUG-698 的 `@supports (height: 1dvh)` 写法;不动报告盘面 grid。
+9. 改 UI 的同一提交内更新 `frontend/DESIGN.md`(「The rail is read-only」一节改写成「只读模式」,删掉 `.nav-rail-*` 的描述)。
+10. 不顺手升级依赖、不顺手修不在本单里的 warning。
+
+## 任务分解
+
+### T1 · `AppSidebar` 收编 `AppNavRail`(D1)
+
+- `AppSidebarProps` 拆成两组:`sessions / activeSessionId / account / pathname`(两边都要)与 `controls?: { sessionControls, onNewChat, onSelectSession, onAccountMenuOpenChange, accountTriggerRef, onOpenProfile … }`(只有 `/` 传)。`controls` 缺省时:
+ - 四个导航项:新建对话是 ``,三个页面项是 `` 并按 `pathname` 打 `data-active`(沿用 `report-nav-button` 现有选中态)。
+ - 会话行:仍渲染 `SidebarSessionRow` 的**同一套标记**(`.session-row > .session-main`),但 `.session-main` 是 ``,不渲染菜单按钮;`grid-template-columns` 在只读模式下不留 44px 空列(加 `data-readonly` 属性切换,不新造类名)。
+ - 页脚:同一 `.profile-trigger` 标记,渲染成 ``,不带 chevron、不带菜单。
+- 删除 `app-nav-rail.tsx`、`globals.css` 里 `.nav-rail-row` / `.nav-rail-identity` 段。
+- `use-nav-rail.ts` 改名为 `use-sidebar-data.ts`(或等价),加 D4 的缓存(见 T3)。
+
+**验收标准**
+- `grep -rn "AppNavRail\|nav-rail" frontend/src` 为 0。
+- `/` 与 `/chart` 的侧栏 DOM 结构除了「菜单按钮 / chevron / 账户菜单」三处之外逐节点相同;进度记录贴两页 `document.querySelector('[data-sidebar="sidebar"]').outerHTML` 的 diff(脱敏会话标题)。
+- 只读模式下 `document.querySelectorAll('[data-sidebar="sidebar"] button')` 只剩折叠触发器。
+- `sidebar-contract.test.ts` 全绿,不删条;新增断言:只读模式会话行有 `.session-main`、无 `.session-menu-trigger`、无 44px 空列。
+
+### T2 · 路由组 layout 承载外壳(D2)
+
+- 新建 `app/(secondary)/layout.tsx`(client component):`SidebarProvider + AppSidebar(只读) + SidebarInset`,`children` 是页面。
+- 把 `app/chart`、`app/ephemeris`、`app/reports` 移进 `app/(secondary)/`;`page.tsx` 内容与 `metadata`、`dynamic` 声明原样保留。
+- `SecondaryShell` 拆成只剩顶栏的 `SecondaryHeader({ title, note, actions })`,8 处调用改名;`SidebarTrigger` 仍在顶栏里(provider 由 layout 提供)。
+- `../site-styles` 的 import 移到 layout 一处。
+
+**验收标准**
+- 四个 URL 不变;`next build` 路由表四个标记与开工时一致(红线 3)。
+- 在 `/chart` → `/ephemeris` → `/reports` 之间跳转,Network 面板里**没有** `/api/sessions`、`/api/account` 请求(缓存未过期时);这条做不了自动化就写进 `docs/testing/` 清单,并用 `tests/` 里的渲染测试证明 layout 只挂一次(记录 `useSidebarData` 的 effect 调用次数)。
+- `/reports/[reportId]` 的打印样式(`media="print"` 隐藏侧栏,见 `DESIGN.md` 「Print inside the shell」)在 layout 化之后仍生效,进度记录写明验证方式。
+
+### T3 · 列表与账户缓存 + 首页写穿(D4)
+
+- 模块级 `Map`,`useSidebarData()` 先同步读缓存渲染,再后台刷新;60 s 内不重拉。
+- `/` 上 `useSessionManagement` 的新建 / 重命名 / 删除 / 归档 / 收藏成功回调里调用 `invalidateSidebarCache()`(或直接写穿)。这是本单唯一允许碰 `page.tsx` / `use-session-management.ts` 的地方。
+- 401 时清缓存并按现有 `signedOut` 分支降级。
+
+**验收标准**
+- 单测:命中缓存时不发 fetch;过期后发一次;写操作后下一次进次级页拿到的是新标题 / 新顺序(用 `vi.useFakeTimers` 推时间)。
+- 在 `/` 重命名一个会话 → 进 `/chart`,侧栏立刻显示新标题(真机清单)。
+
+### T4 · 首页进次级页改客户端跳转(D3)
+
+- `app-sidebar.tsx` 的 `leaveChat()` 删除,三个页面项改 ``,`onClick` 里保留 `persistLoginSessionReturn()` 与移动端关抽屉。
+- `onOpenReports` 这个已经没人用的 prop(当前解构成 `_onOpenReports`)一起删。
+
+**验收标准**
+- 点星盘 / 星历 / 我的报告不再整页刷新(Network 里没有 document 请求;真机清单)。
+- `/login` 仍是硬跳转(`tests/chat-session-url.test.ts` 补一条 `persistLoginSessionReturn` 在跳转前被调用的断言)。
+
+### T5 · 折叠状态持久化(D5)
+
+- `SidebarProvider` 读写 cookie `sidebar_state`(或 localStorage,二选一写明),`defaultOpen` 由 layout / `page.tsx` 从存储读;SSR 首帧与客户端一致,不得闪一下。
+- `tests/sidebar-state.test.ts` 补两条:收起后重挂仍收起;移动端不受影响。
+
+**验收标准**
+- `/` 收起侧栏 → 进 `/chart`,仍收起;反向同理。
+
+### T6 · 文档与清单
+
+- `frontend/DESIGN.md`:侧栏一节改成「一个组件、两种模式」,写明只读模式少哪三样。
+- `docs/testing/sidebar-unify-20260916.md`:真机清单(跳转无 document 请求、跨页无 `/api/sessions`、重命名后立刻可见、折叠状态跨页、打印隐藏侧栏、移动端抽屉)。
+- `docs/BUG_HISTORY.md`:BUG-744 侧栏两套标记结构导致次级页会话行 / 页脚样式分叉;BUG-745 首页进次级页整页刷新 + 次级页外壳逐页重挂导致每次跳转重拉会话列表;BUG-746 折叠状态不持久。三条都要有针对性回归测试才能写 `resolved`。
+- `CHANGELOG.md` 一条。
+
+## 让步顺序
+
+1. T5 折叠持久化如果 SSR 首帧对不齐(闪一下),可以退到只在客户端 `useEffect` 后同步,进度记录写明并保留 `docs/testing/` 条目。
+2. T3 的写穿如果撞 `page.tsx` 增长门禁,退到「写操作后只失效、不写穿」。
+3. T2 若 `/reports/[reportId]` 的打印样式在 layout 化后失效且一时修不好,先让 `[reportId]` 留在路由组外、保留一份自己的外壳,其余三页照做,并在进度记录标 `blocked`。
+4. 不得让步的:T1(一个组件)、T4(不整页刷新)、红线 5 / 6。
+
+## 开工前置命令
+
+```bash
+git -C /workspace/Jyotisha status -sb | head -1
+git -C /workspace/Jyotisha fetch origin --prune
+git -C /workspace/Jyotisha worktree add -b codex/sidebar-unify-20260916 .worktrees/sidebar-unify-20260916 origin/staging
+cd /workspace/Jyotisha/.worktrees/sidebar-unify-20260916/frontend && npm ci
+./node_modules/.bin/tsc --noEmit && npm run lint && npm test 2>&1 | tail -5 # 记基线:测试总数、fail 清单
+npm run build 2>&1 | grep -E "○|ƒ|λ" | grep -E "^\s*[○ƒλ] /(chart|ephemeris|reports|$)" # 记四个路由标记
+```
+
+同一时间若有别的单在改 `frontend/src/app/page.tsx` 或 `globals.css`(当前状态板上:`TASK-home-state-lowering-batch2-20260916` 待领取),本单**后于**它合入;本单对这两个文件的改动都很小,rebase 即可。
+
+## BUG 编号起点
+
+开工时核对 `docs/BUG_HISTORY.md` 最大号;本单写作时最大号为 **BUG-743**,本单从 **BUG-744** 起。