docs/backend-migration/handoffs/frontend-tester-assistant-user-data-2026-04-23.md
Date: 2026-04-23
Branch: feat/backend-migration-assistant-user-data
Owner (outgoing): frontend-tester (Task #7 / T4)
Next owners: team-lead (coordinator closure), e2e-tester (Task #8 / T5)
All T4 gates satisfied.
| Gate | Result |
|---|---|
| Scoped Vitest (plan §4.3) | 70 / 70 passed |
| Full Vitest (plan §4.4) | 4309 / 4479 — 103 pre-existing failures across 37 files; zero new, two fixed |
bunx tsc --noEmit | clean |
bun run lint --quiet | 1800 warnings + 1 error — identical to T3a baseline; zero new |
bun run test --run \
tests/unit/assistantsBridge.test.ts \
tests/unit/assistantUtils.test.ts \
tests/unit/assistantHooks.dom.test.ts \
tests/unit/migrateAssistants.test.ts
| File | Tests | Owner |
|---|---|---|
tests/unit/assistantsBridge.test.ts | 15 new | frontend-tester (this task) |
tests/unit/assistantUtils.test.ts | 23 (rewritten) | frontend-tester (this task) |
tests/unit/assistantHooks.dom.test.ts | 23 (rewritten) | frontend-tester (this task) |
tests/unit/migrateAssistants.test.ts | 9 (landed with T3b @ 26cccd2b9) | frontend-dev |
| Total | 70 |
tests/unit/assistantsBridge.test.tsStubs globalThis.fetch and exercises each of the 6
ipcBridge.assistants.* entries directly through the real factory in
src/common/adapter/ipcBridge.ts. Each method has one happy-path
assertion (HTTP verb + path + body shape + response unwrapping) and one
error-propagation assertion (4xx/5xx → thrown Error with the
"<METHOD> <path> failed (<status>)" message shape produced by
httpBridge.ts:57-65).
list — GET /api/assistants, envelope unwrap, 500 → throwcreate — POST /api/assistants, body is full CreateAssistantRequest, 400 → throwupdate — PUT /api/assistants/:id, id carried in path, body passed through as-is, 404 → throwdelete — DELETE /api/assistants/:id, no body, 409 → throwsetState — PATCH /api/assistants/:id/state, id stripped from body by adapter mapper, 400 → throwimport — POST /api/assistants/import, typed ImportAssistantsResult returned, per-row errors surfaced, 500 → throw/api/...
path (not host:port), and one dedicated case verifies that
window.__backendPort injected by preload overrides the
httpBridge.ts fallback. Per
docs/development-workflow.md §"仓库关系", the backend listens on a
random port selected by findAvailablePort, so binding tests to the
fallback 13400 would break in any environment where preload has
done its job.tests/unit/assistantHooks.dom.test.tsSwapped the legacy ConfigStorage mocks (used by the pre-T3a
useAssistantList) for direct ipcBridge.assistants.* /
ipcBridge.fs.* / ipcBridge.acpConversation.* mocks.
Sections and their counts:
useAssistantList (6) — load via assistants.list, sort-order wins,
first-assistant-active default, reload preserves active id when still
present, falls back when active id disappears, isExtensionAssistant
predicate, error-path keeps list empty.useAssistantEditor (11) — create opens drawer + loads skills, save
in create mode calls assistants.create + writes rule md + reloads,
save in update mode calls assistants.update, validation skip on
empty name, delete-click on user opens dialog, delete-click on
builtin + extension warns (source-based gating), delete-confirm calls
assistants.delete, toggle-enabled on user calls assistants.setState,
toggle-enabled on builtin also calls setState (override path — per
useAssistantEditor.ts:373-391), toggle-enabled on extension skips
backend + warns.useDetectedAgents (3) — unchanged semantically, rewired to
ipcBridge.acpConversation.refreshCustomAgents only.useAssistantSkills (3) — kept from prior coverage (this hook still
uses ipcBridge.fs.* and was not touched by T3a).tests/unit/assistantUtils.test.tsPruned per plan §4.2. Removed suites referenced already-deleted helpers:
normalizeExtensionAssistants, isExtensionAssistant,
getAssistantSource, hasBuiltinSkills. Kept and extended coverage for
surviving helpers:
isEmoji (4) — empty / single emoji / plain text / mixed.resolveAvatarImageSrc (7) — missing / mapped / http / data uri /
ext:// via resolveExtensionAssetUrl / emoji-not-image / bare word
non-image.sortAssistants (4) — empty / ascending sortOrder / non-mutating /
stable on ties. (Simplified per plan — the backend already sorts, so
the helper is now just a deterministic fallback.)filterAssistants (6) — all / enabled / disabled /
builtin+user+extension by source / combined source+query / localized
name & description match. Note AssistantListFilter now includes
'user' (replaces legacy 'custom').groupAssistantsByEnabled (2) — standard split + undefined-enabled
treated as enabled.4a2d73da2): 1800 warnings + 1 error.useAssistantEditor test helper was defined inside a describe
without capturing parent scope, tripping
eslint-plugin-unicorn(consistent-function-scoping).extensionCheck lifted to module scope):
1800 warnings + 1 error. Zero new.The pre-existing error is unchanged:
tests/e2e/helpers/httpBridge.ts:41:9 — owned by the e2e-coverage
track, flagged in the T3a handoff as the 1800-warnings + 1-error
baseline.
bun run test --run with my changes:
37 file failed · 402 passed · 6 skipped and 103 tests failed / 4309 passed.
With my changes git stash-ed (baseline at 26cccd2b9):
39 file failed · 399 passed · 6 skipped and 132 tests failed / 4269 passed.
diff of failing file sets:
tests/unit/assistantHooks.dom.test.ts,
tests/unit/assistantUtils.test.tsThe remaining 37 failing files are unrelated to the Assistant user-data
migration — they are the pre-existing backend-migration collateral that
T3a's handoff already called out (presetAssistantResources.test.ts,
configMigration.test.ts, assorted .dom.test.tsx platform tests,
etc.). Not in T4 scope.
useAssistantEditor.handleToggleEnabled — builtin assistants go
through assistants.setState (the assistant_overrides row path).
Only source === 'extension' is hard-blocked in the frontend. If a
future e2e test asserts "builtin toggle is disabled in UI" it must
exercise the AssistantSettings button rendering, not the hook —
the hook itself allows builtins and relies on the backend override
mechanism to persist the flip.useAssistantEditor.handleSave always calls writeAssistantRule
when editContext.trim() is non-empty, regardless of create/update
mode (see useAssistantEditor.ts:283-289 and 310-317). Empty rule
body skips the rule write.ipcBridge.assistants.* is currently the sole bridge-level contract
for user-data operations. The extensions.getAssistants route is
unchanged and still used for extension-contributed assistants — e2e
fixtures for "Reject extension edit" scenarios should seed via the
extension loader, not via POST /api/assistants/import.migrateAssistantsToBackend is wired in src/index.ts after
backendManager.start() succeeds (T3b commit 26cccd2b9). E2E
fixtures that seed assistant rows directly through the backend API
must set AIONUI_SKIP_ELECTRON_MIGRATION=1 to prevent the main
process from re-importing any seed legacy file, or the import will
run once but be a no-op on the second boot because the flag
migration.electronConfigImported gates it.test(assistant): unit coverage for bridge + hooks + migration
(b0658d79e)
tests/unit/assistantsBridge.test.tstests/unit/assistantHooks.dom.test.tstests/unit/assistantUtils.test.tstest(assistant): decouple bridge tests from fallback port
docs/development-workflow.md after T4 was committed.13400-bound URL assertions with /api/... path
assertions, and added one window.__backendPort override case
against the real httpBridge.getBaseUrl() contract (+1 test).Mark Task #7 / T4 completed. T5 (e2e, Task #8) is unblocked as soon as Task #4 (backend HTTP integration) also completes.