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/.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/.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/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