@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 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.6
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
- Its body and width scale with the terminal (up to 48 rows / a share of the
179
- window), so the key legend stays visible on a short terminal and a tall one
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 size — medium (70%), large (90%), xlarge (98% of the
182
- terminal width, capped at 200 cells) — and `esc` closes it.
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.6",
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 follows the dialog size and clamps", () => {
193
- expect(dialogWidth("medium", 200)).toBe(140)
194
- expect(dialogWidth("large", 200)).toBe(180)
195
- expect(dialogWidth("xlarge", 200)).toBe(196)
196
- expect(dialogWidth("medium", 80)).toBe(56)
197
- expect(dialogWidth("large", 80)).toBe(72)
198
- expect(dialogWidth("xlarge", 80)).toBe(76)
199
- expect(dialogWidth("xlarge", 20)).toBe(36)
200
- // xlarge tracks the window instead of a fixed cap.
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
- * Content width for the popover dialog, as a fraction of the terminal width so
313
- * it scales with the window (`medium` 70%, `large` 90%, `xlarge` 98%), clamped
314
- * for small terminals and capped so it never spans an ultrawide screen.
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
- const total = Math.max(40, viewportWidth)
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
- {"Todos " + progressBar(totals().total - totals().open, totals().total, barWidth()) + " "}
576
- {`${totals().open}/${totals().total} open` +
577
- (position() ? ` · row ${position()!.index}/${position()!.total}` : "")}
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()}>{`filter: "${filter()}" (press / to change)`}</text>
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()}>{truncate(footerHints(width()).join(" · "), Math.max(10, width() - 2))}</text>
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>("large")
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: "large", centered: true })
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"),