Files
Jyotisha/frontend/src/components/rectification-timeline.tsx
T
Jesse_ChenandClaude Fable 5.1 662672c153 fix(web): 手机上时间轴读数不再被裁,「跳到最新」不再压住选项
375px 下读数第四项被 ellipsis 裁成「已…」、第五项整项不可见:compact
段仍加 44px 头像缩进(每侧 60px,可用内容只剩 255px),而 cfb41daf 又把
读数从四项变成五项,按 13px 逐字估宽需要 383px。compact 段改为只取
`space-4`、`column-gap` 收到 `space-2`、第五项 `已对照 N 件` 在 767px 以下
`display: none`(仍在 DOM 与轴 aria-label 里),四项约 292px / 可用 343px。
桌面段、条高 64/56px、滚动锚定逻辑均未动。

「跳到最新」浮层占 composer 上方 56px 带,而 `.message-list` 底部留白恰好
也是 56px,末条选项贴在浮层按钮正下方、正中间点不动。留白改为
`calc(clearance + space-3)`,静止时末条下方 84px = 56px 带 + 28px 空气;
`.conversation` 追加同高 `scroll-padding-block-end`。浮层位置、居中与可见
条件未动。

顺带记一条待产品裁决的口径冲突:DESIGN §10 禁列表写明「已对照 N 件经历」
不上条、仍归交付卡,`cfb41daf` 却把它加上且未改 DESIGN;本轮只在手机上把
它移出可见行,桌面保留原状。

BUG-918、BUG-919

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JUei7K13cYxLHE3Axe4A45
2026-09-17 00:53:21 +00:00

104 lines
4.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* The always-present range bar above the rectification conversation.
*
* Read-only by design: it answers "how wide is the range now", never "which
* minute do I take". Adoption stays on the delivery card, so this component
* takes no callbacks and renders no controls.
*
* It carries the axis and a one-line readout: range, width, working minute and
* the answered-question count. The working minute is here because on a narrow
* viewport the board is an overlay and closed by default, which used to leave
* the one number the reader tracks off screen entirely (D10, 2026-09-16).
*
* Still cut, and staying cut: a legend, the stage name, and the range-change
* sentence — that sentence is already written into the persisted assistant
* message (BUG-588 requires it there, so repeating it would put one sentence on
* screen twice).
*
* Every figure is echoed from the server projection. The working minute comes
* from the same `workingRectificationTime(result)` call the board's title-row
* clock makes, so the two surfaces cannot disagree; the count is the server's
* `answered_probe_count`. Nothing is computed here.
*
* The bar lives outside the scroll container as its own grid row, so its height
* must never change — see `.rectification-timeline` in globals.css. The readout
* stays one line for that reason: it grew wider, never taller. An unready case
* renders the same-height skeleton rather than nothing.
*
* One line has a width budget, and on a 375px phone it is spent by the fourth
* item (BUG-918). Every span below is always rendered; globals.css decides how
* many of them a narrow viewport shows, and it hides `__dated` there. The axis
* `aria-label` below is assembled from all five labels regardless, so nothing a
* screen reader gets depends on the viewport. Adding a sixth item means
* re-measuring that budget, not appending a span.
*/
"use client";
import type { RectificationTimelineView } from "@/lib/rectification-timeline-scale";
export function RectificationTimeline({ view }: { view: RectificationTimelineView | null }) {
if (!view) {
return <div className="rectification-timeline" data-state="pending" aria-hidden="true" />;
}
return (
<div className="rectification-timeline" data-state="ready">
<p className="rectification-timeline__readout">
<span className="rectification-timeline__range">{view.rangeLabel}</span>
<span className="rectification-timeline__width">{view.widthLabel}</span>
{view.workingLabel ? (
<span className="rectification-timeline__working">{view.workingLabel}</span>
) : null}
{view.answeredLabel ? (
<span className="rectification-timeline__answered">{view.answeredLabel}</span>
) : null}
{/* Hidden below 768px by globals.css, never omitted here: the label is
part of the accessible axis description either way. */}
{view.datedEventLabel ? (
<span className="rectification-timeline__dated">{view.datedEventLabel}</span>
) : null}
</p>
<div
className="rectification-timeline__axis"
role="img"
aria-label={[
`出生时间搜索范围 ${view.axisStartLabel} 到 ${view.axisEndLabel}`,
`当前区间 ${view.rangeLabel}`,
view.widthLabel,
view.workingLabel,
view.answeredLabel,
view.datedEventLabel,
].filter(Boolean).join(",")}
>
<span className="rectification-timeline__rule" />
{/* Placed and resized with transform only. Animating left/width would
run layout every frame, and globals.css bans transitions on width
outright (the sidebar shell animates none of its geometry). */}
<span
className="rectification-timeline__band"
style={{ transform: `translateX(${view.bandStartPercent}%) scaleX(${view.bandWidthPercent / 100})` }}
/>
{view.marks.map((mark) => (
<span
key={mark.key}
className="rectification-timeline__mark-at"
style={{ transform: `translateX(${mark.percent}%)` }}
>
<span className="rectification-timeline__mark" data-state={mark.state} />
</span>
))}
{view.ticks.map((tick) => (
<span
key={tick.key}
className="rectification-timeline__tick"
style={{ left: `${tick.percent}%` }}
>
{tick.label}
</span>
))}
</div>
</div>
);
}