Skip to content

Build the Code Review inbox and workspace host (CR-3) - #346

Closed
BunsDev wants to merge 8 commits into
mainfrom
cr3-inbox
Closed

BunsDev wants to merge 8 commits into
mainfrom
cr3-inbox

Conversation

@BunsDev

@BunsDev BunsDev commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Superseded by #367, which preserves this implementation and #362 while integrating producer #366 and current main. Replacement head 54c46f19ac55b68970bc37e8d56f0c4bf5b42c1f passed build/typecheck, 1,979 unit tests (16 skips), 387 desktop E2E tests (six opt-in skips), and seven site tests. Required CI and producer-first landing remain pending. #322 visual/human acceptance stays open. Historical evidence below is retained.

Producer dependency update: #366 supersedes #349 and includes its #354/#361 repairs on current main. Land this inbox PR after #366. Required macOS CI passed on current inbox head ec498e7df97cb195cf2ee7bcc5eb9923b1c4611d in run39528222060; recheck integration against main after the producer lands.

Integrated repair

#362 is merged here at ec498e7df97cb195cf2ee7bcc5eb9923b1c4611d. The inbox now preserves stale/partial/unavailable state per page, deduplicates PR identities across pages, and uses the reviewed CR2 read service. Its authenticated real-app probe passed inbox search, PR resolution, Back/Forward, Changes/split navigation, pin persistence after reload, unavailable-PR errors and isolated signed-out behavior.

Repair candidate 080ea4da2d23e8853843ad896c5c8ca68d8259a0 passed build/typecheck, 1,950 unit tests (16 skips), 375 desktop E2E tests (six opt-in skips), seven site tests and required macOS CI before merge. The resulting parent head requires fresh CI. Land producer #349 before this integrated workspace.

Authenticated navigation evidence is separate from fixture evidence. Original R1–R4 visual comparison and human keyboard/VoiceOver acceptance remain open, so this PR does not complete #322 or Gate A. The original baseline's pending wiring/smoke descriptions below have been superseded by #362.

Original implementation and baseline evidence follow.

CR-3 of tracker #319, for #322: the Code Review inbox and the PR workspace host, built on CR-1's contract (#334). Every read goes through main's code-review:read channel. Overview content (#323) and the remote diff (#324) fill panes this host lays out; until then those panes say so.

