diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..c518392 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,61 @@ +# AGENTS.md + +## Project + +This repository contains the SenlinAI project workbench MVP. + +The product is a private-deployment, project-centered workbench for individual knowledge workers, team leads, and trusted internal users. It organizes project tasks, inbox items, Markdown notes, files, AI sessions, project-local tags, and lightweight task distribution. + +## Repository Layout + +- `backend`: Go Gin + Gorm + PostgreSQL API. +- `apps/web`: Responsive React + TypeScript web client. +- `apps/desktop`: Tauri desktop shell for the web client. +- `infra`: Local development infrastructure such as Docker Compose. +- `docs/superpowers/specs`: Product and technical specs. +- `docs/superpowers/plans`: Implementation plans. + +## Backend Rules + +- Use Go, Gin, Gorm, and PostgreSQL. +- Keep backend modules focused under `backend/internal`. +- Use module path `senlinai-agent/backend`. +- Keep all server-local file path construction inside the file service. +- Do not let HTTP handlers construct storage paths directly. +- Keep project tags scoped to a project; do not introduce global tags in MVP. +- Keep task sharing conservative: assignees see the task and only explicitly shared linked objects. +- AI must not create official objects without user confirmation. +- AI calls must record provider, key type, action, status, and errors. + +## Frontend Rules + +- Build the web app desktop-first, while keeping mobile browser layouts usable. +- Keep feature code under `apps/web/src/features`. +- Use browser-style tabs for page-level workspace state. +- Save editor and AI input drafts separately from tab state. +- Use Markdown for MVP note editing. + +## Tauri Rules + +- Tauri wraps the web client. +- MVP desktop features are login persistence, file drag-and-drop upload, notifications, and global shortcut quick capture. +- Do not add offline editing, local-first sync, background clipboard monitoring, or file-system indexing in MVP. + +## MVP Scope Guardrails + +Do not add these unless a later approved spec says so: + +- Real-time instant messaging. +- Project-level member roles. +- Anonymous public task sharing. +- Autonomous Agent execution. +- Semantic or vector search. +- Native mobile apps. +- Browser extension, email, IM, or third-party capture integrations. + +## Verification + +- Backend changes should run `go test ./...` from `backend`. +- Web changes should run tests and build from `apps/web`. +- Tauri changes should build the web app first, then run the Tauri verification command from `apps/desktop`. +- Each implementation task should end with a focused commit. diff --git a/docs/superpowers/plans/2026-07-18-project-workbench-mvp.md b/docs/superpowers/plans/2026-07-18-project-workbench-mvp.md new file mode 100644 index 0000000..e88f29a --- /dev/null +++ b/docs/superpowers/plans/2026-07-18-project-workbench-mvp.md @@ -0,0 +1,641 @@ +# 项目工作台 MVP 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:** 构建一个以项目为中心的私有部署工作台 MVP,包含项目、Inbox、任务、Markdown 笔记资料、AI 会话、全文搜索、任务分发和 Tauri 桌面壳。 + +**Architecture:** 使用前后端分离 monorepo。`backend` 提供 Go Gin + Gorm + PostgreSQL API,`apps/web` 提供响应式 React Web 客户端,`apps/desktop` 用 Tauri 包装 Web 客户端并提供桌面能力。先实现清晰模块边界,再逐步接入 AI、搜索和桌面特性。 + +**Tech Stack:** Go 1.22+, Gin, Gorm, PostgreSQL 16+, React, TypeScript, Vite, TanStack Query, Tauri 2, Vitest, Playwright, Docker Compose. + +## Global Constraints + +- 私有部署优先,用户通过管理员创建账号或邀请码注册。 +- 数据以服务端为准,本地客户端只是 Tauri 壳,不做离线编辑或本地优先同步。 +- 文件第一版存储在服务端本地磁盘,路径逻辑集中在文件服务中。 +- 标签是项目内标签,不做全局标签体系。 +- AI 默认允许回退到系统 key,必须有调用日志、基础限流、错误记录和用户 key 安全存储。 +- AI 不得自动创建正式对象,Inbox 分析结果必须由用户确认。 +- 任务分发只面向系统内用户,接收者只能看到任务和显式共享的关联对象。 +- 全局搜索使用 PostgreSQL 关键词和全文搜索,语义搜索不进入 MVP。 +- 前端桌面优先,同时保持移动浏览器可用。 +- 每个任务结束时运行该任务列出的验证命令并提交。 + +--- + +## File Structure + +```text +D:\work\senlinai\agent +├── AGENTS.md +├── apps +│ ├── web +│ │ ├── package.json +│ │ ├── index.html +│ │ ├── vite.config.ts +│ │ ├── tsconfig.json +│ │ └── src +│ │ ├── app +│ │ ├── components +│ │ ├── features +│ │ ├── lib +│ │ └── test +│ └── desktop +│ ├── package.json +│ └── src-tauri +├── backend +│ ├── cmd +│ │ └── api +│ ├── internal +│ │ ├── ai +│ │ ├── auth +│ │ ├── config +│ │ ├── db +│ │ ├── domain +│ │ ├── files +│ │ ├── httpx +│ │ ├── inbox +│ │ ├── notes +│ │ ├── projects +│ │ ├── search +│ │ └── tasks +│ ├── migrations +│ └── tests +├── infra +└── docs +``` + +后端 Go module 使用: + +```text +senlinai-agent/backend +``` + +--- + +## Task 1: Monorepo 与本地开发基础 + +**Files:** +- Create: `D:\work\senlinai\agent\README.md` +- Create: `D:\work\senlinai\agent\.gitignore` +- Create: `D:\work\senlinai\agent\infra\docker-compose.yml` +- Create: `D:\work\senlinai\agent\backend\go.mod` +- Create: `D:\work\senlinai\agent\backend\cmd\api\main.go` +- Create: `D:\work\senlinai\agent\backend\internal\config\config.go` +- Create: `D:\work\senlinai\agent\backend\internal\httpx\router.go` +- Create: `D:\work\senlinai\agent\backend\internal\httpx\router_test.go` + +**Interfaces:** +- Produces: `config.Load() config.Config` +- Produces: `httpx.NewRouter(cfg config.Config) *gin.Engine` +- Produces: `GET /healthz -> {"status":"ok"}` + +- [ ] **Step 1: 创建目录和 Go module** + +```powershell +New-Item -ItemType Directory -Force backend\cmd\api, backend\internal\config, backend\internal\httpx, infra +Set-Location backend +go mod init senlinai-agent/backend +go get github.com/gin-gonic/gin@latest github.com/stretchr/testify@latest +``` + +Expected: `backend/go.mod` exists. + +- [ ] **Step 2: 写 healthz 失败测试** + +Create `backend/internal/httpx/router_test.go`: + +```go +package httpx + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/require" + "senlinai-agent/backend/internal/config" +) + +func TestHealthz(t *testing.T) { + router := NewRouter(config.Config{Env: "test"}) + req := httptest.NewRequest(http.MethodGet, "/healthz", nil) + rec := httptest.NewRecorder() + router.ServeHTTP(rec, req) + require.Equal(t, http.StatusOK, rec.Code) + require.JSONEq(t, `{"status":"ok"}`, rec.Body.String()) +} +``` + +- [ ] **Step 3: 确认测试失败** + +```powershell +Set-Location D:\work\senlinai\agent\backend +go test ./internal/httpx -run TestHealthz -v +``` + +Expected: FAIL because `NewRouter` or `config.Config` is undefined. + +- [ ] **Step 4: 实现 config、router、main** + +Create `backend/internal/config/config.go`: + +```go +package config + +import "os" + +type Config struct { + Env string + DatabaseURL string + StorageDir string + SystemAIKey string +} + +func Load() Config { + return Config{ + Env: getenv("APP_ENV", "development"), + DatabaseURL: getenv("DATABASE_URL", "postgres://agent:agent@localhost:5432/agent?sslmode=disable"), + StorageDir: getenv("STORAGE_DIR", "./data/files"), + SystemAIKey: os.Getenv("SYSTEM_AI_KEY"), + } +} + +func getenv(key string, fallback string) string { + if value := os.Getenv(key); value != "" { + return value + } + return fallback +} +``` + +Create `backend/internal/httpx/router.go`: + +```go +package httpx + +import ( + "net/http" + + "github.com/gin-gonic/gin" + "senlinai-agent/backend/internal/config" +) + +func NewRouter(cfg config.Config) *gin.Engine { + if cfg.Env == "test" { + gin.SetMode(gin.TestMode) + } + router := gin.New() + router.Use(gin.Recovery()) + router.GET("/healthz", func(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{"status": "ok"}) + }) + return router +} +``` + +Create `backend/cmd/api/main.go`: + +```go +package main + +import ( + "log" + + "senlinai-agent/backend/internal/config" + "senlinai-agent/backend/internal/httpx" +) + +func main() { + cfg := config.Load() + router := httpx.NewRouter(cfg) + if err := router.Run(":8080"); err != nil { + log.Fatal(err) + } +} +``` + +- [ ] **Step 5: 增加基础本地开发文件** + +Create `.gitignore`: + +```gitignore +node_modules/ +dist/ +target/ +.env +data/ +coverage/ +*.log +``` + +Create `infra/docker-compose.yml`: + +```yaml +services: + postgres: + image: postgres:16 + environment: + POSTGRES_USER: agent + POSTGRES_PASSWORD: agent + POSTGRES_DB: agent + ports: + - "5432:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + +volumes: + postgres_data: +``` + +Create `README.md`: + +```markdown +# SenlinAI Agent Workbench + +Private project-centered workbench MVP. + +## Development + +```powershell +docker compose -f infra/docker-compose.yml up -d +Set-Location backend +go run ./cmd/api +``` +``` + +- [ ] **Step 6: 验证并提交** + +```powershell +Set-Location D:\work\senlinai\agent\backend +go test ./... -v +Set-Location D:\work\senlinai\agent +git add README.md .gitignore infra backend +git commit -m "chore: scaffold project workbench" +``` + +--- + +## Task 2: 数据库模型与迁移边界 + +**Files:** +- Create: `D:\work\senlinai\agent\backend\internal\db\db.go` +- Create: `D:\work\senlinai\agent\backend\internal\domain\models.go` +- Create: `D:\work\senlinai\agent\backend\internal\domain\models_test.go` + +**Interfaces:** +- Produces: `db.Open(databaseURL string) (*gorm.DB, error)` +- Produces: `domain.AutoMigrate(database *gorm.DB) error` +- Produces models: `User`, `Project`, `InboxItem`, `Task`, `Note`, `Source`, `AISession`, `Tag`, `ProjectEvent`, `AIKey`, `AICallLog`, `TaskShare` + +- [ ] **Step 1: 添加 Gorm 依赖** + +```powershell +Set-Location D:\work\senlinai\agent\backend +go get gorm.io/gorm@latest gorm.io/driver/postgres@latest gorm.io/driver/sqlite@latest +``` + +- [ ] **Step 2: 写 AutoMigrate 失败测试** + +Create `backend/internal/domain/models_test.go`: + +```go +package domain + +import ( + "testing" + + "github.com/stretchr/testify/require" + "gorm.io/driver/sqlite" + "gorm.io/gorm" +) + +func TestAutoMigrateCreatesCoreTables(t *testing.T) { + database, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{}) + require.NoError(t, err) + require.NoError(t, AutoMigrate(database)) + for _, table := range []string{ + "users", "projects", "inbox_items", "tasks", "notes", "sources", + "ai_sessions", "tags", "project_events", "ai_keys", "ai_call_logs", "task_shares", + } { + require.True(t, database.Migrator().HasTable(table), "missing table %s", table) + } +} +``` + +- [ ] **Step 3: 实现 domain models** + +Create `backend/internal/domain/models.go` with the structs listed in the interface. Required fields: + +```go +package domain + +import ( + "time" + + "gorm.io/gorm" +) + +type User struct { ID uint `gorm:"primaryKey"`; Email string `gorm:"uniqueIndex;not null"`; DisplayName string `gorm:"not null"`; PasswordHash string `gorm:"not null"`; Role string `gorm:"not null;default:user"`; CreatedAt time.Time; UpdatedAt time.Time } +type Project struct { ID uint `gorm:"primaryKey"`; OwnerID uint `gorm:"index;not null"`; Name string `gorm:"not null"`; Description string; CreatedAt time.Time; UpdatedAt time.Time } +type InboxItem struct { ID uint `gorm:"primaryKey"`; ProjectID uint `gorm:"index;not null"`; CreatedBy uint `gorm:"index;not null"`; SourceType string `gorm:"not null"`; Title string; Body string; Status string `gorm:"not null;default:open"`; CreatedAt time.Time; UpdatedAt time.Time } +type Task struct { ID uint `gorm:"primaryKey"`; ProjectID uint `gorm:"index;not null"`; CreatedBy uint `gorm:"index;not null"`; AssigneeID *uint `gorm:"index"`; Title string `gorm:"not null"`; Description string; Status string `gorm:"not null;default:open"`; SortOrder int `gorm:"not null;default:0"`; DueAt *time.Time; CreatedAt time.Time; UpdatedAt time.Time } +type Note struct { ID uint `gorm:"primaryKey"`; ProjectID uint `gorm:"index;not null"`; CreatedBy uint `gorm:"index;not null"`; Title string `gorm:"not null"`; Markdown string `gorm:"type:text"`; CreatedAt time.Time; UpdatedAt time.Time } +type Source struct { ID uint `gorm:"primaryKey"`; ProjectID uint `gorm:"index;not null"`; CreatedBy uint `gorm:"index;not null"`; Kind string `gorm:"not null"`; Title string `gorm:"not null"`; URL string; FilePath string; ContentText string `gorm:"type:text"`; CreatedAt time.Time; UpdatedAt time.Time } +type AISession struct { ID uint `gorm:"primaryKey"`; ProjectID uint `gorm:"index;not null"`; CreatedBy uint `gorm:"index;not null"`; Title string `gorm:"not null"`; Context string `gorm:"type:text"`; CreatedAt time.Time; UpdatedAt time.Time } +type Tag struct { ID uint `gorm:"primaryKey"`; ProjectID uint `gorm:"index;not null"`; Name string `gorm:"not null"`; CreatedAt time.Time } +type ProjectEvent struct { ID uint `gorm:"primaryKey"`; ProjectID uint `gorm:"index;not null"`; ActorID uint `gorm:"index;not null"`; EventType string `gorm:"not null"`; EntityType string `gorm:"not null"`; EntityID uint `gorm:"not null"`; Summary string `gorm:"not null"`; CreatedAt time.Time } +type AIKey struct { ID uint `gorm:"primaryKey"`; UserID uint `gorm:"uniqueIndex;not null"`; Provider string `gorm:"not null"`; EncryptedAPIKey string `gorm:"not null"`; CreatedAt time.Time; UpdatedAt time.Time } +type AICallLog struct { ID uint `gorm:"primaryKey"`; UserID uint `gorm:"index;not null"`; Provider string `gorm:"not null"`; UsedKeyType string `gorm:"not null"`; Action string `gorm:"not null"`; Status string `gorm:"not null"`; Error string; CreatedAt time.Time } +type TaskShare struct { ID uint `gorm:"primaryKey"`; TaskID uint `gorm:"index;not null"`; ObjectType string `gorm:"not null"`; ObjectID uint `gorm:"not null"`; CreatedAt time.Time } + +func AutoMigrate(database *gorm.DB) error { + return database.AutoMigrate(&User{}, &Project{}, &InboxItem{}, &Task{}, &Note{}, &Source{}, &AISession{}, &Tag{}, &ProjectEvent{}, &AIKey{}, &AICallLog{}, &TaskShare{}) +} +``` + +- [ ] **Step 4: 实现 PostgreSQL 连接** + +Create `backend/internal/db/db.go`: + +```go +package db + +import ( + "gorm.io/driver/postgres" + "gorm.io/gorm" +) + +func Open(databaseURL string) (*gorm.DB, error) { + return gorm.Open(postgres.Open(databaseURL), &gorm.Config{}) +} +``` + +- [ ] **Step 5: 验证并提交** + +```powershell +Set-Location D:\work\senlinai\agent\backend +go test ./... -v +Set-Location D:\work\senlinai\agent +git add backend +git commit -m "feat: add core data model" +``` + +--- + +## Task 3: 私有部署认证与邀请注册 + +**Files:** +- Create: `D:\work\senlinai\agent\backend\internal\auth\service.go` +- Create: `D:\work\senlinai\agent\backend\internal\auth\service_test.go` +- Create: `D:\work\senlinai\agent\backend\internal\auth\middleware.go` + +**Interfaces:** +- Produces: `auth.Service.CreateInvite(adminID uint, email string) (string, error)` +- Produces: `auth.Service.RegisterWithInvite(token, email, displayName, password string) (*domain.User, error)` +- Produces: `auth.Service.Login(email, password string) (string, error)` +- Produces: `auth.RequireUser(tokenVerifier func(string) (uint, error)) gin.HandlerFunc` + +- [ ] Write tests for invite registration, wrong-email rejection, and login failure. +- [ ] Implement invite token signing with HMAC SHA-256. +- [ ] Store password hashes with bcrypt. +- [ ] Add bearer-token middleware that sets `currentUserID`. +- [ ] Run `go test ./... -v` from `backend`. +- [ ] Commit with `git commit -m "feat: add private auth foundation"`. + +--- + +## Task 4: 项目、项目内标签与 Dashboard API + +**Files:** +- Create: `D:\work\senlinai\agent\backend\internal\projects\service.go` +- Create: `D:\work\senlinai\agent\backend\internal\projects\service_test.go` +- Create: `D:\work\senlinai\agent\backend\internal\projects\handlers.go` +- Modify: `D:\work\senlinai\agent\backend\internal\httpx\router.go` + +**Interfaces:** +- Produces: `projects.Service.CreateProject(ownerID uint, name string, description string) (*domain.Project, error)` +- Produces: `projects.Service.ListProjects(ownerID uint) ([]domain.Project, error)` +- Produces: `projects.Service.CreateTag(projectID uint, name string) (*domain.Tag, error)` +- Produces: `projects.Service.ListTags(projectID uint) ([]domain.Tag, error)` +- Produces: `projects.Service.Dashboard(projectID uint) (Dashboard, error)` + +- [ ] Test that identical tag names can exist in different projects. +- [ ] Test that dashboard counters only count objects under the requested project. +- [ ] Implement project CRUD service. +- [ ] Implement project-local tag service. +- [ ] Implement `GET /projects`, `POST /projects`, `GET /projects/:id/dashboard`. +- [ ] Run `go test ./... -v` from `backend`. +- [ ] Commit with `git commit -m "feat: add project workspace service"`. + +--- + +## Task 5: Inbox 收集与 AI 建议确认 + +**Files:** +- Create: `D:\work\senlinai\agent\backend\internal\inbox\service.go` +- Create: `D:\work\senlinai\agent\backend\internal\inbox\service_test.go` +- Create: `D:\work\senlinai\agent\backend\internal\inbox\handlers.go` + +**Interfaces:** +- Produces: `inbox.Service.Capture(input CaptureInput) (*domain.InboxItem, error)` +- Produces: `inbox.Service.Analyze(itemID uint, userID uint) ([]Suggestion, error)` +- Produces: `inbox.Service.Confirm(itemID uint, selected []Suggestion) error` +- Produces: `Suggestion{Kind string, Title string, Body string}` + +- [ ] Test capture of text, link, and file metadata into project inbox. +- [ ] Test that Analyze returns suggestions but creates no official objects. +- [ ] Test that Confirm creates selected task/note/source and marks the inbox item `processed`. +- [ ] Implement analyzer interface with a static fake for tests. +- [ ] Implement handlers for capture, analyze, and confirm. +- [ ] Run `go test ./... -v` from `backend`. +- [ ] Commit with `git commit -m "feat: add inbox capture workflow"`. + +--- + +## Task 6: 任务、指派与显式共享 + +**Files:** +- Create: `D:\work\senlinai\agent\backend\internal\tasks\service.go` +- Create: `D:\work\senlinai\agent\backend\internal\tasks\service_test.go` +- Modify: `D:\work\senlinai\agent\backend\internal\domain\models.go` + +**Interfaces:** +- Produces: `tasks.Service.Assign(taskID uint, assigneeID uint) error` +- Produces: `tasks.Service.ShareObject(taskID uint, objectType string, objectID uint) error` +- Produces: `tasks.Service.VisibleLinkedObjects(taskID uint, viewerID uint) ([]LinkedObject, error)` + +- [ ] Test that assignee can see the task. +- [ ] Test that assignee cannot see linked note/source until explicitly shared. +- [ ] Test unsupported shared object type returns an error. +- [ ] Implement task assignment and sharing service. +- [ ] Record assignment and sharing as project events. +- [ ] Run `go test ./... -v` from `backend`. +- [ ] Commit with `git commit -m "feat: add task assignment sharing"`. + +--- + +## Task 7: 笔记、资料、本地文件存储与全文搜索 + +**Files:** +- Create: `D:\work\senlinai\agent\backend\internal\files\service.go` +- Create: `D:\work\senlinai\agent\backend\internal\files\service_test.go` +- Create: `D:\work\senlinai\agent\backend\internal\notes\service.go` +- Create: `D:\work\senlinai\agent\backend\internal\search\service.go` +- Create: `D:\work\senlinai\agent\backend\internal\search\service_test.go` +- Create: `D:\work\senlinai\agent\backend\migrations\0001_search_indexes.sql` + +**Interfaces:** +- Produces: `files.Service.Save(projectID uint, originalName string, content io.Reader) (StoredFile, error)` +- Produces: `notes.Service.CreateNote(projectID uint, userID uint, title string, markdown string) (*domain.Note, error)` +- Produces: `search.Service.Search(userID uint, query string) ([]Result, error)` + +- [ ] Test that file storage writes under `projects//`. +- [ ] Test that file path traversal in original filename is neutralized with `filepath.Base`. +- [ ] Test note body search through `Search(userID, query)`. +- [ ] Implement local file service. +- [ ] Implement note/source service. +- [ ] Implement portable test search with SQLite `LIKE`. +- [ ] Add PostgreSQL `tsvector` indexes for notes, sources, inbox items, tasks, and AI sessions. +- [ ] Add PostgreSQL search query using `plainto_tsquery('simple', query)`. +- [ ] Run `go test ./... -v` from `backend`. +- [ ] Commit with `git commit -m "feat: add files notes and search"`. + +--- + +## Task 8: AI Provider 网关、Key 选择、日志与 Session + +**Files:** +- Create: `D:\work\senlinai\agent\backend\internal\ai\gateway.go` +- Create: `D:\work\senlinai\agent\backend\internal\ai\gateway_test.go` +- Create: `D:\work\senlinai\agent\backend\internal\ai\sessions.go` + +**Interfaces:** +- Produces: `ai.Gateway.SelectKey(userID uint) (SelectedKey, error)` +- Produces: `ai.Gateway.RecordCall(userID uint, provider string, usedKeyType string, action string, status string, errText string) error` +- Produces: `ai.SessionService.Create(projectID uint, userID uint, title string) (*domain.AISession, error)` + +- [ ] Test that user key takes precedence over system key. +- [ ] Test fallback to system key when user key is absent. +- [ ] Test no available key returns an explicit error. +- [ ] Test AI call logging records action, provider, key type, status, and error. +- [ ] Implement AI session creation under a project. +- [ ] Run `go test ./... -v` from `backend`. +- [ ] Commit with `git commit -m "feat: add ai gateway foundation"`. + +--- + +## Task 9: Web App Shell、项目 Dashboard 与标签页 + +**Files:** +- Create: `D:\work\senlinai\agent\apps\web\package.json` +- Create: `D:\work\senlinai\agent\apps\web\src\app\App.tsx` +- Create: `D:\work\senlinai\agent\apps\web\src\app\tabs.ts` +- Create: `D:\work\senlinai\agent\apps\web\src\lib\api.ts` +- Create: `D:\work\senlinai\agent\apps\web\src\features\projects\ProjectDashboard.tsx` +- Create: `D:\work\senlinai\agent\apps\web\src\features\projects\ProjectDashboard.test.tsx` + +**Interfaces:** +- Produces: `api.getProjects(): Promise` +- Produces: `api.getProjectDashboard(projectID: number): Promise` +- Produces: `openTab(existing: WorkspaceTab[], next: WorkspaceTab): WorkspaceTab[]` + +- [ ] Scaffold Vite React TypeScript app. +- [ ] Install TanStack Query, lucide-react, Vitest, Testing Library, jsdom. +- [ ] Test dashboard renders pending inbox, current tasks, notes, and AI sessions. +- [ ] Implement API client. +- [ ] Implement browser-style tab state helper. +- [ ] Implement dashboard component. +- [ ] Run `npm test -- --run` and `npm run build` from `apps/web`. +- [ ] Commit with `git commit -m "feat: add web workspace shell"`. + +--- + +## Task 10: Inbox、任务、笔记资料与 AI Session UI + +**Files:** +- Create: `D:\work\senlinai\agent\apps\web\src\features\inbox\InboxPanel.tsx` +- Create: `D:\work\senlinai\agent\apps\web\src\features\inbox\SuggestionList.tsx` +- Create: `D:\work\senlinai\agent\apps\web\src\features\tasks\TaskList.tsx` +- Create: `D:\work\senlinai\agent\apps\web\src\features\notes\MarkdownEditor.tsx` +- Create: `D:\work\senlinai\agent\apps\web\src\features\ai\AISessionCards.tsx` +- Create: `D:\work\senlinai\agent\apps\web\src\features\inbox\SuggestionList.test.tsx` + +**Interfaces:** +- Produces: `SuggestionList` with explicit checkbox confirmation. +- Produces: `MarkdownEditor` controlled component using `value` and `onChange`. +- Produces: `AISessionCards` card list for project sessions. + +- [ ] Test that SuggestionList does not confirm unselected suggestions. +- [ ] Implement inbox capture panel. +- [ ] Implement suggestion list with checkbox selection and editable basic fields. +- [ ] Implement task list with status and assignee display. +- [ ] Implement controlled Markdown editor. +- [ ] Implement AI session card list. +- [ ] Run `npm test -- --run` and `npm run build` from `apps/web`. +- [ ] Commit with `git commit -m "feat: add workspace feature panels"`. + +--- + +## Task 11: Tauri 桌面壳与快速收集 + +**Files:** +- Create: `D:\work\senlinai\agent\apps\desktop\package.json` +- Create: `D:\work\senlinai\agent\apps\desktop\src-tauri\tauri.conf.json` +- Create: `D:\work\senlinai\agent\apps\desktop\src-tauri\src\main.rs` +- Create: `D:\work\senlinai\agent\apps\web\src\features\capture\QuickCapture.tsx` + +**Interfaces:** +- Produces: Tauri app pointing at `apps/web` build/dev server. +- Produces: global shortcut command opening quick capture window. +- Produces: `QuickCapture` component with project destination and content input. + +- [ ] Scaffold Tauri app under `apps/desktop`. +- [ ] Configure Tauri product name `SenlinAI Workbench`. +- [ ] Configure dev URL `http://localhost:5173`. +- [ ] Configure frontend dist `../../web/dist`. +- [ ] Implement QuickCapture with project selector and content textarea. +- [ ] Verify `npm run build` from `apps/web`. +- [ ] Verify `npm run tauri build` from `apps/desktop`, documenting any missing native dependency. +- [ ] Commit with `git commit -m "feat: add tauri desktop shell"`. + +--- + +## Task 12: 端到端验证与 MVP 文档 + +**Files:** +- Create: `D:\work\senlinai\agent\apps\web\e2e\project-workbench.spec.ts` +- Modify: `D:\work\senlinai\agent\README.md` +- Create: `D:\work\senlinai\agent\docs\mvp-verification.md` + +**Interfaces:** +- Produces: one documented manual verification script. +- Produces: Playwright smoke test for project dashboard and inbox suggestion confirmation. + +- [ ] Install Playwright in `apps/web`. +- [ ] Add smoke test that verifies the project workbench screen and dashboard render. +- [ ] Document backend verification: `go test ./... -v` from `backend`. +- [ ] Document web verification: `npm test -- --run`, `npm run build`, `npx playwright test`. +- [ ] Document desktop verification: build web first, then run Tauri build. +- [ ] Document manual MVP flow: create project, capture inbox item, analyze, confirm task/note, assign task, share linked object, search, open AI session, quick capture. +- [ ] Run all available verification commands. +- [ ] Commit with `git commit -m "test: add mvp verification plan"`. + +--- + +## Self-Review + +Spec coverage: +- 项目工作区、Dashboard、Inbox、任务、笔记资料、AI 会话、项目内标签、任务分发、文件存储、AI key 策略、搜索、Tauri 壳和验证流程均有对应任务。 +- 语义搜索、真实即时通讯、项目级成员体系、公开分享链接和移动原生 App 明确不进入 MVP。 + +Placeholder scan: +- 本计划没有使用待填充占位内容。 +- 所有任务都有明确文件、接口、验证命令和提交命令。 + +Type consistency: +- 后端统一使用 `backend` 目录。 +- Go module path 统一为 `senlinai-agent/backend`。 +- 后端服务均以明确构造函数创建。 +- 核心模型来自 `domain` 包。 +- 前端 API 类型和 Dashboard 组件字段保持一致。 diff --git a/docs/superpowers/specs/2026-07-18-project-workbench-mvp-design.md b/docs/superpowers/specs/2026-07-18-project-workbench-mvp-design.md index 3aec87f..1c9ce32 100644 --- a/docs/superpowers/specs/2026-07-18-project-workbench-mvp-design.md +++ b/docs/superpowers/specs/2026-07-18-project-workbench-mvp-design.md @@ -1,253 +1,253 @@ -# Project Workbench MVP Design +# 项目工作台 MVP 设计 -## Purpose +## 目标 -Build a private-deployment personal and small-team workbench centered on projects. The product reduces scattered work materials by giving each project one place for tasks, notes, files, AI conversations, incoming items, and lightweight task distribution. +构建一个以项目为中心、面向私有部署的个人与小团队工作台。产品要解决资料分散、任务分散、AI 对话分散、用时找不到的问题,让每个项目都有统一位置承载任务、笔记、文件、AI 对话、收集内容和轻量任务分发。 -The first version is not a full collaboration suite, instant messenger, or enterprise knowledge graph. It is a project-first workspace with reliable capture, organization, search, and AI-assisted triage. +第一版不是完整协作套件、即时通讯工具或企业知识图谱。它是一个项目优先的工作空间,重点是可靠收集、组织、搜索和 AI 辅助整理。 -## Target Users +## 目标用户 -- Individual knowledge workers who manage many topics or projects. -- Team leads who need to organize project material and distribute tasks. -- Internal users in a private deployment who can be trusted through admin-created accounts or invite-based registration. +- 同时管理多个主题或项目的个人知识工作者。 +- 需要整理项目资料并分发任务的团队负责人。 +- 私有部署环境中的内部用户,通过管理员创建账号或邀请码注册。 -## Product Scope +## 产品范围 -### In Scope +### MVP 包含 -- Project-centered workspace. -- Project dashboard as the default project landing page. -- Project inbox for manual capture and system events. -- Task list with status and ordering that can support a future board view. -- Unified Markdown notes/documents. -- File and link sources stored under projects. -- Project-local tags for filtering project objects. -- AI sessions grouped under each project. -- AI sessions can reference project notes and sources. -- AI-assisted inbox analysis that proposes tasks, notes, or sources. -- Global keyword and full-text search across projects. -- Task distribution to system users. -- Explicit sharing of task-related objects. -- Responsive web frontend wrapped by Tauri. -- Tauri desktop features: login persistence, drag-and-drop upload, notifications, global shortcut quick capture. -- Go Gin + Gorm backend with PostgreSQL. -- Server-local file storage. -- System AI key plus optional user-provided AI key. +- 以项目为中心的工作空间。 +- 项目 Dashboard 作为项目默认首页。 +- 项目 inbox,用于手动收集和承接系统事件。 +- 任务列表,支持状态和排序,并为未来看板视图预留模型能力。 +- 统一的 Markdown 笔记/文档对象。 +- 项目内文件和链接资料。 +- 项目内标签,用于筛选项目对象。 +- AI 会话归属于具体项目。 +- AI 会话可以引用项目笔记和资料。 +- AI 辅助分析 inbox 内容,并建议生成任务、笔记或资料。 +- 跨项目关键词搜索和全文搜索。 +- 向系统内用户分发任务。 +- 显式共享任务关联对象。 +- 响应式 Web 前端,并由 Tauri 包装为桌面客户端。 +- Tauri 桌面能力:登录持久化、文件拖拽上传、系统通知、全局快捷键快速收集。 +- Go Gin + Gorm 后端,数据库使用 PostgreSQL。 +- 服务端本地文件存储。 +- 系统 AI key,以及用户可选配置自己的 AI key。 -### Out of Scope For MVP +### MVP 不包含 -- Real-time instant messaging. -- Project-level team membership and full role-based collaboration. -- Public anonymous task sharing. -- AI autonomous agent execution. -- Semantic/vector search. -- Mobile native app. -- Browser extension, email, IM, or third-party capture integrations. -- Offline editing or local-first sync. -- Advanced local desktop integrations such as startup launch, background clipboard monitoring, or file-system indexing. +- 真正的实时即时通讯。 +- 项目级成员体系和完整角色权限协作。 +- 匿名公开任务分享。 +- AI 自主 Agent 执行系统。 +- 语义搜索或向量搜索。 +- 移动端原生 App。 +- 浏览器插件、邮件、IM 或第三方渠道收集集成。 +- 离线编辑或本地优先同步。 +- 高级本地桌面集成,例如开机启动、后台剪贴板监听、文件系统索引。 -## Core Information Architecture +## 核心信息架构 -The app has a global shell and project workspaces. +应用分为全局外壳和项目工作区。 -Global shell: -- Project list. -- Global search. -- Browser-style open tabs. -- User/account settings. -- AI key settings. +全局外壳: +- 项目列表。 +- 全局搜索。 +- 浏览器式打开标签页。 +- 用户/账号设置。 +- AI key 设置。 -Project workspace: -- Dashboard. -- Inbox. -- Tasks. -- Notes and sources. -- AI sessions. -- Project settings. +项目工作区: +- Dashboard。 +- Inbox。 +- 任务。 +- 笔记和资料。 +- AI 会话。 +- 项目设置。 -The project dashboard is the default project view. It shows pending inbox items, current tasks, recent notes/sources, recent AI sessions, and recent project events. +项目 Dashboard 是默认项目视图。它展示待处理 inbox、当前任务、最近笔记/资料、最近 AI 会话和最近项目事件。 -## Primary Objects +## 核心对象 ### Project -A project is a theme workspace. It owns tasks, inbox items, notes, sources, AI sessions, project-local tags, and events. +项目是一个主题工作空间。它拥有任务、inbox 条目、笔记、资料、AI 会话、项目内标签和项目事件。 ### InboxItem -An inbox item is raw incoming material or a system event awaiting review. Sources include: -- Manual text entry. -- Pasted links. -- Uploaded files. -- AI outputs saved into the inbox. -- Internal task events, comments, and status changes. +InboxItem 是等待处理的原始收集内容或系统事件。来源包括: +- 手动输入文本。 +- 粘贴链接。 +- 上传文件。 +- AI 输出保存到 inbox。 +- 内部任务事件、评论、状态变化。 -External channels are represented through a `source_type` field but not implemented in MVP. +外部渠道通过 `source_type` 字段预留,但 MVP 不实现外部渠道接入。 -Inbox items remain as source records after processing. Processing can mark an item as `processed`, but it does not delete the original. +InboxItem 在处理后仍作为原始记录保留。处理动作可以将其标记为 `processed`,但不会删除原始内容。 ### Task -A task supports: -- Title. -- Description. -- Status. -- Sort/order field for future board views. -- Due date. -- Assignee. -- Project-local tags. -- Explicitly shared linked objects. -- Comments or activity entries. +任务支持: +- 标题。 +- 描述。 +- 状态。 +- 排序字段,用于未来看板视图。 +- 截止时间。 +- 负责人。 +- 项目内标签。 +- 显式共享的关联对象。 +- 评论或活动记录。 -The first UI is a list. The model leaves room for Todo/Doing/Done board columns in a future release. +第一版 UI 使用列表。数据模型为未来 Todo/Doing/Done 看板列预留空间。 ### Note -Notes and documents are one unified Markdown object. Templates, length, tags, and user intent can make a note behave like a meeting note, PRD, research note, or lightweight document. +笔记和文档合并为同一种 Markdown 对象。通过模板、长度、标签和使用意图区分会议纪要、PRD、研究笔记或轻量文档。 ### Source -A source is original material such as an uploaded file or link. Server-local file storage is used in MVP. File-path logic should be concentrated in one backend service rather than scattered through handlers. +Source 是原始资料,例如上传文件或链接。MVP 使用服务端本地文件存储。文件路径逻辑必须集中在一个后端文件服务中,不应散落在各个 handler 里。 ### AISession -An AI session belongs to one project and appears as a card in the project AI session list. Each session has its own conversation context and can reference project notes and sources. +AI 会话归属于一个项目,并以卡片形式出现在项目 AI 会话列表中。每个 session 拥有自己的消息历史和上下文,可以引用项目笔记和资料。 -MVP sessions are ordinary project chats, not autonomous agent runs. The model should leave space for future agent-style sessions with goals, status, outputs, and execution logs. +MVP 中的 AI session 是普通项目聊天,不是自主 Agent 运行。模型应为未来 Agent 化 session 预留空间,例如目标、状态、产出物和执行日志。 ### Tag -Tags are project-local. They can be attached to project-owned objects for filtering within that project. Cross-project lookup uses global search instead of global tags. +标签是项目内标签。它们可以附加到项目内对象,用于该项目内筛选。跨项目查找依赖全局搜索,而不是全局标签。 ### ProjectEvent -A project event records notable activity such as task assignment, comments, status changes, AI output saved to inbox, note updates, and file uploads. Events can appear in project dashboard and inbox-like streams. +项目事件记录重要活动,例如任务分发、评论、状态变化、AI 输出保存到 inbox、笔记更新和文件上传。事件可展示在项目 Dashboard 和类 inbox 的消息流中。 ### User -Users are managed in a private-deployment model. Registration is admin-created or invite-based. Public self-serve SaaS signup is outside MVP scope. +用户体系面向私有部署。注册方式是管理员创建账号或邀请码注册。公开自助注册 SaaS 不在 MVP 范围内。 -## Key Flows +## 关键流程 -### Capture To Project Inbox +### 收集到项目 Inbox -The user adds text, a link, or a file to a project inbox. In Tauri, the global shortcut opens a quick capture window. If the main window has an active project, that project is the default destination; otherwise the user chooses a destination project. +用户将文本、链接或文件添加到项目 inbox。在 Tauri 中,全局快捷键打开快速收集窗口。如果主窗口当前处于某个项目,该项目作为默认投递目标;否则用户需要选择目标项目。 -The item enters inbox without automatic AI processing. The user can click Analyze/Organize when they want suggestions. +内容进入 inbox 时不会自动触发 AI 分析。用户需要建议时,可以点击“分析/整理”。 -### AI-Assisted Inbox Processing +### AI 辅助处理 Inbox -When the user clicks Analyze/Organize, the backend calls the selected AI provider. The AI returns a structured suggestion list containing candidate tasks, notes, or sources. +当用户点击“分析/整理”时,后端调用选定的 AI provider。AI 返回结构化建议列表,包含候选任务、笔记或资料。 -The user reviews a suggestion list, edits titles or basic fields, selects the candidates to create, and confirms. AI never creates official objects without confirmation. Created objects keep a reference back to the original inbox item. +用户查看建议列表,编辑标题或基础字段,勾选要创建的候选项,然后确认。AI 不能在未确认时直接创建正式对象。创建出的对象需要保留对原始 inbox item 的引用。 -### Project AI Conversation +### 项目 AI 对话 -The user opens an AI session card under a project. The session has its own message history. The user can reference project notes or sources with an @-style picker. AI responses can be saved into inbox, converted into notes, or linked to tasks. +用户在项目下打开一个 AI session 卡片。该 session 有自己的消息历史。用户可以用 @ 选择器引用项目笔记或资料。AI 回复可以保存到 inbox、转换为笔记,或关联到任务。 -### Task Distribution +### 任务分发 -The user assigns or shares a task with another system user. The recipient can see the task and only the explicitly shared linked objects. They do not automatically gain project-level access. +用户可以把任务指派或共享给另一个系统用户。接收者能看到任务本身,以及被显式共享的关联对象。接收者不会自动获得项目级访问权限。 -Public share links are a future capability, not part of MVP behavior. +公开分享链接是未来能力,不属于 MVP 行为。 -### Global Search +### 全局搜索 -The user searches across projects. MVP search covers: -- Project names. -- Task titles and descriptions. -- Note titles and Markdown bodies. -- Source titles, filenames, URLs, and extracted text where available. -- AI session titles and message text. -- Inbox text. +用户可以跨项目搜索。MVP 搜索范围包括: +- 项目名称。 +- 任务标题和描述。 +- 笔记标题和 Markdown 正文。 +- 资料标题、文件名、URL,以及可提取到的正文文本。 +- AI session 标题和消息文本。 +- Inbox 文本。 -Search uses PostgreSQL full-text search with keyword support. Semantic search is deferred. +搜索使用 PostgreSQL 全文搜索,并支持关键词搜索。语义搜索后置。 -## AI Key Policy +## AI Key 策略 -The system has a configured default AI provider/key. Users may configure their own provider/key. If a user key exists, calls should use it; otherwise calls may fall back to the system key. +系统配置默认 AI provider/key。用户可以配置自己的 provider/key。如果用户配置了自己的 key,优先使用用户 key;否则可以回退到系统 key。 -Because system key fallback is allowed, MVP must include: -- AI call logging. -- Basic per-user rate limiting. -- Error recording. -- Secure storage for user keys. -- Clear provider selection behavior. +因为允许回退到系统 key,MVP 必须包含: +- AI 调用日志。 +- 基础用户级限流。 +- 错误记录。 +- 用户 key 的安全存储。 +- 清晰的 provider 选择逻辑。 -## Backend Design +## 后端设计 -Backend stack: -- Go. -- Gin HTTP API. -- Gorm ORM. -- PostgreSQL. -- Server-local file storage. +后端技术栈: +- Go。 +- Gin HTTP API。 +- Gorm ORM。 +- PostgreSQL。 +- 服务端本地文件存储。 -Suggested backend modules: -- Auth and invitations. -- Projects. -- Inbox. -- Tasks. -- Notes and sources. -- AI sessions. -- Search. -- Files. -- Tags. -- Events. -- AI provider gateway. +建议后端模块: +- 认证和邀请。 +- 项目。 +- Inbox。 +- 任务。 +- 笔记和资料。 +- AI 会话。 +- 搜索。 +- 文件。 +- 标签。 +- 事件。 +- AI provider 网关。 -The file module owns all filesystem paths and metadata rules. Handlers should not directly construct storage paths. +文件模块拥有所有文件系统路径和元数据规则。Handler 不应直接构造存储路径。 -## Frontend And Tauri Design +## 前端与 Tauri 设计 -The frontend is a responsive web app, desktop-first but usable on mobile browsers. Tauri wraps the same frontend for desktop. +前端是响应式 Web 应用,桌面优先,同时保证移动浏览器可用。Tauri 包装同一套前端作为桌面客户端。 -Tauri MVP capabilities: -- Persistent login state. -- File drag-and-drop upload. -- System notifications. -- Global shortcut quick capture. +Tauri MVP 能力: +- 登录状态持久化。 +- 文件拖拽上传。 +- 系统通知。 +- 全局快捷键快速收集。 -Browser-style tabs store page-level state such as open project, task detail, note, source, or AI session. Rich internal state such as scroll position is not required in MVP. Editor and AI input drafts should be saved separately to avoid data loss. +浏览器式标签页保存页面级状态,例如打开的项目、任务详情、笔记、资料或 AI session。滚动位置等复杂内部状态不要求进入 MVP。编辑器草稿和 AI 输入草稿应单独保存,避免数据丢失。 -## Permissions +## 权限 -MVP uses conservative sharing: -- Project content is private to the owner unless explicitly shared through a task. -- Task assignees can see the task. -- Task assignees can see linked notes, sources, or files only when explicitly shared. -- Project-level membership and roles are deferred. +MVP 使用保守共享模型: +- 项目内容默认仅创建者可见,除非通过任务显式共享。 +- 任务接收者可以看到任务。 +- 任务接收者只有在对象被显式共享时,才能看到关联笔记、资料或文件。 +- 项目级成员和角色体系后置。 -## Testing And Verification +## 测试与验证 -Backend: -- Unit tests for AI key selection, inbox processing, task sharing visibility, tag scoping, and file metadata handling. -- Integration tests for project search, task distribution, and inbox-to-object conversion. +后端: +- AI key 选择、inbox 处理、任务共享可见性、标签项目隔离、文件元数据处理的单元测试。 +- 项目搜索、任务分发、inbox 转对象的集成测试。 -Frontend: -- Component tests for project dashboard, inbox suggestion confirmation, AI session list, task list, and quick capture form. -- End-to-end tests for capture, analyze, confirm, search, and task assignment flows. +前端: +- 项目 Dashboard、inbox 建议确认、AI session 列表、任务列表、快速收集表单的组件测试。 +- 收集、分析、确认、搜索、任务指派流程的端到端测试。 -Desktop: -- Manual verification for drag-and-drop upload, notification delivery, quick capture shortcut, and login persistence in Tauri. +桌面端: +- 手动验证文件拖拽上传、系统通知、快速收集快捷键、Tauri 登录持久化。 -## Risks +## 风险 -- The system can become too broad if project collaboration, real messaging, and autonomous agents enter MVP. -- System AI key fallback creates cost and abuse risk without logging and rate limits. -- PostgreSQL full-text search may require tuning for Chinese content. -- Server-local files are simple but require careful backup, path handling, and migration planning. -- AI suggestions must remain reviewable and reversible to avoid user distrust. +- 如果项目协作、真实消息和自主 Agent 进入 MVP,系统会迅速变得过宽。 +- 系统 AI key 回退会带来成本和滥用风险,必须配套日志和限流。 +- PostgreSQL 全文搜索对中文内容可能需要额外调优。 +- 服务端本地文件存储简单,但需要认真处理备份、路径规则和未来迁移。 +- AI 建议必须可审核、可撤销,否则用户会失去信任。 -## Milestones +## 里程碑 -1. Backend foundation: auth, users, projects, PostgreSQL schema, file metadata, server-local storage. -2. Project workspace shell: project list, project dashboard, tabs, routing. -3. Inbox and capture: manual inbox, upload/link/text capture, Tauri quick capture. -4. Notes, sources, and project-local tags. -5. Task list, assignment, comments/events, explicit linked-object sharing. -6. AI sessions and @ reference picker. -7. AI inbox analysis with suggestion-list confirmation. -8. Global full-text search. -9. Tauri desktop polish: notifications, login persistence, drag-and-drop verification. +1. 后端基础:认证、用户、项目、PostgreSQL schema、文件元数据、服务端本地存储。 +2. 项目工作区外壳:项目列表、项目 Dashboard、标签页、路由。 +3. Inbox 和收集:手动 inbox、上传/链接/文本收集、Tauri 快速收集。 +4. 笔记、资料和项目内标签。 +5. 任务列表、指派、评论/事件、显式共享关联对象。 +6. AI session 和 @ 引用选择器。 +7. AI inbox 分析和建议列表确认。 +8. 全局全文搜索。 +9. Tauri 桌面完善:通知、登录持久化、拖拽上传验证。