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
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
/** The fixed Today/Tomorrow
|
|
1
|
+
/** The fixed Today/Tomorrow planner panel — always on the left. */
|
|
2
2
|
|
|
3
3
|
import { For, Show, createEffect, createMemo } from "solid-js";
|
|
4
4
|
|
|
5
5
|
import { ATTR, T } from "~/ui/glyphs";
|
|
6
6
|
import { TaskRow } from "~/ui/TaskRow";
|
|
7
7
|
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
type
|
|
11
|
-
} from "~/store/
|
|
8
|
+
buildPlannerItems,
|
|
9
|
+
groupPlannerItems,
|
|
10
|
+
type PlannerGroup,
|
|
11
|
+
} from "~/store/planner-panel";
|
|
12
12
|
import type { TuiStore } from "~/store/index";
|
|
13
13
|
|
|
14
14
|
interface ScrollBoxLike {
|
|
@@ -32,15 +32,15 @@ const BUCKET_HEADER: Record<string, { label: string; color: string }> = {
|
|
|
32
32
|
priority: { label: "🔺 Priority", color: T.today },
|
|
33
33
|
};
|
|
34
34
|
|
|
35
|
-
export function
|
|
35
|
+
export function PlannerPanel(props: { store: TuiStore }) {
|
|
36
36
|
const items = createMemo(() => {
|
|
37
37
|
props.store.state.rev; // recompute on any board mutation
|
|
38
|
-
return
|
|
38
|
+
return buildPlannerItems(props.store.state.boards.map((b) => b.board));
|
|
39
39
|
});
|
|
40
|
-
const groups = createMemo(() =>
|
|
41
|
-
const isActive = createMemo(() => props.store.state.ui.activeZone === "
|
|
40
|
+
const groups = createMemo(() => groupPlannerItems(items()));
|
|
41
|
+
const isActive = createMemo(() => props.store.state.ui.activeZone === "planner");
|
|
42
42
|
const isZoomed = createMemo(
|
|
43
|
-
() => props.store.state.ui.zoomed && props.store.state.ui.activeZone === "
|
|
43
|
+
() => props.store.state.ui.zoomed && props.store.state.ui.activeZone === "planner",
|
|
44
44
|
);
|
|
45
45
|
const cursorRow = createMemo(() => props.store.state.ui.row);
|
|
46
46
|
let scrollBoxRef: ScrollBoxLike | undefined;
|
|
@@ -121,7 +121,7 @@ export function VirtualPanel(props: { store: TuiStore }) {
|
|
|
121
121
|
availableWidth={isZoomed() ? 100 : 36}
|
|
122
122
|
isMarkedFn={(r) => props.store.isMarked(r)}
|
|
123
123
|
onClickItem={(flatIndex) => {
|
|
124
|
-
props.store.setActiveZone("
|
|
124
|
+
props.store.setActiveZone("planner");
|
|
125
125
|
props.store.setCursor(0, flatIndex);
|
|
126
126
|
// In calendar arm mode, a click also arms the task for the
|
|
127
127
|
// timeline (click a task, then a slot to place it).
|
|
@@ -141,7 +141,7 @@ export function VirtualPanel(props: { store: TuiStore }) {
|
|
|
141
141
|
}
|
|
142
142
|
|
|
143
143
|
function RenderGroups(props: {
|
|
144
|
-
groups:
|
|
144
|
+
groups: PlannerGroup[];
|
|
145
145
|
isActive: boolean;
|
|
146
146
|
cursorRow: number;
|
|
147
147
|
availableWidth: number;
|
|
@@ -258,7 +258,7 @@ function RenderGroups(props: {
|
|
|
258
258
|
}
|
|
259
259
|
|
|
260
260
|
function isFirstOfSection(
|
|
261
|
-
groups:
|
|
261
|
+
groups: PlannerGroup[],
|
|
262
262
|
idx: number,
|
|
263
263
|
): boolean {
|
|
264
264
|
if (idx === 0) return true;
|
package/src/ui/TimelineView.tsx
CHANGED
|
@@ -354,7 +354,7 @@ export function TimelineView(props: TimelineViewProps) {
|
|
|
354
354
|
</Show>
|
|
355
355
|
|
|
356
356
|
{/* The 24h grid now owns the whole panel — no sticky section above
|
|
357
|
-
it. Tasks are armed for scheduling from the board /
|
|
357
|
+
it. Tasks are armed for scheduling from the board / planner panel
|
|
358
358
|
via the `C` shortcut, then placed by clicking a slot here. */}
|
|
359
359
|
<scrollbox
|
|
360
360
|
ref={(r: ScrollBoxLike) => (scrollBoxRef = r)}
|
package/src/ui/glyphs.ts
CHANGED
|
@@ -29,7 +29,7 @@ export const ATTR = {
|
|
|
29
29
|
* instead of raw ANSI names like "red"/"yellow" — those tend to render
|
|
30
30
|
* as fully-saturated traffic-light colors in most themes and clash
|
|
31
31
|
* with everything around them.
|
|
32
|
-
* - The Today/Tomorrow
|
|
32
|
+
* - The Today/Tomorrow planner panel has its own *warm* identity
|
|
33
33
|
* (peach/orange) so the user instantly knows "this is the time zone";
|
|
34
34
|
* everything else uses a *cool* identity (soft cyan) for active focus.
|
|
35
35
|
* - Cursor row highlight is the only opaque paint outside the modal.
|
|
@@ -137,7 +137,7 @@ export function cellWidth(s: string): number {
|
|
|
137
137
|
}
|
|
138
138
|
|
|
139
139
|
/**
|
|
140
|
-
* Per-board accent palette. Used by the
|
|
140
|
+
* Per-board accent palette. Used by the planner panel to color-code the
|
|
141
141
|
* source-board tag on priority/agenda items, so the user can recognize
|
|
142
142
|
* which board a cross-cutting item came from at a glance.
|
|
143
143
|
*
|
package/src/views/BoardOnly.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Standalone fullscreen view for the kanban board (with
|
|
2
|
+
* Standalone fullscreen view for the kanban board (with planner panel).
|
|
3
3
|
* Mounted when the user launches `tuiboard --view=board`, AND used inside
|
|
4
4
|
* the dashboard via composition.
|
|
5
5
|
*
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
import { Show, createMemo } from "solid-js";
|
|
14
14
|
|
|
15
15
|
import { BoardView } from "~/ui/BoardView";
|
|
16
|
-
import {
|
|
16
|
+
import { PlannerPanel } from "~/ui/PlannerPanel";
|
|
17
17
|
import type { TuiStore } from "~/store/index";
|
|
18
18
|
|
|
19
19
|
export function BoardOnly(props: { store: TuiStore }) {
|
|
@@ -24,10 +24,10 @@ export function BoardOnly(props: { store: TuiStore }) {
|
|
|
24
24
|
|
|
25
25
|
return (
|
|
26
26
|
<box style={{ flexDirection: "row", flexGrow: 1 }}>
|
|
27
|
-
<Show when={!ui().zoomed || ui().activeZone === "
|
|
28
|
-
<
|
|
27
|
+
<Show when={!ui().zoomed || ui().activeZone === "planner"}>
|
|
28
|
+
<PlannerPanel store={props.store} />
|
|
29
29
|
</Show>
|
|
30
|
-
<Show when={(!ui().zoomed || ui().activeZone !== "
|
|
30
|
+
<Show when={(!ui().zoomed || ui().activeZone !== "planner") && activeBoard()}>
|
|
31
31
|
<BoardView store={props.store} board={activeBoard()!} />
|
|
32
32
|
</Show>
|
|
33
33
|
</box>
|
package/src/views/Dashboard.tsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The default tuiboard view: a 4-zone dashboard.
|
|
3
3
|
*
|
|
4
|
-
* ┌─
|
|
4
|
+
* ┌─Planner─┬─Board────────┬─Timeline─┐
|
|
5
5
|
* │ │ │ │
|
|
6
6
|
* │ │ │ │
|
|
7
7
|
* │ ├──────────────┤ │
|
|
@@ -24,14 +24,18 @@ import { Show, createMemo } from "solid-js";
|
|
|
24
24
|
import { AgentsBar } from "~/ui/AgentsBar";
|
|
25
25
|
import { BoardView } from "~/ui/BoardView";
|
|
26
26
|
import { TimelineView } from "~/ui/TimelineView";
|
|
27
|
-
import {
|
|
27
|
+
import { PlannerPanel } from "~/ui/PlannerPanel";
|
|
28
28
|
import { AgentsOnly } from "~/views/AgentsOnly";
|
|
29
29
|
import { BoardOnly } from "~/views/BoardOnly";
|
|
30
30
|
import { TimelineOnly } from "~/views/TimelineOnly";
|
|
31
31
|
import type { TuiStore } from "~/store/index";
|
|
32
32
|
|
|
33
|
-
/**
|
|
34
|
-
|
|
33
|
+
/**
|
|
34
|
+
* Width (in cells) for the right-column Agenda panel on a wide terminal. The
|
|
35
|
+
* modal panel matches this so it can drop into the Agenda's slot (see
|
|
36
|
+
* ModalLayer) without reflowing the rest of the dashboard.
|
|
37
|
+
*/
|
|
38
|
+
export const TIMELINE_WIDTH = 50;
|
|
35
39
|
/** Row height for the bottom Agents strip — enough for ~5 sessions. */
|
|
36
40
|
const AGENTS_HEIGHT = 7;
|
|
37
41
|
|
|
@@ -53,7 +57,7 @@ export function Dashboard(props: { store: TuiStore }) {
|
|
|
53
57
|
* standalone view wrappers so a `z` press in the dashboard produces
|
|
54
58
|
* the same visual as launching `tuiboard --view=<zone>`.
|
|
55
59
|
*
|
|
56
|
-
* Board +
|
|
60
|
+
* Board + planner share BoardOnly because both live in the top-left
|
|
57
61
|
* zone of the normal layout and BoardOnly already respects ui.zoomed
|
|
58
62
|
* to render only the active panel between them.
|
|
59
63
|
*/
|
|
@@ -80,12 +84,12 @@ function FourZoneLayout(props: { store: TuiStore }) {
|
|
|
80
84
|
|
|
81
85
|
return (
|
|
82
86
|
<box style={{ flexDirection: "row", flexGrow: 1 }}>
|
|
83
|
-
{/* Left column:
|
|
87
|
+
{/* Left column: planner + board on top, agents on bottom */}
|
|
84
88
|
<box style={{ flexDirection: "column", flexGrow: 1 }}>
|
|
85
89
|
{/* Top zone */}
|
|
86
90
|
<box style={{ flexDirection: "row", flexGrow: 1 }}>
|
|
87
|
-
<Show when={visible().
|
|
88
|
-
<
|
|
91
|
+
<Show when={visible().planner}>
|
|
92
|
+
<PlannerPanel store={props.store} />
|
|
89
93
|
</Show>
|
|
90
94
|
<Show when={visible().board && activeBoard()}>
|
|
91
95
|
<BoardView store={props.store} board={activeBoard()!} />
|
|
@@ -96,8 +100,9 @@ function FourZoneLayout(props: { store: TuiStore }) {
|
|
|
96
100
|
<AgentsBar store={props.store} height={AGENTS_HEIGHT} />
|
|
97
101
|
</Show>
|
|
98
102
|
</box>
|
|
99
|
-
{/* Right column:
|
|
100
|
-
|
|
103
|
+
{/* Right column: Agenda (full height). Hidden while a modal is open so
|
|
104
|
+
the modal panel takes its slot with no reflow. */}
|
|
105
|
+
<Show when={visible().timeline && !ui().modal}>
|
|
101
106
|
<TimelineView store={props.store} width={TIMELINE_WIDTH} />
|
|
102
107
|
</Show>
|
|
103
108
|
{/* ModalLayer rendered at App level so it can sit beside any view */}
|