Lands after CR-2 (#321). This branch carries three CR-2 commits as identical cherry-picks, so the later merge is clean:

  • 372edbf: the BridgeCommands line.
  • a34ef72: the contract extension: stackHint: boolean | null and head-changed.
  • ae6ea30: the Store demo's handler entry.

Main has no handler for the channel until CR-2 lands, so in the app today every read fails, and the UI says so.

Visibility switch (Val's decision pending)

The Code Review rail button shows in development builds (import.meta.env.DEV). A built or packaged app hides it unless localStorage["modex.codeReview"] is "on".

  • The switch lives in src/renderer/code-review/feature.ts. There is no setting and no main-process flag.
  • The E2E spec sets the switch before reloading.
  • Whether Code Review should show before Gate A completes is still open for Val.

What's in it

  • src/renderer/code-review/:
    • reader.ts: one read per slot.
      • A newer read in a slot sends cancel for the one it replaces.
      • A reply is applied only through acceptsReply, with the scope it was asked under.
      • A rejected invoke counts as a failed read.
    • navigation.ts: PR history, each PR's tab arrangement, the split folding when narrow, and saved widths fitted to the window.
    • pins.ts: local pins per account, validated on load, capped at 50.
    • feature.ts: the switch.
    • text.ts: a sentence for every Unavailable reason.
    • CodeReviewView.tsx, Inbox.tsx, PrPane.tsx and code-review.css: the surface itself.
  • Shell:
    • Rail gets the Code Review button, and TitleBar gets a slot for the PR and Changes tabs.
    • App adds the code-review surface. Back and forward follow PR history on it, and the sidebar toggle and its resize handle now drive the inbox too.
    • PanelResizeHandle takes a label and a class.
    • shared/layout.ts persists codeReviewSplit and codeReviewTree.
    • Four icons are added.
  • docs/design/code-review.md: a new Workspace (CR-3) section with the built geometry against R1 and R4, the folding rules, each [CR-3][P1] Build the PR inbox and resizable Code Review workspace #322 control and its test, deviations D6–D10, and the open gates.

Acceptance criteria

AC Evidence (fixtures, through main's real IPC path)
AC1: R1 and R4 through real controls The rail button, an inbox row, the PR and Changes tabs, and the split toggle reach both arrangements. Tab and PR identity are asserted. Captures are at the references' CSS sizes.
AC2: no stale reply on rapid search, PR or account switching; history restores without replaying Four tests. A held overview for lending #73 released after field-guide #73 is selected never shows there, and lands on its own PR. A held search is cancelled and dropped. Typing sends only the paused query (["", "hold", "loan"]). An account switch mid-read cancels every pending read and clears the old account's PRs and history. Back and forward restore each PR's arrangement with no new overview read.
AC3: independent scroll regions; resizing and reopening strand nothing The inbox list scrolls alone; the diff, sheet and document stay at 0. Keyboard resizing of the split persists across a reload. At 900×600 every header action, the split toggle, the menu and the search box are inside the window.
AC4: keyboard-only at 900×600 and 1440×900; focus returns after dismissal Tab reaches the selected row. Arrows move between rows and Enter opens one. Arrows switch tabs. The ⋯ menu closes on Escape and returns focus. Closing Changes focuses the PR tab; closing the PR focuses its row. Unavailable actions are focusable, aria-disabled, and described by their reason. Reduced motion stops the spinner.
AC5: existing local Changes, threads, tab persistence and navigation pass; no remote PR reaches Discard The full E2E suite passes (results below). In the Code Review test, the changes:revert handler is counted and stays at 0, and the surface holds no changes-revert control.
AC6: fixture and authenticated smoke reported separately Fixtures only here. The authenticated navigation smoke test is an open gate, waiting for CR-2's handler.

Controls delivered: C02, C03, C05, C06, C08, C11–C15, C17–C20, C23–C26, C62, C74, C75 and C80. Each is listed with its test in the design page.

Red first

  • Unit: tsc -p tsconfig.main.json failed before the modules existed:
    • TS2307 Cannot find module '../src/renderer/code-review/reader.js', and the same for navigation.js, pins.js and feature.js;
    • TS2353 'codeReviewSplit' does not exist in type 'Layout'.
  • E2E: the first navigation test failed on locator('[data-testid="rail-code-review"]'): element not found.

Results

On 8883428 (base 43d5b9c), macOS, Node 24.18.1:

  • Unit: node --test dist/test/code-review-workspace.test.js passed 12/12. With the contract test, 23/23.
  • Mutation checks: I broke the compiled modules 13 ways, one at a time, on a copy in scratch. Each break failed a test:
    • a superseded reply applied;
    • the reply scope ignored;
    • no cancel sent;
    • a null reply left silent;
    • forward history kept;
    • duplicate history entries;
    • the split never folding;
    • a saved split width not fitted;
    • the tree never folding;
    • pins crossing accounts;
    • pins uncapped;
    • the switch off in development;
    • any value enabling the switch.
  • Code Review spec: npx playwright test e2e/code-review-navigation.spec.ts passed 9/9.
  • Full suite: pnpm build && pnpm test exited 0:
    • worktree scripts: 111/111;
    • core: 16/16;
    • desktop unit: 1,728 passed, 16 skipped, 0 failed;
    • site: 21 and 10;
    • store: 16.
  • E2E: pnpm test:e2e exited 0:
    • desktop: 372 passed, 6 skipped (15.0 min);
    • site: 7 passed.
  • Disk: free space fell to 2.4 GB by the end of the E2E run. No failure came from it.

Captures

Not committed. They show only fixture data:

  • /private/tmp/claude-501/-Users-buns-Documents-GitHub-TypeSafeAI-modex/3fb7a3f9-f061-43ea-98e3-82dec6b844fc/scratchpad/cr3/captures/code-review-r1.png
  • /private/tmp/claude-501/-Users-buns-Documents-GitHub-TypeSafeAI-modex/3fb7a3f9-f061-43ea-98e3-82dec6b844fc/scratchpad/cr3/captures/code-review-r4.png
  • /private/tmp/claude-501/-Users-buns-Documents-GitHub-TypeSafeAI-modex/3fb7a3f9-f061-43ea-98e3-82dec6b844fc/scratchpad/cr3/captures/code-review-narrow.png
  • /private/tmp/claude-501/-Users-buns-Documents-GitHub-TypeSafeAI-modex/3fb7a3f9-f061-43ea-98e3-82dec6b844fc/scratchpad/cr3/captures/code-review-signed-out.png

The spec writes the same four to test-results/ on every run.

Built against measured. At R1's and R4's CSS sizes, against the reference values in CSS px:

Element Built Reference
Inbox x 53–333 R1 x 55–335
PR tab x 339–579 R1 x 343–583
Split PR pane 663 wide R4 659
Split diff 473 wide R4 470
Split file tree 290 wide R4 290

The differences match the captures' crop offsets.

Visual deviations

Not done here

Coven-Harness: Claude Code / claude-opus-5-5

The design page's decision 4: one channel, CodeReviewRequest in and
CodeReviewReply out. A cancel request answers null. No handler is
registered yet; CR-2's read service adds it, and CR-3 binds to this line.

Coven-Harness: Claude Code / claude-opus-5-5
- PrSummary.stackHint is boolean | null. A search result names no base
  branch (checked live: no repository object either), so main knows the
  hint only for PRs it has read itself.
- Unavailable gains head-changed: a files or checks read whose PR moved
  past the asked revision, with no earlier read kept to show as stale.
- parseRevision is exported, so main checks GitHub's base ref and SHAs
  with the same rule the renderer's requests go through.

Coven-Harness: Claude Code / claude-opus-5-5
The renderer half of the read path and the workspace's rules, pure so
they are tested without a window:

- Reader: one read per slot. A newer read cancels the one it replaces;
  only the latest reply is applied, and only through acceptsReply with
  the scope it asked under. A rejected invoke is a failed read.
- Navigation: history between PRs, each PR's tab arrangement (PR,
  Changes, or both side by side), the split folding to its focused pane
  when narrow, and saved pane widths fitted to the window.
- Pins: a local list per account, validated on load, capped at 50.
- The destination's switch: on in development, hidden in a built app
  unless set.
- Layout keeps the split and file-tree widths with the other panes.

Coven-Harness: Claude Code / claude-opus-5-5
A rail button opens the Code Review surface: the inbox beside a PR
workspace whose PR and Changes tabs sit in the title bar, alone or side
by side (R1, R4). Every read goes through main's code-review:read.

- The inbox searches as you pause, opens a pasted or typed PR link,
  pages with Show more, and says when gh is missing or signed out, a
  read failed, or what it shows is stale. None of those look empty.
- A PR opens with its header (state, repository and number, pin, copy
  link, Open in GitHub) over each pane. Review, instructions and merge
  are shown unavailable with their reason.
- Back and forward move between PRs; each PR keeps its own tab
  arrangement and scroll positions.
- The split and file-tree dividers resize by pointer and keyboard and
  persist with the layout, fitted to the window when restored.
- Built apps hide the destination unless localStorage
  "modex.codeReview" is "on" (pending Val's decision).

Overview content (#323) and the remote diff (#324) are not here; their
panes say so.

Coven-Harness: Claude Code / claude-opus-5-5
…(CR-3)

The spec swaps main's code-review:read handler for one serving the
synthetic fixtures, with requests it can hold and release:

- Identical PR numbers in two repositories never show each other's
  answer, however late it arrives.
- A slow search answer is cancelled and dropped when a newer query
  replaces it; typing reads only where it pauses.
- Switching the gh account mid-read drops the answer and every PR of
  the old account.
- Back and forward restore each PR's arrangement without reading it
  again; closing a tab moves focus to the tab or row it leaves.
- No gh, signed out, offline, empty, denied and a failed page each say
  what happened.
- Pin, copy and Open in GitHub use the canonical link; a pasted or
  typed link opens its PR; unavailable actions say why and do nothing;
  nothing reaches local Discard.
- Keyboard alone at 1440x900 and 900x600, the split folding to one
  pane, resized panes persisting, and the inbox scrolling on its own.

Fixes found on the way: a narrow "Review with Codex" kept no icon, and
the signed-out message now says how to sign in.

Coven-Harness: Claude Code / claude-opus-5-5
…(CR-3)

A Workspace (CR-3) section at the end of docs/design/code-review.md:

- The visibility switch.
- How the PR and Changes tabs arrange the panes.
- The built geometry against R1 and R4's measurements.
- Pane minimums and folding.
- Each #322 control with its test.
- Deviations D6 to D10, and the open gates.

Also drops a pane label that duplicated its tab's.

Coven-Harness: Claude Code / claude-opus-5-5
The Store demo's handler map covers every bridge channel, so the new
channel broke its build. The offline demo has no GitHub and answers it
as unavailable, like its other account and system channels.

Coven-Harness: Claude Code / claude-opus-5-5
@vercel

vercel Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
modex Ready Ready Preview Oct 21, 2026 1:35am UTC

Request Review

if (state.account && req.key.account !== state.account.id) return { state: "unavailable", reason: "signed-out", fetchedAt: at };
const known = state.overview[keyId(req.key)];
if (known) return known;
const row = known().find((r) => r.key && keyId(r.key) === keyId(req.key!));
@github-code-quality

github-code-quality Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

Code coverage overview

Languages: TypeScript

TypeScript / code-coverage-agent

main 4e9d965 cr3-inbox ec498e7 +/-
64% 65% +1%
View coverage of the 10 most impacted files.
File main 4e9d965 cr3-inbox ec498e7 +/-
apps/store-desk...rc/main/demo.ts 95% 95% 0%
apps/desktop/sr...ain/commands.ts 86% 87% +1%
apps/desktop/sr.../code-review.ts 99% 100% +1%
apps/desktop/sr...read-service.ts 0% 98% +98%
apps/desktop/sr...eview/github.ts 0% 99% +99%
apps/desktop/sr...de-review/gh.ts 0% 100% +100%
apps/desktop/sr...w/navigation.ts 0% 100% +100%
apps/desktop/sr...eview/reader.ts 0% 100% +100%
apps/desktop/sr...-review/pins.ts 0% 100% +100%
apps/desktop/sr...view/feature.ts 0% 100% +100%

ℹ️ Calculations represent line coverage. Results may change as calculations are updated.


Updated October 21, 2026 02:27 UTC

…#362)

* Name the code-review:read bridge channel (CR-2)

The design page's decision 4: one channel, CodeReviewRequest in and
CodeReviewReply out. A cancel request answers null. No handler is
registered yet; CR-2's read service adds it, and CR-3 binds to this line.

Coven-Harness: Claude Code / claude-opus-5-5

* Extend the read contract for what GitHub's REST answers say (CR-2)

- PrSummary.stackHint is boolean | null. A search result names no base
  branch (checked live: no repository object either), so main knows the
  hint only for PRs it has read itself.
- Unavailable gains head-changed: a files or checks read whose PR moved
  past the asked revision, with no earlier read kept to show as stale.
- parseRevision is exported, so main checks GitHub's base ref and SHAs
  with the same rule the renderer's requests go through.

Coven-Harness: Claude Code / claude-opus-5-5

* Answer code-review:read in the Store demo (CR-2)

The Store demo's handler map covers every bridge channel, so the new
channel broke its build. The offline demo has no GitHub and answers it
as unavailable, like its other account and system channels.

Coven-Harness: Claude Code / claude-opus-5-5

* Add main's Code Review read service through gh (CR-2)

code-review:read is registered localOnly. Every payload passes
validateRequest before anything runs. Reads go through the person's
own gh login: gh api <fixed path> --hostname github.com --method GET
--include, with no shell, no new sign-in and no token in Modex.

- gh.ts runs gh and reads its status line, headers and body. It refuses
  any path that is not user, the issue search or under repos/, and
  keeps nothing gh prints to stderr.
- github.ts maps GitHub's REST answers to the contract, checking and
  bounding every field.
- read-service.ts keys each read by the gh account (rechecked after
  30 s, conditionally) and the repository's stable id. It learns names
  only from its own reads. An account switch aborts reads in flight and
  drops everything learned. A files page is bracketed by PR reads, so a
  moved head reads as stale head-changed, and checks for an old head
  are stale. It has conditional caching, bounded concurrency,
  rate-limit backoff, cancellation, and last-good answers kept as
  stale. It never polls.
- Browser development answers the channel with a disabled service.

Checked live on TypeSafeAI/modex with Val's authorization, and written
into the design page:
- Search results carry no repository object.
- A cross-reference has no id of its own.
- The timeline spells review states in lower case.
- The raw media type answers a directory as JSON.

Coven-Harness: Claude Code / claude-opus-5-5

* Fix the CR-2 review findings: account switches, stale fallback, coverage

The independent review of 197276e requested changes.

- AC2 (blocker): a gh auth switch inside the 30 s identity window went
  unnoticed, so reads ran as the new account but were stamped as the
  old one. Main now fingerprints gh's hosts.yml, which switch, login
  and logout rewrite. It rechecks the account when that changes, and
  confirms the account after every read before replying. Where the
  file can't be read, it confirms with a conditional gh api user (a
  free 304). The read that notices a switch goes on under the new
  account, and a switch clears rate-limit state.
- AC3: when the account can't be checked (offline, slow, limited), the
  last confirmed account still scopes kept answers, which come back
  stale. Repository lookups all settle before one failure is raised. A
  2xx that gh exits non-zero after is failed, not a short file. A short
  files page is partial, and search pages after the first never claim
  complete.
- AC4: checks are bracketed by PR reads, like files.
- Nits: hasOwn enum lookups, strict ISO timestamps, a 429 without
  Retry-After waits 60 s, a recreated repository is re-resolved,
  duplicate request ids are rejected, a cancelled read never starts gh,
  and a forced identity check never joins an older one.
- AC5: the smoke probe and its results are recorded in
  docs/reviews/2026-10-20-cr2-read-smoke.md.

Coven-Harness: Claude Code / claude-opus-5-5

* Refuse a read whose gh sign-in moved under it (CR-2 re-review)

The re-review of f2c2de7 found two ways the after-read check still let
another account's rows through:

- A: a switch away and back within one read. gh answered the original
  account again, so the read was accepted. Each read now records the
  hosts.yml fingerprint it began under. If it moved during the read,
  the answer is refused and nothing is kept, even if the account
  matches now.
- B: a confirming check that failed passed the read. It now fails it.

Also:
- An inbox's groups are kept only after the check.
- A login seen to change stops the offline fallback to the last
  account's answers.
- The fingerprint includes the file's change time.
- The contract gains mergePages, which joins an inbox group's pages,
  deduplicated by key, and calls the list complete only when the
  distinct rows reach the provider's total.

Coven-Harness: Claude Code / claude-opus-5-5

* Invalidate retained PR data after authoritative visibility loss

* Invalidate shared read resources without losing partial inbox results

* Preserve inbox page freshness and retry evidence while deduplicating rows
const release = github.hold(/^search\//);
const inbox = read({ op: "inbox", query: "" });
await until(() => github.inFlight >= 2);
github.user = KIT; // the search runs as Kit
const revision = overview.result.value?.revision;
evidence.revision = revision;
evidence.overview = overview.result.value && { state: overview.result.value.state, totals: overview.result.value.totals, headRepo: overview.result.value.headRepo };
const again = step("overview again (conditional)", await read({ op: "overview", key }));

This branch was successfully deployed

1 active deployment
Preview — ec498e7d Deployed Oct 21, 2026 by vercel[bot]
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.

1 participant