chore: format with prettier [skip ci]
This commit is contained in:
parent
f848ae0baa
commit
aa08f11a86
|
|
@ -21,7 +21,7 @@
|
|||
## 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) |
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
## 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`) |
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ 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`) |
|
||||
|
|
@ -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
|
||||
|
|
@ -166,6 +170,7 @@ 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(...))`.
|
||||
|
|
@ -173,7 +178,9 @@ Terminal states (never auto-prompt): `completed`, `declined`. Prompting states:
|
|||
`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`
|
||||
|
|
@ -186,12 +193,13 @@ 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. |
|
||||
|
||||
### 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`
|
||||
|
|
|
|||
|
|
@ -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"];
|
||||
|
|
|
|||
|
|
@ -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 } } }));
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in New Issue