跳到主要内容

FinClaw V1 UI Prototype and Design System

状态:Accepted Initial Design / B-4 工程蓝图(Wave 2) 日期:2026-05-16 项目:FinClaw 文档级别:项目级前端工程契约(design tokens + 组件清单 + 关键交互原型) 上游文档:v1-ui-ux-interaction-design.md(UX 流程母本)、v1-tech-stack-and-architecture-design.md §4(前端栈锁定)、v1-api-contract-design.md §9(前端适配契约)、v1-product-object-and-schema-design.md §3 / §4 / §5 / §8 / §9(对象 schema) 配套引入:../../reference-experience/sharedocs-finclaw-ui-mvp-design.md(视觉基线)

1. Purpose

本文是 FinClaw V1 前端 sub-packet(特别是 v1-eng-impl-sub-4-refresh-diff-view / sub-5-save-thread-sheet / sub-6-feedback-review-queue直接 consume 的工程契约。

它在 v1-ui-ux-interaction-design.md 已锁定的 UX 流程 / 屏幕逻辑 之上,down-pour 三件可工程化的事:

  1. Design Tokens(颜色、字体、间距、圆角、阴影、动效)的工程化定义,与 Tailwind config + shadcn/ui CSS variables 1:1 对齐;
  2. 组件清单(基于 shadcn/ui 二次封装)+ 每个组件的 prop 边界与禁用项;
  3. 关键交互原型(ASCII / mermaid,无需 Figma),覆盖 6 条核心路径。

本文重复 UX 流程描述(→ v1-ui-ux-interaction-design.md)、重新选型前端栈(已锁定,→ v1-tech-stack-and-architecture-design.md §4)、定义 API client(→ v1-api-contract-design.md §9)。

2. Design Source and Visual Baseline

2.1 视觉基线来源

来源角色锁定状态
reference-experience/sharedocs-finclaw-ui-mvp-design.md团队成员 ShareDocs FinClaw MVP UI 设计已逐字导入治理库;本文继承其全部色彩 / 字体 / 圆角 / 间距规范作为 V1 视觉基线已锁定
reference-experience/sharedocs-finclaw-ui-demo.jpgNew Task 页面实际渲染参照视觉参照
v1-ui-ux-interaction-design.mdUX 流程 / 屏幕逻辑 / 可读性优先级已锁定

2.2 与 ShareDocs 视觉基线的边界

沿用扩展 / 修正
色板(深色主题、紫色 accent、bullish/bearish 双向色)全量沿用增补 boundary / sensitive_reject / degradation 三组语义色
字体(Inter + tabular numerics)全量沿用增补字号阶梯 token
圆角 / 间距 / 卡片密度全量沿用增补 motion / shadow tokens
信息密度(彭博终端式紧凑感)全量沿用Refresh Diff / Checkpoint UI 在 desktop 三列模式下尤其遵守
Widget × Dashboard 形态沿用概念V1 dashboard 的「认知卡片」由 SnapshotCard / ThreadCard / CheckpointCard 三套构成(§4)
ShareDocs 的「行动建议」区块修正:V1 把行动建议拆成独立 PreExecutionCheckpoint 对象,并在 UI 上明确标注「非执行边界」(§5.4)reference §10 标注 ⚠️ 项
「了解付费 / 升级」按钮引入 trial pricing intent dummy 入口必须标注「意愿调查、不扣款」(与 v1-commercial-signal-instrumentation-design.md §4.6 对齐)

2.3 不引入的视觉资产

不引入理由
K-line / 实时撮合行情图 widgetv1-tech-stack-and-architecture-design.md §4 禁止依赖;与边界冲突
Wallet connect / Web3 button 视觉资产mvp-product-definition.md §3 边界
高保真 Figma 源V1 trial 阶段以本文 ASCII 原型 + shadcn/ui 默认视觉为准;高保真 Figma 留待 trial 退出 / 公开扩展前再补(v1-ui-ux-interaction-design.md §11 Open Item)

3. Design Tokens

3.1 颜色(与 Tailwind theme.extend.colors 1:1)

// web/tailwind.config.ts (excerpt)
colors: {
bg: {
base: "#0F1119", // sharedocs §4.1 deep navy-black
surface: "#1A1D2E", // 卡片背景
surface_alt: "#13162A", // 内嵌区块(drawer / sheet inner)
overlay: "rgba(15,17,25,0.72)", // dialog backdrop
},
border: {
subtle: "#2A2D3E", // 卡片边框 / 分隔线
strong: "#3A3F55", // 聚焦 / 选中态
},
fg: {
primary: "#E8E6DF", // 主文本
secondary: "#98968E", // 次文本 / metadata
muted: "#6F6E68", // 占位符 / disabled
inverse: "#0F1119", // 在亮色 chip 上
},
accent: {
DEFAULT: "#7F77DD", // 主紫色 accent
hover: "#9089E5",
pressed: "#6E66C8",
bg_soft: "#251F4D", // 软背景
},
bull: { DEFAULT: "#1D9E75", soft: "#13352A" }, // 看多
bear: { DEFAULT: "#E24B4A", soft: "#3A1A1B" }, // 看空
warn: { DEFAULT: "#EF9F27", soft: "#352618" }, // 数据质量 / risk badge
// V1 新增的语义色:
boundary: { DEFAULT: "#4A6FE3", soft: "#1A2244" }, // 边界提示(非执行)
reject: { DEFAULT: "#C0392B", soft: "#3A1715" }, // 凭证 / 私钥拒收
degrade: { DEFAULT: "#A1887F", soft: "#2A211E" }, // degradation_notice
evidence: {
backed: "#1D9E75", // source-backed
inferred: "#7F77DD", // model-inferred
user: "#E8E6DF", // user-supplied
delayed: "#EF9F27",
unavailable: "#C0392B",
conflicting: "#E24B4A",
simulated: "#4A6FE3",
},
}

全部 evidence / quality state 颜色与 v1-ui-ux-interaction-design.md §6B Evidence Drawer badge 表 1:1 对应;前端实现 <EvidencePill status={...}> 时 token 来源能从 colors.evidence.* 取,禁止 inline 颜色。

3.2 字体

fontFamily: {
sans: ["Inter", "ui-sans-serif", "system-ui"],
mono: ["JetBrains Mono", "ui-monospace", "monospace"],
numeric: ['"Inter var"', "Inter", "system-ui"], // tabular-nums
},
fontSize: {
// (size, lineHeight, letterSpacing)
xs: ["12px", { lineHeight: "16px", letterSpacing: "0.01em" }], // metadata / badge
sm: ["13px", { lineHeight: "18px" }], // 次文本
base: ["14px", { lineHeight: "20px" }], // 正文
md: ["15px", { lineHeight: "22px" }], // 强调正文
lg: ["16px", { lineHeight: "24px" }], // section header
xl: ["18px", { lineHeight: "26px", letterSpacing: "-0.005em" }], // page header
"2xl":["20px", { lineHeight: "28px", letterSpacing: "-0.01em" }], // hero / dialog title
},
fontWeight: { regular: "400", medium: "500", semibold: "600", bold: "700" },

数字字段(confidence score、价格、token usage、change count)必须挂 font-numeric tabular-nums

3.3 间距 / 圆角 / 阴影

spacing: { ... 默认 Tailwind ..., card_pad: "16px", card_gap: "12px", section_y: "24px" },
borderRadius: { sm: "4px", md: "8px", lg: "12px", pill: "9999px" },
boxShadow: {
card: "0 1px 2px rgba(0,0,0,0.4), 0 1px 1px rgba(0,0,0,0.16)",
raised: "0 4px 12px rgba(0,0,0,0.45)",
sheet: "0 -8px 28px rgba(0,0,0,0.55)",
focus: "0 0 0 2px #7F77DD",
},
Token用途
card_pad (16px)所有 Card / Sheet 内 padding
card_gap (12px)同一 Card 内子区块间距
section_y (24px)主区块之间垂直间距
radius.lg (12px)Card / Drawer / Dialog
radius.md (8px)Button / Input / Select
radius.pillBadge / EvidencePill / chip

3.4 动效(Motion Tokens)

transitionDuration: { fast: "120ms", base: "180ms", slow: "260ms" },
transitionTimingFunction: { standard: "cubic-bezier(.2,.8,.2,1)" },

约束:

  • Refresh Diff 中的 changed inference 高亮入场用 base + standard
  • Sheet / Drawer 入场用 slow + standard
  • 任何 ≥ 400ms 的入场动效禁止(高密度信息场景中视觉延迟会被解读为系统延迟);
  • prefers-reduced-motion: reduce 时所有 transition 退化到 0ms,仅保留静态 hover / focus 样式。

3.5 Token 来源约定(守门)

  • 所有颜色 / 字号 / 间距 / 圆角 / 阴影必须通过 Tailwind utility class 引用 token,禁止 inline style={{ color: "#xxx" }}
  • 任何 hex / rgb 字面量只允许出现在 web/tailwind.config.tsweb/src/foundation/tokens.css
  • shadcn/ui CSS variables(--background / --foreground / --primary)由 tokens.css 重新映射到本文 token,禁止 shadcn/ui 默认值「直通」组件。

4. Component Inventory

V1 前端组件分 3 层:Foundation(shadcn/ui 直接复用 / 二次封装)、Domain(FinClaw 业务组件)、Page Shell(页面级布局)。

4.1 Foundation 组件(基于 shadcn/ui 二次封装)

组件shadcn 来源V1 必备 props / 边界
Buttonbuttonvariant: primary / secondary / ghost / destructive / boundarysize: sm / md / lg禁止 variant="trade" / variant="execute"
Inputinput受 SensitiveInputClassifier 包裹(§6.2)
Textareatextarea同 Input;额外 maxLength=4000(与 v1-api-contract-design.md §4.2.2 question 上限对齐)
Selectselect用于 hint_task_type / research_depth
Dialogdialog模态确认;ProfileConsentDialog 必须用此
Sheetsheet右侧 / 底部 sheet;CheckpointSheet / SaveThreadSheet 用此
Drawerdrawer与 Sheet 区别:底部抽屉 + 高度自适应;EvidenceDrawer 用此
Cardcard主信息容器
Badgebadge状态标识(active / refresh_due / paused 等)
TabstabsSnapshot 内分区 / Thread vs Snapshot 视图切换
Tooltiptooltip字段解释;hover 延迟 500ms
Toastsonner短反馈 + sensitive rejection 短文案
Separatorseparator分区分隔
SkeletonskeletonLoading 占位
ScrollAreascroll-area长 thread history / evidence 列表
SwitchswitchProfileConsent 各 scope 开关
CheckboxcheckboxPricing intent feature_kind 多选
RadioGroupradio-groupTrial cohort 单选

禁止引入的 shadcn 组件:Calendar(V1 不做日期选择)、Carousel(信息密度场景不适合)、Progress(进度条易被误读为系统延迟)。

4.2 Domain 组件(FinClaw 业务组件)

组件来源对象必备 props必备行为
EvidencePillEvidenceItem.evidence_status / DataQualityNote.quality_statestatus: enum, claimRef: string, onOpen?: () => void点击展开 EvidenceDrawer;颜色映射强制走 §3.1 evidence token
BoundaryBannerSnapshot.execution_boundary / Checkpoint.non_execution_statementkind: "execution" | "sensitive" | "degradation", message: string不可关闭;颜色 = colors.boundary
RefreshDiffPanelRefreshChangeprevious: SnapshotRef, current: SnapshotRef, change: RefreshChange, mode: "desktop3col" | "mobileTimeline"见 §5.3
AdvisorAvatarAdvisorOutput.advisor_rolerole: enum, provider: "gpt-5.5" | "kimi-k2.6" | "byom:<name>", coordinationMode?: enumprovider 标签必须 visible(与 v1-model-and-provider-policy §6.3 model_name 对齐)
SnapshotCardMarketCognitionSnapshotsnapshot: Snapshot, density: "list" | "detail"必须渲染 ui_state badge + execution_boundary
ThreadHeaderMarketCognitionThreadthread: Thread状态 bar + last_refreshed_at + refresh CTA
CheckpointSheetPreExecutionCheckpointcheckpoint: Checkpoint强制渲染 §6.1 8 步顺序;强制顶部 BoundaryBanner
ProfileConsentDialogProfileConsentcurrentConsent?: ProfileConsent, onSubmit: (req) => Promise<void>, onRevoke: (id) => Promise<void>4 类 scope switch:save_to_profile / save_to_thread / training_use_allowed / consent_for_trial_data
SensitiveInputRejectionToastSensitiveInputHandling.classification = credential_or_permissioninputSegmentIndex: number来自 HTTP 451 触发;短文案 ≤ 80 字符;不展示原文
ClarificationPromptui_state = needs_clarificationquestion: string, onSubmitAnswer / onContinueLowConfidence单一关键问题;不滚屏
WatchQuestionListThread.watch_questionsquestions: WatchQuestion[], onResolve已解决 / 新增 / 拆分 / 失效四态
InvalidatorListThread.invalidatorsinvalidators: Invalidator[]不可一键启用为 alert(边界)
DegradationNoticeBannerSSE degradation_noticekind: enum, affectedField: string, message: string持续可见直到下一次 refresh
PricingIntentCardCommercialSignalEvent pricing_intent_*features: FeatureKind[], onSubmit强制 disclaimer「意愿调查、不扣款」
FeedbackComposerFeedback request schemarelatedRef: string, onSubmit4 类 feedback_kind
KillSwitchBannerAPI 503 kill_switch_active全局顶部条;不可关闭仅 admin token 看到 reason

4.3 Page Shell

组件用途
AppShell左侧 menu (2/10) + 主内容 (8/10)(与 sharedocs §6 双栏布局一致)
LeftMenuDashboard / New Task / History / Threads
TopBar当前用户 / 通知 / 帮助;admin 模式增 Kill Switch 控件
MobileShell单列 + 底部 navigation;Drawer 从底部弹出

4.4 显式禁止存在的组件

名称禁止理由
BuySellButton / LongShortTogglev1-tech-stack-and-architecture-design.md §10.1 边界
LeverageSlider同上
WalletConnect / WalletAddressInput同上
OrderForm / PositionSizeInput同上
AutoExecuteToggle / AlertToExecutionBridge同上
KlineInteractiveTrading同上
BrokerConnectButton同上

§8 frontend lint 守门把这些字符串写入禁止清单,CI 拦截。

5. Key Interaction Prototypes

全部原型用 ASCII / mermaid。无需高保真截图(v1-ui-ux-interaction-design.md §11 Source File Strategy 已声明 V1 不强制 Figma)。

5.1 Home → Snapshot 撰写

┌─[ AppShell · Home ]──────────────────────────────────────────────┐
│ LeftMenu │ TopBar (user · notifications · help) │
│ • Dashboard ├────────────────────────────────────────────────┤
│ • New Task ◀ │ │
│ • Threads │ [ BoundaryBanner kind="execution" ] │
│ • History │ "FinClaw 输出认知与判断;不下单 / 不连账户" │
│ │ │
│ │ ┌── Recent Threads ──────────────────┐ │
│ │ │ ThreadCard · BTC 周内主判断 (refresh_due)│ │
│ │ │ ThreadCard · ETH L2 narrative (active)│ │
│ │ └─────────────────────────────────────┘ │
│ │ │
│ │ ┌── 主输入区 ────────────────────────┐ │
│ │ │ <Textarea maxLength=4000> │ │
│ │ │ 占位:"帮我分析 ETH 最近为什么弱..." │ │
│ │ │ </Textarea> │ │
│ │ │ [Select hint_task_type] │ │
│ │ │ [Pill chips: depth · risk · lang] │ │
│ │ │ [ Run FinClaw (accent) ] │ │
│ │ └─────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────┘

提交后立即进入 SSE 流(v1-api-contract-design.md §5.3):

sequenceDiagram
participant User
participant Home as Home / NewTask
participant Client as FinclawClient
participant API as POST /api/tasks
participant SSE as GET /api/tasks/{id}/stream
participant Snap as SnapshotView

User->>Home: 输入 question + 提交
Home->>Client: createTask({question, hint_task_type})
Client->>API: POST + Idempotency-Key
API-->>Client: 202 { task_id, sse_url }
Client->>SSE: subscribe
SSE-->>Home: task_started → cognition_step (task_routed)
SSE-->>Home: cognition_step (advisor_planned)
SSE-->>Home: advisor_invoked (Asset Research, gpt-5.5)
SSE-->>Home: evidence_updated × N
SSE-->>Home: snapshot_completed { snapshot_id }
Home->>Snap: navigate(/snapshots/{snapshot_id})
SSE-->>Home: task_completed

5.2 Snapshot View → Save Thread

┌─[ SnapshotView ]─────────────────────────────────────────────────┐
│ ⟵ Back Snapshot · BTC 周内主判断 [ui_state: complete] │
├──────────────────────────────────────────────────────────────────┤
│ Header: Object | Time range | DataQuality summary │
│ │
│ Main Thesis ★ EvidencePill · source-backed │
│ "BTC 周内倾向震荡偏弱,关注 60K 关口..." │
│ │
│ Supporting Reasons │
│ 1. ETF 净流入连续两日转负 [evi_xxx · live] │
│ 2. ... │
│ │
│ Counter-Thesis & Risks │
│ • 美元指数走弱可能反转 [model-inferred] │
│ │
│ Uncertainties / Watch Questions │
│ • SEC 下周决议是否影响 ETF 资金? │
│ │
│ Advisor Outputs (collapsed) │
│ AdvisorAvatar · Asset Research · gpt-5.5 │
│ AdvisorAvatar · Counter-Thesis · kimi-k2.6 │
│ │
│ ──────────────────────────────────────────────────────────────── │
│ [ Save as Thread ] [ Open Evidence Drawer ] [ Feedback ] │
└──────────────────────────────────────────────────────────────────┘

点击 Save as Thread 触发 SaveThreadSheet(右侧 Sheet 弹出):

┌─[ SaveThreadSheet ]──────────────┐
│ Title [BTC 周内主判断 ____] │
│ Object BTC │
│ User focus reason [Textarea] │
│ Initial main thesis (auto-filled) │
│ Counter / risks (auto-filled) │
│ Watch questions (editable list) │
│ Refresh conditions (editable) │
│ Invalidators (editable) │
│ │
│ Save user context? │
│ ☐ save_to_thread │
│ ☐ save_to_profile │
│ ☐ training_use_allowed │
│ (require ProfileConsent) │
│ │
│ [ Cancel ] [ Save Snapshot Only ]│
│ [ Save as Thread ] │
└───────────────────────────────────┘

若任一 save_to_* / training_use_allowed 切换为 true 且当前用户无对应 ProfileConsent,必须先弹 ProfileConsentDialog(详 §6.1);否则 API 返回 403 unauthorizedv1-api-contract-design.md §4.2.6)。

