# 森林AI Frontend Foundation Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** 将 Web 客户端统一为“森林AI”品牌，并建立可在桌面和移动浏览器使用的基础布局与视觉系统。

**Architecture:** 保留 React、Arco Design 和现有工作台信息架构。品牌资产通过公共 SVG 文件引用，主题令牌集中到独立样式文件，移动导航状态由 `ProjectPage` 管理，避免组件各自实现断点逻辑。

**Tech Stack:** React 18、TypeScript 6、Vite 8、Arco Design、Playwright visual check、CSS custom properties。

## Global Constraints

- 产品可见名称和 HTML title 必须为“森林AI”。
- 主色固定为 `#165DFF`；ICON 使用已批准的年轮知识树方案。
- 桌面优先，但 390×844 视口必须能够访问主内容。
- 不增加新的前端运行时依赖。
- 超出 MVP 的探索同步、支付、窗口停靠和自主 Agent 控件不得伪装为可用功能。

---

### Task 1: 品牌资产与静态契约

**Files:**
- Create: `apps/web_v1/public/senlinai-icon.svg`
- Modify: `apps/web_v1/public/favicon.svg`
- Modify: `apps/web_v1/index.html`
- Modify: `apps/web_v1/scripts/structure-check.mjs`
- Modify: `apps/web_v1/src/pages/login.tsx`
- Modify: `apps/web_v1/src/pages/projects/project-topbar.tsx`
- Modify: `apps/web_v1/src/pages/projects/project-rail.tsx`

**Interfaces:**
- Produces: `/senlinai-icon.svg` as the single brand image consumed by login, topbar and project rail.

- [ ] **Step 1: Add failing brand checks**

Extend `structure-check.mjs` with exact assertions:

```js
const html = readFileSync('index.html', 'utf8')
if (!html.includes('<html lang="zh-CN">')) failures.push('index language must be zh-CN')
if (!html.includes('<title>森林AI</title>')) failures.push('document title must be 森林AI')
if (!existsSync('public/senlinai-icon.svg')) failures.push('missing public/senlinai-icon.svg')
for (const file of ['src/pages/login.tsx', 'src/pages/projects/project-topbar.tsx', 'src/pages/projects/project-rail.tsx']) {
  const source = readFileSync(file, 'utf8')
  if (!source.includes('/senlinai-icon.svg')) failures.push(`${file} must use the brand icon`)
}
```

- [ ] **Step 2: Verify the check fails**

Run: `node scripts/structure-check.mjs`

Expected: FAIL with `missing public/senlinai-icon.svg` and title/asset usage failures.

- [ ] **Step 3: Add the approved SVG and replace all brand placeholders**

Use one `<img className="brand-icon" src="/senlinai-icon.svg" alt="" />` at decorative positions, and `aria-label="森林AI 工作台"` on the rail brand button. Set `lang="zh-CN"`, title `森林AI`, and favicon `/senlinai-icon.svg` in `index.html`. Replace visible `森林Agent` and `SenlinAI` labels with `森林AI`.

- [ ] **Step 4: Verify brand contracts**

Run: `node scripts/structure-check.mjs && npm run build && npm run lint`

Expected: all commands PASS; no visible `S` placeholder remains in brand components.

- [ ] **Step 5: Commit**

```powershell
git add apps/web_v1/public apps/web_v1/index.html apps/web_v1/scripts/structure-check.mjs apps/web_v1/src/pages/login.tsx apps/web_v1/src/pages/projects/project-topbar.tsx apps/web_v1/src/pages/projects/project-rail.tsx
git commit -m "feat(web): apply forest AI brand identity"
```

### Task 2: 主题令牌、字体与基础文案

**Files:**
- Create: `apps/web_v1/src/styles/tokens.css`
- Modify: `apps/web_v1/src/main.tsx`
- Modify: `apps/web_v1/src/index.css`
- Modify: `apps/web_v1/src/App.css`
- Modify: `apps/web_v1/src/pages/workspace-body.tsx`
- Modify: `apps/web_v1/src/pages/projects/project-overview.tsx`
- Modify: `apps/web_v1/src/pages/projects/project-ai.tsx`

**Interfaces:**
- Produces: semantic custom properties `--color-*`, `--space-*`, `--radius-*` used by all page CSS.

- [ ] **Step 1: Add failing text and token checks**

Add to `structure-check.mjs`:

```js
const tokenSource = existsSync('src/styles/tokens.css') ? readFileSync('src/styles/tokens.css', 'utf8') : ''
for (const token of ['--color-primary: #165dff', '--font-sans', '--space-4: 16px']) {
  if (!tokenSource.toLowerCase().includes(token)) failures.push(`missing token ${token}`)
}
for (const forbidden of ['森林Agent', 'AI智能体', '实时接口', '进度 60%']) {
  for (const file of requiredFiles.filter((name) => name.endsWith('.tsx'))) {
    if (readFileSync(file, 'utf8').includes(forbidden)) failures.push(`${file} contains forbidden copy ${forbidden}`)
  }
}
```

- [ ] **Step 2: Verify failures**

Run: `node scripts/structure-check.mjs`

Expected: FAIL for missing tokens and old copy.

- [ ] **Step 3: Introduce semantic tokens and Chinese system font**

Create tokens beginning with:

```css
:root {
  --color-primary: #165dff;
  --color-bg: #f7f8fa;
  --color-panel: #ffffff;
  --color-border: #e5e6eb;
  --color-text: #1d2129;
  --color-muted: #6b7785;
  --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-6: 24px;
  --radius-control: 8px;
}
```

