Skip to content

Add display settings for command output and file diffs#18

Open
zortos293 wants to merge 5 commits intomainfrom
capy/toggle-output-diff-visibility
Open

Add display settings for command output and file diffs#18
zortos293 wants to merge 5 commits intomainfrom
capy/toggle-output-diff-visibility

Conversation

@zortos293
Copy link
Owner

@zortos293 zortos293 commented Mar 11, 2026

What Changed

Added two new client-side app settings to control UI element visibility:

  • showCommandOutput: Controls whether command execution output appears in the chat timeline
  • showFileChangeDiffs: Controls whether file change diffs appear in the diff panel

Both settings default to true to maintain current behavior.

Why

Users may want to hide verbose command output or diff panels to reduce visual clutter during long-running tasks or when reviewing high-level activity summaries. The settings provide client-side control without affecting the underlying data or functionality.

Implementation

  • Added showCommandOutput and showFileChangeDiffs to AppSettingsSchema with true defaults
  • Added "Display" section in settings page with toggle switches
  • ToolWorkEntryRow conditionally renders output based on settings.showCommandOutput
  • DiffPanel shows placeholder message when settings.showFileChangeDiffs is false

Open in Capy TC-1 · Kimi K2.5

Summary by CodeRabbit

  • New Features

    • Added display settings to toggle command output and file-change diffs.
    • Added a "restore defaults" option for the display settings.
  • User Interface

    • Previews and expanded views now respect the command-output setting.
    • File-diff area shows a clear message when diffs are hidden by settings.

@zortos293 zortos293 added the capy Generated by capy.ai label Mar 11, 2026 — with Capy AI
@coderabbitai
Copy link

coderabbitai bot commented Mar 11, 2026

📝 Walkthrough

Walkthrough

Adds two boolean settings, showCommandOutput and showFileChangeDiffs, to AppSettingsSchema and persists them. Wires both settings into the settings UI as toggles and into UI consumers: ChatView now accepts showCommandOutput when computing previews and conditionally renders command output; DiffPanel conditionally hides diffs when showFileChangeDiffs is false. The collapsedToolWorkEntryPreview signature was changed to include showCommandOutput. The settings UI contains a duplicated "Display" section (inserted twice).