5.3 Refresh Diff Panel(desktop 三列 + mobile timeline)

Desktop:

┌─[ ThreadView · Refresh Diff ]────────────────────────────────────┐
│ ThreadHeader · BTC 周内主判断 · refreshed @ 2026-05-18 10:23 │
│ trigger: condition (ETF flow turned negative) │
│ Summary: +2 facts · 1 inference changed · 1 risk ↑ · 0 evidence │
├──────────────┬───────────────────────┬───────────────────────────┤
│ Previous │ Current │ Change Annotation │
├──────────────┼───────────────────────┼───────────────────────────┤
│ Main thesis: │ Main thesis: │ ↻ inference_changed │
│ "震荡偏弱" │ "短期承压,关注 58K" │ reason: ETF outflow ↑ │
├──────────────┼───────────────────────┼───────────────────────────┤
│ Risks: ETF │ Risks: ETF outflow │ ↑ risk_intensified │
│ flow neutral │ confirmed │ │
├──────────────┼───────────────────────┼───────────────────────────┤
│ Watch Qs: │ Watch Qs: │ + new question added │
│ • SEC 决议 │ • SEC 决议 │ • 矿工卖压是否回升? │
│ │ • 矿工卖压是否回升? │ │
└──────────────┴───────────────────────┴───────────────────────────┘

