system-diff 0.3.0__tar.gz → 0.3.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. {system_diff-0.3.0 → system_diff-0.3.4}/PKG-INFO +3 -5
  2. {system_diff-0.3.0 → system_diff-0.3.4}/README.md +2 -4
  3. {system_diff-0.3.0 → system_diff-0.3.4}/mods/system-diff/.claude-plugin/plugin.json +1 -1
  4. {system_diff-0.3.0 → system_diff-0.3.4}/mods/system-diff/hooks/page.tsx +7 -21
  5. {system_diff-0.3.0 → system_diff-0.3.4}/mods/system-diff/hooks/pane.test.tsx +15 -14
  6. {system_diff-0.3.0 → system_diff-0.3.4}/mods/system-diff/hooks/register.tsx +14 -16
  7. {system_diff-0.3.0 → system_diff-0.3.4}/pyproject.toml +1 -1
  8. {system_diff-0.3.0 → system_diff-0.3.4}/src/system_diff/__init__.py +1 -1
  9. {system_diff-0.3.0 → system_diff-0.3.4}/.claude-plugin/marketplace.json +0 -0
  10. {system_diff-0.3.0 → system_diff-0.3.4}/.gitattributes +0 -0
  11. {system_diff-0.3.0 → system_diff-0.3.4}/.github/workflows/ci.yml +0 -0
  12. {system_diff-0.3.0 → system_diff-0.3.4}/.github/workflows/release.yml +0 -0
  13. {system_diff-0.3.0 → system_diff-0.3.4}/.github/workflows/system-diff.yml +0 -0
  14. {system_diff-0.3.0 → system_diff-0.3.4}/.gitignore +0 -0
  15. {system_diff-0.3.0 → system_diff-0.3.4}/LICENSE +0 -0
  16. {system_diff-0.3.0 → system_diff-0.3.4}/docs/diagrams/system.json +0 -0
  17. {system_diff-0.3.0 → system_diff-0.3.4}/docs/diagrams/system.svg +0 -0
  18. {system_diff-0.3.0 → system_diff-0.3.4}/mods/system-diff/hooks/hooks.json +0 -0
  19. {system_diff-0.3.0 → system_diff-0.3.4}/mods/system-diff/types/index.d.ts +0 -0
  20. {system_diff-0.3.0 → system_diff-0.3.4}/src/system_diff/__main__.py +0 -0
  21. {system_diff-0.3.0 → system_diff-0.3.4}/src/system_diff/activity.py +0 -0
  22. {system_diff-0.3.0 → system_diff-0.3.4}/src/system_diff/agent.py +0 -0
  23. {system_diff-0.3.0 → system_diff-0.3.4}/src/system_diff/architecture.py +0 -0
  24. {system_diff-0.3.0 → system_diff-0.3.4}/src/system_diff/canvas.py +0 -0
  25. {system_diff-0.3.0 → system_diff-0.3.4}/src/system_diff/cli.py +0 -0
  26. {system_diff-0.3.0 → system_diff-0.3.4}/src/system_diff/companion.py +0 -0
  27. {system_diff-0.3.0 → system_diff-0.3.4}/src/system_diff/compare.py +0 -0
  28. {system_diff-0.3.0 → system_diff-0.3.4}/src/system_diff/config.py +0 -0
  29. {system_diff-0.3.0 → system_diff-0.3.4}/src/system_diff/design.py +0 -0
  30. {system_diff-0.3.0 → system_diff-0.3.4}/src/system_diff/diff.py +0 -0
  31. {system_diff-0.3.0 → system_diff-0.3.4}/src/system_diff/extract.py +0 -0
  32. {system_diff-0.3.0 → system_diff-0.3.4}/src/system_diff/graph.py +0 -0
  33. {system_diff-0.3.0 → system_diff-0.3.4}/src/system_diff/inside.py +0 -0
  34. {system_diff-0.3.0 → system_diff-0.3.4}/src/system_diff/learn.py +0 -0
  35. {system_diff-0.3.0 → system_diff-0.3.4}/src/system_diff/mcp_server.py +0 -0
  36. {system_diff-0.3.0 → system_diff-0.3.4}/src/system_diff/narrate.py +0 -0
  37. {system_diff-0.3.0 → system_diff-0.3.4}/src/system_diff/pane.py +0 -0
  38. {system_diff-0.3.0 → system_diff-0.3.4}/src/system_diff/panel.py +0 -0
  39. {system_diff-0.3.0 → system_diff-0.3.4}/src/system_diff/pictures.py +0 -0
  40. {system_diff-0.3.0 → system_diff-0.3.4}/src/system_diff/png.py +0 -0
  41. {system_diff-0.3.0 → system_diff-0.3.4}/src/system_diff/profile.py +0 -0
  42. {system_diff-0.3.0 → system_diff-0.3.4}/src/system_diff/readme.py +0 -0
  43. {system_diff-0.3.0 → system_diff-0.3.4}/src/system_diff/sources.py +0 -0
  44. {system_diff-0.3.0 → system_diff-0.3.4}/src/system_diff/statusline.py +0 -0
  45. {system_diff-0.3.0 → system_diff-0.3.4}/src/system_diff/textmap.py +0 -0
  46. {system_diff-0.3.0 → system_diff-0.3.4}/src/system_diff/topics.py +0 -0
  47. {system_diff-0.3.0 → system_diff-0.3.4}/src/system_diff/tui.py +0 -0
  48. {system_diff-0.3.0 → system_diff-0.3.4}/src/system_diff/watch.py +0 -0
  49. {system_diff-0.3.0 → system_diff-0.3.4}/system-diff.toml +0 -0
  50. {system_diff-0.3.0 → system_diff-0.3.4}/tests/conftest.py +0 -0
  51. {system_diff-0.3.0 → system_diff-0.3.4}/tests/test_capture.py +0 -0
  52. {system_diff-0.3.0 → system_diff-0.3.4}/tests/test_checkpoint_watch_mcp.py +0 -0
  53. {system_diff-0.3.0 → system_diff-0.3.4}/tests/test_design_and_pane.py +0 -0
  54. {system_diff-0.3.0 → system_diff-0.3.4}/tests/test_system_diff.py +0 -0
  55. {system_diff-0.3.0 → system_diff-0.3.4}/tests/test_terminal_and_learning.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: system-diff
