Skip to content

feat(frontend): T-215 ProjectTabBar — Radix Tabs + create-project popover (closes #83)#109

Merged
mpiton merged 2 commits into
mainfrom
feat/i-83-t-215-project-tab-bar
May 12, 2026
Merged

feat(frontend): T-215 ProjectTabBar — Radix Tabs + create-project popover (closes #83)#109
mpiton merged 2 commits into
mainfrom
feat/i-83-t-215-project-tab-bar

Conversation

@mpiton
Copy link
Copy Markdown
Owner

@mpiton mpiton commented May 12, 2026

Summary

  • New ProjectTabBar React component listing projects from useProjectStore via Radix Tabs with a trailing + button opening a Radix Popover that creates a project through invokeProjectsCreate and upserts + auto-selects it into the store.
  • New Popover wrapper at src/shared/components/ui/Popover.tsx (Radix Popover + Portal, --color-bg-elevated / --color-border-default tokens).
  • New projects i18n namespace (FR + EN) — tabs.empty.{title,body}, tabs.create_label, create.{title,name,path,submit,cancel,error}.
  • 18 vitest cases covering empty state, tab rendering + selection, popover open/submit/close, trimmed IPC payload, upsert + auto-select on success, error path, aria-describedby link on rejection, ArrowRight keyboard nav, FR i18n smoke test.

Why

Changes

  • src/features/projects/components/ProjectTabBar.tsx (new) — component with co-located useProjectCreateForm hook (double-submit guard via useRef, try-style tagged-result helper for the IPC call, error path keeps popover open with role="alert", aria-describedby linking inputs to the alert, aria-busy on form during submit) and FormField subcomponent.
  • src/features/projects/components/ProjectTabBar.test.tsx (new) — 18 vitest + RTL cases mocking the projects IPC wrapper at the module boundary, resetting the Zustand store and i18n singleton in beforeEach, asserting trimmed payload (" Gamma ""Gamma"), upsert + auto-select, popover lifecycle, error recovery (submit re-enabled, aria-describedby link), FR i18n smoke.
  • src/shared/components/ui/Popover.tsx (new) — Radix Popover wrapper following the Tabs.tsx / Dialog.tsx forwardRef + cn + displayName pattern.
  • src/shared/i18n/i18n.ts — register projects namespace.
  • src/shared/i18n/locales/{en,fr}.json — new projects keys (FR + EN in lock-step).
  • CHANGELOG.md — T-215 entry under [Unreleased] / Added.

Test plan

  • pnpm exec tsc -b clean.
  • pnpm exec oxlint . 0 warnings / 0 errors.
  • pnpm exec oxfmt --check clean on tracked files.
  • pnpm exec vitest run 166/166 pass (was 142 + 6 baseline drift = 148 before; +18 ProjectTabBar cases).
  • Manual: App.tsx wiring into the kanban data fetch is a sibling Sprint 2 task (see "Known follow-ups" in CHANGELOG entry); component is reviewed in isolation here.

Known follow-ups (intentional scope cuts)

  • App.tsx still uses the hardcoded DEFAULT_PROJECT_ID = 1 — the wiring that swaps selectedId from useProjectStore into invokeTasksList and the AppEvent::Project{Created,Updated,Deleted} subscriptions for cross-window cache invalidation belong to a sibling task per ARCHI §21.
  • The path input is a plain text field — a native file-picker (@tauri-apps/plugin-dialog) is a UX nicety tracked outside Sprint 2.
  • No AppEvent::ProjectCreated reactive subscription yet — only the optimistic upsert after the IPC resolves.

Summary by CodeRabbit

  • New Features

    • Added project tab bar with localized empty-state and a popover-driven create-project form (EN/FR) and a shared popover UI component.
  • Tests

    • Added comprehensive test suite covering accessibility, keyboard navigation, tab behavior, create-flow validation, error handling, and close-during-flight race cases.

Review Change Stack

Review Change Stack

…popover) (closes #83)

ProjectTabBar lists projects from useProjectStore via Radix Tabs and exposes a
trailing + button opening a Radix Popover with name + path inputs that calls
invokeProjectsCreate then upserts + auto-selects into the store.

- src/features/projects/components/ProjectTabBar.tsx: component with co-located
  useProjectCreateForm hook (double-submit guard via useRef, error path keeps
  popover open with role="alert" + aria-describedby on inputs) and FormField
  subcomponent.
- src/shared/components/ui/Popover.tsx: new Radix Popover wrapper (Portal +
  Content with --color-bg-elevated / --color-border-default tokens).
- src/shared/i18n/{i18n.ts,locales/{en,fr}.json}: new projects namespace with
  tabs.empty.{title,body}, tabs.create_label, create.{title,name,path,submit,
  cancel,error} keys (FR + EN).
- src/features/projects/components/ProjectTabBar.test.tsx: 18 vitest cases —
  empty state, tab rendering + selection, popover open/submit/close, trimmed
  IPC payload, upsert + auto-select on success, error path, aria-describedby
  link on rejection, ArrowRight keyboard nav, FR i18n smoke test.

App.tsx wiring of selectedId into the kanban data fetch and AppEvent
subscriptions are sibling Sprint 2 tasks per ARCHI §21.
@qodo-code-review
Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 12, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6b004401-4214-4d88-9c8c-9c3a1185ccab

📥 Commits

Reviewing files that changed from the base of the PR and between 9b0874a and 87bd0e4.

📒 Files selected for processing (3)
  • CHANGELOG.md
  • src/features/projects/components/ProjectTabBar.test.tsx
  • src/features/projects/components/ProjectTabBar.tsx
✅ Files skipped from review due to trivial changes (1)
  • CHANGELOG.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/features/projects/components/ProjectTabBar.tsx

📝 Walkthrough

Walkthrough

Adds ProjectTabBar (Radix Tabs + popover create form), a shared Popover UI wrapper, EN/FR i18n keys under a new "projects" namespace, changelog entries, and a comprehensive Vitest suite covering create-flow, race-guards, accessibility, and localization.

Changes

ProjectTabBar Component Feature

Layer / File(s) Summary
Shared Popover UI Wrapper
src/shared/components/ui/Popover.tsx
Re-exports Radix Popover primitives and adds PopoverContent (typed forwardRef wrapper) with default sideOffset, portal rendering, and state-driven animations.
i18n Configuration and Translations
src/shared/i18n/i18n.ts, src/shared/i18n/locales/en.json, src/shared/i18n/locales/fr.json
Adds "projects" to exported NAMESPACES and supplies English/French projects translation keys for empty state, create form labels/placeholders, submit/cancel, and error messages.
ProjectTabBar Component Implementation
src/features/projects/components/ProjectTabBar.tsx
New exported ProjectTabBar component: renders tabs from project store or empty state, controls tab selection via safe parser, and embeds a Popover create form. Includes tryCreateProject helper, useProjectCreateForm hook with submit token/lock and trimmed inputs, FormField helper, IPC invocation on submit, store upsert/select on success, and localized error handling on failure.
ProjectTabBar Tests and Changelog
src/features/projects/components/ProjectTabBar.test.tsx, CHANGELOG.md
Vitest + Testing Library suite (~362 added lines) validating empty state, tabs and selection, create-popover lifecycle (validation, trimming, submit success/error), in-flight-close race handling, accessibility (tablist, ArrowRight keyboard nav), French i18n assertions, and changelog entries documenting the added feature and a fixed submit-token race guard.

Sequence Diagram

sequenceDiagram
  participant User
  participant ProjectTabBar
  participant ProjectStore
  participant IPC as invokeProjectsCreate
  User->>ProjectTabBar: click + button
  ProjectTabBar->>ProjectTabBar: open popover
  User->>ProjectTabBar: fill name & path, click submit
  ProjectTabBar->>IPC: invoke with trimmed name/path
  IPC->>ProjectTabBar: return created project
  ProjectTabBar->>ProjectStore: upsert project
  ProjectTabBar->>ProjectStore: select project
  ProjectTabBar->>ProjectTabBar: close popover, reset form
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • mpiton/forgent#108: ProjectTabBar directly uses the useProjectStore hook and its upsert/select methods introduced/modified in that PR.
  • mpiton/forgent#107: This PR supplies the invokeProjectsCreate IPC wrapper that the tests/mock and component call.
  • mpiton/forgent#59: Related i18n groundwork; both PRs modify the exported NAMESPACES and localization usage.

Poem

🐰 I hopped to tabs and buttons bright,
I nudged the popover into the light,
Trimmed the names, kept errors near,
Tests danced round, and French chimed clear,
A tiny rabbit cheered: "Create away!"

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly summarizes the main change: adding a ProjectTabBar component with Radix Tabs and a create-project popover feature, directly aligned with the changeset.
Linked Issues check ✅ Passed All coding objectives from issue #83 are met: ProjectTabBar component implemented with Radix Tabs, create-project popover with name/path inputs, IPC integration with upsert/auto-select, design tokens applied, keyboard navigation preserved, empty-state rendering, and comprehensive vitest/RTL test coverage.
Out of Scope Changes check ✅ Passed All changes are directly scoped to #83 requirements: ProjectTabBar component, Popover wrapper, i18n namespace/strings, CHANGELOG update, and tests. No unrelated changes detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/i-83-t-215-project-tab-bar

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/features/projects/components/ProjectTabBar.tsx`:
- Around line 209-245: The submit result handlers can race with
closing/resetting the popover and overwrite fresh state; add a submission
version or token ref (e.g., submissionVersionRef) that you increment inside
onOpenChange when closing/resetting and capture in onSubmit before calling
tryCreateProject, then after the await only apply
setState/handleSuccess/handleFailure (and clear submittingRef) if the captured
token matches the current submissionVersionRef (or if the popover is still
open); update onOpenChange to increment/reset the token when setOpen(false) is
called and guard calls to handleSuccess/handleFailure in onSubmit using that
token so late async results are ignored.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 90adac5f-4119-4594-b7b2-573f13281b04

📥 Commits

Reviewing files that changed from the base of the PR and between ec3701e and 9b0874a.

📒 Files selected for processing (7)
  • CHANGELOG.md
  • src/features/projects/components/ProjectTabBar.test.tsx
  • src/features/projects/components/ProjectTabBar.tsx
  • src/shared/components/ui/Popover.tsx
  • src/shared/i18n/i18n.ts
  • src/shared/i18n/locales/en.json
  • src/shared/i18n/locales/fr.json

Comment thread src/features/projects/components/ProjectTabBar.tsx
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 7 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/features/projects/components/ProjectTabBar.tsx">

<violation number="1" location="src/features/projects/components/ProjectTabBar.tsx:212">
P2: Closing the popover during an in-flight create resets UI state but leaves the submit guard locked, so users can see an enabled submit button that no-ops until the previous request resolves.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread src/features/projects/components/ProjectTabBar.tsx
@codspeed-hq
Copy link
Copy Markdown
Contributor

codspeed-hq Bot commented May 12, 2026

Merging this PR will not alter performance

✅ 7 untouched benchmarks


Comparing feat/i-83-t-215-project-tab-bar (87bd0e4) with main (ec3701e)

Open in CodSpeed

…submit race

Closing the popover while invokeProjectsCreate is in flight previously left
submittingRef locked AND let the late result overwrite fresh state (stale
error shown on next open).

Add submitTokenRef bumped both on close and on submit-start; finalize() drops
the result entirely when the token no longer matches and resets submittingRef
only on the still-current path. onOpenChange(false) now bumps the token and
clears submittingRef synchronously so a subsequent submit is never blocked by
an abandoned in-flight request.

Tests:
- discards the IPC result when the popover closes before it resolves
- frees the submit guard so a subsequent submit can proceed after
  close-during-flight

Addresses CodeRabbit + cubic-dev-ai review findings on PR #109.
@mpiton mpiton merged commit 5760e26 into main May 12, 2026
13 checks passed
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.

T-215: ProjectTabBar component (Radix Tabs + create-project popover)

1 participant