@cardinal4/opencode-fold-diffs 0.3.2 → 0.3.4

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 (3) hide show
  1. package/README.md +4 -2
  2. package/package.json +1 -1
  3. package/tui.js +39 -12
package/README.md CHANGED
@@ -124,7 +124,7 @@ The V2 TUI plugin API (`@opencode/plugin/tui`) has no slot for message parts, so
124
124
 
125
125
  Renderable classes are matched by duck-typing (`typeof node.diff === "string"`, `content` + `filetype` for code) rather than `instanceof`, because the opentui classes are minified in the shipped binary and their names are not stable.
126
126
 
127
- Blocks are re-scanned on `message.part.updated` and `message.updated`, plus a 2 s sweep that catches a session opened from history, whose parts land before any event this plugin sees.
127
+ Blocks are re-scanned on V2's `session.message.content.updated`, `session.tool.success` and `session.tool.failed`, plus `session.step.ended` and `session.execution.succeeded` (`session.shell.ended` joins them when `bash` folding is on). A 2 s sweep catches a session opened from history, whose parts land before any event this plugin sees.
128
128
 
129
129
  ## Migrating from V1
130
130
 
@@ -132,6 +132,8 @@ V1 plugin implementations do not run in V2. The V2 port made these changes:
132
132
 
133
133
  - Entrypoint is a `{ id, setup(context) }` definition (`Plugin.define()` is an identity helper, so the shape is the same). `setup` returns the cleanup function instead of `api.lifecycle.onDispose`.
134
134
  - `api.renderer` → `context.renderer`; `api.route.current.name` → `context.ui.router.current().type`; `api.event.on` → `context.data.on`; `api.ui.toast` → `context.ui.toast.show`; `api.keymap.registerLayer` → `context.keymap.layer`.
135
+ - `context.keymap.layer` is called directly from `setup` on 2.0.23+; earlier V2 builds read Solid context there, so the plugin still registers from a component mounted in the `app` slot for them.
136
+ - Event names are V2's: `message.updated` / `message.part.updated` are legacy schemas in V2 and are never emitted. The plugin listens to `session.message.content.updated`, `session.tool.success` / `session.tool.failed`, `session.step.ended` and `session.execution.succeeded` (plus `session.shell.ended` when bash folding is on). These names exist from 2.0.10 on, so older V2 installs get the same fix without upgrading.
135
137
  - Tool headers are no longer one string. V1 rendered `"← Edit src/app.ts"`; V2 renders a label node plus a path node, so detection and the stats suffix target the label.
136
138
  - `bash` defaults to `false`, because V2 now trims long commands to two lines itself.
137
139
  - Config moves from `tui.json` to `cli.json` (or `opencode.json(c)`).
@@ -153,7 +155,7 @@ V1 plugin implementations do not run in V2. The V2 port made these changes:
153
155
 
154
156
  ## Status
155
157
 
156
- Written against **opencode v2.0.10 / v2.0.11**. The tree-walking, block matching, fold/unfold and toggle logic run green against a mock renderer tree shaped like V2's (`node --test`), and the fold was checked against a real transcript: `visible = false` (Yoga `display: none`) is what actually hides a body, not `maxHeight`.
158
+ Written against **opencode v2.0.24**, and compatible across the V2 line: the `session.*` events this version listens to exist from v2.0.10 on, so an older V2 install gets the fix without upgrading. The tree-walking, block matching, fold/unfold and toggle logic run green against a mock renderer tree shaped like V2's (`node --test`), and the fold was checked against a real transcript: `visible = false` (Yoga `display: none`) is what actually hides a body, not `maxHeight`.
157
159
 
158
160
  Edits with fewer than `min_lines` changed lines (default 6) are left expanded on purpose — a one-line change is already its own summary. Set `min_lines: 0` to fold every file block.
159
161
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cardinal4/opencode-fold-diffs",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
4
4
  "description": "Unofficial OpenCode V2 fork of opencode-fold-diffs: write, edit and apply_patch blocks render folded to their header line, click to expand.",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/tui.js CHANGED
@@ -10,15 +10,15 @@
10
10
  // closed without one, so this does it from a plugin.
11
11
  //
12
12
  // A folded block renders as its header line -- "# Wrote 40 lines · click to