3
- Version: 0.3.0
3
+ Version: 0.3.4
4
4
  Summary: Draws your system from its code (parts, entry points, data, outside systems) and what each change adds, changes or removes. No dependencies; SVG pictures for PRs and READMEs.
5
5
  Project-URL: Homepage, https://github.com/simplifiedmachine/system-diff
6
6
  Project-URL: Documentation, https://github.com/simplifiedmachine/system-diff/wiki
@@ -74,8 +74,7 @@ hooks it added before, and frees `/system-diff` for the pane.
74
74
  ### The System diff pane
75
75
 
76
76
  A tab beside Claude Code's own file diff: your system drawn the way a system designer draws it, live, with what
77
- Claude is changing in it. It opens as wide as Claude Code docks a pane (45% of the terminal); drag its edge to narrow
78
- it.
77
+ Claude is changing in it. `/system-diff close` (or "× close" in its tab row) hides it; `/system-diff` brings it back.
79
78
 
80
79
  - **Now** follows Claude, in the order your attention needs it:
81
80
  - **What Claude is doing** ("● Claude is working ✎ Editing API") and what you asked.
@@ -92,8 +91,7 @@ it.
92
91
  other ends, and a line says what it uses and what uses it.
93
92
  - **Zoom:** Enter (or a second click) zooms in on a container's components, with the lines between them. `b` goes
94
93
  back, `f` follows Claude again.
95
- - **Scroll:** `[` `]` or shift ← → scroll a wide diagram across; shift ↑ ↓, Page Up/Down or the wheel scroll a
96
- tall one.
94
+ - **Scroll:** `[` `]` scroll a wide diagram across; shift ↑ ↓, Page Up/Down or the wheel scroll a tall one.
97
95
  - **This turn**: each change to the design, and what it trades off.
98
96
  - **Learn**: how the part Claude works on is designed.
99
97
  - The principle its place calls for: an edge of the system, an adapter, a shared foundation, a coordinator.
