refactor: 删除未使用的 api_external_url 配置并完善 runtime 文档

- 删除 SupabaseSettings 中未使用的 api_external_url computed field
- 更新测试文件移除相关测试用例
- backend/AGENTS.md 新增软删除设计规则
- runtime-database.md 更新表结构(删除 user_agents,表名更新为 agent_chat_sessions/messages,system_agents)
- runtime-frontend.md 补充路由结构和功能模块说明
- 根 AGENTS.md 清理过时技能路径引用
This commit is contained in:
qzl
2026-03-06 18:25:18 +08:00
parent 105e7849fe
commit 1f6cb1a48f
6 changed files with 523 additions and 320 deletions
-3
View File
@@ -17,7 +17,6 @@ social-app/
Follow this hierarchy when developing:
```
~/.config/opencode/AGENTS.md # Global core rules (skills, agents, process)
├── This file (root AGENTS.md) # Project-level entry
│ ├── backend/AGENTS.md # Backend-specific rules
│ └── apps/AGENTS.md # Frontend-specific rules
@@ -41,8 +40,6 @@ Follow this hierarchy when developing:
## Skills (Domain Knowledge)
Project-specific skills are available in `.opencode/skills/`:
| Skill | Purpose | When to Use |
|-------|---------|-------------|
| **ag-ui** | AG-UI protocol for agent-user interaction | Agent chat, streaming events, tool calls, state sync |
+20
View File
@@ -164,6 +164,26 @@ Use `schemas / repository / service` pattern:
- service_role key is backend-only; never expose credentials
- Prohibit calling Supabase Admin API (service_role key) from repository/service layers
### Soft Delete
**Soft delete marks data as invisible, not cascade delete.**
- Use `deleted_at: datetime | None` column (via `SoftDeleteMixin`)
- **Query filtering**: Repository `_apply_soft_delete_filter()` auto-excludes deleted records
- **No automatic cascade**: Related data stays intact; visibility controlled by JOIN filtering
- **Cascade only for strong dependencies**: When parent deletion must invalidate children, implement in Service layer explicitly
- **Recovery**: Only restore the record itself; related data visibility restored automatically via queries
- **Unique constraints**: Use partial indexes excluding `deleted_at IS NOT NULL` to allow re-creation
```python
# Partial unique index in migration
op.execute("""
CREATE UNIQUE INDEX ux_user_email
ON users(email)
WHERE deleted_at IS NULL
""")
```
### Migrations
- **Alembic is the single source of truth** for schema migrations
-5
View File
@@ -128,11 +128,6 @@ class SupabaseSettings(BaseModel):
def public_url(self) -> str:
return f"{self.public_scheme}://{self.public_host}:{self.kong_http_port}"
@computed_field
@property
def api_external_url(self) -> str:
return self.public_url
@computed_field
@property
def url(self) -> str:
@@ -23,27 +23,13 @@ def test_social_prefixed_supabase_env_populates_settings(
settings = Settings()
assert settings.supabase.public_url == "https://public.example:8443"
assert settings.supabase.api_external_url == "https://public.example:8443"
assert settings.supabase.anon_key == "anon-key"
assert settings.supabase.service_role_key == "service-key"
assert settings.supabase.jwt_secret == "jwt-secret"
supabase_settings = settings.model_dump()["supabase"]
assert supabase_settings["public_url"] == "https://public.example:8443"
assert supabase_settings["api_external_url"] == "https://public.example:8443"
assert supabase_settings["anon_key"] == "anon-key"
assert supabase_settings["service_role_key"] == "service-key"
assert supabase_settings["jwt_secret"] == "jwt-secret"
assert settings.database_url == "postgresql+asyncpg://user:pass@db:5432/app"
def test_social_prefixed_api_external_url_is_loaded(
monkeypatch: MonkeyPatch,
) -> None:
monkeypatch.setenv("SOCIAL_SUPABASE__PUBLIC_SCHEME", "https")
monkeypatch.setenv("SOCIAL_SUPABASE__PUBLIC_HOST", "api.example")
monkeypatch.setenv("SOCIAL_SUPABASE__KONG_HTTP_PORT", "8443")
settings = Settings()
assert settings.supabase.api_external_url == "https://api.example:8443"
+339 -295
View File
@@ -1,64 +1,106 @@
# Database Schema
**Status:** Active
**Reference:** [Plan: social-app 数据模型重设计](../plans/2026-02-26-social-data-model-redesign.md)
**Status:** Active
**Last Updated:** 2026-03-06
---
## 枚举约定
## 架构概览
### 数据库层职责
- **Supabase**: 认证(JWT 签发与验证)
- **Backend**: 业务授权(Service 层)、数据访问(Repository 层)
- **ORM**: SQLAlchemyasync + asyncpg,使用 service_role 连接)
### 核心模块
| 模块 | 路径 | 说明 |
|------|------|------|
| Base Classes | `backend/src/core/db/` | ORM 基类、Session 管理、Repository 基类 |
| Models | `backend/src/models/` | 数据模型定义 |
| Migrations | `backend/alembic/versions/` | 数据库迁移脚本 |
---
## 设计约定
### 枚举存储
**所有枚举使用字符串存储,不使用整数值:**
所有枚举使用字符串存储,不使用整数值:
- Database: `VARCHAR(20)` + `CHECK` 约束
- Code: Python `Enum` 继承 `str`
```python
class AgentType(str, Enum):
INTENT_RECOGNITION = "INTENT_RECOGNITION"
TASK_EXECUTION = "TASK_EXECUTION"
RESULT_REPORTING = "RESULT_REPORTING"
```
### 软删除
**软删除标记数据为不可见,不级联删除:**
- 使用 `deleted_at: datetime | None` 列(通过 `SoftDeleteMixin`
- 查询过滤:Repository `_apply_soft_delete_filter()` 自动排除已删除记录
- 级联策略:默认不级联,强依赖关系在 Service 层手动处理
- 恢复策略:只恢复记录本身,关联数据通过查询自动恢复可见
- 唯一约束:使用 partial index 排除 `deleted_at IS NOT NULL`
```sql
-- Partial unique index in migration
CREATE UNIQUE INDEX ux_user_email
ON users(email)
WHERE deleted_at IS NULL
```
---
## 表清单
| 表名 | 说明 |
|------|------|
| `profiles` | 用户资料(含 settings JSONB |
| `user_agents` | 用户专属 Agent |
| `memories` | 用户/工作记忆 |
| `friendships` | 好友关系 |
| `groups` | 群组 |
| `group_members` | 群组成员 |
| `schedule_items` | 日程事项 |
| `schedule_subscriptions` | 日程订阅与权限 |
| `inbox_messages` | 待处理消息 |
| `todos` | 待办 |
| `todo_sources` | 待办与日程来源关联 |
| `automation_jobs` | 定时任务 |
| `sessions` | Agent 对话会话 |
| `messages` | 会话消息记录 |
| `llm_factory` | LLM 工厂配置 |
| `llms` | LLM 模型实例 |
| `user_agent_catalog` | Agent 类型目录 |
| `invite_codes` | 邀请码 |
| 表名 | 说明 | 状态 |
|------|------|------|
| `profiles` | 用户资料(含 settings JSONB | Active |
| `memories` | 用户记忆 | Active |
| `friendships` | 好友关系 | Active |
| `groups` | 群组 | Active |
| `group_members` | 群组成员 | Active |
| `schedule_items` | 日程事项 | Active |
| `schedule_subscriptions` | 日程订阅与权限 | Active |
| `inbox_messages` | 待处理消息 | Active |
| `todos` | 待办 | Active |
| `todo_sources` | 待办与日程来源关联 | Active |
| `automation_jobs` | 定时任务 | Active |
| `agent_chat_sessions` | Agent 对话会话 | Active |
| `agent_chat_messages` | 会话消息记录 | Active |
| `llm_factory` | LLM 工厂配置 | Active |
| `llms` | LLM 模型实例 | Active |
| `system_agents` | 系统级 Agent 配置 | Active |
| `invite_codes` | 邀请码 | Active |
---
## 表结构
## 表结构详细
### profiles
用户资料表,含内置设置。
用户资料表,含用户设置。
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | UUID | PK`auth.users.id` |
| `username` | VARCHAR(30) | 用户名 |
| `avatar_url` | TEXT | 头像 URL |
| `bio` | VARCHAR(200) | 个人简介 |
| `settings` | JSONB | 用户设置 |
| `referred_by` | UUID | 邀请人 ID |
| `created_at` | TIMESTAMPTZ | 创建时间 |
| `updated_at` | TIMESTAMPTZ | 更新时间 |
| `deleted_at` | TIMESTAMPTZ | 软删时间 |
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `id` | UUID | PK, FK → auth.users.id | 用户 ID |
| `username` | VARCHAR(30) | UNIQUE, NOT NULL | 用户名 |
| `avatar_url` | TEXT | NULLABLE | 头像 URL |
| `bio` | VARCHAR(200) | NULLABLE | 个人简介 |
| `settings` | JSONB | NOT NULL, DEFAULT '{}' | 用户设置 |
| `referred_by` | UUID | NULLABLE, FK → profiles.id | 邀请人 ID |
| `created_at` | TIMESTAMPTZ | NOT NULL, DEFAULT now() | 创建时间 |
| `updated_at` | TIMESTAMPTZ | NOT NULL, DEFAULT now() | 更新时间 |
| `deleted_at` | TIMESTAMPTZ | NULLABLE | 软删时间 |
**约束:** `username` 唯一
**settings JSONB 默认结构:**
**settings JSONB 结构:**
```json
{
"version": 1,
@@ -67,6 +109,10 @@
"ai_language": "zh-CN",
"timezone": "Asia/Shanghai"
},
"agent_prompts": {
"INTENT_RECOGNITION": "自定义提示词...",
"TASK_EXECUTION": "自定义提示词..."
},
"privacy": {},
"notification": {}
}
@@ -74,46 +120,21 @@
---
### user_agents
用户专属 Agent 配置。
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | UUID | PK |
| `user_id` | UUID | 用户 ID |
| `llm_id` | UUID | 关联的 LLM 模型 |
| `agent_type` | VARCHAR(20) | 枚举:`INTENT_RECOGNITION`, `TASK_EXECUTION`, `RESULT_REPORTING` |
| `config` | JSONB | Agent 配置参数 |
| `status` | VARCHAR(20) | 状态:`active`, `paused`, `migrating` |
| `created_by` | UUID | 创建者 |
| `updated_by` | UUID | 更新者 |
| `created_at` | TIMESTAMPTZ | 创建时间 |
| `updated_at` | TIMESTAMPTZ | 更新时间 |
| `deleted_at` | TIMESTAMPTZ | 软删时间 |
**约束:** `(user_id, agent_type)` 唯一
---
### memories
用户与工作记忆。
用户记忆。
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | UUID | PK |
| `owner_id` | UUID | 用户 ID |
| `agent_id` | UUID | Agent IDwork 类型必填) |
| `memory_type` | VARCHAR(20) | 枚举:`user`, `work` |
| `title` | VARCHAR(255) | 标题 |
| `content` | JSONB | 记忆内容 |
| `source` | VARCHAR(20) | 来源:`manual`, `agent`, `imported` |
| `status` | VARCHAR(20) | 状态:`active`, `disabled` |
| `created_at` | TIMESTAMPTZ | 创建时间 |
| `updated_at` | TIMESTAMPTZ | 更新时间 |
**约束:** work 类型必须有 agent_iduser 类型必须无 agent_id
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `id` | UUID | PK | 记忆 ID |
| `owner_id` | UUID | NOT NULL, FK → profiles.id | 所有者 ID |
| `memory_type` | VARCHAR(20) | NOT NULL, CHECK | 枚举:`user`, `work` |
| `title` | VARCHAR(255) | NOT NULL | 标题 |
| `content` | JSONB | NOT NULL | 记忆内容 |
| `source` | VARCHAR(20) | NOT NULL | 来源:`manual`, `agent`, `imported` |
| `status` | VARCHAR(20) | NOT NULL | 状态:`active`, `disabled` |
| `created_at` | TIMESTAMPTZ | NOT NULL, DEFAULT now() | 创建时间 |
| `updated_at` | TIMESTAMPTZ | NOT NULL, DEFAULT now() | 更新时间 |
**content JSONB 示例:**
```json
@@ -130,23 +151,25 @@
好友关系(双向规范化)。
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | UUID | PK |
| `user_low_id` | UUID | 较小 UUID |
| `user_high_id` | UUID | 较大 UUID |
| `initiator_id` | UUID | 发起方用户 ID |
| `status` | VARCHAR(20) | 状态:`pending`, `accepted`, `blocked`, `declined`, `canceled` |
| `requested_at` | TIMESTAMPTZ | 请求时间 |
| `accepted_at` | TIMESTAMPTZ | 接受时间 |
| `blocked_by` | UUID | 阻止者用户 ID |
| `created_by` | UUID | 创建者 |
| `updated_by` | UUID | 更新者 |
| `created_at` | TIMESTAMPTZ | 创建时间 |
| `updated_at` | TIMESTAMPTZ | 更新时间 |
| `deleted_at` | TIMESTAMPTZ | 软删时间 |
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `id` | UUID | PK | 关系 ID |
| `user_low_id` | UUID | NOT NULL | 较小 UUID |
| `user_high_id` | UUID | NOT NULL | 较大 UUID |
| `initiator_id` | UUID | NOT NULL | 发起方用户 ID |
| `status` | VARCHAR(20) | NOT NULL, CHECK | 状态:`pending`, `accepted`, `blocked`, `declined`, `canceled` |
| `requested_at` | TIMESTAMPTZ | NOT NULL, DEFAULT now() | 请求时间 |
| `accepted_at` | TIMESTAMPTZ | NULLABLE | 接受时间 |
| `blocked_by` | UUID | NULLABLE | 阻止者用户 ID |
| `created_by` | UUID | NULLABLE | 创建者 |
| `updated_by` | UUID | NULLABLE | 更新者 |
| `created_at` | TIMESTAMPTZ | NOT NULL, DEFAULT now() | 创建时间 |
| `updated_at` | TIMESTAMPTZ | NOT NULL, DEFAULT now() | 更新时间 |
| `deleted_at` | TIMESTAMPTZ | NULLABLE | 软删时间 |
**约束:** `user_low_id < user_high_id``(user_low_id, user_high_id)` 唯一
**约束:**
- `user_low_id < user_high_id`
- `UNIQUE(user_low_id, user_high_id)`
---
@@ -154,18 +177,18 @@
群组。
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | UUID | PK |
| `name` | VARCHAR(100) | 群组名称 |
| `description` | TEXT | 群组描述 |
| `owner_id` | UUID | 创建者 ID |
| `status` | VARCHAR(20) | 状态:`active`, `archived` |
| `created_by` | UUID | 创建者 |
| `updated_by` | UUID | 更新者 |
| `created_at` | TIMESTAMPTZ | 创建时间 |
| `updated_at` | TIMESTAMPTZ | 更新时间 |
| `deleted_at` | TIMESTAMPTZ | 软删时间 |
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `id` | UUID | PK | 群组 ID |
| `name` | VARCHAR(100) | NOT NULL | 群组名称 |
| `description` | TEXT | NULLABLE | 群组描述 |
| `owner_id` | UUID | NOT NULL, FK → profiles.id | 创建者 ID |
| `status` | VARCHAR(20) | NOT NULL, CHECK | 状态:`active`, `archived` |
| `created_by` | UUID | NULLABLE | 创建者 |
| `updated_by` | UUID | NULLABLE | 更新者 |
| `created_at` | TIMESTAMPTZ | NOT NULL, DEFAULT now() | 创建时间 |
| `updated_at` | TIMESTAMPTZ | NOT NULL, DEFAULT now() | 更新时间 |
| `deleted_at` | TIMESTAMPTZ | NULLABLE | 软删时间 |
---
@@ -173,24 +196,24 @@
群组成员。
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | UUID | PK |
| `group_id` | UUID | 群组 ID |
| `user_id` | UUID | 用户 ID |
| `role` | VARCHAR(20) | 角色:`owner`, `admin`, `member` |
| `join_source` | VARCHAR(20) | 加入方式:`invited`, `joined` |
| `invited_by` | UUID | 邀请人 ID |
| `joined_at` | TIMESTAMPTZ | 加入时间 |
| `removed_at` | TIMESTAMPTZ | 移除时间 |
| `status` | VARCHAR(20) | 状态:`active`, `muted`, `removed` |
| `created_by` | UUID | 创建者 |
| `updated_by` | UUID | 更新者 |
| `created_at` | TIMESTAMPTZ | 创建时间 |
| `updated_at` | TIMESTAMPTZ | 更新时间 |
| `deleted_at` | TIMESTAMPTZ | 软删时间 |
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `id` | UUID | PK | 成员 ID |
| `group_id` | UUID | NOT NULL, FK → groups.id | 群组 ID |
| `user_id` | UUID | NOT NULL, FK → profiles.id | 用户 ID |
| `role` | VARCHAR(20) | NOT NULL, CHECK | 角色:`owner`, `admin`, `member` |
| `join_source` | VARCHAR(20) | NOT NULL | 加入方式:`invited`, `joined` |
| `invited_by` | UUID | NULLABLE, FK → profiles.id | 邀请人 ID |
| `joined_at` | TIMESTAMPTZ | NOT NULL, DEFAULT now() | 加入时间 |
| `removed_at` | TIMESTAMPTZ | NULLABLE | 移除时间 |
| `status` | VARCHAR(20) | NOT NULL, CHECK | 状态:`active`, `muted`, `removed` |
| `created_by` | UUID | NULLABLE | 创建者 |
| `updated_by` | UUID | NULLABLE | 更新者 |
| `created_at` | TIMESTAMPTZ | NOT NULL, DEFAULT now() | 创建时间 |
| `updated_at` | TIMESTAMPTZ | NOT NULL, DEFAULT now() | 更新时间 |
| `deleted_at` | TIMESTAMPTZ | NULLABLE | 软删时间 |
**约束:** `(group_id, user_id)` 唯一
**约束:** `UNIQUE(group_id, user_id)`
---
@@ -198,25 +221,25 @@
日程事项。
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | UUID | PK |
| `owner_id` | UUID | 所有者 ID |
| `title` | VARCHAR(255) | 标题 |
| `description` | TEXT | 描述 |
| `start_at` | TIMESTAMPTZ | 开始时间 |
| `end_at` | TIMESTAMPTZ | 结束时间 |
| `timezone` | VARCHAR(50) | 时区 |
| `metadata` | JSONB | 扩展字段 |
| `recurrence_rule` | VARCHAR(255) | 循环规则 |
| `source_type` | VARCHAR(20) | 来源:`manual`, `imported`, `agent_generated` |
| `status` | VARCHAR(20) | 状态:`active`, `completed`, `canceled`, `archived` |
| `created_by` | UUID | 创建者 |
| `created_at` | TIMESTAMPTZ | 创建时间 |
| `updated_at` | TIMESTAMPTZ | 更新时间 |
| `deleted_at` | TIMESTAMPTZ | 软删时间 |
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `id` | UUID | PK | 事项 ID |
| `owner_id` | UUID | NOT NULL, FK → profiles.id | 所有者 ID |
| `title` | VARCHAR(255) | NOT NULL | 标题 |
| `description` | TEXT | NULLABLE | 描述 |
| `start_at` | TIMESTAMPTZ | NOT NULL | 开始时间 |
| `end_at` | TIMESTAMPTZ | NULLABLE | 结束时间 |
| `timezone` | VARCHAR(50) | NOT NULL, DEFAULT 'UTC' | 时区 |
| `metadata` | JSONB | NOT NULL, DEFAULT '{}' | 扩展字段 |
| `recurrence_rule` | VARCHAR(255) | NULLABLE | 循环规则 |
| `source_type` | VARCHAR(20) | NOT NULL, CHECK | 来源:`manual`, `imported`, `agent_generated` |
| `status` | VARCHAR(20) | NOT NULL, CHECK | 状态:`active`, `completed`, `canceled`, `archived` |
| `created_by` | UUID | NULLABLE | 创建者 |
| `created_at` | TIMESTAMPTZ | NOT NULL, DEFAULT now() | 创建时间 |
| `updated_at` | TIMESTAMPTZ | NOT NULL, DEFAULT now() | 更新时间 |
| `deleted_at` | TIMESTAMPTZ | NULLABLE | 软删时间 |
**metadata JSONB 默认结构:**
**metadata JSONB 结构:**
```json
{
"color": "#FF6B6B",
@@ -246,19 +269,21 @@
日程订阅与权限。
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | UUID | PK |
| `item_id` | UUID | 日程事项 ID |
| `subscriber_id` | UUID | 订阅者 ID |
| `permission` | INTEGER | 权限位图(view=1, invite=2, edit=4,默认 1 |
| `notify_level` | VARCHAR(20) | 通知级别:`all`, `mentions`, `none`,默认 `all` |
| `status` | VARCHAR(20) | 状态:`active`, `paused`, `unsubscribed`,默认 `active` |
| `created_by` | UUID | 创建者 |
| `created_at` | TIMESTAMPTZ | 创建时间 |
| `updated_at` | TIMESTAMPTZ | 更新时间 |
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `id` | UUID | PK | 订阅 ID |
| `item_id` | UUID | NOT NULL, FK → schedule_items.id | 日程事项 ID |
| `subscriber_id` | UUID | NOT NULL, FK → profiles.id | 订阅者 ID |
| `permission` | INTEGER | NOT NULL, CHECK, DEFAULT 1 | 权限位图(view=1, invite=2, edit=4 |
| `notify_level` | VARCHAR(20) | NOT NULL, DEFAULT 'all' | 通知级别:`all`, `mentions`, `none` |
| `status` | VARCHAR(20) | NOT NULL, DEFAULT 'active' | 状态:`active`, `paused`, `unsubscribed` |
| `created_by` | UUID | NULLABLE | 创建者 |
| `created_at` | TIMESTAMPTZ | NOT NULL, DEFAULT now() | 创建时间 |
| `updated_at` | TIMESTAMPTZ | NOT NULL, DEFAULT now() | 更新时间 |
**约束:** `(item_id, subscriber_id)` 唯一,`permission BETWEEN 0 AND 7`
**约束:**
- `UNIQUE(item_id, subscriber_id)`
- `permission BETWEEN 0 AND 7`
---
@@ -266,21 +291,21 @@
待处理消息(接收者视角)。
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | UUID | PK |
| `recipient_id` | UUID | 接收者 ID |
| `sender_id` | UUID | 发送者 ID(系统消息可为 NULL) |
| `message_type` | VARCHAR(20) | 类型:`friend_request`, `calendar`, `system`, `group` |
| `friendship_id` | UUID | 好友请求关联(friend_request 时必填) |
| `schedule_item_id` | UUID | 日程关联(calendar 时必填) |
| `group_id` | UUID | 群组关联(group 时必填) |
| `content` | TEXT | 消息内容(system 用) |
| `is_read` | BOOLEAN | 是否已读,默认 false |
| `status` | VARCHAR(20) | 状态:`pending`, `accepted`, `rejected`, `dismissed` |
| `created_by` | UUID | 创建者 |
| `created_at` | TIMESTAMPTZ | 创建时间 |
| `updated_at` | TIMESTAMPTZ | 更新时间 |
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `id` | UUID | PK | 消息 ID |
| `recipient_id` | UUID | NOT NULL, FK → profiles.id | 接收者 ID |
| `sender_id` | UUID | NULLABLE, FK → profiles.id | 发送者 ID(系统消息可为 NULL) |
| `message_type` | VARCHAR(20) | NOT NULL, CHECK | 类型:`friend_request`, `calendar`, `system`, `group` |
| `friendship_id` | UUID | NULLABLE, FK → friendships.id | 好友请求关联(friend_request 时必填) |
| `schedule_item_id` | UUID | NULLABLE, FK → schedule_items.id | 日程关联(calendar 时必填) |
| `group_id` | UUID | NULLABLE, FK → groups.id | 群组关联(group 时必填) |
| `content` | TEXT | NULLABLE | 消息内容(system 用) |
| `is_read` | BOOLEAN | NOT NULL, DEFAULT false | 是否已读 |
| `status` | VARCHAR(20) | NOT NULL, CHECK | 状态:`pending`, `accepted`, `rejected`, `dismissed` |
| `created_by` | UUID | NULLABLE | 创建者 |
| `created_at` | TIMESTAMPTZ | NOT NULL, DEFAULT now() | 创建时间 |
| `updated_at` | TIMESTAMPTZ | NOT NULL, DEFAULT now() | 更新时间 |
**message_type 与业务字段对应:**
| message_type | 必填字段 |
@@ -298,20 +323,20 @@
待办事项。
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | UUID | PK |
| `owner_id` | UUID | 所有者 ID |
| `title` | VARCHAR(255) | 标题 |
| `description` | VARCHAR(1000) | 描述 |
| `due_at` | TIMESTAMPTZ | 截止时间 |
| `priority` | INTEGER | 优先级(1-41=重要且紧急) |
| `status` | VARCHAR(20) | 状态:`pending`, `done`, `canceled` |
| `completed_at` | TIMESTAMPTZ | 完成时间 |
| `created_by` | UUID | 创建者 |
| `created_at` | TIMESTAMPTZ | 创建时间 |
| `updated_at` | TIMESTAMPTZ | 更新时间 |
| `deleted_at` | TIMESTAMPTZ | 软删时间 |
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `id` | UUID | PK | 待办 ID |
| `owner_id` | UUID | NOT NULL, FK → profiles.id | 所有者 ID |
| `title` | VARCHAR(255) | NOT NULL | 标题 |
| `description` | VARCHAR(1000) | NULLABLE | 描述 |
| `due_at` | TIMESTAMPTZ | NULLABLE | 截止时间 |
| `priority` | INTEGER | NOT NULL, CHECK, DEFAULT 3 | 优先级(1-4,1=重要且紧急) |
| `status` | VARCHAR(20) | NOT NULL, CHECK | 状态:`pending`, `done`, `canceled` |
| `completed_at` | TIMESTAMPTZ | NULLABLE | 完成时间 |
| `created_by` | UUID | NULLABLE | 创建者 |
| `created_at` | TIMESTAMPTZ | NOT NULL, DEFAULT now() | 创建时间 |
| `updated_at` | TIMESTAMPTZ | NOT NULL, DEFAULT now() | 更新时间 |
| `deleted_at` | TIMESTAMPTZ | NULLABLE | 软删时间 |
**约束:** `priority BETWEEN 1 AND 4`
@@ -321,15 +346,15 @@
待办与日程来源关联。
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | UUID | PK |
| `todo_id` | UUID | 待办 ID |
| `schedule_item_id` | UUID | 日程事项 ID |
| `created_at` | TIMESTAMPTZ | 创建时间 |
| `updated_at` | TIMESTAMPTZ | 更新时间 |
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `id` | UUID | PK | 关联 ID |
| `todo_id` | UUID | NOT NULL, FK → todos.id ON DELETE CASCADE | 待办 ID |
| `schedule_item_id` | UUID | NOT NULL, FK → schedule_items.id ON DELETE CASCADE | 日程事项 ID |
| `created_at` | TIMESTAMPTZ | NOT NULL, DEFAULT now() | 创建时间 |
| `updated_at` | TIMESTAMPTZ | NOT NULL, DEFAULT now() | 更新时间 |
**约束:** `(todo_id, schedule_item_id)` 唯一
**约束:** `UNIQUE(todo_id, schedule_item_id)`
---
@@ -337,75 +362,77 @@
自动化定时任务。
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | UUID | PK |
| `owner_id` | UUID | 所有者 ID |
| `title` | VARCHAR(255) | 任务标题 |
| `prompt` | TEXT | AI 执行 prompt |
| `schedule_type` | VARCHAR(20) | 调度类型:`daily`, `weekly` |
| `run_at` | TIMESTAMPTZ | 首次运行时间 |
| `next_run_at` | TIMESTAMPTZ | 下次运行时间 |
| `timezone` | VARCHAR(50) | 时区 |
| `last_run_at` | TIMESTAMPTZ | 最近运行时间 |
| `status` | VARCHAR(20) | 状态:`active`, `disabled` |
| `created_by` | UUID | 创建者 |
| `created_at` | TIMESTAMPTZ | 创建时间 |
| `updated_at` | TIMESTAMPTZ | 更新时间 |
| `deleted_at` | TIMESTAMPTZ | 软删时间 |
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `id` | UUID | PK | 任务 ID |
| `owner_id` | UUID | NOT NULL, FK → profiles.id | 所有者 ID |
| `title` | VARCHAR(255) | NOT NULL | 任务标题 |
| `prompt` | TEXT | NOT NULL | AI 执行 prompt |
| `schedule_type` | VARCHAR(20) | NOT NULL, CHECK | 调度类型:`daily`, `weekly` |
| `run_at` | TIMESTAMPTZ | NOT NULL | 首次运行时间 |
| `next_run_at` | TIMESTAMPTZ | NULLABLE | 下次运行时间 |
| `timezone` | VARCHAR(50) | NOT NULL, DEFAULT 'UTC' | 时区 |
| `last_run_at` | TIMESTAMPTZ | NULLABLE | 最近运行时间 |
| `status` | VARCHAR(20) | NOT NULL, CHECK | 状态:`active`, `disabled` |
| `created_by` | UUID | NULLABLE | 创建者 |
| `created_at` | TIMESTAMPTZ | NOT NULL, DEFAULT now() | 创建时间 |
| `updated_at` | TIMESTAMPTZ | NOT NULL, DEFAULT now() | 更新时间 |
| `deleted_at` | TIMESTAMPTZ | NULLABLE | 软删时间 |
**约束:** `(id, owner_id)` 唯一
**约束:** `UNIQUE(id, owner_id)`
---
### sessions
### agent_chat_sessions
Agent 对话会话。
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | UUID | PK |
| `user_id` | UUID | 用户 ID |
| `session_type` | VARCHAR(20) | 会话类型:`chat`, `automation` |
| `job_id` | UUID | 自动化任务 IDautomation 时必填) |
| `title` | VARCHAR(255) | 会话标题 |
| `status` | VARCHAR(20) | 状态:`pending`, `running`, `completed`, `failed` |
| `last_activity_at` | TIMESTAMPTZ | 最后活跃时间 |
| `message_count` | INTEGER | 消息计数,默认 0 |
| `total_tokens` | INTEGER | 总 token 数,默认 0 |
| `total_cost` | NUMERIC(12,6) | 总费用,默认 0 |
| `created_at` | TIMESTAMPTZ | 创建时间 |
| `updated_at` | TIMESTAMPTZ | 更新时间 |
| `deleted_at` | TIMESTAMPTZ | 软删时间 |
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `id` | UUID | PK | 会话 ID |
| `user_id` | UUID | NOT NULL, FK → profiles.id | 用户 ID |
| `session_type` | VARCHAR(20) | NOT NULL, CHECK | 会话类型:`chat`, `automation` |
| `job_id` | UUID | NULLABLE, FK → automation_jobs.id ON DELETE RESTRICT | 自动化任务 IDautomation 时必填) |
| `title` | VARCHAR(255) | NULLABLE | 会话标题 |
| `status` | VARCHAR(20) | NOT NULL, CHECK | 状态:`pending`, `running`, `completed`, `failed` |
| `last_activity_at` | TIMESTAMPTZ | NULLABLE | 最后活跃时间 |
| `message_count` | INTEGER | NOT NULL, DEFAULT 0 | 消息计数 |
| `total_tokens` | INTEGER | NOT NULL, DEFAULT 0 | 总 token 数 |
| `total_cost` | NUMERIC(12,6) | NOT NULL, DEFAULT 0 | 总费用 |
| `created_at` | TIMESTAMPTZ | NOT NULL, DEFAULT now() | 创建时间 |
| `updated_at` | TIMESTAMPTZ | NOT NULL, DEFAULT now() | 更新时间 |
| `deleted_at` | TIMESTAMPTZ | NULLABLE | 软删时间 |
**约束:** `session_type='chat' → job_id IS NULL`, `session_type='automation' → job_id IS NOT NULL`
**约束:**
- `session_type='chat' → job_id IS NULL`
- `session_type='automation' → job_id IS NOT NULL`
---
### messages
### agent_chat_messages
会话消息记录。
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | UUID | PK |
| `session_id` | UUID | 会话 ID |
| `seq` | INTEGER | 消息序号 |
| `role` | VARCHAR(20) | 角色:`user`, `assistant`, `system`, `tool` |
| `content` | TEXT | 消息内容 |
| `model_code` | VARCHAR(50) | 模型标识 |
| `tool_name` | VARCHAR(100) | 工具名称 |
| `input_tokens` | INTEGER | 输入 token 数,默认 0 |
| `output_tokens` | INTEGER | 输出 token 数,默认 0 |
| `cost` | NUMERIC(12,6) | 费用,默认 0 |
| `currency` | VARCHAR(3) | 货币,默认 USD |
| `latency_ms` | INTEGER | 延迟(毫秒) |
| `metadata` | JSONB | 扩展字段 |
| `created_at` | TIMESTAMPTZ | 创建时间 |
| `updated_at` | TIMESTAMPTZ | 更新时间 |
| `deleted_at` | TIMESTAMPTZ | 软删时间 |
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `id` | UUID | PK | 消息 ID |
| `session_id` | UUID | NOT NULL, FK → agent_chat_sessions.id | 会话 ID |
| `seq` | INTEGER | NOT NULL | 消息序号 |
| `role` | VARCHAR(20) | NOT NULL, CHECK | 角色:`user`, `assistant`, `system`, `tool` |
| `content` | TEXT | NOT NULL | 消息内容 |
| `model_code` | VARCHAR(50) | NULLABLE | 模型标识 |
| `tool_name` | VARCHAR(100) | NULLABLE | 工具名称 |
| `input_tokens` | INTEGER | NOT NULL, DEFAULT 0 | 输入 token 数 |
| `output_tokens` | INTEGER | NOT NULL, DEFAULT 0 | 输出 token 数 |
| `cost` | NUMERIC(12,6) | NOT NULL, DEFAULT 0 | 费用 |
| `currency` | VARCHAR(3) | NOT NULL, DEFAULT 'USD' | 货币 |
| `latency_ms` | INTEGER | NULLABLE | 延迟(毫秒) |
| `metadata` | JSONB | NOT NULL, DEFAULT '{}' | 扩展字段 |
| `created_at` | TIMESTAMPTZ | NOT NULL, DEFAULT now() | 创建时间 |
| `updated_at` | TIMESTAMPTZ | NOT NULL, DEFAULT now() | 更新时间 |
| `deleted_at` | TIMESTAMPTZ | NULLABLE | 软删时间 |
**约束:** `(session_id, seq)` 唯一
**约束:** `UNIQUE(session_id, seq)`
---
@@ -413,17 +440,15 @@ Agent 对话会话。
LLM 工厂配置。
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | UUID | PK |
| `name` | VARCHAR(50) | 工厂名称 |
| `request_url` | VARCHAR(255) | API 请求 URL |
| `avatar` | TEXT | 头像 URL |
| `created_at` | TIMESTAMPTZ | 创建时间 |
| `updated_at` | TIMESTAMPTZ | 更新时间 |
| `deleted_at` | TIMESTAMPTZ | 软删时间 |
**约束:** `name` 唯一
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `id` | UUID | PK | 工厂 ID |
| `name` | VARCHAR(50) | UNIQUE, NOT NULL | 工厂名称 |
| `request_url` | VARCHAR(255) | NOT NULL | API 请求 URL |
| `avatar` | TEXT | NULLABLE | 头像 URL |
| `created_at` | TIMESTAMPTZ | NOT NULL, DEFAULT now() | 创建时间 |
| `updated_at` | TIMESTAMPTZ | NOT NULL, DEFAULT now() | 更新时间 |
| `deleted_at` | TIMESTAMPTZ | NULLABLE | 软删时间 |
---
@@ -431,31 +456,34 @@ LLM 工厂配置。
LLM 模型实例。
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | UUID | PK |
| `factory_id` | UUID | 工厂 ID |
| `model_code` | VARCHAR(50) | 模型标识 |
| `created_at` | TIMESTAMPTZ | 创建时间 |
| `updated_at` | TIMESTAMPTZ | 更新时间 |
| `deleted_at` | TIMESTAMPTZ | 软删时间 |
**约束:** `model_code` 唯一
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `id` | UUID | PK | 模型 ID |
| `factory_id` | UUID | NOT NULL, FK → llm_factory.id ON DELETE RESTRICT | 工厂 ID |
| `model_code` | VARCHAR(50) | UNIQUE, NOT NULL | 模型标识 |
| `created_at` | TIMESTAMPTZ | NOT NULL, DEFAULT now() | 创建时间 |
| `updated_at` | TIMESTAMPTZ | NOT NULL, DEFAULT now() | 更新时间 |
| `deleted_at` | TIMESTAMPTZ | NULLABLE | 软删时间 |
---
### user_agent_catalog
### system_agents
Agent 类型目录
系统级 Agent 配置(原 user_agent_catalog
| 字段 | 类型 | 说明 |
|------|------|------|
| `agent_type` | VARCHAR(20) | PKAgent 类型 |
| `llm_id` | UUID | 关联的 LLM 模型 |
| `status` | VARCHAR(20) | 状态:`active`, `paused`, `migrating` |
| `config` | JSONB | Agent 配置参数 |
| `created_at` | TIMESTAMPTZ | 创建时间 |
| `updated_at` | TIMESTAMPTZ | 更新时间 |
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `agent_type` | VARCHAR(20) | PK, CHECK | Agent 类型 |
| `llm_id` | UUID | NOT NULL, FK → llms.id ON DELETE RESTRICT | 关联的 LLM 模型 |
| `status` | VARCHAR(20) | NOT NULL, CHECK | 状态:`active`, `paused`, `migrating` |
| `config` | JSONB | NOT NULL, DEFAULT '{}' | Agent 配置参数 |
| `created_at` | TIMESTAMPTZ | NOT NULL, DEFAULT now() | 创建时间 |
| `updated_at` | TIMESTAMPTZ | NOT NULL, DEFAULT now() | 更新时间 |
**agent_type 枚举值:**
- `INTENT_RECOGNITION` - 意图识别
- `TASK_EXECUTION` - 任务执行
- `RESULT_REPORTING` - 结果报告
---
@@ -463,33 +491,37 @@ Agent 类型目录。
邀请码。
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | UUID | PK |
| `code` | VARCHAR(8) | 邀请码(8 位大写字母数字) |
| `owner_id` | UUID | 拥有者 ID |
| `status` | VARCHAR(20) | 状态:`active`, `disabled`, `expired` |
| `used_count` | INTEGER | 已使用次数,默认 0 |
| `max_uses` | INTEGER | 最大使用次数 |
| `expires_at` | TIMESTAMPTZ | 过期时间 |
| `reward_config` | JSONB | 奖励配置 |
| `created_at` | TIMESTAMPTZ | 创建时间 |
| `updated_at` | TIMESTAMPTZ | 更新时间 |
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `id` | UUID | PK | 邀请码 ID |
| `code` | VARCHAR(8) | UNIQUE, NOT NULL, CHECK | 邀请码(8 位大写字母数字) |
| `owner_id` | UUID | NOT NULL, FK → profiles.id | 拥有者 ID |
| `status` | VARCHAR(20) | NOT NULL, CHECK | 状态:`active`, `disabled`, `expired` |
| `used_count` | INTEGER | NOT NULL, DEFAULT 0, CHECK | 已使用次数 |
| `max_uses` | INTEGER | NULLABLE | 最大使用次数 |
| `expires_at` | TIMESTAMPTZ | NULLABLE | 过期时间 |
| `reward_config` | JSONB | NULLABLE | 奖励配置 |
| `created_at` | TIMESTAMPTZ | NOT NULL, DEFAULT now() | 创建时间 |
| `updated_at` | TIMESTAMPTZ | NOT NULL, DEFAULT now() | 更新时间 |
**约束:** `code` 唯一,`code` 符合 `[ABCDEFGHJKMNPQRSTUVWXYZ23456789]{8}``used_count >= 0`
**约束:**
- `code` 符合 `[ABCDEFGHJKMNPQRSTUVWXYZ23456789]{8}`
- `used_count >= 0`
---
## 外键删除策略
| 外键 | 删除策略 |
|------|----------|
| `sessions.job_id` | RESTRICT |
| `todo_sources.todo_id` | CASCADE |
| `todo_sources.schedule_item_id` | CASCADE |
| `inbox_messages.friendship_id` | CASCADE |
| `inbox_messages.schedule_item_id` | CASCADE |
| `inbox_messages.group_id` | CASCADE |
| 外键 | 删除策略 | 说明 |
|------|----------|------|
| `agent_chat_sessions.job_id` | RESTRICT | 禁止删除正在使用的自动化任务 |
| `todo_sources.todo_id` | CASCADE | 删除待办时级联删除关联 |
| `todo_sources.schedule_item_id` | CASCADE | 删除日程时级联删除关联 |
| `inbox_messages.friendship_id` | CASCADE | 删除好友关系时级联删除消息 |
| `inbox_messages.schedule_item_id` | CASCADE | 删除日程时级联删除消息 |
| `inbox_messages.group_id` | CASCADE | 删除群组时级联删除消息 |
| `llms.factory_id` | RESTRICT | 禁止删除正在使用的工厂配置 |
| `system_agents.llm_id` | RESTRICT | 禁止删除正在使用的 LLM 模型 |
---
@@ -499,3 +531,15 @@ Agent 类型目录。
- `anon`: 全部 DENY
- `authenticated`: 全部 DENY
- `service_role`: 由后端服务连接,不依赖 RLS
**例外:** 迁移表 `alembic_version` 不暴露给任何角色。
---
## Change Log
| 日期 | 变更 |
|------|------|
| 2026-03-06 | 删除 `user_agents` 表,重命名 `user_agent_catalog``system_agents`,更新 `agent_chat_sessions` / `agent_chat_messages` 表名,删除 `memories.agent_id` 字段 |
| 2026-02-28 | 新增 `invite_codes` 表、`profiles.referred_by` 字段 |
| 2026-02-26 | 初始版本,基于数据模型重设计 |
+164 -3
View File
@@ -1,11 +1,21 @@
# Frontend Runtime Runbook
# Frontend Runtime
**Date:** 2026-02-27
**Status:** Active
**Date:** 2026-03-06
**Status:** Active
**Audience:** 前端开发
---
## 技术栈
- **Framework:** Flutter (Dart)
- **Routing:** go_router
- **State Management:** BLoC + Cubit
- **API Client:** Dio + Retrofit
- **Mock Mode:** 支持 `--dart-define=MOCK_API=true`
---
## 开发环境
### Mock 模式
@@ -35,6 +45,156 @@ Mock 模式下,启动 App 时会自动使用测试账号登录并跳转到首
---
## 路由结构
### 认证路由(无需登录)
| 路由 | 页面 | 说明 |
|------|------|------|
| `/` | LoginScreen | 登录页(默认首页) |
| `/register` | RegisterScreen | 注册页 |
| `/register/verification` | RegisterVerificationScreen | 注册验证码页 |
| `/reset-password` | ResetPasswordScreen | 重置密码页 |
### 受保护路由(需要登录)
| 路由 | 页面 | 说明 |
|------|------|------|
| `/home` | HomeScreen | 首页(AI 助手) |
| `/contacts` | ContactsScreen | 通讯录 |
| `/contacts/add` | AddContactScreen | 添加联系人 |
| `/calendar/month` | CalendarMonthScreen | 月视图 |
| `/calendar/dayweek` | CalendarDayweekScreen | 日/周视图 |
| `/calendar/events/:id` | CalendarEventDetailScreen | 日程详情 |
| `/todo` | TodoQuadrantsScreen | 待办四象限 |
| `/messages/invites` | MessageInviteListScreen | 消息邀请列表 |
| `/messages/invites/:id` | MessageInviteDetailScreen | 消息邀请详情 |
| `/settings` | SettingsScreen | 设置首页 |
| `/settings/features` | FeaturesScreen | 功能开关 |
| `/settings/memory` | MemoryScreen | 记忆管理 |
| `/settings/account` | AccountScreen | 账号设置 |
---
## 功能模块
### Auth(认证)
**路径:** `apps/lib/features/auth/`
| 文件 | 说明 |
|------|------|
| `presentation/bloc/auth_bloc.dart` | 认证状态管理 |
| `presentation/cubits/` | 登录/注册/重置密码 Cubit |
| `ui/screens/` | 认证相关页面 |
| `data/repositories/auth_repository.dart` | 认证 API 调用 |
**流程:**
1. 注册: `/register` → 输入邮箱/用户名/密码 → `/register/verification` → 输入验证码 → `/home`
2. 登录: `/` → 输入邮箱/密码 → `/home`
3. 重置密码: `/reset-password` → 输入邮箱 → 收到邮件 → 输入验证码+新密码 → `/`
---
### Home(首页/AI 助手)
**路径:** `apps/lib/features/home/`
| 文件 | 说明 |
|------|------|
| `ui/screens/home_screen.dart` | 首页(AI 助手入口) |
| `ui/screens/home_sheet.dart` | 首页底部弹出面板 |
**功能:**
- AI 助手对话入口
- 快速访问常用功能
---
### Calendar(日历)
**路径:** `apps/lib/features/calendar/`
| 文件 | 说明 |
|------|------|
| `ui/screens/calendar_month_screen.dart` | 月视图 |
| `ui/screens/calendar_dayweek_screen.dart` | 日/周视图 |
| `ui/screens/calendar_event_detail_screen.dart` | 日程详情 |
| `ui/calendar_time_utils.dart` | 时间工具函数 |
**功能:**
- 月/日/周三视图切换
- 日程创建/编辑/删除
- 日程分享
---
### Todo(待办)
**路径:** `apps/lib/features/todo/`
| 文件 | 说明 |
|------|------|
| `ui/screens/todo_quadrants_screen.dart` | 四象限视图 |
| `ui/screens/todo_detail_screen.dart` | 待办详情 |
**功能:**
- 四象限管理(重要/紧急矩阵)
- 待办创建/编辑/完成/删除
---
### Contacts(通讯录)
**路径:** `apps/lib/features/contacts/`
| 文件 | 说明 |
|------|------|
| `ui/screens/contacts_screen.dart` | 通讯录列表 |
| `ui/screens/add_contact_screen.dart` | 添加联系人 |
**功能:**
- 好友列表
- 搜索用户
- 添加好友
---
### Messages(消息)
**路径:** `apps/lib/features/messages/`
| 文件 | 说明 |
|------|------|
| `ui/screens/message_invite_list_screen.dart` | 邀请消息列表 |
| `ui/screens/message_invite_detail_screen.dart` | 邀请详情 |
**功能:**
- 日程邀请通知
- 好友请求通知
- 群组邀请通知
---
### Settings(设置)
**路径:** `apps/lib/features/settings/`
| 文件 | 说明 |
|------|------|
| `ui/screens/settings_screen.dart` | 设置首页 |
| `ui/screens/features_screen.dart` | 功能开关 |
| `ui/screens/memory_screen.dart` | 记忆管理 |
| `ui/screens/account_screen.dart` | 账号设置 |
**功能:**
- 个人资料编辑
- 记忆管理(用户/工作记忆)
- 功能开关
- 账号安全设置
---
## 打包构建
### Debug Build
@@ -100,4 +260,5 @@ flutter run -d emulator-5554
| 日期 | 变更 |
|------|------|
| 2026-03-06 | 完善路由结构、功能模块说明,补充技术栈信息 |
| 2026-02-27 | 新增 Frontend Runbook,支持 --dart-define=MOCK_API=true 切换 Mock 模式 |