From 6998dc6ead8434eb25d024ce4c7f3fb0a7c0d2d8 Mon Sep 17 00:00:00 2001 From: jesse-ux Date: Tue, 27 Oct 2026 01:39:28 +0800 Subject: [PATCH] =?UTF-8?q?=E9=A1=BA=E6=89=8B=E6=94=B6=E6=8E=89=E6=AD=BB?= =?UTF-8?q?=E6=A0=B7=E5=BC=8F=E3=80=81=E6=97=A7=E6=A0=A1=E6=AD=A3=E5=85=A5?= =?UTF-8?q?=E5=8F=A3=E5=92=8C=E6=95=B4=E9=A1=B5=E8=B7=B3=E8=BD=AC=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 测试文档只在需要的测试里安装。提示和圆点按项分开。清单「去做」走客户端跳转。输入框高亮在离开页面时清掉。六个没有入口的旧校正组件删掉。 Co-Authored-By: Grok (agent loop) --- BLOCKED.md | 2 +- ...PROGRESS-ui-kit-onboarding-fix-20261026.md | 16 ++ docs/tasks/README.md | 2 +- frontend/src/app/(app)/page.tsx | 21 ++- frontend/src/app/globals.css | 2 - .../birth-time-candidate-result.tsx | 159 ---------------- .../components/birth-time-choice-question.tsx | 171 ------------------ .../birth-time-evidence-draft-card.tsx | 87 --------- .../src/components/birth-time-guide-turn.tsx | 69 ------- .../birth-time-legacy-rectification.tsx | 91 ---------- .../components/birth-time-rectification.tsx | 132 -------------- frontend/src/components/onboarding-dot.tsx | 6 +- .../src/components/onboarding-hint-layer.tsx | 2 +- .../components/rectification-choice-card.tsx | 5 +- frontend/src/components/secondary-header.tsx | 2 +- frontend/src/lib/onboarding-journey.tsx | 12 +- frontend/tests/account-deletion.test.tsx | 1 + .../birth-time-guided-review-fixes.test.ts | 33 +--- .../character-remaining-contract.test.ts | 9 +- .../tests/chat-panel-scroll-guard.test.ts | 5 +- frontend/tests/composer-ime-contract.test.ts | 16 +- ...versation-scroll-anchor-lifecycle.test.tsx | 1 + .../tests/ephemeris-page-lifecycle.test.tsx | 1 + .../feedback-complaints-20260930.test.tsx | 1 + frontend/tests/home-bootstrap-reveal.test.ts | 5 +- frontend/tests/home-sidebar-rerender.test.tsx | 1 + .../tests/home-warm-return-lifecycle.test.tsx | 1 + .../new-chat-from-people-lifecycle.test.tsx | 1 + .../onboarding-checklist-20261022.test.ts | 18 ++ .../tests/onboarding-facts-refresh.test.tsx | 18 +- .../tests/onboarding-hint-observer.test.tsx | 1 + .../tests/onboarding-motion-gate.test.tsx | 1 + .../people-pagination-lifecycle.test.tsx | 1 + .../react-client-lifecycle-test-support.ts | 14 +- ...ctification-dup-question-20260926.test.tsx | 1 + .../rectification-latency-20260926.test.tsx | 1 + ...tification-opening-plain-20260926.test.tsx | 1 + .../rectification-surface-contract.test.ts | 4 + .../tests/reply-quality-20260930.test.tsx | 1 + .../report-language-switch-20260930.test.tsx | 1 + .../report-list-paging-20260930.test.tsx | 1 + frontend/tests/report-reader-polish.test.tsx | 1 + .../tests/session-list-lifecycle.test.tsx | 1 + .../tests/shell-frame-stable-mount.test.tsx | 1 + .../tests/surface-feedback-20260929.test.tsx | 1 + 45 files changed, 133 insertions(+), 788 deletions(-) delete mode 100644 frontend/src/components/birth-time-candidate-result.tsx delete mode 100644 frontend/src/components/birth-time-choice-question.tsx delete mode 100644 frontend/src/components/birth-time-evidence-draft-card.tsx delete mode 100644 frontend/src/components/birth-time-guide-turn.tsx delete mode 100644 frontend/src/components/birth-time-legacy-rectification.tsx delete mode 100644 frontend/src/components/birth-time-rectification.tsx diff --git a/BLOCKED.md b/BLOCKED.md index 2d6efca4..3fa3e3a8 100644 --- a/BLOCKED.md +++ b/BLOCKED.md @@ -653,7 +653,7 @@ - **`Home` 要真正受益很可能得拆组件,属业务代码重构,本轮明令禁止。** 诊断结果进一步说明这条路该怎么走:为迎合编译器去改 `Home`(删 `finally`、重排 6 个被声明前引用的函数、逐个绕内部断言)是被编译器 bug 牵着走的打地鼠,每步都拿确定的正确性换不确定的收益;值得做的是按职责把 `Home` 拆小。 证据是同一文件里两个小组件(原始行 760、815)被正常编译、全项目另有 44 个函数被编译,唯独这个 2730 行、24 个 `useState`、18 个 `useEffect` 的函数被拒。任务书要求「哪处代码挡住了编译器就写进 BLOCKED.md,不要自行改写」,故 `frontend/src/**` 一行业务代码未动(仅反向验证时临时加删一行 `"use no memo"`,已还原,`git diff` 为空)。 - **~~待决策~~ 已决策(2026-08-17):维持回滚,不为那 44 个组件保留 `reactCompiler: true`;若要翻案,前置条件是先测出那 44 个的渲染基准。** 本轮按任务 2 止损条款已回滚该配置。数据是:构建耗时无可测量变化(噪声内),`/` 首屏 JS gzip +12031 B / +2.50 %,换来 44 个函数自动记忆化,其中 `app-sidebar`(112 槽)、`sidebar-session-row`(73 槽)、`birth-date-picker`(58 槽)与三个引导 hook 都在 `/` 上渲染。这 44 个的实际运行时收益本轮未测量(无渲染基准),所以不替决策者拍板。重新开启只需在 `frontend/next.config.ts` 顶层加 `reactCompiler: true`、`experimental` 里加 `turbopackRustReactCompiler: true`。 - **`compilationMode: "all"` 不可用,会破坏构建。** 它会编译模块作用域的普通回调,`frontend/src/components/birth-time-intake.tsx:22` 的 `Array.from({ length: 24 }, (_, index) => ...)` 被插入 `useMemoCache`,预渲染 `/` 时抛 `TypeError: Cannot read properties of null (reading 'useMemoCache')`。这是该模式本身的性质(官方标注为不安全),不是本仓库代码的缺陷,未做任何改动。 -- **顺手活一律未做,登记在此:** 其一,`frontend/src/lib/skill-package-registry.ts:523` 的 `readFileSync(currentRegistryPath, "utf8")` 触发 Turbopack 构建警告「Dynamic filesystem access causes tracing of the whole project」,会把整个项目(含 `public/`)打进 server 产物,影响部署体积;升级前后都存在,与本轮无关,未改。其二,`npx eslint` 有 4 个既有 `no-unused-vars` warning,分别在 `birth-time-candidate-result.tsx:143`、~~`birth-time-candidate-completion.ts:10,11`~~(2026-10-13 已随该文件删除)、`tests/identity-auth-factory.test.ts:48`,未改。其三,`eslint-config-next` 仍是 16.2.10、与 next 16.3.1 版本号不同步,但 eslint 实测 0 error,按「不许顺手升别的依赖」未动。 +- **顺手活一律未做,登记在此:** 其一,`frontend/src/lib/skill-package-registry.ts:523` 的 `readFileSync(currentRegistryPath, "utf8")` 触发 Turbopack 构建警告「Dynamic filesystem access causes tracing of the whole project」,会把整个项目(含 `public/`)打进 server 产物,影响部署体积;升级前后都存在,与本轮无关,未改。其二,`npx eslint` 有 4 个既有 `no-unused-vars` warning,分别在 ~~`birth-time-candidate-result.tsx:143`~~(2026-10-27 该组件已删)、~~`birth-time-candidate-completion.ts:10,11`~~(2026-10-13 已随该文件删除)、`tests/identity-auth-factory.test.ts:48`,其余未改。其三,`eslint-config-next` 仍是 16.2.10、与 next 16.3.1 版本号不同步,但 eslint 实测 0 error,按「不许顺手升别的依赖」未动。 - **测试环境噪声(非阻塞,已自行消化):** `frontend/tests/rectification-v9-database.test.ts` 的「v9 migration applies on a fresh database and re-applies idempotently」在全量并发下偶发失败(`database migration failed`,1 !== 0),单独重跑 7/7 通过、全量重跑 1592/1592 通过。靠 Docker 起临时 Postgres,判定为资源争用型 flake,与 Next 版本无关。未改任何测试文件。 - **文件名偏离:** 任务书要求新建 `PROGRESS.md`,但根目录已有受版本控制的 `progress.md`(1025 行)且本机文件系统大小写不敏感,写 `PROGRESS.md` 等于覆盖清单外的文件,故进度记录落在 `PROGRESS-react-compiler-20260817.md`。 diff --git a/docs/tasks/PROGRESS-ui-kit-onboarding-fix-20261026.md b/docs/tasks/PROGRESS-ui-kit-onboarding-fix-20261026.md index 5dd738f7..a81e759d 100644 --- a/docs/tasks/PROGRESS-ui-kit-onboarding-fix-20261026.md +++ b/docs/tasks/PROGRESS-ui-kit-onboarding-fix-20261026.md @@ -103,3 +103,19 @@ 断言三栏(引导页是否预载):原值是目录还没到也按空列表预载。新值是已知空列表才预载,未知不预载、步骤停在称呼。原因是未知不能当成没建完档。 断言三栏(输入框标签和禁用):原值认 `onboardingStep`。新值认 `shownOnboardingStep`,并在目录未到时不收起输入框。原因同上。称呼和发送的两句文案没改。 + +## 顺手项 + +没有新的产品故障,所以没有新的 BUG 编号。 + +- 测试用的文档只在需要文档的测试里安装。做服务端渲染的七个测试文件不再因为导入测试工具就装上文档。 +- 外观菜单里已经没人用的 `.theme-menu-check` 删了。选中勾仍由菜单自己画出来。 +- 校正里选了「这题跳过」时,停止按钮呈选中。 +- 六个没有入口的旧校正组件删了:引导轮、旧选项题、候选结果、证据草稿、旧校正页、更早的旧校正页。首页不再预热其中那一页。 +- 提示和星盘圆点按每一项分开,不再共用同一个键。 +- 清单「去做」在版本一致时走页面内跳转。版本对不上时仍整页打开。 +- 点第三问把输入框点亮后,离开页面会把计时器清掉。 + +| 检查 | 命令 | 结果 | +| --- | --- | --- | +| 上面这几处,加上首页行数和外观菜单 | 在 `frontend` 下 `node node_modules/tsx/dist/cli.mjs --test` 跑清单、校正表面、清单刷新、首屏揭幕、滚动、输入法、字数、旧引导、起步问、首页行数、外观、首屏屏、起步页、提示监听、分栏挂载 | 85 通过,0 失败,0 取消,退出码 0 | diff --git a/docs/tasks/README.md b/docs/tasks/README.md index f189efbc..fab1aeb8 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 与顺手项已提交) | `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/src/app/(app)/page.tsx b/frontend/src/app/(app)/page.tsx index 1a192618..4938b319 100644 --- a/frontend/src/app/(app)/page.tsx +++ b/frontend/src/app/(app)/page.tsx @@ -201,6 +201,14 @@ const BillingPanel = dynamic(loadBillingPanel, { loading: () =>

