Skip to content

feat(import): dashboard-surfaced legacy import offer + daemon API#320

Draft
harshitsinghbhandari wants to merge 6 commits into
mainfrom
session/aa-120-import-wiring
Draft

feat(import): dashboard-surfaced legacy import offer + daemon API#320
harshitsinghbhandari wants to merge 6 commits into
mainfrom
session/aa-120-import-wiring

Conversation

@harshitsinghbhandari

Copy link
Copy Markdown
Collaborator

Wires the trigger around the merged import engine (#314): detect a legacy AO install on daemon start and surface the opt-in in the dashboard, not as a CLI prompt. Reuses the #314 engine verbatim; this is the detection + API + dashboard wiring around it.

Plan reference: https://gist.github.com/harshitsinghbhandari/c5d3233b36fcc4726ec9d7373fd0254a

What changed

ao start / detection (headless)

  • ao start no longer prompts on first boot. It only launches the daemon.
  • New internal/service/importer wraps the legacyimport engine with two operations:
    • Status — available when legacy data is present and the rewrite DB has no projects yet (the first-boot condition).
    • Run — executes the import through the daemon's own store, so the daemon stays the sole writer and the run is idempotent (existing rows skipped, legacy files never modified).
  • GET /api/v1/import (status) and POST /api/v1/import (run), reflected into the code-first OpenAPI spec; openapi.yaml and frontend/src/api/schema.ts regenerated.
  • ao import CLI command is unchanged for explicit offline imports.

Dashboard offer (where the user sees it)

  • useImportStatus / useRunImport hooks poll the daemon and trigger the import. A 501/unreachable daemon resolves to "no offer" rather than an error.
  • ImportOffer banner on the dashboard board: "Import projects and orchestrator from your earlier AO?" with Import / Not now.
    • Accept → POST /api/v1/import, then invalidate the workspace query so the imported projects + revived orchestrator appear.
    • Decline → dismiss for the session; data untouched, can import later.

Notes

Testing

  • cd backend && go build ./... && go test -race ./... — green (1436 tests).
  • golangci-lint (v2.12.2) clean on touched packages.
  • Frontend typecheck + vitest green; new ImportOffer and importer service/controller tests added.

🤖 Generated with Claude Code

harshitsinghbhandari and others added 6 commits June 18, 2026 20:11
Wire the trigger around the merged import engine (#314): detect a legacy
AO install at daemon start and surface the opt-in in the dashboard rather
than the CLI.

Backend:
- internal/service/importer: controller-facing service wrapping the
  legacyimport engine. Status() reports availability (legacy data present
  AND the rewrite DB has no projects yet); Run() executes the import
  through the daemon's own store, so the daemon stays the sole writer and
  the run is idempotent.
- GET/POST /api/v1/import: ImportController, wired into the daemon's API
  deps and the code-first OpenAPI spec (regenerated openapi.yaml +
  schema.ts).
- `ao start` is now headless: it no longer prompts on first boot. The
  CLI `ao import` command is unchanged for explicit offline imports.

Frontend:
- useImportStatus / useRunImport hooks polling the daemon and triggering
  the import; a 501/unreachable daemon resolves to "no offer".
- ImportOffer banner on the dashboard board with accept/decline. Accept
  runs the import and invalidates the workspace query so the imported
  projects and revived orchestrator appear; decline dismisses for the
  session (data untouched, can import later).

Gate: `go build ./... && go test -race ./...` green (1436 tests);
golangci-lint clean on touched packages; frontend typecheck + vitest green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…daemon

The dashboard import offer never appeared. Two independent causes:

1. Legacy config.yaml failed to parse, so Status() reported available:false
   even on an empty rewrite DB. `legacyProjectConfig.Repo` was typed `string`,
   but the real config stores `repo:` as a map ({owner,name,platform,
   originUrl}); yaml.v3 raised a *yaml.TypeError and loadLegacyConfig treated
   any error as fatal, returning an empty registry -> HasLegacyData false ->
   offer hidden. The field was dead (origin is re-resolved via git).
   - Retype Repo as a capture-only *yaml.Node so the structured block parses.
   - Make loadLegacyConfig tolerant of *yaml.TypeError: keep yaml.v3's partial
     decode, hard-fail only on real read/syntax errors.
   - Regression tests: realistic config (repo map, multiple projects) asserts
     HasLegacyData==true and fields parse; a deliberate type mismatch survives
     as a partial parse; a genuine syntax error stays fatal.

2. A manually-started daemon (`ao start` in a terminal) was never discovered:
   port discovery ran only inside the spawn path, so the renderer stayed on its
   compiled default base URL (localhost:3001 -> IPv6, can hit an unrelated
   server). Add always-on discovery from ~/.ao/running.json (read on app ready
   + poll), independent of spawning; it targets 127.0.0.1, sidestepping the
   IPv6 collision, and stays passive while we own the daemon process so it never
   races the spawn path. New shouldAdoptDiscoveredPort helper is unit-tested.

Gate: `go build ./... && go test -race ./...` green (1439 tests); golangci-lint
clean on legacyimport; frontend typecheck + vitest green (148 tests).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Per product decision, the legacy import now brings in projects (and their
per-project settings) only. Orchestrator-session porting and Claude transcript
relocation are removed: workers/orchestrators respawn fresh from the imported
projects instead.

Backend:
- legacyimport: delete orchestrator.go + claude.go (and tests). Run() loops
  projects only; Store drops GetSession/ImportSession; Options drops
  DataDir/ClaudeProjectsDir; Report drops the orchestrator/transcript counters.
  paths.go drops the now-unused projectSessionsDir/isDir/defaultClaudeProjectsDir.
- store: remove the import-only ImportSession verbatim-insert method (+test);
  nothing else used it.
- service/importer + daemon wiring: drop the now-unused DataDir.
- `ao import` CLI: help text, confirm prompt, and summary no longer mention
  orchestrators/transcripts.
- OpenAPI regenerated (ImportReport loses the orchestrator/transcript fields).

Frontend:
- schema.ts regenerated; ImportReport type trimmed; offer copy is now
  "Import projects from your earlier AO?" with matching test assertions.

Gate: `go build ./... && go test -race ./...` green (1424 tests); golangci-lint
clean on changed packages; frontend typecheck + vitest green (148 tests).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@harshitsinghbhandari harshitsinghbhandari marked this pull request as draft June 18, 2026 16:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant