fix(dashboard): first paint from local data, never block on GitHub (#65) - #70
Merged
Merged
Conversation
The team dashboard's roster blocked first paint on GitHub-backed PR queries whose gh/git spawns cost 10-35s on slow-DNS networks. Split the roster so the local half (session activity/stats from JSONL on disk) paints immediately and the PR column hydrates when the slow half arrives. server-core (team-dashboard.ts): - buildRoster is now LOCAL ONLY (activity + stats); pr is null and status comes from activity alone, so an entry is never "blocked" pre-overlay. - New buildRosterGitHub returns a per-workspace RosterPROverlay (PR + checks), gated by an env delay hook (PAPYRUS_DASHBOARD_GH_DELAY_MS) so the acceptance test can simulate a 30s-slow GitHub without touching the shared github-team module. - New pure applyRosterOverlay merges the overlay onto the local roster; deriveRosterStatus precedence unchanged (failing PR => blocked only once the overlay is present; missing overlay => activity status). routers: mirror a rosterGitHub procedure in both the desktop (publicProcedure) and server (authedProcedure) teamDashboard routers. client: useTeamDashboard fires roster (5s) + rosterGitHub (30s) independently, renders RosterHero from the local query, then merges the PR overlay (blocked wins over the live pane overlay). AgentCard reserves the PR slot while the overlay loads so hydration causes no layout shift. ActivityRail/WorkBoard already render per-panel placeholders and never gate mount (verified — no change needed). Tests: extend team-dashboard.test.ts for applyRosterOverlay precedence and the delay-hook env parsing (10/10 pass). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #65
Problem
The team dashboard's first paint blocked on GitHub-backed PR queries.
buildRosterawaitedreadLatestSessionActivity+readLatestSessionStats+fetchGitHubPRStatustogether per workspace, so the whole roster (including live status dots) waited on the 10-35sgh/gitspawns that dominate on slow-DNS networks. The local half (session activity/stats from JSONL on disk) is fast and shouldn't wait.Change
Split the roster into a fast local half and a slow GitHub overlay.
server-core (
packages/server-core/src/team-dashboard.ts)buildRosteris now local only — activity + stats.pris alwaysnulland status comes from activity alone (deriveRosterStatus(status, null)), so an entry is never "blocked" before the overlay arrives.buildRosterGitHub(workspaces): RosterPROverlay[]— the slow half, one PR overlay per workspace fromfetchGitHubPRStatus. Never throws; a per-workspace failure degrades that entry topr: null.applyRosterOverlay(roster, overlay)— merges the overlay onto the local roster, reusingderiveRosterStatusso a failing PR surfaces asblockedonly once the overlay is present; a missing overlay row leaves the entry exactly as built. This is the canonical, unit-tested precedence seam the client mirrors.deriveRosterStatusprecedence unchanged (its first param was widened to the roster-status union so the overlay merge can reuse it — "blocked" still only comes fromchecksStatus === "failure").rosterGitHubDelayMs()readsPAPYRUS_DASHBOARD_GH_DELAY_MS; when set,buildRosterGitHubsleeps that long before itsghcalls. This simulates a 30s-slow GitHub without touching the sharedgithub-team.tsmodule.routers — mirrored a
rosterGitHubprocedure in both, matching the existingrostershape:apps/desktop/src/lib/trpc/routers/team-dashboard.ts(publicProcedure)apps/server/src/routers/team-dashboard.ts(authedProcedure)client (
.../team-dashboard/)useTeamDashboardfiresroster(5s poll) androsterGitHub(30s poll) independently. RosterHero renders from the local query immediately; the PR overlay is merged in when it resolves. Merge order: GitHub overlay first (a failing PR →blocked), then the live pane-status overlay — server/GitHubblockedstill always wins.AgentCardreserves the PR slot with a placeholder while the overlay is loading (prPending), so hydration causes no layout shift.ActivityRail/WorkBoardalready render per-panel placeholders and never gateTeamDashboardmount — verified, no change needed.Verification
bun testinpackages/server-coreonteam-dashboard.test.ts: 10/10 pass. Extended withapplyRosterOverlayprecedence cases (missing overlay never blocks, failing overlay blocks, null-PR overlay, workspaceId matching) androsterGitHubDelayMsenv parsing.tsc --noEmitinapps/serverandapps/desktop: both clean. Confirmed the desktop@papyrus/server-coresymlink resolves to this worktree's server-core, so the router typecheck validated against the newbuildRosterGitHubexport (not a stale copy).packages/server-coresuite has 11 pre-existing failures in unrelated suites (agent-mail, agent-scaffold, agent-repo, changes,github-teamsnapshot, terminal-host) — all spawn external binaries (claude/gh/git/node-pty) and time out at ~5s in this sandbox (nativenode-ptybindings aren't built here). None touch team-dashboard; all were untouched by this PR.Delayed-GitHub acceptance check
The env-gated delay is verified at the server-core boundary by
rosterGitHubDelayMsunit tests. I could not drive the full Electron app in this environment, so the end-to-end visual check is documented here for a manual run:PAPYRUS_DASHBOARD_GH_DELAY_MS=30000.rosterquery), each showing a reserved/placeholder PR slot; ~30s later the PR badges hydrate in with no layout shift. No agent shows "blocked" until its PR overlay arrives.🤖 Generated with Claude Code