Skip to content

Add embed-targetable workspace surfaces and project/worktree registry#258

Open
wesm wants to merge 5 commits intomainfrom
embed-workspace-surfaces
Open

Add embed-targetable workspace surfaces and project/worktree registry#258
wesm wants to merge 5 commits intomainfrom
embed-workspace-surfaces

Conversation

@wesm
Copy link
Copy Markdown
Owner

@wesm wesm commented May 6, 2026

Summary

  • Adds /workspaces/embed/* routes that mount individual pieces of the workspaces UX (list, terminal, per-item detail sidebar, empty placeholder, first-run panel, project card) without the surrounding app chrome. Standalone /workspaces is unchanged.
  • Adds two new WorkspaceTerminalView props, hideWorkspaceList and hideRightSidebar, that an embedder can pass to skip the workspace list column, the right detail sidebar, and the segmented Diff/Issue/PR/Reviews button group.
  • Adds a project/worktree registry (migration 000017) backing six Huma endpoints under /api/v1: register-project, list-projects, get-project, register-worktree, list-worktrees, list-launch-targets. Project identity links to middleman_repos via FK; registering a project does not subscribe its repo to sync.
  • Embed config gains embed.tooling (git/gh availability and gh auth state pushed via __middleman_update_tooling) and a separate actions.project[] registry whose handler returns CommandResult so project-scoped surfaces can render in-flight, success, and failure states.
  • RepoSettings add/remove/refresh actions no longer short-circuit when running inside an embedder; the underlying settings API works the same way embedded or standalone.

The standalone /workspaces page is unchanged. Embedders opt in by routing to a /workspaces/embed/... path.

🤖 Generated with Claude Code

@roborev-ci
Copy link
Copy Markdown

roborev-ci Bot commented May 6, 2026

roborev: Combined Review (99b9eb0)

Medium issue found: the new project card consumes API data the backend does not provide.

Medium

  • frontend/src/lib/components/terminal/WorkspaceProjectCard.svelte:34
    WorkspaceProjectCard expects Project.host, but the new ProjectResponse schema and server response do not include host. This can render missing or incorrect project metadata, while tests may still pass because they mock a field that does not exist in the real API response.
    Fix: Remove host from the component UI/model, or add it consistently to the API response and generated schema.

Synthesized from 3 reviews (agents: codex, gemini | types: default, security)

@wesm
Copy link
Copy Markdown
Owner Author

wesm commented May 6, 2026

Adds a project/worktree registry (migration 000017) backing six Huma endpoints under /api/v1: register-project, list-projects, get-project, register-worktree, list-worktrees, list-launch-targets. Project identity links to middleman_repos via FK; registering a project does not subscribe its repo to sync.

@mariusvniekerk this will need a closer look as this is introducing a new concept to the application.

@mariusvniekerk
Copy link
Copy Markdown
Collaborator

Yeah I'll take a look after landing the provider one.

@mariusvniekerk mariusvniekerk force-pushed the embed-workspace-surfaces branch from 99b9eb0 to 34ab380 Compare May 7, 2026 00:49
@roborev-ci
Copy link
Copy Markdown

roborev-ci Bot commented May 7, 2026

roborev: Combined Review (34ab380)

High: New project/worktree DB schema is not applied at runtime, so the new endpoints can fail on fresh and existing databases.

High

  • internal/db/migrations/000018_add_generic_projects_and_worktrees.up.sql:12
    The new project/worktree tables are only added as migration files, but the DB initialization path does not apply files from internal/db/migrations; it applies schema.sql plus hardcoded migrations. Fresh and existing databases will not have middleman_projects, causing the new endpoints to fail at runtime.
    Fix: Wire this migration into db.init() or update schema.sql and add an idempotent migration path for existing databases.

Medium

  • internal/server/projects_handlers.go:121
    registerProject only checks that local_path exists and is a directory, so any non-git folder can be registered as a project even though the feature models repository checkouts and later worktree flows depend on git.
    Fix: Validate the path is inside a git work tree before creating the project, while still allowing git repos with no parseable remote to remain local-only.

  • frontend/src/lib/components/terminal/WorkspaceProjectCard.svelte:88
    The card loads data only in onMount, so navigating from one /workspaces/embed/project/:projectId route to another reuses the component with a new prop but continues showing the old project/worktree data.
    Fix: Reload when projectId changes, or key the component by projectId; guard in-flight loads so stale responses cannot overwrite the current project.

  • frontend/src/App.svelte:478
    The new /workspaces/embed/* browser-visible surfaces lack full-stack e2e coverage. The diff only adds component/unit tests, so regressions in route wiring, API client generation, or project/worktree rendering could ship despite isolated mocks passing.
    Fix: Add an e2e test that starts the real server with a temp SQLite DB, registers a project/worktree through the real API, navigates to /workspaces/embed/project/:project_id and at least one other new embed surface, and asserts the rendered state.


Synthesized from 3 reviews (agents: codex, gemini | types: default, security)

@roborev-ci
Copy link
Copy Markdown

roborev-ci Bot commented May 7, 2026

roborev: Combined Review (f51475b)

No Medium, High, or Critical findings were reported.

All review outputs are clean or contain no actionable findings above the severity threshold.


Synthesized from 3 reviews (agents: codex, gemini | types: default, security)

@mariusvniekerk
Copy link
Copy Markdown
Collaborator

PR review status on current head f51475b:

  • Project.host finding: addressed in 34ab3800 by removing the non-schema host field from WorkspaceProjectCard and its tests.
  • Migration finding: checked against internal/db/migrations.go; migrations are embedded with //go:embed migrations/*.sql and applied by runMigrations, so no schema.sql wiring is needed.
  • Current roborev result: clean (f51475b, no Medium/High/Critical findings).

Also added provider-aware tooling follow-up for GitLab hosts: the embed tooling panel now shows glab instead of gh when the selected workspace host is GitLab.

@roborev-ci
Copy link
Copy Markdown

roborev-ci Bot commented May 7, 2026

roborev: Combined Review (42b9230)

No Medium, High, or Critical findings were reported.

All review agents found the code clean for reportable issues.


Synthesized from 3 reviews (agents: codex, gemini | types: default, security)

@wesm
Copy link
Copy Markdown
Owner Author

wesm commented May 7, 2026

We should do a proper design sprint on the project concept, but I'll merge this for now

@mariusvniekerk
Copy link
Copy Markdown
Collaborator

mariusvniekerk commented May 7, 2026

wesm and others added 4 commits May 7, 2026 09:14
Restore the ability for an embedding host to mount individual parts of
the new /workspaces UX (list sidebar, terminal/home/empty surface,
per-item right detail sidebar, empty placeholder) without the
surrounding app chrome, and add a generic project/worktree registry
the First Run Panel and Project Card surfaces depend on. The
standalone /workspaces page is unchanged; embed hosts opt in by
routing to a /workspaces/embed/... path.

Workspace embed surfaces:

- /workspaces/embed/list mounts the workspace list sidebar.
- /workspaces/embed/terminal[/:workspaceId] mounts the terminal
  surface with two new WorkspaceTerminalView props
  (hideWorkspaceList, hideRightSidebar) that skip the list column,
  the right detail sidebar, and the segmented PR/Reviews/Issue
  toggle that drives it.
- /workspaces/embed/detail/:itemType/:platformHost/:owner/:name/:number
  mounts the per-item WorkspaceRightSidebar with optional branch and
  tab query params.
- /workspaces/embed/empty/:reason mounts a small placeholder for
  noSelection / noRepo / noWorkspace.
- /workspaces/embed/first-run mounts the WorkspaceFirstRunPanel with
  add-existing, clone, and connect-github actions plus a tooling
  status block.
- /workspaces/embed/project/:project_id mounts the
  WorkspaceProjectCard with a new-worktree CTA scoped to the
  project_id and the project's registered worktrees.

Project/worktree registry (migration 000017):

- middleman_projects records a local repository checkout middleman
  knows about. Identity (host/owner/name), when present, lives in
  middleman_repos and is joined in via the new repo_id FK; projects
  with no parseable remote have repo_id NULL. ON DELETE SET NULL on
  the FK keeps the on-disk checkout as the source of truth for the
  project record - unsyncing a repo turns the project local-only
  rather than stranding or deleting it.
- middleman_project_worktrees records worktrees the caller already
  created on disk; middleman just persists the metadata. Cascade-
  deletes with the project.
- ID generation produces prefixed opaque slugs (prj_/wtr_) so
  callers cannot conflate the two record kinds.
- internal/projects/identity.go runs 'git remote get-url origin' and
  parses HTTPS, SCP-style, and ssh:// URLs into a host/owner/name
  triple. Unparseable, missing, or non-git remotes return
  (nil, nil) - unknown identity is allowed and never blocks
  registration.
- POST /projects resolves identity (caller-provided wins, otherwise
  derived from the path's git origin), upserts a middleman_repos row
  via db.UpsertRepo to give the project a stable FK target, and
  stores the FK. UpsertRepo is pure DDL (INSERT ON CONFLICT DO
  NOTHING + SELECT id) and does NOT subscribe the repo to sync;
  sync subscription remains driven by config.toml and the AddRepo
  settings handler. The middleman_repos row exists solely as a
  non-drifting identity target.
- Six Huma operations under /api/v1 with neutral operation IDs:
  register-project, list-projects, get-project, register-worktree,
  list-worktrees, list-launch-targets. Routes are keyed on a
  server-assigned project_id rather than host/owner/name, so the
  no-remote path has a stable key.
- list-launch-targets resolves fresh on every request from config
  agents and PATH lookup, so it works whether or not the workspace
  runtime manager has been initialized.

Embed-config contract additions (forward-compatible):

- actions.project[]: a separate ProjectActionDef registry whose
  handler must return CommandResult. The existing pull-request and
  issue registries keep their void/Promise<void> shape; project-
  scoped surfaces use the ack-required shape so the firing surface
  can render in-flight, success, and failure states.
- embed.tooling: a ToolingStatus block describing git and gh
  availability and gh authentication state. Pushed by the embedder
  via a new __middleman_update_tooling bridge function.
- invokeProjectAction: an ack-aware runner that awaits the project
  action's result, normalizes thrown errors and async rejections
  into { ok: false, message }, and returns ok: true for
  void-returning handlers so legacy-shaped callers do not regress.

Settings cleanup:

- RepoSettings.svelte previously short-circuited add, remove, and
  refresh actions whenever the SPA was running inside an embedder,
  which made the buttons appear interactive but do nothing. The
  underlying settings API endpoints work the same way standalone or
  embedded, so the short-circuits were not protecting any real
  invariant. Removing them lets an embedded user manage repo sync
  targets with the same UI a standalone user sees.

Test coverage that pins the embed contract so middleman refactors do
not silently break embedders:

- WorkspaceEmbedEmptyState.test.ts: each of the three reason
  variants (noSelection / noRepo / noWorkspace) round-trips its
  message, so a rename of the EmbedEmptyReason enum without
  updating the component fails the build.
- WorkspaceTerminalViewEmbed.test.ts: the new hideWorkspaceList and
  hideRightSidebar props are pinned by four cases (default-on/off
  for each), so a refactor of the CollapsibleResizableSidebar
  wrapping or the segmented PR/Reviews button group cannot regress
  without failing the test.
- router.test.ts: every embed-workspace route is asserted to fire
  onNavigate with type "workspaces", and the
  __middleman_navigate_to_route window bridge is verified to exist
  and to drive the SPA route - both are imperative-call surfaces an
  embedding host depends on.
- queries_projects_test.go: includes a TestCreateProjectFKSetNullOnRepoDelete
  case that pins the ON DELETE SET NULL semantic on the repo FK -
  deleting the linked middleman_repos row clears the project's
  identity but does not delete the project record.
- projects_handlers_test.go: error paths the embedder can hit are
  pinned end-to-end - 422 from Huma's schema validator on missing
  required fields, 400 from the handler's TrimSpace check on
  whitespace-only fields, 404 when registering a worktree against
  a nonexistent project, 400 when local_path is a regular file,
  and a contract test that list-projects emits [] (not null) when
  empty so embedders can iterate the response safely. The
  W1-Slice-A gate test asserts platform_identity is built from the
  joined middleman_repos row by re-fetching the project.
- TestRegisterProject_DoesNotSubscribeRepoToSync pins the
  load-bearing invariant that registering a project does NOT
  subscribe the linked repo to sync. UpsertRepo writes the
  middleman_repos FK target, but the syncer's tracked-repos set is
  driven exclusively by the user's TOML config via SetRepos. If a
  future refactor accidentally couples the two paths, an embedder
  could quietly grow the sync set just by registering a project;
  this test fails first.

Reason: the prior workspace embedding API was removed in the
issue-backed workspaces rework. Hosts that compose their own layout
around the workspace pieces had no way to render just one component,
so the new top-level Workspaces page rendered inside their custom
panels and produced double chrome. Re-exposing the new components
via dedicated embed routes keeps the standalone UX as the single
implementation while letting hosts pick the slice they need.

The registry uses an FK linkage to middleman_repos rather than
duplicating platform identity columns, so a (host, owner, name)
triple has exactly one source of truth across synced repos and
registered projects. The host TEXT NOT NULL DEFAULT 'local' column,
the all-or-nothing CHECK constraint, and the casefold triggers the
initial registry shipped have been dropped - the FK target already
enforces casefold and uniqueness on its row, and host was
speculative.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Rebase onto GitLab provider abstraction introduced a migration number collision and stricter provider-aware frontend types. Renumber the project/worktree migration after main's platform identity migration, and update embed workspace UI/tests to match current project and sidebar contracts.
GitLab workspace hosts need glab availability and authentication surfaced instead of the GitHub CLI row. Make the tooling panel choose gh or glab from the selected workspace provider while preserving the existing GitHub defaults and copy actions.
The workspace embed branch had several duplicated conversion and routing paths that made the new surfaces harder to audit. This keeps the behavior and API contracts intact while giving the project scanners, response mappers, route page classification, and tooling-status rendering a single expression of the same logic.\n\nThe commit hook also exposed that project identity resolution could inherit Git hook environment and read the parent worktree remote instead of the target checkout. Sanitize child git processes with the existing gitenv helper so project registration stays scoped to the requested path under hook-driven tests too.\n\nValidation:\n- go test ./internal/db -shuffle=on\n- go test ./internal/projects -shuffle=on\n- GIT_DIR=/tmp/poison.git GIT_WORK_TREE=/tmp/poison go test ./internal/projects -shuffle=on\n- go test ./internal/server -run '^(TestW1SliceAGate|TestRegisterProject_|TestGetProject_|TestRegisterWorktree_|TestListProjects_|TestListLaunchTargets_)' -shuffle=on\n- bun run --cwd frontend typecheck\n- bun run --cwd frontend lint\n- bun run --cwd frontend test src/lib/stores/router.test.ts src/lib/components/terminal/ToolingStatusBlock.test.ts src/lib/components/terminal/WorkspaceProjectCard.test.ts\n- bunx @sveltejs/mcp@0.1.22 svelte-autofixer frontend/src/App.svelte --svelte-version 5 (no issues; broad pre-existing effect suggestions only)\n- bunx @sveltejs/mcp@0.1.22 svelte-autofixer frontend/src/lib/components/terminal/ToolingStatusBlock.svelte --svelte-version 5\n- bunx @sveltejs/mcp@0.1.22 svelte-autofixer frontend/src/lib/components/terminal/WorkspaceProjectCard.svelte --svelte-version 5\n- pre-commit hook path up to go test (short) after fixes\n\nGenerated with OpenAI Codex\nCo-authored-by: OpenAI Codex <noreply@openai.com>
@mariusvniekerk mariusvniekerk force-pushed the embed-workspace-surfaces branch from 42b9230 to c1c4dd3 Compare May 7, 2026 13:15
@roborev-ci
Copy link
Copy Markdown

roborev-ci Bot commented May 7, 2026

roborev: Combined Review (c1c4dd3)

No Medium, High, or Critical findings were reported.

All reviewed agents’ outputs are clean at the requested severity threshold.


Synthesized from 3 reviews (agents: codex, gemini | types: default, security)

Project registration now exposes platform_identity.platform_host to match the provider-aware API contracts used elsewhere, and the generated clients/schema reflect that contract.
@roborev-ci
Copy link
Copy Markdown

roborev-ci Bot commented May 8, 2026

roborev: Combined Review (1ff5929)

No Medium, High, or Critical findings were reported.

All reviewed outputs are clean or contain no actionable findings with file/line references at the requested severity threshold.


Synthesized from 3 reviews (agents: codex, gemini | types: default, security)

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.

2 participants