@hacky.software/opencode-todo-supervisor 0.3.6 → 0.3.7
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 +8 -6
- package/package.json +1 -1
- package/src/panel.test.ts +9 -10
- package/src/panel.ts +12 -8
- package/src/tui.tsx +23 -12
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.7
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
This also loads the bundled TUI entry. To pass options, use the object form:
|
|
@@ -174,12 +174,14 @@ effective set is `config.markdownFiles ∪ stored files − removed` per session
|
|
|
174
174
|
|
|
175
175
|
- **Composer strip** (`session.composer.top`): `📋 ███░░░░░ 8/17 open` (plus `⏸`
|
|
176
176
|
while paused), hidden when there is neither a list nor a subscription.
|
|
177
|
-
- **Popover** (`/todos`, palette "Todo: open todos"): a centered overlay dialog
|
|
178
|
-
|
|
179
|
-
|
|
177
|
+
- **Popover** (`/todos`, palette "Todo: open todos"): a centered overlay dialog,
|
|
178
|
+
opened at the widest preset. The body scales with the terminal height (up to
|
|
179
|
+
48 rows) so the key legend stays visible on a short terminal and a tall one
|
|
180
180
|
shows more rows; the header shows `open/total` and the selected `row n/m`. `f`
|
|
181
|
-
cycles the dialog
|
|
182
|
-
|
|
181
|
+
cycles the host dialog width — medium (60), large (88), xlarge (116 cells) —
|
|
182
|
+
and `esc` closes it. Rows are laid out to the card width exactly (and render
|
|
183
|
+
with wrapping off), so content never wraps and the card cannot grow past the
|
|
184
|
+
bottom of the screen.
|
|
183
185
|
- header: progress bar, `open/total`, and a supervision chip (`idle · quiet
|
|
184
186
|
12s · nudges 2`, `working: 1 tool · 1 shell · quiet 4s`, `paused
|
|
185
187
|
(no-progress)`, `not supervised`);
|
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.7",
|
|
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
|
@@ -189,16 +189,15 @@ describe("panel cursor", () => {
|
|
|
189
189
|
expect(popoverBodyHeight(200)).toBe(48)
|
|
190
190
|
})
|
|
191
191
|
|
|
192
|
-
test("popover width
|
|
193
|
-
expect(dialogWidth("medium", 200)).toBe(
|
|
194
|
-
expect(dialogWidth("large", 200)).toBe(
|
|
195
|
-
expect(dialogWidth("xlarge", 200)).toBe(
|
|
196
|
-
expect(dialogWidth("medium", 80)).toBe(
|
|
197
|
-
expect(dialogWidth("large", 80)).toBe(
|
|
198
|
-
expect(dialogWidth("xlarge", 80)).toBe(
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
expect(dialogWidth("xlarge", 120)).toBe(116)
|
|
192
|
+
test("popover width matches the host dialog card and clamps", () => {
|
|
193
|
+
expect(dialogWidth("medium", 200)).toBe(60)
|
|
194
|
+
expect(dialogWidth("large", 200)).toBe(88)
|
|
195
|
+
expect(dialogWidth("xlarge", 200)).toBe(116)
|
|
196
|
+
expect(dialogWidth("medium", 80)).toBe(60)
|
|
197
|
+
expect(dialogWidth("large", 80)).toBe(78)
|
|
198
|
+
expect(dialogWidth("xlarge", 80)).toBe(78)
|
|
199
|
+
// Never wider than the window (minus the host card's 2-cell inset).
|
|
200
|
+
expect(dialogWidth("xlarge", 20)).toBe(20)
|
|
202
201
|
})
|
|
203
202
|
|
|
204
203
|
test("id and priority tags and their column widths", () => {
|
package/src/panel.ts
CHANGED
|
@@ -306,15 +306,19 @@ export function popoverBodyHeight(viewportHeight: number, extraRows = 0): number
|
|
|
306
306
|
|
|
307
307
|
export type DialogSizeName = "medium" | "large" | "xlarge"
|
|
308
308
|
|
|
309
|
-
const DIALOG_FACTOR: Record<DialogSizeName, number> = { medium: 0.7, large: 0.9, xlarge: 0.98 }
|
|
310
|
-
|
|
311
309
|
/**
|
|
312
|
-
*
|
|
313
|
-
*
|
|
314
|
-
*
|
|
310
|
+
* The host draws a dialog inside a card of a *fixed* width per size preset
|
|
311
|
+
* (`medium` 60, `large` 88, `xlarge` 116 cells), capped at the render width
|
|
312
|
+
* minus two columns; the card height hugs its content.
|
|
313
|
+
*
|
|
314
|
+
* Content must be laid out to exactly that width. Text passed a wider budget
|
|
315
|
+
* wraps (`wrapMode` defaults to `"word"`), which grows the content-hugging card
|
|
316
|
+
* past the bottom of the screen. So we mirror the host preset rather than a
|
|
317
|
+
* share of the terminal.
|
|
315
318
|
*/
|
|
319
|
+
const DIALOG_WIDTH: Record<DialogSizeName, number> = { medium: 60, large: 88, xlarge: 116 }
|
|
320
|
+
|
|
321
|
+
/** Content width for the popover dialog: the host card width for `size`. */
|
|
316
322
|
export function dialogWidth(size: DialogSizeName, viewportWidth: number): number {
|
|
317
|
-
|
|
318
|
-
const target = Math.round(total * DIALOG_FACTOR[size])
|
|
319
|
-
return Math.max(36, Math.min(target, total - 4, 200))
|
|
323
|
+
return Math.min(DIALOG_WIDTH[size], Math.max(20, viewportWidth - 2))
|
|
320
324
|
}
|
package/src/tui.tsx
CHANGED
|
@@ -539,7 +539,7 @@ export default Plugin.define({
|
|
|
539
539
|
const background = () => (isCurrent() ? selectedBg() : undefined)
|
|
540
540
|
if (row.kind === "section") {
|
|
541
541
|
return (
|
|
542
|
-
<text fg={muted()}>
|
|
542
|
+
<text fg={muted()} wrapMode="none">
|
|
543
543
|
{prefix() + truncate(row.label + (row.detail ? " · " + row.detail : ""), width() - 6)}
|
|
544
544
|
</text>
|
|
545
545
|
)
|
|
@@ -549,7 +549,7 @@ export default Plugin.define({
|
|
|
549
549
|
const detail = `${row.open} open of ${row.total}`
|
|
550
550
|
return (
|
|
551
551
|
<box backgroundColor={background()}>
|
|
552
|
-
<text fg={isCurrent() ? accent() : base()}>
|
|
552
|
+
<text fg={isCurrent() ? accent() : base()} wrapMode="none">
|
|
553
553
|
{prefix() + (row.collapsed ? "▸ " : "▾ ") + truncate(`${label} · ${detail}`, width() - 6)}
|
|
554
554
|
</text>
|
|
555
555
|
</box>
|
|
@@ -562,7 +562,7 @@ export default Plugin.define({
|
|
|
562
562
|
parts.push(truncate(row.content + (row.line !== undefined ? ` :${row.line}` : ""), contentWidth()))
|
|
563
563
|
return (
|
|
564
564
|
<box backgroundColor={background()}>
|
|
565
|
-
<text fg={row.status === "completed" ? muted() : isCurrent() ? accent() : base()}>
|
|
565
|
+
<text fg={row.status === "completed" ? muted() : isCurrent() ? accent() : base()} wrapMode="none">
|
|
566
566
|
{parts.join(" ")}
|
|
567
567
|
</text>
|
|
568
568
|
</box>
|
|
@@ -571,14 +571,23 @@ export default Plugin.define({
|
|
|
571
571
|
|
|
572
572
|
return (
|
|
573
573
|
<box flexDirection="column">
|
|
574
|
-
<text fg={base()}>
|
|
575
|
-
{
|
|
576
|
-
|
|
577
|
-
|
|
574
|
+
<text fg={base()} wrapMode="none">
|
|
575
|
+
{truncate(
|
|
576
|
+
"Todos " +
|
|
577
|
+
progressBar(totals().total - totals().open, totals().total, barWidth()) +
|
|
578
|
+
" " +
|
|
579
|
+
`${totals().open}/${totals().total} open` +
|
|
580
|
+
(position() ? ` · row ${position()!.index}/${position()!.total}` : ""),
|
|
581
|
+
width() - 2,
|
|
582
|
+
)}
|
|
583
|
+
</text>
|
|
584
|
+
<text fg={muted()} wrapMode="none">
|
|
585
|
+
{truncate("supervision: " + supervisorChip(snap().supervisor), width() - 2)}
|
|
578
586
|
</text>
|
|
579
|
-
<text fg={muted()}>{"supervision: " + supervisorChip(snap().supervisor)}</text>
|
|
580
587
|
<Show when={(filter() ?? "") !== ""}>
|
|
581
|
-
<text fg={muted()}
|
|
588
|
+
<text fg={muted()} wrapMode="none">
|
|
589
|
+
{truncate(`filter: "${filter()}" (press / to change)`, width() - 2)}
|
|
590
|
+
</text>
|
|
582
591
|
</Show>
|
|
583
592
|
<scrollbox
|
|
584
593
|
ref={(node: unknown) => (scrollNode = node as typeof scrollNode)}
|
|
@@ -593,7 +602,9 @@ export default Plugin.define({
|
|
|
593
602
|
)}
|
|
594
603
|
</For>
|
|
595
604
|
</scrollbox>
|
|
596
|
-
<text fg={muted()}
|
|
605
|
+
<text fg={muted()} wrapMode="none">
|
|
606
|
+
{truncate(footerHints(width()).join(" · "), Math.max(10, width() - 2))}
|
|
607
|
+
</text>
|
|
597
608
|
</box>
|
|
598
609
|
)
|
|
599
610
|
}
|
|
@@ -601,7 +612,7 @@ export default Plugin.define({
|
|
|
601
612
|
// ---- popover (an overlay dialog, like the host's own panes) -----------
|
|
602
613
|
|
|
603
614
|
function TodoPopover(props: { sessionID: string }) {
|
|
604
|
-
const [size, setSize] = createSignal<DialogSize>("
|
|
615
|
+
const [size, setSize] = createSignal<DialogSize>("xlarge")
|
|
605
616
|
const cycleSize = () => {
|
|
606
617
|
const next = SIZE_ORDER[(SIZE_ORDER.indexOf(size()) + 1) % SIZE_ORDER.length]!
|
|
607
618
|
setSize(next)
|
|
@@ -625,7 +636,7 @@ export default Plugin.define({
|
|
|
625
636
|
|
|
626
637
|
const openPopover = (sessionID: string): boolean => {
|
|
627
638
|
try {
|
|
628
|
-
context.ui.dialog.set({ size: "
|
|
639
|
+
context.ui.dialog.set({ size: "xlarge", centered: true })
|
|
629
640
|
context.ui.dialog.show(
|
|
630
641
|
() => <TodoPopover sessionID={sessionID} />,
|
|
631
642
|
() => logFile("tui", "popover closed"),
|