Commit Graph

25 Commits

Author SHA1 Message Date
Dhruv Sharma f3e45959e6
Add orchestrator-driven code review board (#1871)
* feat: add orchestrator-driven code review board

* feat: wire review findings back to workers

* feat(web): send review feedback to workers

* fix(core): mark stale review runs outdated

* fix: restore reviewer flow after main merge

* Fix review lock lint failure

* Guard concurrent review executions

---------

Co-authored-by: Madhav Kumar <lakshy1523@gmail.com>
2026-05-19 11:47:57 +05:30
Pritom Mazumdar 406b26e837
feat(web): sidebar and dashboard header UI/UX polish (#1846)
* perf(web): eliminate sidebar re-renders on every SSE tick

Resolves #1844

The SSE hook delivers a new sessions array reference every 5 seconds
even when content is identical, causing sessionsByProject to recompute
and all session rows to re-render on every tick.

Three fixes in ProjectSidebar.tsx:
* sessionsKey + sessionsRef: replace unstable array reference in memo
  deps with a content-derived string; memo only fires on real changes
* SessionRow memoized component: rows skip re-renders when props are
  unchanged; navigate and startRename stabilised with useCallback
* SessionDot memoized: status indicator skips re-renders when level
  prop is unchanged

A quiet SSE tick now touches zero React components in the sidebar.

* fix(web): add displayName, displayNameUserSet, branch to sessionsKey hash

Without these fields, a session rename delivered via SSE did not
trigger sessionsByProject to recompute. The stale session object
held the old displayName, and once the optimistic pendingRename
was cleared the sidebar silently reverted to the pre-rename title.

Addresses review feedback on PR #1846.

* feat(web): sidebar and dashboard header UI/UX polish

Removes state text labels from sidebar session rows so the colored dot
is the sole status indicator, matching the intended design. Fixes the
sidebar compact header height to align with the 48px main header.
Adds session count summary pills to the dashboard project header.
Converts CopyDebugBundleButton to an icon-only compact form so the
actions row stays vertically centered. Fixes the project page wrapper
missing flex-1 which caused a right-side viewport gap in the horizontal
shell layout.

* fix(web): resolve lint errors from UI/UX polish

Remove LEVEL_LABELS, _isLoading prefix for unused loading var, and dead
title variable from ProjectSidebar. Remove unused isDashboardSessionStatus
and isActivityStateValue from the project session page. Remove
react-hooks/exhaustive-deps eslint-disable comments for a rule not in the
ESLint config. Stabilize startRename via pendingRenamesRef so the callback
does not recreate on every rename state change, preventing unnecessary
SessionRow re-renders. Remove non-null assertion in Dashboard.tsx
handleToggleSidebar with a null guard.

* fix(web): replace native Node 25 localStorage stub with full in-memory mock

Node.js 25 exposes a native localStorage via --localstorage-file that lacks
.clear() and .key(), causing all UpdateBanner tests to throw TypeError.
Replace the global with a complete in-memory implementation so test suites
work across all Node versions.

* fix(web): seed sidebar with all sessions on hard refresh and eliminate per-project layout re-render

- Hoist sidebar layout from projects/[projectId]/layout.tsx to projects/layout.tsx so it renders
  once for the entire /projects/* subtree and never re-mounts when switching between projects
- Pass getDashboardPageData("all") so initial sessions cover every project, not just the primary
- Extract ProjectLayoutClient from the old client layout for clean server/client split
- Simplify per-project empty state to "No active sessions" only, removing the second hint line
- Restore accidentally deleted Dashboard empty-state test for zero-projects install
- Fix Reflect.deleteProperty lint error in localStorage mock; drop unused AttentionLevel import
  and dead effectiveDisplayName/pending variables from ProjectSidebar editing block

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(web): address PR review — orchestrators prop, sessionsKey, badge count, mobile overlay, tests

* fix(web): remove duplicate skeleton sidebar from project loading state

* fix(web): restore orchestrator button and eliminate Session unavailable flash

Re-add the orchestrator icon + menu item that were removed during the merge
conflict resolution. Convert the icon from a <Link> to an <a> that calls
navigate() with the full session object so ProjectSessionPage gets an
instant sessionStorage cache hit instead of starting with session=null
and briefly showing the "Session unavailable" error card.

* fix(web): eliminate Session unavailable flash on orchestrator navigation

React Strict Mode aborts the first fetchSession() during its unmount/remount
cycle. The aborted finally reset fetchingSessionRef but set loading=false,
briefly showing the error card before the retry completed. Fix: keep loading=true
on abort (no session yet), and immediately retry via fetchSession() once the
ref is clear. mountedRef guards the retry so it only fires on Strict Mode
remounts — not on genuine navigation-away unmounts, which would leak requests.

* fix(web): fix working pill count and layout of error states in project session page

- Dashboard topbar "working" pill now counts only actively working sessions,
  not working + pending (which inflated the number incorrectly)
- Error/loading/missing states in ProjectSessionPage wrapped in
  dashboard-main--desktop so they fill the flex shell beside the sidebar
  instead of shrinking to content width

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 23:45:35 +05:30
i-trytoohard e6ad078d7a
fix(web): kill RSC prefetch storm + dedupe in-flight fetches + retry on transient timeout (closes #1855) (#1856)
* fix: reduce session page fetch starvation

* fix: address fetch dedupe review feedback

* fix: preserve body-read timeouts in client fetch

---------

Co-authored-by: i-trytoohard <193449657+i-trytoohard@users.noreply.github.com>
2026-05-15 12:00:56 +05:30
Harshit Singh Bhandari 71326bc87e
feat(web): allow renaming worker sessions in the sidebar (#1748)
* feat(web): allow renaming worker sessions in the sidebar

Closes #1647

Adds an inline rename UX to each worker session row in the sidebar. A
small pencil button appears on row hover; clicking it swaps the label
for an input pre-filled with the current title. Enter persists via
PATCH /api/sessions/:id, Escape cancels, and an empty value clears the
field — reverting the session to its default title.

The rename writes to the existing displayName metadata field, which is
now the highest-priority signal in getSessionTitle so a user-chosen
label always beats PR/issue titles. The session ID (ao-N) remains
canonical — only display surfaces are affected.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(web): gate displayName promotion on user-set flag

PR review flagged that promoting `displayName` to the top of
`getSessionTitle` regressed every existing session: spawn-time
auto-derived `displayName` would shadow live PR/issue titles for
sessions the user never explicitly renamed.

Adds a `displayNameUserSet` boolean flag to SessionMetadata and
DashboardSession. The dashboard fallback chain promotes `displayName`
above PR/issue titles only when this flag is true; auto-derived
spawn-time values stay at their original position (below PR/issue,
above userPrompt).

PATCH /api/sessions/:id sets `displayNameUserSet=true` when the user
types a name, and clears it when they revert. Sidebar gates its
displayName preference on the flag too, so non-renamed rows keep the
existing branch-first behavior.

Also addresses review #3 (rename-while-pending pre-fill) and #4
(double-submit guard on Enter+blur).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(web): address review feedback on session rename PR

- ProjectSidebar: gate effective displayName on displayNameUserSet so
  auto-derived spawn-time names no longer shadow live PR/issue titles
  in the sidebar (mirrors the gate already in format.ts:getSessionTitle).
  Adds a regression test.
- ProjectSidebar: drop unreachable `?? currentTitle` from startRename
  initial value — the right side of the nullish-coalescing always returns
  a string, so the fallback is already handled by the `|| currentTitle`
  on the next line.
- ProjectSidebar: reveal rename pencil on `group-focus-within` so keyboard
  users tabbing through the session links discover the affordance, not
  just pointer users.
- globals.css: change rename button + input border-radius from 3px to 0
  to match the repo's --radius-base: 0 design rule for UI controls.
- core/metadata: accept legacy "on"/"off" strings for displayNameUserSet
  in readMetadata for parity with prAutoDetect (defensive — the storage
  write path already converts to boolean via unflattenFromStringRecord).
  Adds coverage for all six accepted forms.
- web/serialize: drop dead `=== "on"` check on displayNameUserSet —
  Session.metadata is Record<string, string> and the value can only ever
  be "true" / "false" after flattenToStringRecord.

Refs #1647.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 19:41:48 +05:30
Harsh Batheja eb06a4d090
fix(web): render empty-state in sidebar when no projects configured (#1549)
* fix(web): render empty-state in sidebar when no projects configured

A fresh-install user with zero projects saw a blank sidebar and had no
way to open AddProjectModal from it. The early-return was originally
projects.length <= 1 (#381), softened to === 0 in #927, but no empty-
state UI was added at the same time.

Replace the null branch with a small ProjectSidebarEmpty sibling that
reuses the existing header (with the + button wired to AddProjectModal),
shows a one-line explainer, and renders only the ThemeToggle in the
footer (the show-killed/show-done/settings buttons are meaningless with
zero projects).

* fix(web): mark sidebar + button SVGs aria-hidden

The decorative SVG inside the labeled + buttons (empty-state and
populated sidebar) should not be announced — screen readers should rely
on the button's aria-label. Adds aria-hidden="true" to both for
consistency.

* fix(web): always mount sidebar so empty-state renders on fresh installs

Dashboard previously gated the sidebar on projects.length >= 1, leaving
ProjectSidebarEmpty unreachable. ProjectSidebar handles both cases now,
so drop the gate and add a dashboard-level test for the zero-project
path.

* fix(web): honor collapsed prop in empty sidebar branch

ProjectSidebarEmpty discarded the collapsed prop, so on a fresh install
the wrapper shrank to 44px while the inner sidebar stayed 224px and
overlapped the main content. Render a 44px-wide rail with just the +
button when collapsed, matching the populated sidebar's collapse path.
2026-05-04 20:13:11 +05:30
Harshit Singh Bhandari 7c7ffb5624
fix(web): source sidebar orchestrator from API field, not session list (#1623)
* fix(web): source sidebar orchestrator from API field, not session list

PR #1615 merged the initial implementation but missed the follow-up
fix. The merged sidebar code looks up the orchestrator inside the
`sessions` prop, but /api/sessions/route.ts strips ALL orchestrators
from that array before returning (they're exposed via a separate
`orchestrators` field on the same response). Result: the new menu
entry — and the existing icon button next to the dashboard icon —
never render for any project.

Replace the broken in-sidebar derivation with a new `orchestrators`
prop on ProjectSidebar. Each parent passes the data it already has:

  - Dashboard.tsx: passes `activeOrchestrators` (already in scope)
  - PullRequestsPage.tsx: passes `orchestratorLinks` (already in scope)
  - sessions/[id]/page.tsx: stores the `orchestrators` field already
    returned by /api/sessions and threads it through SessionPageShell
    and SessionDetail as `sidebarOrchestrators`

Tests refocused: the sidebar now just renders what the prop says, so
the live-vs-terminal selection lives in the API
(selectPreferredOrchestratorId) and is no longer the sidebar's
responsibility.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* style: fix indentation on sidebarOrchestrators prop in SessionPage

Addresses Greptile review comment on PR #1623.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor(web): use DashboardOrchestratorLink for API response type

Addresses non-blocking review feedback on PR #1623. The /api/sessions
response actually returns DashboardOrchestratorLink shape (which
includes projectName), so use that as the response type instead of
the narrower ProjectSidebarOrchestrator. The sidebar prop type stays
narrow on purpose — it's the minimum the sidebar needs to render.

Also document the "one orchestrator per project" Map invariant.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-04 00:09:43 +05:30
Harshit Singh Bhandari 3b9ba3122e
feat(web): add 'Open orchestrator' to sidebar 3-dot menu (#1615)
* feat(web): add 'Open orchestrator' to sidebar 3-dot menu

Adds a labeled menu entry above 'Project settings' that navigates to
the project's orchestrator session. The orchestrator is the most-used
session in any project, but today the only path to it is the unlabeled
icon button next to the dashboard icon - easy to miss for new users.

The entry is hidden when no live orchestrator exists (matching the
existing icon-button pattern), so the menu shrinks gracefully on
projects where 'ao start' has never run or has stopped.

Closes #1613

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor(web): drop redundant guard in orchestrator menu render

Hold the validated session in `liveOrchestrator` instead of a separate
boolean flag. TypeScript narrows automatically from the assignment, so
the render condition no longer needs `&& orchestratorSession` to satisfy
the type checker.

Addresses review feedback on #1613.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-03 20:39:57 +05:30
Ashish Huddar f674422a66
Fix orchestrator identity to use one canonical session per project (#1487)
* Add canonical orchestrator identity

* Coalesce concurrent ensureOrchestrator calls

* Fix canonical orchestrator follow-ups

* Move orchestrator id helper into web utils
2026-04-25 18:57:31 +05:30
Ashish Huddar 0538e07b65
Fix root dashboard project selection (#1480)
* style(design): strengthen dashboard hierarchy

* fix(web): prefer current repo dashboard project

* Revert "style(design): strengthen dashboard hierarchy"

This reverts commit 5db28280e1.

* Fix root dashboard project selection

* fix(web): bound session loading and reduce dashboard polling

* Fix project-name test temp path and reuse loaded config

* fix(web): clear recovered errors and restore fresh detail polling

* fix: stabilize dashboard session handling

* Fix ambiguous project-name fallback discovery

* Fix dashboard session selection regressions

* Remove unused orchestrator session imports
2026-04-24 18:35:38 +05:30
Ashish Huddar f3ce113c4c
Add multi-project storage, resolution, and project settings support (#1343)
* feat: add content-addressed project storage keys

* Add per-project resolution and hardened project routing

* Fix storage-key test isolation

* feat(web): redesign Add Project modal with Finder-native layout

* feat: multi-project support with project sidebar, settings, and improved routing

Add per-project configuration in global-config, project-aware CLI commands
(start/spawn/open/session), workspace-worktree project resolution, redesigned
ProjectSidebar with settings modal, repair flow for degraded projects, project
detail page with loading state, reload API endpoint, and comprehensive tests.

* Fix legacy config storage keys and duplicate project flow

* Fix multi-project storage migration and collision handling

* Fix merge regressions in startup and config handling

* Externalize yaml and zod from the web server bundle

* Fix session prefix matching for hashed tmux names

* Fix multi-project migration regressions

* Ignore generated worktree files in ESLint

* Fallback reload config for local-only projects

* Speed up session refresh and redirect after kill

* Use fresh session lists for dashboard polling

* fix(web): remove unused direct terminal child state

* test(web): mock router in merge conflict actions coverage

* docs: call out filesystem browse rollout requirement

* Add portfolio tests and remove unused decomposer export
2026-04-21 17:45:55 +05:30
fastestdevalive 254ecd1098
feat(web): terminal layout, mobile UX, sidebar & instant session navigation (#1278)
* feat(web): fix terminal height to fill viewport, prevent outside scrolling

* feat(web): add touch scroll, font size control, and accurate fit to DirectTerminal

* feat(web): add mobile sidebar overlay, hamburger toggle, and reconnect indicator

* feat(web): add hamburger and reconnect pill to topbar

* fix(web): use xterm instead of @xterm/xterm for terminal-touch-scroll import

Upstream uses xterm@5.3.0 (not @xterm/xterm), fix the type import accordingly.

* ci: make pnpm audit strict step non-blocking

npm's legacy audit endpoint (/npm/v1/security/audits) is returning 410 Gone
as it's being retired. Add continue-on-error until pnpm ships support for
the new bulk advisory endpoint.

* fix(web): replace TerminalLike with minimal interface compatible with xterm@5.3.0

xterm@5.3.0 does not have 'input' or 'attachCustomWheelEventHandler' methods
(those are @xterm/xterm v6 APIs). Define a minimal structural interface
matching only the members actually used in the file.

* fix(web): replace mobile back button with hamburger sidebar toggle on session page

On mobile, the session detail page now shows a hamburger button instead of a back arrow, which toggles the sidebar via SidebarContext. This provides better navigation consistency with the main dashboard.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(web): filter sidebar sessions strictly by projectId to prevent prefix collision

Sessions are now filtered to only show those whose projectId matches a configured project. This prevents sessions from different AO projects (e.g., 'ao-' and 'unl-' prefixes) from being incorrectly merged under the same project entry.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(web): add ResizeObserver and deferred fit to fix blank terminal on mount

The terminal now uses a 100ms deferred fit timeout to ensure the container has been sized before measuring. Additionally, a ResizeObserver monitors the terminal container and calls fit() whenever its size changes, ensuring the terminal refits when flex layouts settle.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(web): debounce session refresh to prevent aborting in-flight fetch calls

The useSessionEvents hook now tracks when the last fetch was started and skips scheduling a new refresh if one was started less than 500ms ago. This prevents aggressive AbortController usage that was canceling in-flight requests unnecessarily. Also avoid rescheduling if a debounce timer is already pending.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test(web): update SessionDetail mobile test for new sidebar toggle button

Update the test to expect the new "Toggle sidebar" aria-label instead of the previous "Back to dashboard" label on the mobile floating header.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(web): active session amber color, compact session meta row, tighter font controls

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(web): address review feedback — fontSize rerender, dead code, test cleanup

- DirectTerminal: remove fontSize from main init useEffect deps. A dedicated
  effect below mutates terminal.options.fontSize in place; keeping it in the
  init deps tore down and recreated the terminal (and WebSocket) on every
  stepper click, losing scrollback and flashing content.
- Remove dead ReconnectingPill component — it rendered null with a placeholder
  comment. Delete the file, import, and render site rather than leaving a shell.
- ProjectSidebar: make the done-filter consistent by using getAttentionLevel
  in the sessionsByProject collector. Previously the collector filtered by the
  narrow `status === "done"` while the render sites filtered by the broader
  `getAttentionLevel(s) === "done"`, so the project badge count could include
  merged/killed/terminated sessions that the rendered list hid.
- Drop the two gutted tests that tested JS primitives rather than the component
  (layout-height.test.tsx, DirectTerminal.test.tsx). They provided false
  coverage. Update ProjectSidebar.test.tsx for the new anchor-based session
  rows (click behavior now queries role="link", and clicking the project
  toggle expands rather than navigates — the separate dashboard button handles
  navigation).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(web): make SessionDetail topbar tests pass after redesign

Reconcile the rebased-upstream tests with our redesigned SessionDetail:

Component:
- Show session.id in the topbar next to the headline. The ID is a stable
  identifier (useful for copy/paste) while the headline changes with PR/
  issue titles.
- Gate the topbar Orchestrator link on `!isOrchestrator` so we don't link
  the orchestrator page to itself.
- Convert the PR popover toggle from a <button> to an <a href={pr.url}>.
  Plain click still toggles the popover; ctrl/cmd-click opens the PR on
  GitHub in a new tab. aria-label="PR #N" keeps the accessible name stable
  regardless of the rendered chevron.

Tests:
- Mobile tests scope orchestrator link queries to the topbar via
  within(getByRole("banner")) because MobileBottomNav also has an
  "Orchestrator" entry.
- Desktop test opens the PR popover before asserting the PR detail
  contents (blocker chips, file count, unresolved comments) — they now
  live inside the popover rather than inline.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(web): restore behavioral tests + remove dead mobileOpen prop

Adds missing behavioral coverage the reviewer flagged on Dashboard.mobile.test.tsx:

- Termination confirmation flow: clicking the kill button once enters a
  confirming state (aria-label becomes "Confirm terminate session") without
  firing the kill request; a second click POSTs to /api/sessions/:id/kill.
- CI check chip rendering: SessionCard renders passing CI check names as chips
  when the session has an enriched PR.

Removes the dead `mobileOpen` prop from ProjectSidebar:

- It was declared but immediately aliased as `_mobileOpen` and never used —
  the actual mobile overlay state is driven by the `sidebar-wrapper--mobile-open`
  class on the parent wrapper div in Dashboard/SessionDetail.
- PullRequestsPage was passing it too but never wired the wrapper class, so
  its hamburger buttons were silently no-ops. Wrapped its sidebar in the same
  sidebar-wrapper + backdrop pattern so the buckled hamburgers now actually
  open the sidebar overlay on mobile.

Not in scope for this round (per reviewer): attention bucket filter /
MobileBottomNav on Dashboard — Dashboard doesn't implement those features,
so there's no behavior to assert. Those would need a feature add, not a test.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(core): listCached stale-data bug + drop inline styles in DirectTerminal

Addresses the reviewer's critical and minor feedback on PR #1278.

Critical — listCached was stale across mutation paths

  listCached() had a 35s TTL, invalidated only by spawn() and kill().
  Every other metadata-writing path (claimPR, restore, send, remap,
  cleanup, lifecycle-manager's direct updateMetadata calls for PR
  detection and state transitions) left stale data visible to
  /api/sessions for up to 35s.

  Fix:
  - Wrap updateMetadata / writeMetadata / deleteMetadata inside
    createSessionManager() so every in-file mutation auto-invalidates
    the cache. The raw imports are aliased as _rawXxx and only used
    by the wrappers. No call site needs to remember to invalidate.
  - Expose invalidateCache() on the SessionManager interface for
    callers that write metadata outside this module.
  - lifecycle-manager.ts: call sessionManager.invalidateCache() after
    its two direct updateMetadata sites (PR detection, state update)
    so polling-driven mutations are visible on the next poll.
  - Move the _cache / invalidateCache declarations to the top of the
    closure so the mutation wrappers can reference them.
  - Update createMockSessionManager + api-routes.test.ts mocks to
    satisfy the expanded interface.
  - New regression test: external mutation + sm.invalidateCache() →
    next listCached() re-reads disk.

C-02 — inline styles on the terminal-container div

  DirectTerminal.tsx was using style={{ overflow, display,
  flexDirection, flex, minHeight }} — all static values moved to
  Tailwind utilities: "w-full p-1.5 flex flex-col flex-1 min-h-0
  overflow-hidden".

Not addressed in this commit (noted as out-of-scope refactor):

  C-04 400-line cap on DirectTerminal.tsx. Splitting fontSizeControls
  and touch-scroll wiring into sub-components is worthwhile but
  mechanical — separate PR.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 00:15:57 +05:30
Ashish Huddar 237c99f76e
feat(web): collapse attention zones to 4 with 5-zone feature flag (#1202)
* feat(web): collapse attention zones to 4 with feature flag for 5 (#1201)

Simplify the dashboard to a 4-zone kanban by default (WORKING,
PENDING, ACTION, READY), merging the former REVIEW + RESPOND
columns into a single ACTION zone that just asks "does the human
need to do something?". The card-level badges still surface the
underlying granular state (ci_failed, needs_input, changes_requested),
so nothing is lost — it just moves off the column header.

Add a `dashboard.attentionZones` config (defaults to "simple") that
opts back into the original 5-zone layout for power users. The
function-level `getAttentionLevel(session, mode)` still defaults to
"detailed" so card-level call sites (SessionCard, BottomSheet,
ProjectSidebar, etc.) keep their granular behavior untouched. Only
the Dashboard kanban and SSE server routes read the config and pass
the mode.

Closes #1201

* fix(web): address PR review feedback on attention zones (#1201)

- Drop .strict() from DashboardConfigSchema (Cursor Bugbot):
  matches other schemas in config.ts so typos in the dashboard
  block gracefully default instead of crashing the whole loader.

- DynamicFavicon: keep `action` at yellow, not red (Codex P2).
  In simple mode, `action` collapses respond + review, which
  means it necessarily catches routine review work (ci_failed,
  changes_requested) that was previously yellow. Escalating to
  red would make every typical PR scream critical. Only the
  detailed-mode `respond` bucket still triggers red.

- useSessionEvents default → "detailed" (Codex P3). The hook
  default now matches getAttentionLevel's default so callers
  that seed with `getAttentionLevel(s)` (no mode) — notably
  PullRequestsPage — stay consistent with their seed after
  the first live SSE/refresh snapshot. Dashboard explicitly
  passes the config's attentionZones to opt into simple mode.

- Expand mobile action chip (Codex P3). When a session
  collapses to `action`, derive the most specific underlying
  cause (needs input, stuck, errored, ci failed, changes,
  waiting, conflicts) instead of falling through to the
  generic "action" label, so mobile users keep the reason to
  intervene.

- DynamicFavicon tests: add cases for `action` staying yellow
  and `respond` still escalating when both are present.

* fix(web): thread attentionZones config through PullRequestsPage (#1201)

Cursor Bugbot caught a deeper version of the same inconsistency
my previous fix didn't fully resolve: the server SSE route uses
`config.dashboard?.attentionZones ?? "simple"`, but PullRequestsPage
was seeding `initialAttentionLevels` and calling useSessionEvents
without passing any mode (falling back to the hook's "detailed"
default). Result: snapshots from the server arrived as "action"/
"merge", then scheduleRefresh recomputed them client-side as
"respond"/"review", and the favicon oscillated between yellow
and red on every refresh cycle.

Fix: plumb pageData.attentionZones through prs/page.tsx into
PullRequestsPage, use it in both the seed and useSessionEvents.
Now the seed, the server SSE, and the client refresh all use the
same mode — stable, no flicker.

* fix(web): accurate action labels for crashed agents + yellow tone (#1201)

Two Cursor Bugbot findings on the action-zone collapse work:

1. [Medium] Mobile chip mislabeled crashed agents as "needs input".
   When an agent's activity is `exited` and the session is in the
   action bucket, the agent has crashed — sending it a message
   won't help, the user needs to restart. Split the label: exited
   → "crashed", blocked → "blocked".

2. [Low] ProjectMetric for Action used tone="error" (red), which
   contradicts the favicon fix's rationale. The action bucket
   catches routine review work (ci_failed, changes_requested)
   that was previously orange/yellow severity; screaming red in
   the project overview grid would have the same cry-wolf problem
   as the favicon did. Use tone="orange" (the less severe of the
   two merged buckets) to match.

* refactor(web): make attentionZones required in useSessionEvents (#1201)

Cursor Bugbot caught that the hook default of "detailed" mismatches
the server SSE route default of "simple" — a latent footgun for any
future caller that forgets to pass the mode explicitly. The exact
oscillation bug I already fixed once for PullRequestsPage would
silently come back on the next page that uses this hook.

Real fix: refactor the hook to take an options object and make
`attentionZones` required with no default. TypeScript now enforces
that every caller explicitly passes the mode the server is using.
You literally cannot call useSessionEvents without supplying it.

Also cleaner: the hook had 6 positional parameters, which is past
the point where an options object helps readability. Dashboard and
PullRequestsPage call sites now read as self-documenting.

* fix(web): address human code review on attention zones (#1201)

Review from i-trytoohard on commit 8d27db79:

1. [Medium] `ZoneCounts` in sessions/[id]/page.tsx had an `action`
   field that was always 0 — the page always computes in detailed
   mode (getAttentionLevel with no mode = detailed), and the
   consumer (`OrchestratorZones` in SessionDetail) never reads
   an `action` field either. Removed the dead field, added a
   `continue` guard for the never-reached "action" case so
   TypeScript narrows the index correctly, and documented the
   intent in a header comment.

2. [Medium] The simple-mode action chip had 10+ sub-conditions
   with no tests. Extracted the label logic into a pure
   `getActionChipLabel(session)` helper and added 16 unit tests
   covering every branch: status-based signals (needs_input,
   stuck, errored, ci_failed, changes_requested), activity-based
   (waiting_input, exited→crashed, blocked), PR-based (failing
   CI, changes_requested, conflicts), precedence between signal
   classes, and the generic fallback.

3. [Low] `LEVEL_LABELS.action` in ProjectSidebar is dead code
   today (sidebar uses detailed mode). Added a comment explaining
   why the entry still exists (TypeScript exhaustiveness on
   `Record<AttentionLevel, string>` + forward-compat).

* fix(web): address inline review on attention zones (#1201)

- Reorder getActionChipLabel to mirror getDetailedAttentionLevel: respond-class
  activity (waiting_input/exited/blocked) now outranks review-class status
  (ci_failed/changes_requested). A crashed agent with changes_requested no
  longer reads as "changes" and hides the crash.
- Paint data-level="action" dot with --color-accent-orange to match the
  favicon's yellow-severity treatment of the collapsed bucket.
- Tighten attentionLevel on the mux boundary (mux-protocol, mux-websocket,
  useSessionEvents) from string to AttentionLevel so invalid values like "none"
  no longer launder through into DynamicFavicon's count.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-17 22:24:25 +05:30
Ashish Huddar 1e415ab6cc
fix: prevent sidebar flash on session detail pages (#1232)
* fix: prevent sidebar empty-state flash on session pages (#1230)

* fix: reuse sidebar data across session navigation (#1230)

* fix: revalidate cached sidebar data on session pages (#1230)

* fix: harden session sidebar live updates (#1230)

* fix: remove sidebarSessions from mux effect deps

Read from the module-level cachedSidebarSessions cache instead so the
mux effect only re-fires when new mux data arrives, not on every
sidebar state change. Addresses review feedback on #1230.

* fix: address PR #1232 review comments on sidebar session handling

- Guard against out-of-order sidebar fetches with a per-invocation token
  so concurrent pollers/retries don't overwrite fresh responses with stale ones.
- Gate mux overlay on `mux.status === "connected"`; clear pending patches on
  disconnect so a stale WS snapshot doesn't shadow fresher REST data.
- Surface a stale-data banner in the sidebar when a refresh fails but cached
  sessions are still rendered, so users see the failure instead of silently
  viewing outdated state.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-17 16:49:13 +05:30
Ashish Huddar af2af115bd
style(design): design review fixes + fresh Warm Terminal design system (#927)
* style(design): FINDING-001 — add prefers-reduced-motion support

All animations and transitions are disabled when the user's system
requests reduced motion, per DESIGN.md accessibility requirements.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* style(design): FINDING-003 — remove concurrent breathe animations

Status pills had two animations: a breathe animation on the pill
and a dot-pulse on the child dot. DESIGN.md says "one animation per
element, one purpose" and "keep dot pulse, remove border heartbeat."

Removed all three breathe keyframes, kept dot-pulse only.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* style(design): FINDING-004 — fix dashboard title weight and tracking

DESIGN.md specifies display headings at weight 680 and letter-spacing
-0.035em. The dashboard title was using 600 / -0.05em.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* style(design): FINDING-005 — fix detail-card text to blue-tinted graphite

Detail cards overrode text-secondary and text-tertiary with neutral
grays (#9898a0, #5c5c66). DESIGN.md specifies blue-tinted graphite
palette (#a5afc4, #6f7c94) for dark mode text.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* style(design): FINDING-008 — add text-wrap: balance on headings

Dashboard title and kanban column titles now use text-wrap: balance
for more even line breaks on narrow viewports.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* style(design): FINDING-006/009 — fix section label semantics and spacing

Changed "Attention Board" from <h2> to <div role="heading"> since it's
styled as a 12px uppercase label, not a heading. Also fixed letter-spacing
from 0.16em to 0.06em per DESIGN.md UI/Labels spec.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* style(design): FINDING-007 — contextual empty state messages

Empty kanban columns now show context-specific messages instead of
generic "No sessions" text. Each column's empty state reflects its
purpose: "No agents need your input" (Respond), "No code waiting
for review" (Review), etc.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(design): fresh design system — Warm Terminal

Complete redesign from Industrial Precision (blue-tinted) to Warm Terminal
(brown-tinted). Key changes:

- Warm charcoal surfaces (#121110, #1a1918, #222120) replace blue-gray
- Cream text (#f0ece8) replaces blue-white (#eef3ff)
- Warm periwinkle accent (#8b9cf7) replaces cool blue (#5B7EF8)
- Berkeley Mono for display headlines (mono cohesion)
- Added: Accessibility section (44px touch targets, WCAG AA, focus-visible)
- Added: Component anatomy (button states, card structure, input fields)
- Added: Light mode design rationale (warm parchment, not clinical white)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(design): swap Berkeley Mono for JetBrains Mono (free)

Berkeley Mono is a paid font ($75). JetBrains Mono is free, open source,
already loaded in the project, and the mono-for-headlines concept works
the same way with it.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(design): fix light mode contrast failures

Light mode text-tertiary #a8a29e failed WCAG AA at 2.5:1 on white.
Darkened to #736e6b (5.0:1). Light mode accent #6b73c4 was borderline
at 4.3:1, darkened to #5c64b5 (5.3:1). All pairs now pass AA.

Added verified contrast ratios for both modes to accessibility section.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: gitignore .gstack/ directory

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(design): add design audit report and screenshots

Design review audit report with before/after screenshots for all
dashboard pages (kanban, session detail, PRs) across desktop, tablet,
and mobile viewports in both light and dark mode.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(design): address PR review comments

- Fix mobile test expecting removed "No sessions" text. The merge zone
  emptyMessage is now "Nothing cleared to land yet." (Bugbot comment #1)
- Remove no-op .dark .detail-card override that duplicated global dark
  values after FINDING-005 fix aligned them (Bugbot comment #2)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: gitignore .gstack-report/ and remove from tracking

The .gstack-report/ directory contains local audit artifacts with
filesystem paths. Should not be tracked in the repository.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(design): align dashboard title CSS to new DESIGN.md spec

Dashboard title was using old Geist Sans values (weight 680, -0.035em).
New spec is JetBrains Mono, weight 500, letter-spacing -0.02em.
Added font-family: var(--font-mono) to match.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: use native h2 element for Attention Board section heading

Replace ARIA role="heading" div with semantic h2 per ARIA first rule — native elements are preferred over ARIA roles for actual headings.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: pre-landing review fixes — a11y, dead code, test coverage

- Add aria-controls + id to accordion button/body pair in AttentionZone
- Wrap empty-state messages in aria-live="polite" regions for AT announcements
- Remove dead message prop and isDefault from EmptyState (Skeleton.tsx)
- Add parameterized test covering all 6 zone-specific empty messages

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: revert aria-live on empty states — causes false AT announcements

Codex review identified that role="status" aria-live on static empty-state
text causes burst announcements on page load (all empty columns fire) and
announces in collapsed mobile sections that aren't visible. Empty states are
static text, not dynamic transitions. The aria-controls fix is kept.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: bump version and changelog (v0.0.1.0)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: remove .gstack-report/ from .gitignore

* chore: remove VERSION and CHANGELOG (not used in this project)

* style(design): warm terminal color migration + inline style removal

Migrate all CSS tokens from cool blue-tinted graphite to warm
brown-tinted terminal aesthetic per DESIGN.md spec. Replace inline
style color mappings in ActivityDot, AttentionZone, Dashboard,
ProjectSidebar, and SessionCard with data-attribute CSS selectors.
Fix duplicate className bug on SessionCard done-title element.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: pre-landing review fixes — activity dot fallback + review stat color

Add base CSS fallback for activity-dot, activity-pill, and
activity-pill__text so null/unknown activity states render visibly
(gray) instead of invisible. Fix review stat card to use accent-orange
(matching kanban/sidebar/mobile review indicators) instead of cyan.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore: checkpoint current design branch state

* design changes

* feat(web): redesign session detail page — compact PR card, identity strip, layout reorder

- Redesign SessionTopStrip with simplified breadcrumbs, action buttons (Message/Kill)
- Replace stacked PR card with compact inline layout: title row + blocker/CI chips + collapsible comments
- Move PR card above terminal for better information hierarchy
- Replace vertical IssuesList with inline buildBlockerChips helper
- Add ~200 lines of new CSS classes for compact PR card design system
- Add changedFiles field to DashboardPR type

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(web): design system tokens, sidebar redesign, and component primitives

- Align all color tokens (status, bg, border, text) across three HTML mockups
- Rewrite ProjectSidebar to match finalized.html: rotation chevron, session status text, border-bottom project separators, 224px width
- Add packages/web/DESIGN.md: agent-readable reference for tokens, typography, component patterns, anti-patterns
- Add Badge.tsx: generic badge/chip/pill primitive with status/outline/default variants
- Add Button.tsx: ghost/primary/danger button primitive
- Update CLAUDE.md to reference DESIGN.md as required pre-read for web UI work

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* style(design): FINDING-001 — card border-radius 0 → 6px to match mockup

* style(design): FINDING-002 — column border-radius 0 → 7px, border subtle to match mockup

* style(design): FINDING-003 — column header mono font, 500 weight, muted color to match mockup

* style(design): FINDING-004/005/006 — fix accent-blue/yellow/purple tokens to match mockup

* style(design): FINDING-007 — add --color-bg-card token (light #fff, dark #1c1b19)

* Refine dashboard design system and remove fixture flow

* Fix respond status colors in dashboard indicators

* Align tests with updated dashboard and metadata behavior

* fix(core): register notifier aliases consistently

* chore(web): drop uncovered showcase routes

* Add desktop PullRequestsPage coverage tests

* Remove generated coverage artifact

* Consolidate web design guidance into the root design system

* Fix working and ready status color tokens

* Fix sidebar collapse and inline kill confirmation

* fix(qa): ISSUE-001 - show all mobile filter chips

* Restore full title contrast in session cards

* Fix review feedback in dashboard state styling

* style: implement mobile responsive designs (feed, terminal-first, dense PRs)

Dashboard: replace accordion with urgency-sorted priority feed, horizontal scroll filter pills.
Session Detail: terminal-first layout with floating header, status pill, PR bottom sheet.
PRs: dense rows with CI dots, grouped sections, muted merged/closed rows.
Update tests to match new mobile layouts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Fix mobile terminal padding with PR sheet layout

* Polish mobile feed and session detail styling

* Align mobile dashboard layouts with gstack designs

* Fix mobile terminal actions and PR review labels

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 16:30:03 +05:30
Prateek 550bdf589c fix: rename @composio refs in tsx files and regenerate lockfile 2026-04-09 15:59:33 +00:00
Harsh Batheja 34bc5bb2d5
feat: support multiple concurrent orchestrators with isolated worktrees (#870)
* feat: support multiple concurrent orchestrators with isolated worktrees

- Each orchestrator session gets a numbered ID ({prefix}-orchestrator-N)
  and an isolated git worktree, replacing the single shared orchestrator
- reserveNextOrchestratorIdentity atomically reserves the next available
  number and detects conflicting project prefix configurations early
- isOrchestratorSession / isOrchestratorSessionName accept optional
  allSessionPrefixes for cross-project false-positive prevention
- getProjectPause and resolveGlobalPause track the longest active pause
  across all concurrent orchestrators instead of returning the first found
- cleanupWorktreeAndMetadata helper used consistently on all failure paths
  including post-launch; system prompt file included in cleanup
- pollBacklog, status.ts, ProjectSidebar, sessions page, global-pause,
  project-utils, serialize, and sessions API route all pass
  allSessionPrefixes to isOrchestratorSession
- Web sessions/[id]/page.tsx fetches project prefix map and passes it
  through prefixByProjectRef to avoid stale closure in fetchProjectSessions

* fix: seed sseAttentionLevels from fresh sessions on full refresh

When scheduleRefresh dispatches a reset action, derive sseAttentionLevels
from the freshly fetched sessions using getAttentionLevel. This clears
phantom entries for removed sessions and seeds correct levels for new
sessions, preventing stale favicon color and attention counts until the
next SSE snapshot.

* fix: merge duplicate @/lib/types imports into single import statement

Combining the separate import type and value import from @/lib/types
into one import to satisfy the no-duplicate-imports lint rule.
2026-04-04 12:30:51 +05:30
Ashish Huddar 402dc1f861 fix(web): fix mobile sidebar specificity and toggle close behavior
- Increase .project-sidebar--mobile-open specificity to (0,2,0) so it
  overrides .project-sidebar.project-sidebar--collapsed transform
- Make "Hide sidebar" and "Show project sidebar" buttons also call
  onMobileClose to dismiss the mobile drawer when toggling collapse

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 08:04:47 +05:30
Ashish Huddar a4a86594c7 fix(web): add mobile backdrop to collapsed sidebar branch
The collapsed sidebar early-return path was missing the backdrop overlay
and close mechanism that the expanded branch had, making it impossible
to dismiss the mobile drawer by tapping outside.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 01:00:17 +05:30
Ashish Huddar 8fc778cf30 chore(web): add mobile-responsive layout for dashboard and session views
Add proper mobile breakpoints (768px, 480px) with structural layout changes:

- Sidebar: auto-hide on mobile with hamburger toggle and overlay drawer
- Dashboard hero: stack stats cards and controls vertically on mobile
- Kanban board: stack columns vertically instead of horizontal scroll
- Session cards: flexible height with 44px minimum touch targets
- Session detail: responsive header metadata and full-width terminal
- Global: phone-specific breakpoint (480px) for single-column layouts

Desktop layout remains completely unchanged.

Closes #633

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 00:55:37 +05:30
Ashish Huddar 6b7a8bd20c chore(web): design audit fixes — rename column, refine styles, fix terminal
- Rename "Merge Ready" kanban column to "Ready"
- Refine card design system and globals.css theme tokens
- Fix terminal scrollbar and theme handling in DirectTerminal
- Update SessionDetail layout
- Update tests to match component changes
- Add .gstack/ to .gitignore

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 09:58:24 +05:30
Ashish Huddar 13dcbc8716 fix(web): move early return before hooks in ProjectSidebar
Fixes React Rules of Hooks violation where useMemo ran after a
conditional early return, causing inconsistent hook call counts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 15:34:57 +05:30
Ashish Huddar 038620ffd1 fix(web): pre-landing review fixes
- Remove unnecessary !important on light mode background
- Add useMemo to ProjectSidebar session grouping (O(n*m) → O(n))
- Filter done sessions from sidebar nav (prevents empty board)
- Fix stale session param showing empty columns instead of empty state
- Add dark class to server-rendered HTML to prevent FOUC

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-21 13:17:06 +05:30
Ashish Huddar f24abc2145 Add collapsible project sidebar with bottom toggle 2026-03-21 11:49:39 +05:30
Ashish Huddar f9feb4bbf8 chore(web): kanban dashboard redesign — Linear-inspired design system
Redesign the kanban dashboard with a Linear-inspired aesthetic:
- New design tokens (neutral charcoal darks, white-alpha borders, muted status colors, indigo accent)
- Tighter spacing, visible column backgrounds, dark mode weight reduction
- Redesigned SessionCard, ProjectSidebar, and AttentionZone components
- Removed list view toggle, reordered columns (respond → merge → done)
- Added ThemeToggle, Skeleton/EmptyState components, and showcase page
- Exported CI_STATUS and getSizeLabel for component consumption

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 11:01:31 +05:30
Harsh Batheja c7c04c14df
feat(web): Project-scoped dashboard with sidebar navigation (#381)
* feat(web): add project-based dashboard architecture

- Add project query parameter to API routes
- Filter sessions by project in both SSR and SSE
- Update useSessionEvents hook to accept project param
- Update Dashboard and pass project to hook
- Add unit tests for project filtering
- Add architecture spec document

Implements project-based architecture as defined in docs/specs/project-based-dashboard-architecture.md:
- GET /api/sessions?project=X returns sessions for project X
- GET /api/events?project=X streams only sessions for project X
- Dashboard uses project filter from config
- SSE URL includes project param when provided
- Project matching uses projectId and sessionPrefix
- Full backward compatibility maintained (no param = all sessions)

* fix: remove duplicate export default in page.tsx

* fix(web): clean project-scoped dashboard verification

* fix(web): scope dashboard using project id

* fix(web): address Bugbot findings in project-scoped dashboard

- Consolidate triplicated matchesProject into shared lib/project-utils.ts
- Fix Dashboard to receive both projectId (for SSE filtering) and projectName (for display)
- Remove inline matchesProject definitions from page.tsx, sessions/route.ts, events/route.ts

* fix(web): resolve Bugbot issues in project-scoped dashboard

- Add ?project=all query param support in SSR page to show all sessions
  (previously getPrimaryProjectId() always returned non-empty, making
  else branches unreachable)
- Exclude orchestrator sessions from SSE stream to match SSR/API behavior
  (orchestrator sessions get their own button, not a card)
- Add tests for SSE stream orchestrator exclusion and project filtering

Addresses Bugbot issues:
- #2903595986: Dead else branches when project filter always applied
- #2903595995: SSE stream includes orchestrator sessions unlike SSR/API

* feat(web): add project navigation sidebar

Add visible left sidebar with project navigation for multi-project setups:
- ProjectSidebar component with active state styling
- /api/projects endpoint to fetch configured projects
- getAllProjects() helper in project-name.ts
- Sidebar appears only when 2+ projects configured
- Click navigation updates ?project= query param
- Active project highlighted with accent color

Tests:
- ProjectSidebar component tests (8 tests)
- API routes tests with proper mocking
- All 386 web tests passing

Manual test steps:
1. Configure 2+ projects in agent-orchestrator.yaml
2. Start dashboard - sidebar should appear on left
3. Click different projects - URL updates, sessions filter
4. Click "All Projects" - shows all sessions across projects
5. Active project highlighted in sidebar

* fix(web): remove duplicate import in test file

* fix(web): consolidate ProjectInfo type to shared source

Remove duplicated ProjectInfo interface definitions from Dashboard.tsx and
ProjectSidebar.tsx. Both now import the type from @/lib/project-name where it is exported as the single source of truth.

This addresses Bugbot issue #2906835895: Triplicated ProjectInfo type instead of shared import.

* fix(web): integrate globalPause state from main

* fix(web): consolidate duplicate @/lib/types import

* feat(web): add project-based dashboard architecture

- Add project query parameter to API routes
- Filter sessions by project in both SSR and SSE
- Update useSessionEvents hook to accept project param
- Update Dashboard and pass project to hook
- Add unit tests for project filtering
- Add architecture spec document

Implements project-based architecture as defined in docs/specs/project-based-dashboard-architecture.md:
- GET /api/sessions?project=X returns sessions for project X
- GET /api/events?project=X streams only sessions for project X
- Dashboard uses project filter from config
- SSE URL includes project param when provided
- Project matching uses projectId and sessionPrefix
- Full backward compatibility maintained (no param = all sessions)

* fix: remove duplicate export default in page.tsx

* fix(web): clean project-scoped dashboard verification

* fix(web): scope dashboard using project id

* fix(web): address Bugbot findings in project-scoped dashboard

- Consolidate triplicated matchesProject into shared lib/project-utils.ts
- Fix Dashboard to receive both projectId (for SSE filtering) and projectName (for display)
- Remove inline matchesProject definitions from page.tsx, sessions/route.ts, events/route.ts

* fix(web): resolve Bugbot issues in project-scoped dashboard

- Add ?project=all query param support in SSR page to show all sessions
  (previously getPrimaryProjectId() always returned non-empty, making
  else branches unreachable)
- Exclude orchestrator sessions from SSE stream to match SSR/API behavior
  (orchestrator sessions get their own button, not a card)
- Add tests for SSE stream orchestrator exclusion and project filtering

Addresses Bugbot issues:
- #2903595986: Dead else branches when project filter always applied
- #2903595995: SSE stream includes orchestrator sessions unlike SSR/API

* feat(web): add project navigation sidebar

Add visible left sidebar with project navigation for multi-project setups:
- ProjectSidebar component with active state styling
- /api/projects endpoint to fetch configured projects
- getAllProjects() helper in project-name.ts
- Sidebar appears only when 2+ projects configured
- Click navigation updates ?project= query param
- Active project highlighted with accent color

Tests:
- ProjectSidebar component tests (8 tests)
- API routes tests with proper mocking
- All 386 web tests passing

Manual test steps:
1. Configure 2+ projects in agent-orchestrator.yaml
2. Start dashboard - sidebar should appear on left
3. Click different projects - URL updates, sessions filter
4. Click "All Projects" - shows all sessions across projects
5. Active project highlighted in sidebar

* fix(web): remove duplicate import in test file

* fix(web): consolidate ProjectInfo type to shared source

Remove duplicated ProjectInfo interface definitions from Dashboard.tsx and
ProjectSidebar.tsx. Both now import the type from @/lib/project-name where it is exported as the single source of truth.

This addresses Bugbot issue #2906835895: Triplicated ProjectInfo type instead of shared import.

* fix(web): integrate globalPause state from main

* fix(web): consolidate duplicate @/lib/types import

* fix(web): restore global pause state and membership refresh

* fix(web): satisfy lint in project-scoped page defaults

* fix(web): remove dead global pause reducer action

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* fix(web): restore global pause resume time

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* chore(web): retrigger bugbot after thread reset

* refactor(web): centralize project session filtering helpers

* fix(web): reset pause banner dismissal on new pause

* fix(web): remove useless orchestrator assignment

* fix(web): derive header stats from live session state

* fix(web): restore project name in dashboard header

* fix(web): restore backlog poller startup in events stream

* refactor(web): keep project-utils helpers internal

* chore: retrigger bugbot evaluation

* chore: retrigger stuck bugbot check

* fix: address latest bugbot findings for dashboard events

* test(web): add dashboard bugbot regression coverage

* refactor(web): simplify projectId selection in dashboard props

* fix(web): derive selected project name from project filter

* refactor(web): initialize dashboard defaults before service load

* fix(web): satisfy lint in dashboard page error path
2026-03-11 09:22:37 +05:30