账户信息还没拿到。

, }); +let nudgeComposerTimer: number | undefined; + +function clearNudgeComposerTimer() { + if (nudgeComposerTimer === undefined) return; + window.clearTimeout(nudgeComposerTimer); + nudgeComposerTimer = undefined; +} + export default function Home() { const sessionList = useSessionList(); const { @@ -706,10 +714,7 @@ export default function Home() { return () => window.clearTimeout(timer); }, [bootstrapPhase, bootstrapPrepareReady, hydrated]); - useEffect(() => { - if (bootstrapPhase !== "prepare" || uiPreview.current) return; - void import("@/components/birth-time-rectification"); - }, [bootstrapPhase]); + useEffect(() => () => { clearNudgeComposerTimer(); }, []); useEffect(() => { if (!hydrated || uiPreview.current) return; @@ -1075,7 +1080,11 @@ export default function Home() { if (!input) return; input.classList.add("is-starter-focus"); input.focus({ preventScroll: true }); - window.setTimeout(() => input.classList.remove("is-starter-focus"), 700); + clearNudgeComposerTimer(); + nudgeComposerTimer = window.setTimeout(() => { + nudgeComposerTimer = undefined; + input.classList.remove("is-starter-focus"); + }, 700); } async function copyAssistantMessage(renderKey: string, text: string) { @@ -1270,7 +1279,7 @@ export default function Home() {
- +
{activeSession?.title || "新对话"} diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css index 78bcb9b9..44774f50 100644 --- a/frontend/src/app/globals.css +++ b/frontend/src/app/globals.css @@ -1974,9 +1974,7 @@ section[popover]:has(> [data-sonner-toaster]), section[popover][aria-label^="页 .theme-menu { display: grid; gap: 1px; padding: var(--space-1) 0; } .theme-menu-label { padding: var(--space-1) var(--space-3); color: var(--color-ink-tertiary); font-size: var(--type-caption); font-weight: 500; } .theme-menu-item { cursor: pointer; } -.theme-menu-check { display: inline-grid; margin-left: auto; place-items: center; color: var(--color-action); } .theme-menu [data-slot="dropdown-menu-radio-item-indicator"] { color: var(--color-action); } -.theme-menu-check svg { width: 15px; height: 15px; } .account-menu-separator { height: 1px; margin: var(--space-2) var(--space-3); background: var(--color-border); } .account-info-list { display: grid; gap: var(--space-3); margin: var(--space-5) 0 0; } diff --git a/frontend/src/components/birth-time-candidate-result.tsx b/frontend/src/components/birth-time-candidate-result.tsx deleted file mode 100644 index dd75d069..00000000 --- a/frontend/src/components/birth-time-candidate-result.tsx +++ /dev/null @@ -1,159 +0,0 @@ -"use client"; - -import type { JourneyClientResponse } from "@/lib/birth-time-journey-client"; -import type { BirthTimeGuidedController } from "@/hooks/use-birth-time-guided-journey"; -import { guidedTerminalPath } from "@/lib/birth-time-guided-terminal"; -import { ErrorToast } from "@/components/error-toast"; -import { Button } from "@/components/ui/button"; - -type CandidateResultProps = { - readonly journey: JourneyClientResponse; - readonly controller: BirthTimeGuidedController; - readonly error: string; -}; - -const confidenceLabels = { - low: "证据不足", - medium: "中等置信", - high: "较高置信", -} as const; - -const gateLabels: Readonly> = { - event_quality: "经历质量", - cross_domain_coverage: "跨领域覆盖", - required_layers: "必需计算层", - neighbor_stability: "相邻分钟稳定性", - leave_one_event_out: "删除单条经历复算", - three_engine_input_parity: "三引擎同输入对照", - public_holdout_release: "公开 AA 盲测", -}; - -const gateStatusLabels: Readonly> = { - pass: "通过", - fail: "未通过", - blocked: "尚未具备条件", - not_evaluated: "尚未执行", -}; - -export function BirthTimeCandidateResult({ journey, controller, error }: CandidateResultProps) { - const result = journey.candidateResult; - const action = journey.nextAction; - const dynamic = journey.journeyProtocol === "dynamic-choice-v2"; - const terminalPath = guidedTerminalPath(journey); - if (result?.eventCount === 0) { - return ( -
-

尚未进入分钟计算:还没有可评分的关键经历资料。

-

当前范围仅为填报范围,不是校正结果;请补充跨领域、可注明年月的重大事件后重新评估。

- {terminalPath && } -
- ); - } - if (!result && action.kind === "present_low_result") { - return ( -
-

系统不会选择或应用未经证据支持的具体分钟,当前排盘使用时间保持不变。

- {terminalPath && } - {!terminalPath ? : null} -
- ); - } - if (!result) return null; - const winner = result.winningSegment; - const receipt = result.techniqueReceipt; - - return ( -
-
- {dynamic ? "候选范围评估结果" : "关键经历评分结果"} - {confidenceLabels[result.confidence]} -
- {winner ? ( -
-
候选时间范围
{winner.startTime}—{winner.endTime}
-
候选代表时间
{winner.representativeTime}
- {!dynamic &&
有效经历
{result.eventCount} 条 / {result.domainCount} 个领域
} - {!dynamic &&
领先幅度
{result.marginPercent}%
} -
- ) : ( -

候选仍然并列或缺少必要计算层,系统不会选择具体分钟。

- )} -

这是可复现的候选评分,不代表已经证明出生记录中的具体分钟。

- {receipt && ( -
- 本次计算回执 -

已用:{[...receipt.usedDivisionalCharts, ...receipt.usedArudha, ...receipt.dashaTracks].join("、") || "无"}

-

辅助:{receipt.auxiliaryLayers.join("、") || "无"}

-

未完成:{receipt.missingLayers.join("、") || "无"}

- {receipt.gates && ( -
    - {Object.entries(receipt.gates).map(([name, gate]) => ( -
  • {gateLabels[name] ?? name}:{gateStatusLabels[gate.status] ?? gate.status}
  • - ))} -
- )} - {receipt.confirmationAllowed === false &&

当前仅保留候选范围,分钟确认尚未开放。

} -
- )} - - {action.kind === "present_low_result" && ( -
-

{dynamic ? "目前没有足够的新信息继续稳定缩小范围,本次评估已结束并保存当前候选范围。" : "本轮校正已安全结束,只保留候选范围,当前排盘使用时间保持不变。"}

- {terminalPath && } -
- )} - {action.kind === "present_medium_result" && winner && dynamic && ( -
-

已形成较窄的候选范围,本次评估已结束;它不会自动改动当前排盘使用时间。

- {terminalPath && } -
- )} - {action.kind === "present_medium_result" && winner && !dynamic && ( -
-

可以保存候选时间范围,代表时间不会用于排盘。

- -
- )} - {action.kind === "candidate_saved" && ( -
-

候选时间范围已保存。当前排盘使用时间没有改变。

- {terminalPath && } -
- )} - {action.kind === "request_candidate_confirmation" && winner && ( -
- 确认当前排盘使用时间 -

候选时间为 {winner.representativeTime}。确认后它会成为当前排盘使用时间,原始填报时间仍会保留。

- -
- )} - {action.kind === "ready" && ( -
-

当前排盘使用时间已更新为 {action.activeTime},原始填报时间仍已保留。

- -
- )} - {!terminalPath ? : null} -
- ); -} - -function TerminalAction({ controller, error, path }: { - readonly controller: BirthTimeGuidedController; - readonly error: string; - readonly path: NonNullable>; -}) { - return ( -
- 尚未达到采用条件 -

候选范围已保留,但当前证据不足以将具体分钟写入当前排盘时间。补充经历后可重新评估。

- - - 会建立新的记录,当前结果仍会保留。 -
- ); -} diff --git a/frontend/src/components/birth-time-choice-question.tsx b/frontend/src/components/birth-time-choice-question.tsx deleted file mode 100644 index 7098dc99..00000000 --- a/frontend/src/components/birth-time-choice-question.tsx +++ /dev/null @@ -1,171 +0,0 @@ -"use client"; - -import { useEffect, useRef, useState } from "react"; -import { CharacterRemaining } from "@/components/character-remaining"; -import { RectificationFade } from "@/components/rectification-fade"; -import { Button } from "@/components/ui/button"; -import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"; -import { characterRemainingVisible } from "@/lib/character-remaining"; -import { - choiceQuestionGroups, - choiceSelectionIntent, - normalizeUnmatchedNote, - rangeLabel, -} from "@/lib/birth-time-choice-question-model"; -import type { - DynamicJourneyProgress, - PublicDynamicChoiceQuestion, -} from "@/lib/birth-time-journey-turn-protocol"; -import { ErrorToast } from "@/components/error-toast"; - -type SharedQuestionProps = { - readonly progress: DynamicJourneyProgress; - readonly pending: boolean; - readonly error: string; - readonly onPause: () => void; - readonly onFinish: () => void; -}; - -type ChoiceQuestionProps = SharedQuestionProps & { - readonly question: PublicDynamicChoiceQuestion; - readonly onSelect: (optionId: string) => void; -}; - -type ClarificationProps = SharedQuestionProps & { - readonly onReframe: (note: string) => void; -}; - -export function BirthTimeQuestionProgress({ progress }: { - readonly progress: DynamicJourneyProgress; -}) { - return ( -
- 已完成 {progress.effectiveAnswerCount} 个有效判断 - 当前候选范围:{rangeLabel(progress.currentRange)} -
- ); -} - -export function BirthTimeChoiceQuestion(props: ChoiceQuestionProps) { - const groups = choiceQuestionGroups(props.question); - const [selectedId, setSelectedId] = useState(""); - const firstChoiceRef = useRef(null); - useEffect(() => { - if (!props.pending && (props.progress.answeredCount > 0 || props.error)) { - /* preventScroll: the transcript has its own anchor; letting the browser - scroll ancestors here scrolls `.chat-panel`, an overflow:hidden box with - no scrollbar, and the 46px header never comes back. */ - firstChoiceRef.current?.focus({ preventScroll: true }); - } - }, [props.error, props.pending, props.progress.answeredCount, props.question.questionId]); - const select = (option: PublicDynamicChoiceQuestion["options"][number]) => { - const intent = choiceSelectionIntent(option); - setSelectedId(intent.optionId); - props.onSelect(intent.optionId); - }; - - return ( - -
- -
- {props.question.prompt} - { - const id = next[0]; - if (!id || id === selectedId) return; - const option = [...groups.primary, groups.unknown, groups.unmatched].find((item) => item.optionId === id); - if (option) select(option); - }} - > -
- {groups.primary.map((option, index) => ( - - {option.label} - - ))} -
-
- {[groups.unknown, groups.unmatched].map((option) => ( - - {option.label} - - ))} -
-
-
- {props.pending &&

正在缩小候选范围…

} - -
-
- ); -} - -const unmatchedRemainingId = "birth-time-unmatched-remaining"; - -export function BirthTimeUnmatchedClarification(props: ClarificationProps) { - const [note, setNote] = useState(""); - const reframeRef = useRef(null); - useEffect(() => { - if (!props.pending && (props.progress.answeredCount > 0 || props.error)) { - reframeRef.current?.focus({ preventScroll: true }); - } - }, [props.error, props.pending, props.progress.answeredCount]); - return ( - -
- -
- 这道题都不符合你的情况吗? -