From 519423cb0df7467bd48a4e033d0a9953e1419e4d Mon Sep 17 00:00:00 2001 From: richblack Date: Thu, 7 May 2026 16:52:01 +0800 Subject: [PATCH] feat(arcrun): mira wiki page with tag filter + accumulated WIP MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - landing/app/mira/wiki: tag=mira-wiki list now shows all wiki paragraphs (depends on KBDB tag filter exposed in matrix/kbdb commit, separate repo) - landing: app/mira hub + feed split + various WIP from prior sessions - registry/components: claude_api / kbdb_create_block / kbdb_get / km_writer / platform_crypto / auth_oauth2 contracts + main.go (accumulated) - .component-builds: pkg-lock updates + index.ts adjustments (WIP) - .agents/specs/arcrun/frontend-redesign: design notes - docs/test_credentials, docs/user_requirements/arcrun-landing-page: WIP docs - cypher-executor: auth-dispatcher / wasi-shim adjustments (WIP) Includes accumulated work from prior sessions plus the wiki UI tag-filter update that surfaces the AI-generated wiki paragraphs at /mira/wiki. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.7 --- .../design-source/SOURCE_README.md | 25 + .../frontend-redesign/design-source/app.jsx | 56 + .../design-source/components/chrome.jsx | 92 + .../design-source/components/primitives.jsx | 86 + .../design-source/design-chat.md | 95 + .../design-source/index.html | 1211 +++++++++++++ .../design-source/screens/ApiKeys.jsx | 128 ++ .../design-source/screens/Auth.jsx | 90 + .../design-source/screens/Dashboard.jsx | 126 ++ .../design-source/screens/Landing.jsx | 168 ++ .../design-source/screens/WorkflowViewer.jsx | 255 +++ .../specs/arcrun/frontend-redesign/design.md | 300 ++++ .../arcrun/frontend-redesign/requirements.md | 159 ++ .../specs/arcrun/frontend-redesign/tasks.md | 140 ++ .../ai_transform_compile/src/index.ts | 11 +- .../ai_transform_run/src/index.ts | 11 +- .component-builds/array_ops/src/index.ts | 11 +- .../auth_oauth2/package-lock.json | 1539 +++++++++++++++++ .component-builds/auth_oauth2/package.json | 14 + .component-builds/auth_oauth2/src/index.ts | 86 + .component-builds/auth_oauth2/tsconfig.json | 11 + .component-builds/auth_oauth2/wrangler.toml | 21 + .../auth_service_account/src/index.ts | 12 +- .../auth_static_key/src/index.ts | 12 +- .../claude_api/package-lock.json | 1539 +++++++++++++++++ .component-builds/claude_api/package.json | 14 + .component-builds/claude_api/src/index.ts | 92 + .component-builds/claude_api/tsconfig.json | 11 + .component-builds/claude_api/wrangler.toml | 11 + .component-builds/cron/src/index.ts | 12 +- .component-builds/date_ops/src/index.ts | 11 +- .component-builds/filter/src/index.ts | 11 +- .../foreach_control/src/index.ts | 11 +- .component-builds/gmail/src/index.ts | 12 +- .component-builds/google_sheets/src/index.ts | 12 +- .component-builds/http_request/src/index.ts | 25 +- .component-builds/if_control/src/index.ts | 11 +- .../kbdb_create_block/package-lock.json | 1539 +++++++++++++++++ .../kbdb_create_block/package.json | 14 + .../kbdb_create_block/src/index.ts | 74 + .../kbdb_create_block/tsconfig.json | 11 + .../kbdb_create_block/wrangler.toml | 11 + .../kbdb_ingest/package-lock.json | 1539 +++++++++++++++++ .component-builds/kbdb_ingest/package.json | 14 + .component-builds/kbdb_ingest/src/index.ts | 82 + .component-builds/kbdb_ingest/tsconfig.json | 11 + .component-builds/kbdb_ingest/wrangler.toml | 11 + .../kbdb_patch_block/package-lock.json | 1539 +++++++++++++++++ .../kbdb_patch_block/package.json | 14 + .../kbdb_patch_block/src/index.ts | 80 + .../kbdb_patch_block/tsconfig.json | 11 + .../kbdb_patch_block/wrangler.toml | 11 + .component-builds/km_writer/package.json | 14 + .component-builds/km_writer/pnpm-lock.yaml | 898 ++++++++++ .component-builds/km_writer/src/index.ts | 77 + .component-builds/km_writer/tsconfig.json | 11 + .component-builds/km_writer/wrangler.toml | 11 + .component-builds/line_notify/src/index.ts | 12 +- .component-builds/merge/src/index.ts | 11 +- .component-builds/number_ops/src/index.ts | 11 +- .../platform_crypto/package.json | 14 + .../platform_crypto/pnpm-lock.yaml | 898 ++++++++++ .../platform_crypto/src/index.ts | 108 ++ .../platform_crypto/tsconfig.json | 11 + .../platform_crypto/wrangler.toml | 14 + .component-builds/set/src/index.ts | 11 +- .component-builds/string_ops/src/index.ts | 11 +- .component-builds/switch/src/index.ts | 11 +- .component-builds/telegram/src/index.ts | 12 +- .component-builds/try_catch/src/index.ts | 11 +- .component-builds/validate_json/src/index.ts | 11 +- .component-builds/wait/src/index.ts | 11 +- .github/workflows/deploy.yml | 12 + .../src/actions/auth-dispatcher.ts | 6 +- cypher-executor/src/lib/kbdb-partner.ts | 81 + cypher-executor/src/lib/wasi-shim.ts | 345 +++- cypher-executor/src/routes/auth.ts | 88 +- cypher-executor/wrangler.toml | 2 + .../stanley-494303-2f8ae8098cc8.json | 13 + .../arcrun-landing-page/app.jsx | 56 + .../arcrun-landing-page/components/chrome.jsx | 92 + .../components/primitives.jsx | 86 + .../arcrun-landing-page/index.html | 1211 +++++++++++++ .../arcrun-landing-page/screens/ApiKeys.jsx | 128 ++ .../arcrun-landing-page/screens/Auth.jsx | 90 + .../arcrun-landing-page/screens/Dashboard.jsx | 126 ++ .../arcrun-landing-page/screens/Landing.jsx | 168 ++ .../screens/WorkflowViewer.jsx | 255 +++ .../arcrun-landing-page/styles.css | 1181 +++++++++++++ landing/app/api-docs/page.tsx | 18 +- landing/app/components/AppLauncher.tsx | 90 + landing/app/components/SiteNav.tsx | 82 + landing/app/components/apps.ts | 54 + landing/app/dashboard/page.tsx | 23 +- landing/app/integrations/page.tsx | 15 +- landing/app/mira/_shared/markdown.tsx | 63 + landing/app/mira/feed/page.tsx | 1459 ++++++++++++++++ landing/app/mira/layout.tsx | 87 + landing/app/mira/mira.css | 757 ++++++++ landing/app/mira/page.tsx | 122 ++ landing/app/mira/wiki/[pageName]/page.tsx | 165 ++ landing/app/mira/wiki/page.tsx | 269 +++ landing/app/page.tsx | 33 +- landing/middleware.ts | 6 +- landing/package-lock.json | 1498 ++++++++++++++++ landing/package.json | 2 + .../auth_oauth2/component.contract.yaml | 80 + registry/components/auth_oauth2/main.go | 514 ++++++ .../claude_api/component.contract.yaml | 75 + registry/components/claude_api/go.mod | 3 + registry/components/http_request/main.go | 44 +- .../kbdb_create_block/component.contract.yaml | 74 + registry/components/kbdb_create_block/go.mod | 3 + registry/components/kbdb_create_block/main.go | 152 ++ .../kbdb_ingest/component.contract.yaml | 68 + registry/components/kbdb_ingest/go.mod | 3 + registry/components/kbdb_ingest/main.go | 155 ++ .../kbdb_patch_block/component.contract.yaml | 75 + registry/components/kbdb_patch_block/go.mod | 3 + registry/components/kbdb_patch_block/main.go | 155 ++ .../km_writer/component.contract.yaml | 67 + registry/components/km_writer/go.mod | 3 + registry/components/km_writer/main.go | 177 ++ .../platform_crypto/component.contract.yaml | 56 + registry/components/platform_crypto/main.go | 206 +++ registry/package.json | 1 + registry/pnpm-lock.yaml | 16 + 127 files changed, 23909 insertions(+), 264 deletions(-) create mode 100644 .agents/specs/arcrun/frontend-redesign/design-source/SOURCE_README.md create mode 100644 .agents/specs/arcrun/frontend-redesign/design-source/app.jsx create mode 100644 .agents/specs/arcrun/frontend-redesign/design-source/components/chrome.jsx create mode 100644 .agents/specs/arcrun/frontend-redesign/design-source/components/primitives.jsx create mode 100644 .agents/specs/arcrun/frontend-redesign/design-source/design-chat.md create mode 100644 .agents/specs/arcrun/frontend-redesign/design-source/index.html create mode 100644 .agents/specs/arcrun/frontend-redesign/design-source/screens/ApiKeys.jsx create mode 100644 .agents/specs/arcrun/frontend-redesign/design-source/screens/Auth.jsx create mode 100644 .agents/specs/arcrun/frontend-redesign/design-source/screens/Dashboard.jsx create mode 100644 .agents/specs/arcrun/frontend-redesign/design-source/screens/Landing.jsx create mode 100644 .agents/specs/arcrun/frontend-redesign/design-source/screens/WorkflowViewer.jsx create mode 100644 .agents/specs/arcrun/frontend-redesign/design.md create mode 100644 .agents/specs/arcrun/frontend-redesign/requirements.md create mode 100644 .agents/specs/arcrun/frontend-redesign/tasks.md create mode 100644 .component-builds/auth_oauth2/package-lock.json create mode 100644 .component-builds/auth_oauth2/package.json create mode 100644 .component-builds/auth_oauth2/src/index.ts create mode 100644 .component-builds/auth_oauth2/tsconfig.json create mode 100644 .component-builds/auth_oauth2/wrangler.toml create mode 100644 .component-builds/claude_api/package-lock.json create mode 100644 .component-builds/claude_api/package.json create mode 100644 .component-builds/claude_api/src/index.ts create mode 100644 .component-builds/claude_api/tsconfig.json create mode 100644 .component-builds/claude_api/wrangler.toml create mode 100644 .component-builds/kbdb_create_block/package-lock.json create mode 100644 .component-builds/kbdb_create_block/package.json create mode 100644 .component-builds/kbdb_create_block/src/index.ts create mode 100644 .component-builds/kbdb_create_block/tsconfig.json create mode 100644 .component-builds/kbdb_create_block/wrangler.toml create mode 100644 .component-builds/kbdb_ingest/package-lock.json create mode 100644 .component-builds/kbdb_ingest/package.json create mode 100644 .component-builds/kbdb_ingest/src/index.ts create mode 100644 .component-builds/kbdb_ingest/tsconfig.json create mode 100644 .component-builds/kbdb_ingest/wrangler.toml create mode 100644 .component-builds/kbdb_patch_block/package-lock.json create mode 100644 .component-builds/kbdb_patch_block/package.json create mode 100644 .component-builds/kbdb_patch_block/src/index.ts create mode 100644 .component-builds/kbdb_patch_block/tsconfig.json create mode 100644 .component-builds/kbdb_patch_block/wrangler.toml create mode 100644 .component-builds/km_writer/package.json create mode 100644 .component-builds/km_writer/pnpm-lock.yaml create mode 100644 .component-builds/km_writer/src/index.ts create mode 100644 .component-builds/km_writer/tsconfig.json create mode 100644 .component-builds/km_writer/wrangler.toml create mode 100644 .component-builds/platform_crypto/package.json create mode 100644 .component-builds/platform_crypto/pnpm-lock.yaml create mode 100644 .component-builds/platform_crypto/src/index.ts create mode 100644 .component-builds/platform_crypto/tsconfig.json create mode 100644 .component-builds/platform_crypto/wrangler.toml create mode 100644 cypher-executor/src/lib/kbdb-partner.ts create mode 100644 docs/test_credentials/stanley-494303-2f8ae8098cc8.json create mode 100644 docs/user_requirements/arcrun-landing-page/app.jsx create mode 100644 docs/user_requirements/arcrun-landing-page/components/chrome.jsx create mode 100644 docs/user_requirements/arcrun-landing-page/components/primitives.jsx create mode 100644 docs/user_requirements/arcrun-landing-page/index.html create mode 100644 docs/user_requirements/arcrun-landing-page/screens/ApiKeys.jsx create mode 100644 docs/user_requirements/arcrun-landing-page/screens/Auth.jsx create mode 100644 docs/user_requirements/arcrun-landing-page/screens/Dashboard.jsx create mode 100644 docs/user_requirements/arcrun-landing-page/screens/Landing.jsx create mode 100644 docs/user_requirements/arcrun-landing-page/screens/WorkflowViewer.jsx create mode 100644 docs/user_requirements/arcrun-landing-page/styles.css create mode 100644 landing/app/components/AppLauncher.tsx create mode 100644 landing/app/components/SiteNav.tsx create mode 100644 landing/app/components/apps.ts create mode 100644 landing/app/mira/_shared/markdown.tsx create mode 100644 landing/app/mira/feed/page.tsx create mode 100644 landing/app/mira/layout.tsx create mode 100644 landing/app/mira/mira.css create mode 100644 landing/app/mira/page.tsx create mode 100644 landing/app/mira/wiki/[pageName]/page.tsx create mode 100644 landing/app/mira/wiki/page.tsx create mode 100644 registry/components/auth_oauth2/component.contract.yaml create mode 100644 registry/components/auth_oauth2/main.go create mode 100644 registry/components/claude_api/component.contract.yaml create mode 100644 registry/components/claude_api/go.mod create mode 100644 registry/components/kbdb_create_block/component.contract.yaml create mode 100644 registry/components/kbdb_create_block/go.mod create mode 100644 registry/components/kbdb_create_block/main.go create mode 100644 registry/components/kbdb_ingest/component.contract.yaml create mode 100644 registry/components/kbdb_ingest/go.mod create mode 100644 registry/components/kbdb_ingest/main.go create mode 100644 registry/components/kbdb_patch_block/component.contract.yaml create mode 100644 registry/components/kbdb_patch_block/go.mod create mode 100644 registry/components/kbdb_patch_block/main.go create mode 100644 registry/components/km_writer/component.contract.yaml create mode 100644 registry/components/km_writer/go.mod create mode 100644 registry/components/km_writer/main.go create mode 100644 registry/components/platform_crypto/component.contract.yaml create mode 100644 registry/components/platform_crypto/main.go diff --git a/.agents/specs/arcrun/frontend-redesign/design-source/SOURCE_README.md b/.agents/specs/arcrun/frontend-redesign/design-source/SOURCE_README.md new file mode 100644 index 0000000..9e1ca3d --- /dev/null +++ b/.agents/specs/arcrun/frontend-redesign/design-source/SOURCE_README.md @@ -0,0 +1,25 @@ +# CODING AGENTS: READ THIS FIRST + +This is a **handoff bundle** from Claude Design (claude.ai/design). + +A user mocked up designs in HTML/CSS/JS using an AI design tool, then exported this bundle so a coding agent can implement the designs for real. + +## What you should do — IMPORTANT + +**Read the chat transcripts first.** There are 1 chat transcript(s) in `arcrun/chats/`. The transcripts show the full back-and-forth between the user and the design assistant — they tell you **what the user actually wants** and **where they landed** after iterating. Don't skip them. The final HTML files are the output, but the chat is where the intent lives. + +**Find the primary design file under `arcrun/project/` and read it top to bottom.** The chat transcripts will tell you which file the user was last iterating on. Then **follow its imports**: open every file it pulls in (shared components, CSS, scripts) so you understand how the pieces fit together before you start implementing. + +**If anything is ambiguous, ask the user to confirm before you start implementing.** It's much cheaper to clarify scope up front than to build the wrong thing. + +## About the design files + +The design medium is **HTML/CSS/JS** — these are prototypes, not production code. Your job is to **recreate them pixel-perfectly** in whatever technology makes sense for the target codebase (React, Vue, native, whatever fits). Match the visual output; don't copy the prototype's internal structure unless it happens to fit. + +**Don't render these files in a browser or take screenshots unless the user asks you to.** Everything you need — dimensions, colors, layout rules — is spelled out in the source. Read the HTML and CSS directly; a screenshot won't tell you anything they don't. + +## Bundle contents + +- `arcrun/README.md` — this file +- `arcrun/chats/` — conversation transcripts (read these!) +- `arcrun/project/` — the `arcrun` project files (HTML prototypes, assets, components) diff --git a/.agents/specs/arcrun/frontend-redesign/design-source/app.jsx b/.agents/specs/arcrun/frontend-redesign/design-source/app.jsx new file mode 100644 index 0000000..23a9f42 --- /dev/null +++ b/.agents/specs/arcrun/frontend-redesign/design-source/app.jsx @@ -0,0 +1,56 @@ +// App root — screen switcher with persistent route +const { useState, useEffect } = React; + +const SCREENS = [ + { id: 'landing', label: 'Landing' }, + { id: 'auth', label: 'Auth' }, + { id: 'dashboard', label: 'Dashboard' }, + { id: 'keys', label: 'API Keys' }, + { id: 'workflow', label: 'Workflow' }, +]; + +// Synonyms from sidebar ids +const aliases = { apps: 'dashboard', workflows: 'dashboard', docs: 'landing', settings: 'keys' }; + +function App() { + const [screen, setScreen] = useState(() => { + const saved = localStorage.getItem('arcrun:screen'); + return saved && SCREENS.some(s => s.id === saved) ? saved : 'landing'; + }); + + useEffect(() => { + localStorage.setItem('arcrun:screen', screen); + window.scrollTo(0, 0); + }, [screen]); + + const nav = (id) => { + const resolved = aliases[id] || id; + if (SCREENS.some(s => s.id === resolved)) setScreen(resolved); + }; + + const Current = { + landing: Landing, + auth: Auth, + dashboard: Dashboard, + keys: ApiKeys, + workflow: WorkflowViewer, + }[screen]; + + return ( +
+ + +
+ {SCREENS.map(s => ( + + ))} +
+
+ ); +} + +ReactDOM.createRoot(document.getElementById('root')).render(); diff --git a/.agents/specs/arcrun/frontend-redesign/design-source/components/chrome.jsx b/.agents/specs/arcrun/frontend-redesign/design-source/components/chrome.jsx new file mode 100644 index 0000000..b14ed26 --- /dev/null +++ b/.agents/specs/arcrun/frontend-redesign/design-source/components/chrome.jsx @@ -0,0 +1,92 @@ +// Top nav and sidebar + +const TopNav = ({ onNav, current }) => { + const [scrolled, setScrolled] = React.useState(false); + React.useEffect(() => { + const onScroll = () => setScrolled(window.scrollY > 8); + window.addEventListener('scroll', onScroll); + return () => window.removeEventListener('scroll', onScroll); + }, []); + return ( + + ); +}; + +const Footer = ({ onNav }) => ( + +); + +// App shell with sidebar for logged-in screens +const Sidebar = ({ current, onNav }) => { + const items = [ + { id: 'dashboard', label: 'Dashboard', icon: 'home' }, + { id: 'apps', label: 'Apps', icon: 'grid', count: 6 }, + { id: 'workflows', label: 'Workflows', icon: 'workflow', count: 12 }, + { id: 'keys', label: 'API Keys', icon: 'key' }, + { id: 'docs', label: 'Docs', icon: 'book' }, + ]; + const bottom = [ + { id: 'settings', label: 'Settings', icon: 'settings' }, + ]; + return ( + + ); +}; + +Object.assign(window, { TopNav, Footer, Sidebar }); diff --git a/.agents/specs/arcrun/frontend-redesign/design-source/components/primitives.jsx b/.agents/specs/arcrun/frontend-redesign/design-source/components/primitives.jsx new file mode 100644 index 0000000..1aa5fd5 --- /dev/null +++ b/.agents/specs/arcrun/frontend-redesign/design-source/components/primitives.jsx @@ -0,0 +1,86 @@ +// Shared primitives: icons, logo, etc. + +const Icon = ({ name, size = 16, stroke = 1.7 }) => { + const paths = { + arrow_right: , + arrow_left: , + plus: , + copy: <>, + check: , + close: , + eye: <>, + search: <>, + warn: <>, + home: <>, + grid: <>, + workflow: <>, + key: <>, + book: <>, + settings: <>, + chevron_right: , + chevron_down: , + external: <>, + trash: <>, + spark: , + bolt: , + github: , + google: <>, + share: <>, + download: <>, + zoom_in: <>, + zoom_out: <>, + maximize: <>, + slack: <>, + database: <>, + mail: <>, + filter: , + star: , + linear: <>, + clock: <>, + send: <>, + terminal: <>, + logout: <>, + }; + + return ( + + {paths[name]} + + ); +}; + +// Arcrun wordmark — custom "arc" glyph made of an arc stroke + ascending dot/node +const Logo = ({ size = 'md', onClick }) => { + const dims = size === 'sm' ? { w: 18, h: 18, f: 10 } : size === 'lg' ? { w: 28, h: 28, f: 14 } : { w: 22, h: 22, f: 12 }; + return ( +
+ + + + + + + Arcrun +
+ ); +}; + +// App icon with gradient background +const AppIcon = ({ tone = 'indigo', children, size = 38 }) => { + const tones = { + indigo: 'linear-gradient(135deg, #6366F1, #8B5CF6)', + orange: 'linear-gradient(135deg, #F59E0B, #EF4444)', + green: 'linear-gradient(135deg, #10B981, #22C55E)', + pink: 'linear-gradient(135deg, #EC4899, #8B5CF6)', + blue: 'linear-gradient(135deg, #3B82F6, #06B6D4)', + slate: 'linear-gradient(135deg, #475569, #334155)', + amber: 'linear-gradient(135deg, #F59E0B, #D97706)', + }; + return ( +
+ {children} +
+ ); +}; + +Object.assign(window, { Icon, Logo, AppIcon }); diff --git a/.agents/specs/arcrun/frontend-redesign/design-source/design-chat.md b/.agents/specs/arcrun/frontend-redesign/design-source/design-chat.md new file mode 100644 index 0000000..7088767 --- /dev/null +++ b/.agents/specs/arcrun/frontend-redesign/design-source/design-chat.md @@ -0,0 +1,95 @@ +# Chat + +_Started 2026-04-23 04:37 UTC_ + +--- + +## User + + + +## Assistant + +I'll design Arcrun as a cohesive 5-screen system. Let me plan and build this as an interactive prototype with all screens navigable. + +_[tool: update_todos]_ + +Let me plan the architecture. I'll build a single HTML file with React, using multiple JSX component files, with screen navigation via state. + +Design system note: I'll use Inter, electric indigo #6366F1 primary, #0F0F0F bg, #1A1A1A cards, with subtle indigo→violet gradients on CTAs. Dark, minimal, developer-focused. "Arcrun" wordmark will be original — a stylized "↯" or arc mark paired with the name. + +Let me start building. + +_[tool: write_file]_ + +_[tool: write_file]_ + +Now the primitives and chrome components: + +_[tool: write_file]_ + +_[tool: write_file]_ + +_[tool: snip]_ + +Now the 5 screens. Let me write them concisely. + +_[tool: write_file]_ + +_[tool: write_file]_ + +_[tool: snip]_ + +_[tool: write_file]_ + +_[tool: write_file]_ + +_[tool: snip]_ + +_[tool: write_file]_ + +_[tool: write_file]_ + +_[tool: snip]_ + +_[tool: update_todos]_ + +_[tool: register_assets]_ + +_[tool: done]_ + +_[tool: snip]_ + +_[tool: list_files]_ + +The file exists. The error may have been transient. Let me verify by checking the page loads. + +_[tool: show_html]_ + +_[tool: snip]_ + +_[tool: done]_ + +The CSS exists but isn't loading through the preview path. Let me inline it to eliminate the dependency. + +_[tool: read_file]_ + +_[tool: run_script]_ + +_[tool: snip]_ + +_[tool: done]_ + +_[tool: fork_verifier_agent]_ + +Built **Arcrun** as a cohesive 5-screen dark-mode prototype — Landing, Auth, API Keys, Dashboard, and Workflow Viewer — all navigable via the floating screen switcher at the bottom. + +**Highlights** +- **Landing** — hero with grid/glow bg, two-path split (developer terminal w/ npm·pip·bun tabs + Claude-style chat preview showing an Arcrun tool call) +- **Auth** — centered card, Sign in / Sign up tabs, GitHub + Google OAuth, email + password +- **API Keys** — first-time key callout with copy + hide-again warning, full keys table with live status toggles +- **Dashboard** — sidebar shell, 3-col apps grid (+ empty-state card), workflows table below +- **Workflow Viewer** — full-bleed dark canvas, 8 connected nodes with left-to-right bezier arrows, selectable with detail panel (I/O schema, editable triplet config for the AI node), minimap + zoom controls, prominent "Edit in Claude" CTA + +Screen state persists in localStorage so reloads land you where you were. + diff --git a/.agents/specs/arcrun/frontend-redesign/design-source/index.html b/.agents/specs/arcrun/frontend-redesign/design-source/index.html new file mode 100644 index 0000000..8a41c33 --- /dev/null +++ b/.agents/specs/arcrun/frontend-redesign/design-source/index.html @@ -0,0 +1,1211 @@ + + + + + +Arcrun — AI Workflow Automation + + + + + + + + + + +
+ + + + + + + + + + + diff --git a/.agents/specs/arcrun/frontend-redesign/design-source/screens/ApiKeys.jsx b/.agents/specs/arcrun/frontend-redesign/design-source/screens/ApiKeys.jsx new file mode 100644 index 0000000..e39af48 --- /dev/null +++ b/.agents/specs/arcrun/frontend-redesign/design-source/screens/ApiKeys.jsx @@ -0,0 +1,128 @@ +const ApiKeys = ({ onNav }) => { + const [newKeyCopied, setNewKeyCopied] = React.useState(false); + const [keys, setKeys] = React.useState([ + { id: 'k_dev', name: 'Local Development', prefix: 'ar_dev_', created: 'Mar 12, 2026', lastUsed: '2 min ago', active: true }, + { id: 'k_prod', name: 'Production — Northwind API', prefix: 'ar_live_', created: 'Feb 3, 2026', lastUsed: '12 sec ago', active: true }, + { id: 'k_staging', name: 'Staging — Vercel', prefix: 'ar_test_', created: 'Jan 28, 2026', lastUsed: '4 hours ago', active: true }, + { id: 'k_ci', name: 'CI/CD (GitHub Actions)', prefix: 'ar_live_', created: 'Jan 10, 2026', lastUsed: 'Yesterday', active: false }, + { id: 'k_old', name: 'Legacy — Zapier import', prefix: 'ar_live_', created: 'Nov 4, 2025', lastUsed: '3 weeks ago', active: false, revoked: true }, + ]); + + const newKey = 'ar_live_sk_7x9Qf2vLm8nR4TpW6ZjKc3bEhN1aSyU5oP0dI'; + + const copyKey = () => { + setNewKeyCopied(true); + setTimeout(() => setNewKeyCopied(false), 1800); + }; + + const toggleKey = (id) => { + setKeys(keys.map(k => k.id === id ? { ...k, active: !k.active } : k)); + }; + + return ( +
+ +
+
+
+
+ Workspace + + Settings +
+

API Keys

+
Scoped credentials for calling the Arcrun API from your code and CI.
+
+
+ + +
+
+ +
+
+
+ + Save this key now. For security, we won't show it again — if you lose it, you'll need to create a new one. +
+

Your new API key

+

Key named "Production — Northwind API" · created just now · all scopes

+
+ {newKey} + +
+
+ Full workspace access + Never expires + Add expiry or restrict scopes → +
+
+ +
+
+

All keys

+
{keys.filter(k => !k.revoked).length} active · {keys.filter(k => k.revoked).length} revoked
+
+
+ +
+
+ +
+ + + + + + + + + + + + + {keys.map(k => ( + + + + + + + + + ))} + +
NameKeyCreatedLast usedStatus
+
{k.name}
+
{k.prefix}••••{k.id.slice(-4)}{k.created}{k.lastUsed} + {k.revoked ? ( + Revoked + ) : ( +
+ toggleKey(k.id)} /> + + {k.active ? 'Active' : 'Paused'} + +
+ )} +
+ {!k.revoked && ( + + )} +
+
+ +
+ + Revoking a key stops all in-flight requests within 60 seconds. This cannot be undone. +
+
+
+
+ ); +}; + +window.ApiKeys = ApiKeys; diff --git a/.agents/specs/arcrun/frontend-redesign/design-source/screens/Auth.jsx b/.agents/specs/arcrun/frontend-redesign/design-source/screens/Auth.jsx new file mode 100644 index 0000000..cd4ace7 --- /dev/null +++ b/.agents/specs/arcrun/frontend-redesign/design-source/screens/Auth.jsx @@ -0,0 +1,90 @@ +const Auth = ({ onNav }) => { + const [mode, setMode] = React.useState('signin'); + const [email, setEmail] = React.useState(''); + const [pw, setPw] = React.useState(''); + const [remember, setRemember] = React.useState(true); + + const submit = (e) => { e.preventDefault(); onNav('dashboard'); }; + + return ( +
+
+
+ +
+ onNav('landing')} /> +
+ +
+

{mode === 'signin' ? 'Welcome back' : 'Create your account'}

+

{mode === 'signin' ? 'Sign in to your Arcrun workspace.' : 'Start building AI workflows in minutes.'}

+ +
+ + +
+ +
+ + +
+ +
or continue with email
+ +
+ {mode === 'signup' && ( +
+ + +
+ )} +
+ + setEmail(e.target.value)} /> +
+
+
+ + {mode === 'signin' && Forgot password?} +
+ setPw(e.target.value)} /> +
+ + {mode === 'signin' && ( +
+
setRemember(!remember)} + style={{width: 15, height: 15, borderRadius: 4, border: '1px solid var(--line-2)', + background: remember ? 'var(--primary)' : 'transparent', + display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer'}}> + {remember && } +
+ setRemember(!remember)} style={{cursor: 'pointer'}}>Keep me signed in for 30 days +
+ )} + + +
+ + {mode === 'signup' && ( +

+ By signing up, you agree to our Terms and Privacy Policy. +

+ )} + +
+ {mode === 'signin' + ? <>New to Arcrun? setMode('signup')}>Create an account + : <>Already have an account? setMode('signin')}>Sign in} +
+
+
+ ); +}; + +window.Auth = Auth; diff --git a/.agents/specs/arcrun/frontend-redesign/design-source/screens/Dashboard.jsx b/.agents/specs/arcrun/frontend-redesign/design-source/screens/Dashboard.jsx new file mode 100644 index 0000000..b9033aa --- /dev/null +++ b/.agents/specs/arcrun/frontend-redesign/design-source/screens/Dashboard.jsx @@ -0,0 +1,126 @@ +const Dashboard = ({ onNav }) => { + const apps = [ + { id: 'digest', name: 'Weekly Digest', desc: 'Summarize customer activity into a Monday email for the revenue team.', icon: 'mail', tone: 'indigo' }, + { id: 'triage', name: 'Support Triage', desc: 'Classify inbound tickets, attach context from the CRM, and route.', icon: 'filter', tone: 'orange' }, + { id: 'seo', name: 'SEO Brief Generator', desc: 'Turn a keyword into a draft brief with outline, FAQs, and SERP notes.', icon: 'search', tone: 'green' }, + { id: 'slack', name: 'Standup Bot', desc: 'Collect Linear updates and post a tidy engineering standup to Slack.', icon: 'slack', tone: 'pink' }, + { id: 'doc', name: 'Docs Sync', desc: 'Keep Notion runbooks in sync with the production API surface.', icon: 'book', tone: 'blue' }, + ]; + + const workflows = [ + { id: 'digest_weekly', name: 'digest/weekly', nodes: 9, modified: '2 hours ago', runs: '147 runs', status: 'healthy' }, + { id: 'triage_inbound', name: 'triage/inbound-email', nodes: 14, modified: 'Yesterday', runs: '2,318 runs', status: 'healthy' }, + { id: 'seo_brief', name: 'seo/brief-from-keyword', nodes: 7, modified: '3 days ago', runs: '42 runs', status: 'healthy' }, + { id: 'standup', name: 'slack/standup-collector', nodes: 6, modified: '1 week ago', runs: '24 runs', status: 'idle' }, + { id: 'docs_sync', name: 'docs/sync-notion', nodes: 11, modified: '2 weeks ago', runs: '8 runs', status: 'failed' }, + ]; + + return ( +
+ +
+
+
+
+ Northwind + + Dashboard +
+

Welcome back, Maya

+
5 apps running · 12 workflows · 2,538 runs this week
+
+
+ + +
+
+ +
+ {/* Apps grid */} +
+
+

My Apps

+
Packaged workflows your team can run from chat or code
+
+ {apps.length} apps +
+ +
+ {apps.map(a => ( +
+ +

{a.name}

+

{a.desc}

+
+ onNav('workflow')}>Open app + +
+
+ ))} +
+
+
Create new app
+
Start from scratch or template
+
+
+ + {/* Workflows */} +
+
+
+

My Workflows

+
The graphs that power your apps
+
+
+ + +
+
+ +
+ + + + + + + + + + + + + {workflows.map(w => ( + + + + + + + + + ))} + +
WorkflowNodesLast modifiedActivityStatus
+
+ + {w.name} +
+
{w.nodes}{w.modified}{w.runs} + + {w.status} + + + +
+
+
+
+
+
+ ); +}; + +window.Dashboard = Dashboard; diff --git a/.agents/specs/arcrun/frontend-redesign/design-source/screens/Landing.jsx b/.agents/specs/arcrun/frontend-redesign/design-source/screens/Landing.jsx new file mode 100644 index 0000000..460d35c --- /dev/null +++ b/.agents/specs/arcrun/frontend-redesign/design-source/screens/Landing.jsx @@ -0,0 +1,168 @@ +const Landing = ({ onNav }) => { + const [installer, setInstaller] = React.useState('npm'); + const installCmds = { + npm: '$ npm install arcrun', + pip: '$ pip install arcrun', + bun: '$ bun add arcrun', + }; + + return ( +
+ + +
+
+
+
+
+ + Now in public beta — MCP-native +
+

Build AI workflows
without the glue code.

+

Connect your tools, automate your work. Orchestrate workflows from Claude.ai, your IDE, or a few lines of code — Arcrun handles auth, retries, and state.

+
+ + +
+
+ +
+ {/* Developer path */} +
+
+ For Developers +
+

Three lines, any runtime.

+

Install once, call Arcrun from Node, Python, or your edge runtime. OAuth, rate limits, and retries are handled.

+ +
+ {Object.keys(installCmds).map(k => ( + + ))} +
+ +
+
+
+
terminal
+
+
+
{installCmds[installer]}
+
+
+ +
+
+
+
{installer === 'pip' ? 'app.py' : 'app.ts'}
+
+
+ {installer === 'pip' ? ( + <> +
from arcrun import Arcrun
+
+
client = Arcrun(token=os.getenv("ARCRUN_KEY"))
+
run = client.run("digest/weekly", inputs={'{'}"user": "u_219"{'}'})
+ + ) : ( + <> +
import {'{'} Arcrun {'}'} from "arcrun";
+
+
const client = new Arcrun({'{'} token: process.env.ARCRUN_KEY {'}'});
+
const run = await client.run("digest/weekly", {'{'} user: "u_219" {'}'});
+ + )} +
+
+ +
+
+ Typed SDKs + Idempotent runs + Self-host ready +
+
+ + {/* Everyone path */} +
+
+ For Everyone +
+

Talk to your workflows.

+

Install Arcrun inside your AI assistant and run your apps by asking. Trigger workflows, fetch data, or draft messages — in plain English.

+ +
+
+ AI + Your assistant — Arcrun connected + 2 apps +
+
+
+
M
+
Send this week's customer digest to the revenue team.
+
+
+
A
+
+ Running digest/weekly for 147 accounts, then posting to #revenue. +
+
AR
+
+
arcrun · digest/weekly
+
4 of 5 steps complete · 00:12 elapsed
+
+ running +
+
+
+
+
+ Reply to your assistant… + +
+
+ +
+
+ One-click connect + Works in your IDE + Audit trail +
+
+
+ +
+
+
+

Run anywhere

+

Node, Python, Deno, Bun, Cloudflare Workers. One API, same semantics.

+
+
+
+

Composable steps

+

Model calls, HTTP, database, branching — wire them visually or in code.

+
+
+
+

Scoped keys

+

Per-workflow API keys with fine-grained scopes and live revocation.

+
+
+
+

Observable

+

Every run is replayable. Inspect inputs, outputs, and token usage.

+
+
+
+ +
+
+ ); +}; + +window.Landing = Landing; diff --git a/.agents/specs/arcrun/frontend-redesign/design-source/screens/WorkflowViewer.jsx b/.agents/specs/arcrun/frontend-redesign/design-source/screens/WorkflowViewer.jsx new file mode 100644 index 0000000..cb943ed --- /dev/null +++ b/.agents/specs/arcrun/frontend-redesign/design-source/screens/WorkflowViewer.jsx @@ -0,0 +1,255 @@ +const WorkflowViewer = ({ onNav }) => { + const nodes = [ + { id: 'trigger', x: 60, y: 260, title: 'Weekly Schedule', type: 'trigger', badge: 'CRON', icon: 'clock', tone: '#22C55E', + inputs: [], outputs: [{k: 'timestamp', t: 'ISO8601'}, {k: 'runId', t: 'string'}] }, + { id: 'fetch', x: 320, y: 140, title: 'Fetch Accounts', type: 'database.query', badge: 'DB', icon: 'database', tone: '#3B82F6', + inputs: [{k: 'segment', t: 'string'}], outputs: [{k: 'accounts', t: 'Account[]'}, {k: 'count', t: 'number'}] }, + { id: 'events', x: 320, y: 380, title: 'Pull Events', type: 'segment.events', badge: 'API', icon: 'bolt', tone: '#F59E0B', + inputs: [{k: 'since', t: 'ISO8601'}], outputs: [{k: 'events', t: 'Event[]'}] }, + { id: 'summarize', x: 600, y: 260, title: 'Summarize with Claude', type: 'ai.completion', badge: 'AI', icon: 'spark', tone: '#8B5CF6', + inputs: [{k: 'accounts', t: 'Account[]'}, {k: 'events', t: 'Event[]'}, {k: 'prompt', t: 'string'}], + outputs: [{k: 'digest', t: 'Digest'}, {k: 'tokens', t: 'number'}] }, + { id: 'filter', x: 880, y: 160, title: 'Filter — priority ≥ 2', type: 'logic.filter', badge: 'IF', icon: 'filter', tone: '#64748B', + inputs: [{k: 'digest', t: 'Digest'}], outputs: [{k: 'items', t: 'Item[]'}] }, + { id: 'slack', x: 1140, y: 100, title: 'Post to #revenue', type: 'slack.message', badge: 'OUT', icon: 'slack', tone: '#EC4899', + inputs: [{k: 'channel', t: 'string'}, {k: 'blocks', t: 'Block[]'}], outputs: [{k: 'ts', t: 'string'}] }, + { id: 'mail', x: 1140, y: 260, title: 'Email Digest', type: 'mail.send', badge: 'OUT', icon: 'mail', tone: '#6366F1', + inputs: [{k: 'to', t: 'string[]'}, {k: 'subject', t: 'string'}, {k: 'html', t: 'string'}], outputs: [{k: 'messageId', t: 'string'}] }, + { id: 'log', x: 880, y: 400, title: 'Log run metadata', type: 'arcrun.log', badge: 'LOG', icon: 'terminal', tone: '#475569', + inputs: [{k: 'runId', t: 'string'}, {k: 'stats', t: 'Stats'}], outputs: [] }, + ]; + + const edges = [ + ['trigger', 'fetch'], + ['trigger', 'events'], + ['fetch', 'summarize'], + ['events', 'summarize'], + ['summarize', 'filter'], + ['summarize', 'log'], + ['filter', 'slack'], + ['filter', 'mail'], + ]; + + const [selectedId, setSelectedId] = React.useState('summarize'); + const [title, setTitle] = React.useState('digest/weekly'); + const [zoom, setZoom] = React.useState(100); + + const selected = nodes.find(n => n.id === selectedId); + + // Edit triplet inline (for the summarize node's prompt config) + const [triplet, setTriplet] = React.useState({ + model: 'claude-haiku-4-5', + temperature: '0.3', + prompt: 'Summarize this week\'s account activity for the revenue team.', + }); + + // Measure node widths for edge endpoint accuracy + const nodeRefs = React.useRef({}); + const [sizes, setSizes] = React.useState({}); + React.useEffect(() => { + const ns = {}; + for (const n of nodes) { + const el = nodeRefs.current[n.id]; + if (el) ns[n.id] = { w: el.offsetWidth, h: el.offsetHeight }; + } + setSizes(ns); + }, []); + + const getPort = (id, side) => { + const n = nodes.find(x => x.id === id); + const sz = sizes[id] || { w: 200, h: 60 }; + return { + x: side === 'out' ? n.x + sz.w : n.x, + y: n.y + sz.h / 2, + }; + }; + + return ( +
+
+
onNav('dashboard')} title="Back to dashboard"> + +
+ onNav('landing')} /> +
+
+ onNav('dashboard')}>Workflows + + setTitle(e.target.value)} + /> +
+ + + Saved · 2m ago + +
+ + + +
+ +
+ + + + + + + + + + {edges.map(([a, b], i) => { + const p1 = getPort(a, 'out'); + const p2 = getPort(b, 'in'); + const dx = Math.max(40, (p2.x - p1.x) * 0.5); + const d = `M ${p1.x} ${p1.y} C ${p1.x + dx} ${p1.y}, ${p2.x - dx} ${p2.y}, ${p2.x - 2} ${p2.y}`; + const highlight = a === selectedId || b === selectedId; + return ( + + ); + })} + + +
+ {nodes.map(n => ( +
(nodeRefs.current[n.id] = el)} + className={`wf-node ${selectedId === n.id ? 'selected' : ''}`} + style={{left: n.x, top: n.y}} + onClick={() => setSelectedId(n.id)}> + {n.inputs.length > 0 && } + {n.outputs.length > 0 && } +
+ + + + {n.title} + {n.badge} +
+
{n.type}
+
+ ))} +
+ + {/* Detail panel */} + {selected && ( +
+
+ + + +
+

{selected.title}

+
{selected.type}
+
+ +
+
+
+

Input schema

+ {selected.inputs.length === 0 ? ( +
No inputs — this is a trigger.
+ ) : selected.inputs.map(f => ( +
+ {f.k} + {f.t} +
+ ))} +
+ +
+

Output schema

+ {selected.outputs.length === 0 ? ( +
No outputs — terminal node.
+ ) : selected.outputs.map(f => ( +
+ {f.k} + {f.t} +
+ ))} +
+ + {selected.id === 'summarize' && ( +
+

Configuration

+
+
+
model
+ setTriplet({...triplet, model: e.target.value})} /> +
+
+
temp
+ setTriplet({...triplet, temperature: e.target.value})} /> +
+
+
prompt
+ setTriplet({...triplet, prompt: e.target.value})} /> +
+
+
+ )} + +
+