@@ -42,8 +42,7 @@ hooks it added before, and frees `/system-diff` for the pane.
42
42
  ### The System diff pane
43
43
 
44
44
  A tab beside Claude Code's own file diff: your system drawn the way a system designer draws it, live, with what
45
- Claude is changing in it. It opens as wide as Claude Code docks a pane (45% of the terminal); drag its edge to narrow
46
- it.
45
+ Claude is changing in it. `/system-diff close` (or "× close" in its tab row) hides it; `/system-diff` brings it back.
47
46
 
48
47
  - **Now** follows Claude, in the order your attention needs it:
49
48
  - **What Claude is doing** ("● Claude is working ✎ Editing API") and what you asked.
@@ -60,8 +59,7 @@ it.
60
59
  other ends, and a line says what it uses and what uses it.
61
60
  - **Zoom:** Enter (or a second click) zooms in on a container's components, with the lines between them. `b` goes
62
61
  back, `f` follows Claude again.
63
- - **Scroll:** `[` `]` or shift ← → scroll a wide diagram across; shift ↑ ↓, Page Up/Down or the wheel scroll a
64
- tall one.
62
+ - **Scroll:** `[` `]` scroll a wide diagram across; shift ↑ ↓, Page Up/Down or the wheel scroll a tall one.
65
63
  - **This turn**: each change to the design, and what it trades off.
66
64
  - **Learn**: how the part Claude works on is designed.
67
65
  - The principle its place calls for: an edge of the system, an adapter, a shared foundation, a coordinator.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "system-diff",
3
- "version": "0.3.0",
3
+ "version": "0.3.4",
4
4
  "description": "A pane beside the chat: what Claude is working on in your system, zoomed in, what just happened and why it matters, and a topic to learn (system-diff)",
5
5
  "types": "./types/index.d.ts",
6
6
  "author": {
@@ -1,12 +1,12 @@
1
- import type { ClientKeyEvent, ClientModule, ClientPointerEvent, JsonValue } from 'claude-code'
1
+ import type { ClientKeyEvent, ClientModule, JsonValue } from 'claude-code'
2
2
 
3
3
  import type { Page, PageBlock, Row, Run } from '../types'
4
4
 
5
5
  // The page `system-diff panel` lays out, painted cell for cell, in its own region of the pane so it gets the keys and
6
6
  // the clicks: ← ↑ ↓ → pick a block (a box of the system diagram, a component in a container, a technology), Enter
7
- // or a second click zooms in on it, space marks a technology new to you, [ and ] (or shift ← →) scroll a picture
8
- // wider than the pane, shift ↑ ↓ and Page Up/Down scroll a tall one (as the wheel does), b goes back, f follows
9
- // Claude, n m c t switch tabs, Esc hands the keys back.
7
+ // or a second click zooms in on it, space marks a technology new to you, [ and ] scroll a picture wider than the
8
+ // pane, shift ↑ ↓ and Page Up/Down scroll a tall one (as the wheel does), b goes back, f follows Claude, n m c t
9
+ // switch tabs, Esc hands the keys back. Shift ← → are left to the terminal.
10
10
  //
11
11
  // A picked block is blue; what it connects to (its lines, in and out, and the blocks at their other ends) is lit;
12
12
  // the rest keeps its colours: yellow what changed, green what is new, red what went, gray the background.
@@ -123,15 +123,12 @@ const PageView: ClientModule<JsonValue, State> = (raw, surface) => {
123
123
  } else if (e.key === 'pageup' || e.key === 'pagedown') {
124
124
  surface.post({ scrollBy: e.key === 'pageup' ? -1000 : 1000 }) // a window's worth
125
125
  } else if ((e.key === 'left' || e.key === 'right') && e.shift) {
126
- surface.setState({ sel, sx: across(page, cols, sx + (e.key === 'left' ? -STEP : STEP), undefined), mine: true,
127
- ticking: true })
126
+ // shift+← → belong to the terminal (its own splits): nothing here
128
127
  } else if (['up', 'down', 'left', 'right'].includes(e.key)) {
129
128
  pick(move(page.blocks, picked, e.key))
130
129
  } else if (e.key === '[' || e.key === ']') {
131
130
  surface.setState({ sel, sx: across(page, cols, sx + (e.key === '[' ? -STEP : STEP), undefined), mine: true,
132
131
  ticking: true })
133
- } else if (e.key === '<' || e.key === '>' || e.key === ',' || e.key === '.') {
134
- surface.post({ resize: e.key === '<' || e.key === ',' ? STEP : -STEP }) // the pane: wider, or narrower
135
132
  } else if (e.key === 'return') {
136
133
  act(picked, 'enter')
137
134
  } else if (e.key === ' ') {
@@ -144,17 +141,6 @@ const PageView: ClientModule<JsonValue, State> = (raw, surface) => {
144
141
  surface.post({ view: { n: 'now', m: 'map', c: 'changes', t: 'topics' }[e.key] ?? 'now' })
145
142
  }
146
143
  })
147
- surface.onPointer((e: ClientPointerEvent) => {
148
- if (e.type !== 'down' || e.button !== 'left') return
149
- const x = e.x + sx
150
- const y = e.y - (page.wide > cols ? 1 : 0)
151
- const hit = page.blocks.filter(b => x >= b.x && x < b.x + b.w && y >= b.y && y < b.y + b.h)
152
- .sort((a, b) => a.w * a.h - b.w * b.h)[0] // the innermost: a component over its container
153
- if (!hit) return
154
- if (hit.id === sel) act(hit, 'enter')
155
- else pick(hit)
156
- })
157
-
158
144
  // The detail of the picked block, in the slot under its picture.
159
145
  const rows: Row[] = page.rows.slice()
160
146
  if (picked?.slot) {
@@ -198,7 +184,7 @@ const PageView: ClientModule<JsonValue, State> = (raw, surface) => {
198
184
  {wide && (
199
185
  <Text dimColor wrap="truncate-end">
200
186
  {`${sx > 0 ? '◂ ' : ' '}columns ${sx + 1}–${Math.min(page.wide, sx + cols)} of ${page.wide}` +
201
- ` · [ ] or shift ← → scroll${sx + cols < page.wide ? ' ▸' : ''}`}
187
+ ` · [ ] scroll across${sx + cols < page.wide ? ' ▸' : ''}`}
202
188
  </Text>
203
189
  )}
204
190
  {rows.map((row, y) => {
@@ -210,7 +196,7 @@ const PageView: ClientModule<JsonValue, State> = (raw, surface) => {
210
196
  )
211
197
  })}
212
198
  <Text dimColor wrap="truncate-end">
213
- {'click here, then ← ↑ ↓ → pick · ↵ zoom in · shift+arrows scroll · < > width' + (canGoBack ? ' · b back' : '') + ' · f follow · Esc leaves'}
199
+ {'click here, then ← ↑ ↓ → pick · ↵ zoom in · [ ] shift+↑ ↓ scroll' + (canGoBack ? ' · b back' : '') + ' · f follow · Esc leaves'}
214
200
  </Text>
215
201
  </Box>
216
202
  )
@@ -108,7 +108,7 @@ test('a resized split is laid out again at once, for the width the page reports'
108
108
  await ui.unmount()
109
109
  })
110
110
 
111
- test('the arrows pick a block, Enter zooms in, a click picks and a second click zooms', async ($, on) => {
111
+ test('the arrows pick a block, Enter zooms in, b goes back', async ($, on) => {
112
112
  const asked: string[][] = []
113
113
  answer(on, asked)
114
114
  await $.command.run(RUN)
@@ -124,10 +124,7 @@ test('the arrows pick a block, Enter zooms in, a click picks and a second click
124
124
  expect(last()).toContain('--part ')
125
125
  expect(last()).not.toContain('--part core')
126
126
 
127
- await ui.pointer({ type: 'down', x: 2, y: 2, button: 'left' }) // Developer: picked
128
- await ui.pointer({ type: 'down', x: 2, y: 2, button: 'left' }) // again: zoomed in on
129
- expect(last()).toContain('--part a:dev')
130
-
127
+ await ui.key({ key: 'return', in: 'page' }) // zoomed in again: Follow Claude shows
131
128
  await ui.key({ key: 'm', in: 'page' })
132
129
  expect(last()).toContain('--view map')
133
130
  await ui.press({ key: 'follow' })
@@ -145,7 +142,7 @@ test('space on a technology marks it new to you', async ($, on) => {
145
142
  await ui.unmount()
146
143
  })
147
144
 
148
- test('/system-diff and /system-diff-pane both open it, as wide as a dock goes', async ($, on) => {
145
+ test('/system-diff and /system-diff-pane both open it, at the width the person keeps', async ($, on) => {
149
146
  const registered: string[] = []
150
147
  const opened: Array<{ columns?: number }> = []
151
148
  on('ui.open', (_: unknown, e: { columns?: number }) => {
@@ -167,17 +164,21 @@ test('/system-diff and /system-diff-pane both open it, as wide as a dock goes',
167
164
  const got = await $.command.run({ ...RUN, command })
168
165
  expect((got as { text?: string }).text).toBe('System diff pane opened.')
169
166
  }
170
- expect(opened.every(o => (o.columns ?? 0) >= 200)).toBe(true)
167
+ expect(opened.every(o => o.columns === undefined)).toBe(true) // the width is the person's
171
168
  })
172
169
 
173
- test('the width buttons ask Claude Code for a wider or narrower pane', async ($, on) => {
174
- const opened: Array<{ columns?: number }> = []
175
- answer(on, [], 0, '', opened)
170
+ test('/system-diff close closes the pane, and so does its close button', async ($, on) => {
171
+ const closed: string[] = []
172
+ answer(on, [])
173
+ on('ui.close', (_: unknown, e: { id: string }) => {
174
+ closed.push(e.id)
175
+ return { value: undefined }
176
+ })
177
+ const got = await $.command.run({ ...RUN, args: 'close' })
178
+ expect((got as { text?: string }).text).toContain('closed')
176
179
  await $.command.run(RUN)
177
180
  const ui = await $.ui.mount({ ...PANE, surface: 'terminal' })
178
- await ui.press({ key: 'narrower' })
179
- expect(opened[opened.length - 1]?.columns).toBe(100) // the body's 110, less 10
180
- await ui.post({ resize: 8 }, { in: 'page' }) // `<` on the page
181
- expect(opened[opened.length - 1]?.columns).toBe(118)
181
+ await ui.press({ key: 'close' })
182
+ expect(closed).toEqual(['system-diff', 'system-diff'])
182
183
  await ui.unmount()
183
184
  })
@@ -16,7 +16,6 @@ import type { Act, Page, Row } from '../types'
16
16
  const PANE = 'system-diff'
17
17
  const COMMANDS = ['system-diff', 'system-diff-pane'] as const
18
18
  const VIEWS = [['now', 'Now', 'n'], ['map', 'Map', 'm'], ['changes', 'Changes', 'c'], ['topics', 'Topics', 't']] as const
19
- const WIDEST = 999 // as wide as Claude Code docks a pane (it caps a dock at 45% of the terminal)
20
19
 
21
20
  const page = atom({ plugin: 'system-diff', key: 'page' } as const, null as Page | null, { shape: 'page-2' })
22
21
  const view = atom({ plugin: 'system-diff', key: 'view' } as const, 'now', { shape: 'view-1' })
@@ -151,14 +150,10 @@ async function scrollBy($: EngineInterface, rows: number): Promise<void> {
151
150
  }
152
151
  }
153
152
 
154
- function open($: EngineInterface, columns = WIDEST) {
155
- return $.ui.open({ id: PANE, title: 'System diff', columns })
156
- }
157
-
158
- // Wider or narrower: the pane asks Claude Code for a width, which docks it at most at 45% of the terminal (a width
159
- // the person dragged this session wins over the ask).
160
- async function resize($: EngineInterface, by: number): Promise<void> {
161
- await open($, Math.max(24, columns + by))
153
+ // As wide as Claude Code docks a pane, the first time: a width the person set (dragged, or with the pane's keys:
154
+ // ctrl+x ← →) is theirs from then on.
155
+ function open($: EngineInterface) {
156
+ return $.ui.open({ id: PANE, title: 'System diff' })
162
157
  }
163
158
 
164
159
  export const register: Register = on => {
@@ -167,7 +162,8 @@ export const register: Register = on => {
167
162
  // `/system-diff` opens it; `/system-diff-pane` too, the name it had first. A name someone else holds is skipped.
168
163
  for (const name of COMMANDS) {
169
164
  try {
170
- await $.command.register({ name, description: 'Show the System diff pane beside the chat' })
165
+ await $.command.register({ name, description: 'Show the System diff pane beside the chat (close: hide it)',
166
+ argumentHint: '[close]' })
171
167
  } catch {
172
168
  // taken
173
169
  }
@@ -178,7 +174,11 @@ export const register: Register = on => {
178
174
  })
179
175
 
180
176
  for (const command of COMMANDS) {
181
- on('command.run', { command }, async $ => {
177
+ on('command.run', { command }, async ($, e) => {
178
+ if (e.args.trim().toLowerCase() === 'close') {
179
+ await $.ui.close({ id: PANE })
180
+ return { text: 'System diff pane closed. /system-diff opens it again.' }
181
+ }
182
182
  await refresh($)
183
183
  await open($)
184
184
  return { text: 'System diff pane opened.' }
@@ -210,13 +210,12 @@ export const register: Register = on => {
210
210
  // What the page (page.tsx) asks for.
211
211
  on('ui.message', { component: 'Pane', requestId: PANE }, async ($, e) => {
212
212
  const data = (e.data ?? {}) as Act & { back?: boolean; follow?: boolean; width?: number; reveal?: number;
213
- scrollBy?: number; resize?: number }
213
+ scrollBy?: number }
214
214
  if (typeof data.width === 'number' && data.width > 0 && data.width !== columns) {
215
215
  columns = data.width
216
216
  await refresh($)
217
217
  } else if (typeof data.reveal === 'number') await reveal($, data.reveal)
218
218
  else if (typeof data.scrollBy === 'number') await scrollBy($, data.scrollBy)
219
- else if (typeof data.resize === 'number') await resize($, data.resize)
220
219
  else if (typeof data.zoom === 'string') await zoom($, data.zoom)
221
220
  else if (typeof data.learn === 'string') await learn($, data.learn)
222
221
  else if (data.back) await goBack($)
@@ -276,12 +275,11 @@ export const register: Register = on => {
276
275
  {behind.length > 0 && (
277
276
  <Button key="back" hotkey="b" plain onPress={() => void goBack($)}><Text dimColor>← Back</Text></Button>
278
277
  )}
279
- <Button key="wider" plain onPress={() => void resize($, 10)}><Text dimColor>◂ wider</Text></Button>
280
- <Button key="narrower" plain onPress={() => void resize($, -10)}><Text dimColor>narrower ▸</Text></Button>
278
+ <Button key="close" plain onPress={() => void $.ui.close({ id: PANE })}><Text dimColor>× close</Text></Button>
281
279
  </Box>
282
280
  {said !== '' && <Text dimColor>{said}</Text>}
283
281
  {drawn !== null && Client !== undefined && (
284
- <Client key="page" module="./page.tsx" width="100%" props={{ page: drawn, canGoBack: behind.length > 0 }} />
282
+ <Client key="page" module="./page.tsx" props={{ page: drawn, canGoBack: behind.length > 0 }} />
285
283
  )}
286
284
  {drawn !== null && Client === undefined && drawn.rows.map(line)}
287
285
  {drawn !== null && Client === undefined && (
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "system-diff"
7
- version = "0.3.0"
7
+ version = "0.3.4"
8
8
  description = "Draws your system from its code (parts, entry points, data, outside systems) and what each change adds, changes or removes. No dependencies; SVG pictures for PRs and READMEs."
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -5,4 +5,4 @@
5
5
  bp = extract.extract(DirSource(Path(".")), cfg)
6
6
  svg = graph.system_svg(bp, cfg)
7
7
  """
8
- __version__ = "0.3.0"
8
+ __version__ = "0.3.4"
File without changes
File without changes
File without changes