Mobile(单列时间线,每变化一卡片):

┌─[ Refresh Diff · timeline ]──┐
│ ↻ inference_changed │
│ "短期承压,关注 58K" │
│ reason: ETF outflow ↑ │
├──────────────────────────────┤
│ ↑ risk_intensified │
│ ETF outflow confirmed │
├──────────────────────────────┤
│ + watch_question added │
│ "矿工卖压是否回升?" │
└──────────────────────────────┘

无实质变化时 RefreshDiffPanel 必须渲染:

┌──────────────────────────────────────────┐
│ ✓ 本次刷新未发现实质性变化 │
│ 原因:来源未更新 / 无新事件 / 原判断维持 │
│ [ Open Evidence Drawer ] [ Re-run later ]│
└──────────────────────────────────────────┘

(与 v1-ui-ux-interaction-design.md §6A 「无实质变化」要求一致;前端应在零变化时生成新文案。)

5.4 Pre-Execution Checkpoint

行动邻近问题命中后 SSE checkpoint_completed 触发,导航到 CheckpointSheet:

┌─[ CheckpointSheet ]──────────────────────────────────────────────┐
│ [ BoundaryBanner kind="execution" ] │
│ "你提出的是行动邻近问题。FinClaw 不下单 / 不连账户 / 不执行。" │
├──────────────────────────────────────────────────────────────────┤
│ 1. 降级后的认知任务 │
│ "评估 BTC 短线突破 60K 后的持续性,识别条件与失效信号" │
│ │
│ 2. 条件化策略假设 │
│ "若 ETF 资金日净流入 > $200M 持续 ≥ 2d,则主线倾向延续" │
│ │
│ 3. 必须确认的前提(user_confirmation_needed) │
│ ☐ ETF 数据是否覆盖到最新交易日 │
│ ☐ 主导市场情绪是否已反映在期货持仓 │
│ │
│ 4. 风险约束(risk_constraints) │
│ • 宏观突发事件可一夜失效 │
│ │
│ 5. 失效条件(invalidators) │
│ • ETF 净流入翻负且持续 1d │
│ │
│ 6. 数据质量与缺口(data_quality_notes) │
│ • 链上数据 delayed 6h │
│ │
│ 7. 非可执行边界(non_execution_statement) │
│ "本 Checkpoint 不构成下单 / 仓位 / 杠杆指令;不可被外部交易系统直接消费。"│
│ │
│ forbidden_execution_fields (debug only): [] │
└──────────────────────────────────────────────────────────────────┘

禁止渲染:buy / sell button、position size input、leverage control、wallet field、auto execute toggle(v1-ui-ux-interaction-design.md §7 禁用清单)。

5.5 Boundary Rejection Flow(sensitive_input_rejected)

用户在主输入框输入疑似私钥 / API key:

sequenceDiagram
participant User
participant Home
participant Client as FinclawClient
participant API as POST /api/tasks
participant Toast as SensitiveInputRejectionToast

User->>Home: 输入 "我的 Binance API key 是 sk_live_..."
Home->>Client: createTask({question})
Client->>API: POST
API-->>Client: 451 sensitive_input_rejected
Client->>Toast: show({inputSegmentIndex})
Note over Toast: "FinClaw 不接受凭证、私钥、交易所 API Key 或助记词。"
Toast->>Home: 自动清空疑似命中的 input segment

Toast 文案:

SensitiveInputRejectionToast (≤ 80 chars):
"FinClaw 不接受凭证、私钥、交易所 API Key 或助记词。已拒收并清除该段。"
[ 了解为什么 ] ← 弹小 popover 解释,不展示原文

5.6 ProfileConsent 引导

首次触发任何「保存」类操作或商业信号采集前:

┌─[ ProfileConsentDialog ]─────────────────────────────────────────┐
│ FinClaw 想为你保存什么? │
│ │
│ ☐ save_to_thread 把你的关注理由 / 持仓上下文存到当前线程 │
│ ☐ save_to_profile 跨线程长期记住你的偏好(语言 / 深度 / 风险)│
│ ☐ training_use_allowed 允许去识别后用于改进 FinClaw(V1 不进入实际训练)│
│ ☐ consent_for_trial_data 允许采集行为信号供 trial 评测使用 │
│ │
│ • 你随时可以撤回;撤回后 48 小时内删除商业信号事件。 │
│ • 凭证 / 私钥 / 助记词永远不被保存(与本同意书无关,硬约束)。 │
│ • 详细见隐私边界。 │
│ │
│ [ 跳过 ] [ 确认(生效本次 + 后续) ] │
└──────────────────────────────────────────────────────────────────┘