Last run

+
+
+
Duration
+
2.4s
+
+
+
Status
+
success
+
+
+
+ + +
+
+ )} + + {/* Minimap */} +
+
Overview
+ {nodes.map(n => { + const sz = sizes[n.id] || {w: 180, h: 60}; + return ( +
+ ); + })} +
+ + {/* Zoom controls */} +
+ +
{zoom}%
+ + +
+
+
+ ); +}; + +window.WorkflowViewer = WorkflowViewer; diff --git a/.agents/specs/arcrun/frontend-redesign/design.md b/.agents/specs/arcrun/frontend-redesign/design.md new file mode 100644 index 0000000..9ea56f8 --- /dev/null +++ b/.agents/specs/arcrun/frontend-redesign/design.md @@ -0,0 +1,300 @@ +# Frontend Redesign — Design + +> 讀此檔前請先讀 `requirements.md` 和 `design-source/index.html`。 +> 視覺 spec 的 single source of truth 是 `design-source/`(Claude Design 匯出的 HTML/JSX prototype)。 + +--- + +## 1. 架構總覽 + +``` +landing/ (Next.js 15 App Router) +├── app/ +│ ├── layout.tsx ← 全站 layout:next/font + design tokens + 全域 CSS 匯入 +│ ├── globals.css ← 匯入 design-tokens.css;Tailwind @import +│ ├── design-tokens.css ← 新增:從 design-source 抽出的 CSS variables(:root {...}) +│ ├── page.tsx ← Landing(RSC) +│ ├── auth/ +│ │ └── page.tsx ← Auth("use client") +│ ├── dashboard/ +│ │ └── page.tsx ← Dashboard("use client",仍靠 middleware 保護) +│ ├── keys/ +│ │ └── page.tsx ← API Keys("use client") +│ ├── workflows/ +│ │ ├── page.tsx ← Workflows 清單(redirect 到 dashboard 的 table,本身極簡) +│ │ └── [name]/page.tsx ← Workflow Viewer("use client") +│ ├── integrations/page.tsx ← 保留現有 +│ ├── api-docs/page.tsx ← 保留現有 +│ └── login/page.tsx ← 保留現有(redirect /auth 同義;見 §9 遷移策略) +├── components/ +│ ├── shell/ +│ │ ├── Logo.tsx +│ │ ├── Icon.tsx +│ │ ├── TopNav.tsx +│ │ ├── Footer.tsx +│ │ └── Sidebar.tsx +│ ├── primitives/ +│ │ ├── Button.tsx ← btn / btn-primary / btn-secondary / btn-ghost 對應 class +│ │ ├── Pill.tsx +│ │ ├── Toggle.tsx +│ │ ├── Terminal.tsx ← landing hero 右卡用 +│ │ └── ChatPreview.tsx ← landing hero 右卡用 +│ └── workflow/ +│ ├── Canvas.tsx ← wf-viewer 本體(節點 + SVG edges) +│ ├── NodeCard.tsx +│ ├── DetailPanel.tsx +│ ├── Minimap.tsx +│ └── ZoomControls.tsx +├── lib/ +│ ├── api.ts ← typed fetch wrapper(fetch ${API_BASE}${path}, credentials: 'include') +│ ├── workflows.ts ← listWorkflows / getWorkflow / getWorkflowYaml +│ ├── apiKeys.ts ← listKeys / createKey / patchKey / deleteKey +│ └── me.ts ← 已存在邏輯,集中到此 +├── middleware.ts ← 擴展 matcher(加 /keys, /workflows/*) +└── ...(既有 package.json / wrangler.toml 不變) +``` + +**路由對照設計稿的 5 screen**: + +| Screen | Route | +|---|---| +| Landing | `/` | +| Auth | `/auth`(新增;`/login` 保留並內部 `redirect('/auth')`) | +| Dashboard | `/dashboard` | +| API Keys | `/keys` | +| Workflow Viewer | `/workflows/[name]` | + +--- + +## 2. Design tokens 對應 + +設計稿所有 CSS 變數抄進 `app/design-tokens.css`,**不解析、不改名**: + +```css +:root { + --bg: #0F0F0F; + --bg-1: #141414; + --card: #1A1A1A; + --card-2: #222222; + --line: #262626; + --line-2: #303030; + --text: #EDEDED; + --text-dim: #A0A0A0; + --text-mute: #6B6B6B; + --primary: #6366F1; + --primary-2: #8B5CF6; + --primary-soft: rgba(99, 102, 241, 0.12); + --primary-ring: rgba(99, 102, 241, 0.32); + --success: #22C55E; + --warn: #F59E0B; + --danger: #EF4444; + --gradient: linear-gradient(135deg, #6366F1 0%, #8B5CF6 100%); + --gradient-soft: linear-gradient(135deg, rgba(99,102,241,0.16) 0%, rgba(139,92,246,0.16) 100%); +} +``` + +並在 Tailwind v4 的 `@theme inline` block 內對應出: + +```css +@theme inline { + --color-bg: var(--bg); + --color-card: var(--card); + --color-card-2: var(--card-2); + --color-line: var(--line); + --color-line-2: var(--line-2); + --color-text: var(--text); + --color-text-dim: var(--text-dim); + --color-text-mute: var(--text-mute); + --color-primary: var(--primary); + --color-primary-2: var(--primary-2); +} +``` + +這樣 JSX 裡可用 `bg-bg / text-text-dim / border-line`,又保留 CSS 變數語義。 + +**現有的 `--background: #0a0a0a` 要換成 `#0F0F0F`**(視覺 breaking change;受影響:所有沿用 `bg-[#0a0a0a]` 的 inline 值)。 + +--- + +## 3. 字型 + +```tsx +// app/layout.tsx +import { Inter, JetBrains_Mono } from 'next/font/google'; + +const inter = Inter({ + subsets: ['latin'], + variable: '--font-inter', + weight: ['300', '400', '500', '600', '700', '800'], +}); +const mono = JetBrains_Mono({ + subsets: ['latin'], + variable: '--font-mono', + weight: ['400', '500', '600'], +}); + +// body class = `${inter.variable} ${mono.variable}` +``` + +`globals.css` 中的 `body { font-family: var(--font-inter), -apple-system, sans-serif; }`,`.mono` class 用 `font-family: var(--font-mono)`。 + +**移除**: +- `design-source/index.html` 第 7-9 行的 ` / `(不寫入 production)。 +- React / Babel standalone script 標籤(prototype 專用,不進 production)。 + +--- + +## 4. 元件 porting 規則 + +Claude Design 用了 `window.Icon / window.Logo / window.AppIcon / window.TopNav ...` 的 globals 風格 — 那是 prototype 專用。Port 到 Next.js 時: + +1. 每個元件拆單檔、具名 export。 +2. 用 Tailwind + `className` 模板字串;共用 variant(如 btn)用 `cva`-style helper 即可(自己寫 5 行的 `clsx`-alike 函式),**不引入 class-variance-authority / clsx 套件**(避免新依賴)。 +3. Icon 的 `paths` 直接搬,但每個 icon 拆成自己的 functional component 或集中在一個 ``(沿用 design source 的 pattern)。 +4. SVG arc wordmark 的 logo 直接 port。 + +--- + +## 5. 各 screen 實作細節 + +### 5.1 Landing — `app/page.tsx` + +- 結構:`` + `` + `` + `` + `