807 lines
20 KiB
Markdown
807 lines
20 KiB
Markdown
# Bug Fix Implementation Plan
|
|
|
|
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
|
|
|
**Goal:** 修复 TRIGGER-001 (user_agents 自动创建) 和 TOKEN-001 (Flutter 硬编码颜色迁移)
|
|
|
|
**Architecture:**
|
|
- TRIGGER-001: YAML 配置 → catalog 表 → Trigger 自动创建
|
|
- TOKEN-001: 渐进式迁移硬编码颜色到 AppColors
|
|
|
|
**Tech Stack:** Python, PostgreSQL, Alembic, Dart, Flutter
|
|
|
|
---
|
|
|
|
## TRIGGER-001: user_agents 自动创建
|
|
|
|
### Task 1: 创建 user_agent_catalog.yaml 配置文件
|
|
|
|
**Files:**
|
|
- Modify: `backend/src/core/config/static/database/user_agent_catalog.yaml`
|
|
|
|
**Step 1: 填充 YAML 配置**
|
|
|
|
```yaml
|
|
agents:
|
|
- agent_type: INTENT_RECOGNITION
|
|
llm_model_code: qwen3.5-flash
|
|
status: active
|
|
config:
|
|
temperature: 0.7
|
|
|
|
- agent_type: TASK_EXECUTION
|
|
llm_model_code: deepseek-v3.2
|
|
status: active
|
|
config:
|
|
temperature: 0.7
|
|
|
|
- agent_type: RESULT_REPORTING
|
|
llm_model_code: deepseek-v3.2
|
|
status: active
|
|
config:
|
|
temperature: 0.7
|
|
```
|
|
|
|
**Step 2: 验证 YAML 语法**
|
|
|
|
Run: `cat backend/src/core/config/static/database/user_agent_catalog.yaml`
|
|
|
|
Expected: YAML 格式正确,包含 3 个 agent 配置
|
|
|
|
**Step 3: Commit**
|
|
|
|
```bash
|
|
git add backend/src/core/config/static/database/user_agent_catalog.yaml
|
|
git commit -m "feat(config): add user_agent_catalog.yaml with default agent configs"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 2: 创建 UserAgentCatalog 模型
|
|
|
|
**Files:**
|
|
- Create: `backend/src/models/user_agent_catalog.py`
|
|
- Modify: `backend/src/models/__init__.py`
|
|
|
|
**Step 1: 创建模型文件**
|
|
|
|
```python
|
|
# backend/src/models/user_agent_catalog.py
|
|
from __future__ import annotations
|
|
|
|
from sqlalchemy import ForeignKey, String
|
|
from sqlalchemy.dialects.postgresql import JSONB, UUID
|
|
from sqlalchemy.orm import Mapped, mapped_column
|
|
|
|
from core.db.base import Base, TimestampMixin
|
|
|
|
|
|
class UserAgentCatalog(TimestampMixin, Base):
|
|
__tablename__ = "user_agent_catalog"
|
|
|
|
agent_type: Mapped[str] = mapped_column(
|
|
String(20),
|
|
primary_key=True,
|
|
)
|
|
llm_id: Mapped[str] = mapped_column(
|
|
UUID(as_uuid=True),
|
|
ForeignKey("llms.id", ondelete="RESTRICT"),
|
|
nullable=False,
|
|
)
|
|
status: Mapped[str] = mapped_column(
|
|
String(20),
|
|
nullable=False,
|
|
)
|
|
config: Mapped[dict] = mapped_column(
|
|
JSONB,
|
|
nullable=False,
|
|
server_default="{}",
|
|
)
|
|
```
|
|
|
|
**Step 2: 在 __init__.py 中导出**
|
|
|
|
```python
|
|
# backend/src/models/__init__.py (添加)
|
|
from models.user_agent_catalog import UserAgentCatalog
|
|
|
|
__all__ = [
|
|
# ... 现有导出
|
|
"UserAgentCatalog",
|
|
]
|
|
```
|
|
|
|
**Step 3: 运行类型检查**
|
|
|
|
Run: `cd backend && uv run basedpyright src/models/user_agent_catalog.py`
|
|
|
|
Expected: No errors
|
|
|
|
**Step 4: Commit**
|
|
|
|
```bash
|
|
git add backend/src/models/user_agent_catalog.py backend/src/models/__init__.py
|
|
git commit -m "feat(models): add UserAgentCatalog model"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 3: 创建 Alembic 迁移
|
|
|
|
**Files:**
|
|
- Create: `backend/alembic/versions/20260302_add_user_agent_catalog.py`
|
|
|
|
**Step 1: 生成迁移文件**
|
|
|
|
Run: `cd backend && uv run alembic revision -m "add_user_agent_catalog"`
|
|
|
|
**Step 2: 编写迁移逻辑**
|
|
|
|
```python
|
|
# backend/alembic/versions/20260302_add_user_agent_catalog.py
|
|
"""add user_agent_catalog table
|
|
|
|
Revision ID: 202603020001
|
|
Revises:
|
|
Create Date: 2026-03-02
|
|
|
|
"""
|
|
from typing import Sequence, Union
|
|
|
|
from alembic import op
|
|
import sqlalchemy as sa
|
|
from sqlalchemy.dialects import postgresql
|
|
|
|
revision: str = "202603020001"
|
|
down_revision: Union[str, Sequence[str], None] = None
|
|
branch_labels: Union[str, Sequence[str], None] = None
|
|
depends_on: Union[str, Sequence[str], None] = None
|
|
|
|
|
|
def upgrade() -> None:
|
|
# 创建 user_agent_catalog 表
|
|
op.create_table(
|
|
"user_agent_catalog",
|
|
sa.Column("agent_type", sa.String(20), nullable=False),
|
|
sa.Column("llm_id", sa.UUID(), nullable=False),
|
|
sa.Column("status", sa.String(20), nullable=False),
|
|
sa.Column(
|
|
"config",
|
|
postgresql.JSONB(astext_type=sa.Text()),
|
|
server_default="{}",
|
|
nullable=False,
|
|
),
|
|
sa.Column(
|
|
"created_at",
|
|
sa.DateTime(timezone=True),
|
|
server_default=sa.text("now()"),
|
|
nullable=False,
|
|
),
|
|
sa.Column(
|
|
"updated_at",
|
|
sa.DateTime(timezone=True),
|
|
server_default=sa.text("now()"),
|
|
nullable=False,
|
|
),
|
|
sa.PrimaryKeyConstraint("agent_type"),
|
|
sa.ForeignKeyConstraint(
|
|
["llm_id"], ["llms.id"], name="fk_user_agent_catalog_llm_id", ondelete="RESTRICT"
|
|
),
|
|
)
|
|
|
|
op.execute(
|
|
"ALTER TABLE user_agent_catalog "
|
|
"ADD CONSTRAINT chk_user_agent_catalog_status "
|
|
"CHECK (status IN ('active', 'paused', 'migrating'))"
|
|
)
|
|
|
|
_enable_rls("user_agent_catalog")
|
|
|
|
# 替换 trigger 函数
|
|
op.execute("""
|
|
CREATE OR REPLACE FUNCTION public.create_profile_for_new_user()
|
|
RETURNS trigger
|
|
LANGUAGE plpgsql
|
|
SECURITY DEFINER
|
|
SET search_path = public
|
|
AS $$
|
|
BEGIN
|
|
INSERT INTO public.profiles (id, username, avatar_url, bio, settings, created_at, updated_at)
|
|
VALUES (
|
|
NEW.id,
|
|
COALESCE(
|
|
NEW.raw_user_meta_data ->> 'username',
|
|
split_part(NEW.email, '@', 1),
|
|
'user_' || substring(NEW.id::text, 1, 8)
|
|
),
|
|
NULL,
|
|
NULL,
|
|
'{}'::jsonb,
|
|
now(),
|
|
now()
|
|
)
|
|
ON CONFLICT (id) DO NOTHING;
|
|
|
|
INSERT INTO public.user_agents (id, user_id, llm_id, agent_type, config, status, created_by, updated_by)
|
|
SELECT
|
|
gen_random_uuid(),
|
|
NEW.id,
|
|
uac.llm_id,
|
|
uac.agent_type,
|
|
uac.config,
|
|
uac.status,
|
|
NEW.id,
|
|
NEW.id
|
|
FROM public.user_agent_catalog uac;
|
|
|
|
RETURN NEW;
|
|
END;
|
|
$$;
|
|
""")
|
|
|
|
|
|
def downgrade() -> None:
|
|
# 恢复旧 trigger 函数
|
|
op.execute("""
|
|
CREATE OR REPLACE FUNCTION public.create_profile_for_new_user()
|
|
RETURNS trigger
|
|
LANGUAGE plpgsql
|
|
SECURITY DEFINER
|
|
SET search_path = public
|
|
AS $$
|
|
BEGIN
|
|
INSERT INTO public.profiles (id, username, avatar_url, bio, settings, created_at, updated_at)
|
|
VALUES (
|
|
NEW.id,
|
|
COALESCE(
|
|
NEW.raw_user_meta_data ->> 'username',
|
|
split_part(NEW.email, '@', 1),
|
|
'user_' || substring(NEW.id::text, 1, 8)
|
|
),
|
|
NULL,
|
|
NULL,
|
|
'{}'::jsonb,
|
|
now(),
|
|
now()
|
|
)
|
|
ON CONFLICT (id) DO NOTHING;
|
|
|
|
RETURN NEW;
|
|
END;
|
|
$$;
|
|
""")
|
|
|
|
_drop_rls("user_agent_catalog")
|
|
op.drop_constraint("chk_user_agent_catalog_status", "user_agent_catalog", type_="check")
|
|
op.drop_constraint("fk_user_agent_catalog_llm_id", "user_agent_catalog", type_="foreignkey")
|
|
op.drop_table("user_agent_catalog")
|
|
|
|
|
|
def _enable_rls(table_name: str) -> None:
|
|
for role in ["anon", "authenticated"]:
|
|
for action in ["select", "insert", "update", "delete"]:
|
|
op.execute(
|
|
f"DROP POLICY IF EXISTS {role}_{action}_{table_name} ON {table_name}"
|
|
)
|
|
op.execute(f"ALTER TABLE {table_name} ENABLE ROW LEVEL SECURITY")
|
|
for role in ["anon", "authenticated"]:
|
|
op.execute(
|
|
f"CREATE POLICY {role}_select_{table_name} ON {table_name} FOR SELECT TO {role} USING (false)"
|
|
)
|
|
op.execute(
|
|
f"CREATE POLICY {role}_insert_{table_name} ON {table_name} FOR INSERT TO {role} WITH CHECK (false)"
|
|
)
|
|
op.execute(
|
|
f"CREATE POLICY {role}_update_{table_name} ON {table_name} FOR UPDATE TO {role} USING (false) WITH CHECK (false)"
|
|
)
|
|
op.execute(
|
|
f"CREATE POLICY {role}_delete_{table_name} ON {table_name} FOR DELETE TO {role} USING (false)"
|
|
)
|
|
|
|
|
|
def _drop_rls(table_name: str) -> None:
|
|
for role in ["anon", "authenticated"]:
|
|
op.execute(f"DROP POLICY IF EXISTS {role}_delete_{table_name} ON {table_name}")
|
|
op.execute(f"DROP POLICY IF EXISTS {role}_update_{table_name} ON {table_name}")
|
|
op.execute(f"DROP POLICY IF EXISTS {role}_insert_{table_name} ON {table_name}")
|
|
op.execute(f"DROP POLICY IF EXISTS {role}_select_{table_name} ON {table_name}")
|
|
op.execute(f"ALTER TABLE {table_name} DISABLE ROW LEVEL SECURITY")
|
|
```
|
|
|
|
**Step 3: 运行迁移检查**
|
|
|
|
Run: `cd backend && uv run alembic check`
|
|
|
|
Expected: No issues
|
|
|
|
**Step 4: Commit**
|
|
|
|
```bash
|
|
git add backend/alembic/versions/20260302_add_user_agent_catalog.py
|
|
git commit -m "feat(migration): add user_agent_catalog table and update trigger"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 4: 扩展初始化函数
|
|
|
|
**Files:**
|
|
- Modify: `backend/src/core/config/initial/init_data.py`
|
|
|
|
**Step 1: 添加 Pydantic 模型**
|
|
|
|
```python
|
|
# backend/src/core/config/initial/init_data.py (添加到文件顶部)
|
|
from models.user_agent_catalog import UserAgentCatalog
|
|
|
|
|
|
class UserAgentCatalogSeed(BaseModel):
|
|
agent_type: str
|
|
llm_model_code: str
|
|
status: str
|
|
config: dict[str, Any]
|
|
|
|
|
|
class UserAgentCatalogYaml(BaseModel):
|
|
agents: list[UserAgentCatalogSeed]
|
|
```
|
|
|
|
**Step 2: 添加 load 函数**
|
|
|
|
```python
|
|
# backend/src/core/config/initial/init_data.py (添加)
|
|
|
|
def _default_user_agent_catalog_path() -> Path:
|
|
return (
|
|
Path(__file__).resolve().parents[1] / "static" / "database" / "user_agent_catalog.yaml"
|
|
)
|
|
|
|
|
|
def load_user_agent_catalog(catalog_path: Path | None = None) -> dict[str, Any]:
|
|
path = catalog_path or _default_user_agent_catalog_path()
|
|
with path.open("r", encoding="utf-8") as file:
|
|
loaded = yaml.safe_load(file) or {}
|
|
if not isinstance(loaded, dict):
|
|
raise ValueError(f"Invalid user agent catalog format: {path}")
|
|
raw_agents = loaded.get("agents", [])
|
|
if not isinstance(raw_agents, list):
|
|
raise ValueError(f"Invalid user agent catalog agents section: {path}")
|
|
try:
|
|
parsed = UserAgentCatalogYaml.model_validate({"agents": list(raw_agents)})
|
|
except ValidationError as exc:
|
|
raise ValueError(f"Invalid user agent catalog data: {path}") from exc
|
|
|
|
return parsed.model_dump()
|
|
```
|
|
|
|
**Step 3: 添加初始化函数**
|
|
|
|
```python
|
|
# backend/src/core/config/initial/init_data.py (添加)
|
|
|
|
async def _upsert_user_agent_catalog(
|
|
session: AsyncSession,
|
|
*,
|
|
agent_type: str,
|
|
llm_id: uuid.UUID,
|
|
status: str,
|
|
config: dict[str, Any],
|
|
) -> None:
|
|
result = await session.execute(
|
|
select(UserAgentCatalog).where(UserAgentCatalog.agent_type == agent_type)
|
|
)
|
|
catalog_entry = result.scalar_one_or_none()
|
|
|
|
if catalog_entry is None:
|
|
session.add(UserAgentCatalog(
|
|
agent_type=agent_type,
|
|
llm_id=llm_id,
|
|
status=status,
|
|
config=config,
|
|
))
|
|
else:
|
|
catalog_entry.llm_id = llm_id
|
|
catalog_entry.status = status
|
|
catalog_entry.config = config
|
|
|
|
|
|
async def initialize_user_agent_catalog() -> None:
|
|
"""Initialize user agent catalog from YAML."""
|
|
catalog = load_user_agent_catalog()
|
|
|
|
async with AsyncSessionLocal() as session:
|
|
async with session.begin():
|
|
for agent in catalog["agents"]:
|
|
# 查找 llm_id
|
|
result = await session.execute(
|
|
select(Llm).where(Llm.model_code == agent["llm_model_code"])
|
|
)
|
|
llm = result.scalar_one_or_none()
|
|
if llm is None:
|
|
raise RuntimeError(
|
|
f"LLM model '{agent['llm_model_code']}' not found for agent type '{agent['agent_type']}'"
|
|
)
|
|
|
|
await _upsert_user_agent_catalog(
|
|
session,
|
|
agent_type=agent["agent_type"],
|
|
llm_id=llm.id,
|
|
status=agent["status"],
|
|
config=agent["config"],
|
|
)
|
|
|
|
logger.info("Initialized user agent catalog")
|
|
```
|
|
|
|
**Step 4: 更新 initialize_data 函数**
|
|
|
|
```python
|
|
# backend/src/core/config/initial/init_data.py (修改)
|
|
|
|
async def initialize_data() -> bool:
|
|
"""Initialize bootstrap data."""
|
|
await initialize_llm_catalog()
|
|
await initialize_user_agent_catalog() # 新增
|
|
|
|
return True
|
|
```
|
|
|
|
**Step 5: 运行类型检查**
|
|
|
|
Run: `cd backend && uv run basedpyright src/core/config/initial/init_data.py`
|
|
|
|
Expected: No errors
|
|
|
|
**Step 6: Commit**
|
|
|
|
```bash
|
|
git add backend/src/core/config/initial/init_data.py
|
|
git commit -m "feat(init): add user_agent_catalog initialization"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 5: 编写单元测试
|
|
|
|
**Files:**
|
|
- Modify: `backend/tests/unit/core/test_agent_init_data.py`
|
|
|
|
**Step 1: 添加测试**
|
|
|
|
```python
|
|
# backend/tests/unit/core/test_agent_init_data.py (添加到文件末尾)
|
|
|
|
def test_user_agent_catalog_file_exists_and_has_required_fields() -> None:
|
|
catalog_path = (
|
|
Path(__file__).resolve().parents[4]
|
|
/ "src"
|
|
/ "core"
|
|
/ "config"
|
|
/ "static"
|
|
/ "database"
|
|
/ "user_agent_catalog.yaml"
|
|
)
|
|
|
|
assert catalog_path.exists(), f"Catalog file not found: {catalog_path}"
|
|
|
|
catalog = init_data.load_user_agent_catalog(catalog_path)
|
|
|
|
assert "agents" in catalog
|
|
assert isinstance(catalog["agents"], list)
|
|
assert len(catalog["agents"]) == 3
|
|
|
|
for agent in catalog["agents"]:
|
|
assert "agent_type" in agent
|
|
assert "llm_model_code" in agent
|
|
assert "status" in agent
|
|
assert "config" in agent
|
|
assert isinstance(agent["config"], dict)
|
|
|
|
|
|
def test_load_user_agent_catalog_raises_on_invalid_structure(tmp_path: Path) -> None:
|
|
catalog_path = tmp_path / "user_agent_catalog.yaml"
|
|
catalog_path.write_text("invalid: structure\n")
|
|
|
|
with pytest.raises(ValueError, match="Invalid user agent catalog"):
|
|
init_data.load_user_agent_catalog(catalog_path)
|
|
```
|
|
|
|
**Step 2: 运行测试**
|
|
|
|
Run: `cd backend && uv run pytest tests/unit/core/test_agent_init_data.py -v`
|
|
|
|
Expected: All tests pass
|
|
|
|
**Step 3: Commit**
|
|
|
|
```bash
|
|
git add backend/tests/unit/core/test_agent_init_data.py
|
|
git commit -m "test(init): add user_agent_catalog validation tests"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 6: 运行迁移并验证
|
|
|
|
**Files:**
|
|
- None (database operations)
|
|
|
|
**Step 1: 运行迁移**
|
|
|
|
Run: `cd backend && uv run alembic upgrade head`
|
|
|
|
Expected: Migration succeeds
|
|
|
|
**Step 2: 运行初始化**
|
|
|
|
Run: `cd backend && uv run python -m core.runtime.cli init-data`
|
|
|
|
Expected: "Initialized user agent catalog" in output
|
|
|
|
**Step 3: 验证数据库**
|
|
|
|
Run: `docker exec -it social-supabase-db psql -U postgres -d postgres -c "SELECT * FROM user_agent_catalog;"`
|
|
|
|
Expected: 3 rows (INTENT_RECOGNITION, TASK_EXECUTION, RESULT_REPORTING)
|
|
|
|
**Step 4: 测试 Trigger(手动)**
|
|
|
|
```sql
|
|
-- 创建测试用户
|
|
INSERT INTO auth.users (id, email, raw_user_meta_data)
|
|
VALUES (gen_random_uuid(), 'test@example.com', '{"username": "testuser"}');
|
|
|
|
-- 验证 profiles 和 user_agents 自动创建
|
|
SELECT * FROM profiles WHERE username = 'testuser';
|
|
SELECT * FROM user_agents WHERE user_id = (SELECT id FROM profiles WHERE username = 'testuser');
|
|
|
|
-- 清理测试数据
|
|
DELETE FROM user_agents WHERE user_id = (SELECT id FROM profiles WHERE username = 'testuser');
|
|
DELETE FROM profiles WHERE username = 'testuser';
|
|
DELETE FROM auth.users WHERE email = 'test@example.com';
|
|
```
|
|
|
|
Expected: profiles 有 1 条,user_agents 有 3 条
|
|
|
|
---
|
|
|
|
### Task 7: 为已存在用户补充 user_agents
|
|
|
|
**Files:**
|
|
- None (database operations)
|
|
|
|
**Step 1: 执行补充脚本**
|
|
|
|
```sql
|
|
-- 为已存在但没有 user_agents 的用户补充记录
|
|
INSERT INTO user_agents (id, user_id, llm_id, agent_type, config, status, created_by, updated_by)
|
|
SELECT
|
|
gen_random_uuid(),
|
|
p.id,
|
|
uac.llm_id,
|
|
uac.agent_type,
|
|
uac.config,
|
|
uac.status,
|
|
p.id,
|
|
p.id
|
|
FROM profiles p
|
|
CROSS JOIN user_agent_catalog uac
|
|
WHERE NOT EXISTS (
|
|
SELECT 1 FROM user_agents ua WHERE ua.user_id = p.id
|
|
);
|
|
```
|
|
|
|
**Step 2: 验证结果**
|
|
|
|
Run: `docker exec -it social-supabase-db psql -U postgres -d postgres -c "SELECT user_id, COUNT(*) FROM user_agents GROUP BY user_id;"`
|
|
|
|
Expected: 每个用户有 3 条 user_agents 记录
|
|
|
|
---
|
|
|
|
## TOKEN-001: Flutter 硬编码颜色迁移
|
|
|
|
### Task 8: 扫描硬编码颜色
|
|
|
|
**Files:**
|
|
- None (analysis only)
|
|
|
|
**Step 1: 扫描所有硬编码颜色**
|
|
|
|
Run:
|
|
```bash
|
|
cd apps && grep -r "Color(0x" lib --include="*.dart" | \
|
|
sed 's/.*Color(0x\([0-9A-Fa-f]*\)).*/\1/' | \
|
|
sort | uniq -c | sort -rn > /tmp/color_stats.txt && \
|
|
cat /tmp/color_stats.txt
|
|
```
|
|
|
|
Expected: 统计结果输出,显示每个颜色的使用频率
|
|
|
|
**Step 2: 记录统计结果**
|
|
|
|
将统计结果保存到临时文件供后续使用。
|
|
|
|
---
|
|
|
|
### Task 9: 扩展 AppColors
|
|
|
|
**Files:**
|
|
- Modify: `apps/lib/core/theme/design_tokens.dart`
|
|
|
|
**Step 1: 添加新颜色常量**
|
|
|
|
```dart
|
|
// apps/lib/core/theme/design_tokens.dart (在 AppColors 类中添加)
|
|
|
|
class AppColors {
|
|
// 现有颜色...
|
|
|
|
// 从硬编码迁移的颜色(根据 Task 8 的统计结果)
|
|
static const Color linkBlue = Color(0xFF2196F3);
|
|
static const Color errorRed = Color(0xFFD32F2F);
|
|
static const Color successGreen = Color(0xFF4CAF50);
|
|
static const Color warningOrange = Color(0xFFFF9800);
|
|
// ... 根据统计结果添加其他高频颜色
|
|
}
|
|
```
|
|
|
|
**Step 2: 运行 Flutter 分析**
|
|
|
|
Run: `cd apps && flutter analyze lib/core/theme/design_tokens.dart`
|
|
|
|
Expected: No issues
|
|
|
|
**Step 3: Commit**
|
|
|
|
```bash
|
|
git add apps/lib/core/theme/design_tokens.dart
|
|
git commit -m "feat(theme): add migrated colors to AppColors"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 10: 迁移 register_screen.dart
|
|
|
|
**Files:**
|
|
- Modify: `apps/lib/features/auth/ui/screens/register_screen.dart`
|
|
|
|
**Step 1: 替换硬编码颜色**
|
|
|
|
```dart
|
|
// Before
|
|
Container(color: Color(0xFF2196F3))
|
|
|
|
// After
|
|
Container(color: AppColors.linkBlue)
|
|
```
|
|
|
|
**Step 2: 检查所有实例**
|
|
|
|
Run: `cd apps && grep "Color(0x" lib/features/auth/ui/screens/register_screen.dart`
|
|
|
|
Expected: No matches (或只剩下合理的动态颜色)
|
|
|
|
**Step 3: 运行 Flutter 分析**
|
|
|
|
Run: `cd apps && flutter analyze lib/features/auth/ui/screens/register_screen.dart`
|
|
|
|
Expected: No issues
|
|
|
|
**Step 4: Commit**
|
|
|
|
```bash
|
|
git add apps/lib/features/auth/ui/screens/register_screen.dart
|
|
git commit -m "refactor(auth): migrate hardcoded colors in register_screen"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 11: 迁移其他页面(批量)
|
|
|
|
**Files:**
|
|
- Modify: 所有包含硬编码颜色的页面文件
|
|
|
|
**Step 1: 逐页面迁移**
|
|
|
|
按照频率顺序迁移:
|
|
1. `register_verification_screen.dart`
|
|
2. `settings_screen.dart`
|
|
3. `account_screen.dart`
|
|
4. `contacts_screen.dart`
|
|
5. `calendar_event_detail_screen.dart`
|
|
6. `add_contact_screen.dart`
|
|
7. `features_screen.dart`
|
|
8. `memory_screen.dart`
|
|
9. `home_screen.dart`
|
|
10. `todo_detail_screen.dart`
|
|
|
|
每个文件重复 Task 10 的步骤。
|
|
|
|
**Step 2: 批量提交**
|
|
|
|
```bash
|
|
git add apps/lib/features/
|
|
git commit -m "refactor(ui): migrate hardcoded colors to AppColors"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 12: 验证和测试
|
|
|
|
**Files:**
|
|
- None (validation only)
|
|
|
|
**Step 1: 检查剩余硬编码**
|
|
|
|
Run: `cd apps && grep -r "Color(0x" lib --include="*.dart" | wc -l`
|
|
|
|
Expected: < 10 (只保留合理的动态颜色)
|
|
|
|
**Step 2: 运行 Flutter 测试**
|
|
|
|
Run: `cd apps && flutter test`
|
|
|
|
Expected: All tests pass
|
|
|
|
**Step 3: 视觉检查**
|
|
|
|
手动运行应用,检查页面显示是否正常。
|
|
|
|
---
|
|
|
|
## Final Verification
|
|
|
|
### Task 13: 全量测试
|
|
|
|
**Files:**
|
|
- None (testing only)
|
|
|
|
**Step 1: 运行后端测试**
|
|
|
|
Run: `cd backend && uv run pytest`
|
|
|
|
Expected: All tests pass
|
|
|
|
**Step 2: 运行 Flutter 测试**
|
|
|
|
Run: `cd apps && flutter test`
|
|
|
|
Expected: All tests pass
|
|
|
|
**Step 3: 运行迁移检查**
|
|
|
|
Run: `cd backend && uv run alembic check`
|
|
|
|
Expected: No issues
|
|
|
|
**Step 4: 类型检查**
|
|
|
|
Run: `cd backend && uv run basedpyright src/`
|
|
|
|
Expected: No errors
|
|
|
|
---
|
|
|
|
## Summary
|
|
|
|
**Total Tasks:** 13
|
|
|
|
**Estimated Time:** 3-4 hours
|
|
|
|
**Key Deliverables:**
|
|
- ✅ user_agent_catalog.yaml 配置文件
|
|
- ✅ UserAgentCatalog 模型
|
|
- ✅ 数据库迁移(表 + trigger)
|
|
- ✅ 初始化函数扩展
|
|
- ✅ 单元测试
|
|
- ✅ 硬编码颜色迁移(109 → <10)
|
|
|
|
**Success Criteria:**
|
|
- [ ] 新用户注册后自动创建 3 个 user_agents 记录
|
|
- [ ] 已存在用户不受影响
|
|
- [ ] 配置可通过 YAML + 重启更新
|
|
- [ ] 硬编码颜色数量 < 10
|
|
- [ ] 所有测试通过
|
|
- [ ] UI 视觉无回归
|