SLOPSHOPPER

quiet-diffs

Show Claude Code file edits as one-line summaries instead of full diffs

newrowscommand
v1.2.0MITupdated 2026-10-10AnnihilationWizard/quiet-diffs
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · quiet-diffs
› fix the failing auth test and add an audit log call ⏺ Read(src/auth.ts) ⎿ Read 6 lines ⏺ Update(src/auth.ts) ⎿ Added 2 lines, removed 1 line ⏺ Bash(bun test) ⎿ 3 pass, 1 fail ● Done. refresh now rejects expired claims and logs an audit event. ✻ Worked for 42s · done 4:20 PM › /quiet-diffs ⎿ quiet-diffs: Showing full diffs ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts
README

quiet-diffs

<strong>Follow what Claude did, not every line it changed.</strong><br> A Claude Code mod that replaces the full diff under each file edit with a one-line summary.

<a href="CHANGELOG.md"><img src="https://img.shields.io/badge/Version-1.2.0-green.svg" alt="Version 1.2.0"></a> <a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-blue.svg" alt="MIT License"></a> <a href="https://code.claude.com/docs"><img src="https://img.shields.io/badge/Platform-Claude%20Code-D97757.svg" alt="Platform: Claude Code"></a> <a href="hooks/register.tsx"><img src="https://img.shields.io/badge/Type-Function%20hooks%20mod-6E7681.svg" alt="Function hooks mod"></a> <a href="hooks/register.test.tsx"><img src="https://img.shields.io/badge/Tests-6%20passing-3FB950.svg" alt="6 tests passing"></a>

<a href="#features">Features</a> · <a href="#install">Install</a> · <a href="#usage">Usage</a> · <a href="#development">Development</a> · <a href="CONTRIBUTING.md">Contributing</a> · <a href="CHANGELOG.md">Changelog</a>

Side-by-side Claude Code transcript: on the left an edit shows a full red and green diff, on the right the same edit shows one dim summary line

Illustration of the transcript before and after.

When Claude Code edits files, each Edit or Write step, and each shell command that changes files, prints the whole diff into the transcript. Over a long task, those diffs push the steps and Claude's explanations off the screen. quiet-diffs keeps every step visible and turns each diff into a single line with the file path and its added and removed line counts. The change only affects what the transcript draws: the stored tool result, and what Claude reads, stay the same.

[!NOTE] quiet-diffs is built on Claude Code's function-hooks mod API, which is in early access and may change between releases.

Features

  • One line per edit: Edit, Write and NotebookEdit results show the path and +added −removed instead of the diff.
  • Shell edits too: When a Bash command changes files, its output stays and each changed file gets one line instead of a diff.
  • Steps stay visible: Works with the default transcript view, so tool calls and replies are not hidden as they are in focus view.
  • Toggle on demand: /quiet-diffs brings the full diff back for the session; run it again to return to summaries.
  • Safe fallbacks: Failed edits, edits held for review and every other tool keep Claude Code's own rendering.
  • Display only: Nothing is written to your files or settings, and the model's context is unchanged.
ToolShown instead of the diff
EditEdited src/app.ts (+2 −1)
Write, new fileCreated docs/notes.md (40 lines)
Write, overwriteOverwrote config.json (+5 −2)
NotebookEditEdited analysis.ipynb (cell abc123, replace)
Bash that changes filesCommand output, then Edited notes.txt (+2 −1), Created new.txt (+1 −0) or Deleted old.txt (+0 −2) per file

Install

In a Claude Code terminal session, run:

/plugin install quiet-diffs --marketplace AnnihilationWizard/quiet-diffs
  1. Answer y to add the github:AnnihilationWizard/quiet-diffs marketplace.
  2. Choose a scope. The user scope loads the mod in every session.
  3. Claude Code confirms Installed quiet-diffs. Plugin is now active. The mod works in the current session without a restart.

To update later, run claude plugin update, then /reload-plugins.

git clone https://github.com/AnnihilationWizard/quiet-diffs.git
claude --plugin-dir ./quiet-diffs

To load a clone in every session, add it to the env block of ~/.claude/settings.json:

{
  "env": { "CLAUDE_CODE_PLUGIN_DIRS": "/path/to/quiet-diffs" }
}

Usage

Once installed, there is nothing to configure. Ask Claude to change a file and the transcript shows the summary line.

CommandEffect
/quiet-diffsShow full diffs for this session
/quiet-diffs againReturn to one-line summaries
Ctrl+OOpen the detailed transcript (also summarized while the mod is on)

Two Claude Code settings complete the quiet view. Add them to ~/.claude/settings.json:

{
  "viewMode": "default",
  "bashEditDiffEnabled": true
}
SettingWhy
viewMode: "default"Shows each step. Focus view hides steps and only shows the final reply.
bashEditDiffEnabled: trueRecords which files a shell command changed in every permission mode, so quiet-diffs can list them. Without it, Claude Code records them only in auto and bypassPermissions modes. Set it to false to hide shell edits entirely.

Limitations

  • A mod cannot tell whether a result is drawn in the Ctrl+O transcript, so summaries apply there too. Use /quiet-diffs when you need the full diff.
  • Claude Code folds some edits into a single group line, such as edits in its scratchpad. Those lines are Claude Code's own and are left as they are.
  • Shell edit summaries read a Bash result field that Claude Code marks as internal. If a later release changes it, quiet-diffs falls back to Claude Code's full diff.
  • The /plugin install command is available in terminal sessions only. A user-scope install also loads in the desktop app's Code tab.

Development

claude plugin validate .   # manifest, marketplace file and hooks module
claude plugin test .       # hooks/*.test.tsx against the engine

