feat(ui): 标题不再落宋体,强调色分阶换 coral,删输入框常驻底栏(BUG-737/738)
BUG-737:--font-display 以两个从未加载的 Anthropic 授权字体开头 (Tiempos Headline / StyreneB:无 @font-face、public/ 无字体、layout.tsx 只 vendor 了 Inter),于是每个中文标题都掉到 Songti SC / SimSun——20 条 规则宽,含助手回答正文的 h2/h3。display 与 body 合并为同一条无衬线栈, 层级改由字重承担(33 条 display 规则 400→500)。 拉丁衬线方案实测否决:Newsreader 拉丁子集 132 KB(Inter 的 2.7 倍), 只为给一个品牌字上衬线,且会把「D10 事业盘怎么读」劈成两种字形。 BUG-738:亮色 #85432f 与暗色 #d78064 不同源;--color-ring 在 @theme inline 里硬编码不跟随 :root。产品拍板换 Claude coral #cc785c, 但实测它作文字色只有 3.14:1,而 62 个调用点里 53 个是 color:。 按角色拆两阶:--color-action #a9583e(文字 4.85:1)、 --color-action-strong #cc785c(填充/导轨/焦点环,3:1 非文字阈值)。 --report-accent 刻意保持 #85432f(报告是纸面,不跟应用强调色)。 T1.3/T1.4:模型选择器进 ChatComposer 的 toolbar 插槽,删掉常驻 44px 的 .composer-footer;顶栏 68→46px,「分析对象:」改为标题旁静默 chip。 防复发:新增 font-stack-loadable-contract,断言字体栈里每个 family 要么 vendored 要么是系统字体,且 --font-display 不得触达任何 CJK 衬线。 测试 3346→3350,fail 仍 31 且清单与基线逐条一致;/ 仍 Static; CSS gzip 41,095→41,096(+0.002%);快速门 pytest 段 798 passed / 0 failed。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0193vBv6w5MV2cifdTUu9H5P
This commit is contained in:
co-authored by
Claude Opus 5
parent
3fd74daf34
commit
66d9f59d84
@@ -51,7 +51,7 @@ export function AdminApp({ children }: { children: ReactNode }) {
|
||||
borderRadiusLG: 8,
|
||||
boxShadowSecondary: "0 1px 2px rgba(29, 29, 31, .06)",
|
||||
boxShadowTertiary: "none",
|
||||
fontFamily: "StyreneB, var(--font-inter, Inter), system-ui, sans-serif",
|
||||
fontFamily: "var(--font-inter, Inter), system-ui, sans-serif",
|
||||
},
|
||||
components: {
|
||||
Layout: {
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import { useSyncExternalStore } from "react";
|
||||
import type { ChangeEvent, FormEvent, KeyboardEvent, RefObject } from "react";
|
||||
import type { ChangeEvent, FormEvent, KeyboardEvent, ReactNode, RefObject } from "react";
|
||||
import { ArrowUp, Square } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { CharacterRemaining } from "@/components/character-remaining";
|
||||
import { characterRemainingVisible } from "@/lib/character-remaining";
|
||||
import {
|
||||
composerDraftSnapshot,
|
||||
@@ -43,6 +44,12 @@ type ChatComposerProps = {
|
||||
readonly stopLabel: string;
|
||||
readonly stopTitle: string;
|
||||
readonly queued?: { text: string; onRecall: () => void };
|
||||
/**
|
||||
* Controls that belong to the composer itself (model picker, chart subject).
|
||||
* They render on the composer's own bottom row; there is no longer a strip
|
||||
* below the composer holding a permanent 44px of chrome.
|
||||
*/
|
||||
readonly toolbar?: ReactNode;
|
||||
readonly onSubmit: (event: FormEvent<HTMLFormElement>) => void;
|
||||
readonly onChange: (event: ChangeEvent<HTMLTextAreaElement>) => void;
|
||||
readonly onKeyDown: (event: KeyboardEvent<HTMLTextAreaElement>) => void;
|
||||
@@ -64,6 +71,7 @@ export function ChatComposer({
|
||||
stopLabel,
|
||||
stopTitle,
|
||||
queued,
|
||||
toolbar,
|
||||
onSubmit,
|
||||
onChange,
|
||||
onKeyDown,
|
||||
@@ -100,22 +108,30 @@ export function ChatComposer({
|
||||
onChange={onChange}
|
||||
onKeyDown={onKeyDown}
|
||||
/>
|
||||
{stopVisible ? (
|
||||
<Button
|
||||
className="composer-stop"
|
||||
aria-label={stopLabel}
|
||||
title={stopTitle}
|
||||
size="icon"
|
||||
type="button"
|
||||
onClick={onStop}
|
||||
>
|
||||
<Square aria-hidden="true" />
|
||||
</Button>
|
||||
) : (
|
||||
<Button aria-label={submitLabel} disabled={!draft.trim() || submitBlocked} size="icon" type="submit">
|
||||
<ArrowUp aria-hidden="true" />
|
||||
</Button>
|
||||
)}
|
||||
<div className="composer-row">
|
||||
{toolbar ? <div className="composer-tools">{toolbar}</div> : null}
|
||||
<div className="composer-row-end">
|
||||
{showRemaining ? (
|
||||
<CharacterRemaining id={remainingId} length={draft.length} maxLength={maxLength} />
|
||||
) : null}
|
||||
{stopVisible ? (
|
||||
<Button
|
||||
className="composer-stop"
|
||||
aria-label={stopLabel}
|
||||
title={stopTitle}
|
||||
size="icon"
|
||||
type="button"
|
||||
onClick={onStop}
|
||||
>
|
||||
<Square aria-hidden="true" />
|
||||
</Button>
|
||||
) : (
|
||||
<Button aria-label={submitLabel} disabled={!draft.trim() || submitBlocked} size="icon" type="submit">
|
||||
<ArrowUp aria-hidden="true" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { CharacterRemaining } from "@/components/character-remaining";
|
||||
import { composerRemainingId, useComposerDraft } from "@/components/chat-composer";
|
||||
|
||||
export function ComposerCharacterRemaining({ maxLength }: { maxLength: number }) {
|
||||
const draft = useComposerDraft();
|
||||
return <CharacterRemaining id={composerRemainingId} length={draft.length} maxLength={maxLength} />;
|
||||
}
|
||||
@@ -100,7 +100,6 @@ import { isPersistedFocusId, parseRectificationChoiceCard, type ChoiceKey, type
|
||||
import type { PublicLanguageModel } from "@/lib/public-models";
|
||||
import { useConversationScrollAnchor } from "@/hooks/use-conversation-scroll-anchor";
|
||||
import { useVisibilityAwarePoll } from "@/hooks/use-visibility-aware-poll";
|
||||
import { CharacterRemaining } from "./character-remaining";
|
||||
import { ChatComposer } from "./chat-composer";
|
||||
import { ConsultationTimelineLiveRow } from "./consultation-run-timeline";
|
||||
import { JumpToLatestButton } from "./jump-to-latest-button";
|
||||
@@ -1828,22 +1827,16 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
event.currentTarget.form?.requestSubmit();
|
||||
}
|
||||
}}
|
||||
toolbar={(
|
||||
<ModelSelector
|
||||
models={models}
|
||||
selectedModelId={selectedModelId}
|
||||
disabled={busy || readonly}
|
||||
onSelect={onSelectModel}
|
||||
/>
|
||||
)}
|
||||
onStop={stopRun}
|
||||
/>
|
||||
|
||||
<div className="composer-footer">
|
||||
<ModelSelector
|
||||
models={models}
|
||||
selectedModelId={selectedModelId}
|
||||
disabled={busy || readonly}
|
||||
onSelect={onSelectModel}
|
||||
/>
|
||||
<CharacterRemaining
|
||||
id={composerRemainingId}
|
||||
length={draft.length}
|
||||
maxLength={RECTIFICATION_COMPOSER_MAX_LENGTH}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<RectificationBoard
|
||||
|
||||
Reference in New Issue
Block a user