Dialog 退出条件:用户提交(生成 csn_<user_id>_v<version>v1-data-and-persistence-design.md §5.1)或显式跳过。跳过状态下:

  • save_* 都为 false → 不创建 saved-context;
  • consent_for_trial_data 为 false → 后端 POST /api/events/cs 收到 accepted: falsev1-api-contract-design.md §4.2.16);
  • 用户仍可使用全部认知功能。

6. Boundary and Rejection UX Patterns

6.1 Pattern:Execution Boundary(持续可见)

出现位置组件文案锚点
Home 顶部BoundaryBanner kind="execution""FinClaw 输出认知与判断;不下单 / 不连账户。"
SnapshotView 底部<p class="text-fg-muted">snapshot.execution_boundary</p>来自 schema
CheckpointSheet 顶部BoundaryBanner kind="execution" + non_execution_statement强约束
任何 pricing_intent_* 卡片inline <small>"意愿调查,不会扣款"

颜色统一 colors.boundary(深蓝),与 accent(紫色 CTA)刻意区分,避免 boundary 提示被误读为 CTA。

6.2 Pattern:Sensitive Input

Input 组件包装层(client 端预检):
┌──────────────────────────────────┐
│ <Textarea> ◀ wrapped by │
│ <SensitiveInputClassifierClient/> │
└──────────────────────────────────┘
↓ 用户键入
↓ debounce 300ms
↓ regex 预检:API key / private key / seed phrase 模式
↓ 命中 → 不阻塞输入,但触发 inline warning:
"检测到疑似凭证;FinClaw 不会保存或使用这一段。"
↓ 用户提交 → backend Sensitive Input Classifier 终判
↓ 后端命中 → HTTP 451 → SensitiveInputRejectionToast
Sensitive classUX 强度
ordinary_preference无提示
financial_contextinline hint:「将仅用于本次任务,保存前会确认」
sensitive_personal_financialinline warning:「不会保存到 Profile,除非你显式同意」
credential_or_permissioninline warning + 提交时 toast 拒收

