tuiboard 0.9.0 → 0.9.2

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/CHANGELOG.md CHANGED
@@ -7,6 +7,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.9.2] - 2026-09-08
11
+
12
+ ### Fixed
13
+ - **A narrow terminal now opens on Today/Tomorrow**, like a wide one, instead of
14
+ on the first column of the first board. The default was conditioned on the
15
+ planner *fitting* rather than being enabled — the last place still reading
16
+ "does not fit" as "does not exist" — so single-pane skipped it.
17
+
18
+ ## [0.9.1] - 2026-09-08
19
+
20
+ ### Fixed
21
+ - **Modals were unreadable in single-pane.** With one zone filling the screen a
22
+ dialog floated over it as an absolute overlay — and had nothing to paint over,
23
+ because the theme leaves panel backgrounds transparent so the terminal shows
24
+ through. The keyboard reference interleaved with the pane character by
25
+ character; the new-task and edit dialogs were effectively invisible. A modal
26
+ now takes the pane's place, exactly as the four-zone layout drops it into the
27
+ Agenda's slot: with one zone on screen, that zone *is* the slot.
28
+ - **A dialog standing in for a pane now fills it**, in both axes, like the zone
29
+ it replaces — instead of keeping a slot-sized box while the rest of the strip
30
+ sits empty, which read as a window that had failed to open. In the four-zone
31
+ layout it still matches the Agenda's slot to the cell, or the whole dashboard
32
+ would shift when a modal opens; verified by the frame borders landing on
33
+ identical columns with and without a modal.
34
+
10
35
  ## [0.9.0] - 2026-09-08
11
36
 
12
37
  ### Added
@@ -273,6 +298,8 @@ First public release on npm. This entry captures the full feature set at launch.
273
298
 
