@miadi/chronicle-tui 0.6.0 → 0.7.0
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 +24 -0
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +148 -0
- package/dist/App.js.map +1 -1
- package/dist/actions.d.ts +43 -0
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +57 -1
- package/dist/actions.js.map +1 -1
- package/dist/api.d.ts +10 -2
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +19 -10
- package/dist/api.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/inquiries.d.ts +76 -0
- package/dist/inquiries.d.ts.map +1 -0
- package/dist/inquiries.js +77 -0
- package/dist/inquiries.js.map +1 -0
- package/dist/tide.d.ts +79 -0
- package/dist/tide.d.ts.map +1 -0
- package/dist/tide.js +102 -0
- package/dist/tide.js.map +1 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -34,6 +34,8 @@ On Termux, install with `--ignore-scripts`. Node 22 or later.
|
|
|
34
34
|
| `!` | the chronicle's attention board: every open item, newest asked first, filtered by `/`. `e` on an item opens its episode |
|
|
35
35
|
| `c` | talking circles: the ceremonies held in the episode in focus, the circles gathered for it, yours, the others |
|
|
36
36
|
| `v` | hold a Miadi review in the episode (writer token) |
|
|
37
|
+
| `i` | the episode's inquiries and, when you may write, the shelf: Enter attaches one, a dry run first, `y` confirms |
|
|
38
|
+
| `t` | the episode's companion seat (Mia) through tide: the plan first, `y` runs it only when it would act, `A` asks again archiving the running session |
|
|
37
39
|
| `L` | relate episodes: `L` marks the one in focus, move to the other, `L` again, say why (relates to or continues from, one way or both). Esc forgets the mark |
|
|
38
40
|
| `?` | all keys · `q` quits |
|
|
39
41
|
|
|
@@ -95,6 +97,28 @@ miadi-chronicle --url https://miadi.tail3b11eb.ts.net
|
|
|
95
97
|
Write actions declare `effect: "write"` and are offered only when the app said
|
|
96
98
|
it would record them.
|
|
97
99
|
|
|
100
|
+
## Inquiries and the companion seat
|
|
101
|
+
|
|
102
|
+
Both show what would happen before anything does, and act only on `y`:
|
|
103
|
+
Enter never confirms, so a double press cannot start a seat or land a commit.
|
|
104
|
+
|
|
105
|
+
- **Inquiries** (`i`) are the room's Inquiries card
|
|
106
|
+
(`GET /api/chronicle/episodes/{ref}/inquiry`): what the episode holds, and
|
|
107
|
+
for a writer the shelf, newest first, `/` to filter (`?q=`). Enter on a
|
|
108
|
+
shelf item asks for its thread (`owner/repo#number`, optional), posts a dry
|
|
109
|
+
run and shows it in the card's words, with the copy's size and what the door
|
|
110
|
+
noticed. `y` attaches: the door relates, copies, registers on the wheel and
|
|
111
|
+
commits. A refused dry run offers nothing to confirm.
|
|
112
|
+
- **The companion seat** (`t`) is the cockpit's "Open Mia for this episode"
|
|
113
|
+
(`POST /api/tide/companion`, jgwill/Miadi#714). The plan comes first: the
|
|
114
|
+
session, tide's state, the listener, the commands and the starter prompt.
|
|
115
|
+
`y` runs that plan, only when it would act (`create`, a re-arm that starts a
|
|
116
|
+
listener or relaunches, `archive then create`); a seat already listening, a
|
|
117
|
+
refusal or a pane running something else offer nothing. Archiving the
|
|
118
|
+
running session is the person's word, so `A` asks for a new plan with it.
|
|
119
|
+
The run takes up to a few minutes and ends with `tmux attach -t <session>`.
|
|
120
|
+
The browser never types into the seat's pane; tide does, once it is idle.
|
|
121
|
+
|
|
98
122
|
## Talking circles
|
|
99
123
|
|
|
100
124
|
`c` opens the circles, from the episode in focus: the ceremonies held in it
|
package/dist/App.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"App.d.ts","sourceRoot":"","sources":["../src/App.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAiD,MAAM,OAAO,CAAA;AAIrE,OAAO,EAML,KAAK,eAAe,EAIrB,MAAM,cAAc,CAAA;
|
|
1
|
+
{"version":3,"file":"App.d.ts","sourceRoot":"","sources":["../src/App.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAiD,MAAM,OAAO,CAAA;AAIrE,OAAO,EAML,KAAK,eAAe,EAIrB,MAAM,cAAc,CAAA;AAqErB,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAA;IACZ,oCAAoC;IACpC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,+CAA+C;IAC/C,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,4DAA4D;IAC5D,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,gDAAgD;IAChD,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,2CAA2C;IAC3C,OAAO,CAAC,EAAE,SAAS,eAAe,EAAE,CAAA;IACpC,kFAAkF;IAClF,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,sEAAsE;IACtE,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAC7B;AAuHD,wBAAgB,GAAG,CAAC,KAAK,EAAE,QAAQ,qBAirDlC"}
|
package/dist/App.js
CHANGED
|
@@ -19,6 +19,8 @@ import { DEFAULT_ACTIONS, actionFor, enterCeremony, enterCircle, } from "./actio
|
|
|
19
19
|
import { TOKEN_ENV, answerAttention, connectApi } from "./api.js";
|
|
20
20
|
import { BOARD_LABELS, ROOM_LABELS, answeredMessage, checkAnswer, filterBoard, loadBoard, loadEpisodePanel, panelView, readOnlyLabel, } from "./attention.js";
|
|
21
21
|
import { CEREMONY_TYPES, DIARY_PHASES, DIRECTIONS, ENTRY_TYPES, PHASE_LABELS, ceremonyHeadline, ceremonyLines, circleEntries, closeCeremony, mayWitness, openCeremony, readCeremony, readCircle, scrollTo, speak, splitLearnings, typeLabel, whenOf, witness, writeDiary, } from "./circles.js";
|
|
22
|
+
import { THREAD, attachInquiry, attachedMessage, previewLines, readInquiries } from "./inquiries.js";
|
|
23
|
+
import { companionLines, companionPlan, planActs, ranMessage, runCompanion } from "./tide.js";
|
|
22
24
|
import { VISITOR, wheelDoors, loadChronicle, loadDetail, loadText, openUrl, qrLines, search, visibleRows, } from "./data.js";
|
|
23
25
|
import { ageOf, clamp, fileView, filterRows, formatDate, screenLineOf, windowOf, wrapSegments, } from "./model.js";
|
|
24
26
|
import { clean, cleanLine, link } from "./text.js";
|
|
@@ -598,6 +600,76 @@ export function App(props) {
|
|
|
598
600
|
await reloadCeremony(handle);
|
|
599
601
|
}, (error) => set({ status: cleanLine(error.message) }));
|
|
600
602
|
};
|
|
603
|
+
// ── inquiries and the companion seat: a plan or a dry run, then `y` ───────
|
|
604
|
+
const reloadInquiries = async (handle) => {
|
|
605
|
+
const entry = ui.current.overlays.find((candidate) => candidate.id === handle);
|
|
606
|
+
if (!entry || entry.overlay.type !== "inquiries" || !ui.current.api)
|
|
607
|
+
return;
|
|
608
|
+
const view = await readInquiries(ui.current.api, entry.overlay.episode, entry.overlay.query);
|
|
609
|
+
updateById(handle, (o) => (o.type === "inquiries" ? { ...o, view, error: undefined, selected: clamp(o.selected, view.shelf?.items.length ?? 0) } : o));
|
|
610
|
+
};
|
|
611
|
+
const filterInput = (handle, episode, query) => ({
|
|
612
|
+
type: "input",
|
|
613
|
+
title: "Filter the shelf by name",
|
|
614
|
+
hint: "Enter filters; an empty filter shows the newest.",
|
|
615
|
+
value: query,
|
|
616
|
+
allowEmpty: true,
|
|
617
|
+
submit: async (value) => {
|
|
618
|
+
const q = value.trim();
|
|
619
|
+
const view = await readInquiries(ui.current.api, episode, q);
|
|
620
|
+
updateById(handle, (o) => (o.type === "inquiries" ? { ...o, view, query: q, selected: 0 } : o));
|
|
621
|
+
return `${view.shelf?.items.length ?? 0} of ${view.shelf?.total ?? 0} on the shelf${q ? ` for “${cleanLine(q)}”` : ""}, newest first.`;
|
|
622
|
+
},
|
|
623
|
+
});
|
|
624
|
+
const attachInput = (handle, episode, label, item) => ({
|
|
625
|
+
type: "input",
|
|
626
|
+
title: `Attach ${cleanLine(item.artefact)} to ${label}`,
|
|
627
|
+
hint: "Its thread (optional, owner/repo#number). Enter shows what attaching does; nothing is written yet.",
|
|
628
|
+
value: item.issue ?? "",
|
|
629
|
+
allowEmpty: true,
|
|
630
|
+
submit: async (value) => {
|
|
631
|
+
const issue = value.trim();
|
|
632
|
+
if (issue && !THREAD.test(issue))
|
|
633
|
+
throw new Error("A thread is owner/repo#number, or nothing.");
|
|
634
|
+
const api = ui.current.api;
|
|
635
|
+
const preview = await attachInquiry(api, episode, { artefact: item.artefact, issue, dryRun: true });
|
|
636
|
+
return {
|
|
637
|
+
next: {
|
|
638
|
+
type: "confirm",
|
|
639
|
+
title: `Attach ${cleanLine(item.artefact)} to ${label}?${issue ? ` · ${issue}` : ""}`,
|
|
640
|
+
lines: previewLines(preview, item.artefact),
|
|
641
|
+
...(preview.ok === false ? {} : { yes: "attach" }),
|
|
642
|
+
run: async () => attachedMessage(await attachInquiry(api, episode, { artefact: item.artefact, issue, dryRun: false }), item.artefact),
|
|
643
|
+
after: () => reloadInquiries(handle),
|
|
644
|
+
},
|
|
645
|
+
};
|
|
646
|
+
},
|
|
647
|
+
});
|
|
648
|
+
const askCompanion = (handle, archive) => {
|
|
649
|
+
const entry = ui.current.overlays.find((candidate) => candidate.id === handle);
|
|
650
|
+
if (!entry || entry.overlay.type !== "companion" || !ui.current.api)
|
|
651
|
+
return;
|
|
652
|
+
const episode = entry.overlay.episode;
|
|
653
|
+
updateById(handle, (o) => (o.type === "companion" ? { ...o, archive, busy: "plan", answer: undefined, error: undefined } : o));
|
|
654
|
+
companionPlan(ui.current.api, episode, { archive }).then((answer) => updateById(handle, (o) => (o.type === "companion" ? { ...o, busy: undefined, answer } : o)), (error) => updateById(handle, (o) => (o.type === "companion" ? { ...o, busy: undefined, error: cleanLine(error.message) } : o)));
|
|
655
|
+
};
|
|
656
|
+
const runPlan = (handle) => {
|
|
657
|
+
const entry = ui.current.overlays.find((candidate) => candidate.id === handle);
|
|
658
|
+
if (!entry || entry.overlay.type !== "companion" || !ui.current.api)
|
|
659
|
+
return;
|
|
660
|
+
const { episode, archive, answer } = entry.overlay;
|
|
661
|
+
if (!planActs(answer))
|
|
662
|
+
return set({ status: `Nothing to run: ${cleanLine(answer?.plan?.state ?? "no plan on screen")}.` });
|
|
663
|
+
updateById(handle, (o) => (o.type === "companion" ? { ...o, busy: "run", error: undefined } : o));
|
|
664
|
+
set({ status: "Opening the seat: up to a few minutes. Esc leaves this open; the outcome comes here." });
|
|
665
|
+
runCompanion(ui.current.api, episode, { archive }).then((ran) => {
|
|
666
|
+
updateById(handle, (o) => (o.type === "companion" ? { ...o, busy: undefined, ran } : o));
|
|
667
|
+
set({ status: ranMessage(ran) });
|
|
668
|
+
}, (error) => {
|
|
669
|
+
updateById(handle, (o) => (o.type === "companion" ? { ...o, busy: undefined, error: cleanLine(error.message) } : o));
|
|
670
|
+
set({ status: cleanLine(`The seat did not open: ${error.message}`) });
|
|
671
|
+
});
|
|
672
|
+
};
|
|
601
673
|
// ── keys ─────────────────────────────────────────────────────────────────
|
|
602
674
|
// A paste into an input arrives whole, line breaks kept (bracketed paste).
|
|
603
675
|
const pasteActive = ui.current.overlays[ui.current.overlays.length - 1]?.overlay.type === "input";
|
|
@@ -685,6 +757,9 @@ export function App(props) {
|
|
|
685
757
|
if (top) {
|
|
686
758
|
if (top.type === "help")
|
|
687
759
|
return closeTop();
|
|
760
|
+
// An act under way is not left half-seen.
|
|
761
|
+
if (top.type === "confirm" && top.busy)
|
|
762
|
+
return;
|
|
688
763
|
if (key.escape || input === "q" || input === "h" || key.backspace || key.leftArrow)
|
|
689
764
|
return closeTop();
|
|
690
765
|
if (top.type === "doors") {
|
|
@@ -704,6 +779,51 @@ export function App(props) {
|
|
|
704
779
|
}
|
|
705
780
|
if (top.type === "qr" && input === "i")
|
|
706
781
|
return updateTop({ ...top, invert: !top.invert });
|
|
782
|
+
if (top.type === "confirm") {
|
|
783
|
+
// Only an act the dry run allowed: a refusal offers nothing to confirm.
|
|
784
|
+
if (input !== "y" || !top.run || !top.yes)
|
|
785
|
+
return;
|
|
786
|
+
const handle = topHandle();
|
|
787
|
+
const run = top.run;
|
|
788
|
+
updateTop({ ...top, busy: true, error: undefined });
|
|
789
|
+
run().then(async (message) => {
|
|
790
|
+
set({ status: cleanLine(message), overlays: ui.current.overlays.filter((entry) => entry.id !== handle) });
|
|
791
|
+
try {
|
|
792
|
+
await top.after?.();
|
|
793
|
+
}
|
|
794
|
+
catch (error) {
|
|
795
|
+
set({ status: cleanLine(`Done, but the refresh failed: ${error.message}`) });
|
|
796
|
+
}
|
|
797
|
+
}, (error) => updateById(handle, (o) => (o.type === "confirm" ? { ...o, busy: false, error: cleanLine(error.message) } : o)));
|
|
798
|
+
return;
|
|
799
|
+
}
|
|
800
|
+
if (top.type === "inquiries") {
|
|
801
|
+
const handle = topHandle();
|
|
802
|
+
const items = top.view?.shelf?.items ?? [];
|
|
803
|
+
if (key.downArrow || input === "j")
|
|
804
|
+
return updateTop({ ...top, selected: clamp(top.selected + 1, items.length) });
|
|
805
|
+
if (key.upArrow || input === "k")
|
|
806
|
+
return updateTop({ ...top, selected: clamp(top.selected - 1, items.length) });
|
|
807
|
+
if (input === "/" && top.view?.attach)
|
|
808
|
+
return void openOverlay(filterInput(handle, top.episode, top.query));
|
|
809
|
+
const item = items[clamp(top.selected, items.length)];
|
|
810
|
+
if (item && (key.return || input === "l" || key.rightArrow)) {
|
|
811
|
+
if (item.wovenHere)
|
|
812
|
+
return set({ status: `${cleanLine(item.artefact)} is already woven into this episode.` });
|
|
813
|
+
return void openOverlay(attachInput(handle, top.episode, top.label, item));
|
|
814
|
+
}
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
817
|
+
if (top.type === "companion") {
|
|
818
|
+
const handle = topHandle();
|
|
819
|
+
if (top.busy || top.ran)
|
|
820
|
+
return;
|
|
821
|
+
if (input === "A")
|
|
822
|
+
return askCompanion(handle, !top.archive);
|
|
823
|
+
if (input === "y")
|
|
824
|
+
return runPlan(handle);
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
707
827
|
if (top.type === "circles" && top.index) {
|
|
708
828
|
const entries = circleEntries(top.index);
|
|
709
829
|
const step = (from, by) => {
|
|
@@ -971,6 +1091,8 @@ export function App(props) {
|
|
|
971
1091
|
return (_jsxs(Text, { wrap: "truncate-start", inverse: isSelected, color: file.kind === "text" ? "blue" : "gray", children: [cleanLine(file.relativePath), file.kind === "text" ? "" : ` · ${file.kind}`] }, file.relativePath));
|
|
972
1092
|
})] })) : (_jsx(Text, { color: "gray", children: "Nothing selected." })) }) }));
|
|
973
1093
|
const fileBody = s.open ? (_jsxs(Box, { flexDirection: "column", height: bodyHeight, overflow: "hidden", children: [s.open.empty ? _jsx(Text, { color: "gray", children: "(empty file)" }) : null, s.open.view.lines.slice(s.open.scroll, s.open.scroll + bodyHeight).map((line, index) => (_jsx(Segments, { segments: line.segments, selectedRef: s.open.refSel }, s.open.scroll + index)))] })) : null;
|
|
1094
|
+
/** How many screen rows these lines take inside an overlay. */
|
|
1095
|
+
const rowsOf = (texts) => Math.max(1, texts.reduce((sum, text) => sum + wrapSegments([{ text, style: "text" }], Math.max(10, columns - 4)).length, 0));
|
|
974
1096
|
// Children never shrink: a fixed-height box clips what does not fit instead
|
|
975
1097
|
// of squeezing rows to zero height, where Ink draws them over each other.
|
|
976
1098
|
const box = (color, children) => (_jsx(Box, { flexDirection: "column", borderStyle: "round", borderColor: color, paddingLeft: 1, paddingRight: 1, flexGrow: 1, height: bodyHeight, overflow: "hidden", children: _jsx(Box, { flexDirection: "column", flexShrink: 0, children: children }) }));
|
|
@@ -1023,6 +1145,32 @@ export function App(props) {
|
|
|
1023
1145
|
if (top.type === "item") {
|
|
1024
1146
|
return box("yellow", _jsxs(_Fragment, { children: [itemBody(top.item, columns - 4), _jsxs(Text, { color: "gray", wrap: "wrap", children: [top.item.state === "open" ? (s.mayAnswer ? "Enter answers · " : `${readOnlyLabel(TOKEN_ENV)} · `) : "", "e opens its episode \u00B7 Esc"] })] }));
|
|
1025
1147
|
}
|
|
1148
|
+
if (top.type === "confirm") {
|
|
1149
|
+
const room = Math.max(1, bodyHeight - 2 - (2 + (top.busy ? 1 : 0) + (top.error ? 1 : 0)));
|
|
1150
|
+
return box("red", _jsxs(_Fragment, { children: [_jsx(Text, { bold: true, wrap: "truncate-end", children: cleanLine(top.title) }), _jsx(Box, { flexDirection: "column", height: Math.min(room, rowsOf(top.lines.map((line) => cleanLine(line.text)))), overflow: "hidden", flexShrink: 0, children: _jsx(Box, { flexDirection: "column", flexShrink: 0, children: top.lines.map((line, index) => (_jsx(Text, { wrap: "wrap", color: line.tone === "warn" ? "yellow" : line.tone === "meta" ? "gray" : undefined, children: cleanLine(line.text) }, index))) }) }), top.busy ? _jsx(Text, { color: "gray", children: "Working\u2026" }) : null, top.error ? _jsx(Text, { color: "red", wrap: "wrap", children: cleanLine(top.error) }) : null, _jsxs(Text, { wrap: "truncate-end", children: [top.yes && !top.busy ? (_jsxs(_Fragment, { children: [_jsx(Text, { color: "red", bold: true, children: "y" }), " ", top.yes, " \u00B7", " "] })) : null, _jsx(Text, { color: "gray", children: top.busy ? "" : "Esc cancels" })] })] }));
|
|
1151
|
+
}
|
|
1152
|
+
if (top.type === "inquiries") {
|
|
1153
|
+
const view = top.view;
|
|
1154
|
+
const items = view?.shelf?.items ?? [];
|
|
1155
|
+
const wovenRows = view ? Math.max(1, view.woven.length) : 0;
|
|
1156
|
+
const room = Math.max(1, bodyHeight - 2 - (1 + Math.min(wovenRows, 6) + (view?.attach ? 1 : 0) + 1));
|
|
1157
|
+
const shelfWindow = windowOf(items, clamp(top.selected, items.length), room);
|
|
1158
|
+
return box("blue", _jsxs(_Fragment, { children: [_jsxs(Text, { bold: true, wrap: "truncate-end", children: ["Inquiries \u00B7 ", top.label] }), !view && !top.error ? _jsx(Text, { color: "gray", children: "Asking the episode's inquiry door\u2026" }) : null, top.error ? _jsx(Text, { color: "red", wrap: "wrap", children: cleanLine(top.error) }) : null, view && view.woven.length === 0 ? _jsx(Text, { color: "gray", italic: true, children: "No inquiry is woven into this episode." }) : null, view?.woven.slice(0, 6).map((w) => (_jsxs(Text, { wrap: "truncate-end", children: [_jsx(Text, { color: "blue", children: "\u25C7" }), " ", w.artefact, _jsxs(Text, { color: "gray", children: [w.issue ? ` · ${w.issue}` : "", w.freshness ? ` · ${w.freshness}` : "", w.exists ? "" : " · missing from the shelf"] })] }, w.artefact))), view?.attach ? (_jsxs(Text, { color: "gray", wrap: "truncate-end", children: ["\u2500\u2500 the shelf \u00B7 ", items.length, " of ", view.shelf?.total ?? 0, ", newest first", top.query ? ` · “${cleanLine(top.query)}”` : "", view.shelf?.unavailable ? " · unavailable on the app's host" : "", " \u2500\u2500"] })) : null, shelfWindow.items.map((item, index) => {
|
|
1159
|
+
const isSelected = shelfWindow.start + index === clamp(top.selected, items.length);
|
|
1160
|
+
return (_jsxs(Text, { wrap: "truncate-end", inverse: isSelected, color: item.wovenHere ? "gray" : undefined, children: [cleanLine(item.artefact), _jsxs(Text, { color: "gray", children: [" · ", item.modified.slice(0, 10), item.issue ? ` · ${item.issue}` : "", item.episodes.length ? ` · in episode ${item.episodes.join(", ")}` : "", item.wovenHere ? " · already here" : ""] })] }, item.artefact));
|
|
1161
|
+
}), _jsx(Text, { color: "gray", wrap: "truncate-end", children: view?.attach ? "↑↓ · ⏎ attach · / filter · Esc" : view ? "Attaching needs a writer the app accepts · Esc" : "Esc" })] }));
|
|
1162
|
+
}
|
|
1163
|
+
if (top.type === "companion") {
|
|
1164
|
+
const shown = top.ran ?? top.answer;
|
|
1165
|
+
const lines = shown ? companionLines(shown) : [];
|
|
1166
|
+
const acts = !top.ran && planActs(top.answer);
|
|
1167
|
+
const toneColor = { state: "yellow", meta: "gray", warn: "yellow", error: "red", step: "green" };
|
|
1168
|
+
const nothing = !acts && !top.busy && !top.ran && Boolean(shown?.plan?.state);
|
|
1169
|
+
// The plan is clipped above the keys, so `y` and Esc stay on screen however long the starter prompt is.
|
|
1170
|
+
const room = Math.max(1, bodyHeight - 2 - (2 + (top.busy ? 1 : 0) + (top.error ? 1 : 0) + (nothing ? 1 : 0)));
|
|
1171
|
+
const planRows = Math.min(room, rowsOf(lines.map((line) => line.text)));
|
|
1172
|
+
return box("red", _jsxs(_Fragment, { children: [_jsxs(Text, { bold: true, wrap: "truncate-end", children: ["Companion \u00B7 ", top.label, " \u00B7 mia ", _jsxs(Text, { color: "gray", children: ["\u00B7 archive the running session: ", top.archive ? "yes" : "no"] })] }), top.busy === "plan" ? _jsx(Text, { color: "gray", children: "Asking tide for the plan\u2026" }) : null, top.busy === "run" ? _jsx(Text, { color: "yellow", children: "Opening the seat\u2026 (up to a few minutes)" }) : null, top.error ? _jsx(Text, { color: "red", wrap: "wrap", children: cleanLine(top.error) }) : null, _jsx(Box, { flexDirection: "column", height: planRows, overflow: "hidden", flexShrink: 0, children: _jsx(Box, { flexDirection: "column", flexShrink: 0, children: lines.map((line, index) => (_jsx(Text, { wrap: "wrap", color: line.tone ? toneColor[line.tone] : undefined, children: line.text }, index))) }) }), _jsxs(Text, { wrap: "truncate-end", children: [acts && !top.busy ? (_jsxs(_Fragment, { children: [_jsx(Text, { color: "red", bold: true, children: "y" }), " runs this plan \u00B7", " "] })) : null, !top.ran && !top.busy ? _jsxs(Text, { color: "gray", children: ["A ", top.archive ? "keeps" : "archives", " the running session and asks again \u00B7 "] }) : null, _jsx(Text, { color: "gray", children: "Esc" })] }), nothing ? _jsx(Text, { color: "gray", children: "Nothing to run for this plan." }) : null] }));
|
|
1173
|
+
}
|
|
1026
1174
|
if (top.type === "circles") {
|
|
1027
1175
|
const entries = top.index ? circleEntries(top.index) : [];
|
|
1028
1176
|
const listWindow = windowOf(entries, clamp(top.selected, entries.length), Math.max(1, bodyHeight - 4));
|