Skip to content

Add fullscreen review panel for show_changes#75

Open
Waishnav wants to merge 5 commits into
mainfrom
codex/fullscreen-review-panel
Open

Add fullscreen review panel for show_changes#75
Waishnav wants to merge 5 commits into
mainfrom
codex/fullscreen-review-panel

Conversation

@Waishnav

@Waishnav Waishnav commented Jul 14, 2026

Copy link
Copy Markdown
Owner

Summary

  • add a persistent expand/collapse footer to show_changes cards
  • add a fullscreen Review action using the MCP Apps display mode API
  • render the selected Pierre diff beside a Pierre file tree
  • add review model coverage and split the UI into focused components

Validation

  • npm ci
  • npm run typecheck
  • npm test
  • npm run build
  • npx react-doctor@latest --verbose --scope changed (100/100)

Summary by CodeRabbit

  • New Features

    • Added fullscreen review mode for browsing changed files with a file tree, file selection, and change statistics.
    • Added inline and fullscreen presentation options for review results.
    • Added clear status messages for unavailable diffs and review-mode errors.
    • Improved review handling for added, deleted, modified, and renamed files.
    • Added responsive layouts and controls, including Review and Close review actions.
  • Tests

    • Added coverage for review file parsing, statuses, statistics, and initial file selection.

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The review UI now builds shared file entries, supports inline and fullscreen presentations, adds file-tree navigation, and handles host display-mode transitions. New components, responsive styling, model tests, and the tree dependency support the workflow.

Changes

Review workspace

Layer / File(s) Summary
Review entry model and validation
src/ui/review-model.ts, src/ui/review-model.test.ts, package.json
Patch data is normalized into review file entries with statuses, paths, statistics, parsed diffs, selection behavior, and unit coverage.
Review diff and file navigation components
src/ui/review-file-body.tsx, src/ui/review-file-tree.tsx, src/ui/review-fullscreen.tsx, src/ui/review-status-line.tsx, package.json
Diff bodies, status messages, fullscreen selection state, and searchable file-tree navigation were added.
Review payload presentation
src/ui/review-payload.tsx
Review payloads now use shared entries and switch between inline and fullscreen rendering.
Display-mode switching and fullscreen controls
src/ui/workspace-app.tsx
Workspace review cards request fullscreen or inline display modes, propagate presentation state, handle errors, and render fullscreen reviews.
Review workspace styling
src/ui/workspace-app.css
Review headers, actions, fullscreen panels, file trees, errors, and responsive layouts were styled.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

  • Waishnav/devspace#47: Routes apply_patch results through the review payload rendering path.
  • Waishnav/devspace#73: Also modifies the review-card and payload rendering path in src/ui/workspace-app.tsx.

Poem

A rabbit hops through diffs so bright,
Renames bloom in tree-view light.
“Review!” I click, fullscreen wide,
Then close the burrowed code inside.
Tests thump softly: all is right! 🐇

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding a fullscreen review panel for show_changes cards.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/fullscreen-review-panel

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Waishnav Waishnav force-pushed the codex/fullscreen-review-panel branch from c27164f to ebe763c Compare July 14, 2026 09:57

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (4)
package.json (1)

31-31: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider using a glob pattern for tests to simplify the script.

As the number of test files grows, concatenating commands with && becomes difficult to maintain. Consider using a glob pattern with tsx --test (or node --test if using the native Node test runner) to automatically discover and run all test files.