🚥 Pre-merge checks | ✅ 2 | ❌ 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 (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding display settings to control visibility of command output and file diffs.
Description check ✅ Passed The description covers all required sections: What Changed (clear feature description), Why (user benefit explanation), and Implementation (technical details). UI changes are referenced but screenshots/videos are not applicable for settings toggles.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

📝 Coding Plan
  • Generate coding plan for human review comments

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

primaryPath ||
additionalPaths.length > 0 ||
workEntry.output ||
(workEntry.output && settings.showCommandOutput) ||
Copy link

Choose a reason for hiding this comment

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

[🟡 Medium]

showCommandOutput only gates the expanded output block and hasExpandedDetails, but the summary preview is still computed from workEntry.output, so command output text still appears in the timeline when the setting is off. This violates the new visibility contract and can leak output users explicitly hid. Update preview generation to exclude workEntry.output when settings.showCommandOutput is false (or pass the setting into collapsedToolWorkEntryPreview and gate its output branch).

// apps/web/src/components/ChatView.tsx
const preview = collapsedToolWorkEntryPreview(workEntry, primaryPath);
const hasExpandedDetails = Boolean(
  workEntry.command ||
  primaryPath ||
  additionalPaths.length > 0 ||
  (workEntry.output && settings.showCommandOutput) ||

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/web/src/components/ChatView.tsx (1)

537-545: ⚠️ Potential issue | 🟠 Major

Hide the collapsed preview when command output is disabled.

collapsedToolWorkEntryPreview() still prefers workEntry.output, so command output can remain visible in the summary row even when settings.showCommandOutput is false. In the output-only case, this also removes the expander while still leaking the very text the setting is meant to hide.

Suggested direction
-  const preview = collapsedToolWorkEntryPreview(workEntry, primaryPath);
+  const preview = collapsedToolWorkEntryPreview(
+    workEntry,
+    primaryPath,
+    settings.showCommandOutput,
+  );

Then update collapsedToolWorkEntryPreview(...) to skip every workEntry.output branch when that flag is false.

As per coding guidelines, apps/web/**: Prioritize UI correctness, responsive layout behavior, and avoiding unnecessary rerenders or fragile client state transitions.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/components/ChatView.tsx` around lines 537 - 545, The collapsed
preview is still showing workEntry.output even when settings.showCommandOutput
is false; update the call site and logic so output is ignored when that flag is
off and the expander/summary reflects that: change the preview computation to
call collapsedToolWorkEntryPreview with the showCommandOutput flag (or make it
read settings.showCommandOutput) so it skips any workEntry.output branches when
false, and also update hasExpandedDetails to only consider (workEntry.output &&
settings.showCommandOutput) so the expander isn't rendered for output-only
entries when output is disabled; touch the collapsedToolWorkEntryPreview
implementation to early-skip output branches accordingly.
🧹 Nitpick comments (1)
apps/web/src/routes/_chat.settings.tsx (1)

492-526: Minor spacing inconsistency with existing sections.

Both toggle rows use mb-3, but existing single-toggle sections (Responses, Safety) don't apply bottom margin to the toggle row itself—they rely solely on mt-3 on the restore button container. This creates 24px total gap before the restore button here vs. 12px elsewhere.

Consider wrapping the toggle rows in a space-y-3 container and removing the individual mb-3 classes for consistency with patterns elsewhere in this file (e.g., space-y-4 / space-y-5 used in other multi-item sections).

💅 Suggested refactor
-              <div className="flex items-center justify-between rounded-lg border border-border bg-background px-3 py-2 mb-3">
-                <div>
-                  <p className="text-sm font-medium text-foreground">Show command output</p>
-                  <p className="text-xs text-muted-foreground">
-                    Display command execution output in the chat timeline.
-                  </p>
-                </div>
-                <Switch
-                  checked={settings.showCommandOutput}
-                  onCheckedChange={(checked) =>
-                    updateSettings({
-                      showCommandOutput: Boolean(checked),
-                    })
-                  }
-                  aria-label="Show command output"
-                />
-              </div>
-
-              <div className="flex items-center justify-between rounded-lg border border-border bg-background px-3 py-2 mb-3">
+              <div className="space-y-3">
+                <div className="flex items-center justify-between rounded-lg border border-border bg-background px-3 py-2">
+                  <div>
+                    <p className="text-sm font-medium text-foreground">Show command output</p>
+                    <p className="text-xs text-muted-foreground">
+                      Display command execution output in the chat timeline.
+                    </p>
+                  </div>
+                  <Switch
+                    checked={settings.showCommandOutput}
+                    onCheckedChange={(checked) =>
+                      updateSettings({
+                        showCommandOutput: Boolean(checked),
+                      })
+                    }
+                    aria-label="Show command output"
+                  />
+                </div>
+
+                <div className="flex items-center justify-between rounded-lg border border-border bg-background px-3 py-2">
                 <div>
                   <p className="text-sm font-medium text-foreground">Show file change diffs</p>
                   <p className="text-xs text-muted-foreground">
                     Display diffs for file modifications in the diff panel.
                   </p>
                 </div>
                 <Switch
                   checked={settings.showFileChangeDiffs}
                   onCheckedChange={(checked) =>
                     updateSettings({
                       showFileChangeDiffs: Boolean(checked),
                     })
                   }
                   aria-label="Show file change diffs"
                 />
               </div>
+              </div>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/routes/_chat.settings.tsx` around lines 492 - 526, Wrap the two
toggle rows that contain the Switch components (the blocks using
settings.showCommandOutput/updateSettings and
settings.showFileChangeDiffs/updateSettings) in a parent container with class
"space-y-3" and remove the individual "mb-3" classes from each toggle row div;
keep the restore-button container's "mt-3" as-is so spacing matches other
single-toggle sections and the total gap before the restore button becomes
consistent with the rest of the file.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/web/src/appSettings.ts`:
- Around line 31-36: The formatting of the Schema.Boolean.pipe blocks for
showCommandOutput and showFileChangeDiffs is causing CI to fail (oxfmt);
reformat this hunk to match project style (run the repo formatter—oxfmt or bun
fmt as configured) so the lines using Schema.Boolean.pipe and
Schema.withConstructorDefault(() => Option.some(true)) adhere to the project's
code style, then run the full preflight (bun fmt, bun lint, bun typecheck) to
ensure the formatting/regression is resolved before merging.

---

Outside diff comments:
In `@apps/web/src/components/ChatView.tsx`:
- Around line 537-545: The collapsed preview is still showing workEntry.output
even when settings.showCommandOutput is false; update the call site and logic so
output is ignored when that flag is off and the expander/summary reflects that:
change the preview computation to call collapsedToolWorkEntryPreview with the
showCommandOutput flag (or make it read settings.showCommandOutput) so it skips
any workEntry.output branches when false, and also update hasExpandedDetails to
only consider (workEntry.output && settings.showCommandOutput) so the expander
isn't rendered for output-only entries when output is disabled; touch the
collapsedToolWorkEntryPreview implementation to early-skip output branches
accordingly.

---

Nitpick comments:
In `@apps/web/src/routes/_chat.settings.tsx`:
- Around line 492-526: Wrap the two toggle rows that contain the Switch
components (the blocks using settings.showCommandOutput/updateSettings and
settings.showFileChangeDiffs/updateSettings) in a parent container with class
"space-y-3" and remove the individual "mb-3" classes from each toggle row div;
keep the restore-button container's "mt-3" as-is so spacing matches other
single-toggle sections and the total gap before the restore button becomes
consistent with the rest of the file.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ae02ffaa-f061-4086-9c44-6f92f6ae651a

📥 Commits

Reviewing files that changed from the base of the PR and between 59134a6 and 8839b9f.

📒 Files selected for processing (5)
  • apps/web/src/appSettings.ts
  • apps/web/src/components/ChatView.tsx
  • apps/web/src/components/DiffPanel.tsx
  • apps/web/src/routes/_chat.settings.tsx
  • core.394

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
apps/web/src/components/ChatView.tsx (1)

528-538: Pass showCommandOutput down instead of subscribing per row.

ToolWorkEntryRow now calls useAppSettings() for every rich work item, even though ChatView already has the same settings object. In long timelines this adds one store subscription per row and forces all of them to rerender on unrelated settings changes. Thread settings.showCommandOutput from the existing ChatView-level subscription instead.

♻️ Suggested refactor
 const ToolWorkEntryRow = memo(function ToolWorkEntryRow(props: {
   workEntry: WorkLogEntry;
   workEntryIndex: number;
+  showCommandOutput: boolean;
 }) {
-  const { workEntry, workEntryIndex } = props;
+  const { workEntry, workEntryIndex, showCommandOutput } = props;
   const [open, setOpen] = useState(false);
-  const { settings } = useAppSettings();
   const iconConfig = workToneIcon(workEntry.tone);
   const EntryIcon = workEntryIcon(workEntry);
   const heading = toolWorkEntryHeading(workEntry);
   const statusBadge = toolWorkEntryStatusBadge(workEntry);
   const primaryPath = !workEntry.command ? primaryWorkEntryPath(workEntry) : null;
@@
-  const preview = collapsedToolWorkEntryPreview(workEntry, primaryPath, settings.showCommandOutput);
+  const preview = collapsedToolWorkEntryPreview(workEntry, primaryPath, showCommandOutput);
@@
-    (workEntry.output && settings.showCommandOutput) ||
+    (workEntry.output && showCommandOutput) ||

And pass it from the existing ChatView settings read when rendering ToolWorkEntryRow.

As per coding guidelines, apps/web/**: "Prioritize UI correctness, responsive layout behavior, and avoiding unnecessary rerenders or fragile client state transitions."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/components/ChatView.tsx` around lines 528 - 538, ChatView
currently calls useAppSettings() and computes preview using
settings.showCommandOutput but ToolWorkEntryRow still calls useAppSettings() per
row causing extra subscriptions; thread settings.showCommandOutput down as a
prop instead. Update ChatView to pass showCommandOutput (the boolean used in
collapsedToolWorkEntryPreview) into ToolWorkEntryRow, update the
ToolWorkEntryRow signature to accept a showCommandOutput prop, remove the
internal useAppSettings() call from ToolWorkEntryRow, and ensure
collapsedToolWorkEntryPreview(workEntry, primaryPath, showCommandOutput) is used
so rows no longer subscribe individually.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@apps/web/src/components/ChatView.tsx`:
- Around line 528-538: ChatView currently calls useAppSettings() and computes
preview using settings.showCommandOutput but ToolWorkEntryRow still calls
useAppSettings() per row causing extra subscriptions; thread
settings.showCommandOutput down as a prop instead. Update ChatView to pass
showCommandOutput (the boolean used in collapsedToolWorkEntryPreview) into
ToolWorkEntryRow, update the ToolWorkEntryRow signature to accept a
showCommandOutput prop, remove the internal useAppSettings() call from
ToolWorkEntryRow, and ensure collapsedToolWorkEntryPreview(workEntry,
primaryPath, showCommandOutput) is used so rows no longer subscribe
individually.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d9cf8229-d054-46a3-b5c7-6157250e0f46

📥 Commits

Reviewing files that changed from the base of the PR and between 8839b9f and 8900c66.

📒 Files selected for processing (3)
  • apps/web/src/appSettings.ts
  • apps/web/src/components/ChatView.tsx
  • packages/contracts/src/ws.ts
💤 Files with no reviewable changes (1)
  • packages/contracts/src/ws.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/src/appSettings.ts

Co-authored-by: Capy <capy@capy.ai>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
apps/web/src/components/DiffPanel.tsx (1)

529-532: Hide diff mode toggles when diffs are disabled.

When showFileChangeDiffs is false, the stacked/split controls remain interactive but have no effect. Consider conditionally hiding (or disabling) that toggle group in this state.

Suggested refactor
-      <ToggleGroup
-        className="shrink-0 [-webkit-app-region:no-drag]"
-        variant="outline"
-        size="xs"
-        value={[diffRenderMode]}
-        onValueChange={(value) => {
-          const next = value[0];
-          if (next === "stacked" || next === "split") {
-            setDiffRenderMode(next);
-          }
-        }}
-      >
-        <Toggle aria-label="Stacked diff view" value="stacked">
-          <Rows3Icon className="size-3" />
-        </Toggle>
-        <Toggle aria-label="Split diff view" value="split">
-          <Columns2Icon className="size-3" />
-        </Toggle>
-      </ToggleGroup>
+      {settings.showFileChangeDiffs && (
+        <ToggleGroup
+          className="shrink-0 [-webkit-app-region:no-drag]"
+          variant="outline"
+          size="xs"
+          value={[diffRenderMode]}
+          onValueChange={(value) => {
+            const next = value[0];
+            if (next === "stacked" || next === "split") {
+              setDiffRenderMode(next);
+            }
+          }}
+        >
+          <Toggle aria-label="Stacked diff view" value="stacked">
+            <Rows3Icon className="size-3" />
+          </Toggle>
+          <Toggle aria-label="Split diff view" value="split">
+            <Columns2Icon className="size-3" />
+          </Toggle>
+        </ToggleGroup>
+      )}

As per coding guidelines, apps/web/**: Prioritize UI correctness, responsive layout behavior, and avoiding unnecessary rerenders or fragile client state transitions.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/components/DiffPanel.tsx` around lines 529 - 532, In DiffPanel,
when settings.showFileChangeDiffs is false the stacked/split view controls
should be hidden or disabled; update the JSX that renders the toggle group (the
stacked/split controls inside DiffPanel) to conditionally render nothing or a
disabled state based on settings.showFileChangeDiffs, and ensure any handlers
(e.g. the view mode setter used by the toggle) are not invoked when the control
is hidden/disabled to avoid no-op state changes or rerenders; reference
settings.showFileChangeDiffs and the toggle group / view-mode control in
DiffPanel to implement this guard.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@apps/web/src/components/DiffPanel.tsx`:
- Around line 529-532: In DiffPanel, when settings.showFileChangeDiffs is false
the stacked/split view controls should be hidden or disabled; update the JSX
that renders the toggle group (the stacked/split controls inside DiffPanel) to
conditionally render nothing or a disabled state based on
settings.showFileChangeDiffs, and ensure any handlers (e.g. the view mode setter
used by the toggle) are not invoked when the control is hidden/disabled to avoid
no-op state changes or rerenders; reference settings.showFileChangeDiffs and the
toggle group / view-mode control in DiffPanel to implement this guard.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: de6b2496-07fc-4cd9-bedd-c472a3cc2ac3

📥 Commits

Reviewing files that changed from the base of the PR and between 24d832a and 6b5b540.

📒 Files selected for processing (1)
  • apps/web/src/components/DiffPanel.tsx

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

capy Generated by capy.ai size:M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant