chore: format with prettier [skip ci]

This commit is contained in:
github-actions[bot] 2026-06-26 16:21:42 +00:00
parent f848ae0baa
commit aa08f11a86
6 changed files with 103 additions and 86 deletions

View File

@ -20,30 +20,30 @@
## File Structure
| File | Status | Responsibility |
| --- | --- | --- |
| `backend/internal/legacyimport/orchestrator.go` (+`_test.go`) | delete | orchestrator-session mapping/import (out of scope) |
| `backend/internal/legacyimport/claude.go` (+`_test.go`) | delete | Claude transcript relocation (out of scope) |
| `backend/internal/storage/sqlite/store/session_import_store.go` (+`_test.go`) | delete | `ImportSession` (only used by orchestrator import) |
| `backend/internal/legacyimport/importer.go` | modify | trim `Store`/`Options`/`Report`; drop orchestrator loop; add `quote()` |
| `backend/internal/legacyimport/config.go` | modify | `Repo` -> `*yaml.Node`; tolerate `*yaml.TypeError` |
| `backend/internal/legacyimport/paths.go` | modify | drop `defaultClaudeProjectsDir` + `projectSessionsDir` |
| `backend/internal/legacyimport/{importer,config,project}_test.go` | modify | projects-only assertions |
| `backend/internal/cli/import.go` | modify | drop orchestrator/transcript copy + summary lines |
| `backend/internal/service/importer/importer.go` (+`_test.go`) | create | `Status`/`Run` over the daemon store |
| `backend/internal/httpd/controllers/imports.go` (+`_test.go`) | create | `GET`/`POST /import`, 501 on nil svc |
| `backend/internal/httpd/controllers/dto.go` | modify | `ImportStatusResponse`, `ImportRunResponse` |
| `backend/internal/httpd/apispec/specgen/build.go` | modify | `import` tag + ops + schemaNames |
| `backend/internal/httpd/api.go` | modify | `APIDeps.Import`, `API.imports`, wire + Register |
| `backend/internal/daemon/daemon.go` | modify | `Import: importsvc.New(...)` |
| `backend/internal/cli/start.go` | modify | delete `maybeFirstBootImport` + 2 imports |
| `backend/internal/httpd/apispec/openapi.yaml` | regenerate | — |
| `frontend/src/api/schema.ts` | regenerate | — |
| `frontend/src/shared/daemon-discovery.ts` (+`.test.ts`) | modify | `shouldAdoptDiscoveredPort` |
| `frontend/src/main.ts` | modify | external daemon discovery loop |
| `frontend/src/renderer/hooks/useImportStatus.ts` | create | `useImportStatus`/`useRunImport` |
| `frontend/src/renderer/components/ImportOffer.tsx` (+`.test.tsx`) | create | the banner |
| `frontend/src/renderer/components/SessionsBoard.tsx` | modify | render `<ImportOffer/>` |
| File | Status | Responsibility |
| ----------------------------------------------------------------------------- | ---------- | ---------------------------------------------------------------------- |
| `backend/internal/legacyimport/orchestrator.go` (+`_test.go`) | delete | orchestrator-session mapping/import (out of scope) |
| `backend/internal/legacyimport/claude.go` (+`_test.go`) | delete | Claude transcript relocation (out of scope) |
| `backend/internal/storage/sqlite/store/session_import_store.go` (+`_test.go`) | delete | `ImportSession` (only used by orchestrator import) |
| `backend/internal/legacyimport/importer.go` | modify | trim `Store`/`Options`/`Report`; drop orchestrator loop; add `quote()` |
| `backend/internal/legacyimport/config.go` | modify | `Repo` -> `*yaml.Node`; tolerate `*yaml.TypeError` |
| `backend/internal/legacyimport/paths.go` | modify | drop `defaultClaudeProjectsDir` + `projectSessionsDir` |
| `backend/internal/legacyimport/{importer,config,project}_test.go` | modify | projects-only assertions |
| `backend/internal/cli/import.go` | modify | drop orchestrator/transcript copy + summary lines |
| `backend/internal/service/importer/importer.go` (+`_test.go`) | create | `Status`/`Run` over the daemon store |
| `backend/internal/httpd/controllers/imports.go` (+`_test.go`) | create | `GET`/`POST /import`, 501 on nil svc |
| `backend/internal/httpd/controllers/dto.go` | modify | `ImportStatusResponse`, `ImportRunResponse` |
| `backend/internal/httpd/apispec/specgen/build.go` | modify | `import` tag + ops + schemaNames |
| `backend/internal/httpd/api.go` | modify | `APIDeps.Import`, `API.imports`, wire + Register |
| `backend/internal/daemon/daemon.go` | modify | `Import: importsvc.New(...)` |
| `backend/internal/cli/start.go` | modify | delete `maybeFirstBootImport` + 2 imports |
| `backend/internal/httpd/apispec/openapi.yaml` | regenerate | — |
| `frontend/src/api/schema.ts` | regenerate | — |
| `frontend/src/shared/daemon-discovery.ts` (+`.test.ts`) | modify | `shouldAdoptDiscoveredPort` |
| `frontend/src/main.ts` | modify | external daemon discovery loop |
| `frontend/src/renderer/hooks/useImportStatus.ts` | create | `useImportStatus`/`useRunImport` |
| `frontend/src/renderer/components/ImportOffer.tsx` (+`.test.tsx`) | create | the banner |
| `frontend/src/renderer/components/SessionsBoard.tsx` | modify | render `<ImportOffer/>` |
---
@ -52,6 +52,7 @@
**Files:** Delete `legacyimport/orchestrator.go`, `orchestrator_test.go`, `claude.go`, `claude_test.go`, `storage/sqlite/store/session_import_store.go`, `session_import_store_test.go`. Modify `legacyimport/importer.go`, `config.go`, `paths.go`, and their tests; `cli/import.go`.
**Interfaces:**
- Produces: trimmed `legacyimport.Store` (`GetProject`, `UpsertProject` only), `legacyimport.Options{Root, DryRun, Now, RepoOriginURL}`, `legacyimport.Report{DryRun, ProjectsImported, ProjectsSkipped, Notes}`. **Tasks 2 and 3 depend on this `Report` shape.**
- [ ] **Step 1: Delete the orchestrator/transcript files.**
@ -84,7 +85,7 @@ func quote(s string) string {
- [ ] **Step 3: `legacyimport/config.go` parsing-robustness fix (PR-bundled, improves project import).**
- Add `"errors"` import.
- Change the project config's `Repo string \`yaml:"repo"\`` field to `Repo *yaml.Node \`yaml:"repo"\`` with a comment that it is captured but never consumed (origin is re-resolved from the repo path).
- Change the project config's `Repo string \`yaml:"repo"\``field to`Repo \*yaml.Node \`yaml:"repo"\`` with a comment that it is captured but never consumed (origin is re-resolved from the repo path).
- In `loadLegacyConfig`, when `yaml.Unmarshal` errors, keep the partial decode on a `*yaml.TypeError` instead of failing the whole registry:
```go
@ -107,6 +108,7 @@ func quote(s string) string {
**Files:** Create `backend/internal/service/importer/importer.go`, `…/importer_test.go`
**Interfaces:**
- Consumes: trimmed `legacyimport.Store`, `legacyimport.Run`, `legacyimport.HasLegacyData`, `legacyimport.DefaultLegacyRootDir`, `legacyimport.Report`, `legacyimport.Options{Root}`; `domain.ProjectRecord`.
- Produces: `importer.Status{Available bool; LegacyRoot string}`, `importer.Service` (`Status(ctx)`, `Run(ctx)`), `importer.Deps{Store, Root}`, `importer.New(Deps) *Manager`. **Controller (Task 3) and daemon (Task 5) depend on these exact names.**
@ -214,6 +216,7 @@ func (m *Manager) Run(ctx context.Context) (legacyimport.Report, error) {
**Files:** Create `backend/internal/httpd/controllers/imports.go`, `…/imports_test.go`; Modify `…/controllers/dto.go`
**Interfaces:**
- Consumes: `importsvc.Status`, `legacyimport.Report`, `apispec.NotImplemented`, `envelope.WriteJSON`/`WriteError`, `httpd.NewRouterWithControl(cfg, log, termMgr, APIDeps, ControlDeps)`.
- Produces: `controllers.ImportService`, `controllers.ImportController{Svc}`, `controllers.ImportStatusResponse`, `controllers.ImportRunResponse`. **api.go (Task 5) and specgen (Task 4) depend on these.**
@ -332,9 +335,13 @@ type ImportRunResponse struct {
- [ ] **Step 1:** Add `import { ImportOffer } from "./ImportOffer";` and, directly under the `<DashboardSubhead .../>` line, insert:
```tsx
{/* First-run legacy-AO import opt-in. Renders only when the daemon
reports an importable install, and only on the top-level board. */}
{!projectId && <ImportOffer />}
{
/* First-run legacy-AO import opt-in. Renders only when the daemon
reports an importable install, and only on the top-level board. */
}
{
!projectId && <ImportOffer />;
}
```
- [ ] **Step 2:** `npm --prefix frontend run typecheck && npm --prefix frontend run test` → expect PASS.

View File

@ -22,20 +22,20 @@
## File Structure
| File | Status | Responsibility |
| --- | --- | --- |
| (backend import API) | per import-offer plan | projects-only `legacyimport`, `service/importer`, controller, DTOs, OpenAPI, wiring |
| `backend/internal/service/importer/importer.go` | create (modified Status) | `Status` = `HasLegacyData` only; `Run` |
| `backend/internal/cli/start.go` | modify | drop the `§6.4` TODO comment (`:78`) |
| `frontend/src/main/app-state.ts` | modify | `MigrationState`, schema v2, preserve, `updateMigration`, `readMigrationState` |
| `frontend/src/main/app-state.test.ts` | modify | marker v2 + migration tests |
| `frontend/src/main.ts` | modify | `appState:getMigration` / `appState:setMigration` IPC handlers |
| `frontend/src/preload.ts` | modify | `ao.appState` bridge methods |
| `frontend/src/renderer/lib/bridge.ts` | modify | `appState` preview fallback |
| `frontend/src/renderer/hooks/useMigrationOffer.ts` | create | gate: IPC marker + daemon GET |
| `frontend/src/renderer/components/MigrationPopup.tsx` | create | the three-action dialog |
| `frontend/src/renderer/components/MigrationPopup.test.tsx` | create | popup tests |
| `frontend/src/renderer/routes/_shell.index.tsx` | modify | mount `<MigrationPopup/>` on the board |
| File | Status | Responsibility |
| ---------------------------------------------------------- | ------------------------ | ----------------------------------------------------------------------------------- |
| (backend import API) | per import-offer plan | projects-only `legacyimport`, `service/importer`, controller, DTOs, OpenAPI, wiring |
| `backend/internal/service/importer/importer.go` | create (modified Status) | `Status` = `HasLegacyData` only; `Run` |
| `backend/internal/cli/start.go` | modify | drop the `§6.4` TODO comment (`:78`) |
| `frontend/src/main/app-state.ts` | modify | `MigrationState`, schema v2, preserve, `updateMigration`, `readMigrationState` |
| `frontend/src/main/app-state.test.ts` | modify | marker v2 + migration tests |
| `frontend/src/main.ts` | modify | `appState:getMigration` / `appState:setMigration` IPC handlers |
| `frontend/src/preload.ts` | modify | `ao.appState` bridge methods |
| `frontend/src/renderer/lib/bridge.ts` | modify | `appState` preview fallback |
| `frontend/src/renderer/hooks/useMigrationOffer.ts` | create | gate: IPC marker + daemon GET |
| `frontend/src/renderer/components/MigrationPopup.tsx` | create | the three-action dialog |
| `frontend/src/renderer/components/MigrationPopup.test.tsx` | create | popup tests |
| `frontend/src/renderer/routes/_shell.index.tsx` | modify | mount `<MigrationPopup/>` on the board |
---
@ -54,6 +54,7 @@ Execute the committed plan `docs/plans/2026-06-26-import-offer.md`, **Tasks 1, 3
**Files:** Create `backend/internal/service/importer/importer.go`, `…/importer_test.go`
**Interfaces:**
- Produces: `importer.Status{Available bool; LegacyRoot string}`, `importer.Service` (`Status(ctx)`, `Run(ctx)`), `importer.Deps{Store, Root}`, `importer.New(Deps) *Manager`. `Store` is exactly `legacyimport.Store` (the projects-only `GetProject`/`UpsertProject`) — **no `ListProjects`** (the design drops the empty-DB heuristic; the app marker governs prompting).
- [ ] **Step 1: Write `importer.go`:**
@ -229,6 +230,7 @@ func TestNew_DefaultsRoot(t *testing.T) {
**Files:** Modify `frontend/src/main/app-state.ts`, `frontend/src/main/app-state.test.ts`
**Interfaces:**
- Produces: `MigrationStatus`, `MigrationState`, `updateMigration({stateDir, migration, now})`, `readMigrationState(stateDir) => Promise<MigrationState>`. `AppStateMarker` gains `migration?: MigrationState`. `SCHEMA_VERSION === 2`. **B2/B3/B4 depend on these names.**
- [ ] **Step 1 (TDD): add tests to `app-state.test.ts`** (follows the file's existing temp-dir style):
@ -239,12 +241,7 @@ import os from "node:os";
import path from "node:path";
import { mkdtemp } from "node:fs/promises";
import { describe, expect, it } from "vitest";
import {
APP_STATE_FILE_NAME,
readMigrationState,
updateMigration,
writeAppStateMarker,
} from "./app-state";
import { APP_STATE_FILE_NAME, readMigrationState, updateMigration, writeAppStateMarker } from "./app-state";
const fixedNow = () => new Date("2026-06-26T10:00:00.000Z");
async function tmp() {
@ -478,7 +475,8 @@ const { getMock, postMock, getMigration, setMigration } = vi.hoisted(() => ({
vi.mock("../lib/api-client", () => ({
apiClient: { GET: getMock, POST: postMock },
apiErrorMessage: (e: unknown, fb = "Request failed") => (e instanceof Error ? e.message : (e as { message?: string })?.message ?? fb),
apiErrorMessage: (e: unknown, fb = "Request failed") =>
e instanceof Error ? e.message : ((e as { message?: string })?.message ?? fb),
}));
vi.mock("../lib/bridge", () => ({ aoBridge: { appState: { getMigration, setMigration } } }));
@ -629,14 +627,12 @@ export function MigrationPopup() {
Import projects from your earlier AO?
</Dialog.Title>
<Dialog.Description className="mt-2 text-[13px] leading-[1.5] text-muted-foreground">
We found an existing install at{" "}
<span className="font-mono text-[11px] text-foreground">{legacyRoot}</span>. Importing brings in
your projects. Your old files are never modified, and you can do this later.
We found an existing install at <span className="font-mono text-[11px] text-foreground">{legacyRoot}</span>.
Importing brings in your projects. Your old files are never modified, and you can do this later.
</Dialog.Description>
{error && (
<div className="mt-3 text-[12px] text-destructive">
Migration failed: {error}. Your legacy projects are untouched (nothing is ever deleted). You
can retry.
Migration failed: {error}. Your legacy projects are untouched (nothing is ever deleted). You can retry.
</div>
)}
<p className="mt-3 text-[11px] text-muted-foreground">You can run this again later.</p>

View File

@ -25,20 +25,20 @@ or declined import loses nothing.
## 1. Ground truth (what the code is today)
| Fact | Value | Source |
| --- | --- | --- |
| Marker file | `~/.ao/app-state.json`, **app is sole writer**, written every launch | `frontend/src/main/app-state.ts`; spec §5, invariant 3 |
| Marker fields today | `schemaVersion, appPath, version, installedAt, lastReconciledAt, installSource` | `frontend/src/main/app-state.ts` `AppStateMarker` |
| Marker write call site | `app.whenReady()` before `createWindow()` | `frontend/src/main.ts:859` (inside `whenReady`, `:868`) |
| Go reader of marker | read-only, ignores unknown JSON fields, does NOT gate on `schemaVersion` | `backend/internal/cli/start.go` `appState` struct (~`:38`) |
| Import engine | `internal/legacyimport` (projects + per-project settings + orchestrator + transcripts) + `ao import` CLI | present from #314 |
| Import **daemon API** | **does not exist on main** (no `service/importer`, no `httpd/controllers/imports.go`) | verified |
| Daemon ownership | the app spawns + owns the daemon | `frontend/src/main.ts` `startDaemon` |
| HTTP client (renderer) | `apiClient` over the daemon loopback API | `frontend/src/renderer/lib/api-client.ts:81` |
| IPC bridge | `contextBridge.exposeInMainWorld("ao", api)`; renderer calls `window.ao.<ns>.*` | `frontend/src/preload.ts:73` |
| IPC handler pattern | `ipcMain.handle("<ns>:<action>", …)` in main | `frontend/src/main.ts:773-796` |
| Dashboard route | `_shell.index.tsx` (the board) | `frontend/src/renderer/routes/` |
| **Global Settings page** | **none** — only per-project `_shell.projects.$projectId_.settings.tsx` | verified |
| Fact | Value | Source |
| ------------------------ | -------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- |
| Marker file | `~/.ao/app-state.json`, **app is sole writer**, written every launch | `frontend/src/main/app-state.ts`; spec §5, invariant 3 |
| Marker fields today | `schemaVersion, appPath, version, installedAt, lastReconciledAt, installSource` | `frontend/src/main/app-state.ts` `AppStateMarker` |
| Marker write call site | `app.whenReady()` before `createWindow()` | `frontend/src/main.ts:859` (inside `whenReady`, `:868`) |
| Go reader of marker | read-only, ignores unknown JSON fields, does NOT gate on `schemaVersion` | `backend/internal/cli/start.go` `appState` struct (~`:38`) |
| Import engine | `internal/legacyimport` (projects + per-project settings + orchestrator + transcripts) + `ao import` CLI | present from #314 |
| Import **daemon API** | **does not exist on main** (no `service/importer`, no `httpd/controllers/imports.go`) | verified |
| Daemon ownership | the app spawns + owns the daemon | `frontend/src/main.ts` `startDaemon` |
| HTTP client (renderer) | `apiClient` over the daemon loopback API | `frontend/src/renderer/lib/api-client.ts:81` |
| IPC bridge | `contextBridge.exposeInMainWorld("ao", api)`; renderer calls `window.ao.<ns>.*` | `frontend/src/preload.ts:73` |
| IPC handler pattern | `ipcMain.handle("<ns>:<action>", …)` in main | `frontend/src/main.ts:773-796` |
| Dashboard route | `_shell.index.tsx` (the board) | `frontend/src/renderer/routes/` |
| **Global Settings page** | **none** — only per-project `_shell.projects.$projectId_.settings.tsx` | verified |
---
@ -65,6 +65,7 @@ or declined import loses nothing.
## 3. Scope
**In scope:**
- Backend: the projects-only import daemon API: `GET /api/v1/import` (availability)
and `POST /api/v1/import` (run), plus `service/importer`, DTOs, OpenAPI regen.
(= the import-offer plan, with the status-semantics tweak in §5.2.)
@ -75,6 +76,7 @@ or declined import loses nothing.
reassurance.
**Out of scope (deferred / separate):**
- Global Settings page + the Migration section / redo entry point → #2205.
- The engine simplification details themselves live in the import-offer plan; this
design consumes that API, it does not re-specify the engine internals.
@ -99,6 +101,7 @@ or declined import loses nothing.
## 5. Backend: the import daemon API
### 5.1 Surface (from the import-offer plan)
- `GET /api/v1/import``{ available: bool, legacyRoot: string }`.
- `POST /api/v1/import``{ report: { dryRun, projectsImported, projectsSkipped, notes? } }`.
- `internal/service/importer` wraps `legacyimport` (projects-only); wired into
@ -106,6 +109,7 @@ or declined import loses nothing.
- A nil service answers OpenAPI-backed `501`. OpenAPI + `schema.ts` regenerated.
### 5.2 Status semantics (the one change vs the import-offer plan)
The import-offer plan computed `available = HasLegacyData(root) && len(projects)==0`
(the empty-DB heuristic). **Here the marker governs whether to prompt**, so the DB
heuristic is redundant and can be wrong (a user who added projects but never
@ -129,11 +133,11 @@ Bump `SCHEMA_VERSION` to `2` and extend `AppStateMarker`
export type MigrationStatus = "pending" | "completed" | "declined" | "failed";
export interface MigrationState {
status: MigrationStatus; // absent file => treated as "pending"
lastAttemptAt?: string; // last Proceed attempt (success or failure)
completedAt?: string; // set when status -> completed
status: MigrationStatus; // absent file => treated as "pending"
lastAttemptAt?: string; // last Proceed attempt (success or failure)
completedAt?: string; // set when status -> completed
report?: { projectsImported: number; projectsSkipped: number };
error?: string; // last failure message (status === "failed")
error?: string; // last failure message (status === "failed")
}
export interface AppStateMarker {
@ -143,7 +147,7 @@ export interface AppStateMarker {
installedAt: string;
lastReconciledAt: string;
installSource: string;
migration?: MigrationState; // new; preserved across launches
migration?: MigrationState; // new; preserved across launches
}
```
@ -166,14 +170,17 @@ Terminal states (never auto-prompt): `completed`, `declined`. Prompting states:
## 7. Renderer: detection, gate, popup
### 7.1 IPC additions
- main: `ipcMain.handle("appState:getMigration", …)` → returns `MigrationState`
(or `{status:"pending"}` when absent); `ipcMain.handle("appState:setMigration",
(_e, partial) => updateMigration(...))`.
(_e, partial) => updateMigration(...))`.
- preload: extend the `ao` bridge with
`appState: { getMigration, setMigration }`.
### 7.2 Gate (where the popup decides to show)
A `useMigrationOffer()` hook, consumed on the dashboard (`_shell.index.tsx`):
1. read `window.ao.appState.getMigration()`,
2. if `status ∈ {completed, declined}` → render nothing,
3. else `GET /api/v1/import`; show the popup only when `available === true`
@ -185,13 +192,14 @@ keeping `Don't Migrate` permanent.
### 7.3 Popup actions / data flow
| Action | Effect |
| --- | --- |
| **Proceed** | `POST /api/v1/import`. On success: `setMigration({status:"completed", completedAt, report})`, then invalidate the workspace query so projects appear. On failure: `setMigration({status:"failed", lastAttemptAt, error})` and show the reassurance inline. |
| **Skip** | session-only dismiss; no marker write; re-prompts next launch. |
| **Don't Migrate** (red) | `setMigration({status:"declined", lastAttemptAt})`; never auto-prompts again. |
| Action | Effect |
| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Proceed** | `POST /api/v1/import`. On success: `setMigration({status:"completed", completedAt, report})`, then invalidate the workspace query so projects appear. On failure: `setMigration({status:"failed", lastAttemptAt, error})` and show the reassurance inline. |
| **Skip** | session-only dismiss; no marker write; re-prompts next launch. |
| **Don't Migrate** (red) | `setMigration({status:"declined", lastAttemptAt})`; never auto-prompts again. |
### 7.4 Copy
- Heading: "Import projects from your earlier AO?"
- Body: names the legacy root; "Importing brings in your projects. Your old files
are never modified, and you can do this later." (projects-only; **no orchestrator
@ -212,6 +220,7 @@ keeping `Don't Migrate` permanent.
`openapi.yaml` + `frontend/src/api/schema.ts`.
**App / renderer (new in this design):**
- `frontend/src/main/app-state.ts``MigrationState`, schema v2, preserve +
`updateMigration`.
- `frontend/src/main.ts``appState:getMigration` / `appState:setMigration`

View File

@ -3,7 +3,13 @@ import { describe, it, expect, beforeEach, afterEach } from "vitest";
import { mkdtemp, readFile, readdir, rm, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { APP_STATE_FILE_NAME, writeAppStateMarker, readMigrationState, updateMigration, type AppStateMarker } from "./app-state";
import {
APP_STATE_FILE_NAME,
writeAppStateMarker,
readMigrationState,
updateMigration,
type AppStateMarker,
} from "./app-state";
// The exact key set the Go reader (start.go `appState`) unmarshals.
const GO_READER_KEYS = ["schemaVersion", "appPath", "version", "installedAt", "lastReconciledAt", "installSource"];

View File

@ -13,7 +13,8 @@ const { getMock, postMock, getMigration, setMigration } = vi.hoisted(() => ({
vi.mock("../lib/api-client", () => ({
apiClient: { GET: getMock, POST: postMock },
apiErrorMessage: (e: unknown, fb = "Request failed") => (e instanceof Error ? e.message : (e as { message?: string })?.message ?? fb),
apiErrorMessage: (e: unknown, fb = "Request failed") =>
e instanceof Error ? e.message : ((e as { message?: string })?.message ?? fb),
}));
vi.mock("../lib/bridge", () => ({ aoBridge: { appState: { getMigration, setMigration } } }));

View File

@ -71,14 +71,12 @@ export function MigrationPopup() {
Import projects from your earlier AO?
</Dialog.Title>
<Dialog.Description className="mt-2 text-[13px] leading-[1.5] text-muted-foreground">
We found an existing install at{" "}
<span className="font-mono text-[11px] text-foreground">{legacyRoot}</span>. Importing brings in
your projects. Your old files are never modified, and you can do this later.
We found an existing install at <span className="font-mono text-[11px] text-foreground">{legacyRoot}</span>.
Importing brings in your projects. Your old files are never modified, and you can do this later.
</Dialog.Description>
{error && (
<div className="mt-3 text-[12px] text-destructive">
Migration failed: {error}. Your legacy projects are untouched (nothing is ever deleted). You
can retry.
Migration failed: {error}. Your legacy projects are untouched (nothing is ever deleted). You can retry.
</div>
)}
<p className="mt-3 text-[11px] text-muted-foreground">You can run this again later.</p>