274
299
  Built with [OpenTUI](https://opentui.com) + SolidJS on Bun.
275
300
 
301
+ [0.9.2]: https://github.com/NazzarenoGiannelli/tuiboard/releases/tag/v0.9.2
302
+ [0.9.1]: https://github.com/NazzarenoGiannelli/tuiboard/releases/tag/v0.9.1
276
303
  [0.9.0]: https://github.com/NazzarenoGiannelli/tuiboard/releases/tag/v0.9.0
277
304
  [0.8.5]: https://github.com/NazzarenoGiannelli/tuiboard/releases/tag/v0.8.5
278
305
  [0.8.4]: https://github.com/NazzarenoGiannelli/tuiboard/releases/tag/v0.8.4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tuiboard",
3
- "version": "0.9.0",
3
+ "version": "0.9.2",
4
4
  "description": "Terminal kanban for markdown task boards, with optional Today/Tomorrow planner, 24h agenda + calendar overlay, and a live Claude Code agent view. Use only the panels you want.",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/app.tsx CHANGED
@@ -126,9 +126,14 @@ applyResponsiveLayout();
126
126
  process.stdout.on("resize", applyResponsiveLayout);
127
127
 
128
128
  // Land on the Today/Tomorrow panel by default — for a daily-planning tool the
129
- // first question is "what's on my plate today", and that panel answers it. On
130
- // a narrow terminal where the panel auto-hides, fall back to the board.
131
- if (!needsOnboarding && store.state.ui.visibleZones.planner) {
129
+ // first question is "what's on my plate today", and that panel answers it.
130
+ //
131
+ // The condition is that the planner is *enabled*, not that it fits: on a narrow
132
+ // terminal it does not fit beside anything, but single-pane makes it the one
133
+ // thing on screen, which is exactly where a daily planner belongs. Keying this
134
+ // off `visibleZones` was the last place still reading "does not fit" as "does
135
+ // not exist", and it left a vertical strip opening on the first board column.
136
+ if (!needsOnboarding && store.state.ui.enabledZones.planner) {
132
137
  store.setActiveZone("planner");
133
138
  }
134
139
 
package/src/ui/Modal.tsx CHANGED
@@ -11,7 +11,7 @@
11
11
  * checks `ui.modal` first and bails if set (only Escape passes through).
12
12
  */
13
13
 
14
- import { For, Show, createMemo, createSignal } from "solid-js";
14
+ import { For, Show, createContext, createMemo, createSignal, useContext } from "solid-js";
15
15
 
16
16
  import { isTask } from "~/parser/markdown";
17
17
  import {
@@ -29,14 +29,23 @@ import type { PriorityLevel, TimeBlock } from "~/types";
29
29
  * slot (the Dashboard renders it there while a modal is open) with no reflow. */
30
30
  const MODAL_WIDTH = AGENDA_WIDTH;
31
31
 
32
+ /**
33
+ * Whether the dialog is standing in for a whole pane (single-pane) or sitting
34
+ * in the Agenda's slot (four-zone). It decides how wide the dialog may be, and
35
+ * only ModalLayer is in a position to know.
36
+ */
37
+ const SinglePaneContext = createContext<() => boolean>(() => false);
38
+
32
39
  export function ModalLayer(props: { store: TuiStore }) {
33
40
  const modal = createMemo(() => props.store.state.ui.modal);
34
41
  return (
42
+ <SinglePaneContext.Provider value={() => props.store.singlePane()}>
35
43
  <Show when={modal()}>
36
44
  {/* Each modal's DialogShell IS the panel box (border + title + slot
37
45
  dimensions), so it drops into the Agenda's slot directly. */}
38
46
  <ModalRouter store={props.store} modal={modal()!} />
39
47
  </Show>
48
+ </SinglePaneContext.Provider>
40
49
  );
41
50
  }
42
51
 
@@ -194,7 +203,30 @@ function BoardNewModal(props: { store: TuiStore }) {
194
203
  );
195
204
  }
196
205
 
206
+ /**
207
+ * How wide the dialog may be.
208
+ *
209
+ * In the four-zone layout it takes the Agenda's slot and must match it to the
210
+ * cell, or the whole dashboard shifts when a modal opens — that invariant is
211
+ * why the requested width used to be ignored outright. In single-pane the
212
+ * dialog takes a whole pane's place instead, so there it can use what the
213
+ * terminal actually offers: the keyboard reference asks for 92 columns and is
214
+ * unreadable squeezed into 50, while a 60-column strip needs it to shrink
215
+ * rather than overflow.
216
+ */
217
+ function dialogWidth(singlePane: boolean): number {
218
+ if (!singlePane) return MODAL_WIDTH;
219
+ // Standing in for a pane means behaving like one: take the strip. A dialog
220
+ // that keeps its slot-sized box while the rest of the screen sits empty
221
+ // reads as a window that failed to open, not as a panel.
222
+ const terminal = process.stdout.columns ?? 80;
223
+ return Math.max(20, terminal - 4);
224
+ }
225
+
197
226
  function DialogShell(props: DialogShellProps) {
227
+ const singlePane = useContext(SinglePaneContext);
228
+ const width = () => dialogWidth(singlePane());
229
+ // `width` survives as the four-zone hint it always was; single-pane fills.
198
230
  void props.width;
199
231
  return (
200
232
  <box
@@ -204,9 +236,12 @@ function DialogShell(props: DialogShellProps) {
204
236
  // slot at the exact same size and the dashboard doesn't shift when a
205
237
  // modal opens. The title rides in the top border like the columns/zones.
206
238
  flexDirection: "column",
207
- width: MODAL_WIDTH,
208
- minWidth: MODAL_WIDTH,
209
- flexGrow: 0,
239
+ width: width(),
240
+ minWidth: Math.min(MODAL_WIDTH, width()),
241
+ // In the Agenda's slot the box must not grow, or the dashboard shifts.
242
+ // Filling a pane, it must — in both axes, like the zone it replaces.
243
+ flexGrow: singlePane() ? 1 : 0,
244
+ height: singlePane() ? "100%" : undefined,
210
245
  marginLeft: 1,
211
246
  backgroundColor: T.panelBgActive,
212
247
  border: true,
@@ -55,17 +55,19 @@ export function Dashboard(props: { store: TuiStore }) {
55
55
  * zone of the normal layout and BoardOnly already respects ui.zoomed
56
56
  * to render only the active panel between them.
57
57
  *
58
- * Modals: the normal layout drops them into the Agenda's slot, which doesn't
59
- * exist while zoomed so here the modal floats as a centered absolute overlay
60
- * on top of the zoomed view. The user stays zoomed; close the modal and the
61
- * zoomed view is exactly as they left it (no exit-zoom / re-zoom flip).
58
+ * Modals take the pane's place, exactly as the four-zone layout drops them
59
+ * into the Agenda's slot: with one zone filling the screen, that zone IS the
60
+ * slot. They used to float as an absolute overlay, which had nothing to paint
61
+ * over the theme leaves panel backgrounds transparent so the terminal shows
62
+ * through — so the dialog's text interleaved with the pane underneath and both
63
+ * became unreadable. Closing returns to the pane exactly as it was.
62
64
  */
63
65
  function ZoomedLayout(props: { store: TuiStore }) {
64
66
  const ui = () => props.store.state.ui;
65
67
  const zone = () => ui().activeZone;
66
68
 
67
69
  return (
68
- <>
70
+ <Show when={ui().modal} fallback={
69
71
  <Show when={zone() === "timeline"} fallback={
70
72
  <Show when={zone() === "agents"} fallback={<BoardOnly store={props.store} />}>
71
73
  <AgentsOnly store={props.store} />
@@ -73,27 +75,9 @@ function ZoomedLayout(props: { store: TuiStore }) {
73
75
  }>
74
76
  <TimelineOnly store={props.store} />
75
77
  </Show>
76
- {/* Modal overlay — only while zoomed AND a modal is open. Absolute + high
77
- zIndex so it paints over the zoomed view; centered; transparent
78
- backdrop so the board stays visible behind the (opaque) modal panel. */}
79
- <Show when={ui().modal}>
80
- <box
81
- style={{
82
- position: "absolute",
83
- top: 0,
84
- left: 0,
85
- right: 0,
86
- bottom: 0,
87
- zIndex: 100,
88
- flexDirection: "column",
89
- alignItems: "center",
90
- justifyContent: "center",
91
- }}
92
- >
93
- <ModalLayer store={props.store} />
94
- </box>
95
- </Show>
96
- </>
78
+ }>
79
+ <ModalLayer store={props.store} />
80
+ </Show>
97
81
  );
98
82
  }
99
83