docs: plan forest AI MVP unification
This commit is contained in:
230
docs/superpowers/plans/2026-07-21-forest-ai-api-backend.md
Normal file
230
docs/superpowers/plans/2026-07-21-forest-ai-api-backend.md
Normal 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 参数和 CORS;service 负责所有权与业务规则;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"
|
||||
```
|
||||
|
||||
144
docs/superpowers/plans/2026-07-21-forest-ai-docs-verification.md
Normal file
144
docs/superpowers/plans/2026-07-21-forest-ai-docs-verification.md
Normal 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"
|
||||
```
|
||||
|
||||
206
docs/superpowers/plans/2026-07-21-forest-ai-feature-alignment.md
Normal file
206
docs/superpowers/plans/2026-07-21-forest-ai-feature-alignment.md
Normal 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"
|
||||
```
|
||||
|
||||
@@ -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"
|
||||
```
|
||||
Reference in New Issue
Block a user