From 4d635807082dead7d593b29dc65b6451a3add378 Mon Sep 17 00:00:00 2001 From: jesse-ux Date: Tue, 27 Oct 2026 01:58:07 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A1=A5=E4=B8=8A=E7=B1=BB=E5=9E=8B=E6=A3=80?= =?UTF-8?q?=E6=9F=A5=E5=92=8C=20lint=20=E6=B2=A1=E8=BF=87=E7=9A=84?= =?UTF-8?q?=E5=87=A0=E5=A4=84=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 测试里的类型标注补全。侧栏拖宽把手改用组件库按钮,样子仍是 6px 透明细条。报告列表换人时不再在渲染中改引用。删掉已经没人用的证据回执类名。 Co-Authored-By: Grok (agent loop) --- ...PROGRESS-ui-kit-onboarding-fix-20261026.md | 19 +++++++++++++++++++ docs/tasks/README.md | 2 +- frontend/DESIGN.md | 2 +- frontend/src/app/globals.css | 5 ++++- .../personal-report-center.tsx | 14 ++++++++------ frontend/src/components/shell-frame.tsx | 4 +++- .../class-name-definition-contract.test.ts | 4 +++- .../tests/language-switch-selected.test.tsx | 4 ++-- frontend/tests/login-code-entry.test.tsx | 2 +- .../tests/onboarding-facts-refresh.test.tsx | 1 + .../tests/onboarding-motion-gate.test.tsx | 8 ++++---- frontend/tests/report-export-drawer.test.tsx | 6 ++++-- .../tests/shell-frame-stable-mount.test.tsx | 14 ++++++++------ 13 files changed, 59 insertions(+), 26 deletions(-) diff --git a/docs/tasks/PROGRESS-ui-kit-onboarding-fix-20261026.md b/docs/tasks/PROGRESS-ui-kit-onboarding-fix-20261026.md index a81e759d..8cbcb477 100644 --- a/docs/tasks/PROGRESS-ui-kit-onboarding-fix-20261026.md +++ b/docs/tasks/PROGRESS-ui-kit-onboarding-fix-20261026.md @@ -119,3 +119,22 @@ | 检查 | 命令 | 结果 | | --- | --- | --- | | 上面这几处,加上首页行数和外观菜单 | 在 `frontend` 下 `node node_modules/tsx/dist/cli.mjs --test` 跑清单、校正表面、清单刷新、首屏揭幕、滚动、输入法、字数、旧引导、起步问、首页行数、外观、首屏屏、起步页、提示监听、分栏挂载 | 85 通过,0 失败,0 取消,退出码 0 | + +## 类型检查和 lint + +没有新的产品故障,所以没有新的 BUG 编号。报告列表换人时不在渲染里改引用,和 BUG-620 是同一条规矩。 + +- 四处测试只有类型不过:语言切换的循环变量、登录邮箱赋值、清单刷新的路由桩缺 `bfcacheId`、动效测试读透明。断言没改。动效仍是「失败或太慢时不能藏起来」。 +- 侧栏拖宽把手改成组件库按钮。样子仍是 6px 透明细条。 +- 报告列表记住「这份名单属于谁」。换人之后,上一位还在写的报告不会被当成这一位刚写完。 +- 删掉已经没人用的类名 `birth-time-evidence-receipt`。原值在白名单里。新值删掉。原因是旧校正组件删了,这个类名没人用了。 + +| 检查 | 命令 | 结果 | +| --- | --- | --- | +| 类型检查 | 在 `frontend` 下 `node_modules\.bin\tsc.cmd --noEmit` | 退出码 0 | +| lint | 在 `frontend` 下 `npm run lint` | 退出码 0,0 error,122 条原有 warning | +| 类型那四个文件 | `node node_modules/tsx/dist/cli.mjs --test` 语言切换、登录、清单刷新、动效 | 8 通过,0 失败,0 取消,退出码 0 | +| 把手、导出、报告列表、按钮样式、白名单规则 | 同上,六个文件 | 31 通过,0 失败,0 取消,退出码 0 | +| 类名白名单 | 同上,`class-name-definition-contract.test.ts` | 3 通过,0 失败,0 取消,退出码 0 | + +全量 `npm test` 这次是在改文件的同时跑的,不能当作交回数字。5208 项,通过 5176,失败 32,取消 0,退出码 1。32 条里,13 条是 Windows 已知的数据库名单。`birth-time-evidence-receipt` 是上面已修的那条。启程阶段那条「300 毫秒内出现第一行」在全量里没赶上,单独重跑通过。其余是 Windows 上旧的双盘符路径、Skill 符号链接、部署脚本和临时目录被占。交回前会再跑一遍干净的全量。 diff --git a/docs/tasks/README.md b/docs/tasks/README.md index fab1aeb8..71178de5 100644 --- a/docs/tasks/README.md +++ b/docs/tasks/README.md @@ -40,7 +40,7 @@ | `TASK-rectification-explicit-open-20261023.md` | `PROGRESS-rectification-explicit-open-20261023.md` | **明说要校正只回一句话(issue #103)**:找会话直接读一张对服务角色撤权的表、必然失败被吞(真正进行中的也续不上);`candidate_accepted` 被算作进行中挡住入口和提议卡;兜底只给一句话。产品选 C;只改入口口径、经 RPC 找会话、删兜底,不改共享状态集合、不动表。BUG-1423 | 已合入 staging | 分支 `codex/rectification-explicit-open-20261023` | | `TASK-chart-table-span-20261023.md` | `PROGRESS-chart-table-span-20261023.md` | **星盘页本命 / 月亮行星表挤进左栏(issue #102)**:#87 给表包了 `ChartSymbolHelp` 后跨栏规则失配;根因修法是 `VedicLayout` 用固定整行容器承载表格,跨栏只认容器;加结构断言防复发。BUG-1422 | 已合入 staging | 分支 `codex/chart-table-span-20261023` | | `TASK-ui-kit-migration-20261023.md` | `PROGRESS-ui-kit-0-base-20261023.md`;`PROGRESS-ui-kit-1-shell-20261023.md`;`PROGRESS-ui-kit-2-chat-20261023.md`;`PROGRESS-ui-kit-3-chart-20261023.md`;`PROGRESS-ui-kit-4-people-20261023.md`;`PROGRESS-ui-kit-5-pages-20261023.md`;`PROGRESS-ui-kit-6-rectification-20261023.md`;`PROGRESS-ui-kit-7-cleanup-20261023.md` | **界面统一换成组件库**:shadcn base-nova(补 `shadcn/tailwind.css`)· Lucide · Motion · react-resizable-panels · dnd-kit · Zustand;lint 拦手写控件(白名单只减不增);对话旁星盘面板;会话、档案拖动排序(动表);长相不变。产品 2026-10-23 拍板 D1~D8;8 个 issue(K0~K7)串行,取代全站动效 #98 #100 #101 | K0–K7 已在测试环境,待验收 | 原型 https://claude.ai/artifact/PGnWsjHhu38KJseEfEMNWG | -| `TASK-ui-kit-onboarding-fix-20261026.md` | `PROGRESS-ui-kit-onboarding-fix-20261026.md` | **组件库 K0~K7 与启程(二)~(四)事后验收**:桌面首屏后整页重挂、中英切换看不出选中、提示层每次 DOM 变化全页扫描、启程动效对所有人生效且会藏内容、清单数据只拉一次、拖动排序 N 个请求与部分失败、缺真库重读测试、首屏超 2%、依赖卫生、补回削弱的测试、启程(二)三处未做 | 执行中(#111,U1–U6、U9、U10、U11 与顺手项已提交) | `codex/ui-kit-onboarding-fix-20261026` | +| `TASK-ui-kit-onboarding-fix-20261026.md` | `PROGRESS-ui-kit-onboarding-fix-20261026.md` | **组件库 K0~K7 与启程(二)~(四)事后验收**:桌面首屏后整页重挂、中英切换看不出选中、提示层每次 DOM 变化全页扫描、启程动效对所有人生效且会藏内容、清单数据只拉一次、拖动排序 N 个请求与部分失败、缺真库重读测试、首屏超 2%、依赖卫生、补回削弱的测试、启程(二)三处未做 | 执行中(#111,U1–U6、U9、U10、U11 与顺手项已提交;类型检查和 lint 已过) | `codex/ui-kit-onboarding-fix-20261026` | | —(门禁修复,无新任务书) | `PROGRESS-birth-journey-contract-20261025.md` | **测试分支过不了门禁,网站停在完整数据版(二)**:BUG-1439 出生时间合同;BUG-1440 测试画布;BUG-1441 侧栏流式检查等第一次补画结束再数帧。页面不改 | 已合入 staging,等门禁 | `codex/gate-birth-journey-contract-20261025` | | `TASK-onboarding-journey-20261022.md` | `PROGRESS-onboarding-1-data-20261022.md`;`PROGRESS-onboarding-2-intake-20261022.md`;`PROGRESS-onboarding-4-motion-20261022.md` | **新用户启程**:建档加「先看谁的盘」(自己 / 帮别人看,本人资料可后补)、人物关系字段、首问免费(服务端原子)、起步三问、启程清单(七项从数据推导,收费项标价,老用户可见)、八条一次性提示(含星盘四页签)、启程流程专用动效。产品 2026-10-22 拍板 D1~D6;动表经产品同意进循环,拆四个 issue 串行。BUG-1420 已被文档去重占用,本轮首问免费记 BUG-1421。(一)T1+T2 在 `codex/onboarding-1-data-20261022`。(二)T3+T4 记 BUG-1431,在 `codex/onboarding-2-intake-20261022`。(四)T7 在 `codex/onboarding-4-motion-20261022` | (一)(二)(三)已在 staging;(四)本分支待验收 | 原型 https://claude.ai/artifact/PcTwMVb9RD611R3qStxv3U | | `TASK-site-motion-20261022.md` | — | **全站动效**:换页按侧栏方向滑入、选中块跟随、页签、盘型切换星体移宫、换人、翻日期、列表补位、弹窗从触发处长出、抽屉、顶部提示、点数滚动、中英交叉淡入;重写 DESIGN §6(推翻 180 ms 上限与 Spatial Instant),不引动画库,先验证 View Transitions。产品 2026-10-22 拍板;三个 issue,排在启程(三)之后、启程(四)之前;**2026-10-23 被 ui-kit-migration 取代** | 已取代 | 原型 https://claude.ai/artifact/7u9goivnmTNSGTNWNeVMQd | diff --git a/frontend/DESIGN.md b/frontend/DESIGN.md index a3cec9b0..d805dffb 100644 --- a/frontend/DESIGN.md +++ b/frontend/DESIGN.md @@ -1267,7 +1267,7 @@ Yogini 还在取时,右栏用和 Chara 相同的轨道等待(`OrbitWaiting` ## 外壳(2026-10-24) -桌面侧栏宽 200–360px,默认 288,拖右侧把手调整。拖到比 200 更窄就收成 64px 窄轨;收起不把 64 写成宽度。宽度记在本机 `sidebar_width`。侧栏和主区始终在各自的容器里:视口就绪、跨过手机断点,都不会把整页卸掉再挂上。手机用左侧抽屉,宽 `min(86vw, 320px)`。当前页高亮用已有的 `layoutId` 滑动。翻页用已有的页面进入 280ms、离开 150ms,方向跟着侧栏顺序。顶栏点数是描边按钮:硬币图标加数字,44px;「点」只在可读名称里。数字变少时往下滚,变多时往上滚,360ms;系统要求减少动态时立刻换数字。启程清单胶囊仍是 32px,仍在点数左边。账户按钮只显示头像和名字。设置仍是三个分区。会话只在收藏组内、或最近一组内拖动排序;收藏整组仍在最前。翻页、高亮滑动和拖动排序用的库在页面出来后再载入,不进首屏。拖宽不再另载一套分栏库。 +桌面侧栏宽 200–360px,默认 288。右侧把手是组件库按钮,仍是 6px 透明细条;拖它调整宽度。拖到比 200 更窄就收成 64px 窄轨;收起不把 64 写成宽度。宽度记在本机 `sidebar_width`。侧栏和主区始终在各自的容器里:视口就绪、跨过手机断点,都不会把整页卸掉再挂上。手机用左侧抽屉,宽 `min(86vw, 320px)`。当前页高亮用已有的 `layoutId` 滑动。翻页用已有的页面进入 280ms、离开 150ms,方向跟着侧栏顺序。顶栏点数是描边按钮:硬币图标加数字,44px;「点」只在可读名称里。数字变少时往下滚,变多时往上滚,360ms;系统要求减少动态时立刻换数字。启程清单胶囊仍是 32px,仍在点数左边。账户按钮只显示头像和名字。设置仍是三个分区。会话只在收藏组内、或最近一组内拖动排序;收藏整组仍在最前。翻页、高亮滑动和拖动排序用的库在页面出来后再载入,不进首屏。拖宽不再另载一套分栏库。 ## 对话页(2026-10-25) diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css index 44774f50..85afb11c 100644 --- a/frontend/src/app/globals.css +++ b/frontend/src/app/globals.css @@ -829,7 +829,10 @@ section[popover]:has(> [data-sonner-toaster]), section[popover][aria-label^="页 .shell-inset, .shell-page { min-width: 0; min-height: 0; } .shell-page { height: 100%; } .shell-page-presence { position: relative; height: 100%; min-width: 0; min-height: 0; } -.shell-resize-handle { position: absolute; top: 0; right: -3px; z-index: 2; width: 6px; height: 100%; padding: 0; border: 0; background: transparent; cursor: col-resize; } +/* The handle is the shared Button. These states keep it a 6px transparent strip. */ +button.shell-resize-handle, +button.shell-resize-handle:hover, +button.shell-resize-handle:active { position: absolute; top: 0; right: -3px; z-index: 2; width: 6px; height: 100%; padding: 0; border: 0; border-radius: 0; background: transparent; transform: none; cursor: col-resize; } .sidebar-nav-current { position: absolute; inset: 0; z-index: 0; border-radius: var(--radius-md); background: var(--sidebar-accent); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--sidebar-ring) 22%, transparent); pointer-events: none; } .sidebar { position: relative; width: 100%; height: 100%; min-width: 0; min-height: 0; overflow: hidden; display: flex; flex-direction: column; border-right: 1px solid var(--sidebar-border); color: var(--sidebar-foreground); padding: var(--space-5) var(--space-3) 0; background: var(--sidebar-background); backdrop-filter: saturate(130%) blur(20px); } [data-sidebar="header"] { flex: 0 0 auto; padding-bottom: var(--space-3); } diff --git a/frontend/src/components/personal-report/personal-report-center.tsx b/frontend/src/components/personal-report/personal-report-center.tsx index 454d492d..3fd88f5d 100644 --- a/frontend/src/components/personal-report/personal-report-center.tsx +++ b/frontend/src/components/personal-report/personal-report-center.tsx @@ -104,17 +104,19 @@ export function PersonalReportCenter() { const [loadingMore, setLoadingMore] = useState(false); const deletedIds = useRef(new Set()); const cancelled = useRef(false); - const listedReports = useRef(state.reports); + // Written when a snapshot arrives, never during render (BUG-620). + // A different person does not inherit the previous generating rows. + const listedReports = useRef({ subjectId, reports: state.reports }); if (seenSubject !== subjectId) { setSeenSubject(subjectId); - listedReports.current = []; setState({ phase: "loading", ...EMPTY_LIST }); } const applySnapshot = useCallback((snapshot: ReportsPageSnapshot) => { const kept = (reports: readonly ReportListItem[]) => reports.filter(report => !deletedIds.current.has(report.id)); + const remembered = listedReports.current.subjectId === subjectId ? listedReports.current.reports : []; if (snapshot.kind === "unauthorized") { - listedReports.current = []; + listedReports.current = { subjectId, reports: [] }; setState({ phase: "unauthorized", ...EMPTY_LIST }); return; } @@ -123,13 +125,13 @@ export function PersonalReportCenter() { return; } const nextReports = kept(snapshot.reports); - const generating = new Set(listedReports.current.filter((report) => report.status === "generating").map((report) => report.id)); + const generating = new Set(remembered.filter((report) => report.status === "generating").map((report) => report.id)); const finished = nextReports.filter((report) => report.status === "ready" && generating.has(report.id)); - listedReports.current = nextReports; + listedReports.current = { subjectId, reports: nextReports }; if (finished.some((report) => report.reportType === "synastry_full")) notifyOnboardingFactsChanged("syn"); if (finished.some((report) => report.reportType !== "synastry_full")) notifyOnboardingFactsChanged("report"); setState(current => ({ phase: "ready", ...applyFirstReportPage(current, { reports: nextReports, nextCursor: snapshot.nextCursor }) })); - }, []); + }, [subjectId]); const load = useCallback(async (showLoading = false) => { if (!subjectReady) return; diff --git a/frontend/src/components/shell-frame.tsx b/frontend/src/components/shell-frame.tsx index 9f17f54d..74fb9b05 100644 --- a/frontend/src/components/shell-frame.tsx +++ b/frontend/src/components/shell-frame.tsx @@ -2,6 +2,7 @@ import { useEffect, useRef, type PointerEvent as ReactPointerEvent, type ReactNode } from "react"; +import { Button } from "@/components/ui/button"; import { useSidebar } from "@/components/ui/sidebar"; import { SIDEBAR_WIDTH_COLLAPSED, @@ -64,8 +65,9 @@ export function ShellFrame({ sidebar, children }: { sidebar: ReactNode; children
{sidebar} {desktop ? ( -