docs: plan forest AI MVP unification

This commit is contained in:
2026-07-21 12:25:25 +08:00
parent ea69d509a6
commit bbc3a752dc
4 changed files with 828 additions and 0 deletions

View File

@@ -0,0 +1,230 @@
# 森林AI API And Backend 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:** 建立不兼容旧接口的 `/api/v1` camelCase 契约,并把后端拆分为职责清晰、以 identity 对外的业务模块。
**Architecture:** Gorm 模型继续集中在 `internal/models`HTTP request/response DTO 放在各业务包。`httpx` 负责通用错误、identity 参数和 CORSservice 负责所有权与业务规则handler 不直接构造存储路径或查询数据库。
**Tech Stack:** Go、Gin、Gorm、PostgreSQL、httptest。
## Global Constraints
- 外部 API 前缀为 `/api/v1`,字段为 camelCase对外 ID 为 UUIDv7 identity。
- 数据库模型和表名前缀规则保持 `SenlinAgent` / `senlin_agent_`
- session token 必须签名并过期handler 只从认证 middleware 读取用户。
- 文件路径必须由文件服务构造;标签必须限定在项目内。
- AI 请求必须限流并记录 provider、key 类型、action、status 和 error。
- 允许不兼容旧 API但不得自动删除生产数据。
---
### Task 1: 通用 v1 路由、错误和 identity 参数
**Files:**
- Create: `backend/internal/httpx/response.go`
- Create: `backend/internal/httpx/params.go`
- Create: `backend/internal/httpx/response_test.go`
- Modify: `backend/internal/httpx/router.go`
- Modify: `backend/internal/httpx/router_test.go`
- Modify: `backend/internal/config/config.go`
- Modify: `backend/etc/agent.dev.yaml`
**Interfaces:**
- Produces: `Error(c, status, code, message)`, `IdentityParam(c, name) (string, bool)` and `/api/v1` route group.
- [ ] **Step 1: Write failing response and routing tests**
```go
func TestErrorUsesStableEnvelope(t *testing.T) {
recorder := httptest.NewRecorder()
ctx, _ := gin.CreateTestContext(recorder)
Error(ctx, http.StatusBadRequest, "invalid_request", "请求参数无效")
require.JSONEq(t, `{"error":{"code":"invalid_request","message":"请求参数无效"}}`, recorder.Body.String())
}
func TestStatusLivesUnderAPIV1(t *testing.T) {
router := NewRouter(config.Config{Env: "test"})
req := httptest.NewRequest(http.MethodGet, "/api/v1/status", nil)
recorder := httptest.NewRecorder()
router.ServeHTTP(recorder, req)
require.Equal(t, http.StatusOK, recorder.Code)
}
```
- [ ] **Step 2: Verify tests fail**
Run: `go test ./internal/httpx -run 'TestErrorUsesStableEnvelope|TestStatusLivesUnderAPIV1' -v`
Expected: FAIL because helpers and route do not exist.
- [ ] **Step 3: Implement response helpers and configured CORS**
Use:
```go
type ErrorBody struct { Code string `json:"code"`; Message string `json:"message"` }
type ErrorEnvelope struct { Error ErrorBody `json:"error"` }
func Error(c *gin.Context, status int, code, message string) {
c.AbortWithStatusJSON(status, ErrorEnvelope{Error: ErrorBody{Code: code, Message: message}})
}
```
Change the API group to `router.Group("/api/v1")`. Add `AllowedOrigins []string` to config and build the CORS map from config with safe development defaults.
- [ ] **Step 4: Verify the package**
Run: `go test ./internal/httpx -v`
Expected: PASS.
- [ ] **Step 5: Commit**
```powershell
git add backend/internal/httpx backend/internal/config backend/etc/agent.dev.yaml
git commit -m "refactor(api): introduce v1 response contract"
```
### Task 2: 项目 DTO、identity 查询与项目更新
**Files:**
- Create: `backend/internal/logic/projects/dto.go`
- Create: `backend/internal/logic/projects/ownership.go`
- Modify: `backend/internal/logic/projects/service.go`
- Modify: `backend/internal/logic/projects/handlers.go`
- Modify: `backend/internal/logic/projects/handlers_test.go`
- Modify: `backend/internal/logic/projects/service_test.go`
**Interfaces:**
- Produces: `ProjectDTO`, `CreateProjectRequest`, `UpdateProjectRequest`, `FindOwnedProject(userID, identity)`.
- [ ] **Step 1: Write failing contract tests**
Add tests that create a project, call `GET /api/v1/projects`, assert `id` equals its identity, assert no numeric `ID` field exists, and patch `name`, `identifier`, `icon`, `background`, `description` through `PATCH /api/v1/projects/:projectId`.
- [ ] **Step 2: Verify failures**
Run: `go test ./internal/logic/projects -run 'TestListProjectsUsesIdentityDTO|TestUpdateProject' -v`
Expected: FAIL on old response and missing PATCH route.
- [ ] **Step 3: Implement explicit DTO mapping**
```go
type ProjectDTO struct {
ID string `json:"id"`
Name string `json:"name"`
Identifier string `json:"identifier"`
Icon string `json:"icon"`
Background string `json:"background"`
Description string `json:"description"`
}
func projectDTO(project models.SenlinAgentProject) ProjectDTO {
return ProjectDTO{ID: project.Identity, Name: project.Name, Identifier: project.Identifier, Icon: project.Icon, Background: project.Background, Description: project.Description}
}
```
Resolve projects with `owner_id = ? AND identity = ?`. Return 404 for missing owned projects and 409 for duplicate identifiers.
- [ ] **Step 4: Verify project contracts**
Run: `go test ./internal/logic/projects -v`
Expected: PASS.
- [ ] **Step 5: Commit**
```powershell
git add backend/internal/logic/projects
git commit -m "refactor(api): expose project identity DTOs"
```
### Task 3: 拆分工作区聚合查询
**Files:**
- Create: `backend/internal/logic/projects/workspace.go`
- Create: `backend/internal/logic/projects/workspace_test.go`
- Modify: `backend/internal/logic/projects/service.go`
- Modify: `backend/internal/logic/projects/dto.go`
**Interfaces:**
- Produces: `Workspace(userID uint, projectIdentity string) (WorkspaceDTO, error)`.
- [ ] **Step 1: Move existing workspace tests to identity-based expectations**
Assert that project, tasks, tags, notes/sources, sessions and cron plans all expose string identities and RFC3339 timestamps, and that tasks return the actual project-scoped tag.
- [ ] **Step 2: Verify tests fail against the old aggregate**
Run: `go test ./internal/logic/projects -run Workspace -v`
Expected: FAIL on numeric IDs and preformatted display times.
- [ ] **Step 3: Move workspace-only code and remove presentation formatting**
Move `workspaceCounts`, `workspaceChannels`, `workspaceInbox`, `workspaceTasks`, `workspaceAISessions`, `workspaceNotesSources` and `workspaceCronPlans` into `workspace.go`. DTO times must use `time.Time`/`*time.Time` JSON serialization; delete `displayTime`, `displayOptionalTime` and `displayDuration` from backend presentation logic.
- [ ] **Step 4: Verify and check file focus**
Run: `go test ./internal/logic/projects -v`
Expected: PASS; `service.go` contains project CRUD only and is under 300 lines.
- [ ] **Step 5: Commit**
```powershell
git add backend/internal/logic/projects
git commit -m "refactor(backend): isolate workspace aggregation"
```
### Task 4: 任务、标签、资料与计划任务职责迁移
**Files:**
- Create: `backend/internal/logic/tasks/dto.go`
- Create: `backend/internal/logic/tasks/handlers.go`
- Create: `backend/internal/logic/tasks/handlers_test.go`
- Create: `backend/internal/logic/files/handlers.go`
- Create: `backend/internal/logic/files/handlers_test.go`
- Create: `backend/internal/logic/projects/tag_handlers.go`
- Create: `backend/internal/logic/projects/cron_handlers.go`
- Modify: `backend/internal/logic/tasks/service.go`
- Modify: `backend/internal/logic/files/service.go`
- Modify: `backend/internal/logic/projects/handlers.go`
- Modify: `backend/cmd/api/main.go`
**Interfaces:**
- Produces: identity-based Task DTO and separate route registrars for tasks/files/tags/cron plans.
- [ ] **Step 1: Write failing registrar contract tests**
Cover create/update task, create/list tag, upload source and create cron plan. Assert camelCase responses, identity IDs, project ownership, tag scope and source paths that do not contain absolute storage roots.
- [ ] **Step 2: Verify tests fail**
Run: `go test ./internal/logic/tasks ./internal/logic/files ./internal/logic/projects -v`
Expected: FAIL because registrars and DTOs are missing.
- [ ] **Step 3: Move handlers without weakening rules**
`tasks.Service` must accept a DB transaction or repository dependency and retain explicit-share checks. `files.Handler` must call `files.Service.Save` before creating the source record. Tag and cron handlers may remain in `projects` but must not be in the core project handler file.
- [ ] **Step 4: Register every handler under `/api/v1`**
Construct and pass auth, projects, tasks, files, inbox, search and AI registrars in `cmd/api/main.go`. Add Chinese comments to ownership, sharing, Inbox confirmation and file-path boundaries.
- [ ] **Step 5: Verify backend**
Run: `go test ./...`
Expected: PASS.
- [ ] **Step 6: Commit**
```powershell
git add backend/internal/logic backend/cmd/api/main.go
git commit -m "refactor(backend): split project write responsibilities"
```

View File

@@ -0,0 +1,144 @@
# 森林AI Documentation And Verification 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:** 统一开发配置、演示数据、README 和最终验证使新开发者能够按文档启动森林AI并复现验收结果。
**Architecture:** Docker Compose、YAML 配置和 README 使用同一组开发默认值;本地数据通过显式 seed 命令创建。最终验证脚本只读取环境配置,不携带真实凭据。
**Tech Stack:** Docker Compose、PostgreSQL 16、Go、Node.js、Vite、Tauri。
## Global Constraints
- 不提交真实凭据、本地数据库、文件存储、日志、审计截图或构建产物。
- 开发配置可以包含明确标注的非生产默认值。
- 程序启动不得自动 drop 表或清空生产数据。
- README 必须为 UTF-8 中文名称统一为“森林AI”。
---
### Task 1: 开发配置与演示数据一致性
**Files:**
- Modify: `infra/docker-compose.yml`
- Modify: `backend/etc/agent.dev.yaml`
- Modify: `backend/internal/config/config.go`
- Modify: `backend/internal/config/config_test.go`
- Modify: `backend/internal/seed/demo.go`
- Modify: `backend/internal/seed/demo_test.go`
- Modify: `.gitignore`
**Interfaces:**
- Produces: one PostgreSQL DSN shared by Compose and development YAML; explicit `go run ./cmd/seed` reset-safe seed workflow.
- [ ] **Step 1: Write failing configuration tests**
Assert the loaded development config uses port 9150, `/api/v1` compatible CORS origins, a non-empty storage dir, and that seed is idempotent while preserving project-scoped tags.
- [ ] **Step 2: Verify failures**
Run: `go test ./internal/config ./internal/seed -v`
Expected: FAIL where config lacks allowed origins or seed contracts.
- [ ] **Step 3: Align Compose and YAML**
Use the same development values in both files: user `agent`, password `agent`, database `agent`, host `localhost`, port `5432`. Keep secrets explicitly marked as development-only. Add `.superpowers/`, `backend/tmp/`, `backend/data/`, `apps/web_v1/test-results/` and build outputs to `.gitignore`.
- [ ] **Step 4: Verify config and seed**
Run: `go test ./internal/config ./internal/seed -v`
Expected: PASS.
- [ ] **Step 5: Commit**
```powershell
git add infra/docker-compose.yml backend/etc/agent.dev.yaml backend/internal/config backend/internal/seed .gitignore
git commit -m "chore: align forest AI development setup"
```
### Task 2: 重写 README
**Files:**
- Modify: `README.md`
- Modify: `docs/mvp-verification.md`
**Interfaces:**
- Produces: authoritative local setup and verification guide.
- [ ] **Step 1: Check every documented command before writing**
Run the exact commands that README will contain: Compose startup, backend API startup, seed, Web dev server, backend tests, visual check, build and lint. Record only commands that actually succeed.
- [ ] **Step 2: Replace README with the approved structure**
Use these exact top-level sections: `产品定位`, `MVP 范围`, `技术栈`, `目录结构`, `快速开始`, `配置`, `API v1 概览`, `验证`, `桌面端`, `常见问题`, `安全说明`.
Document `POST /api/v1/auth/login`, project/workspace, task/tag/source/cron, Inbox, search and AI session endpoints without embedding tokens or real secrets.
- [ ] **Step 3: Update verification documentation**
Include the current expected commands and artifact paths; remove stale Svelte references, old API paths, old product names and deprecated ports.
- [ ] **Step 4: Verify text consistency**
Run:
```powershell
rg -n "SenlinAI Agent Workbench|森林Agent|/api/projects|apps/web/|Svelte" README.md docs/mvp-verification.md
```
Expected: no matches.
- [ ] **Step 5: Commit**
```powershell
git add README.md docs/mvp-verification.md
git commit -m "docs: update forest AI setup and verification"
```
### Task 3: 全量验证与前后截图对比
**Files:**
- Modify: `apps/web_v1/scripts/visual-check.mjs`
- Create: `docs/forest-ai-audit.md`
**Interfaces:**
- Produces: repeatable desktop/mobile audit summary with final screenshots.
- [ ] **Step 1: Run backend verification**
Run: `go test ./...`
Expected: PASS for every package.
- [ ] **Step 2: Run 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: PASS; build may report a size warning but no error.
- [ ] **Step 3: Run PostgreSQL integration checks when DATABASE_URL is present**
Run: `go test ./internal/models -run TestPostgresPing -v`
Expected: PASS when `DATABASE_URL` is configured; otherwise the test reports its documented skip.
- [ ] **Step 4: Capture final evidence**
Capture and inspect 1440×1024 login, workspace, project overview and tasks, plus 390×844 login and project navigation. Compare against the five accepted audit screenshots from the design phase and document visible fixes and remaining limits in `docs/forest-ai-audit.md`.
- [ ] **Step 5: Verify repository cleanliness**
Run: `git status --short`
Expected: only intended source/docs changes are present; no logs, test screenshots, database files or secrets.
- [ ] **Step 6: Commit**
```powershell
git add apps/web_v1/scripts/visual-check.mjs docs/forest-ai-audit.md
git commit -m "test: verify forest AI MVP unification"
```

