Skip to content

feat(web): project switcher and create dialog - #24

Open
marcorivm wants to merge 1 commit into
feat/projects-switch-transportfrom
feat/projects-switcher-ui
Open

feat(web): project switcher and create dialog#24
marcorivm wants to merge 1 commit into
feat/projects-switch-transportfrom
feat/projects-switcher-ui

Conversation

@marcorivm

Copy link
Copy Markdown
Member

Slice 3b of #20, stacked on #23. 5 files, ~230 lines. The UI on top of 3a's transport.

A dropdown in the sidebar lists the projects you can reach, marks the active one, switches between them, and opens a dialog to create one. This is the slice that makes the whole stack visible: create → list → switch now works end to end.

Two details that are easy to get wrong

Switching clears the entire query cache, not just the project list.

queryClient.clear();
router.refresh();

Query keys are scoped by getProjectId() — an undefined stub in this edition (api-fetch.ts:5). So changing project does not change any query key, and cached agents, secrets and policy would keep being served for the previous project. Invalidating just projects.list() would look right and be silently wrong. router.refresh() follows because the cookie only reaches the server on the next request and most of the dashboard is server-rendered.

useCurrentProjectId mirrors the server's resolution chain rather than guessing it. Cookie wins when set; the session's default answers when it isn't. That ordering isn't arbitrary — the session endpoint resolves through findUserDefaultProject and ignores X-Project-Id, so it always reports the default project, never the selected one. If the cookie didn't take precedence the switcher would show the wrong project the moment you switched.

The cookie is read in an effect rather than during render, since it lives on document — reading it while rendering would mismatch the server-rendered HTML. undefined on first paint is correct and momentary.

Smaller choices

  • The list is already filtered server-side, so there's nothing to gate client-side. An empty list renders nothing rather than an empty control.
  • Creating switches to the new project — safe because the API seeds the creator as owner in the same write (feat(api): POST /v1/projects — create a project, owned by its creator #22), so it's immediately usable and manageable.
  • No duplicate-name check in the dialog: projectNameSchema deliberately permits duplicate names and the API disambiguates slugs silently. Validating against it would contradict the API.

Tests

None — same deliberate gap as #23. apps/web has no vitest harness on the 1.44.0 base; upstream ships apps/web/vitest.config.ts in v1.45.0, and adding one here would collide with that adoption.

Verified instead with:

pnpm run check                    # lint + types + clippy + format, 9/9 tasks
pnpm --filter @onecli/web build   # builds clean

Once the v1.45.0 harness lands, useCurrentProjectId's precedence and the cookie helpers are the first things worth covering.

Slice 3b of docs/project-lifecycle.md — the UI on top of 3a's transport.

A dropdown in the sidebar lists the projects the caller can reach, marks the
active one, switches between them, and opens a dialog to create a new one.
The list arrives already filtered by the API (listProjects mirrors
canAccessProjectAsUser), so there is nothing to gate client-side: an empty
list renders nothing at all.

Two details that are easy to get wrong:

Switching CLEARS the whole query cache, not just the project list. Query keys
are scoped by getProjectId(), which is an `undefined` stub in this edition, so
changing project does not change any key — cached agents, secrets and policy
would otherwise be served for the previous project. router.refresh() follows
because the cookie only reaches the server on the next request and most of the
dashboard is server-rendered.

useCurrentProjectId mirrors the server's resolution chain rather than
guessing: the cookie wins when set, the session's default answers when it is
not. The session endpoint deliberately reports the DEFAULT project (it
resolves through findUserDefaultProject and ignores the header), which is
exactly why the cookie has to take precedence. The cookie is read in an effect
rather than during render since it lives on document — reading it while
rendering would mismatch the server-rendered HTML.

Creating switches to the new project, which is safe because the API seeds the
creator as owner in the same write.

Still no tests: apps/web has no vitest harness on 1.44.0 and adding one would
collide with the config upstream ships in v1.45.0. Verified with pnpm check
(lint + types + clippy + format) and a full web build.
@marcorivm
marcorivm force-pushed the feat/projects-switcher-ui branch from 88e1333 to b21c323 Compare August 8, 2026 19:34
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