@hacky.software/opencode-todo-supervisor 0.3.7 → 0.3.9
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.
- package/README.md +7 -3
- package/package.json +1 -1
- package/src/panel.test.ts +22 -0
- package/src/panel.ts +26 -0
- package/src/tui.tsx +12 -4
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ Tested against OpenCode `2.0.24`.
|
|
|
23
23
|
## Install
|
|
24
24
|
|
|
25
25
|
```sh
|
|
26
|
-
opencode plugin add @hacky.software/opencode-todo-supervisor@0.3.
|
|
26
|
+
opencode plugin add @hacky.software/opencode-todo-supervisor@0.3.9
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
This also loads the bundled TUI entry. To pass options, use the object form:
|
|
@@ -197,7 +197,11 @@ effective set is `config.markdownFiles ∪ stored files − removed` per session
|
|
|
197
197
|
- it re-reads every 3 s while open, so edits made outside OpenCode show up
|
|
198
198
|
without waiting for an event.
|
|
199
199
|
- **Side panel** (palette "Todo: open side panel"): the same list docked to
|
|
200
|
-
`session.panel` (`todo.todos`), with `f` for fullscreen.
|
|
200
|
+
`session.panel` (`todo.todos`), with `f` for fullscreen. Its keys are live only
|
|
201
|
+
while the panel is focused *and* no overlay is up (dialog, menu, completions,
|
|
202
|
+
composer), so a docked panel never steals the arrow keys from the model picker,
|
|
203
|
+
command palette or the prompt's completions. The gate is applied per key at
|
|
204
|
+
dispatch, not on the layer: the host ignores a layer-level `enabled`.
|
|
201
205
|
- `/todos add <path>` and `/todos remove <path>` manage subscriptions from the
|
|
202
206
|
prompt.
|
|
203
207
|
- **Toasts** on `nudged` (idle vs stalled wording) and on the all-done transition.
|
|
@@ -380,7 +384,7 @@ There is no filesystem watcher. Instead:
|
|
|
380
384
|
(source edits and config content changes; `touch` alone does not).
|
|
381
385
|
- `opencode2 reload` forces reconciliation; `opencode2 service restart` only as a
|
|
382
386
|
last resort (it interrupts sessions).
|
|
383
|
-
- Tests: `bun test` (
|
|
387
|
+
- Tests: `bun test` (123 tests across parser, storage, tools, RPC, state machine,
|
|
384
388
|
panel helpers).
|
|
385
389
|
- Watch logs: `tail -f ~/.local/share/opencode/log/todo-supervisor.log`.
|
|
386
390
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hacky.software/opencode-todo-supervisor",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.9",
|
|
4
4
|
"description": "OpenCode V2 plugin that gives every session a todo list (todo_write/todo_read), supervises idle sessions until the list is done, and can drive checklists in markdown files from a TUI panel.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
package/src/panel.test.ts
CHANGED
|
@@ -10,7 +10,9 @@ import {
|
|
|
10
10
|
isSelectable,
|
|
11
11
|
itemColumns,
|
|
12
12
|
moveCursor,
|
|
13
|
+
panelKeys,
|
|
13
14
|
popoverBodyHeight,
|
|
15
|
+
popoverKeys,
|
|
14
16
|
priorityTag,
|
|
15
17
|
progressBar,
|
|
16
18
|
quietLabel,
|
|
@@ -200,6 +202,26 @@ describe("panel cursor", () => {
|
|
|
200
202
|
expect(dialogWidth("xlarge", 20)).toBe(20)
|
|
201
203
|
})
|
|
202
204
|
|
|
205
|
+
test("the panel layer yields to overlays that own the keyboard", () => {
|
|
206
|
+
// Normal session view: live while focused.
|
|
207
|
+
expect(panelKeys(true, "base")).toBe(true)
|
|
208
|
+
expect(panelKeys(false, "base")).toBe(false)
|
|
209
|
+
// The model picker, dialogs, menus and completions all take the keyboard.
|
|
210
|
+
for (const mode of ["modal", "menu", "autocomplete", "composer"]) {
|
|
211
|
+
expect(panelKeys(true, mode)).toBe(false)
|
|
212
|
+
}
|
|
213
|
+
// Any other mode (including the plain session view) keeps the panel live.
|
|
214
|
+
expect(panelKeys(true, "base")).toBe(true)
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
test("the popover keeps the keyboard in its own dialog", () => {
|
|
218
|
+
expect(popoverKeys("modal")).toBe(true)
|
|
219
|
+
expect(popoverKeys("base")).toBe(true)
|
|
220
|
+
expect(popoverKeys("menu")).toBe(false)
|
|
221
|
+
expect(popoverKeys("autocomplete")).toBe(false)
|
|
222
|
+
expect(popoverKeys("composer")).toBe(false)
|
|
223
|
+
})
|
|
224
|
+
|
|
203
225
|
test("id and priority tags and their column widths", () => {
|
|
204
226
|
expect(idTag(undefined)).toBe("")
|
|
205
227
|
expect(idTag("12")).toBe("#12")
|
package/src/panel.ts
CHANGED
|
@@ -322,3 +322,29 @@ const DIALOG_WIDTH: Record<DialogSizeName, number> = { medium: 60, large: 88, xl
|
|
|
322
322
|
export function dialogWidth(size: DialogSizeName, viewportWidth: number): number {
|
|
323
323
|
return Math.min(DIALOG_WIDTH[size], Math.max(20, viewportWidth - 2))
|
|
324
324
|
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Keymap modes that own the keyboard while they are up: `modal` (dialogs, the
|
|
328
|
+
* model picker, confirmations), `menu` (the command/palette menus),
|
|
329
|
+
* `autocomplete` (the prompt's completion list) and `composer` (composer
|
|
330
|
+
* overlays). They all drive themselves with the arrow keys.
|
|
331
|
+
*/
|
|
332
|
+
const OVERLAY_MODES: ReadonlySet<string> = new Set(["modal", "menu", "autocomplete", "composer"])
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Whether the side panel's keys should be live. Only the overlay modes take the
|
|
336
|
+
* keyboard away; any other mode (the plain session view included) keeps the
|
|
337
|
+
* panel live while it is focused. Without this a focused docked panel swallows
|
|
338
|
+
* the model picker's up/down.
|
|
339
|
+
*/
|
|
340
|
+
export function panelKeys(focused: boolean, mode: string): boolean {
|
|
341
|
+
return focused && !OVERLAY_MODES.has(mode)
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Whether the popover's keys should be live. The popover *is* a dialog, so
|
|
346
|
+
* `modal` is ours; any other overlay takes the keyboard away from it.
|
|
347
|
+
*/
|
|
348
|
+
export function popoverKeys(mode: string): boolean {
|
|
349
|
+
return mode === "modal" || !OVERLAY_MODES.has(mode)
|
|
350
|
+
}
|
package/src/tui.tsx
CHANGED
|
@@ -26,7 +26,9 @@ import {
|
|
|
26
26
|
idTag,
|
|
27
27
|
itemColumns,
|
|
28
28
|
moveCursor,
|
|
29
|
+
panelKeys,
|
|
29
30
|
popoverBodyHeight,
|
|
31
|
+
popoverKeys,
|
|
30
32
|
priorityTag,
|
|
31
33
|
progressBar,
|
|
32
34
|
rowIndex,
|
|
@@ -409,8 +411,9 @@ export default Plugin.define({
|
|
|
409
411
|
})
|
|
410
412
|
|
|
411
413
|
context.keymap.layer(() => ({
|
|
414
|
+
// "global" keeps the layer mode-agnostic; the actual gating is done
|
|
415
|
+
// per command below.
|
|
412
416
|
mode: "global",
|
|
413
|
-
enabled: () => props.active(),
|
|
414
417
|
priority: 10,
|
|
415
418
|
commands: [
|
|
416
419
|
{ title: "Todo: select next", bind: "down", run: () => move(1) },
|
|
@@ -491,7 +494,12 @@ export default Plugin.define({
|
|
|
491
494
|
}),
|
|
492
495
|
},
|
|
493
496
|
{ title: "Todo: close", bind: "escape", run: () => props.close() },
|
|
494
|
-
|
|
497
|
+
// The host drops a *layer*-level `enabled`; the *command*-level one
|
|
498
|
+
// is consulted on every dispatch. Gate each key here so it yields —
|
|
499
|
+
// and falls through — the moment an overlay (model picker, palette,
|
|
500
|
+
// completion list, composer) owns the keyboard while a todo surface
|
|
501
|
+
// happens to still be focused.
|
|
502
|
+
].map((command) => ({ ...command, enabled: () => props.active() })),
|
|
495
503
|
}))
|
|
496
504
|
|
|
497
505
|
const totals = () => {
|
|
@@ -625,7 +633,7 @@ export default Plugin.define({
|
|
|
625
633
|
return (
|
|
626
634
|
<TodoList
|
|
627
635
|
sessionID={props.sessionID}
|
|
628
|
-
active={() =>
|
|
636
|
+
active={() => popoverKeys(context.keymap.mode.current())}
|
|
629
637
|
width={() => dialogWidth(size(), viewportWidth())}
|
|
630
638
|
height={() => popoverBodyHeight(viewportHeight())}
|
|
631
639
|
close={() => context.ui.dialog.clear()}
|
|
@@ -660,7 +668,7 @@ export default Plugin.define({
|
|
|
660
668
|
<Show when={panel.name === "todo.todos"}>
|
|
661
669
|
<TodoList
|
|
662
670
|
sessionID={panel.sessionID}
|
|
663
|
-
active={() => panel.focused}
|
|
671
|
+
active={() => panelKeys(panel.focused, context.keymap.mode.current())}
|
|
664
672
|
width={() => panel.width}
|
|
665
673
|
close={() => panel.close()}
|
|
666
674
|
cycleSize={() => panel.toggleFullscreen()}
|