2026-07-18 15:47:34 +08:00
# 项目工作台 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 桌面壳。
2026-07-18 16:40:09 +08:00
**Architecture:** 使用前后端分离 monorepo。`backend` 提供 Go Gin + Gorm + PostgreSQL API, `apps/web` 提供响应式 Svelte Web 客户端,`apps/desktop` 用 Tauri 包装 Web 客户端并提供桌面能力。先实现清晰模块边界,再逐步接入 AI、搜索和桌面特性。
2026-07-18 15:47:34 +08:00
2026-07-18 16:40:09 +08:00
**Tech Stack:** Go 1.22+, Gin, Gorm, PostgreSQL 16+, Svelte, TypeScript, Vite, Tauri 2, Vitest, Testing Library for Svelte, Playwright, Docker Compose.
2026-07-18 15:47:34 +08:00
## Global Constraints
- 私有部署优先,用户通过管理员创建账号或邀请码注册。
- 数据以服务端为准,本地客户端只是 Tauri 壳,不做离线编辑或本地优先同步。
- 文件第一版存储在服务端本地磁盘,路径逻辑集中在文件服务中。
- 标签是项目内标签,不做全局标签体系。
- AI 默认允许回退到系统 key, 必须有调用日志、基础限流、错误记录和用户 key 安全存储。
- AI 不得自动创建正式对象, Inbox 分析结果必须由用户确认。
- 任务分发只面向系统内用户,接收者只能看到任务和显式共享的关联对象。
- 全局搜索使用 PostgreSQL 关键词和全文搜索,语义搜索不进入 MVP。
2026-07-18 16:40:09 +08:00
- 前端只使用 Svelte, 不使用 React。
- 登录页必须允许用户填写服务器 IP 或域名,并将其保存为 API base URL。
2026-07-18 17:42:50 +08:00
- PostgreSQL 集成验证通过 `DATABASE_URL` 读取测试服务器地址;不要提交真实凭据。
2026-07-18 15:47:34 +08:00
- 前端桌面优先,同时保持移动浏览器可用。
- 每个任务结束时运行该任务列出的验证命令并提交。
---
## 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/<projectID>/` .
- [ ] 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<Project[]>`
- Produces: `api.getProjectDashboard(projectID: number): Promise<ProjectDashboardSummary>`
- Produces: `openTab(existing: WorkspaceTab[], next: WorkspaceTab): WorkspaceTab[]`
2026-07-18 16:40:09 +08:00
- [ ] Scaffold Vite Svelte TypeScript app.
- [ ] Install Svelte, Vitest, Testing Library for Svelte, jest-dom, jsdom.
- [ ] Implement server address input on the login shell and persist it as API base URL.
2026-07-18 15:47:34 +08:00
- [ ] 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`
2026-07-18 17:31:49 +08:00
- Create: `D:\work\senlinai\agent\apps\web\src\features\capture\QuickCapture.svelte`
2026-07-18 15:47:34 +08:00
**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 组件字段保持一致。
2026-07-18 17:34:06 +08:00
---
## Implementation Status
Last updated: 2026-07-18.
- [x] Checkpoint 0: 规格、实施计划和 `AGENTS.md` 已提交。
- [x] Checkpoint 1: Monorepo 与 `backend` 基础脚手架已完成,`GET /healthz` 测试通过。
- [x] Checkpoint 2: 核心 Gorm 数据模型和 AutoMigrate 测试已完成。
- [x] Checkpoint 3: 私有部署邀请注册、bcrypt 登录和 bearer middleware 已完成。
- [x] Checkpoint 4: 项目、项目内标签和 Dashboard service/handler 已完成。
- [x] Checkpoint 5: Inbox 收集、AI 建议分析接口和确认创建对象流程已完成。
- [x] Checkpoint 6: 任务指派、显式共享和项目事件记录已完成。
- [x] Checkpoint 7: 本地文件服务、Markdown note service、搜索 service 和 PostgreSQL `tsvector` 索引迁移已完成。
- [x] Checkpoint 8: AI key 选择、系统 key fallback、调用日志和项目 AI session service 已完成。
- [x] Checkpoint 9: Svelte Web shell、服务器 IP/域名输入、Dashboard、API client 和标签页 helper 已完成。
- [x] Checkpoint 10: Svelte Inbox、AI 建议确认、任务列表、Markdown 编辑器、AI session 卡片和快速收集组件已完成。
- [x] Checkpoint 11: Tauri 2 桌面壳文件已完成, Web 构建可供 Tauri 使用。
- [x] Checkpoint 12: 后端单元测试、PostgreSQL integration ping、Web component tests、Web build 和 Playwright smoke test 已完成。
2026-07-18 17:42:50 +08:00
- [x] Router/main wiring audit: `/api` route registrar 已接入,`cmd/api` 启动时连接 PostgreSQL、执行 AutoMigrate, 并注册项目与 Inbox handlers。
- [x] Search coverage audit: 搜索 service 已覆盖 project、task、note、source、inbox 和 AI session。
- [x] Credential hygiene audit: 当前文档不再保存真实 PostgreSQL 测试库连接串,集成验证统一通过 `DATABASE_URL` 注入。
2026-07-18 20:09:29 +08:00
- [x] Desktop binary verification: Cargo/Rust is available, and `npm run build` from `apps/desktop` passes with `tauri build --no-bundle` , producing `target/release/senlinai_workbench.exe` .
2026-07-18 17:34:06 +08:00
Verification evidence from the latest run:
- `backend` : `go test ./... -v` passed.
- PostgreSQL test server: `go test -tags integration ./internal/db -run TestPostgresPing -v` passed using `DATABASE_URL` .
- `apps/web` : `npm test -- --run` passed.
- `apps/web` : `npm run build` passed.
- `apps/web` : `npx playwright test` passed.
- `apps/desktop` : `npm run build` is blocked before compilation by incomplete Rust stable toolchain.
2026-07-18 18:02:29 +08:00
## 2026-07-18 Full Audit Follow-Up
- [x] Backend API startup now wires PostgreSQL, AutoMigrate, project routes, and inbox routes through `/api` .
- [x] Auth sessions are signed and expiring; legacy forgeable `user:<id>` tokens are rejected.
- [x] Project and inbox HTTP handlers use the authenticated bearer-token user instead of a hard-coded user ID.
- [x] Project dashboard reads are owner-scoped before counters are returned.
- [x] Search now covers projects, tasks, notes, sources, inbox items, and AI sessions.
- [x] Inbox confirmation keeps provenance through `source_inbox_item_id` on generated tasks, notes, and sources.
- [x] Task sharing rejects note/source objects from a different project.
- [x] AI user keys are encrypted at rest and gateway rate-limit checks are covered by tests.
- [x] Svelte web client calls the backend `/api` prefix, passes type checking, and has Playwright coverage for inbox suggestion confirmation.
- [x] Current Markdown docs no longer include the live PostgreSQL test credential; integration tests must receive it through `DATABASE_URL` .
2026-07-18 20:09:29 +08:00
- [x] Desktop executable build passes with Cargo/Rust available: `npx tauri build --no-bundle --verbose` produces `apps/desktop/src-tauri/target/release/senlinai_workbench.exe` .
- [ ] Desktop installer bundling remains blocked on this machine because Tauri CLI packaging times out before producing `target/release/bundle` artifacts. Use `npm run bundle` to retry installer generation.