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.
- package/.tuiboard/config.example.yaml +14 -0
- package/CHANGELOG.md +98 -0
- package/README.md +53 -15
- package/package.json +2 -1
- package/src/app.tsx +24 -19
- package/src/config/loader.ts +51 -0
- package/src/input/handleKey.ts +23 -22
- package/src/store/index.test.ts +76 -10
- package/src/store/index.ts +112 -19
- package/src/store/{virtual-panel.ts → planner-panel.ts} +0 -0
- package/src/ui/AgentsBar.tsx +1 -1
- package/src/ui/BoardView.tsx +31 -18
- package/src/ui/Modal.tsx +38 -46
- package/src/ui/{VirtualPanel.tsx → PlannerPanel.tsx} +13 -13
- package/src/ui/TimelineView.tsx +1 -1
- package/src/ui/glyphs.ts +2 -2
- package/src/views/BoardOnly.tsx +5 -5
- package/src/views/Dashboard.tsx +15 -10
|
@@ -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
|
|
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.
|
|
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
|
|
104
|
-
`microsoft`) — it interviews me, opens
|
|
105
|
-
`calendars:` YAML block to add. Don't try
|
|
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 +
|
|
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 |
|
|
253
|
-
| 120–149 |
|
|
254
|
-
| 100–119 |
|
|
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
|
|
270
|
-
| `Shift-Tab` | Cycle active zone (
|
|
271
|
-
| `F1` / `F2` / `F3` | Toggle visibility of
|
|
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,
|
|
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 +
|
|
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.
|
|
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 +
|
|
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 {
|
|
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
|
|
65
|
+
// < 100 → hide planner too (board is non-hideable)
|
|
66
66
|
//
|
|
67
|
-
//
|
|
68
|
-
//
|
|
69
|
-
//
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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.
|
|
83
|
-
store.setActiveZone("
|
|
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
|
|
101
|
-
|
|
105
|
+
const plannerItems = createMemo(() =>
|
|
106
|
+
buildPlannerItems(store.state.boards.map((b) => b.board)),
|
|
102
107
|
);
|
|
103
108
|
|
|
104
|
-
useKeyboard((key) => handleKey(store, key,
|
|
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
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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 }}>
|
package/src/config/loader.ts
CHANGED
|
@@ -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
|
|
package/src/input/handleKey.ts
CHANGED
|
@@ -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 (
|
|
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
|
|
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 {
|
|
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
|
-
|
|
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.
|
|
131
|
+
store.toggleZoneDesired("planner");
|
|
131
132
|
return;
|
|
132
133
|
}
|
|
133
134
|
if (key.name === "f2") {
|
|
134
|
-
store.
|
|
135
|
+
store.toggleZoneDesired("timeline");
|
|
135
136
|
return;
|
|
136
137
|
}
|
|
137
138
|
if (key.name === "f3") {
|
|
138
|
-
store.
|
|
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
|
|
154
|
+
// Switch in/out of planner panel with `v`
|
|
154
155
|
if (key.name === "v") {
|
|
155
|
-
store.setActiveZone(ui.activeZone === "
|
|
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
|
|
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 === "
|
|
231
|
-
|
|
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
|
|
253
|
+
function handlePlannerZone(
|
|
253
254
|
store: TuiStore,
|
|
254
255
|
key: KeyEvent,
|
|
255
|
-
|
|
256
|
+
plannerCount: number,
|
|
256
257
|
openLater: (m: ModalKind) => void,
|
|
257
258
|
): void {
|
|
258
259
|
const ui = store.state.ui;
|
|
259
|
-
const items =
|
|
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(
|
|
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
|
|
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("
|
|
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 /
|
|
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/
|
|
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 /
|
|
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) {
|