From 48f8d01c094ae7f5f42ada0c1cfa710a65332a3a Mon Sep 17 00:00:00 2001 From: Chenjie <131379140+cl-fi@users.noreply.github.com> Date: Thu, 23 Jul 2026 17:31:54 +0800 Subject: [PATCH 1/3] chore: clean up project structure and remove unused design-sync files - Removed obsolete design-sync files including build scripts, configuration, and documentation to streamline the project. - Updated .gitignore to exclude transient build artifacts and unnecessary directories. - Cleaned up package.json and package-lock.json by removing deprecated Storybook dependencies. - Added a new skill entry in skills-lock.json for batch-grill-me to enhance project capabilities. --- .design-sync/NOTES.md | 73 - .design-sync/build-ds-pkg.mjs | 62 - .design-sync/config.json | 11 - .design-sync/conventions.md | 70 - .design-sync/tsconfig.dspkg.json | 27 - .gitignore | 8 - .storybook/main.ts | 38 - .storybook/preview-fonts.css | 46 - .storybook/preview.ts | 15 - docs/agent-native-product-architecture.md | 520 ----- docs/anker-agent-native-roadmap.md | 837 -------- docs/design-system-audit.md | 142 -- package-lock.json | 2154 ++------------------- package.json | 6 +- public/enoki/favicon.svg | 13 - public/enoki/icon-badge.png | Bin 3057 -> 0 bytes public/enoki/icon-badge.svg | 8 - public/enoki/icon.svg | 5 - public/enoki/logo.png | Bin 10063 -> 0 bytes public/enoki/logo.svg | 17 - public/enoki/open-graph.webp | Bin 22678 -> 0 bytes public/enoki/webclip.svg | 13 - scripts/quality-gates.test.mjs | 2 +- skills-lock.json | 8 +- src/components/HomePage.test.tsx | 34 - src/components/HomePage.tsx | 157 -- src/components/PayoffChart.tsx | 21 - src/components/brandMarks.tsx | 5 +- src/hooks/useCurrentUsdsuiApr.ts | 12 - src/hooks/useStructuredQuote.test.ts | 13 - src/hooks/useStructuredQuote.ts | 43 - src/i18n/copy.ts | 104 - src/mobile.css | 25 - src/products/timeFormat.test.ts | 18 - src/products/timeFormat.ts | 7 - src/styles.css | 539 +----- src/ui/Badge.stories.tsx | 17 - src/ui/Button.stories.tsx | 20 - src/ui/Card.stories.tsx | 21 - src/ui/Dialog.stories.tsx | 24 - src/ui/Disclosure.stories.tsx | 25 - src/ui/Disclosure.tsx | 26 - src/ui/Field.stories.tsx | 26 - src/ui/KeyValue.stories.tsx | 24 - src/ui/MobileActionDock.tsx | 74 - src/ui/MobileResponsive.test.tsx | 90 +- src/ui/Stat.stories.tsx | 23 - src/ui/Tabs.stories.tsx | 19 - src/ui/index.ts | 4 - 49 files changed, 208 insertions(+), 5238 deletions(-) delete mode 100644 .design-sync/NOTES.md delete mode 100644 .design-sync/build-ds-pkg.mjs delete mode 100644 .design-sync/config.json delete mode 100644 .design-sync/conventions.md delete mode 100644 .design-sync/tsconfig.dspkg.json delete mode 100644 .storybook/main.ts delete mode 100644 .storybook/preview-fonts.css delete mode 100644 .storybook/preview.ts delete mode 100644 docs/agent-native-product-architecture.md delete mode 100644 docs/anker-agent-native-roadmap.md delete mode 100644 docs/design-system-audit.md delete mode 100644 public/enoki/favicon.svg delete mode 100644 public/enoki/icon-badge.png delete mode 100644 public/enoki/icon-badge.svg delete mode 100644 public/enoki/icon.svg delete mode 100644 public/enoki/logo.png delete mode 100644 public/enoki/logo.svg delete mode 100644 public/enoki/open-graph.webp delete mode 100644 public/enoki/webclip.svg delete mode 100644 src/components/HomePage.test.tsx delete mode 100644 src/components/HomePage.tsx delete mode 100644 src/components/PayoffChart.tsx delete mode 100644 src/hooks/useCurrentUsdsuiApr.ts delete mode 100644 src/hooks/useStructuredQuote.test.ts delete mode 100644 src/hooks/useStructuredQuote.ts delete mode 100644 src/products/timeFormat.test.ts delete mode 100644 src/products/timeFormat.ts delete mode 100644 src/ui/Badge.stories.tsx delete mode 100644 src/ui/Button.stories.tsx delete mode 100644 src/ui/Card.stories.tsx delete mode 100644 src/ui/Dialog.stories.tsx delete mode 100644 src/ui/Disclosure.stories.tsx delete mode 100644 src/ui/Disclosure.tsx delete mode 100644 src/ui/Field.stories.tsx delete mode 100644 src/ui/KeyValue.stories.tsx delete mode 100644 src/ui/MobileActionDock.tsx delete mode 100644 src/ui/Stat.stories.tsx delete mode 100644 src/ui/Tabs.stories.tsx diff --git a/.design-sync/NOTES.md b/.design-sync/NOTES.md deleted file mode 100644 index 03c06a0..0000000 --- a/.design-sync/NOTES.md +++ /dev/null @@ -1,73 +0,0 @@ -# design-sync notes — Anker Protocol Design System - -Project: `1802f7a4-b9fe-4e53-9b29-2a053beb279f` (claude.ai/design) -Shape: **storybook** (`.storybook/` at repo root, `@storybook/react-vite`) -Components: 8 primitives in `src/ui/` — Button, Card, Badge, Stat, KeyValue, Disclosure, InputField, Tabs. - -## How this repo is made syncable - -This is a **Next.js app**, not a published component library — the primitives live as -TS source in `src/ui/` with **no build that emits declarations**. The converter discovers -components (and their prop types) from a package's shipped `.d.ts`. So we generate one: - -- `[GENERAL]` `cfg.buildCmd = "node .design-sync/build-ds-pkg.mjs"` compiles `src/ui/` into a - self-contained package at **`dist/ui/`** (JS + `.d.ts` + `package.json` name `@anker/ui`) - via `tsc -p .design-sync/tsconfig.dspkg.json` (jsx: react-jsx, declaration: true). - `dist/` is gitignored, so the output is transient and regenerated on every sync. - **No repo source files are modified** — the generated package is the only build artifact. -- `cfg.entry = "dist/ui/index.js"` → PKG_DIR becomes `dist/ui`, whose package.json supplies - name/version/types. This is what lets `exportedNames()` find the 11 PascalCase exports - (Button, Card, Badge, Stat, StatGroup, KeyValue, KeyValueList, Disclosure, InputField, - Tabs, Tab) so the 8 storybook titles map to public exports. -- Story files import components via **relative** paths (`./Button`); the preview redirect - (story-imports rule 2) keys off the exported NAME set, not PKG_DIR, so previews correctly - render `window.AnkerUI.*` regardless of where PKG_DIR points. -- Story titles (`Primitives/Button`, …) match export names 1:1 → no `titleMap` needed. -- `KeyValueList`, `StatGroup`, `Tab` are exported (on the global, usable by the design agent) - but have no stories of their own → no cards; they appear inside KeyValue/Stat/Tabs stories. - -## Fonts - -- `Geist` + `GeistMono` ship as local woff2 (`fonts/`), scraped from sb-reference. -- `Fredoka` (the brand **display** font, `--font-display`) is **self-hosted** (woff2 in - `src/fonts/Fredoka-latin*.woff2`). `[GENERAL]` `.storybook/preview-fonts.css` was changed - from a Google Fonts `@import` to local `@font-face` rules (latin + latin-ext, with - unicode-range) so the converter ships the font FILES — Claude Design flagged the remote - `@import` as a "Missing brand font" (it wants uploaded files, not a runtime font host). - Verified Fredoka loads with external network blocked - (`document.fonts.check('700 16px Fredoka') === true`). No more `[FONT_REMOTE]`. - -## CSS - -- The DS styling lives in one large `src/styles.css` (4280 lines) + `tokens.css` + `button.css` - + `badge.css`, all `@import`ed by `app/globals.css`, which `.storybook/preview.ts` imports. - Converter scrapes the compiled closure from sb-reference (`[CSS_FROM_STORYBOOK]`). -- `[CSS_ASSETS]` warns that `../anker-logo.png` and `#returnPathFade` url()/fragment refs in - the scraped CSS won't resolve post-upload. None of the 8 primitives use these, so it's - cosmetic for unrelated app classes carried along in the big stylesheet. - -## First sync result (2026-06-23) - -- 8/8 components graded **match** (every story), validate exits clean, driver verdict - `ok: true` with `pendingGrade: []`. All carried forward on the receipt build (0 cleared). -- No owned previews, no `provider`, no `titleMap`, no `overrides` — the defaults fit once - the generated `dist/ui` package supplies declarations. - -## Re-sync risks (watch-list for the next run) - -- **Fredoka is self-hosted** (`src/fonts/Fredoka-latin*.woff2`, wired in - `.storybook/preview-fonts.css`). No network dependency. If Fredoka ever regresses, check - those woff2 files exist and the `@font-face` url()s in `preview-fonts.css` still resolve. - The files cover latin + latin-ext only (the DS text); add more Google subsets if a design - needs other scripts. -- **Component discovery = exports of `src/ui/index.ts`.** A new primitive that isn't re-exported - there won't be discovered (and won't get a `dist/ui` declaration). Keep `index.ts` current. - After re-sync, sanity-check the build log shows `[DTS] parsed 9 .d.ts` and `components: 8` - (or the new count). -- **`dist/ui` is generated + gitignored.** Fresh clone needs: `.ds-sync` deps + chromium, - `.design-sync/sb-reference` rebuilt, and `buildCmd` (the driver runs it). `tsc` uses - `noEmitOnError:false`, so a type error in `src/ui` yields partial `.d.ts` silently — if a - component's prop card goes empty, check `dist/ui/.d.ts`. -- **Sub-components without stories** (StatGroup, KeyValueList, Tab) ship on the global but have - no cards; they're only verified indirectly through their parent stories. -- **Story content is static** (no `new Date()`/random), so grades are stable across captures. diff --git a/.design-sync/build-ds-pkg.mjs b/.design-sync/build-ds-pkg.mjs deleted file mode 100644 index 7536cb6..0000000 --- a/.design-sync/build-ds-pkg.mjs +++ /dev/null @@ -1,62 +0,0 @@ -// design-sync buildCmd: compile the src/ui design-system primitives into a -// self-contained package at dist/ui/ (JS + .d.ts + package.json). -// -// Why this exists: this repo is a Next.js app, not a published component -// library — the primitives live as TS source in src/ui with no build that -// emits declarations. The design-sync converter discovers components (and -// their prop types) from a package's shipped .d.ts. This script produces that -// package deterministically so the converter has real declarations to read, -// WITHOUT modifying any of the repo's own source files. dist/ is gitignored, -// so the output is transient and regenerated on every sync. -// -// The converter is pointed at dist/ui via cfg.entry = "dist/ui/index.js" -// (--entry), which makes dist/ui the package root: package.json below supplies -// the name/version/types the converter reads. - -import { spawnSync } from 'node:child_process'; -import { existsSync, mkdirSync, writeFileSync } from 'node:fs'; -import { dirname, join, resolve } from 'node:path'; -import { fileURLToPath } from 'node:url'; - -const here = dirname(fileURLToPath(import.meta.url)); -const repoRoot = resolve(here, '..'); -const tsconfig = join(here, 'tsconfig.dspkg.json'); -const outDir = join(repoRoot, 'dist', 'ui'); - -console.error(`[build-ds-pkg] tsc -p ${tsconfig} → ${outDir}`); -const r = spawnSync('npx', ['tsc', '-p', tsconfig], { - cwd: repoRoot, - stdio: ['ignore', 'inherit', 'inherit'], - encoding: 'utf8', - shell: process.platform === 'win32', -}); -// noEmitOnError is false, so tsc emits even with type-noise; only a hard -// launch failure or missing output is fatal. -if (r.error) { - console.error(`[build-ds-pkg] failed to launch tsc: ${r.error.message}`); - process.exit(1); -} -if (!existsSync(join(outDir, 'index.js')) || !existsSync(join(outDir, 'index.d.ts'))) { - console.error('[build-ds-pkg] tsc did not emit dist/ui/index.{js,d.ts} — aborting'); - process.exit(1); -} - -mkdirSync(outDir, { recursive: true }); -writeFileSync( - join(outDir, 'package.json'), - JSON.stringify( - { - name: '@anker/ui', - version: '0.1.0', - description: 'Anker Protocol design-system primitives (generated for design-sync).', - type: 'module', - main: 'index.js', - module: 'index.js', - types: 'index.d.ts', - peerDependencies: { react: '>=18', 'react-dom': '>=18' }, - }, - null, - 2, - ) + '\n', -); -console.error('[build-ds-pkg] wrote dist/ui/package.json'); diff --git a/.design-sync/config.json b/.design-sync/config.json deleted file mode 100644 index 8a4264e..0000000 --- a/.design-sync/config.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "projectId": "1802f7a4-b9fe-4e53-9b29-2a053beb279f", - "shape": "storybook", - "storybookConfigDir": ".storybook", - "storybookStatic": ".design-sync/sb-reference", - "pkg": "@anker/ui", - "globalName": "AnkerUI", - "entry": "dist/ui/index.js", - "buildCmd": "node .design-sync/build-ds-pkg.mjs", - "readmeHeader": ".design-sync/conventions.md" -} diff --git a/.design-sync/conventions.md b/.design-sync/conventions.md deleted file mode 100644 index 202ebb9..0000000 --- a/.design-sync/conventions.md +++ /dev/null @@ -1,70 +0,0 @@ -## Anker UI — how to build with these components - -Anker's "sticker" design system: chunky navy outlines, hard offset shadows, a -cream canvas, a gold accent, and the rounded **Fredoka** display font. Build with -the real components below; do your own layout with the CSS variables (tokens) the -system already defines. There is **no Tailwind / utility-class system** here — and -no theme provider. - -### Setup & wrapping -- **No provider, no context, no client-only setup.** Every component is a pure - presentational primitive (props in → markup out). Render them directly. -- Styling comes entirely from the bound `styles.css` closure (tokens + component - CSS) plus fonts. Fonts are wired for you and all ship as local `@font-face` - files (`Geist`, `GeistMono`, and the `Fredoka` display font). Just use the - components and the tokens. - -### Styling idiom — props, then tokens (never utility classes) -- **Style components through their PROPS**, not classes: `variant`, `tone`, `size`, - `label`, `suffix`, `as`. The look is fixed by the design system. -- **For your own layout/containers, use the design tokens** via `var(--*)` — do not - invent colors, radii, or shadows. The vocabulary: - - Color: `--cream` (page bg), `--paper` (raised surface), `--navy` (ink/border), - `--gold` (accent), `--grass` (positive), `--coral` (danger), `--ink`, - `--ink-soft` (muted). Tints: `--gold-tint`, `--grass-tint`, `--coral-tint`. - - Semantic aliases: `--color-accent`, `--color-on-accent`, `--surface-raised`, - `--surface-accent-soft`, `--border-strong`. - - Spacing (4px scale): `--space-1`…`--space-6`, `--space-8`. - - Type: `--font-display` (Fredoka, for headings/labels), `--font-sans` (Geist, - body), `--font-mono` (GeistMono, numbers/addresses). - - "Sticker" chrome: `--bw` (2px border), `--bw-thick` (3px), `--r` (16px radius), - `--r-sm` (12px), `--shadow-hard` (`4px 4px 0 var(--navy)`), `--shadow-hard-sm`, - `--focus-ring`. -- Each component also exports a `*ClassName()` helper (`buttonClassName`, - `cardClassName`, `badgeClassName`, `tabClassName`) so a non-component element - (e.g. a link styled as a button) can wear the same look: - `Launch`. - -### The components -- **Button** — `variant: 'primary'|'secondary'`, `size: 'sm'|'md'`, native button props. -- **Card** — sticker surface. `variant: 'default'|'empty'|'error'`, `as: 'div'|'article'|'section'`. -- **Badge** — pill status. `tone: 'neutral'|'positive'|'warning'|'danger'`; label is children. -- **Stat** + **StatGroup** — metric tiles: `` wraps ``. -- **KeyValue** + **KeyValueList** — key→value rows: `` wraps - `` (`tone: 'good'|'warn'|'neutral'`). -- **Disclosure** — native `
` with sticker chrome + chevron: `summary`, - `children`, `defaultOpen?`. -- **InputField** — bordered input with optional `label` and trailing `suffix`/unit. -- **Tabs** + **Tab** — pill tab bar: `` wraps ``. - -### Where the truth lives -Read the bound `styles.css` and its `@import`ed token/component CSS for exact -values, and each component's `.prompt.md` / `.d.ts` for its full API before styling. - -### Idiomatic example -```jsx - - - Buy Low - Sell High - - - - - -
- Ready to claim - -
-
-``` diff --git a/.design-sync/tsconfig.dspkg.json b/.design-sync/tsconfig.dspkg.json deleted file mode 100644 index 494cb20..0000000 --- a/.design-sync/tsconfig.dspkg.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "//": "design-sync only: compiles src/ui into a self-contained dist/ui package (JS + .d.ts) so the converter can discover components from real declarations. Not used by the Next app. Regenerated on every sync via .design-sync/build-ds-pkg.mjs.", - "compilerOptions": { - "target": "ES2022", - "lib": ["DOM", "DOM.Iterable", "ES2022"], - "module": "ESNext", - "moduleResolution": "Bundler", - "jsx": "react-jsx", - "declaration": true, - "emitDeclarationOnly": false, - "outDir": "../dist/ui", - "rootDir": "../src/ui", - "skipLibCheck": true, - "strict": false, - "noEmitOnError": false, - "esModuleInterop": true, - "allowSyntheticDefaultImports": true, - "types": [] - }, - "include": ["../src/ui/**/*.ts", "../src/ui/**/*.tsx"], - "exclude": [ - "../src/ui/**/*.stories.tsx", - "../src/ui/**/*.stories.ts", - "../src/ui/**/*.test.tsx", - "../src/ui/**/*.spec.tsx" - ] -} diff --git a/.gitignore b/.gitignore index b6168e8..d7df16f 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,6 @@ node_modules/ .next/ .next-e2e/ dist/ -storybook-static/ coverage/ playwright-report/ test-results/ @@ -22,13 +21,6 @@ demo-day-sprint-plan.md # presentation material — kept local only, never synced docs/presentation/ -# design-sync (claude.ai/design) — transient build/verification artifacts -.design-sync/sb-reference/ -.design-sync/learnings/ -.design-sync/.cache/ -.design-sync/node_modules -.ds-sync/ -ds-bundle/ .vercel .env* !.env.example diff --git a/.storybook/main.ts b/.storybook/main.ts deleted file mode 100644 index 23d3560..0000000 --- a/.storybook/main.ts +++ /dev/null @@ -1,38 +0,0 @@ -import type { StorybookConfig } from '@storybook/react-vite'; - -/** - * Storybook scoped to the design-system primitives in src/ui only — it does NOT - * pull in the Next app, the Sui wallet, or react-query. This is also the input - * the design-sync skill consumes (the .storybook/ dir makes it the storybook shape). - */ -const config: StorybookConfig = { - stories: ['../src/ui/**/*.stories.@(ts|tsx)'], - framework: { - name: '@storybook/react-vite', - options: {}, - }, - core: { - disableTelemetry: true, - }, - // The src/ui components use JSX with the automatic runtime (no `import React`, - // matching the Next app). Force esbuild to the automatic runtime here too, or - // Storybook's Vite falls back to classic React.createElement → "React is not - // defined" at runtime (the repo tsconfig uses jsx: "preserve" for Next). - async viteFinal(viteConfig) { - const { mergeConfig } = await import('vite'); - return mergeConfig(viteConfig, { - // Force the automatic JSX runtime for our SOURCE (.tsx files use no - // `import React`; the repo tsconfig sets jsx:"preserve" for Next, which - // would otherwise leave JSX untransformed → "React is not defined"). - esbuild: { jsx: 'automatic', jsxImportSource: 'react' }, - // Pre-bundle React so its CommonJS default export gets proper ESM interop - // in dev. Do NOT set optimizeDeps.esbuildOptions.jsx here: applying the - // JSX transform while pre-bundling React breaks its default export - // ("react … does not provide an export named 'default'") and the preview - // iframe renders blank with an endless spinner. - optimizeDeps: { include: ['react', 'react-dom', 'react/jsx-runtime'] }, - }); - }, -}; - -export default config; diff --git a/.storybook/preview-fonts.css b/.storybook/preview-fonts.css deleted file mode 100644 index 57a9769..0000000 --- a/.storybook/preview-fonts.css +++ /dev/null @@ -1,46 +0,0 @@ -/* Storybook-only font wiring. - * - * The Next app sets --font-fredoka / --font-geist-sans / --font-geist-mono via - * next/font in app/layout.tsx. Storybook has no Next runtime, so we redefine - * those same CSS variables here (all from the repo's local woff2) — giving - * previews the real sticker typography. Fredoka is self-hosted (not a Google - * Fonts @import) so the design-sync export ships the font FILES to - * claude.ai/design instead of relying on a runtime remote font host. */ -@font-face { - font-family: 'Fredoka'; - src: url('../src/fonts/Fredoka-latin.woff2') format('woff2'); - font-weight: 300 700; - font-display: swap; - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, - U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, - U+2212, U+2215, U+FEFF, U+FFFD; -} -@font-face { - font-family: 'Fredoka'; - src: url('../src/fonts/Fredoka-latin-ext.woff2') format('woff2'); - font-weight: 300 700; - font-display: swap; - unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, - U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, - U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; -} - -@font-face { - font-family: 'Geist'; - src: url('../src/fonts/Geist.woff2') format('woff2'); - font-weight: 100 900; - font-display: swap; -} - -@font-face { - font-family: 'GeistMono'; - src: url('../src/fonts/GeistMono.woff2') format('woff2'); - font-weight: 100 900; - font-display: swap; -} - -:root { - --font-fredoka: 'Fredoka'; - --font-geist-sans: 'Geist'; - --font-geist-mono: 'GeistMono'; -} diff --git a/.storybook/preview.ts b/.storybook/preview.ts deleted file mode 100644 index 08e206b..0000000 --- a/.storybook/preview.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { Preview } from '@storybook/react'; - -// Font variables first, then the full token + component stylesheet closure -// (app/globals.css @imports src/styles.css + the src/ui/*.css token & component files). -import './preview-fonts.css'; -import '../app/globals.css'; - -const preview: Preview = { - parameters: { - layout: 'centered', - controls: { expanded: true }, - }, -}; - -export default preview; diff --git a/docs/agent-native-product-architecture.md b/docs/agent-native-product-architecture.md deleted file mode 100644 index 1ddb942..0000000 --- a/docs/agent-native-product-architecture.md +++ /dev/null @@ -1,520 +0,0 @@ -# Agent-native product architecture 学习指南 - -> 目的: 帮你建立一套面向 agent 协作的软件设计开发心智模型。它不是某个框架教程,而是解释在代码生成变便宜之后,产品、设计、前端、后端、部署 runtime 和架构应该怎么重新分工。 - -## 1. 核心判断 - -Agent 时代真正变化的不是"前端可以写得更快",也不是"后端 API 可以写得更快",而是: - -> 代码实现变便宜之后,设计载体会从静态稿逐渐迁移到可运行页面;但新的主要成本变成代码熵。 - -传统时代的问题是实现贵,所以先用 Figma、PRD、接口文档降低试错成本。Agent 时代可以直接生成可运行页面、HTTP endpoint、数据 mock、测试和脚本,所以试错成本下降;但如果没有架构边界,agent 会把业务规则、后端 runtime、链上 adapter、取数逻辑、视觉样式、文案和状态分支混在一起,短期很快,长期很乱。 - -所以 agent-native 架构的目标不是"让 agent 多写代码",而是: - -> 把代码库设计成 agent 可以安全修改、快速实验、可控收敛的系统。 - -## 2. 从"前后端分离"到"变化速度分层" - -传统工程里最常见的分界是: - -```txt -frontend / backend -``` - -但在现代全栈项目里,页面、HTTP endpoint、serverless function、edge runtime、有状态服务、wallet SDK、React hooks、第三方 API、链上 SDK 经常混在同一个 repo 里。这个时候"前端/后端"仍然有意义,但不够指导 agent 怎么安全改代码。Next.js 的 `app/api/*`、Cloudflare Worker 的 `fetch()` handler、普通 Node 服务的 route handler 都是后端入口,只是 runtime 不同。 - -Agent 时代更重要的分界是: - -```txt -稳定层 / 可变层 / 实验层 -``` - -稳定层: - -- 产品数学 -- 交易构造 -- 链上规则 -- 结算规则 -- 风控规则 -- API contract -- HTTP / worker route behavior -- external service adapters -- stateful service invariants - -可变层: - -- 页面信息架构 -- view model -- 页面状态组合 -- API response shaping -- cache / retry policy -- runtime placement -- 产品文案 -- layout -- 主题 - -实验层: - -- 视觉方向 -- 页面 variants -- 假数据状态 -- demo copy -- theme tokens -- branch prototypes - -Agent 可以频繁改实验层,谨慎改可变层,默认不碰稳定层。 - -## 3. Runtime adapter 也是架构边界 - -如果一个文件负责这些事情,它就是后端边界: - -- 接收 HTTP request -- 调外部服务 -- 读写数据库或链上数据 -- 隐藏 API key 或环境变量 -- 聚合多个数据源 -- 处理缓存、重试、allowlist、rate limit -- 返回稳定 response contract -- 管理有状态对象、队列、定时任务或后台 worker - -不同 runtime 只是不同 adapter: - -```txt -Next.js: - app/api/* - server actions - route handlers - -Cloudflare: - Workers fetch handler - Pages Functions - Durable Objects / stateful coordination - Queues / Cron Triggers - -Serverful Node: - Fastify/Express/Hono routes - background workers - long-lived processes - -Shared backend: - src/server/* - external API clients - chain indexer / transaction preflight services -``` - -Agent-native 架构不是把这些后端拆到另一个 repo 才算清楚,也不是绑定某一个框架才算清楚。真正要保持的是 contract、adapter、application 和 domain 的边界。部署到 Next、Cloudflare、普通 VPS 或有状态服务,只是最外层 runtime adapter 的替换。 - -## 4. 理想分层 - -一个 agent-native 产品项目可以按下面的层次组织: - -```txt -1. Domain Core - 产品规则、数学、状态机、报价、结算 - 纯 TypeScript, 不依赖 React / Next / Cloudflare / CSS - -2. Application Layer - 用例编排 - 例如 subscribe、claim、settle、validate、preflight - -3. Contracts - 稳定边界 - API response schema、view model schema、event shape、error shape、state transition shape - -4. Server Adapters - 后端连接器 - HTTP handlers、serverless functions、Workers、stateful services、外部 API、链 RPC、数据库、缓存、队列 - -5. Client Adapters - 客户端连接器 - React Query hooks、wallet SDK、browser storage、client-side polling - -6. Containers - 页面运行时容器 - 调 hooks、读 URL、拿 wallet、处理 loading/error、提交 action - -7. ViewModel Layer - 把复杂业务数据整理成页面直接可渲染的数据结构 - -8. Pure Views - 完整页面 UI - 不 fetch、不连钱包、不读 URL、不调链、不构造交易 - -9. Design System - tokens、components、layout primitives、product components、themes - -10. Design Lab - fixtures、stories、/design preview、state matrix、visual snapshots - -11. Production Shell - framework routing、metadata、providers、deployment/runtime glue -``` - -这不是为了制造很多目录,而是为了让每种变化都有明确落点。 - -## 5. 两个核心公式 - -把页面理解成一个纯函数: - -```txt -ViewModel + Theme + Locale + Viewport -> Rendered Page -``` - -把 HTTP/backend endpoint 理解成另一个纯边界: - -```txt -Request + Config + External Services -> Contracted Response -``` - -理想状态下,完整页面可以不依赖真实系统运行: - -```tsx - - - -``` - -这就是 code-native design lab 的基础。 - -理想状态下,API 也可以用 deterministic fixtures 测试: - -```txt -Predict fixture + current time -> current APR response -Portfolio fixture + manager fixture -> dashboard backend response -``` - -这就是 agent 不污染后端逻辑的基础。 - -## 6. Pure View 是什么 - -Pure View 不是小组件,而是完整页面。 - -它可以包含: - -- 页面布局 -- section -- table -- card grid -- button -- empty state -- error state -- responsive layout -- local UI state, 比如展开/收起、tab 选择 - -它不应该包含: - -- `useQuery` -- wallet SDK hook -- API fetch -- transaction builder -- pricing math -- settlement math -- URL parsing -- environment variable -- localStorage side effect - -Pure View 的输入应该是页面模型: - -```ts -export type DashboardViewModel = { - locale: 'en' | 'zh'; - state: 'no-wallet' | 'loading' | 'error' | 'empty' | 'populated'; - hero: { - title: string; - subtitle: string; - }; - portfolio?: { - totalDeposited: string; - expectedRewards: string; - openPositions: string; - }; - positions: PositionCardViewModel[]; -}; -``` - -这样 agent 改 UI 的时候,不用理解钱包、链、API 和结算细节。反过来,agent 改 API contract 的时候,也不应该顺手改页面视觉。 - -## 7. Fixture-first product development - -传统开发常见流程是: - -```txt -先接真实数据 -> 页面能跑 -> 再处理 loading/error/empty -``` - -Agent-native 推荐反过来: - -```txt -先列产品状态矩阵 -> 写 fixtures -> 设计完整页面和 API contract -> 接真实数据 -``` - -每个核心页面至少需要这些 view fixtures: - -- no wallet -- loading -- error -- empty -- populated -- populated with many rows -- long text / long address -- Chinese -- English -- mobile stress - -每个关键后端入口至少需要这些 server fixtures: - -- upstream success -- upstream timeout -- upstream malformed response -- empty result -- stale snapshot -- permission/allowlist failure -- deterministic current time - -这会逼迫产品经理和工程师先说清楚页面到底有哪些状态。 - -## 8. Design System 的升级 - -传统 design system: - -```txt -Color -Typography -Button -Card -Input -Badge -``` - -Agent-native design system: - -```txt -Tokens -Primitive components -Layout primitives -Product components -Page views -Fixtures -State matrix -Copy dictionary -Theme variants -Agent edit rules -Visual QA -``` - -换句话说,设计系统不再只是 UI 组件库,而是 agent 可操作的产品界面实验室。 - -## 9. 工具应该怎么理解 - -工具不是核心答案,但工具可以承载某一层能力。 - -Storybook: - -- 不只是看小组件,也可以看完整页面 -- 适合状态矩阵、fixtures、视觉回归 -- 前提是页面已经可以用假数据纯渲染 - -`/design` preview: - -- 更接近真实 app shell -- 适合产品经理直接点 URL 看完整页面 -- 可以是 Next route、独立 preview app、Storybook iframe、Cloudflare preview URL -- 适合组合 query 参数: `?state=populated&theme=terminal&locale=zh` - -Branch prototypes: - -- 适合同时试 3-5 个视觉方向 -- 需要限制改动范围,否则 branch 会变成多个不同版本的混乱生产代码 - -Visual builder: - -- 适合拖拽和局部编辑 -- 必须建立在稳定组件和 token 之上 -- 不能替代架构边界 - -## 10. Agent edit boundary - -Agent-native repo 必须明确哪些文件能改。 - -建议规则: - -```txt -Green zone: 可以大胆改 -- views -- fixtures -- stories -- themes -- copy -- visual-only CSS - -Yellow zone: 谨慎改 -- viewModel -- containers -- hooks -- framework routes / HTTP handlers -- runtime adapters - -Red zone: 默认不碰 -- products -- application -- sui -- transaction -- pricing -- settlement -- API contracts -``` - -这类规则应该写进 `AGENTS.md` 或 `docs/architecture.md`,因为它是给 agent 的操作边界。 - -## 11. Branch 工作流 - -推荐: - -```txt -main - stable production - -codex/design-dashboard-minimal -codex/design-dashboard-terminal -codex/design-dashboard-premium -codex/design-dashboard-sticker -``` - -每个设计 branch 只能改: - -- pure views -- themes -- fixtures -- stories -- copy - -选中方案后: - -1. 对比同一批 fixtures 的截图 -2. 把胜出的 view/theme 收敛回主线 -3. 删除未采用的实验代码 -4. 保证 production container 和 domain core 没被污染 - -Branch 解决并行实验,架构边界解决长期可维护。 - -## 12. Framework/runtime 在这个架构里的位置 - -Next.js、Cloudflare Workers、Hono、Fastify、serverful Node 都不是问题;问题是让 runtime 变成业务架构。 - -Framework/runtime 应该负责: - -- routing / HTTP handling -- metadata -- server/client composition, if applicable -- providers / runtime bindings -- backend entry adapter -- deployment glue -- platform-specific state, queue, cron, durable coordination adapters - -Framework/runtime 不应该承载: - -- 产品数学 -- 复杂状态机 -- 链上交易规则 -- 外部 API response contract 的隐式变形 -- 页面大段 JSX + fetch + formatting 混写 -- 视觉实验的主要场地 - -理想的 page / screen entry 应该很薄: - -```tsx -export default function Page() { - return ; -} -``` - -理想的 backend endpoint 也应该很薄: - -```ts -export async function handleCurrentApr(request: Request, runtime: RuntimeBindings) { - const input = parseRequest(request); - const result = await getCurrentApr(input, buildAdapters(runtime)); - return Response.json(toCurrentAprResponse(result)); -} -``` - -业务规则在 application/domain, HTTP handler 只做 runtime adapter。今天这个 handler 可以包成 Next route;明天可以包成 Cloudflare Worker;如果需要有状态协调,也可以把 stateful 部分放进专门的 state adapter,而不是散落在页面或 API handler 里。 - -## 13. 反模式 - -需要警惕这些情况: - -- 一个 page component 同时 fetch、format、branch、render、style -- 一个 HTTP handler 同时 parse request、调多个外部服务、做业务规则、拼 response、吞错误 -- agent 为了改视觉去改 hook 或交易逻辑 -- agent 为了改 demo 数据去改真实 API response -- 文案散落在 JSX 里,没有 locale/copy 层 -- loading/error/empty 只有真实触发时才看得到 -- 后端错误状态只有真实第三方服务挂掉时才看得到 -- runtime-specific binding 泄漏进 domain core -- 为了迁移部署平台而改产品规则 -- 每个视觉实验都复制一整套页面 -- Tailwind class 或 CSS class 无约束堆叠 -- design system 只抽 Button,但完整页面仍然无法假数据运行 -- Storybook 只有 primitive components,没有 page states -- branch 实验没有收敛规则 - -## 14. 学习路线 - -第一阶段: 建立分层意识 - -- 理解 domain / application / adapter / container / view 的区别 -- 看现有项目中哪些逻辑属于稳定层,哪些属于实验层 -- 练习把一个页面拆成 container + view model + pure view -- 练习把一个 HTTP endpoint 拆成 runtime handler + application service + external adapter -- 练习把部署平台能力包成 adapter,例如 KV/cache、queue、cron、durable state - -第二阶段: 建立状态矩阵 - -- 为一个页面列出所有用户状态 -- 为每个状态写 fixture -- 用同一个 Pure View 渲染这些 fixture -- 为一个 backend endpoint 列出 upstream success/error/stale 状态 - -第三阶段: 建立设计实验室 - -- 用 Storybook 或 `/design` preview 展示完整页面 -- 增加 theme / locale / viewport 切换 -- 加入截图验证 - -第四阶段: 建立 agent 工作流 - -- 写清楚 green/yellow/red edit zones -- 规定 design branch 只能改哪些目录 -- 规定 merge 前必须通过哪些截图和测试 - -第五阶段: 产品化 - -- 把核心页面都转成可假数据渲染 -- 把文案和国际化抽出来 -- 把视觉 tokens 主题化 -- 把设计探索变成稳定流程 - -## 15. 最终目标 - -一个成熟的 agent-native 产品系统应该满足: - -- 真实产品可以运行 -- 完整页面可以用假数据运行 -- 后端 API 可以用 deterministic fixtures 验证 -- API contract 明确且有测试 -- runtime adapter 可以替换或迁移 -- 有状态服务有明确 state transition contract -- 每个核心页面有状态矩阵 -- 每个核心页面支持语言切换测试 -- 每个核心页面支持 theme/density 试验 -- agent 知道哪些文件可以改 -- 视觉实验不会污染业务核心 -- demo 数据不会污染真实后端 contract -- 选中方案可以干净合并回生产代码 -- 代码生成速度快,维护成本不失控 - -最短总结: - -> Agent-native architecture = production app + runnable design lab + explicit edit boundaries. diff --git a/docs/anker-agent-native-roadmap.md b/docs/anker-agent-native-roadmap.md deleted file mode 100644 index 9d8292b..0000000 --- a/docs/anker-agent-native-roadmap.md +++ /dev/null @@ -1,837 +0,0 @@ -# AnkerProtocol agent-native 产品架构路线图 - -> 目的: 把当前 AnkerProtocol 项目从"真实页面和当前 Next serverless API 可运行"推进到"agent 可以安全做完整产品实验,且未来可迁移 runtime"。这里的产品实验包括页面、文案、状态矩阵、API contract mock、runtime adapter 和链上/第三方服务边界。当前 runtime 是 Next.js;未来可以是 Cloudflare Workers、Pages Functions、Durable Objects、普通 Node 服务或其他有状态后端。 - -## 1. 当前判断 - -当前项目已经有比较好的底座: - -- `src/products/*`: 产品数学、报价、结算相关逻辑,基本不依赖 UI。 -- `src/sui/*`: 链上对象、交易和 portfolio 读取。 -- `src/deepbook/*`: DeepBook / Binance / Predict 数据连接。 -- `src/server/*`: server response shaping、curated oracle、deterministic fixtures。 -- `src/hooks/*`: React 查询层。 -- `app/api/*`: 当前 Next HTTP/serverless adapter,属于后端边界,但不应该承载不可迁移的业务规则。 -- `src/ui/*`: 已经抽出 Button、Card、Badge、Stat、Tabs、Field 等 primitives。 -- `app/*/page.tsx`: 当前 Next page route 层很薄,基本只挂载页面组件。 - -主要问题集中在 `src/components/*`: - -- 页面组件仍然同时做取数、状态派生、页面拼装和视觉 class。 -- 完整页面还不能脱离 wallet/API/chain 独立渲染。 -- Storybook 目前只覆盖 `src/ui/**/*.stories.tsx`,没有产品级区块和完整页面状态。 -- `src/styles.css` 仍然是 4000+ 行全局页面样式,主题和页面 class 耦合较重。 -- 当前项目没有 Tailwind 依赖,不要假设 Tailwind 工具可以直接接入。 -- HTTP 后端入口和页面在同一个 Next repo,但仍要按 backend contract / frontend view / runtime adapter 分开治理。 - -本路线图的核心目标: - -> 先选 Dashboard 做 pilot,跑通 backend contract -> runtime adapter -> container -> view model -> pure view -> fixtures -> design lab 的闭环。 - -## 2. 目标目录形态 - -推荐逐步把核心页面迁移到 feature 结构。短期不需要大搬家,但目标形态如下: - -```txt -src/features/dashboard/ - dashboard.contract.ts - dashboard.serverFixtures.ts - DashboardPageContainer.tsx - DashboardView.tsx - dashboardViewModel.ts - dashboard.fixtures.ts - DashboardView.stories.tsx - dashboard.copy.ts - dashboard.types.ts - -src/features/dual-investment/ - dualInvestment.contract.ts - dualInvestment.serverFixtures.ts - DualInvestmentPageContainer.tsx - DualInvestmentView.tsx - dualInvestmentViewModel.ts - dualInvestment.fixtures.ts - DualInvestmentView.stories.tsx - dualInvestment.copy.ts - dualInvestment.types.ts - -src/design-system/ - primitives/ - layout/ - product/ - themes/ - -src/runtime/ - next/ - api/ - pages/ - cloudflare/ // future, only when migration starts - workers/ - durable-objects/ -``` - -为了降低风险,第一阶段可以先保留现有 `src/components/*`,新增文件逐步承接纯 view 和 fixtures。 - -## 3. 第 1 步: 固化架构边界 - -状态: 需要补齐。 - -产物: - -- `docs/agent-native-product-architecture.md` -- `docs/anker-agent-native-roadmap.md` -- 后续建议新增 `AGENTS.md` - -要明确: - -```txt -Green zone: agent 可大胆改 -- pure views -- fixtures -- stories -- themes -- copy -- visual-only CSS - -Yellow zone: 谨慎改 -- view models -- containers -- hooks -- framework routes -- HTTP handlers -- runtime adapters -- src/server response shaping - -Red zone: 默认不碰 -- src/products -- src/application -- src/sui -- src/deepbook pricing/quote logic -- API contracts -- transaction/preflight/settlement -- state transition invariants -``` - -完成标准: - -- 文档写清楚。 -- 后续每次视觉探索都能引用这套边界。 -- Agent 不再为了改 UI 默认触碰 `products/sui/application/api`。 -- Agent 不再为了造 demo 状态修改真实 API contract。 - -## 4. 第 2 步: 先标清 backend/runtime 边界 - -在做 Dashboard pilot 前,先把项目里的"后端"和"runtime"说清楚。当前代码运行在 Next.js 里,但架构目标不能被 Next.js 绑定。 - -当前 AnkerProtocol 的后端不是独立服务,但这些都是后端或后端 adapter 职责: - -```txt -app/api/* 当前 Next HTTP adapter -src/server/* response shaping、server fixtures、server helpers -src/deepbook/* 外部服务 adapter / Predict 数据连接 -src/sui/* 链上读取/交易 primitive -src/application/* 用例编排 -src/products/* domain core -``` - -需要建立两个概念: - -```txt -Server contract: - HTTP endpoint 返回给前端/客户端的稳定 shape。 - -View model: - 页面最终消费的展示 shape。 - -Runtime adapter: - 把同一个 application/backend service 接到 Next、Cloudflare、Node 或其他平台。 -``` - -它们不能混为一个东西。API contract 可以偏机器可读,ViewModel 可以偏页面友好,Runtime adapter 应该只处理 request/response/platform binding。 - -建议短期新增或整理: - -```txt -src/server/dashboardResponse.ts // 如果后续有 dashboard 聚合 API -src/server/dashboardResponse.test.ts -src/components/dashboardViewModel.ts -src/components/dashboardViewModel.test.ts -``` - -如果暂时没有 dashboard 聚合 API,也要在文档里声明: Dashboard 当前由 client hooks 聚合数据,backend 主要存在于 market/predict/current APR routes。 - -完成标准: - -- `app/api/*` 被明确视为当前 Next runtime adapter。 -- HTTP handler 只做 request/response/platform adapter,复杂规则下沉到 `src/server` / `src/application` / `src/products`。 -- demo fixtures 不复用或污染真实 API response contract。 -- 如果以后迁移 Cloudflare,优先新增 runtime adapter,不要重写 domain/application/view。 - -## 5. 第 3 步: 建立 runtime portability contract - -这一步不是马上迁移 Cloudflare,而是避免现在写出以后迁不动的代码。 - -建议约束: - -```txt -Domain core: - 不 import Next、Cloudflare、React、DOM。 - -Application service: - 接收显式 adapters,不直接读 process.env、Request、Response、window。 - -Server adapter: - 可以读 Request/env/bindings,但只负责转换输入输出。 - -State adapter: - 如果未来需要 Durable Object、数据库、队列或长连接,先定义接口,再绑定平台实现。 -``` - -示例形态: - -```ts -export type RuntimeAdapters = { - predictClient: PredictClient; - clock: Clock; - cache?: CacheAdapter; - stateStore?: StateStore; -}; - -export async function getCurrentApr(input: CurrentAprInput, adapters: RuntimeAdapters) { - // application logic only -} -``` - -完成标准: - -- 新增/改造后端逻辑时,优先写 application function,再写 Next route wrapper。 -- `process.env`、`Request`、`Response`、platform bindings 不进入 `src/products`。 -- 有状态能力先抽接口,再决定是 Cloudflare Durable Object、数据库还是普通 server process。 - -## 6. 第 4 步: Dashboard pilot - -这是下一步最重要的工程动作。 - -### 6.1 新增 DashboardViewModel 类型 - -新增: - -```txt -src/components/dashboardViewModel.ts -``` - -或如果愿意开始 feature 化: - -```txt -src/features/dashboard/dashboard.types.ts -src/features/dashboard/dashboardViewModel.ts -``` - -建议模型: - -```ts -export type DashboardViewState = - | 'no-wallet' - | 'contract-missing' - | 'loading' - | 'error' - | 'empty' - | 'populated'; - -export type DashboardViewModel = { - state: DashboardViewState; - locale: 'en' | 'zh'; - hero: { - title: string; - subtitle: string; - refreshLabel: string; - refreshDisabled: boolean; - }; - portfolio?: { - totalDeposited: string; - expectedRewards: string; - openPositions: string; - }; - positionsHeading?: { - title: string; - countLabel: string; - }; - filters: Array<{ - key: 'all' | 'ready' | 'active' | 'completed'; - label: string; - count: number; - visible: boolean; - }>; - activeFilter: 'all' | 'ready' | 'active' | 'completed'; - positions: PositionCardViewModel[]; - emptyMessage?: string; - errorMessage?: string; -}; -``` - -关键原则: - -- ViewModel 里尽量放已经格式化好的字符串。 -- View 不应该知道 `AnkerProductNoteRecord` 的原始结构。 -- View 不应该自己判断 contract 是否配置。 - -### 6.2 抽 DashboardView - -新增: - -```txt -src/components/DashboardView.tsx -``` - -职责: - -- 渲染完整 Dashboard 页面。 -- 接收 `model` 和 `actions`。 -- 不调用 wallet hook。 -- 不调用 portfolio hook。 -- 不调用 predict manager hook。 -- 不调用 `Date.now()` 计算业务状态。 - -建议接口: - -```ts -export type DashboardViewActions = { - onRefresh: () => void; - onFilterChange: (filter: PositionFilter) => void; - onClaim?: (positionId: string) => void; -}; - -export function DashboardView({ - model, - actions, -}: { - model: DashboardViewModel; - actions: DashboardViewActions; -}) { - // render only -} -``` - -短期可以先让部分复杂子组件继续存在,但 DashboardView 本身必须不取数。 - -### 6.3 改 DashboardPage 成 container - -当前 `src/components/DashboardPage.tsx` 应变薄: - -```txt -DashboardPage - - useCurrentAccount - - useAnkerPortfolio - - usePredictManagers - - useProductNoteEventIndex - - buildDashboardViewModel(...) - - -``` - -完成后,DashboardPage 仍是生产入口,DashboardView 是设计入口。 - -### 6.4 新增 Dashboard fixtures - -新增: - -```txt -src/components/dashboard.fixtures.ts -``` - -至少包含: - -```ts -export const dashboardNoWalletFixture -export const dashboardLoadingFixture -export const dashboardErrorFixture -export const dashboardEmptyFixture -export const dashboardPopulatedFixture -export const dashboardPopulatedZhFixture -export const dashboardManyPositionsFixture -export const dashboardLongTextFixture -``` - -这些 fixture 必须直接喂给 `DashboardView`。 - -### 6.5 新增 Dashboard full-page story - -修改 `.storybook/main.ts`: - -```ts -stories: [ - '../src/ui/**/*.stories.@(ts|tsx)', - '../src/components/**/*.stories.@(ts|tsx)', -] -``` - -新增: - -```txt -src/components/DashboardView.stories.tsx -``` - -Stories: - -- Populated -- PopulatedChinese -- NoWallet -- Loading -- Error -- Empty -- ManyPositions - -完成标准: - -- 不启动生产 app / 不连真实 runtime 也能看完整 Dashboard。 -- 不连钱包也能看 claim/action 状态。 -- agent 可以只改 DashboardView / fixtures / CSS 做视觉探索。 - -## 7. 第 5 步: 抽 PositionCard pure view - -当前 `DashboardProductNoteCard` 里还有: - -- `usePredictManagerState` -- `useQuery(fetchOracleMarket)` -- proof link 计算 -- lifecycle 计算 -- 展示 markup - -建议拆成: - -```txt -ProductNoteCardContainer.tsx -ProductNoteCardView.tsx -productNoteCardViewModel.ts -productNoteCard.fixtures.ts -``` - -目标: - -```tsx - - - - -``` - -完成标准: - -- DashboardView 可以用纯 `PositionCardViewModel[]` 渲染卡片。 -- 卡片所有视觉状态都可以在 stories 中独立查看。 -- 链上 proof 的实时查询只存在 container 或 viewModel builder 里。 - -## 8. 第 6 步: Dual Investment 页面重复同样模式 - -Dashboard pilot 跑通后,再处理 `DualInvestmentPage`。 - -拆分目标: - -```txt -DualInvestmentPageContainer - - useMarketData - - useDualInvestmentScan - - useBinanceDualInvestment - - debounce live quote verification - - selectedOracle / principal / target / legCount state - -DualInvestmentView - - 完整页面渲染 - - DirectionPairBar - - ReturnOverview - - ReferenceTable - - BuyLowControls - - Confirm panel - - Advanced details - -dualInvestmentViewModel - - market ticker display - - selection options - - quote display state - - reference table rows - - confirm state -``` - -Fixtures: - -- loading market -- stale snapshot -- no valid target -- estimate quote -- live executable quote -- quote verification error -- Binance benchmark present -- long settlement date -- Chinese copy - -完成标准: - -- 不连 DeepBook / Binance 也能看完整 Dual Investment 页面。 -- 视觉实验不需要触发 live quote。 -- 真实报价逻辑仍留在 container/hooks/products。 - -## 9. 第 7 步: 文案与国际化 - -你提到中英文切换,这个应该在 view model / copy 层解决,不要让中文英文散落在 JSX 里。 - -新增: - -```txt -src/i18n/types.ts -src/i18n/en.ts -src/i18n/zh.ts -``` - -或按 feature: - -```txt -src/features/dashboard/dashboard.copy.ts -src/features/dual-investment/dualInvestment.copy.ts -``` - -原则: - -- Pure View 接收已经选好的 copy 或 `locale`。 -- Story/fixture 必须覆盖中英文。 -- 中文要专门测长文本换行。 -- 不要在 JSX 中写一堆条件: `{locale === 'zh' ? ... : ...}`。 - -完成标准: - -- Dashboard 和 DualInvestment 至少支持 English / Chinese fixture。 -- 所有核心页面 story 都有中文状态。 -- 长中文不会撑爆按钮、卡片、表格。 - -## 10. 第 8 步: Theme / token 实验层 - -当前 `src/styles.css` 是主样式来源,`src/ui/tokens.css` 只有初步 token。 - -建议下一步不是立刻迁移 Tailwind,而是先建立主题入口: - -```txt -src/ui/tokens.css -src/ui/themes/sticker.css -src/ui/themes/fintech.css -src/ui/themes/terminal.css -src/ui/themes/minimal.css -``` - -先做 CSS variables: - -```css -:root, -[data-theme='sticker'] { - --color-bg: ...; - --color-surface: ...; - --color-text: ...; - --color-accent: ...; - --radius-card: ...; - --shadow-card: ...; -} - -[data-theme='terminal'] { - --color-bg: ...; -} -``` - -完成标准: - -- 同一个 DashboardView 可以通过 `data-theme` 切换视觉方向。 -- 主题主要改 token,不是复制 JSX。 -- 设计 branch 改主题时不需要动业务逻辑。 - -## 11. 第 9 步: Design Lab - -有两种方式,可以并行。 - -### 11.1 Storybook - -适合: - -- 组件状态矩阵 -- 页面状态矩阵 -- 开发时快速切换 props -- 视觉回归 - -必做: - -- `DashboardView.stories.tsx` -- `ProductNoteCardView.stories.tsx` -- `DualInvestmentView.stories.tsx` -- `ReturnOverview.stories.tsx` - -### 11.2 `/design` preview - -适合: - -- 产品经理直接看完整页面 -- 更接近真实 app shell -- query 参数切换 state/theme/locale - -建议路由: - -```txt -app/design/dashboard/page.tsx -app/design/dual-investment/page.tsx -``` - -这是当前 Next 实现。未来如果部署到 Cloudflare,它也可以是独立 preview app 或 Worker preview URL,不必绑定 Next route。 - -示例: - -```txt -/design/dashboard?state=populated&theme=sticker&locale=en -/design/dashboard?state=populated&theme=terminal&locale=zh -``` - -完成标准: - -- 不连钱包、不调真实 API 也能看完整产品页面。 -- 一个 URL 可以表达一个设计状态。 -- branch 上的设计方案可以直接发 URL 审查。 - -## 12. 第 10 步: 视觉验证 - -当 design lab 成型后,再加自动验证。 - -建议: - -- Playwright 打开 `/design/*` 或 Storybook iframe。 -- 对 desktop/mobile 截图。 -- 检查关键文本是否可见。 -- 检查 console error。 -- 对核心 states 做 snapshot baseline。 - -初始覆盖: - -- Dashboard populated desktop/mobile -- Dashboard empty desktop/mobile -- Dashboard Chinese desktop/mobile -- DualInvestment executable quote desktop/mobile -- DualInvestment empty/invalid target desktop/mobile - -完成标准: - -- 视觉实验 merge 前有截图验证。 -- agent 改页面后能自己跑检查。 - -## 13. 第 11 步: Branch 实验机制 - -推荐命名: - -```txt -codex/design-dashboard-minimal -codex/design-dashboard-terminal -codex/design-dashboard-premium -codex/design-dashboard-sticker-v2 -``` - -每个设计 branch 允许改: - -- `DashboardView.tsx` -- `ProductNoteCardView.tsx` -- fixtures -- themes -- visual CSS -- copy - -默认不允许改: - -- `src/products/*` -- `src/sui/*` -- `src/application/*` -- transaction / quote / settlement logic -- API contracts -- runtime state invariants - -合并前检查: - -- typecheck -- unit tests for view model -- visual screenshots -- diff 是否只落在 green/yellow zone - -## 14. 推荐执行顺序 - -短期 1-2 天: - -1. 新增本路线图和学习文档。 -2. 标清 backend/runtime 边界。 -3. 建立 runtime portability contract。 -4. 给 Dashboard 定义 `DashboardViewModel`。 -5. 抽 `DashboardView`。 -6. 写 Dashboard fixtures。 -7. 写 `DashboardView.stories.tsx`。 - -中期 3-5 天: - -1. 抽 `ProductNoteCardView`。 -2. 把 DashboardPage 变薄。 -3. 补中文/英文 copy fixture。 -4. 扩 Storybook 到产品级 components。 -5. 建立第一个 `/design/dashboard` route。 - -中期 1-2 周: - -1. DualInvestment 重复 container/view/viewModel/fixtures。 -2. 建立 theme tokens。 -3. 做 3 个 dashboard theme branch。 -4. 增加 Playwright visual smoke tests。 - -长期: - -1. 所有核心页面都有 pure view。 -2. 所有核心页面都有状态矩阵。 -3. 所有核心页面都可通过 fixture 运行。 -4. agent 工作流固定为: design branch -> fixture screenshots -> select -> merge。 - -## 15. 还需要补的工作流文档 - -为了把这套系统真正跑起来,建议后续再补这些文档。 - -### 15.1 `AGENTS.md` - -给 agent 的仓库操作规则。 - -内容: - -- green/yellow/red edit zones -- 视觉任务默认流程 -- 禁止触碰的业务核心目录 -- 测试和截图要求 -- branch 命名规则 - -优先级: 最高。 - -### 15.2 `docs/architecture.md` - -给人和 agent 共用的项目架构说明。 - -内容: - -- 当前目录职责 -- domain/application/adapter/container/view 分层 -- backend adapter / runtime adapter 分层 -- 数据流图 -- API contract 和 view model 的区别 -- 哪些模块可以互相依赖 -- 当前 Next.js 在项目里的边界 -- 未来 Cloudflare / stateful backend 迁移边界 - -优先级: 高。 - -### 15.3 `docs/design-workflow.md` - -定义设计探索流程。 - -内容: - -- 怎么开 design branch -- 怎么选 fixture -- 怎么加新视觉方向 -- 怎么评审截图 -- 怎么把胜出方案 merge 回生产 -- 怎么清理失败实验 - -优先级: 高。 - -### 15.4 `docs/api-contracts.md` - -定义后端 API contract。 - -内容: - -- 每个 HTTP endpoint 的输入/输出 shape -- response versioning 策略 -- upstream error 如何映射 -- stale snapshot 如何表达 -- deterministic fixtures 如何组织 -- 哪些 API 可以为了设计实验 mock,哪些不能改 contract - -优先级: 高。 - -### 15.5 `docs/runtime-portability.md` - -定义未来迁移到 Cloudflare 或有状态服务时的边界。 - -内容: - -- 当前 Next runtime adapter 清单 -- 哪些逻辑必须保持 runtime-agnostic -- `process.env` / platform binding 使用规则 -- cache、queue、cron、state store 的 adapter interface -- Cloudflare Workers / Durable Objects / Node service 的可能映射 -- 迁移时哪些测试必须保持不变 - -优先级: 高。 - -### 15.6 `docs/state-matrix.md` - -列出每个核心页面必须覆盖的状态。 - -内容: - -- Dashboard states -- DualInvestment states -- wallet states -- quote states -- error states -- i18n stress states -- mobile/desktop states - -优先级: 高。 - -### 15.7 `docs/i18n-copy-guidelines.md` - -中英文和未来多语言文案规范。 - -内容: - -- copy key 命名 -- 中文长度策略 -- 金融产品术语表 -- APR / settlement / claim 等词汇标准 -- 禁止 JSX 内散落硬编码文案 - -优先级: 中。 - -### 15.8 `docs/visual-regression.md` - -视觉验证规范。 - -内容: - -- 哪些 route/story 必须截图 -- desktop/mobile 尺寸 -- 如何判断失败 -- 如何更新 baseline - -优先级: 中。 - -### 15.9 `docs/theme-token-contract.md` - -主题 token contract。 - -内容: - -- 允许的 token 名称 -- primitive/semantic/component token 分层 -- 新主题必须实现哪些变量 -- 禁止组件直接引用 raw pigment 的规则 - -优先级: 中。 - -## 16. 最重要的下一步 - -不要同时重构所有页面。下一步先标清 backend/runtime 边界,再做 Dashboard pilot。 - -目标闭环: - -```txt -DashboardPageContainer - -> buildDashboardViewModel - -> DashboardView - -> dashboard fixtures - -> DashboardView stories -``` - -这个闭环跑通之后,再复制到 DualInvestment。否则会变成大重构,风险高,反馈慢。 diff --git a/docs/design-system-audit.md b/docs/design-system-audit.md deleted file mode 100644 index 10147d9..0000000 --- a/docs/design-system-audit.md +++ /dev/null @@ -1,142 +0,0 @@ -# Anker 设计系统 · 现状审计 - -> **日期**:2026-06-22 -> **分支**:`design-system`(`main` 为比赛提交分支,已冻结;所有设计系统工作在本分支进行) -> **范围**:纯分析报告,不含代码改动。 -> **目的**:把 Anker hackathon 原型的 UI 从"页面级写死"逐步工程化为可复用的设计系统,作为产品化的地基。 - ---- - -## 一、总体结论 - -**核心判断:问题不是"没设计",而是"设计没被结构化"。** - -视觉语言已经成型且有辨识度(纸感 / sticker 风格:硬偏移阴影 + 粗描边 + 暖色纸底),值得保留;但它只活在一份 **4280 行、按页面堆叠的 `src/styles.css`** 里,没有被抽象成 token 体系和可复用组件。 - -| 维度 | 现状 | 评价 | -|---|---|---| -| 视觉资产 | 纸感 / sticker 风格,色板源自 anchor 图标(navy + gold + cream) | ✅ 有辨识度,值得留 | -| Token | 仅 38 个,几乎全是颜色 | ⚠️ 太薄,缺间距/字阶/动效/层级/断点 | -| 组件 | 10 个组件全是页面/区块级,样式是一次性 class | ⚠️ 零抽象,复用靠复制 | -| 数据/视图分离 | 少数大组件把 `useQuery` + 钱包 + 渲染揉在一起 | ⚠️ 无法独立渲染/测试 | - -**结论:提炼路线清晰、性价比高,且可增量推进(一个组件一个组件地做,不必停下整个产品)。** - ---- - -## 二、Token 审计 - -### 2.1 现有 38 个 token(全在单个 `:root`,单主题、无暗色) - -| 类别 | 数量 | 样例 | -|---|---|---| -| 字体 | 3 | `--font-sans` `--font-mono` `--font-display`(Fredoka) | -| 中性 / 表面色 | 10 | `--cream` `--cream-2` `--paper` `--paper-2` `--navy` `--navy-2` `--navy-panel` `--slate` `--ink` `--ink-soft` | -| 品牌强调色 | 4 | `--gold` `--gold-2` `--gold-3` `--gold-tint` | -| 语义状态色 | 4 | `--grass`(正) `--grass-tint` · `--coral`(负) `--coral-tint` | -| 说明态铜色 | 5 | `--copper` `--copper-deep` `--copper-tint` `--copper-tint-2` `--copper-line`(只服务一个模块) | -| 形状 | 4 | `--bw`(2px) `--bw-thick`(3px) `--r`(16px) `--r-sm`(12px) | -| 阴影 | 5 | `--shadow-hard` `--shadow-hard-sm`(主)· `--shadow-sm/md/lg`(legacy) | -| 别名 | 3 | `--accent` `--accent-strong` `--focus-ring` | - -### 2.2 三个关键缺口(要补的核心) - -1. **无间距尺度** —— 全文件 128 个裸 `gap`、87 个裸 `padding`。数据显示已隐含一套 ~4px 基准(高频值 8 / 10 / 12 / 14 / 16 / 18),只是从没被命名成 token。 -2. **无字阶体系** —— 132 个裸 `font-size`,没有 `--text-sm/base/lg` 阶梯,也没有行高 / 字重 token。 -3. **无动效 / 层级 / 断点 token** —— 响应式断点 `980px` `640px` 在文件里写死 10+ 次;没有 `z-index` 层级,没有 `duration` / `easing`。 - -### 2.3 冗余与不一致(收敛目标) - -- **双套阴影并存**:sticker 硬阴影(`--shadow-hard*`)+ legacy 软阴影(`--shadow-sm/md/lg`),源码注释自己已标 "Legacy"。需定主次,逐步淘汰一套。 -- **颜色按"颜料名"而非"角色"命名**:`--gold` / `--navy` / `--cream`,而非 `--accent` / `--text` / `--surface`。这是**做不了换肤 / 暗色**的根因。 -- **状态词表分裂成三套**:`good/warn/neutral`(校验)、`active/ready/attention/done`(持仓)、`validation-*`(链上)——同一个"语义状态"概念三种叫法,应合并为一个 `Tone` enum。 - ---- - -## 三、组件审计 - -### 3.1 运行时耦合分布(10 个非测试组件) - -| 纯展示(可直接抽) | 耦合 wallet/query(需"容器 + 展示"解耦) | -|---|---| -| `AppHeader` `HomePage` `DualInvestmentPage` `DualInvestmentQuoteDetail` `DualInvestmentQuoteSections` `PayoffChart` | `DashboardPage` `DashboardProductNoteCard` `DashboardClaimAction` `TargetBuyExecutionPanel` `WalletConnectButton`* | - -> \* `WalletConnectButton` 仅 1 行,转发第三方 `@mysten/dapp-kit-react` 的 `ConnectButton`,**不是重构目标**。真正的耦合大户是大卡片:`DashboardProductNoteCard`、`TargetBuyExecutionPanel`。 - -### 3.2 原子组件其实已存在,只是"埋"在页面 class 里 - -以 `DashboardProductNoteCard` 一个文件为证,内含 6 个可复用原语: - -| 现状 class | 实际是 | 复用信号 | -|---|---|---| -| `.detail-panel` | **Card / Panel** | 多处复用 | -| `.di-status-pill is-${tone}` | **Badge**(带 tone 变体) | 教科书级变体组件 | -| `.di-position-stats > div` | **Stat**(标签 + 值 + 副值) | 一卡内重复 3 次 | -| `.oracle-meta > div(span + dd)` | **KeyValueRow** | 一卡内重复 **~20 次** | -| `.di-position-proof`(`
`) | **Disclosure** | — | -| `ProofLink`(已是局部函数) | **Link** | 已半成形,信号最好 | - -### 3.3 Button:重复最严重的点 - -`.primary-action` / `.secondary-action` / `.ghost-button` / `.small-action` / `.subscribe-button` / `.redeem-action` / `.preview-action` / `.view-more` -—— **8 个 class 名,实际只是 3–4 个变体**,定义散落在文件 **6+ 个位置**,`hover`/`active` 在响应式块里反复重写。应合并为单个 `Button(variant, size)`。 - ---- - -## 四、推荐:三层 Token 方案 - -``` -原始层 (primitive) 语义层 (semantic) 组件层 (component, 按需) ---gold-500: #eaa53a → --color-accent → --btn-bg: var(--color-accent) ---navy-900: #20304d → --color-text / --border → --card-shadow: var(--shadow-card) ---space-4: 8px (新增) → --space-inline / -stack → ... -``` - -**铁律:组件只准引用语义层(角色),不准直接碰原始色值。** - -- **换肤 / 暗色** = 只改"语义层 → 原始层"这一层映射,所有组件零改动。 -- **迁移可渐进**:先给现有 38 个套上语义别名(零破坏),再逐步把组件里的裸 px 换成新增的 `--space-*` / `--text-*`。 - ---- - -## 五、第一批原子组件抽取清单(按优先级) - -| 优先级 | 组件 | 来源 class | 变体 | 需解耦数据? | -|---|---|---|---|---| -| **P0** | `Button` | 8 个 `*-action` 类 | variant: primary/secondary/ghost · size: sm/md | 否 | -| **P0** | `Card` / `Panel` | `.detail-panel` | 默认 / 强调 | 否 | -| **P0** | `Badge` | `.di-status-pill` `.quote-badge` | tone:(统一后)neutral/positive/warning/done | 否 | -| **P0** | `Stat` | `.di-position-stats` `.pair-stat` | 带 / 不带副值 | 否 | -| **P1** | `KeyValueRow` / `DescriptionList` | `.oracle-meta` | — | 否 | -| **P1** | `Tabs` | `.mode-tabs` `.direction-tabs` `.return-scenario-tabs` | — | 否 | -| **P1** | `Disclosure` | `.di-position-proof` | — | 否 | -| **P1** | `Field` / `Input` | `.di-input-wrap` `.custom-grid` | — | 否 | -| **P2** | `DataTable` | `.offer-table` `.di-reference-table` | — | 视用法 | - -> P0 这一组全部**无运行时耦合**,可以立即开始抽,不需要碰 wallet/query。 - ---- - -## 六、路线图 - -``` -①定地基 ②抽原子组件 ③回填 ④(可选)沙盒 ⑤(可选)进工具 -token 三层 → 把 inline 样式 → 用 - , - ); - - const action = screen.getByRole('button', { name: 'Subscribe Buy Low' }); - const dock = action.parentElement; - expect(screen.getAllByRole('button', { name: 'Subscribe Buy Low' })).toHaveLength(1); - expect(dock).not.toHaveClass('is-floating'); - - act(() => { - IntersectionObserverStub.callback?.( - [intersectionEntry({ isIntersecting: false, top: 900 })], - {} as IntersectionObserver, - ); - }); - expect(dock).not.toHaveClass('is-floating'); - - act(() => { - IntersectionObserverStub.callback?.( - [intersectionEntry({ isIntersecting: true, top: 300 })], - {} as IntersectionObserver, - ); - }); - act(() => { - IntersectionObserverStub.callback?.( - [intersectionEntry({ isIntersecting: false, top: -20 })], - {} as IntersectionObserver, - ); - }); - - expect(dock).toHaveClass('is-floating'); - expect(screen.getAllByRole('button', { name: 'Subscribe Buy Low' })).toHaveLength(1); - - act(() => { - IntersectionObserverStub.callback?.( - [intersectionEntry({ isIntersecting: true, top: 120 })], - {} as IntersectionObserver, - ); - }); - expect(dock).not.toHaveClass('is-floating'); - }); -}); diff --git a/src/ui/Stat.stories.tsx b/src/ui/Stat.stories.tsx deleted file mode 100644 index 27cf977..0000000 --- a/src/ui/Stat.stories.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; -import { Stat, StatGroup } from './Stat'; - -const meta = { - title: 'Primitives/Stat', - component: Stat, - args: { label: 'Deposit', value: '500 dUSDC' }, -} satisfies Meta; - -export default meta; -type Story = StoryObj; - -export const Group: Story = { - render: () => ( -
- - - - - -
- ), -}; diff --git a/src/ui/Tabs.stories.tsx b/src/ui/Tabs.stories.tsx deleted file mode 100644 index 12ce272..0000000 --- a/src/ui/Tabs.stories.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; -import { Tab, Tabs } from './Tabs'; - -const meta = { - title: 'Primitives/Tabs', - component: Tabs, -} satisfies Meta; - -export default meta; -type Story = StoryObj; - -export const Default: Story = { - render: () => ( - - Buy Low - Sell High - - ), -}; diff --git a/src/ui/index.ts b/src/ui/index.ts index b4e9480..9f9febf 100644 --- a/src/ui/index.ts +++ b/src/ui/index.ts @@ -10,13 +10,9 @@ export { KeyValue, KeyValueList } from './KeyValue'; export type { KeyValueProps } from './KeyValue'; export { Dialog } from './Dialog'; export type { DialogProps } from './Dialog'; -export { Disclosure } from './Disclosure'; -export type { DisclosureProps } from './Disclosure'; export { InputField } from './Field'; export type { InputFieldProps } from './Field'; export { Tabs, Tab, tabClassName } from './Tabs'; export type { TabProps } from './Tabs'; export { MobileDisclosure } from './MobileDisclosure'; export type { MobileDisclosureProps } from './MobileDisclosure'; -export { MobileActionDock } from './MobileActionDock'; -export type { MobileActionDockProps } from './MobileActionDock'; From 1032118a195eaeaf4b58f9dd716202d52c3218bc Mon Sep 17 00:00:00 2001 From: Chenjie <131379140+cl-fi@users.noreply.github.com> Date: Mon, 27 Jul 2026 20:09:30 +0800 Subject: [PATCH 2/3] =?UTF-8?q?refactor:=20serve=20live=20data=20or=20an?= =?UTF-8?q?=20honest=20error=20=E2=80=94=20remove=20demo-era=20fixture=20t?= =?UTF-8?q?iers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The competition live demo is over; the app now runs for real users. Remove both stand-in data layers so unavailable data surfaces as an error state instead of canned market data (ADR-0011): - Demo Mode (NEXT_PUBLIC_ANKER_DEMO_MODE): fixture data routes, disabled transaction builders, demo banner, and all related copy/CSS - Snapshot tier (ADR-0004): the committed 2026-07-12 day photograph, legacyOracles parsers, SnapshotBanner, TenorSource row provenance, frozen-clock plumbing, and the capture script - SnapshotQuoteProvider: quoting without an SVI surface now throws instead of inventing non-executable prices - Benchmark recorder records live inputs only; historical 'snapshot' sample rows remain valid in the schema Deterministic Playwright fixtures stay, now gated solely by isDeterministicE2E(); the unit-test market fixture moves to src/test/. Co-Authored-By: Claude Fable 5 --- .env.example | 1 - CONTEXT.md | 15 +- app/api/binance/dual-investment/route.ts | 4 +- app/api/markets/btc-oracles/route.test.ts | 10 +- app/api/markets/btc-oracles/route.ts | 4 +- app/api/predict/[...path]/route.test.ts | 19 - app/api/predict/[...path]/route.ts | 4 +- ...011-live-data-or-error-no-demo-fixtures.md | 15 + scripts/capture-day-snapshot.mjs | 131 - src/components/AppHeader.tsx | 98 +- src/components/DualInvestmentMobileCommit.tsx | 10 - src/components/DualInvestmentPage.test.tsx | 67 +- src/components/DualInvestmentPage.tsx | 75 +- src/components/DualInvestmentQuoteDetail.tsx | 30 +- .../DualInvestmentQuoteSections.tsx | 18 +- src/components/PortfolioClaimAction.tsx | 13 +- src/components/PortfolioPage.tsx | 5 +- src/components/PortfolioProductNoteCard.tsx | 12 +- src/components/SendDialog.tsx | 5 +- src/components/SettlementSelect.tsx | 15 +- src/components/SnapshotBanner.tsx | 31 - src/components/TargetBuyExecutionPanel.tsx | 4 +- src/config/runtimeModes.ts | 17 +- src/deepbook/binanceDualInvestment.ts | 2 +- src/deepbook/dayScaleFixtures.ts | 2 +- src/deepbook/legacyOracles.test.ts | 82 - src/deepbook/legacyOracles.ts | 159 - src/deepbook/quoteProvider.test.ts | 39 +- src/deepbook/quoteProvider.ts | 50 +- src/hooks/useDualInvestmentScan.test.ts | 2 +- src/hooks/useDualInvestmentScan.ts | 6 +- src/hooks/useMarketData.test.ts | 2 - src/hooks/useMarketData.ts | 14 +- src/i18n/copy.ts | 44 +- src/products/dualInvestment.test.ts | 2 +- src/products/dualInvestmentScan.test.ts | 2 +- src/products/dualInvestmentScan.ts | 5 +- src/products/payoff.test.ts | 2 +- src/products/tenorMarkets.test.ts | 11 +- src/products/tenorMarkets.ts | 16 - src/recorder/buildBenchmarkRun.test.ts | 2 +- src/recorder/executeBenchmarkSweep.ts | 1 - src/recorder/loadAnalyticsStats.test.ts | 1 - src/recorder/loadAnalyticsStats.ts | 6 +- src/recorder/loadRecorderInputs.ts | 75 +- src/server/curatedOracles.dayLadder.test.ts | 50 +- src/server/curatedOracles.ts | 88 +- src/server/daySnapshot.data.json | 8143 ----------------- src/server/daySnapshot.test.ts | 28 - src/server/daySnapshot.ts | 51 - src/server/deterministicPredictFixtures.ts | 8 +- src/styles.css | 60 - src/sui/accountTransactions.test.ts | 13 - src/sui/accountTransactions.ts | 9 - src/sui/ankerTransactions.test.ts | 19 - src/sui/ankerTransactions.ts | 8 - src/sui/sendTransactions.ts | 4 - src/sui/subscribeTransactions.ts | 8 - .../marketSnapshotFixture.ts} | 0 tests/deepharbor.spec.ts | 4 +- 60 files changed, 212 insertions(+), 9409 deletions(-) create mode 100644 docs/adr/0011-live-data-or-error-no-demo-fixtures.md delete mode 100644 scripts/capture-day-snapshot.mjs delete mode 100644 src/components/SnapshotBanner.tsx delete mode 100644 src/deepbook/legacyOracles.test.ts delete mode 100644 src/deepbook/legacyOracles.ts delete mode 100644 src/server/daySnapshot.data.json delete mode 100644 src/server/daySnapshot.test.ts delete mode 100644 src/server/daySnapshot.ts rename src/{deepbook/fixtures.ts => test/marketSnapshotFixture.ts} (100%) diff --git a/.env.example b/.env.example index 105e469..aefadce 100644 --- a/.env.example +++ b/.env.example @@ -40,7 +40,6 @@ NEXT_PUBLIC_SITE_URL=https://www.ankerprotocol.xyz # ENOKI_PRIVATE_API_KEY= # --- Feature flags (default off; never enable E2E on Production) --- -# NEXT_PUBLIC_ANKER_DEMO_MODE=false # ENABLE_EXPERIMENTAL_PRODUCTS=false # NEXT_PUBLIC_ENABLE_EXPERIMENTAL_PRODUCTS=false # ANKER_DETERMINISTIC_E2E=false diff --git a/CONTEXT.md b/CONTEXT.md index 717a29b..0f9868a 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -80,7 +80,7 @@ _Avoid_: per-tenor line (retired chart grouping — a market's history jumped be **Benchmark Sample (基准采样)**: One recorded comparison for one day-tenor ladder row at one instant: the Anker quote, the Benchmark result (or its absence), and the data-source condition at that moment. -_Avoid_: snapshot / 快照 (reserved for the day-browse fallback photograph) +_Avoid_: snapshot / 快照 (names the retired day-browse fallback tier, ADR-0011) **Run (采样轮)**: One scheduled sweep that records Benchmark Samples across the whole day-tenor ladder. Degraded or failed sweeps are recorded as such, never silently skipped. @@ -101,13 +101,6 @@ A DeepBook Predict schedule that continuously creates per-expiry markets (curren One DeepBook Predict market object for a single expiry timestamp, discovered from the Predict indexer; where legs are minted, priced, and settled. _Avoid_: oracle market (pre-6-24 vocabulary), pool -**Legacy Oracle (旧部署 oracle)**: -An expiry oracle object on the retired 4-16 deployment; its price feed stopped updating on 2026-07-12. No longer a market-data source: its prices had drifted far from the real market, so day browse never reads it live (ADR-0004). The term survives only to name the stored format of the Snapshot capture. -_Avoid_: fixture (Snapshot data is real, not canned), old market, live tier (historical usage — it was once the mid ladder tier for day browse, removed for untrustworthy data) - -**Snapshot (行情快照)**: -Real market data — Legacy Oracle states plus the matching Binance benchmark — captured at one recorded instant and committed to the repo. Rendered as a frozen photograph of that instant: countdowns, expiry dates, and the Binance comparison all display as of the capture time, under a visible "snapshot as of