@miadi/chronicle-tui 0.5.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 +31 -0
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +185 -6
- package/dist/App.js.map +1 -1
- package/dist/actions.d.ts +58 -2
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +115 -2
- package/dist/actions.js.map +1 -1
- package/dist/api.d.ts +47 -2
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +50 -9
- package/dist/api.js.map +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- 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/model.d.ts +10 -0
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +15 -0
- package/dist/model.js.map +1 -1
- 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,9 @@ 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 |
|
|
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 |
|
|
37
40
|
| `?` | all keys · `q` quits |
|
|
38
41
|
|
|
39
42
|
Under 90 columns (a phone, a narrow split) one pane shows at a time: the list
|
|
@@ -84,10 +87,38 @@ miadi-chronicle --url https://miadi.tail3b11eb.ts.net
|
|
|
84
87
|
`POST /api/chronicle/attention`, and the wheel is reported, never assumed.
|
|
85
88
|
- **A review** (`v`) posts to `POST /api/chronicle/episodes/{ref}/reviews`;
|
|
86
89
|
with your person token its talking circle opens in your name.
|
|
90
|
+
- **Lineage** (`L`) is the room's lineage form
|
|
91
|
+
(`POST /api/chronicle/episodes/{ref}/lineage`): the door writes
|
|
92
|
+
`episode.yaml`, puts the edge and its words on the wheel, and lands the
|
|
93
|
+
commit. Offered when the episode door says it would take the write
|
|
94
|
+
(`capabilities.mint`). The episode card shows what each episode continues
|
|
95
|
+
from and relates to.
|
|
87
96
|
|
|
88
97
|
Write actions declare `effect: "write"` and are offered only when the app said
|
|
89
98
|
it would record them.
|
|
90
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
|
+
|
|
91
122
|
## Talking circles
|
|
92
123
|
|
|
93
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";
|
|
@@ -108,6 +110,7 @@ export function App(props) {
|
|
|
108
110
|
reader: null,
|
|
109
111
|
access: VISITOR,
|
|
110
112
|
mayAnswer: false,
|
|
113
|
+
mayMint: false,
|
|
111
114
|
board: null,
|
|
112
115
|
boardSel: 0,
|
|
113
116
|
history: [],
|
|
@@ -203,7 +206,7 @@ export function App(props) {
|
|
|
203
206
|
return;
|
|
204
207
|
try {
|
|
205
208
|
const reader = await api.me();
|
|
206
|
-
const mayAnswer = await api.mayAnswer();
|
|
209
|
+
const [mayAnswer, mayMint] = await Promise.all([api.mayAnswer(), api.mayMint()]);
|
|
207
210
|
if (!live)
|
|
208
211
|
return;
|
|
209
212
|
// Signing in can show more episodes (community ones), so the selection is kept by name, not by row.
|
|
@@ -211,7 +214,7 @@ export function App(props) {
|
|
|
211
214
|
const keep = before.s.view === "attention" ? undefined : before.currentRow?.name;
|
|
212
215
|
const keepItem = before.boardItem;
|
|
213
216
|
details.current.clear();
|
|
214
|
-
set({ reader, mayAnswer, access: { signedIn: Boolean(reader), mayWrite: mayAnswer } });
|
|
217
|
+
set({ reader, mayAnswer, mayMint, access: { signedIn: Boolean(reader), mayWrite: mayAnswer } });
|
|
215
218
|
const after = derive();
|
|
216
219
|
const listSel = keep ? after.filtered.findIndex((row) => row.name === keep) : -1;
|
|
217
220
|
const boardSel = keepItem ? after.board.findIndex((item) => item.episode === keepItem.episode && item.id === keepItem.id) : -1;
|
|
@@ -329,8 +332,22 @@ export function App(props) {
|
|
|
329
332
|
api: s.api,
|
|
330
333
|
reader: s.reader,
|
|
331
334
|
mayAnswer: s.mayAnswer,
|
|
335
|
+
mayMint: s.mayMint,
|
|
336
|
+
relateFrom: s.relateFrom,
|
|
337
|
+
setRelateFrom: (name) => set({ relateFrom: name }),
|
|
338
|
+
reload: reloadChronicle,
|
|
332
339
|
};
|
|
333
340
|
};
|
|
341
|
+
/** Read the chronicle again after a write landed in it, keeping the episode in focus. */
|
|
342
|
+
async function reloadChronicle() {
|
|
343
|
+
const keep = derive().currentRow?.name;
|
|
344
|
+
const chronicle = await loadChronicle(root);
|
|
345
|
+
details.current.clear();
|
|
346
|
+
set({ chronicle, status: ui.current.status });
|
|
347
|
+
const index = keep ? derive().filtered.findIndex((row) => row.name === keep) : -1;
|
|
348
|
+
if (index !== -1)
|
|
349
|
+
set({ listSel: index, status: ui.current.status });
|
|
350
|
+
}
|
|
334
351
|
const openFile = (name, file, from, line) => {
|
|
335
352
|
const s = ui.current;
|
|
336
353
|
const text = loadText(s.chronicle.root, name, file, s.access);
|
|
@@ -583,6 +600,76 @@ export function App(props) {
|
|
|
583
600
|
await reloadCeremony(handle);
|
|
584
601
|
}, (error) => set({ status: cleanLine(error.message) }));
|
|
585
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
|
+
};
|
|
586
673
|
// ── keys ─────────────────────────────────────────────────────────────────
|
|
587
674
|
// A paste into an input arrives whole, line breaks kept (bracketed paste).
|
|
588
675
|
const pasteActive = ui.current.overlays[ui.current.overlays.length - 1]?.overlay.type === "input";
|
|
@@ -645,7 +732,11 @@ export function App(props) {
|
|
|
645
732
|
if (!value.trim() && !top.allowEmpty)
|
|
646
733
|
return updateTop({ ...top, error: "Nothing to send yet." });
|
|
647
734
|
updateTop({ ...top, busy: true, error: undefined });
|
|
648
|
-
top.submit(value, choices.map((c) => c.values[c.index])).then(async (
|
|
735
|
+
top.submit(value, choices.map((c) => c.values[c.index])).then(async (result) => {
|
|
736
|
+
// A two-part form: the next input takes this one's place.
|
|
737
|
+
if (typeof result !== "string")
|
|
738
|
+
return updateTop(result.next);
|
|
739
|
+
const message = result;
|
|
649
740
|
set({ status: cleanLine(message), overlays: ui.current.overlays.slice(0, -1) });
|
|
650
741
|
try {
|
|
651
742
|
await top.after?.();
|
|
@@ -666,6 +757,9 @@ export function App(props) {
|
|
|
666
757
|
if (top) {
|
|
667
758
|
if (top.type === "help")
|
|
668
759
|
return closeTop();
|
|
760
|
+
// An act under way is not left half-seen.
|
|
761
|
+
if (top.type === "confirm" && top.busy)
|
|
762
|
+
return;
|
|
669
763
|
if (key.escape || input === "q" || input === "h" || key.backspace || key.leftArrow)
|
|
670
764
|
return closeTop();
|
|
671
765
|
if (top.type === "doors") {
|
|
@@ -685,6 +779,51 @@ export function App(props) {
|
|
|
685
779
|
}
|
|
686
780
|
if (top.type === "qr" && input === "i")
|
|
687
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
|
+
}
|
|
688
827
|
if (top.type === "circles" && top.index) {
|
|
689
828
|
const entries = circleEntries(top.index);
|
|
690
829
|
const step = (from, by) => {
|
|
@@ -877,6 +1016,8 @@ export function App(props) {
|
|
|
877
1016
|
return move(1_000_000, PLAIN_KEY);
|
|
878
1017
|
if (key.return || key.rightArrow || input === "l")
|
|
879
1018
|
return forward();
|
|
1019
|
+
if (key.escape && s.relateFrom)
|
|
1020
|
+
return set({ relateFrom: undefined, status: "No longer relating." });
|
|
880
1021
|
if (key.escape || key.leftArrow || input === "h" || key.backspace)
|
|
881
1022
|
return back();
|
|
882
1023
|
if (input === "?")
|
|
@@ -933,13 +1074,25 @@ export function App(props) {
|
|
|
933
1074
|
const linesOf = (text, cap) => Math.min(cap, Math.max(1, wrapSegments([{ text, style: "text" }], textWidth).length));
|
|
934
1075
|
const titleLines = d.currentRow ? linesOf(`${d.currentRow.number ?? "·"} ${d.currentRow.title}`, 3) : 0;
|
|
935
1076
|
const goalLines = goal ? linesOf(goal, compact ? 6 : 8) : 0;
|
|
936
|
-
|
|
1077
|
+
// The episodes this one continues from or relates to, by number where it has one.
|
|
1078
|
+
const lineage = d.currentRow?.lineage.length
|
|
1079
|
+
? ["continues from", "relates to", "branch of"]
|
|
1080
|
+
.map((kind) => {
|
|
1081
|
+
const targets = d.currentRow.lineage.filter((link) => link.kind === kind).map((link) => (link.number !== undefined ? String(link.number) : link.target));
|
|
1082
|
+
return targets.length ? `${kind} ${targets.join(", ")}` : "";
|
|
1083
|
+
})
|
|
1084
|
+
.filter(Boolean)
|
|
1085
|
+
.join(" · ")
|
|
1086
|
+
: "";
|
|
1087
|
+
const fixedLines = titleLines + 2 + (openHere ? 1 : 0) + (lineage ? 1 : 0) + goalLines + (d.detail?.error ? 1 : 0) + 1;
|
|
937
1088
|
const fileWindow = windowOf(d.files, clamp(s.fileSel, d.files.length), Math.max(1, bodyHeight - fixedLines));
|
|
938
|
-
const detailPane = (_jsx(Box, { flexDirection: "column", width: rightWidth, height: bodyHeight, overflow: "hidden", paddingLeft: compact ? 0 : 1, children: _jsx(Box, { flexDirection: "column", flexShrink: 0, children: s.view === "attention" ? (d.boardItem ? itemBody(d.boardItem, rightWidth - 1) : _jsx(Text, { color: "gray", children: "Nothing selected." })) : d.currentRow ? (_jsxs(_Fragment, { children: [_jsx(Box, { height: titleLines, overflow: "hidden", flexShrink: 0, children: _jsxs(Text, { bold: true, wrap: "wrap", children: [_jsx(Text, { color: "yellow", children: d.currentRow.number ?? "·" }), " ", d.currentRow.title] }) }), _jsxs(Text, { color: "gray", wrap: "truncate-end", children: [[formatDate(d.currentRow.date), d.currentRow.status, d.currentRow.type].filter(Boolean).join(" · "), d.currentRow.structured ? "" : " · no episode.yaml", d.currentRow.audience === "community" ? " · community" : ""] }), _jsxs(Text, { color: "green", wrap: "truncate-end", children: ["reading: ", d.currentRow.readings.direction, " \u00B7 ", d.currentRow.readings.theme, " \u00B7 ", d.currentRow.readings.arc] }), openHere ? _jsxs(Text, { color: "yellow", children: ["? ", openHere, " open question", openHere === 1 ? "" : "s", " \u00B7 a"] }) : null, goal ? (_jsx(Box, { height: goalLines, overflow: "hidden", flexShrink: 0, children: _jsx(Text, { wrap: "wrap", children: goal }) })) : null, d.detail?.error ? _jsx(Text, { color: "red", children: cleanLine(d.detail.error) }) : null, _jsxs(Text, { color: "gray", wrap: "truncate-end", children: ["── ", d.files.length, d.files.length >= MAX_FILES ? "+" : "", " file", d.files.length === 1 ? "" : "s", s.view === "files" ? "" : " · Enter", " ──"] }), fileWindow.items.map((file, index) => {
|
|
1089
|
+
const detailPane = (_jsx(Box, { flexDirection: "column", width: rightWidth, height: bodyHeight, overflow: "hidden", paddingLeft: compact ? 0 : 1, children: _jsx(Box, { flexDirection: "column", flexShrink: 0, children: s.view === "attention" ? (d.boardItem ? itemBody(d.boardItem, rightWidth - 1) : _jsx(Text, { color: "gray", children: "Nothing selected." })) : d.currentRow ? (_jsxs(_Fragment, { children: [_jsx(Box, { height: titleLines, overflow: "hidden", flexShrink: 0, children: _jsxs(Text, { bold: true, wrap: "wrap", children: [_jsx(Text, { color: "yellow", children: d.currentRow.number ?? "·" }), " ", d.currentRow.title] }) }), _jsxs(Text, { color: "gray", wrap: "truncate-end", children: [[formatDate(d.currentRow.date), d.currentRow.status, d.currentRow.type].filter(Boolean).join(" · "), d.currentRow.structured ? "" : " · no episode.yaml", d.currentRow.audience === "community" ? " · community" : ""] }), _jsxs(Text, { color: "green", wrap: "truncate-end", children: ["reading: ", d.currentRow.readings.direction, " \u00B7 ", d.currentRow.readings.theme, " \u00B7 ", d.currentRow.readings.arc] }), openHere ? _jsxs(Text, { color: "yellow", children: ["? ", openHere, " open question", openHere === 1 ? "" : "s", " \u00B7 a"] }) : null, lineage ? _jsxs(Text, { color: "cyan", wrap: "truncate-end", children: ["\u2197 ", lineage] }) : null, goal ? (_jsx(Box, { height: goalLines, overflow: "hidden", flexShrink: 0, children: _jsx(Text, { wrap: "wrap", children: goal }) })) : null, d.detail?.error ? _jsx(Text, { color: "red", children: cleanLine(d.detail.error) }) : null, _jsxs(Text, { color: "gray", wrap: "truncate-end", children: ["── ", d.files.length, d.files.length >= MAX_FILES ? "+" : "", " file", d.files.length === 1 ? "" : "s", s.view === "files" ? "" : " · Enter", " ──"] }), fileWindow.items.map((file, index) => {
|
|
939
1090
|
const isSelected = s.view === "files" && fileWindow.start + index === clamp(s.fileSel, d.files.length);
|
|
940
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));
|
|
941
1092
|
})] })) : (_jsx(Text, { color: "gray", children: "Nothing selected." })) }) }));
|
|
942
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));
|
|
943
1096
|
// Children never shrink: a fixed-height box clips what does not fit instead
|
|
944
1097
|
// of squeezing rows to zero height, where Ink draws them over each other.
|
|
945
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 }) }));
|
|
@@ -992,6 +1145,32 @@ export function App(props) {
|
|
|
992
1145
|
if (top.type === "item") {
|
|
993
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"] })] }));
|
|
994
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
|
+
}
|
|
995
1174
|
if (top.type === "circles") {
|
|
996
1175
|
const entries = top.index ? circleEntries(top.index) : [];
|
|
997
1176
|
const listWindow = windowOf(entries, clamp(top.selected, entries.length), Math.max(1, bodyHeight - 4));
|
|
@@ -1102,6 +1281,6 @@ export function App(props) {
|
|
|
1102
1281
|
? "…"
|
|
1103
1282
|
: "read-only"
|
|
1104
1283
|
: "";
|
|
1105
|
-
return (_jsxs(Box, { flexDirection: "column", height: rows, width: columns, children: [_jsxs(Text, { wrap: "truncate-end", children: [_jsx(Text, { bold: true, color: "yellow", children: "miadi-chronicle" }), " ", _jsx(Text, { color: "gray", children: where }), readerLabel ? _jsx(Text, { color: s.reader ? "green" : "gray", children: ` · ${readerLabel}` }) : null] }), _jsxs(Text, { wrap: "truncate-end", children: [_jsx(Text, { color: s.inputMode ? "yellow" : "gray", children: "/ " }), cleanLine(s.query), s.inputMode ? _jsx(Text, { inverse: true, children: " " }) : null, " ", s.view === "attention" ? (_jsx(Text, { color: "gray", children: "attention" })) : (_jsxs(_Fragment, { children: [_jsx(Text, { color: s.searchMode === "title" ? "yellow" : "gray", children: "title" }), _jsx(Text, { color: "gray", children: compact ? "|" : " | " }), _jsx(Text, { color: s.searchMode === "full" ? "yellow" : "gray", children: compact ? "full" : "full text" })] })), s.view === "list" ? _jsxs(Text, { color: "gray", children: [" \u00B7 ", d.filtered.length] }) : null, s.view === "attention" ? _jsxs(Text, { color: "gray", children: [" \u00B7 ", d.board.length] }) : null] }), _jsx(Box, { flexDirection: "row", height: bodyHeight, overflow: "hidden", children: body }), _jsx(Text, { color: "gray", wrap: "truncate-end", children: s.status }), _jsxs(Text, { wrap: "truncate-end", children: [(s.overlays.length ? [] : available).map((action) => (_jsxs(Text, { children: [_jsx(Text, { color: action.effect === "write" ? "red" : "yellow", children: action.key === "Enter" ? "⏎" : action.key }), " ", action.label, " "] }, action.id))), _jsx(Text, { color: "gray", children: hints })] })] }));
|
|
1284
|
+
return (_jsxs(Box, { flexDirection: "column", height: rows, width: columns, children: [_jsxs(Text, { wrap: "truncate-end", children: [_jsx(Text, { bold: true, color: "yellow", children: "miadi-chronicle" }), " ", _jsx(Text, { color: "gray", children: where }), readerLabel ? _jsx(Text, { color: s.reader ? "green" : "gray", children: ` · ${readerLabel}` }) : null] }), _jsxs(Text, { wrap: "truncate-end", children: [_jsx(Text, { color: s.inputMode ? "yellow" : "gray", children: "/ " }), cleanLine(s.query), s.inputMode ? _jsx(Text, { inverse: true, children: " " }) : null, " ", s.view === "attention" ? (_jsx(Text, { color: "gray", children: "attention" })) : (_jsxs(_Fragment, { children: [_jsx(Text, { color: s.searchMode === "title" ? "yellow" : "gray", children: "title" }), _jsx(Text, { color: "gray", children: compact ? "|" : " | " }), _jsx(Text, { color: s.searchMode === "full" ? "yellow" : "gray", children: compact ? "full" : "full text" })] })), s.view === "list" ? _jsxs(Text, { color: "gray", children: [" \u00B7 ", d.filtered.length] }) : null, s.relateFrom ? _jsxs(Text, { color: "red", children: [" \u00B7 relating ", episodeLabel(s.relateFrom), " \u2192 L"] }) : null, s.view === "attention" ? _jsxs(Text, { color: "gray", children: [" \u00B7 ", d.board.length] }) : null] }), _jsx(Box, { flexDirection: "row", height: bodyHeight, overflow: "hidden", children: body }), _jsx(Text, { color: "gray", wrap: "truncate-end", children: s.status }), _jsxs(Text, { wrap: "truncate-end", children: [(s.overlays.length ? [] : available).map((action) => (_jsxs(Text, { children: [_jsx(Text, { color: action.effect === "write" ? "red" : "yellow", children: action.key === "Enter" ? "⏎" : action.key }), " ", action.label, " "] }, action.id))), _jsx(Text, { color: "gray", children: hints })] })] }));
|
|
1106
1285
|
}
|
|
1107
1286
|
//# sourceMappingURL=App.js.map
|