前端做最终判定(仅 UX hint);最终拒收由后端 SensitiveInputClassifier 与 BoundaryGuard 决定(v1-tech-stack-and-architecture-design.md §6.1)。

6.3 Pattern:Degradation Notice

SSE degradation_notice 触发 DegradationNoticeBanner

┌─ DegradationNoticeBanner ────────────────────────────────────┐
│ ⚠ 部分输出已降级 │
│ kind: missing_advisor_view · affected: counter_thesis │
│ "本次未能获得 Counter-Thesis Advisor 视角;下次刷新会重试。" │
└───────────────────────────────────────────────────────────────┘

颜色 colors.degrade(中性褐色),不使用 warn 色(避免与数据质量 warning 混淆)。

6.4 Pattern:Kill Switch

API 任意端点返回 503 kill_switch_activev1-api-contract-design.md §7.2):

┌─ KillSwitchBanner (顶部全宽,sticky) ──────────────────────┐
│ ⛔ FinClaw trial 已暂停 │
│ 现有 thread 与 snapshot 仍可阅读;新 task / refresh 已停用。│
│ 如有疑问请联系项目发起人。 │
└─────────────────────────────────────────────────────────────┘

非 admin 用户看到 reason(reason 仅 admin 可见,与 v1-api-contract-design.md §4.2.17 对齐)。

7. Accessibility Baseline

V1 不通过完整 WCAG 2.2 AA 认证(trial 内测无外部审计),但实施以下基线:

要求实施
颜色对比度主文本 / 主背景 ≥ 4.5:1;次文本 ≥ 3:1fg.primary #E8E6DF on bg.base #0F1119 ≈ 12.4:1 ✅;fg.secondary on bg.surface ≈ 4.7:1 ✅
不依赖颜色单一信号bullish/bearish 必须同时用图标(↑/↓)+ 文本("+2.3%"),不仅靠颜色<TrendBadge> 组件强制三要素
键盘可达所有 Button / Input / Sheet / Dialog 全键盘可操作;Tab 顺序可预测shadcn/ui radix primitives 默认满足;本文不重写
Focus visible全部 focusable 元素显示 boxShadow.focus(紫色 2px 描边)tokens.css:focus-visible 全局规则
屏幕阅读器所有 EvidencePill / BoundaryBanner / DegradationNoticeBanner 必须有 aria-labelaria-describedby组件内置;prop aria-label 必填
Motion尊重 prefers-reduced-motion(§3.4)全局 CSS 守门
字号用户浏览器缩放至 200% 后主流程不破版layout 用 rem 而非 pxtokens.csshtml { font-size: 16px } 仅作 base
中英文混排中英标点 / 数字 tabular-nums 兼容font-numeric token

V1 阶段承诺:屏幕阅读器完整 narration、高对比度模式、自定义字号 UI、多语言切换(trial 仅中英)。

8. Frontend Lint Guardrails

8.1 与 Wave 1 一致性约束

本节与 v1-tech-stack-and-architecture-design.md §10.1 边界硬约束 + v1-api-contract-design.md §9.6 Frontend lint 守门 完全一致。本文不重新讨论清单,只负责前端代码层落地。

8.2 禁止字符串清单(CI lint 拦截)

禁止字符串出现处例外
BuySellButton / LongShortToggle / LeverageSlider / WalletConnect / OrderForm / PositionSizeInput / AutoExecuteToggle / BrokerConnectButton任何 web/src/**/*.{ts,tsx}
order_side / order_type / quantity / leverage / wallet_address / private_key / seed_phrase / auto_execute / alert_trigger_to_execute任何 web/src/**/*.{ts,tsx}web/src/api/types.ts 中作为 forbidden_execution_fields 字段值的字符串字面量(受 v1-api-contract-design §4.2.12 Pydantic schema 约束)
inline style={{ color: "#...任何 web/src/**web/tailwind.config.tsweb/src/foundation/tokens.css

8.3 Lint 实现

// web/.eslintrc.cjs (excerpt)
module.exports = {
rules: {
"no-restricted-syntax": [
"error",
{
selector: "Identifier[name=/^(BuySellButton|LongShortToggle|LeverageSlider|WalletConnect|OrderForm|PositionSizeInput|AutoExecuteToggle|BrokerConnectButton)$/]",
message: "FinClaw V1 禁止此组件名(边界硬约束)。见 v1-ui-prototype-and-design-system §8。",
},
],
},
};

并在 web/scripts/check-forbidden-strings.mjs 中跑一遍 grep(兜底捕获 ESLint AST 漏掉的字符串字面量),通过 package.json "lint:strings" 接入 pnpm lint

CI workflow(.github/workflows/ci.ymllint job 任一拦截失败 = PR 阻断。

8.4 守门测试

web/src/__tests__/forbidden-strings.test.ts

import { execSync } from "node:child_process";
test("no forbidden execution-control strings in source", () => {
const out = execSync("node scripts/check-forbidden-strings.mjs", { encoding: "utf8" });
expect(out).toMatch(/0 violations/);
});

9. Component Library Folder Layout

v1-tech-stack-and-architecture-design.md §7 Repository Skeleton web/src/ 骨架对齐,本文细化组件库布局:

web/src/
├── foundation/ ← Design tokens + 工具
│ ├── tokens.css ← :root CSS variables (映射到 §3 token)
│ ├── theme.ts ← 主题切换(V1 仅深色)
│ ├── motion.ts ← framer-motion 预设(与 §3.4 对齐)
│ └── icons.ts ← lucide-react re-exports

├── components/ ← Foundation 组件(shadcn/ui 二次封装)
│ ├── ui/ ← shadcn 生成的原始组件(极少改动)
│ │ ├── button.tsx
│ │ ├── input.tsx
│ │ ├── dialog.tsx
│ │ ├── sheet.tsx
│ │ ├── drawer.tsx
│ │ ├── card.tsx
│ │ ├── badge.tsx
│ │ ├── tabs.tsx
│ │ ├── tooltip.tsx
│ │ ├── select.tsx
│ │ ├── switch.tsx
│ │ ├── checkbox.tsx
│ │ ├── radio-group.tsx
│ │ ├── separator.tsx
│ │ ├── skeleton.tsx
│ │ └── scroll-area.tsx
│ ├── Toast.tsx ← sonner 包装
│ └── index.ts ← barrel export

├── domains/ ← 业务域切片(与 B-1 §7 对齐)
│ ├── analysis/ ← Snapshot / Checkpoint 阅读层
│ │ ├── components/
│ │ │ ├── SnapshotCard.tsx
│ │ │ ├── EvidencePill.tsx
│ │ │ ├── BoundaryBanner.tsx
│ │ │ ├── AdvisorAvatar.tsx
│ │ │ ├── DegradationNoticeBanner.tsx
│ │ │ └── ClarificationPrompt.tsx
│ │ └── pages/
│ │ └── SnapshotView.tsx
│ │
│ ├── cognition/ ← Thread / Refresh
│ │ ├── components/
│ │ │ ├── ThreadHeader.tsx
│ │ │ ├── ThreadCard.tsx
│ │ │ ├── RefreshDiffPanel.tsx
│ │ │ ├── WatchQuestionList.tsx
│ │ │ ├── InvalidatorList.tsx
│ │ │ └── SaveThreadSheet.tsx
│ │ └── pages/
│ │ ├── ThreadView.tsx
│ │ └── ThreadList.tsx
│ │
│ ├── checkpoint/
│ │ ├── components/CheckpointSheet.tsx
│ │ └── pages/CheckpointView.tsx
│ │
│ ├── onboarding/
│ │ └── components/ProfileConsentDialog.tsx
│ │
│ ├── sensitive/
│ │ ├── components/SensitiveInputClassifierClient.tsx
│ │ └── components/SensitiveInputRejectionToast.tsx
│ │
│ ├── feedback/
│ │ ├── components/FeedbackComposer.tsx
│ │ └── pages/FeedbackQueue.tsx (admin only)
│ │
│ ├── pricing/
│ │ └── components/PricingIntentCard.tsx
│ │
│ └── task/ ← New Task 入口与 SSE 编排
│ ├── components/
│ │ ├── TaskInputComposer.tsx
│ │ └── TaskStreamView.tsx
│ └── pages/HomePage.tsx

├── layout/ ← Page Shell
│ ├── AppShell.tsx
│ ├── LeftMenu.tsx
│ ├── TopBar.tsx
│ ├── KillSwitchBanner.tsx
│ └── MobileShell.tsx

├── api/ ← REST + SSE client(B-3 §9)
│ ├── client.ts
│ ├── sse.ts
│ ├── types.ts ← zod schemas, forbidden_execution_fields 字面量许可
│ └── errors.ts

├── lib/ ← utils / hooks
│ ├── hooks/
│ │ ├── useSnapshot.ts
│ │ ├── useThread.ts
│ │ ├── useTaskStream.ts
│ │ └── useProfileConsent.ts
│ └── utils/
│ ├── classify-sensitive.ts ← client 端 regex 预检
│ ├── token-est.ts ← 与 B-7 §10 cost telemetry 对齐
│ └── analytics.ts ← reportEvent 包装

├── pages/ ← 路由根节点(React Router 6)
│ ├── home/
│ ├── analysis/
│ ├── cognition/
│ └── dashboard/

└── __tests__/
├── forbidden-strings.test.ts
└── tokens.test.ts ← 验证 §3 tokens 与 tailwind config 一致

9.1 边界约定

  • components/ui/* 导入 domains/*
  • domains/<X>/* 导入 components/ui/* + foundation/* + api/* + lib/*,但不能跨 domain 导入彼此的 components/;跨 domain 共享需上提到 components/lib/
  • layout/* 可导入任何层;
  • api/* 不导入任何 React 组件(保持 framework-agnostic,便于 Vitest 单测)。

9.2 命名约定

文件命名
组件PascalCase.tsx
HookuseXxx.ts
utilkebab-case.ts
zod schema<Object>Schema (e.g. SnapshotSchema)

10. Acceptance

本文满足 V1 工程化 B-4 任务的接收条件:

状态
视觉基线明确指向 reference-experience/sharedocs-finclaw-ui-mvp-design.md §4 + §7(§2)
Design tokens(color / typography / spacing / radius / shadow / motion)工程化定义,与 Tailwind config 1:1(§3)
组件清单覆盖 Foundation(≥ 17 个 shadcn 包装)+ Domain(≥ 16 个业务组件)+ Page Shell(5 个)+ 显式禁止清单(§4)
关键交互原型覆盖 Home → Snapshot → Save Thread → Refresh Diff → Pre-Execution Checkpoint → Boundary Rejection → ProfileConsent 7 条路径,全部 ASCII / mermaid(§5 + §6)
Boundary / Sensitive / Degradation / Kill Switch UX pattern 与 v1-ui-ux-interaction-design.md §7 / §8 / §10 对齐(§6)
Accessibility 基线 7 项可工程化检查(§7)
Frontend lint 守门清单与 v1-tech-stack-and-architecture-design.md §10.1 + v1-api-contract-design.md §9.6 完全一致(§8)
组件库目录与 v1-tech-stack-and-architecture-design.md §7 web/src/ 骨架对齐 + 边界约定(§9)
YAML frontmatter + section_navigation + 跨文档相对路径精确到 §
全文不出现订单 / 仓位 / 私钥 / 钱包字段(仅作为禁止清单出现)

11. Open Items

  • O-1:高保真 Figma 源是否在 trial 退出前补齐 — V1 trial 期间以本文 ASCII 原型为准(与 v1-ui-ux-interaction-design.md §11 Open Item 一致);trial 退出 / 公开扩展前与项目发起人对齐;
  • O-2:浏览器可运行原型(Storybook 或 Ladle)是否在 V1 落地 — 本 Wave 不强制;如 sub-4 / sub-5 / sub-6 owner 选择 Storybook 接入,需补一行 pnpm storybookweb/package.json scripts,但写入 trial-prod docker image;
  • O-3:色板是否在 trial 期收集到对比度 / 可读性的负反馈 — 由 v1-trial-sub-2-trial-execution 收集;如 D7 内出现 ≥ 1 条对比度抱怨,由 PM 决议是否调整;
  • O-4:MobileShell 在 ≤ 360px 屏宽下的折叠策略尚未压力测试 — 由 sub-4 / sub-5 实施时跑 pnpm vitest --run mobile-layout 验证;
  • O-5:是否在 V1 引入 @storybook/test-runner / Playwright 视觉回归 — 推迟到 trial 退出前评估。