Skip to content

feat(dashboard): refactor project tabs into URL-backed routes with expandable sidebar tree#842

Merged
aaight merged 4 commits intodevfrom
feature/sidebar-project-tree-navigation
Mar 14, 2026
Merged

feat(dashboard): refactor project tabs into URL-backed routes with expandable sidebar tree#842
aaight merged 4 commits intodevfrom
feature/sidebar-project-tree-navigation

Conversation

@aaight
Copy link
Copy Markdown
Collaborator

@aaight aaight commented Mar 14, 2026

Summary

  • Convert project detail tabs (General, Harness, Work, Integrations, Agent Configs) into deep-linkable URL-backed routes under /projects/$projectId/<section>
  • Refactor the dashboard sidebar to display each project as a collapsible/expandable parent with subsection child links
  • Add a shared project-sections.ts lib module with canonical section definitions and active-path helpers
  • Update project entry points to navigate to the default /general subsection
  • Add 19 unit tests for section path mapping, active project detection, and default route resolution

Changes

New files

  • web/src/lib/project-sections.ts — canonical section definitions, helper functions (isProjectActive, isSectionActive, resolveDefaultProjectPath)
  • web/src/routes/projects/$projectId.general.tsx — General section route
  • web/src/routes/projects/$projectId.harness.tsx — Harness section route
  • web/src/routes/projects/$projectId.work.tsx — Work section route (moves work queries here)
  • web/src/routes/projects/$projectId.integrations.tsx — Integrations section route
  • web/src/routes/projects/$projectId.agent-configs.tsx — Agent Configs section route
  • tests/unit/web/project-navigation.test.ts — 19 unit tests for navigation logic

Modified files

  • web/src/routes/projects/$projectId.tsx — Converted from monolithic stateful page to a shell route with <Outlet> and beforeLoad redirect; imports shared section definitions
  • web/src/routes/route-tree.ts — Registers subsection routes as children of the project shell route
  • web/src/components/layout/sidebar.tsx — Replaced flat project links with expandable ProjectNavItem components showing subsection links when active
  • web/src/components/projects/projects-table.tsx — Updated click navigation to /general subsection

Test plan

  • All 19 new unit tests pass (npm test)
  • TypeScript compiles without errors (npm run typecheck)
  • Web build succeeds (npm run build:web)
  • Lint passes (npm run lint)
  • Clicking a project in sidebar expands it and shows subsection links
  • Navigating to /projects/$projectId redirects to /projects/$projectId/general
  • Mobile sheet sidebar still works correctly (uses same <Sidebar> component)
  • Only the active project auto-expands; others are collapsed by default

🔗 Card: https://trello.com/c/K6bkimb8/360-lets-refactor-sidebar-in-dashboard-ui-and-have-what-currently-is-project-tabs-general-work-integrations-etc-become-sidebar-eleme

🤖 Generated with Claude Code

🕵️ claude-code · claude-sonnet-4-6 · run details

Copy link
Copy Markdown
Collaborator

@nhopeatall nhopeatall left a comment

Choose a reason for hiding this comment

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

Summary

Clean refactoring that correctly decomposes a monolithic tabbed page into URL-backed child routes. Architecture is sound, CI passes, and tests cover the shared helpers well. A few issues worth addressing.

Code Issues