View File

@@ -0,0 +1,206 @@
# 森林AI Feature Alignment 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:** 让前端核心工作流完整使用 `/api/v1`补齐搜索、项目设置、Inbox 和受控 AI 会话,同时移除伪功能入口。
**Architecture:** 前端 API 层只认一种 camelCase DTO页面事件由 `App` 编排后刷新聚合工作区。后端为搜索和 AI 会话提供独立 registrar超出 MVP 的入口从页面移除。
**Tech Stack:** React、TypeScript、Gin、Gorm、Playwright、Go httptest。
## Global Constraints
- 所有前端 ID 是 identity 字符串;不兼容 PascalCase 或数字 ID。
- AI 未经用户确认不得创建任务、笔记或资料。
- 核心操作必须有 loading、成功反馈和可执行的中文错误。
- 不实现探索数据源、支付、窗口停靠、自主 Agent 或实时聊天。
---
### Task 1: 前端 API v1 客户端和错误类型
**Files:**
- Modify: `apps/web_v1/src/api/client.ts`
- Modify: `apps/web_v1/src/api/projects.ts`
- Modify: `apps/web_v1/src/api/mappers.tsx`
- Create: `apps/web_v1/src/api/search.ts`
- Create: `apps/web_v1/src/api/inbox.ts`
- Modify: `apps/web_v1/scripts/structure-check.mjs`
**Interfaces:**
- Produces: `ApiError { status: number; code: string; message: string }` and camelCase DTOs only.
- [ ] **Step 1: Add failing static checks**
Reject `ID?`, `Name?`, `/api/projects`, and `number` project/task IDs in `src/api`. Require `/api/v1` in login and project API paths.
- [ ] **Step 2: Verify failures**
Run: `node scripts/structure-check.mjs`
Expected: FAIL on legacy paths and PascalCase compatibility fields.
- [ ] **Step 3: Implement one response contract**
Parse the standard error envelope:
```ts
export class ApiError extends Error {
constructor(public status: number, public code: string, message: string) {
super(message)
}
}
```
All DTOs use required camelCase fields and string IDs. Update date formatting to happen only in mappers.
- [ ] **Step 4: Verify frontend types**
Run: `node scripts/structure-check.mjs && npm run build && npm run lint`
Expected: PASS.
- [ ] **Step 5: Commit**
```powershell
git add apps/web_v1/src/api apps/web_v1/scripts/structure-check.mjs
git commit -m "refactor(web): consume api v1 contracts"
```
### Task 2: 搜索与项目设置真实往返
**Files:**
- Create: `backend/internal/logic/search/handlers.go`
- Create: `backend/internal/logic/search/handlers_test.go`
- Modify: `backend/cmd/api/main.go`
- Modify: `apps/web_v1/src/pages/projects/project-topbar.tsx`
- Modify: `apps/web_v1/src/pages/projects/project-sidebar.tsx`
- Modify: `apps/web_v1/src/app/App.tsx`
**Interfaces:**
- `GET /api/v1/search?q=` returns `{items: SearchResultDTO[]}`.
- `PATCH /api/v1/projects/:projectId` persists settings and returns ProjectDTO.
- [ ] **Step 1: Write failing search handler tests**
Assert blank query returns 400 `invalid_query`, authenticated matching query returns project/task/note results, and another user's private objects are absent.
- [ ] **Step 2: Verify backend failures**
Run: `go test ./internal/logic/search -v`
Expected: FAIL because handler is missing.
- [ ] **Step 3: Implement search registrar and front-end search state**
Topbar accepts `query`, `loading`, `onQueryChange`, `onSearch`. Submit on Enter or the Search button. Render results in an Arco dropdown/list and navigate to the owning project/channel when the result type is known.
- [ ] **Step 4: Persist project settings**
Replace the local `setWorkspaces`-only update with `updateProject(session, update)`, then call `loadWorkspaces` with the same identity. Keep the modal open on failure.
- [ ] **Step 5: Verify**
Run: `go test ./internal/logic/search ./internal/logic/projects -v; npm run build; npm run lint`
Expected: PASS.
- [ ] **Step 6: Commit**
```powershell
git add backend/internal/logic/search backend/cmd/api/main.go apps/web_v1/src
git commit -m "feat: connect search and project settings"
```
### Task 3: Inbox 确认流与核心写操作
**Files:**
- Modify: `backend/internal/logic/inbox/handlers.go`
- Modify: `backend/internal/logic/inbox/service.go`
- Modify: `backend/internal/logic/inbox/service_test.go`
- Modify: `apps/web_v1/src/api/inbox.ts`
- Create: `apps/web_v1/src/pages/projects/project-inbox.tsx`
- Modify: `apps/web_v1/src/pages/projects/project-channel-page.tsx`
- Modify: `apps/web_v1/src/app/App.tsx`
**Interfaces:**
- Inbox capture/analyze/confirm uses identity strings.
- `confirm` accepts only suggestions returned for the same inbox item and current user.
- [ ] **Step 1: Add failing identity and authorization tests**
Test capture, analyze without writes, confirm selected task/note/source with source Inbox identity, and reject confirming another user's inbox item.
- [ ] **Step 2: Verify failures**
Run: `go test ./internal/logic/inbox -v`
Expected: legacy numeric paths or authorization gaps fail new tests.
- [ ] **Step 3: Implement v1 Inbox DTOs and page**
The page shows item content, suggestion checkboxes and one `确认创建` action. Analysis results remain drafts until this action. On success refresh the workspace and show the created object count.
- [ ] **Step 4: Verify core write operations**
Run: `go test ./internal/logic/inbox ./internal/logic/tasks ./internal/logic/projects -v; npm run build; npm run lint`
Expected: PASS.
- [ ] **Step 5: Commit**
```powershell
git add backend/internal/logic/inbox apps/web_v1/src
git commit -m "feat: connect inbox confirmation workflow"
```
### Task 4: 受控 AI 会话与伪功能清理
**Files:**
- Create: `backend/internal/logic/ai/handlers.go`
- Create: `backend/internal/logic/ai/handlers_test.go`
- Modify: `backend/internal/logic/ai/sessions.go`
- Modify: `backend/internal/logic/ai/gateway.go`
- Modify: `backend/cmd/api/main.go`
- Modify: `apps/web_v1/src/pages/projects/project-ai.tsx`
- Modify: `apps/web_v1/src/pages/workspace-explore.tsx`
- Modify: `apps/web_v1/src/pages/projects/project-new-channel.tsx`
- Modify: `apps/web_v1/src/pages/projects/project-statusbar.tsx`
- Modify: `apps/web_v1/src/pages/projects/project-topbar.tsx`
- Modify: `apps/web_v1/scripts/visual-check.mjs`
**Interfaces:**
- `GET/POST /api/v1/projects/:projectId/ai-sessions` lists/creates controlled sessions.
- No endpoint converts AI output into formal objects without a separate confirmation request.
- [ ] **Step 1: Write failing AI handler tests**
Assert session creation checks project ownership, rate limit runs before provider selection, call logs contain required fields, and the response contains no automatic object IDs.
- [ ] **Step 2: Verify failures**
Run: `go test ./internal/logic/ai -v`
Expected: FAIL because handlers are missing.
- [ ] **Step 3: Implement controlled session endpoints**
Return a session DTO with identity, title, context, status and timestamps. If no provider key exists, return `ai_key_missing` without creating formal objects.
- [ ] **Step 4: Remove unsupported controls**
Remove active-looking sync/edit/delete actions from Explore, payment choices from the status bar, dock buttons, fake app launcher and nonfunctional channel save. Keep a concise “暂未开放” empty state only where navigation must remain visible.
- [ ] **Step 5: Update visual checks and verify**
Run: `go test ./...; $env:VISUAL_CHECK_PORT='4174'; node scripts/visual-check.mjs; npm run build; npm run lint`
Expected: all PASS, AI page has an `AI 助手` heading, and console errors are empty.
- [ ] **Step 6: Commit**
```powershell
git add backend/internal/logic/ai backend/cmd/api/main.go apps/web_v1/src apps/web_v1/scripts/visual-check.mjs
git commit -m "feat: align controlled AI sessions and MVP controls"
```

View File

@@ -0,0 +1,248 @@
# 森林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"
```