9.9 KiB
森林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.svgas the single brand image consumed by login, topbar and project rail. -
Step 1: Add failing brand checks
Extend structure-check.mjs with exact assertions:
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
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:
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:
: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
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:
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
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' | nullstate controlled byProjectPage. -
ProjectTopbaremitsonOpenProjectsandonOpenChannels. -
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:
<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
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"