diff --git a/docs/BUG_HISTORY.md b/docs/BUG_HISTORY.md index 9b2fb77a..c77ebaf6 100644 --- a/docs/BUG_HISTORY.md +++ b/docs/BUG_HISTORY.md @@ -19626,3 +19626,19 @@ - 复发自:无 - 修复版本:分支 `codex/full-data-parity-3-20261022`。 +## BUG-1444 | 桌面加载后整页被卸掉再挂上 + +- 状态:resolved +- 首次发现:2026-10-26 +- 最近更新:2026-10-26 +- 影响面:桌面外壳和对话页旁边的本命盘。手机抽屉的开关方式没改。 +- 用户现象:桌面打开网站,或把窗口从手机宽度拉到桌面,整页会再闪一次,正在生成的回答可能丢掉。第一次打开旁边的本命盘时,对话列也会重新挂上,输入到一半的内容和滚动位置会丢。 +- 触发条件:可拖分栏的代码还没到时,外壳是普通容器;代码到达后改成另一套面板组件,把整页包进去。本命盘同理,对话列被挪进分栏组件里。窗口跨过手机断点时再来一次。 +- 根因:两种状态下,页面内容的祖先元素类型变了。React 会卸载再重挂,启动效果跑两次。 +- 修复:侧栏和主区始终留在各自的容器里。拖宽用侧栏容器里的把手,夹在 200 到 360 之间,拖到比 200 更窄就收成 64。不再另载一套分栏库包住整页。本命盘作为对话列的兄弟,第一次打开后加在旁边,不挪走对话列。不用 `key` 掩盖重挂。 +- 验证:`frontend/tests/shell-frame-stable-mount.test.tsx` 在分栏到达前后、以及视口 500 与 1440 之间,侧栏和对话列都是挂载 1 次、卸载 0 次。同一批还跑了 `sidebar-contract.test.ts`、`chat-chart-panel-state.test.ts`、`class-name-definition-contract.test.ts`,合计 52 通过、0 失败、退出码 0。 +- 防复发:上面的挂载计数测试。再把页面包进后到达的面板里,计数会从 1 变成 2。 +- 相关记录:issue #111 验收 U1。#105、#106。 +- 复发自:无 +- 修复版本:分支 `codex/ui-kit-onboarding-fix-20261026`。 + diff --git a/docs/tasks/PROGRESS-ui-kit-onboarding-fix-20261026.md b/docs/tasks/PROGRESS-ui-kit-onboarding-fix-20261026.md new file mode 100644 index 00000000..b09a4e19 --- /dev/null +++ b/docs/tasks/PROGRESS-ui-kit-onboarding-fix-20261026.md @@ -0,0 +1,15 @@ +# 进度:组件库与启程事后修复(2026-10-26) + +分支 `codex/ui-kit-onboarding-fix-20261026`。只推这个分支。验收通过前不合进测试环境。承载 issue #111。BUG 从 1444 起。 + +## U1 · 桌面加载不再把整页卸掉再挂上(BUG-1444) + +侧栏和主区始终在各自的容器里。拖宽用侧栏里的把手,宽 200–360,默认 288,拖到比 200 更窄收成 64。本命盘加在对话列旁边,不把对话列挪进后到达的分栏里。 + +| 检查 | 命令 | 结果 | +| --- | --- | --- | +| 挂载与相关合同 | `node node_modules/tsx/dist/cli.mjs --test tests/shell-frame-stable-mount.test.tsx tests/sidebar-contract.test.ts tests/chat-chart-panel-state.test.ts tests/class-name-definition-contract.test.ts` | 52 通过,0 失败,退出码 0 | + +断言三栏(`sidebar-contract.test.ts`「selects desktop and tablet shell widths」):原值是网格列分出 288 / 240 / 64,并检查 `minSize={SIDEBAR_WIDTH_MIN}`。新值检查横向 flex、`shell-sidebar-slot` / `shell-main-slot`,以及宽度上下限常量。原因是可拖面板不再包住整页,页面在分栏到达或跨断点时不再卸载。 + +全量 `npm test`、`npm run build`、`npm run test:db`、Python 快速门留到本单全部改完再跑。 diff --git a/docs/tasks/README.md b/docs/tasks/README.md index 284f5700..ebde06c3 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` | — | **组件库 K0~K7 与启程(二)~(四)事后验收**:桌面首屏后整页重挂、中英切换看不出选中、提示层每次 DOM 变化全页扫描、启程动效对所有人生效且会藏内容、清单数据只拉一次、拖动排序 N 个请求与部分失败、缺真库重读测试、首屏超 2%、依赖卫生、补回削弱的测试、启程(二)三处未做 | 待执行(#111 承载) | `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 已提交) | `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 3d87eac8..c34f4f08 100644 --- a/frontend/DESIGN.md +++ b/frontend/DESIGN.md @@ -1267,13 +1267,13 @@ Yogini 还在取时,右栏用和 Chara 相同的轨道等待(`OrbitWaiting` ## 外壳(2026-10-24) -桌面侧栏在可拖分栏里,宽 200–360px,默认 288。拖到最左收成 64px 窄轨;收起不把 64 写成宽度。宽度记在本机 `sidebar_width`。手机用左侧抽屉,宽 `min(86vw, 320px)`。当前页高亮用已有的 `layoutId` 滑动。翻页用已有的页面进入 280ms、离开 150ms,方向跟着侧栏顺序。顶栏点数是描边按钮:硬币图标加数字,44px;「点」只在可读名称里。数字变少时往下滚,变多时往上滚,360ms;系统要求减少动态时立刻换数字。启程清单胶囊仍是 32px,仍在点数左边。账户按钮只显示头像和名字。设置仍是三个分区。会话只在收藏组内、或最近一组内拖动排序;收藏整组仍在最前。翻页、高亮滑动、拖宽和拖动排序用的库在页面出来后再载入,不进首屏。 +桌面侧栏宽 200–360px,默认 288,拖右侧把手调整。拖到比 200 更窄就收成 64px 窄轨;收起不把 64 写成宽度。宽度记在本机 `sidebar_width`。侧栏和主区始终在各自的容器里:视口就绪、跨过手机断点,都不会把整页卸掉再挂上。手机用左侧抽屉,宽 `min(86vw, 320px)`。当前页高亮用已有的 `layoutId` 滑动。翻页用已有的页面进入 280ms、离开 150ms,方向跟着侧栏顺序。顶栏点数是描边按钮:硬币图标加数字,44px;「点」只在可读名称里。数字变少时往下滚,变多时往上滚,360ms;系统要求减少动态时立刻换数字。启程清单胶囊仍是 32px,仍在点数左边。账户按钮只显示头像和名字。设置仍是三个分区。会话只在收藏组内、或最近一组内拖动排序;收藏整组仍在最前。翻页、高亮滑动和拖动排序用的库在页面出来后再载入,不进首屏。拖宽不再另载一套分栏库。 ## 对话页(2026-10-25) 输入框仍是一个框。框底左边是模型菜单,只显示模型名;价格「N 点」只在菜单里。菜单用外壳已有的延后载入,不进首屏;库还没到时按钮上仍是模型名。右边是圆形发送,和框的底边对齐。回答下面赞、踩、复制、重新生成是 26px 的图标按钮,各有一句提示。不满意原因和兑换窗用组件库对话框,句子不改。追问是描边小按钮,仍按启程进场依次浮出(260ms,间隔 64ms),不另写一套时长。 -桌面顶栏、点数右边,是打开或收起本命盘的图标按钮,提示「打开本命盘」或「收起本命盘」。面板在可拖分栏里,宽 280–560px,默认 360。拖到最窄可以收起。开着和宽度记在本机 `chat_chart_panel_open`、`chat_chart_panel_width`。手机(视口还没定,或小于 768)不显示这个按钮,也不因为藏起来就把「开着」写成关掉。面板里是当前人物的本命盘,复用星盘页的绘制,不写骨架、不写「正在加载」。可拖分栏和星盘绘制都在第一次打开后面才载入,不进首屏。 +桌面顶栏、点数右边,是打开或收起本命盘的图标按钮,提示「打开本命盘」或「收起本命盘」。面板宽 280–560px,默认 360。拖到最窄可以收起。开着和宽度记在本机 `chat_chart_panel_open`、`chat_chart_panel_width`。手机(视口还没定,或小于 768)不显示这个按钮,也不因为藏起来就把「开着」写成关掉。面板里是当前人物的本命盘,复用星盘页的绘制,不写骨架、不写「正在加载」。对话列始终留在原处。本命盘在第一次打开后载入,加在对话列旁边,不把对话列卸掉再挂上。星盘绘制不进首屏。 ## 新用户启程(2026-10-25) diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css index e65c8a19..31b02b5e 100644 --- a/frontend/src/app/globals.css +++ b/frontend/src/app/globals.css @@ -823,11 +823,13 @@ section[popover]:has(> [data-sonner-toaster]), section[popover][aria-label^="页 .group\/sidebar-provider[data-viewport] { height: 100vh; min-height: 0; overflow: hidden; } .chat-app { width: 100%; height: 100%; min-height: 0; overflow: clip; display: flex; flex-direction: column; background: var(--color-canvas); } .shell-frame { display: flex; flex: 1 1 auto; flex-direction: row; min-width: 0; min-height: 0; } -.shell-frame[data-shell-resize="pending"] > :first-child { flex: 0 0 var(--shell-sidebar-width, 288px); width: var(--shell-sidebar-width, 288px); min-width: 0; } +.shell-sidebar-slot { position: relative; flex: 0 0 var(--shell-sidebar-width, 288px); width: var(--shell-sidebar-width, 288px); min-width: 0; min-height: 0; } +.shell-frame[data-shell-resize="mobile"] > .shell-sidebar-slot { flex: 0 0 auto; width: auto; } +.shell-main-slot { display: flex; flex: 1 1 auto; flex-direction: column; min-width: 0; min-height: 0; } .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 { flex: 0 0 6px; } +.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; } .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); } @@ -1016,8 +1018,9 @@ section[popover]:has(> [data-sonner-toaster]), section[popover][aria-label^="页 .chat-panel.is-rectification { grid-template-rows: 46px minmax(0, 1fr); } /* Home spans the outer rows with one frame, then repeats the same templates inside `.chat-column`. Secondary pages stay direct children of `.chat-panel`. */ -.chat-chart-frame { grid-row: 1 / -1; grid-column: 1; min-width: 0; min-height: 0; height: 100%; } -.chat-chart-split { height: 100%; min-width: 0; min-height: 0; } +.chat-chart-frame { grid-row: 1 / -1; grid-column: 1; min-width: 0; min-height: 0; height: 100%; display: flex; flex-direction: row; } +.chat-chart-frame > .chat-column { flex: 1 1 auto; } +.chat-chart-split { height: 100%; min-width: 0; min-height: 0; flex: 0 0 auto; } .chat-column { height: 100%; min-width: 0; min-height: 0; display: grid; grid-template-rows: 46px minmax(0, 1fr) auto; } .chat-panel.is-rectification .chat-column { grid-template-rows: 46px minmax(0, 1fr); } .chat-panel:has(.starter-balance) .chat-column { grid-template-rows: 46px minmax(0, 1fr) auto minmax(0, 1fr); } diff --git a/frontend/src/components/chat-chart-frame.tsx b/frontend/src/components/chat-chart-frame.tsx index 98efb645..7d84a0b8 100644 --- a/frontend/src/components/chat-chart-frame.tsx +++ b/frontend/src/components/chat-chart-frame.tsx @@ -15,7 +15,6 @@ type SplitComponent = (props: { subjectId: string; onOpenChange: (open: boolean) => void; onWidth: (width: number) => void; - children: ReactNode; }) => ReactNode; export function ChatChartToggle() { @@ -78,14 +77,12 @@ export function ChatChartFrame({ children }: { children: ReactNode }) { }, [Split, desktop, hydrated, open]); const column =