docs/backend-migration/plans/2026-04-24-model-config-migration-plan.md
Team mode. Pre-launch cleanup: frontend stops using local
model.config, talks to/api/providersdirectly. Backend softens 3 constraints to make the frontend rewrite clean.Companion specs:
- Backend:
aionui-backend/docs/backend-migration/specs/2026-04-24-model-config-backend-migration-design.md- Frontend:
AionUi/docs/backend-migration/specs/2026-04-24-model-config-frontend-migration-design.md
Goal: (1) frontend IProvider aligns with backend ProviderResponse
wire contract (snake_case + models plural); (2) frontend consumers use
single-provider CRUD instead of the batch saveModelConfig shim;
(3) drop model.config from the client-preferences KV so
/api/settings/client stops returning the model array; (4) backend
accepts optional id + per-model fields on create and returns plaintext
api_key.
Team size: 1 coordinator + 3 teammates (backend-dev, frontend-dev,
frontend-tester). Backend changes are small (3 schema tweaks + service
glue) — backend-dev self-gates via cargo test. Frontend touches ~14
consumer sites + IProvider-wide type flip + process-side HTTP rewiring,
which is exactly the shape of the 2026-04-23 "Migration work needs
real-user-data dry-run" lesson — dev self-test misses cross-site
regressions, so a dedicated frontend-tester is mandatory. No backend-
tester.
| Role | Worktree | Branch | Base |
|---|---|---|---|
| coordinator | /Users/zhoukai/Documents/github/AionUi (primary) | feat/backend-migration-coordinator | (existing) |
| frontend-dev | /Users/zhoukai/Documents/worktrees/aionui-model-sync-fe | feat/model-sync-fe | origin/feat/backend-migration-coordinator |
| frontend-tester | /Users/zhoukai/Documents/worktrees/aionui-model-sync-fe (same worktree, after frontend-dev push) | feat/model-sync-fe | (pulls after T2) |
| backend-dev | /Users/zhoukai/Documents/worktrees/aionui-backend-model-sync-be | feat/model-sync-be | origin/feat/builtin-skills |
Worktrees already created.
T0 coordinator (spec + plan commit, team spawn)
│
▼
T1 backend-dev (soften provider API)
│
▼
T2 frontend-dev (rewrite to CRUD, drop model.config)
│
▼
T2.5 frontend-tester (vitest baseline + new coverage + e2e smoke + regression)
│
▼
T3 coordinator closure (smoke + handoff)
Critical path: T0 → T1 → T2 → T2.5 → T3. T2 strictly depends on T1 — posting IProvider with new optional fields before backend accepts them returns 400 on every save. T2.5 strictly depends on T2 — nothing to test until the rewrite lands. T3 waits for T2.5 green.
Owner: coordinator.
feat/backend-migration-coordinator + push. Also commit backend spec to feat/model-sync-be.TeamCreate aionui-model-sync.backend-dev and frontend-dev (Agent tool, general-purpose).Owner: backend-dev. Worktree: aionui-backend-model-sync-be. Branch: feat/model-sync-be.
See backend spec §Changes 1–3. In short:
CreateProviderRequest: add id: Option<String>, model_protocols, model_enabled, model_health as optional.ProviderService::create: use req.id if set (validate UUID), else generate; persist per-model fields instead of hardcoding None at current lines 52–54.ProviderResponse.api_key: decrypt and return plaintext; delete masking helper + its tests.Gates (per spec §Definition of Done):
cargo fmt --all -- --check cleancargo test -p aionui-api-types -p aionui-system greencargo clippy --workspace -- -D warnings baselineCommit message: feat(provider): accept optional id + per-model fields on create; return plaintext api_key (pre-launch)
Push + SendMessage team-lead with SHA + probe output.
Progress reporting: ping team-lead every ~10 min if still working.
Owner: frontend-dev. Worktree: aionui-model-sync-fe. Branch: feat/model-sync-fe. Depends on T1.
See frontend spec §File Changes. In short:
IProvider to snake_case + models plural. Remove 'model.config' from ConfigKeyMap and ALL_LEGACY_KEYS.ipcBridge.ts lines 515–540 — single-provider CRUD surface; drop batch save.ModelModalContent.tsx: SWR key + optimistic update + per-mutation CRUD calls.createConversationParams.ts: drop configService.get('model.config') — route through new providers cache or HTTP bridge.TeamSessionService, modelListHandler, WorkerTaskManagerJobExecutor, SystemActions, createConversationParams reads): route through httpBridge like the assistant migration does.Gates (per spec §Definition of Done):
bunx tsc --noEmit cleanbun run lint --quiet baselinebun run test --run baseline or bettermodel.config in /api/settings/client responseCommit message: refactor(model-config): migrate IProvider to /api/providers CRUD; drop local model.config store
Push + SendMessage team-lead with SHA + smoke transcript.
Progress reporting every ~10 min.
Owner: frontend-tester. Worktree: aionui-model-sync-fe (pulls T2's commit). Depends on T2.
Scope:
Vitest baseline diff. Pre-T2 baseline (on origin/feat/backend-migration-coordinator): run bun run test --run, capture pass/fail/skip counts. Post-T2: rerun, diff. Any NEW failure vs baseline = regression, route back to frontend-dev with file:line.
New Vitest coverage for the rewrite (write these, they did not exist before):
tests/unit/ipcBridge.providers.test.ts — unit test the 6 new mode.* bridge entries (create, update, delete, list, fetchModelList, detectProtocol) hit the right URL + method + body shape. Mock fetch.tests/unit/ModelModalContent.crud.test.tsx — add, remove, update, toggle-model-enable, toggle-protocol each call the expected single CRUD endpoint with the expected payload. Mock ipcBridge.mode.*. Important scenarios:
createProvider with a UUID v4 id.model_enabled sends updateProvider with ONLY model_enabled in the body (partial update), not the whole IProvider.deleteProvider by id only.updateProvider with only model_health.tests/unit/createConversationParams.providers.test.ts — verifies the new provider lookup path (not configService.get('model.config')).tests/unit/configMigration.noModelConfig.test.ts — regression: ALL_LEGACY_KEYS does NOT contain 'model.config', and migrating a legacy store containing a model.config entry does NOT push it to the backend (important — this is the observed bug).Playwright e2e smoke. Run tests/e2e/features/** suites that touch model selection or settings. Expected: baseline green. If a provider-related scenario exists, rerun it against the real backend built from feat/model-sync-be (launch backend manually, or per existing e2e harness).
Integration regression probe (MANDATORY — this is the whole point of the migration, lesson from 2026-04-23):
cd /Users/zhoukai/Documents/worktrees/aionui-backend-model-sync-be && cargo run --release -- --local --port 25910 --data-dir "$(mktemp -d)")fetch) pointing at 127.0.0.1:25910curl http://127.0.0.1:25910/api/settings/client | jq 'keys' → must NOT contain "model.config"curl http://127.0.0.1:25910/api/providers | jq '.data[0].id' → must equal what the frontend sentGates:
Deliverables:
tests/unit/, message: test(model-config): coverage for /api/providers CRUD migrationdocs/backend-migration/e2e-reports/2026-04-24-model-config-migration.md (new file): baseline diff, new test results, probe transcript.If you find regressions: route back to frontend-dev with specific file:line and expected vs actual. Do not claim T2.5 complete until everything is green.
Progress reporting every ~10 min.
Owner: coordinator.
/api/settings/client).feat/model-sync-fe into feat/backend-migration-coordinator (+ merge backend branch on the backend repo per that repo's convention).docs/backend-migration/handoffs/coordinator-model-config-2026-04-24.md.docs/backend-migration/modules/ (new file: provider-config.md).No PRs — per user convention.
git log -3 on both repos every ~10 min when actively waiting.grep -rn "'model.config'" AionUi/src/ zero hitsPOST /api/providers with {"id":"..."} returns that exact id; response api_key is plaintext/api/providers/api/settings/client response has no model.config key after a clean round-trip