Import it before `index.css`. Replace old `--senlin-*` variables and scattered brand colors with semantic tokens. Rename visible `AI智能体` to `AI 助手`; remove `实时接口` and fixed progress copy.

- [ ] **Step 4: Verify tokens and copy**

Run: `node scripts/structure-check.mjs && npm run build && npm run lint`

Expected: PASS.

- [ ] **Step 5: Commit**

```powershell
git add apps/web_v1/src/styles apps/web_v1/src/main.tsx apps/web_v1/src/index.css apps/web_v1/src/App.css apps/web_v1/src/pages
git commit -m "refactor(web): unify tokens and product copy"
```

### Task 3: 登录页响应式重构

**Files:**
- Modify: `apps/web_v1/src/pages/login.tsx`
- Modify: `apps/web_v1/src/App.css`
- Modify: `apps/web_v1/scripts/visual-check.mjs`

**Interfaces:**
- Consumes: semantic tokens and `/senlinai-icon.svg`.
- Produces: two-column desktop and one-column mobile login layout.

- [ ] **Step 1: Add desktop and mobile login assertions**

In `visual-check.mjs`, capture login at 1440×1024 and 390×844 and assert:

```js
const loginMetrics = await page.evaluate(() => ({
  overflowX: document.documentElement.scrollWidth > document.documentElement.clientWidth,
  title: document.title,
  columns: getComputedStyle(document.querySelector('.login-card')).gridTemplateColumns,
}))
if (loginMetrics.overflowX) failures.push('login must not overflow horizontally')
if (loginMetrics.title !== '森林AI') failures.push(`unexpected title ${loginMetrics.title}`)
```

At the mobile viewport assert `.login-card` width is at most 390 and its computed column count is one.

- [ ] **Step 2: Verify the current layout fails**

Run: `$env:VISUAL_CHECK_PORT='4174'; node scripts/visual-check.mjs`

Expected: FAIL on mobile overflow or login column assertions.

- [ ] **Step 3: Implement the two-column login**

Keep `.login-brand-panel` and `.login-form-panel`; remove the third marketing panel. Add `@media (max-width: 760px)` that sets `grid-template-columns: 1fr`, reduces outer padding to 16px, and keeps every input/button at full available width.

- [ ] **Step 4: Verify at both viewports**

Run: `$env:VISUAL_CHECK_PORT='4174'; node scripts/visual-check.mjs`

Expected: login checks PASS with no clipping.

- [ ] **Step 5: Commit**

```powershell
git add apps/web_v1/src/pages/login.tsx apps/web_v1/src/App.css apps/web_v1/scripts/visual-check.mjs
git commit -m "fix(web): make login responsive"
```

### Task 4: 移动工作台导航与有效 DOM

**Files:**
- Modify: `apps/web_v1/src/pages/workspace-home.tsx`
- Modify: `apps/web_v1/src/pages/projects/project-topbar.tsx`
- Modify: `apps/web_v1/src/pages/projects/project-rail.tsx`
- Modify: `apps/web_v1/src/pages/projects/project-sidebar.tsx`
- Modify: `apps/web_v1/src/pages/projects/project-statusbar.tsx`
- Modify: `apps/web_v1/src/App.css`
- Modify: `apps/web_v1/scripts/visual-check.mjs`

**Interfaces:**
- Produces: `navOpen: 'projects' | 'channels' | null` state controlled by `ProjectPage`.
- `ProjectTopbar` emits `onOpenProjects` and `onOpenChannels`.

- [ ] **Step 1: Add failing DOM and mobile navigation checks**

Assert that the status user container has no descendant button, the mobile viewport has no horizontal overflow, and buttons labelled `打开项目导航` and `打开频道导航` can reveal their respective asides.

- [ ] **Step 2: Verify failures**

Run: `$env:VISUAL_CHECK_PORT='4174'; node scripts/visual-check.mjs`

Expected: FAIL for nested button console error and missing mobile navigation.

- [ ] **Step 3: Implement controlled drawers**

Add `const [navOpen, setNavOpen] = useState<'projects' | 'channels' | null>(null)` to `ProjectPage`. Pass an `open` class and close callback to rail/sidebar. Add topbar buttons with exact accessible names. CSS must position asides as fixed overlays below 768px, add a backdrop button, and remove `.workbench-shell { min-width: 1180px; }`.

Replace the status user outer `<button>` with a non-nested structure:

```tsx
<div className="status-user">
  <button className="status-profile" type="button" onClick={() => setProfileOpen(true)}>
    <span className="status-avatar" aria-hidden="true">张</span>
    <span className="status-name">张明</span>
  </button>
  <Button className="status-upgrade" size="mini" type="primary" onClick={() => setUpgradeOpen(true)}>升级</Button>
</div>
```

- [ ] **Step 4: Run full frontend verification**

Run: `node scripts/structure-check.mjs; $env:VISUAL_CHECK_PORT='4174'; node scripts/visual-check.mjs; npm run build; npm run lint`

Expected: all PASS and browser console errors are empty.

- [ ] **Step 5: Commit**

```powershell
git add apps/web_v1/src/pages/workspace-home.tsx apps/web_v1/src/pages/projects apps/web_v1/src/App.css apps/web_v1/scripts/visual-check.mjs
git commit -m "feat(web): add responsive workbench navigation"
```
