Keep sidebar decision counts fresh across repositories and reconnects
Review of #378 (#345) left four findings: counts in other sidebar repositories never refreshed, a reconnecting viewer kept counts it missed, migration 016 counted zero for sidecars containing NUL or unpaired surrogates, and the smoke test still matched the bare project name. - A socket now sends session:decisions-watch with the repositories its sidebar shows (at most 50, each with at most 500 loaded document IDs). The server rechecks GitHub read access for each one through the admission repository check, requires the resolved node ID to match, subscribes the readable repositories to their decision topics, refuses the rest, and unsubscribes repositories that leave the list, lose access on the periodic recheck, or outlive the socket. - Each accepted watch is followed by a session:decisions-snapshot per repository, ordered with that repository's count frames, so a reconnected or newly subscribed socket reconciles every loaded row and project total. - channel_state.unanswered_decisions is no longer a generated column. The adapter writes it with every sidecar, and migration 016 backfills it in JavaScript, because JSONB rejects the escaped NUL and lone surrogates that sidecar text may contain. Parity tests commit both cases through the PostgreSQL adapter and run the backfill. - The smoke test accepts the count suffix on the score disclosure. Assistant-model: Claude Opus 5.5 (fast) Assistant-workflow: issue-to-pr Assistant-duration: 1h20m converged, estimated 1h30m Assistant-verification: bun run fix passed: dprint fmt and oxlint --fix, 10 existing warnings, 0 errors; changes inspected Assistant-verification: bun test failed: 4060 pass, 3 skip, 29 fail, all in scripts/pr-maintenance because this host requires commit signing; that suite passes 166/166 with GIT_CONFIG_GLOBAL=/dev/null Assistant-verification: bun unit passed: decision-watch (bounds, malformed lists, node ID mismatch, refusal, list diff, superseded requests, revocation recheck, release), snapshot service, catch-up and watch-list client helpers, Unicode fixtures against the migration counter Assistant-verification: bun run types passed: every workspace package and e2e Assistant-verification: bun run ci passed: dprint, oxlint, tokens, design contract after renewing the navigation-shell owner hash, design record, impeccable Assistant-verification: postgres contract passed: TEST_DATABASE_URL against a disposable postgres:17 on 127.0.0.1:55345, apps/server/src/storage/postgres 74/74, including NUL and unpaired surrogate parity through commit, channels.get, counts by ID, repository total, and the 016 backfill Assistant-verification: playwright e2e passed: sidebar-decision-counts, smoke, and document-navigation 50/50, including the new cross-repository and reconnect catch-up specs Assistant-verification: playwright e2e failed: the first run of the same three files failed smoke:247 (room never became editable) and document-navigation:322 (412.125 > 412); both passed in a repeat-each=2 rerun (88/88) and in a clean third run User-preference: keep sidebar counts fresh across repositories with authorized socket subscriptions Co-authored-by: Alex Lavaee <lavaman131@github.com>
Parente0759d4
29 files changed+1253-103
StackedSplit