SLOPSHOPPER

what-changed

Band above the prompt counting files Claude changed this turn, with a toast for files in final/.

bandguardtoastprompt
v0.1.0no licenseupdated 2026-10-19ice-lfernandes/what-changed
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · what-changed
› 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 Changed this turn: 3 files, 0 in final/ [ Hide ] ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts

Draws

Band
Changed this turn: 3 files, 0 in final/ [ Hide ]
README

what-changed

A Claude Code mod that shows a band above the prompt counting the files Claude changed this turn (via Edit and Write), and how many of them are in a final/ folder. A toast appears each time a file in final/ changes. The band has a Hide button.

Install

In a terminal Claude Code session, run:

/plugin install what-changed --marketplace ice-lfernandes/what-changed

Then answer y to add the marketplace and pick a scope (user scope is the first option). The mod is active right away, with no reload.

Develop

claude plugin validate .
claude plugin test .
claude --plugin-dir .
Source 2 files
hooks/register.tsx 54 lines
1import { atom, read, update } from 'claude-code'
2import type { Register } from 'claude-code'
3
4import type { ChangedFiles } from '../types'
5
6const files = atom({ plugin: 'what-changed', key: 'files' } as const, [] as ChangedFiles)
7const isHidden = atom({ plugin: 'what-changed', key: 'isHidden' } as const, false)
8
9const isFinal = (path: string) => path.split('/').includes('final')
10
11export const register: Register = on => {
12  on('prompt.submit', async ($, e, next) => {
13    await update($, files, () => [])
14    await update($, isHidden, () => false)
15
16    return next(e)
17  })
18
19  for (const tool of ['Edit', 'Write'] as const) {
20    on('tool.call', { tool }, async ($, e, next) => {
21      const ran = await next(e)
22
23      if (ran.deny === undefined && ran.isError !== true) {
24        await update($, files, list => (list.includes(e.file_path) ? list : [...list, e.file_path]))
25
26        if (isFinal(e.file_path)) {
27          $.ui.toast(`final/ changed: ${e.file_path.split('/').pop()}`)
28        }
29      }
30
31      return ran
32    })
33  }
34
35  on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
36    const list = await read($, files)
37
38    if (list.length === 0 || (await read($, isHidden))) {
39      return next(e)
40    }
41
42    const { Box, Button, Text } = $.ui.resolve(e)
43
44    return (
45      <Box>
46        <Text dimColor>
47          Changed this turn: {list.length} files, {list.filter(isFinal).length} in final/{' '}
48        </Text>
49        <Button key="hide" label="Hide" onPress={() => update($, isHidden, () => true)} />
50      </Box>
51    )
52  })
53}
54
types/index.d.ts 8 lines
1export type ChangedFiles = string[]
2
3declare module 'claude-code' {
4  interface PluginState {
5    'what-changed': { files: ChangedFiles; isHidden: boolean }
6  }
7}
8