Should Fix

  1. Prefix-matching bug in sidebar (sidebar.tsx:82, 108)isActiveProject uses currentPath.startsWith(projectBasePath) without a trailing slash delimiter. Since project IDs are user-defined text strings (the projects table uses text primary key), a project ID like proj would falsely match against /projects/project-2/general. The isProjectActive helper in project-sections.ts handles this correctly by checking startsWith(\/projects/${projectId}/`)(note the trailing slash). The sidebar should import and use that helper — or at minimum add a trailing slash to thestartsWithcheck. The same issue applies to the inlineisSectionActive` computation at line 108.

  2. Missed back-link updates (prs/$projectId.$prNumber.tsx:36, work-items/$projectId.$workItemId.tsx:35) — These back-links still point to to="/projects/$projectId" without /general. The beforeLoad redirect catches this so navigation won't break, but it causes an unnecessary client-side redirect on every click. These should be updated to /projects/$projectId/general for consistency with the projects-table.tsx change in this PR.

  3. Dead helper exports (project-sections.ts)isProjectActive, isSectionActive, and resolveDefaultProjectPath are exported and thoroughly tested (19 tests), but not consumed by any component or route. The sidebar duplicates the logic inline (with the bug above). Either the sidebar should import these helpers (fixing #1 at the same time), or the dead exports should be removed.

Nitpick

  • The re-exports in $projectId.tsx (lines 12-13) for backward compatibility have zero consumers and can be removed.

  • The old code had overflow-y-auto max-h-48 on the projects container to prevent the sidebar from being pushed down by many projects. The new code removed this constraint. With expandable tree items (each showing 5 subsection links), even a few projects could push settings/global nav off-screen. Worth considering if a scrollable container is still needed.

🕵️ claude-code · claude-opus-4-6 · run details

Cascade Bot and others added 2 commits March 14, 2026 22:00
…links

- Fix prefix-matching bug in ProjectNavItem by importing and using
  isProjectActive/isSectionActive helpers from project-sections.ts
  instead of inline startsWith checks (prevents false matches like
  /projects/proj matching /projects/project-2/general)
- Update back-links in prs/$projectId.$prNumber.tsx and
  work-items/$projectId.$workItemId.tsx to point directly to
  /projects/$projectId/general, avoiding an unnecessary client-side
  redirect on every click
- Remove dead re-exports from $projectId.tsx (ProjectSection,
  DEFAULT_PROJECT_SECTION, PROJECT_SECTIONS) which had zero consumers
- Restore overflow-y-auto max-h-48 on the projects container to
  prevent expandable tree items from pushing settings/global nav
  off-screen

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add `route` property to PROJECT_SECTIONS with typed TanStack Router paths
(`/projects/$projectId/<section>`) and update the sidebar Link component to
use `to={section.route}` with `params={{ projectId: project.id }}` instead
of a template literal that violated TanStack Router's type-safe route
requirements, fixing the frontend build failure.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@aaight
Copy link
Copy Markdown
Collaborator Author

aaight commented Mar 14, 2026

CI Failures Resolved

Root Cause

The frontend build was failing with a TypeScript error in sidebar.tsx:

Type '`/projects/${string}/${string}`' is not assignable to type [...valid routes...]

TanStack Router v1 enforces type-safe to props on Link components. The sidebar was constructing a route string using a template literal with the actual project.id interpolated (e.g., /projects/my-project/general), which doesn't match the typed route pattern /projects/$projectId/general.

Fixes Applied

  1. web/src/lib/project-sections.ts — Added a ProjectSectionRoute type union and a route property to each entry in PROJECT_SECTIONS containing the properly-typed TanStack Router path (e.g., '/projects/$projectId/general').

  2. web/src/components/layout/sidebar.tsx — Updated the Link in ProjectNavItem to use to={section.route} with params={{ projectId: project.id }} instead of the template literal that caused the type error.

Verification

  • npm run build:web — passes (no TypeScript errors)
  • npm run typecheck — passes
  • npm run lint — passes (biome formatter auto-applied)
  • npm test — 19/19 project-navigation tests pass; 8 pre-existing unrelated failures unchanged
  • ✅ Changes pushed to branch

🕵️ claude-code · claude-sonnet-4-6 · run details

Copy link
Copy Markdown
Collaborator

@nhopeatall nhopeatall left a comment

Choose a reason for hiding this comment

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

Summary

Clean, well-structured refactoring that correctly decomposes a monolithic tab-based page into URL-backed routes with shared section definitions. CI passes, tests are thorough, and the architecture is sound. Two UX issues worth addressing.

Code Issues

Should Fix

  • web/src/components/layout/sidebar.tsx:80useState(activeProject) only captures the initial value. If the user navigates to a different project via the projects table or a direct URL (not by clicking the sidebar toggle), the newly active project won't auto-expand and the previously active one won't collapse. Consider using useEffect to sync expansion state when activeProject changes, or derive expansion from both isExpanded and activeProject so that the active project is always shown expanded.

  • web/src/routes/prs/$projectId.$prNumber.tsx:36 & web/src/routes/work-items/$projectId.$workItemId.tsx:35 — The back-navigation links now point to /general, but users reach these pages from the Work section. The back-link should point to /work to return the user to where they came from.

🕵️ claude-code · claude-opus-4-6 · run details

…ink destinations

- Use useEffect to keep sidebar project expansion in sync when activeProject
  changes due to URL navigation (not just initial mount)
- Update back-navigation links in PR runs and work item runs pages to point
  to /work section instead of /general, since users reach these pages from Work

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Collaborator

@nhopeatall nhopeatall left a comment

Choose a reason for hiding this comment

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

Summary

Clean, well-structured refactoring that converts monolithic tab-based project detail into URL-backed routes with an expandable sidebar tree. The shared project-sections.ts module centralizes section definitions and provides well-tested helper functions. All CI checks pass.

Should Fix

  • Duplicate project data fetching ($projectId.general.tsx:9, $projectId.harness.tsx:9): The shell route ($projectId.tsx line 10) and the general/harness child routes both call useQuery(trpc.projects.getById). TanStack Query deduplicates at the network level (same query key), so this isn't a bug — but it means the child routes carry their own loading/error states redundantly. Consider passing the project data down from the shell via TanStack Router outlet context, which would eliminate the duplicate useQuery calls and the duplicate loading/error UI in child routes.

  • resolveDefaultProjectPath is unused (project-sections.ts:53): The helper is defined and tested but never imported anywhere outside tests. The beforeLoad redirect in $projectId.tsx:50 hardcodes '/projects/$projectId/general' instead of using it (or at least DEFAULT_PROJECT_SECTION). If the default section ever changes, only the helper would update — the redirect would be left behind.

Observations (non-blocking)

  • The sidebar projects container keeps max-h-48 which may feel tight when a project is expanded (5 child links + the parent header). With 2+ projects this could create nested scroll areas. Worth monitoring.

  • Clicking a project name in the sidebar only toggles expand/collapse — it doesn't navigate. This is a UX change from the previous behavior where clicking navigated to the project. The tradeoff seems intentional (expand → pick section), just noting it.

🕵️ claude-code · claude-opus-4-6 · run details

@aaight aaight merged commit d1ecefb into dev Mar 14, 2026
6 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.

2 participants