feat(ui): 空状态改成「问候 + 输入框 + 两枚入口 pill」
Independent Staging Quality Gate / validate (push) Failing after 9m13s
Independent Staging Quality Gate / publish (push) Skipped

此前空态是 hero 卡(h1 clamp 到 52px)加两张 min-height 228px 的产品卡,
输入框被压在约 800px 内容之下,主动作不是视觉焦点。

改法:问候拆成 StarterGreeting 留在对话区并底对齐,两个产品入口降级成
StarterEntries 渲染在 composer 之后。composer 本身一行未动——它在空态与
会话态是同一个 DOM 位置,首条消息落地时不重挂载,草稿/焦点/排队卡都不受影响。

顺带修掉:空态 composer 1040px、会话态 760px,发首条消息时输入框会当场
变窄一截。两态统一到 --session-column-width,并新增断言锁住。

校正入口保留三个服务端驱动的文案分支(我第一版收成了两个,是
rectification-agentic-entry 打红才发现)。

DESIGN.md:Product entrypoint card 整节重写为 Starter home;删掉
Suggestion card 死文档(它描述的主题卡样式正是本轮删除的 CSS)。

测试 3350 不变,fail 仍 31 且与基线双向零差异;/ 仍 Static;
CSS gzip 41,096→41,044(−0.13%)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0193vBv6w5MV2cifdTUu9H5P
This commit is contained in:
Jesse_Chen
2026-09-16 05:31:15 +00:00
co-authored by Claude Opus 5
parent 66d9f59d84
commit 62903b7a12
13 changed files with 486 additions and 493 deletions
+41 -18
View File
@@ -370,13 +370,6 @@ Every assistant reply moves through the same states on both chat surfaces, and e
Text release is paced, not animated: the frame buffer commits at most once per animation frame and reveals `max(2, ⌈backlog ÷ 12⌉)` characters per frame, so a burst catches up in about twelve frames and a slow model never reads as stalled. The reader's own toggle on the timeline always wins over the state default. Under `prefers-reduced-motion: reduce` the collapse and the label fade are instant; the pacing stays, because it is content arrival rather than decoration.
### Suggestion card
- **Structure:** topic label, question, directional icon. Categories are not numbered because they have no required order. At tablet widths, the cards stack into one column so Chinese questions keep natural phrase boundaries beside the persistent sidebar.
- **Surface:** warm light cards; the lead card uses the pale brown emphasis surface and border instead of a dark block.
- **States:** default, hover, active, focus, disabled, loading, fallback notice.
- **Visibility:** the initial cards, one per consultation domain, remain visible while the user types a custom question. Clicking a card starts that consultation immediately instead of filling the composer; the cards leave once the session receives its first user message.
### Personal report centre
- **Structure:** full-page archive on the page floor. A compact back control, serif page title, supporting copy, one generate action, then a list of report cards.
@@ -399,19 +392,49 @@ Text release is paced, not animated: the frame buffer commits at most once per a
- **States:** loading, generating, timed-out, unauthorized, not-found, failed, invalid, network-error, markdown-ready, legacy-unavailable. Waiting uses `InlineSpinner` and one-step copy.
- **Accessibility:** back, print, and Markdown export are 44px. Generating copy uses `role="status"`. Print remains keyboard-initiated from the chrome button. Markdown is rendered with `react-markdown` + `remark-gfm`, `skipHtml`, and no `rehype-raw`.
### Product entrypoint card
### Starter home
- **Structure:** the homepage daily-reading and birth-time cards are single native-button targets stretched across their article surface. Content remains semantic card copy. On two-column viewports a compact action label and arrow sit at the trailing edge; on a stacked homepage they sit under the supporting line, leading-aligned, so a short label like “深入看今日” does not float to the opposite corner from “开始新的生时校正”.
- **Copy:** chat history titles daily readings as “8月22日 · 今日节奏”, typed natal questions as “事业 · …”, and rectification as “8月21日 · 生时校正”. Clicking the daily card starts a consultation session immediately; it does not place the question in the composer. Private model instructions are selected by a closed entrypoint identifier and expanded only on the server. The homepage no longer shows a “从一个主题开始” grid; leftover `guided_topic` values in stored sessions still pin domains when replayed.
- **States:** default, whole-card hover, pressed, focus-visible, and disabled. The card surface—not an inner promotional button—carries the interaction feedback.
- **Responsive:** cards stack below 900px without introducing a large nested button. Stacked cards hug their copy instead of stretching to a min-height; the footer stacks supporting copy above a leading-aligned one-line action.
- **Accessibility:** each card exposes exactly one native button with a descriptive accessible name, preserves a visible focus ring, and meets the full-card touch target.
The empty state is the composer. It used to be a bordered hero card at
`clamp(34px, 5vw, 52px)` plus two 132px product cards — roughly 800px of content
sitting on top of the input, so the primary action was the last thing on the
page. Three parts now, in reading order:
### Start greeting
- **Greeting** (`.starter-greeting-block`): a tertiary-ink salutation line and
one question at `clamp(26px, 3.2vw, 34px)`, weight 500, centred. It lives in
the conversation area with `align-content: end`, so it sits directly above the
composer rather than floating in the middle of the row.
- **Composer**: unchanged, and *in the same DOM position it occupies during a
live session*. Nothing remounts when the first message lands, so an in-flight
draft, focus, or queued card survives the transition. Its width is
`--session-column-width` in both states — the empty-state composer used to be
1040px against the session's 760px, so the input visibly narrowed the moment
a conversation started.
- **Entry pills** (`.starter-entry`): the two product entrances, rendered inside
`.composer-wrap` *below* the composer. 36px pills, hairline, accent icon, one
word each. Below them one line merges the daily trend with the rectification
hint, and an accuracy-boundary line appears when no natal minute is available.
- **Content:** invite the user to ask what matters now; do not repeat that birth data is ready or explain setup state.
- **Timing:** use the browser's local hour: morning 05:0010:59, noon 11:0013:59, afternoon 14:0017:59, evening 18:0022:59, and late night 23:0004:59.
- **Variation:** each time band has three concise prompts; select one once per visit so re-renders do not change the sentence.
- **States:** default, hover, pressed, focus-visible, disabled, and `data-opening`
while a Case is being opened. Hover only moves the hairline; the surface tint
belongs to the pressed state, because a hover that shades the surface reads as
"selected" and sticks on touch. Opening a Case changes the cursor to `progress`
and nothing else — no spinner (unified-loading ruling).
- **Copy:** chat history titles daily readings as “8月22日 · 今日节奏”, typed natal
questions as “事业 · …”, and rectification as “8月21日 · 生时校正”. Clicking the
daily pill starts a consultation session immediately; it does not place the
question in the composer. Private model instructions are selected by a closed
entrypoint identifier and expanded only on the server. There is no
“从一个主题开始” grid; leftover `guided_topic` values in stored sessions still
pin domains when replayed.
- **Rectification hint** keeps three server-driven branches — an unfinished case,
a finished one that can be redone (`rectificationCardAction === "restart"`),
and a first run. The branch comes from the entry summary, never the session
list.
- **Responsive:** the pill row wraps and stays centred; there is no
phone-specific layout, because there is no fixed-height card to collapse.
- **Accessibility:** each pill is one native button with a descriptive accessible
name (the full action label, not the one-word face). The daily hint keeps
`role="status"` while the trend is still arriving.
### Account popover
@@ -483,7 +506,7 @@ Only `transform`, `opacity`, and color/filter transitions animate. Reduced-motio
## 7. Depth & Surface
Strategy: warm tonal shifts, restrained translucency, and fine hairlines. Most depth comes from the page floor, main canvas, warm cards, and selected white glass. Standard UI uses no drop shadow. Modal, popover, floating-control and raised-sheet depth use one restrained two-stage token: `--shadow-elevated: 0 1px 2px rgba(29, 29, 31, .07), 0 12px 28px -16px rgba(29, 29, 31, .18)`. A second, quieter token, `--shadow-soft: 0 1px 2px rgba(29, 29, 31, .06), 0 8px 20px -18px rgba(29, 29, 31, .2)`, belongs to surfaces inside the reading column — the conversational narrative card, the composer field within a session, the starter theme card's focus state and the auth mode tab. Floating controls and overlays (jump to latest, popovers, sheets) always take `--shadow-elevated`. Interactive rings use `0 0 0 1px` or the focus ring; no generic card shadows, no utility-class shadows, no atmospheric gradients. When transparency is reduced, frosted surfaces fall back to opaque warm neutrals.
Strategy: warm tonal shifts, restrained translucency, and fine hairlines. Most depth comes from the page floor, main canvas, warm cards, and selected white glass. Standard UI uses no drop shadow. Modal, popover, floating-control and raised-sheet depth use one restrained two-stage token: `--shadow-elevated: 0 1px 2px rgba(29, 29, 31, .07), 0 12px 28px -16px rgba(29, 29, 31, .18)`. A second, quieter token, `--shadow-soft: 0 1px 2px rgba(29, 29, 31, .06), 0 8px 20px -18px rgba(29, 29, 31, .2)`, belongs to surfaces inside the reading column — the conversational narrative card, the composer field within a session and the auth mode tab. Floating controls and overlays (jump to latest, popovers, sheets) always take `--shadow-elevated`. Interactive rings use `0 0 0 1px` or the focus ring; no generic card shadows, no utility-class shadows, no atmospheric gradients. When transparency is reduced, frosted surfaces fall back to opaque warm neutrals.
## 8. Agent wording and accuracy boundary