ADR 0211 — A single navigation: the sidebar absorbs the project rail
Status
Accepted. 2026-10-03 (AT-404; owner decision on 03/10: "The expandable navigation bar on the left becomes the ONLY one; remove the other navigation bar, but include in this one all items of the removed bar"). Revises ADR 0126 — which stays accepted and is not edited — and RN-201.
Context
Since ADR 0126 the project tabs existed in TWO places: the 180px vertical rail
(ProjectRail.tsx, mounted in ProjectPage.tsx) and the sidebar's per-project
list (LinhaDeAba, RN-196).
CLAUDE.md declared this as a gap ("reconciling is a separate product decision"),
along with the frame's price: 492px on the Code tab (264 of the sidebar + 180 of
the rail + 48 of the CodeShell).
Measured before changing, what only the rail had: the three GROUPS with a
heading; the ACTIVE tab marked (aria-selected); the five counters (Insights,
PRs, Approvals, Backlog, Architecture), each on its tab and never summed — the
sidebar showed only Approvals, from the dashboard summary; the keyboard
(ArrowDown/ArrowUp/Home/End with wrap-around, crossing groups); the
active one scrolled into view; the vertical role="tablist"; and, on the phone
(RN-643), the scrollable horizontal bar above the
panel. The sidebar had, and the rail did not: the project collapsed in the 62px
rail, the phone drawer and the link to another browser window.
Decision
- The sidebar is the project's only navigation.
ProjectRail.tsx, its CSS and its mounting inProjectPage.tsxgo. The project list in the sidebar becomesAbasDoProjeto(apps/web/src/routes/AbasDoProjeto.tsx), with everything the rail had: groups fromGRUPOS_DO_PROJETO, active tab, per-tab counters, keyboard, a verticaltablistnamed by the project, roving tabindex and the active one scrolled into view. Each tab is a link to/projects/<id>?tab=<chave>. - The five counters have the SAME source, with no new request. The five
queues are now read by a single hook,
useContagensDoProjeto(apps/web/src/lib/contagens-do-projeto.ts), which the frame uses for the "needs you" panel (RN-467) and the sidebar uses for the numbers. SamequeryKeys, deduplicated. The sidebar only turns it on while the project frame is MOUNTED — that was the only place the rail existed; on another project screen (the Session, for example) and on closed projects the Approvals number from the summary stays, as before. The*.orcamento.test.tsxbudgets pass without changing any ceiling. - The active tab comes from the frame.
ProjectPagepublishes the tab it shows (publicarAbaAtiva) — the URL is not enough, because the "needs you" panel switches tabs without going through the router. The sidebar ASKS the frame for the clicked tab (pedirAba, an event and not state, so two frames never pull each other) and navigates with?tab=. The project link without?tab=goes back to the default tab. - Collapsed, a flyout. In the 62px rail, the OPEN project's square opens a flyout with the same tabs (closes on Esc, on choosing and on the second click), without expanding the sidebar. The other projects still expand.
- On the phone, the drawer. RN-643's horizontal bar goes; the tabs live in the drawer, which closes when one is chosen.
- The
CodeShell's 48px rail stays: it is the editor's activity, not project navigation.
Consequences
- The Code tab's frame drops from 492px to 312px with the sidebar expanded (264 + 48) and to 110px collapsed (62 + 48).
- The gap "the tabs exist in two places" closes.
- Switching tabs requires the expanded sidebar, the flyout or the drawer. Whoever collapsed the sidebar and navigated through the rail now uses the flyout.
- The sidebar list loses the router's
<Link>per tab: it is an<a>with the click intercepted, callingnavigate. Middle button/Ctrl still open in another window. rail.ariaLabelleaves both languages; the list is named bysidebar.projects.tabsAriaLabel("Abas de {{name}}").