After Claude Code first loads the mod, it writes the API types to .claude-plugin/types/ (git-ignored). Type-check against them with TypeScript 5.4 or newer:

npx -p typescript@5 tsc -p .

Run claude --plugin-dir . to try changes. In an interactive session the folder is watched, and saving a file reloads the mod.

Project Structure

.claude-plugin/   plugin.json manifest and marketplace.json
hooks/            hooks.json, the hooks module and its tests
types/            state contract for the /quiet-diffs toggle
docs/images/      README illustration

Contributing

Bug reports and ideas are welcome. Read CONTRIBUTING.md before opening an issue or pull request, and follow SECURITY.md for security reports.

License

MIT License. Copyright © 2026 AnnihilationWizard.

Source 2 files
hooks/register.tsx 112 lines
1import { atom, read, update } from 'claude-code'
2import type { Register } from 'claude-code'
3
4import type { ShowDiffs } from '../types'
5
6const showDiffs = atom({ plugin: 'quiet-diffs', key: 'showDiffs' } as const, false as ShowDiffs)
7
8type Hunk = { lines: string[] }
9
10const countLines = (hunks: readonly Hunk[]) => {
11  let added = 0
12  let removed = 0
13  for (const hunk of hunks) {
14    for (const line of hunk.lines) {
15      if (line.startsWith('+')) added += 1
16      else if (line.startsWith('-')) removed += 1
17    }
18  }
19
20  return { added, removed }
21}
22
23const summarize = (tool: string, output: unknown): string | undefined => {
24  if (typeof output !== 'object' || output === null) return undefined
25  const o = output as Record<string, unknown>
26  if (o.staged === true) return undefined
27
28  if (tool === 'Edit' && typeof o.filePath === 'string' && Array.isArray(o.structuredPatch)) {
29    const { added, removed } = countLines(o.structuredPatch as Hunk[])
30    return `Edited ${o.filePath} (+${added} −${removed})`
31  }
32  if (tool === 'Write' && typeof o.filePath === 'string') {
33    if (o.type === 'create') {
34      const text = typeof o.content === 'string' ? o.content.replace(/\n$/, '') : ''
35      const lines = text === '' ? 0 : text.split('\n').length
36      return `Created ${o.filePath} (${lines} ${lines === 1 ? 'line' : 'lines'})`
37    }
38    const hunks = Array.isArray(o.structuredPatch) ? (o.structuredPatch as Hunk[]) : []
39    const { added, removed } = countLines(hunks)
40    return `Overwrote ${o.filePath} (+${added} −${removed})`
41  }
42  if (tool === 'NotebookEdit' && typeof o.notebook_path === 'string' && !o.error) {
43    return `Edited ${o.notebook_path} (cell ${String(o.cell_id ?? '?')}, ${String(o.edit_mode)})`
44  }
45
46  return undefined
47}
48
49type BashFile = { filePath: string; hunks: Hunk[]; created?: true; deleted?: true }
50
51// Splits a Bash result into the output without its file-edit diff and one
52// line per changed file. Undefined when the command changed no files.
53const splitBashEdits = (output: unknown) => {
54  if (typeof output !== 'object' || output === null) return undefined
55  const { bashEditDiff, ...rest } = output as Record<string, unknown>
56  if (typeof bashEditDiff !== 'object' || bashEditDiff === null) return undefined
57  const diff = bashEditDiff as { files?: unknown; moreFiles?: unknown }
58  if (!Array.isArray(diff.files) || diff.files.length === 0) return undefined
59
60  const lines = (diff.files as BashFile[]).map(file => {
61    const { added, removed } = countLines(file.hunks ?? [])
62    const verb = file.created ? 'Created' : file.deleted ? 'Deleted' : 'Edited'
63    return `${verb} ${file.filePath} (+${added} −${removed})`
64  })
65  const more = typeof diff.moreFiles === 'number' ? diff.moreFiles : 0
66  if (more > 0) lines.push(`and ${more} more ${more === 1 ? 'file' : 'files'}`)
67
68  return { output: rest, lines }
69}
70
71export const register: Register = on => {
72  on('session.start', async ($, e, next) => {
73    await $.command.register({
74      name: 'quiet-diffs',
75      description: 'Toggle between full diffs and one-line summaries for file edits',
76    })
77
78    return next(e)
79  })
80
81  on('command.run', { command: 'quiet-diffs' }, async $ => {
82    const shown = await update($, showDiffs, v => !v)
83
84    return { text: shown ? 'Showing full diffs' : 'Showing one-line summaries' }
85  })
86
87  on('ui.render', { component: 'ToolResult' }, async ($, e, next) => {
88    if (e.props.isErrored || (await read($, showDiffs))) return next(e)
89    const { Box, Text } = $.ui.resolve(e)
90
91    if (e.props.tool === 'Bash') {
92      const split = splitBashEdits(e.props.output)
93      if (split === undefined) return next(e)
94      const drawn = await next({ ...e, props: { ...e.props, output: split.output } })
95
96      return (
97        <Box flexDirection="column">
98          {drawn}
99          {split.lines.map(line => (
100            <Text dimColor>{line}</Text>
101          ))}
102        </Box>
103      )
104    }
105
106    const line = summarize(e.props.tool, e.props.output)
107    if (line === undefined) return next(e)
108
109    return <Text dimColor>{line}</Text>
110  })
111}
112
types/index.d.ts 8 lines
1export type ShowDiffs = boolean
2
3declare module 'claude-code' {
4  interface PluginState {
5    'quiet-diffs': { showDiffs: ShowDiffs }
6  }
7}
8