tuiboard 0.6.2 → 0.7.1

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.
@@ -31,6 +31,20 @@ done_column: Done
31
31
  # doesn't exist in a board, tuiboard creates it on the fly.
32
32
  archive_column: Archive
33
33
 
34
+ # Optional: pick which zones you want. The board is always on; the other three
35
+ # are yours to turn off. Each takes one of:
36
+ # on enabled and shown at launch (the default)
37
+ # off disabled entirely — never rendered, skipped by Shift-Tab, its F-key
38
+ # is inert, and its background work never runs (no calendar fetch, no
39
+ # ~/.claude reads). Use this for a pure kanban, or kanban + one panel.
40
+ # hidden enabled but collapsed at launch — reveal it any time with its F-key
41
+ # Want just a kanban? Set agenda and agents to off. (true/false also work.)
42
+ #
43
+ # zones:
44
+ # planner: on # Today/Tomorrow cross-board panel (F1)
45
+ # agenda: on # 24h agenda + calendar overlay (F2)
46
+ # agents: on # live Claude Code session view (F3)
47
+
34
48
  # Optional: override what Enter does in the Agents zone ("open the selected
35
49
  # Claude Code session"). An argv array — the tokens {cwd} and {sessionId} are
36
50
  # substituted, then it's run directly (no shell). Point it at your own script
package/CHANGELOG.md ADDED
@@ -0,0 +1,98 @@
1
+ # Changelog
2
+
3
+ All notable changes to **tuiboard** are documented here.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.7.1] - 2026-06-02
9
+
10
+ ### Changed
11
+ - Modals (new task, schedule, time block, assign, delete, detail, search, help)
12
+ now open in the Agenda's slot — an opaque panel of the same width — instead of
13
+ a side panel that pushed the dashboard left. Opening a modal no longer reflows
14
+ the board/planner; the Agenda returns when the modal closes.
15
+ - Modal titles now ride in the panel's top border (`┤ … ├`), matching the board
16
+ columns and the zones, instead of sitting as a body text line.
17
+ - A clipped board column keeps its task rows until it's scrolled down to less
18
+ than half visible (previously: blanked as soon as it was clipped at all), so a
19
+ column that's mostly on-screen stays useful.
20
+
21
+ ## [0.7.0] - 2026-06-01
22
+
23
+ ### Added
24
+ - **Configurable zones.** A `zones:` config block turns the planner, agenda, or
25
+ agents view off (`off`), starts it collapsed (`hidden`), or leaves it on
26
+ (`on`, the default; `true`/`false` alias `on`/`off`). The board is always on.
27
+ tuiboard can now be a pure kanban, kanban + calendar, kanban + agents, or any
28
+ mix. A disabled zone is never rendered, is skipped by `Shift-Tab`, has an
29
+ inert F-key, and **its background work never starts** — no calendar fetch and
30
+ no `~/.claude` reads when the agents zone is off.
31
+ - Documented zones in the README, the AI setup prompt, and `config.example.yaml`.
32
+
33
+ ### Changed
34
+ - Renamed the internal "virtual" zone to **"planner"** throughout (code,
35
+ identifiers, comments, and the `VirtualPanel`/`virtual-panel` files) for
36
+ clarity. The visible "Today/Tomorrow" panel is unchanged.
37
+ - Reworked the responsive layout to combine three inputs — `enabled ∧ desired ∧
38
+ fits-width`. Auto-hide now only reports what fits; it never force-shows a
39
+ disabled or intentionally-hidden zone, and `F1`/`F2`/`F3` toggles persist
40
+ across terminal resizes.
41
+
42
+ ## [0.6.2] - 2026-05-30
43
+
44
+ ### Changed
45
+ - Updated the hero screenshot to show the live calendar overlay (Google +
46
+ Microsoft 365 events side by side) and the aligned agent rows.
47
+ - Refreshed the README intro to mention the calendar overlay.
48
+
49
+ ## [0.6.1] - 2026-05-30
50
+
51
+ ### Added
52
+ - **Manual full-refresh key (`r`).** Reloads boards from disk, rescans agents,
53
+ and force-refetches the agenda calendar (bypassing the 30-minute cache) so
54
+ externally-edited events show without a restart.
55
+
56
+ ### Changed
57
+ - Day-navigation keys (`[` / `]` / `\`) now work from any zone, not just when
58
+ the agenda is focused; pressing one also moves focus to the agenda.
59
+ - Added arrow keys and `r refresh` to the bottom cheat-sheet; the day-navigation
60
+ hint is now always visible in the agenda's resting state.
61
+ - Agent rows right-align the activity age in a fixed-width field so the end of
62
+ each working directory lines up across rows.
63
+
64
+ ## [0.6.0] - 2026-05-30
65
+
66
+ First public release on npm. This entry captures the full feature set at launch.
67
+
68
+ ### Added
69
+ - **Kanban board** over plain CommonMark files using the Obsidian Tasks-plugin
70
+ emoji vocabulary — no lock-in, the files stay yours. Multiple boards as tabs;
71
+ `##` headings become columns; `Done` and `Archive` columns are treated
72
+ specially. Quick-add syntax (`@assignee`, `#tag`, scheduling, time blocks,
73
+ priority), multi-select (`Space`), undo (`Ctrl-Z`), filters, search (`/`),
74
+ zoom (`z`), and atomic file round-trips with an external-edit watcher.
75
+ - **Planner** — a Today/Tomorrow panel aggregating everything scheduled across
76
+ all boards.
77
+ - **Agenda** — a 24-hour timeline with click-to-arm time-blocking, plus a
78
+ read-only **calendar overlay** for Google Calendar and Microsoft 365
79
+ (dependency-light, bring-your-own-credentials, all-day events skipped, each
80
+ calendar in its own color). Day-navigation with `[` / `]` / `\` pages tasks
81
+ and events across days.
82
+ - **`tuiboard calendar-setup`** — one-time OAuth for new users (Google browser
83
+ flow, Microsoft device-code flow); prints the exact `calendars:` block to add.
84
+ - **Live agents view** — reads local Claude Code sessions from `~/.claude` with
85
+ zero setup, showing status, branch, and last activity. `Enter` opens a session
86
+ in a terminal; the launch command is overridable via `resume_command`.
87
+ - **Keyboard-first with full mouse support**, a responsive multi-zone layout
88
+ that adapts to terminal width, standalone `--view=` modes, and the `tb` alias.
89
+ - Config resolution via `$TUIBOARD_CONFIG`, a project-local `.tuiboard/`, the
90
+ global `~/.config/tuiboard/`, or a cwd fallback scan.
91
+
92
+ Built with [OpenTUI](https://opentui.com) + SolidJS on Bun.
93
+
94
+ [0.7.1]: https://github.com/NazzarenoGiannelli/tuiboard/releases/tag/v0.7.1
95
+ [0.7.0]: https://github.com/NazzarenoGiannelli/tuiboard/releases/tag/v0.7.0
96
+ [0.6.2]: https://github.com/NazzarenoGiannelli/tuiboard/releases/tag/v0.6.2
97
+ [0.6.1]: https://github.com/NazzarenoGiannelli/tuiboard/releases/tag/v0.6.1
98
+ [0.6.0]: https://github.com/NazzarenoGiannelli/tuiboard/releases/tag/v0.6.0
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # tuiboard
2
2
 
3
- A terminal dashboard that unifies **kanban**, a **Today/Tomorrow virtual
3
+ A terminal dashboard that unifies **kanban**, a **Today/Tomorrow planner
4
4
  panel**, a **24-hour agenda** (with a read-only Google / Microsoft 365
5
5
  calendar overlay), and a **live agent view** for Claude Code sessions — all
6
6
  on top of plain markdown task files.
@@ -98,12 +98,17 @@ https://github.com/NazzarenoGiannelli/tuiboard). Set it up for me from scratch:
98
98
  3. Create a global config at `~/.config/tuiboard/config.yaml` (resolve the real
99
99
  home path) with a `boards:` list pointing at those files by ABSOLUTE path,
100
100
  plus `assignees: [...]`, `done_column: Done`, `archive_column: Archive`.
101
- 4. Do NOT configure the Agent view tuiboard reads `~/.claude` automatically.
101
+ 4. Ask me which zones I want besides the kanban board: the Today/Tomorrow
102
+ planner, the 24h agenda, and the live Claude Code agents view. For any I
103
+ don't want, add a `zones:` block setting it to `off` (e.g. someone who
104
+ doesn't use Claude Code would set `agents: off`). If I want them all, omit
105
+ the block. Do NOT configure the agents view beyond on/off — it reads
106
+ `~/.claude` automatically when enabled.
102
107
  5. Ask me whether I want to overlay my Google Calendar or Microsoft 365 events
103
- on the Agenda. If yes, tell me to run `tuiboard calendar-setup google` (or
104
- `microsoft`) — it interviews me, opens the browser, and prints the exact
105
- `calendars:` YAML block to add. Don't try to do the OAuth yourself. If no,
106
- skip it (it's optional and can be added later).
108
+ on the Agenda (skip this if I turned the agenda off). If yes, tell me to run
109
+ `tuiboard calendar-setup google` (or `microsoft`) — it interviews me, opens
110
+ the browser, and prints the exact `calendars:` YAML block to add. Don't try
111
+ to do the OAuth yourself. If no, skip it (it's optional and can be added later).
107
112
  6. Show me the final config, then tell me to run `tuiboard`, and how to add a
108
113
  board later (create a new `.md` and append it to the `boards:` list).
109
114
 
@@ -143,6 +148,34 @@ archive_column: Archive
143
148
  # resume_command: ["nu", "C:/Users/you/.config/tuiboard/code-resume.nu", "{cwd}", "{sessionId}"]
144
149
  ```
145
150
 
151
+ ## Zones
152
+
153
+ tuiboard is four zones — **board** (kanban), **planner** (Today/Tomorrow across
154
+ all boards), **agenda** (24h timeline + calendar overlay), and **agents** (live
155
+ Claude Code sessions). Only want some of them? The board is always on; the other
156
+ three are yours to configure:
157
+
158
+ ```yaml
159
+ zones:
160
+ planner: on # Today/Tomorrow panel (toggle at runtime with F1)
161
+ agenda: on # 24h agenda + calendars (F2)
162
+ agents: off # live Claude Code view (F3)
163
+ ```
164
+
165
+ Each zone takes one of:
166
+
167
+ | Value | Behavior |
168
+ |---|---|
169
+ | `on` | Enabled and shown at launch (the default). |
170
+ | `off` | **Disabled entirely** — never rendered, skipped by `Shift-Tab`, its F-key is inert, and its background work never starts (no calendar fetch, no `~/.claude` reads). |
171
+ | `hidden` | Enabled but **collapsed at launch** — reveal it any time with its F-key. |
172
+
173
+ `true`/`false` work as aliases for `on`/`off`. So a pure kanban is just
174
+ `agenda: off` and `agents: off`; kanban + calendar is `agents: off`. The
175
+ difference between `off` and the F-key hide: `off` means the feature never runs
176
+ at all — handy if you don't use Claude Code and don't want tuiboard reading
177
+ `~/.claude`.
178
+
146
179
  ## Calendars (Agenda overlay)
147
180
 
148
181
  The **Agenda** zone (the 24h timeline) can overlay read-only events from Google
@@ -241,7 +274,7 @@ Launch `tuiboard` with no flag for the default 4-zone dashboard.
241
274
  | Flag | View | Use case |
242
275
  |---|---|---|
243
276
  | (none) | **Dashboard** — all 4 zones | Default; everything in one terminal |
244
- | `--view=board` | Kanban + virtual panel only | Focus mode, or a single WezTerm pane |
277
+ | `--view=board` | Kanban + planner panel only | Focus mode, or a single WezTerm pane |
245
278
  | `--view=timeline` | Timeline fullscreen | Wall-mounted "what's now" |
246
279
  | `--view=agents` | Agent view fullscreen | Cross-machine session monitor |
247
280
 
@@ -249,9 +282,9 @@ The dashboard auto-collapses optional zones on narrow terminals:
249
282
 
250
283
  | Terminal width | Default zones visible |
251
284
  |---|---|
252
- | ≥ 150 cols | virtual + board + timeline + agents |
253
- | 120–149 | virtual + board + agents |
254
- | 100–119 | virtual + board |
285
+ | ≥ 150 cols | planner + board + timeline + agents |
286
+ | 120–149 | planner + board + agents |
287
+ | 100–119 | planner + board |
255
288
  | < 100 | board only |
256
289
 
257
290
  `F1` / `F2` / `F3` toggles override the auto-collapse for the current
@@ -266,9 +299,9 @@ session (until the next terminal resize).
266
299
  | `h j k l` / arrows | Move cursor inside the active zone |
267
300
  | `Tab` | Cycle to next board |
268
301
  | `1`..`9` | Jump to board N |
269
- | `v` | Toggle Today/Tomorrow virtual panel focus |
270
- | `Shift-Tab` | Cycle active zone (virtual → board → timeline → agents) |
271
- | `F1` / `F2` / `F3` | Toggle visibility of Virtual / Timeline / Agents zones |
302
+ | `v` | Toggle Today/Tomorrow planner panel focus |
303
+ | `Shift-Tab` | Cycle active zone (planner → board → timeline → agents) |
304
+ | `F1` / `F2` / `F3` | Toggle visibility of Planner / Timeline / Agents zones |
272
305
  | `z` | Zoom active zone to full screen |
273
306
  | `r` | Refresh everything — reload boards from disk, rescan agents, force-refetch the agenda calendar (bypasses the 30-min cache) |
274
307
 
@@ -282,7 +315,7 @@ session (until the next terminal resize).
282
315
  | `j` / `k` | While armed: nudge the block ±15 min |
283
316
  | `+` / `-` | While armed: resize the block's end ±15 min |
284
317
 
285
- ### Task actions (work in board, virtual, AND timeline zones)
318
+ ### Task actions (work in board, planner, AND timeline zones)
286
319
 
287
320
  | Key | Action |
288
321
  |---|---|
@@ -320,10 +353,15 @@ session (until the next terminal resize).
320
353
 
321
354
  ## Status
322
355
 
356
+ See [CHANGELOG.md](CHANGELOG.md) for the full release history.
357
+
358
+ - **v0.7** — configurable zones: turn the planner, agenda, or agents view off
359
+ (or start it collapsed) via the `zones:` config, so tuiboard can be a pure
360
+ kanban, kanban + calendar, or any mix.
323
361
  - **v0.6** — adds the Agenda calendar overlay (Google + Microsoft 365,
324
362
  read-only, BYO credentials), day-navigation (`[` / `]` / `\`) to page tasks
325
363
  and events across days, and a manual full-refresh key (`r`).
326
- - **v0.5** — daily-driver ready. Kanban + virtual + timeline + agents
364
+ - **v0.5** — daily-driver ready. Kanban + planner + timeline + agents
327
365
  all functional, multi-select, undo, atomic file roundtrip, mouse click,
328
366
  responsive layout. Tested on Windows with WezTerm; Linux/macOS should
329
367
  work via the same OpenTUI binaries (untested).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tuiboard",
3
- "version": "0.6.2",
3
+ "version": "0.7.1",
4
4
  "description": "Terminal dashboard for markdown task boards. Kanban + Today/Tomorrow + 24h timeline + Claude Code agent view, all in one TUI.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -36,6 +36,7 @@
36
36
  "bin/",
37
37
  ".tuiboard/config.example.yaml",
38
38
  "README.md",
39
+ "CHANGELOG.md",
39
40
  "LICENSE"
40
41
  ],
41
42
  "scripts": {
package/src/app.tsx CHANGED
@@ -4,7 +4,7 @@
4
4
  * Loads config, builds the reactive store, parses argv, and dispatches
5
5
  * to one of four root views:
6
6
  * - undefined → Dashboard (all 4 zones)
7
- * - "board" → BoardOnly (kanban + virtual fullscreen)
7
+ * - "board" → BoardOnly (kanban + planner fullscreen)
8
8
  * - "timeline"→ TimelineOnly
9
9
  * - "agents" → AgentsOnly
10
10
  *
@@ -22,7 +22,7 @@ import {
22
22
  createTuiStore,
23
23
  type TuiStore,
24
24
  } from "~/store/index";
25
- import { buildVirtualItems } from "~/store/virtual-panel";
25
+ import { buildPlannerItems } from "~/store/planner-panel";
26
26
  import { T } from "~/ui/glyphs";
27
27
  import { TopBar, BottomBar } from "~/ui/Chrome";
28
28
  import { ModalLayer } from "~/ui/Modal";
@@ -62,16 +62,21 @@ process.on("SIGTERM", () => {
62
62
  // ≥ 150 col → all four zones
63
63
  // 120–149 → hide timeline
64
64
  // 100–119 → hide agents too
65
- // < 100 → hide virtual too (board is non-hideable)
65
+ // < 100 → hide planner too (board is non-hideable)
66
66
  //
67
- // User F1/F2/F3 toggles still work they last until the next resize, at
68
- // which point auto re-evaluates. Acceptable trade-off: resize events are
69
- // rare, predictable layout > sticky overrides.
67
+ // This only reports what FITS. The store combines it with each zone's enabled
68
+ // flag and the user's desired visibility, so F1/F2/F3 toggles persist across
69
+ // resizes and a disabled/hidden zone is never force-shown.
70
70
  function applyResponsiveLayout(): void {
71
71
  const width = process.stdout.columns ?? 200;
72
- store.setZoneVisible("timeline", width >= 150);
73
- store.setZoneVisible("agents", width >= 120);
74
- store.setZoneVisible("virtual", width >= 100);
72
+ // Report which zones FIT at this width. The store ANDs this with each zone's
73
+ // enabled flag and the user's desired visibility, so a disabled or
74
+ // intentionally-hidden zone is never force-shown just because there's room.
75
+ store.applyResponsiveFits({
76
+ planner: width >= 100,
77
+ timeline: width >= 150,
78
+ agents: width >= 120,
79
+ });
75
80
  }
76
81
  applyResponsiveLayout();
77
82
  process.stdout.on("resize", applyResponsiveLayout);
@@ -79,8 +84,8 @@ process.stdout.on("resize", applyResponsiveLayout);
79
84
  // Land on the Today/Tomorrow panel by default — for a daily-planning tool the
80
85
  // first question is "what's on my plate today", and that panel answers it. On
81
86
  // a narrow terminal where the panel auto-hides, fall back to the board.
82
- if (store.state.ui.visibleZones.virtual) {
83
- store.setActiveZone("virtual");
87
+ if (store.state.ui.visibleZones.planner) {
88
+ store.setActiveZone("planner");
84
89
  }
85
90
 
86
91
  const { view } = parseArgs(process.argv.slice(2));
@@ -97,11 +102,11 @@ function rootViewFor(v: ViewKind | undefined, s: TuiStore) {
97
102
  }
98
103
 
99
104
  function App() {
100
- const virtualItems = createMemo(() =>
101
- buildVirtualItems(store.state.boards.map((b) => b.board)),
105
+ const plannerItems = createMemo(() =>
106
+ buildPlannerItems(store.state.boards.map((b) => b.board)),
102
107
  );
103
108
 
104
- useKeyboard((key) => handleKey(store, key, virtualItems().length));
109
+ useKeyboard((key) => handleKey(store, key, plannerItems().length));
105
110
 
106
111
  return (
107
112
  <box
@@ -116,11 +121,11 @@ function App() {
116
121
  <TopBar store={store} />
117
122
  <box style={{ height: 1 }} />
118
123
  {/*
119
- rootView and ModalLayer are siblings inside a flex-row so the
120
- modal can sit beside the view as a fixed-width, full-height
121
- side panel. ModalLayer renders only when ui.modal is set,
122
- otherwise its <Show> resolves to nothing and the rootView gets
123
- the whole row.
124
+ rootView + ModalLayer are flex-row siblings. The modal panel is exactly
125
+ the Agenda's width, and the Dashboard hides the Agenda while a modal is
126
+ open so the modal drops into the Agenda's slot with zero reflow of the
127
+ left side (board / planner / agents). When no modal is open ModalLayer
128
+ renders nothing and rootView gets the whole row.
124
129
  */}
125
130
  <box style={{ flexDirection: "row", flexGrow: 1 }}>
126
131
  <box style={{ flexDirection: "column", flexGrow: 1 }}>
@@ -49,6 +49,30 @@ export interface Config {
49
49
  * Tokens are produced by `tuiboard calendar-setup`.
50
50
  */
51
51
  calendars?: CalendarsConfig;
52
+ /**
53
+ * Which optional zones are enabled and how they start. The board zone is
54
+ * always on (load-bearing) and not configurable here.
55
+ */
56
+ zones: ZonesConfig;
57
+ }
58
+
59
+ /**
60
+ * Per-zone startup mode:
61
+ * - "on" → enabled and visible at launch (default)
62
+ * - "off" → disabled entirely: never rendered, skipped by Shift-Tab, its
63
+ * F-key is inert, and its background work (calendar fetch /
64
+ * `~/.claude` watcher) never starts
65
+ * - "hidden" → enabled but collapsed at launch; reveal it with its F-key
66
+ */
67
+ export type ZoneMode = "on" | "off" | "hidden";
68
+
69
+ export interface ZonesConfig {
70
+ /** Today/Tomorrow cross-board panel. */
71
+ planner: ZoneMode;
72
+ /** 24h agenda + calendar overlay (internally the "timeline" zone). */
73
+ agenda: ZoneMode;
74
+ /** Live Claude Code session view. */
75
+ agents: ZoneMode;
52
76
  }
53
77
 
54
78
  export interface GoogleCalendarConfig {
@@ -79,6 +103,7 @@ export const DEFAULT_CONFIG: Omit<Config, "root" | "loaded" | "boards"> = {
79
103
  assignees: [],
80
104
  doneColumn: "Done",
81
105
  archiveColumn: "Archive",
106
+ zones: { planner: "on", agenda: "on", agents: "on" },
82
107
  };
83
108
 
84
109
  /**
@@ -135,6 +160,13 @@ interface RawConfig {
135
160
  color?: string;
136
161
  };
137
162
  };
163
+ // Values may be boolean (js-yaml parses on/off/yes/no → boolean) or the
164
+ // string "hidden"/"collapsed". Anything else falls back to "on".
165
+ zones: {
166
+ planner?: boolean | string;
167
+ agenda?: boolean | string;
168
+ agents?: boolean | string;
169
+ };
138
170
  }
139
171
 
140
172
  /** Expand `~` to the home dir; resolve relative paths against the config dir. */
@@ -173,6 +205,24 @@ function normalizeCalendars(
173
205
  return out.google || out.microsoft ? out : undefined;
174
206
  }
175
207
 
208
+ /** Normalize one zone's raw value to a ZoneMode. Default (undefined) → "on". */
209
+ function normalizeZoneMode(v: boolean | string | undefined): ZoneMode {
210
+ if (v === undefined || v === true) return "on";
211
+ if (v === false) return "off";
212
+ const s = String(v).toLowerCase();
213
+ if (s === "off" || s === "false" || s === "no" || s === "none") return "off";
214
+ if (s === "hidden" || s === "collapsed" || s === "closed") return "hidden";
215
+ return "on";
216
+ }
217
+
218
+ function normalizeZones(raw: RawConfig["zones"] | undefined): ZonesConfig {
219
+ return {
220
+ planner: normalizeZoneMode(raw?.planner),
221
+ agenda: normalizeZoneMode(raw?.agenda),
222
+ agents: normalizeZoneMode(raw?.agents),
223
+ };
224
+ }
225
+
176
226
  interface FoundConfig {
177
227
  path: string;
178
228
  dir: string;
@@ -239,6 +289,7 @@ function normalize(raw: Partial<RawConfig>, root: string, loaded: boolean): Conf
239
289
  ? raw.resume_command.map(String)
240
290
  : undefined,
241
291
  calendars: normalizeCalendars(raw.calendars, root),
292
+ zones: normalizeZones(raw.zones),
242
293
  };
243
294
  }
244
295
 
@@ -2,11 +2,11 @@
2
2
  * Centralized keyboard input handler. Dispatches based on:
3
3
  * 1. modal state (modal eats most keys)
4
4
  * 2. global keys (quit, help, undo, board switch, escape, zone cycle)
5
- * 3. active zone (virtual / board / timeline / agents)
5
+ * 3. active zone (planner / board / timeline / agents)
6
6
  *
7
7
  * Task-level actions (`t`/`m`/`s`/`b`/`a`/`o`/`Space`/`X`/`d`/`p`/`.`/`Enter`)
8
8
  * all route through `dispatchTaskAction`, so they work identically on a
9
- * cursor task whether you reach it via the kanban board, the virtual
9
+ * cursor task whether you reach it via the kanban board, the planner
10
10
  * panel, or a timeline block.
11
11
  *
12
12
  * Extracted from app.tsx so every root view (Dashboard, BoardOnly, etc.)
@@ -24,7 +24,7 @@ import {
24
24
  } from "~/store/index";
25
25
  import type { Board, PriorityLevel } from "~/types";
26
26
  import { buildTimelineEntries, formatAgendaDay } from "~/store/timeline";
27
- import { buildVirtualItems } from "~/store/virtual-panel";
27
+ import { buildPlannerItems } from "~/store/planner-panel";
28
28
  import { jumpToKanban } from "~/ui/TimelineView";
29
29
 
30
30
  interface KeyEvent {
@@ -37,7 +37,7 @@ interface KeyEvent {
37
37
  export function handleKey(
38
38
  store: TuiStore,
39
39
  key: KeyEvent,
40
- virtualCount: number,
40
+ plannerCount: number,
41
41
  ): void {
42
42
  const ui = store.state.ui;
43
43
  const board = store.state.boards[ui.activeBoardIndex]?.board;
@@ -125,17 +125,18 @@ export function handleKey(
125
125
  return;
126
126
  }
127
127
 
128
- // F1/F2/F3 toggle zone visibility. Board cannot be hidden.
128
+ // F1/F2/F3 toggle zone visibility. Board cannot be hidden; a disabled zone's
129
+ // key is inert (toggleZoneDesired no-ops).
129
130
  if (key.name === "f1") {
130
- store.setZoneVisible("virtual", !ui.visibleZones.virtual);
131
+ store.toggleZoneDesired("planner");
131
132
  return;
132
133
  }
133
134
  if (key.name === "f2") {
134
- store.setZoneVisible("timeline", !ui.visibleZones.timeline);
135
+ store.toggleZoneDesired("timeline");
135
136
  return;
136
137
  }
137
138
  if (key.name === "f3") {
138
- store.setZoneVisible("agents", !ui.visibleZones.agents);
139
+ store.toggleZoneDesired("agents");
139
140
  return;
140
141
  }
141
142
 
@@ -150,9 +151,9 @@ export function handleKey(
150
151
  return;
151
152
  }
152
153
 
153
- // Switch in/out of virtual panel with `v`
154
+ // Switch in/out of planner panel with `v`
154
155
  if (key.name === "v") {
155
- store.setActiveZone(ui.activeZone === "virtual" ? "board" : "virtual");
156
+ store.setActiveZone(ui.activeZone === "planner" ? "board" : "planner");
156
157
  return;
157
158
  }
158
159
 
@@ -183,7 +184,7 @@ export function handleKey(
183
184
  return;
184
185
  }
185
186
 
186
- // Zoom toggle: focus the active panel (board column or virtual panel)
187
+ // Zoom toggle: focus the active panel (board column or planner panel)
187
188
  // at full width.
188
189
  if (key.name === "z") {
189
190
  store.toggleZoom();
@@ -227,8 +228,8 @@ export function handleKey(
227
228
 
228
229
  // ─── Per-zone dispatching ───────────────────────────────────────────────
229
230
 
230
- if (ui.activeZone === "virtual") {
231
- handleVirtualZone(store, key, virtualCount, openLater);
231
+ if (ui.activeZone === "planner") {
232
+ handlePlannerZone(store, key, plannerCount, openLater);
232
233
  return;
233
234
  }
234
235
 
@@ -249,19 +250,19 @@ export function handleKey(
249
250
 
250
251
  // ─── Zone handlers ──────────────────────────────────────────────────────────
251
252
 
252
- function handleVirtualZone(
253
+ function handlePlannerZone(
253
254
  store: TuiStore,
254
255
  key: KeyEvent,
255
- virtualCount: number,
256
+ plannerCount: number,
256
257
  openLater: (m: ModalKind) => void,
257
258
  ): void {
258
259
  const ui = store.state.ui;
259
- const items = buildVirtualItems(store.state.boards.map((b) => b.board));
260
+ const items = buildPlannerItems(store.state.boards.map((b) => b.board));
260
261
  const target = items[ui.row];
261
262
 
262
263
  // Navigation
263
264
  if (key.name === "j" || key.name === "down") {
264
- store.setCursor(ui.col, Math.min(virtualCount - 1, ui.row + 1));
265
+ store.setCursor(ui.col, Math.min(plannerCount - 1, ui.row + 1));
265
266
  return;
266
267
  }
267
268
  if (key.name === "k" || key.name === "up") {
@@ -273,7 +274,7 @@ function handleVirtualZone(
273
274
  return;
274
275
  }
275
276
 
276
- // Task actions on the virtual cursor's target (works cross-board).
277
+ // Task actions on the planner cursor's target (works cross-board).
277
278
  if (target) {
278
279
  dispatchTaskAction(store, key, target.ref, openLater);
279
280
  }
@@ -501,7 +502,7 @@ function handleBoardZone(
501
502
  // unrendered, unscrollable column.
502
503
  const prev = adjacentVisibleColumn(store, board, ui.col, -1);
503
504
  if (prev === undefined) {
504
- store.setActiveZone("virtual");
505
+ store.setActiveZone("planner");
505
506
  } else {
506
507
  store.setCursor(prev, 0);
507
508
  }
@@ -551,7 +552,7 @@ function handleBoardZone(
551
552
 
552
553
  /**
553
554
  * Apply a task-level action to the given cursorRef. Used by every zone
554
- * (board / virtual / timeline) so the keys feel identical wherever the
555
+ * (board / planner / timeline) so the keys feel identical wherever the
555
556
  * cursor lives. Multi-select aware: when there are marked tasks, the
556
557
  * action operates on all of them via `applyToMarkedOr`.
557
558
  *
@@ -565,7 +566,7 @@ function dispatchTaskAction(
565
566
  ref: TaskRef,
566
567
  openLater: (m: ModalKind) => void,
567
568
  ): boolean {
568
- // Toggle done (Enter). Only meaningful for board/virtual; timeline has
569
+ // Toggle done (Enter). Only meaningful for board/planner; timeline has
569
570
  // its own Enter behavior (jump to kanban) which is handled earlier.
570
571
  if (key.name === "enter" || key.name === "return") {
571
572
  const n = store.applyToMarkedOr(ref, (r) => store.toggleDone(r));
@@ -623,7 +624,7 @@ function dispatchTaskAction(
623
624
  // Calendar arm mode (lowercase c): toggle a persistent mode for batch
624
625
  // scheduling onto the timeline. Entering also arms the cursor task and
625
626
  // focuses the timeline so you can immediately click a slot. While the mode
626
- // is on, clicking ANY task (board / virtual) arms it — click a task, click a
627
+ // is on, clicking ANY task (board / planner) arms it — click a task, click a
627
628
  // slot, repeat. `c` again or `Esc` exits. Works from any zone.
628
629
  if (key.name === "c" && !key.shift) {
629
630
  if (store.state.ui.armMode) {