Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions apps/web/src/appSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ const AppSettingsSchema = Schema.Struct({
enableAssistantStreaming: Schema.Boolean.pipe(
Schema.withConstructorDefault(() => Option.some(false)),
),
showCommandOutput: Schema.Boolean.pipe(Schema.withConstructorDefault(() => Option.some(true))),
Comment thread
capy-ai[bot] marked this conversation as resolved.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[🟡 Medium] [🔵 Bug]

showCommandOutput and showFileChangeDiffs are introduced as required fields but only receive constructor defaults:

// apps/web/src/appSettings.ts
showCommandOutput: Schema.Boolean.pipe(Schema.withConstructorDefault(() => Option.some(true))),
showFileChangeDiffs: Schema.Boolean.pipe(
  Schema.withConstructorDefault(() => Option.some(true)),
),

parsePersistedSettings() later reads the unchanged t3code:app-settings:v1 localStorage entry through Schema.decodeSync(Schema.fromJsonString(AppSettingsSchema)), and Effect's withConstructorDefault only applies to make/constructor paths, not decode-time missing fields (the repo uses Schema.withDecodingDefault elsewhere for that case). As a result, any existing settings blob from before this PR is missing these keys, decode throws, and the catch resets the entire settings object to DEFAULT_APP_SETTINGS, wiping previously saved preferences on first load after upgrade. Use withDecodingDefault or withDefaults for these new booleans so legacy persisted settings are upgraded instead of discarded.

showFileChangeDiffs: Schema.Boolean.pipe(
Schema.withConstructorDefault(() => Option.some(true)),
),
Comment on lines +37 to +40
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[🟠 High] [🔵 Bug]

These two fields are added as required properties on AppSettingsSchema, but they use constructor defaults instead of decode defaults:

// apps/web/src/appSettings.ts
showCommandOutput: Schema.Boolean.pipe(Schema.withConstructorDefault(() => Option.some(true))),
showFileChangeDiffs: Schema.Boolean.pipe(
  Schema.withConstructorDefault(() => Option.some(true)),
),

parsePersistedSettings() decodes raw JSON from localStorage with Schema.decodeSync(Schema.fromJsonString(AppSettingsSchema)), and Effect’s default-constructor docs explicitly scope withConstructorDefault to construction of decoded values, not decoding encoded input. So any existing t3code:app-settings:v1 blob created before this PR is now missing these required keys, decode throws, and the catch block returns DEFAULT_APP_SETTINGS, wiping all previously customized settings instead of backfilling the two new flags. The repo’s own schema convention for backward-compatible decode paths is Schema.withDecodingDefault (for example in packages/contracts/src/terminal.ts and packages/contracts/src/orchestration.ts), so these new fields should use decode defaults as well.

Suggested change
showCommandOutput: Schema.Boolean.pipe(Schema.withConstructorDefault(() => Option.some(true))),
showFileChangeDiffs: Schema.Boolean.pipe(
Schema.withConstructorDefault(() => Option.some(true)),
),
showCommandOutput: Schema.Boolean.pipe(Schema.withDecodingDefault(() => true)),
showFileChangeDiffs: Schema.Boolean.pipe(
Schema.withDecodingDefault(() => true),
)

timestampFormat: Schema.Literals(["locale", "12-hour", "24-hour"]).pipe(
Schema.withConstructorDefault(() => Option.some(DEFAULT_TIMESTAMP_FORMAT)),
),
Expand Down
12 changes: 7 additions & 5 deletions apps/web/src/components/ChatView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -496,9 +496,10 @@ function summarizeToolOutput(value: string, limit = 96): string {
function collapsedToolWorkEntryPreview(
workEntry: WorkLogEntry,
primaryPath: string | null,
showCommandOutput: boolean,
): string | null {
if (workEntry.itemType === "command_execution" || workEntry.requestKind === "command") {
if (workEntry.output) {
if (workEntry.output && showCommandOutput) {
return summarizeToolOutput(workEntry.output);
}
if (workEntry.command) {
Expand All @@ -511,7 +512,7 @@ function collapsedToolWorkEntryPreview(
if (workEntry.command) {
return workEntry.command;
}
if (workEntry.output) {
if (workEntry.output && showCommandOutput) {
return summarizeToolOutput(workEntry.output);
}
if (workEntry.detail) {
Expand All @@ -531,6 +532,7 @@ const ToolWorkEntryRow = memo(function ToolWorkEntryRow(props: {
}) {
const { workEntry, workEntryIndex, timestampFormat } = props;
const [open, setOpen] = useState(false);
const { settings } = useAppSettings();
const iconConfig = workToneIcon(workEntry.tone);
const EntryIcon = workEntryIcon(workEntry);
const heading = toolWorkEntryHeading(workEntry);
Expand All @@ -540,13 +542,13 @@ const ToolWorkEntryRow = memo(function ToolWorkEntryRow(props: {
workEntry.changedFiles?.slice(primaryPath ? 1 : 0, primaryPath ? 4 : 4) ?? [];
const hiddenPathCount =
(workEntry.changedFiles?.length ?? 0) - additionalPaths.length - (primaryPath ? 1 : 0);
const preview = collapsedToolWorkEntryPreview(workEntry, primaryPath);
const preview = collapsedToolWorkEntryPreview(workEntry, primaryPath, settings.showCommandOutput);
const displayText = preview ? `${heading} - ${preview}` : heading;
const hasExpandedDetails = Boolean(
workEntry.command ||
primaryPath ||
additionalPaths.length > 0 ||
workEntry.output ||
(workEntry.output && settings.showCommandOutput) ||
Comment thread
capy-ai[bot] marked this conversation as resolved.
typeof workEntry.exitCode === "number",
);

Expand Down Expand Up @@ -646,7 +648,7 @@ const ToolWorkEntryRow = memo(function ToolWorkEntryRow(props: {
</div>
)}

{workEntry.output && (
{workEntry.output && settings.showCommandOutput && (
<div className="rounded-md border border-border/55 bg-background/75 px-2.5 py-2">
<pre className="overflow-x-auto whitespace-pre-wrap break-words font-mono text-[10px] leading-4 text-foreground/78">
{workEntry.output}
Expand Down
6 changes: 5 additions & 1 deletion apps/web/src/components/DiffPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ import { readNativeApi } from "../nativeApi";
import { resolvePathLinkTarget } from "../terminal-links";
import { parseDiffRouteSearch, stripDiffSearchParams } from "../diffRouteSearch";
import { useTheme } from "../hooks/useTheme";
import { useAppSettings } from "../appSettings";
import { buildPatchCacheKey } from "../lib/diffRendering";
import { resolveDiffThemeName } from "../lib/diffRendering";
import { useTurnDiffSummaries } from "../hooks/useTurnDiffSummaries";
import { useStore } from "../store";
import { useAppSettings } from "../appSettings";
import { formatShortTimestamp } from "../timestampFormat";
import { DiffPanelLoadingState, DiffPanelShell, type DiffPanelMode } from "./DiffPanelShell";
import { ToggleGroup, Toggle } from "./ui/toggle-group";
Expand Down Expand Up @@ -526,6 +526,10 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) {
<div className="flex flex-1 items-center justify-center px-5 text-center text-xs text-muted-foreground/70">
No completed turns yet.
</div>
) : !settings.showFileChangeDiffs ? (
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[🟡 Medium] [🔵 Bug]

This new branch hides the entire diff viewport, which drops the DOM node that the existing selectedFilePath restoration effect scrolls into view. When the user turns diffs back on, selectedFilePath and renderableFiles are usually unchanged, so the effect at lines 296–304 does not rerun and the previously selected file is no longer restored. That leaves deep-linked or timeline-opened file diffs at the top of the list after a visibility toggle. Re-trigger the restoration logic when showFileChangeDiffs flips back to true (for example by including it in the effect dependencies or by preserving the viewport subtree instead of unmounting it).

// apps/web/src/components/DiffPanel.tsx
      ) : !settings.showFileChangeDiffs ? (
        <div className="flex flex-1 items-center justify-center px-5 text-center text-xs text-muted-foreground/70">
          File change diffs are hidden in settings.
        </div>
      ) : (

<div className="flex flex-1 items-center justify-center px-5 text-center text-xs text-muted-foreground/70">
File change diffs are hidden in settings.
</div>
) : (
<>
<div
Expand Down
63 changes: 63 additions & 0 deletions apps/web/src/routes/_chat.settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,69 @@ function SettingsRouteView() {
) : null}
</section>

<section className="rounded-2xl border border-border bg-card p-5">
<div className="mb-4">
<h2 className="text-sm font-medium text-foreground">Display</h2>
<p className="mt-1 text-xs text-muted-foreground">
Control visibility of command outputs and file changes in the UI.
</p>
</div>

<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>
<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>

{(settings.showCommandOutput !== defaults.showCommandOutput ||
settings.showFileChangeDiffs !== defaults.showFileChangeDiffs) && (
<div className="mt-3 flex justify-end">
<Button
size="xs"
variant="outline"
onClick={() =>
updateSettings({
showCommandOutput: defaults.showCommandOutput,
showFileChangeDiffs: defaults.showFileChangeDiffs,
})
}
>
Restore defaults
</Button>
</div>
)}
</section>

<section className="rounded-2xl border border-border bg-card p-5">
<div className="mb-4">
<h2 className="text-sm font-medium text-foreground">Keybindings</h2>
Expand Down
Binary file added core.394
Binary file not shown.
Loading