13
- // expand src/app.ts" -- and opens on click, or with ctrl+o for every block at
14
- // once.
13
+ // expand src/app.ts" -- and opens on click, or with the optional `key`
14
+ // binding for every block at once.
15
15
  //
16
16
  // Options (cli.json -> ["opencode-fold-diffs", { ... }] or opencode.json(c)):
17
17
  // lines lines of the body left visible when folded (default 0, title only)
18
18
  // min_lines leave blocks with fewer content lines alone (default 6)
19
19
  // stats append "40 lines · click to expand" to the header (default true)
20
20
  // folded new blocks start folded (default true)
21
- // key binding that folds/unfolds every block (default "ctrl+o")
21
+ // key binding that folds/unfolds every block (default "", none)
22
22
  // bash fold long bash commands too (default false on V2)
23
23
  // bash_lines rows of the command left visible when folded (default 1)
24
24
  //
@@ -241,6 +241,16 @@ function dumpNode(node, depth, lines, limit) {
241
241
  for (const kid of kids) dumpNode(kid, depth + 1, lines, limit);
242
242
  }
243
243
 
244
+ // 2.0.23 was the first V2 release whose `keymap.layer` may be called from
245
+ // setup; before that it reads Solid context and needs a mounted component.
246
+ // An unparseable version takes the fallback, which works on every V2 build.
247
+ function supportsSetupKeymap(version) {
248
+ const match = /(\d+)\.(\d+)\.(\d+)/.exec(String(version ?? ""));
249
+ if (!match) return false;
250
+ const [major, minor, patch] = match.slice(1).map(Number);
251
+ return major > 2 || (major === 2 && (minor > 0 || patch >= 23));
252
+ }
253
+
244
254
  export const PLUGIN_ID = "opencode-fold-diffs";
245
255
 
246
256
  export default {
@@ -420,17 +430,26 @@ export default {
420
430
  }, 120);
421
431
  }
422
432
 
423
- const offs = [
424
- context.data.on("message.part.updated", schedule),
425
- context.data.on("message.updated", schedule),
433
+ // V1's message.part.updated / message.updated are legacy schemas in V2 --
434
+ // the client emits the session.* events instead, so the V1 names were a
435
+ // silent no-op and a new block only folded on the sweeping timer.
436
+ const events = [
437
+ "session.message.content.updated", // a part was appended or replaced
438
+ "session.tool.success", // a tool body (diff or file) appeared
439
+ "session.tool.failed", // a failed tool still renders its block
440
+ "session.step.ended", // the step's final content has arrived
441
+ "session.execution.succeeded", // the turn settled; take a final look
426
442
  ];
443
+ if (shell) events.push("session.shell.ended"); // the command now carries its "$ " prefix
444
+ const offs = events.map((name) => context.data.on(name, schedule));
427
445
  const timer = setInterval(sweep, SWEEP_MS);
428
446
  schedule();
429
447
 
430
- // `keymap.layer` reads Solid context, so it must run while a component is
431
- // rendering, not directly during setup. A null component mounted in the
432
- // `app` slot owns the layer for the plugin's lifetime.
433
- function FoldCommands() {
448
+ // The command layer itself is identical everywhere; only who registers it
449
+ // differs. 2.0.23+ runs `keymap.layer` from setup and owns its teardown.
450
+ // Older V2 reads Solid context when registering, so there the layer has to
451
+ // be created by a mounted component. The `app` slot supplies one.
452
+ function registerCommands() {
434
453
  context.keymap.layer(() => ({
435
454
  mode: "global",
436
455
  priority: 100,
@@ -508,9 +527,17 @@ export default {
508
527
  ],
509
528
  bindings: opts.key ? ["opencode-fold-diffs.toggle"] : [],
510
529
  }));
511
- return null;
512
530
  }
513
- const stopCommands = context.ui.slot({ append: "app", render: () => FoldCommands() });
531
+ let stopCommands = () => {};
532
+ if (supportsSetupKeymap(context.app?.version)) {
533
+ registerCommands();
534
+ } else {
535
+ function FoldCommands() {
536
+ registerCommands();
537
+ return null;
538
+ }
539
+ stopCommands = context.ui.slot({ append: "app", render: () => FoldCommands() });
540
+ }
514
541
 
515
542
  return () => {
516
543
  stopCommands();