🛠️ Proposed refactor
-    "test": "tsx src/config.test.ts && tsx src/ui/card-types.test.ts && tsx src/ui/patch-display.test.ts && tsx src/ui/tool-display.test.ts && tsx src/ui/review-model.test.ts && tsx src/apply-patch.test.ts && tsx src/process-platform.test.ts && tsx src/process-sessions.test.ts && tsx src/local-agent-runtime.test.ts && tsx src/local-agent-adapters.test.ts && tsx src/local-agent-availability.test.ts && tsx src/local-agent-profiles.test.ts && tsx src/local-agent-targets.test.ts && tsx src/local-agent-store.test.ts && tsx src/roots.test.ts && tsx src/skills.test.ts && tsx src/workspaces.test.ts && tsx src/review-checkpoints.test.ts && tsx src/oauth-store.test.ts && tsx src/cli.test.ts",
+    "test": "tsx --test \"src/**/*.test.ts\"",
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@package.json` at line 31, Update the package.json test script to use the test
runner’s glob-based discovery via tsx --test, replacing the manually chained
test file list. Ensure the pattern includes all relevant test files under src
while preserving the existing test command behavior.
src/ui/workspace-app.tsx (2)

283-300: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Simplify a no-op Math.max expression.

(card.files ?? []).slice(0, 3).length is capped at 3, so Math.max(3, …) always evaluates to 3 regardless of the actual file count. The expression reads as if it scales with file count but it doesn't — simplify it to the literal it always resolves to.

♻️ Suggested refactor
     const visibleFileCount = isReviewTool(card.tool) &&
         presentation === "inline" &&
         !reviewFilesExpanded
-      ? Math.max(3, (card.files ?? []).slice(0, 3).length)
+      ? 3
       : undefined;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/ui/workspace-app.tsx` around lines 283 - 300, In the visibleFileCount
calculation within the review/patch tool branch, replace the redundant Math.max
expression with the literal value it always produces. Preserve the existing
conditions and leave the undefined fallback unchanged.

30-39: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Avoid duplicating MountedPayload/PayloadRendererOptions shape.

This interface is manually kept in sync with the one in review-payload.tsx (both needed the presentation field added in this PR). Since import type is fully erased at compile time, it won't affect the dynamic/lazy import of the module, so you can import the type directly instead of hand-duplicating it.

♻️ Suggested refactor
-interface MountedPayload {
-  update(options: {
-    card: ToolResultCard;
-    hostContext?: HostContext;
-    errorMessage?: string | null;
-    visibleFileCount?: number;
-    presentation?: "inline" | "fullscreen";
-  }): void;
-  unmount(): void;
-}
+import type { MountedPayload } from "./review-payload.js";
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/ui/workspace-app.tsx` around lines 30 - 39, Remove the locally duplicated
MountedPayload interface in workspace-app.tsx and import the existing payload
type from review-payload.tsx using import type. Update the relevant mounting
code to reference that shared type, preserving the current update options and
unmount behavior.
src/ui/workspace-app.css (1)

201-257: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove the unused .review-header-actions selector src/ui/workspace-app.css:201-257.review-header-actions has no consumer in the reviewed TS/TSX; remove the dead selector or apply it to the header wrapper if it’s still needed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/ui/workspace-app.css` around lines 201 - 257, Remove the unused
.review-header-actions selector from the shared display and alignment rule in
the review header styles. Keep .review-fullscreen-actions and
.review-fullscreen-title behavior unchanged; only retain .review-header-actions
if a verified header wrapper consumer requires those styles.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/ui/review-model.ts`:
- Around line 51-53: Update the fallback status mapping in the review model to
call reviewFileStatus(fileDiff.type) instead of returning the hardcoded
"modified" value, while preserving the existing "renamed" branch for files whose
prevName differs from name.

In `@src/ui/workspace-app.tsx`:
- Around line 429-442: Update the fullscreen Review button visibility condition
in the surrounding header-rendering logic to use the already computed expandable
state (or equivalent isExpandableCard result) instead of requiring files.length
> 0, so patch-only review cards with card.payload.patch can show the button
while non-expandable cards remain hidden.
- Around line 101-117: Update the app.onhostcontextchanged handler to clear the
stale reviewDisplayModeError whenever the host display mode changes externally,
before rendering the updated review tool UI. Preserve the existing display-mode
change detection and render flow, and avoid clearing the error when the display
mode is unchanged.

---

Nitpick comments:
In `@package.json`:
- Line 31: Update the package.json test script to use the test runner’s
glob-based discovery via tsx --test, replacing the manually chained test file
list. Ensure the pattern includes all relevant test files under src while
preserving the existing test command behavior.

In `@src/ui/workspace-app.css`:
- Around line 201-257: Remove the unused .review-header-actions selector from
the shared display and alignment rule in the review header styles. Keep
.review-fullscreen-actions and .review-fullscreen-title behavior unchanged; only
retain .review-header-actions if a verified header wrapper consumer requires
those styles.

In `@src/ui/workspace-app.tsx`:
- Around line 283-300: In the visibleFileCount calculation within the
review/patch tool branch, replace the redundant Math.max expression with the
literal value it always produces. Preserve the existing conditions and leave the
undefined fallback unchanged.
- Around line 30-39: Remove the locally duplicated MountedPayload interface in
workspace-app.tsx and import the existing payload type from review-payload.tsx
using import type. Update the relevant mounting code to reference that shared
type, preserving the current update options and unmount behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 5304498a-6df1-4948-a0b7-fce4713b06ae

📥 Commits

Reviewing files that changed from the base of the PR and between b027795 and ebe763c.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (10)
  • package.json
  • src/ui/review-file-body.tsx
  • src/ui/review-file-tree.tsx
  • src/ui/review-fullscreen.tsx
  • src/ui/review-model.test.ts
  • src/ui/review-model.ts
  • src/ui/review-payload.tsx
  • src/ui/review-status-line.tsx
  • src/ui/workspace-app.css
  • src/ui/workspace-app.tsx

Comment thread src/ui/review-model.ts
Comment on lines +51 to +53
status: fileDiff.prevName && fileDiff.prevName !== fileDiff.name
? "renamed"
: "modified",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Inspect `FileDiffMetadata` definition to see available properties for added/deleted files.

rg -A 15 'interface FileDiffMetadata|type FileDiffMetadata' node_modules/@pierre/diffs

Repository: Waishnav/devspace

Length of output: 222


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the review model and any type definitions for diffs in the repo.
git ls-files 'src/ui/review-model.ts' 'package.json' 'pnpm-lock.yaml' 'yarn.lock' 'package-lock.json' '**/*diff*' '**/*review*' | sed -n '1,200p'

echo
echo '--- src/ui/review-model.ts ---'
cat -n src/ui/review-model.ts | sed -n '1,140p'

echo
echo '--- search for FileDiffMetadata / diff metadata shapes ---'
rg -n "FileDiffMetadata|isAdded|isDeleted|prevName|status:\s*fileDiff" src . --glob '!**/node_modules/**' --glob '!**/dist/**' --glob '!**/build/**' || true

Repository: Waishnav/devspace

Length of output: 5107


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- package.json ---'
cat -n package.json | sed -n '1,220p'

echo
echo '--- package-lock references to `@pierre/diffs` ---'
rg -n '"`@pierre/diffs`"|`@pierre/diffs`' package-lock.json package.json || true

echo
echo '--- nearby package-lock context ---'
python3 - <<'PY'
import json
from pathlib import Path

p = Path('package-lock.json')
if not p.exists():
    print('package-lock.json missing')
    raise SystemExit(0)

data = json.loads(p.read_text())
pkgs = data.get('packages', {})
for k, v in pkgs.items():
    if 'node_modules/@pierre/diffs' in k:
        print(k)
        for key in ['version', 'resolved', 'integrity', 'dependencies']:
            if key in v:
                print(f'  {key}: {v[key]}')
PY

Repository: Waishnav/devspace

Length of output: 4578


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- src/ui/review-model.test.ts ---'
cat -n src/ui/review-model.test.ts | sed -n '1,260p'

echo
echo '--- look for review-model consumers / status assumptions ---'
rg -n "reviewFileStatus|buildReviewFileEntries|status === \"added\"|status === \"deleted\"|status === \"renamed\"|status === \"modified\"" src/ui src --glob '!**/node_modules/**' || true

Repository: Waishnav/devspace

Length of output: 4618


Use the parsed file type in the fallback status mapping

The fallback branch only checks for renames, so unmatched added or deleted files will still be labeled modified. Reuse reviewFileStatus(fileDiff.type) here instead of hardcoding "modified".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/ui/review-model.ts` around lines 51 - 53, Update the fallback status
mapping in the review model to call reviewFileStatus(fileDiff.type) instead of
returning the hardcoded "modified" value, while preserving the existing
"renamed" branch for files whose prevName differs from name.

Comment thread src/ui/workspace-app.tsx
Comment on lines 101 to 117
app.onhostcontextchanged = (ctx) => {
const previousDisplayMode = hostContext?.displayMode;
hostContext = {
...hostContext,
...ctx,
};
applyHostContext();
if (
previousDisplayMode !== hostContext.displayMode &&
card &&
isReviewTool(card.tool)
) {
render();
return;
}
renderPayloadIfNeeded();
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Stale reviewDisplayModeError can persist across an external display-mode change.

reviewDisplayModeError is only cleared in requestReviewDisplayMode and on a new tool result. If the host changes displayMode through this handler (not via requestReviewDisplayMode), a previously shown error banner remains stuck in the new render.

🩹 Suggested fix
     if (
       previousDisplayMode !== hostContext.displayMode &&
       card &&
       isReviewTool(card.tool)
     ) {
+      reviewDisplayModeError = null;
       render();
       return;
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
app.onhostcontextchanged = (ctx) => {
const previousDisplayMode = hostContext?.displayMode;
hostContext = {
...hostContext,
...ctx,
};
applyHostContext();
if (
previousDisplayMode !== hostContext.displayMode &&
card &&
isReviewTool(card.tool)
) {
render();
return;
}
renderPayloadIfNeeded();
};
app.onhostcontextchanged = (ctx) => {
const previousDisplayMode = hostContext?.displayMode;
hostContext = {
...hostContext,
...ctx,
};
applyHostContext();
if (
previousDisplayMode !== hostContext.displayMode &&
card &&
isReviewTool(card.tool)
) {
reviewDisplayModeError = null;
render();
return;
}
renderPayloadIfNeeded();
};
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/ui/workspace-app.tsx` around lines 101 - 117, Update the
app.onhostcontextchanged handler to clear the stale reviewDisplayModeError
whenever the host display mode changes externally, before rendering the updated
review tool UI. Preserve the existing display-mode change detection and render
flow, and avoid clearing the error when the display mode is unchanged.

Comment thread src/ui/workspace-app.tsx
Comment on lines +429 to +442
headerRow.append(header);
if (files.length > 0 && canRequestDisplayMode("fullscreen")) {
const reviewButton = element("button", {
className: "review-button",
type: "button",
text: reviewDisplayModePending ? "Opening…" : "Review",
disabled: reviewDisplayModePending,
});
reviewButton.setAttribute("aria-busy", String(reviewDisplayModePending));
reviewButton.addEventListener("click", () => {
void requestReviewDisplayMode("fullscreen");
});
headerRow.append(reviewButton);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Fullscreen "Review" button hidden for patch-only review cards.

The button visibility check uses files.length > 0, but isExpandableCard (and the expandable variable already computed above) treats a review card as expandable via card.files?.length || card.payload?.patch. A show_changes card whose diff only lives in payload.patch (no files entries) can still be expanded inline, but the fullscreen entry point never appears for it.

🐛 Suggested fix
-  if (files.length > 0 && canRequestDisplayMode("fullscreen")) {
+  if (expandable && canRequestDisplayMode("fullscreen")) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
headerRow.append(header);
if (files.length > 0 && canRequestDisplayMode("fullscreen")) {
const reviewButton = element("button", {
className: "review-button",
type: "button",
text: reviewDisplayModePending ? "Opening…" : "Review",
disabled: reviewDisplayModePending,
});
reviewButton.setAttribute("aria-busy", String(reviewDisplayModePending));
reviewButton.addEventListener("click", () => {
void requestReviewDisplayMode("fullscreen");
});
headerRow.append(reviewButton);
}
headerRow.append(header);
if (expandable && canRequestDisplayMode("fullscreen")) {
const reviewButton = element("button", {
className: "review-button",
type: "button",
text: reviewDisplayModePending ? "Opening…" : "Review",
disabled: reviewDisplayModePending,
});
reviewButton.setAttribute("aria-busy", String(reviewDisplayModePending));
reviewButton.addEventListener("click", () => {
void requestReviewDisplayMode("fullscreen");
});
headerRow.append(reviewButton);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/ui/workspace-app.tsx` around lines 429 - 442, Update the fullscreen
Review button visibility condition in the surrounding header-rendering logic to
use the already computed expandable state (or equivalent isExpandableCard
result) instead of requiring files.length > 0, so patch-only review cards with
card.payload.patch can show the button while non-expandable cards remain hidden.

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