@tenyokjs/devdevian-cli 0.4.0-alpha.1 → 0.5.0-alpha.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/README.md +26 -0
- package/VERSIONS.md +71 -0
- package/bin/devdevian.mjs +51 -26
- package/lib/appearance.mjs +179 -0
- package/lib/client.mjs +12 -4
- package/lib/external-editor.mjs +111 -0
- package/lib/picker.mjs +6 -5
- package/lib/problems.mjs +29 -0
- package/lib/review-workspace.mjs +17 -8
- package/lib/rich-text.mjs +96 -0
- package/lib/theme.mjs +66 -13
- package/lib/tui-data.mjs +44 -1
- package/lib/tui-model.mjs +2 -1
- package/lib/tui.mjs +528 -163
- package/package.json +2 -1
package/lib/tui.mjs
CHANGED
|
@@ -29,11 +29,23 @@ import {
|
|
|
29
29
|
loadPage,
|
|
30
30
|
contentLines,
|
|
31
31
|
assertSnippet,
|
|
32
|
+
notificationRoute,
|
|
32
33
|
} from "./tui-data.mjs";
|
|
33
34
|
import { basename } from "node:path";
|
|
34
|
-
import {
|
|
35
|
+
import { themeFor, ThemeContext, useTheme } from "./theme.mjs";
|
|
36
|
+
import {
|
|
37
|
+
appearanceFields,
|
|
38
|
+
defaultAppearance,
|
|
39
|
+
loadAppearance,
|
|
40
|
+
saveAppearance,
|
|
41
|
+
normalizeAppearance,
|
|
42
|
+
appearanceFlags,
|
|
43
|
+
} from "./appearance.mjs";
|
|
35
44
|
import { liveLoop, anchoredIndex } from "./live.mjs";
|
|
36
45
|
import { contextActions } from "./context-actions.mjs";
|
|
46
|
+
import { editExternal } from "./external-editor.mjs";
|
|
47
|
+
import { markdownRows } from "./rich-text.mjs";
|
|
48
|
+
import { problemMessage } from "./problems.mjs";
|
|
37
49
|
import { ReviewWorkspace, codeRange } from "./review-workspace.mjs";
|
|
38
50
|
const h = React.createElement;
|
|
39
51
|
const itemsOf = (value) => (Array.isArray(value) ? value : value?.items || []);
|
|
@@ -79,6 +91,7 @@ function openBrowser(url) {
|
|
|
79
91
|
child.unref();
|
|
80
92
|
}
|
|
81
93
|
function fieldsFor(form) {
|
|
94
|
+
if (form.kind === "appearance") return appearanceFields;
|
|
82
95
|
if (form.kind === "publication")
|
|
83
96
|
return [
|
|
84
97
|
{
|
|
@@ -186,25 +199,49 @@ function fieldsFor(form) {
|
|
|
186
199
|
},
|
|
187
200
|
];
|
|
188
201
|
}
|
|
189
|
-
function TextLine({ text, width, color
|
|
190
|
-
|
|
202
|
+
function TextLine({ text, width, color, bold = false }) {
|
|
203
|
+
const colors = useTheme();
|
|
204
|
+
return h(
|
|
205
|
+
Text,
|
|
206
|
+
{ color: color || colors.text, bold, wrap: "truncate" },
|
|
207
|
+
clip(text, width),
|
|
208
|
+
);
|
|
191
209
|
}
|
|
192
210
|
function FrameRows({ rows, width, height, offset = 0 }) {
|
|
211
|
+
const colors = useTheme();
|
|
193
212
|
return h(
|
|
194
213
|
Box,
|
|
195
214
|
{ flexDirection: "column", height, overflow: "hidden" },
|
|
196
215
|
...rows.slice(offset, offset + height).map((row, i) =>
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
216
|
+
row.spans
|
|
217
|
+
? h(
|
|
218
|
+
Text,
|
|
219
|
+
{
|
|
220
|
+
key: i,
|
|
221
|
+
color: row.color || colors.text,
|
|
222
|
+
bold: row.bold,
|
|
223
|
+
wrap: "truncate",
|
|
224
|
+
},
|
|
225
|
+
...row.spans.map((span, n) =>
|
|
226
|
+
h(
|
|
227
|
+
Text,
|
|
228
|
+
{ key: n, color: span.color, bold: span.bold || row.bold },
|
|
229
|
+
span.text,
|
|
230
|
+
),
|
|
231
|
+
),
|
|
232
|
+
)
|
|
233
|
+
: h(TextLine, {
|
|
234
|
+
key: i,
|
|
235
|
+
text: typeof row === "string" ? row : row.text,
|
|
236
|
+
width,
|
|
237
|
+
color: row.color || colors.text,
|
|
238
|
+
bold: row.bold || false,
|
|
239
|
+
}),
|
|
204
240
|
),
|
|
205
241
|
);
|
|
206
242
|
}
|
|
207
243
|
function InputText({ value, cursor, width, height = 1 }) {
|
|
244
|
+
const colors = useTheme();
|
|
208
245
|
const result = cursorLines(value, cursor, width),
|
|
209
246
|
start = Math.max(0, result.line - height + 1);
|
|
210
247
|
return h(
|
|
@@ -223,6 +260,7 @@ function InputText({ value, cursor, width, height = 1 }) {
|
|
|
223
260
|
);
|
|
224
261
|
}
|
|
225
262
|
function FormView({ form, active, cursor, width, height }) {
|
|
263
|
+
const colors = useTheme();
|
|
226
264
|
const fields = fieldsFor(form),
|
|
227
265
|
rows = [];
|
|
228
266
|
let selectedLine = 0;
|
|
@@ -274,14 +312,19 @@ function FormView({ form, active, cursor, width, height }) {
|
|
|
274
312
|
rows.push({
|
|
275
313
|
text:
|
|
276
314
|
(active === fields.length ? "› " : " ") +
|
|
277
|
-
|
|
315
|
+
(form.kind === "appearance"
|
|
316
|
+
? "Save appearance [Ctrl+S]"
|
|
317
|
+
: "Preview publication [Ctrl+S]"),
|
|
278
318
|
color: colors.blue,
|
|
279
319
|
bold: true,
|
|
280
320
|
});
|
|
281
|
-
const offset = Math.max(
|
|
321
|
+
const offset = Math.max(
|
|
322
|
+
0,
|
|
323
|
+
Math.min(selectedLine - (height >= 4 ? 1 : 0), rows.length - height),
|
|
324
|
+
);
|
|
282
325
|
return h(FrameRows, { rows, width, height, offset });
|
|
283
326
|
}
|
|
284
|
-
function listRows(route, data, index, width) {
|
|
327
|
+
function listRows(route, data, index, width, colors, density = "comfortable") {
|
|
285
328
|
const items = data.items || [];
|
|
286
329
|
return items.flatMap((item, i) => {
|
|
287
330
|
let title = item.title || item.name || "",
|
|
@@ -367,13 +410,113 @@ function listRows(route, data, index, width) {
|
|
|
367
410
|
color: selected ? colors.text : colors.muted,
|
|
368
411
|
selected,
|
|
369
412
|
},
|
|
370
|
-
|
|
413
|
+
...(density === "compact"
|
|
414
|
+
? []
|
|
415
|
+
: [
|
|
416
|
+
{
|
|
417
|
+
text: " " + "─".repeat(Math.max(0, width - 4)),
|
|
418
|
+
color: colors.line,
|
|
419
|
+
},
|
|
420
|
+
]),
|
|
371
421
|
];
|
|
372
422
|
});
|
|
373
423
|
}
|
|
374
|
-
function
|
|
375
|
-
|
|
424
|
+
function documentRows(route, data, width, colors) {
|
|
425
|
+
if (route.view !== "post" || (route.tab && route.tab !== "overview"))
|
|
426
|
+
return contentLines(route, data).flatMap((line) => wrap(line, width));
|
|
427
|
+
const post = data?.post;
|
|
428
|
+
if (!post) return [];
|
|
429
|
+
const rows = markdownRows(post.body || "", width, colors);
|
|
430
|
+
if (post.review_focus)
|
|
431
|
+
rows.push(
|
|
432
|
+
{ text: "" },
|
|
433
|
+
{ text: "REVIEW FOCUS", color: colors.purple, bold: true },
|
|
434
|
+
...markdownRows(post.review_focus, width, colors),
|
|
435
|
+
);
|
|
436
|
+
if (post.code) {
|
|
437
|
+
// Pick a fence longer than any backtick run in the snippet.
|
|
438
|
+
const fence = "`".repeat(
|
|
439
|
+
Math.max(
|
|
440
|
+
3,
|
|
441
|
+
...(post.code.match(/`+/g) || []).map((run) => run.length + 1),
|
|
442
|
+
),
|
|
443
|
+
);
|
|
444
|
+
rows.push(
|
|
445
|
+
{ text: "" },
|
|
446
|
+
...markdownRows(fence + "\n" + post.code + "\n" + fence, width, colors),
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
if (post.links?.length)
|
|
450
|
+
rows.push(
|
|
451
|
+
{ text: "LINKS", bold: true },
|
|
452
|
+
...post.links.flatMap((link) =>
|
|
453
|
+
wrap(link.label + " · " + link.url, width),
|
|
454
|
+
),
|
|
455
|
+
);
|
|
456
|
+
if (post.archived_at || post.locked_at)
|
|
457
|
+
rows.push({
|
|
458
|
+
text: "Replies are paused for this conversation.",
|
|
459
|
+
color: colors.muted,
|
|
460
|
+
});
|
|
461
|
+
return rows;
|
|
462
|
+
}
|
|
463
|
+
function AppearanceView({ form, active, cursor, width, height }) {
|
|
464
|
+
const c = useTheme(),
|
|
465
|
+
wide = width >= 100;
|
|
466
|
+
return h(
|
|
467
|
+
Box,
|
|
468
|
+
{ height, overflow: "hidden", gap: wide ? 2 : 0 },
|
|
469
|
+
h(
|
|
470
|
+
Box,
|
|
471
|
+
{ width: wide ? width - 36 : width, flexDirection: "column" },
|
|
472
|
+
h(FormView, {
|
|
473
|
+
form,
|
|
474
|
+
active,
|
|
475
|
+
cursor,
|
|
476
|
+
width: wide ? width - 36 : width,
|
|
477
|
+
height,
|
|
478
|
+
}),
|
|
479
|
+
),
|
|
480
|
+
wide
|
|
481
|
+
? h(
|
|
482
|
+
Box,
|
|
483
|
+
{
|
|
484
|
+
width: 34,
|
|
485
|
+
height,
|
|
486
|
+
borderStyle: c.frame,
|
|
487
|
+
borderColor: c.line,
|
|
488
|
+
paddingX: 1,
|
|
489
|
+
flexDirection: "column",
|
|
490
|
+
overflow: "hidden",
|
|
491
|
+
},
|
|
492
|
+
h(Text, { color: c.purple, bold: true }, "LIVE PREVIEW"),
|
|
493
|
+
h(Text, { color: c.muted }, "Only this terminal changes."),
|
|
494
|
+
h(Text, null, " "),
|
|
495
|
+
h(
|
|
496
|
+
Text,
|
|
497
|
+
{ color: c.text, bold: true },
|
|
498
|
+
"A good conversation starts here",
|
|
499
|
+
),
|
|
500
|
+
h(
|
|
501
|
+
Text,
|
|
502
|
+
{ color: c.blue, backgroundColor: c.selected },
|
|
503
|
+
"▸ Your selected conversation",
|
|
504
|
+
),
|
|
505
|
+
h(Text, { color: c.muted }, "Ada · code review · just now"),
|
|
506
|
+
h(Text, { color: c.green }, "+ ship with confidence"),
|
|
507
|
+
h(Text, { color: c.red }, "− leave the guesswork behind"),
|
|
508
|
+
h(Text, null, " "),
|
|
509
|
+
h(Text, { color: c.purple }, "← → change · Tab next"),
|
|
510
|
+
h(Text, { color: c.text }, "Ctrl+S save · Esc cancel"),
|
|
511
|
+
h(Text, { color: c.muted }, "0 restores the default preview"),
|
|
512
|
+
)
|
|
513
|
+
: null,
|
|
514
|
+
);
|
|
515
|
+
}
|
|
516
|
+
function App({ initialConfig, flags, root, initialAppearance }) {
|
|
517
|
+
const { exit, suspendTerminal } = useApp(),
|
|
376
518
|
{ columns, rows } = useWindowSize();
|
|
519
|
+
const [appearance, setAppearance] = useState(initialAppearance.settings);
|
|
377
520
|
const [config, setConfig] = useState(initialConfig),
|
|
378
521
|
[phase, setPhase] = useState(
|
|
379
522
|
initialConfig && !flags.initialLogin ? "checking" : "auth",
|
|
@@ -392,7 +535,7 @@ function App({ initialConfig, flags, root }) {
|
|
|
392
535
|
[command, setCommand] = useState(""),
|
|
393
536
|
[commandCursor, setCommandCursor] = useState(0),
|
|
394
537
|
[error, setError] = useState(""),
|
|
395
|
-
[notice, setNotice] = useState("");
|
|
538
|
+
[notice, setNotice] = useState(initialAppearance.warning || "");
|
|
396
539
|
const [form, setForm] = useState(null),
|
|
397
540
|
[field, setField] = useState(0),
|
|
398
541
|
[cursor, setCursor] = useState(0),
|
|
@@ -417,6 +560,8 @@ function App({ initialConfig, flags, root }) {
|
|
|
417
560
|
notificationGeneration = useRef(0);
|
|
418
561
|
const cache = useRef(new PageCache()),
|
|
419
562
|
history = useRef([]),
|
|
563
|
+
restoreSelection = useRef(null),
|
|
564
|
+
seenSnippet = useRef(null),
|
|
420
565
|
busy = useRef(false),
|
|
421
566
|
alive = useRef(true),
|
|
422
567
|
authController = useRef(null),
|
|
@@ -434,9 +579,13 @@ function App({ initialConfig, flags, root }) {
|
|
|
434
579
|
() => (config ? client(config.origin, config.token) : null),
|
|
435
580
|
[config?.origin, config?.token],
|
|
436
581
|
);
|
|
582
|
+
const effectiveAppearance =
|
|
583
|
+
form?.kind === "appearance" ? normalizeAppearance(form.values) : appearance;
|
|
584
|
+
const colors = themeFor(effectiveAppearance),
|
|
585
|
+
stride = effectiveAppearance.density === "compact" ? 3 : 4;
|
|
437
586
|
const width = Math.max(1, columns),
|
|
438
587
|
height = Math.max(1, rows - 1),
|
|
439
|
-
compact = width < 90,
|
|
588
|
+
compact = width < 90 || effectiveAppearance.sidebar === "hidden",
|
|
440
589
|
sidebar = compact ? 0 : 25,
|
|
441
590
|
bodyHeight = Math.max(1, height - 9),
|
|
442
591
|
contentWidth = Math.max(1, width - sidebar - 6),
|
|
@@ -522,9 +671,18 @@ function App({ initialConfig, flags, root }) {
|
|
|
522
671
|
if (current?.unread_count !== undefined) setUnread(current.unread_count);
|
|
523
672
|
}, [current]);
|
|
524
673
|
useEffect(() => {
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
674
|
+
const post = current?.post;
|
|
675
|
+
if (!post?.code) return;
|
|
676
|
+
if (
|
|
677
|
+
seenSnippet.current?.id === post.id &&
|
|
678
|
+
seenSnippet.current.code !== post.code
|
|
679
|
+
) {
|
|
680
|
+
setReviewLine(0);
|
|
681
|
+
setReviewAnchor(null);
|
|
682
|
+
setReviewHorizontal(0);
|
|
683
|
+
} else
|
|
684
|
+
setReviewLine((line) => Math.min(line, post.code.split("\n").length - 1));
|
|
685
|
+
seenSnippet.current = { id: post.id, code: post.code };
|
|
528
686
|
}, [current?.post?.id, current?.post?.code]);
|
|
529
687
|
const resetAccount = () => {
|
|
530
688
|
cache.current.clear();
|
|
@@ -535,24 +693,55 @@ function App({ initialConfig, flags, root }) {
|
|
|
535
693
|
setPreview(null);
|
|
536
694
|
setDevice(null);
|
|
537
695
|
setUser(null);
|
|
696
|
+
setReading(null);
|
|
697
|
+
setActionIndex(null);
|
|
698
|
+
setUnread(0);
|
|
699
|
+
setLiveStatus("");
|
|
700
|
+
notificationGeneration.current++;
|
|
538
701
|
setRoute({ view: "feed", page: 1 });
|
|
539
702
|
setError("");
|
|
540
703
|
};
|
|
541
704
|
function fail(e) {
|
|
542
705
|
if (!alive.current) return;
|
|
543
|
-
if (e.status === 401) {
|
|
706
|
+
if (e.status === 401 && form) {
|
|
707
|
+
setLive(false);
|
|
708
|
+
setError(problemMessage(e));
|
|
709
|
+
} else if (e.status === 401) {
|
|
544
710
|
resetAccount();
|
|
545
711
|
setPhase("auth");
|
|
546
712
|
setConfig(null);
|
|
547
713
|
setError(
|
|
548
714
|
"Your session expired or was revoked. Press Enter to reconnect.",
|
|
549
715
|
);
|
|
550
|
-
} else setError(
|
|
716
|
+
} else setError(problemMessage(e));
|
|
551
717
|
}
|
|
552
718
|
function navigate(next, { replace = false } = {}) {
|
|
553
|
-
if (!replace)
|
|
719
|
+
if (!replace)
|
|
720
|
+
history.current.push({
|
|
721
|
+
route,
|
|
722
|
+
index,
|
|
723
|
+
scroll,
|
|
724
|
+
selectedId: selected?.id,
|
|
725
|
+
entryType: selected?.entryType,
|
|
726
|
+
reviewFocus,
|
|
727
|
+
reviewLine,
|
|
728
|
+
reviewAnchor,
|
|
729
|
+
reviewHorizontal,
|
|
730
|
+
});
|
|
731
|
+
restoreSelection.current = {
|
|
732
|
+
key: JSON.stringify(next),
|
|
733
|
+
selectedId: next.targetReply,
|
|
734
|
+
entryType:
|
|
735
|
+
next.view === "feedback" && next.targetReply
|
|
736
|
+
? "feedbackReply"
|
|
737
|
+
: undefined,
|
|
738
|
+
index: 0,
|
|
739
|
+
};
|
|
554
740
|
if (history.current.length > 30) history.current.shift();
|
|
555
741
|
setReading(null);
|
|
742
|
+
setReviewLine(0);
|
|
743
|
+
setReviewAnchor(null);
|
|
744
|
+
setReviewHorizontal(0);
|
|
556
745
|
if (next.view === "feedback") setReviewFocus("notes");
|
|
557
746
|
setRoute(next);
|
|
558
747
|
setIndex(0);
|
|
@@ -564,6 +753,15 @@ function App({ initialConfig, flags, root }) {
|
|
|
564
753
|
function back() {
|
|
565
754
|
const previous = history.current.pop();
|
|
566
755
|
if (previous) {
|
|
756
|
+
restoreSelection.current = {
|
|
757
|
+
...previous,
|
|
758
|
+
key: JSON.stringify(previous.route),
|
|
759
|
+
};
|
|
760
|
+
setReading(null);
|
|
761
|
+
setReviewFocus(previous.reviewFocus);
|
|
762
|
+
setReviewLine(previous.reviewLine);
|
|
763
|
+
setReviewAnchor(previous.reviewAnchor);
|
|
764
|
+
setReviewHorizontal(previous.reviewHorizontal);
|
|
567
765
|
setRoute(previous.route);
|
|
568
766
|
setIndex(previous.index);
|
|
569
767
|
setScroll(previous.scroll);
|
|
@@ -675,10 +873,14 @@ function App({ initialConfig, flags, root }) {
|
|
|
675
873
|
};
|
|
676
874
|
}, []);
|
|
677
875
|
useEffect(() => {
|
|
678
|
-
if (
|
|
876
|
+
if (
|
|
877
|
+
effectiveAppearance.motion === "reduced" ||
|
|
878
|
+
(!loading && !pending && phase !== "pending")
|
|
879
|
+
)
|
|
880
|
+
return;
|
|
679
881
|
const timer = setInterval(() => setTick((v) => (v + 1) % 4), 180);
|
|
680
882
|
return () => clearInterval(timer);
|
|
681
|
-
}, [loading, pending, phase]);
|
|
883
|
+
}, [loading, pending, phase, effectiveAppearance.motion]);
|
|
682
884
|
useEffect(() => {
|
|
683
885
|
if (phase !== "checking" || !api) return;
|
|
684
886
|
const controller = new AbortController();
|
|
@@ -704,6 +906,23 @@ function App({ initialConfig, flags, root }) {
|
|
|
704
906
|
cached = cache.current.get(key);
|
|
705
907
|
setData(cached ? { ...cached, key } : null);
|
|
706
908
|
setLoading(!cached);
|
|
909
|
+
if (cached && restoreSelection.current?.key === key) {
|
|
910
|
+
const saved = restoreSelection.current;
|
|
911
|
+
const found = cached.items?.findIndex(
|
|
912
|
+
(item) =>
|
|
913
|
+
item.id === saved.selectedId &&
|
|
914
|
+
(saved.entryType === undefined || item.entryType === saved.entryType),
|
|
915
|
+
);
|
|
916
|
+
setIndex(
|
|
917
|
+
found >= 0
|
|
918
|
+
? found
|
|
919
|
+
: Math.min(
|
|
920
|
+
saved.index || 0,
|
|
921
|
+
Math.max(0, (cached.items?.length || 0) - 1),
|
|
922
|
+
),
|
|
923
|
+
);
|
|
924
|
+
restoreSelection.current = null;
|
|
925
|
+
}
|
|
707
926
|
setError("");
|
|
708
927
|
if (!cached)
|
|
709
928
|
(route.view === "drafts"
|
|
@@ -719,9 +938,27 @@ function App({ initialConfig, flags, root }) {
|
|
|
719
938
|
if (!active) return;
|
|
720
939
|
cache.current.set(key, result);
|
|
721
940
|
setData({ ...result, key });
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
941
|
+
const saved = restoreSelection.current;
|
|
942
|
+
if (saved?.key === key) {
|
|
943
|
+
const found = result.items?.findIndex(
|
|
944
|
+
(item) =>
|
|
945
|
+
item.id === saved.selectedId &&
|
|
946
|
+
(saved.entryType === undefined ||
|
|
947
|
+
item.entryType === saved.entryType),
|
|
948
|
+
);
|
|
949
|
+
setIndex(
|
|
950
|
+
found >= 0
|
|
951
|
+
? found
|
|
952
|
+
: Math.min(
|
|
953
|
+
saved.index || 0,
|
|
954
|
+
Math.max(0, (result.items?.length || 0) - 1),
|
|
955
|
+
),
|
|
956
|
+
);
|
|
957
|
+
restoreSelection.current = null;
|
|
958
|
+
} else
|
|
959
|
+
setIndex((i) =>
|
|
960
|
+
Math.min(i, Math.max(0, (result.items?.length || 0) - 1)),
|
|
961
|
+
);
|
|
725
962
|
})
|
|
726
963
|
.catch((e) => {
|
|
727
964
|
if (active) fail(e);
|
|
@@ -798,6 +1035,7 @@ function App({ initialConfig, flags, root }) {
|
|
|
798
1035
|
async function chooseNav(i) {
|
|
799
1036
|
const name = navigation[i]?.[0];
|
|
800
1037
|
setNavIndex(i);
|
|
1038
|
+
if (name === "appearance") return openAppearance();
|
|
801
1039
|
if (name === "ask" || name === "review")
|
|
802
1040
|
return composePublication(name === "review" ? "review" : "question");
|
|
803
1041
|
if (name === "open")
|
|
@@ -813,6 +1051,16 @@ function App({ initialConfig, flags, root }) {
|
|
|
813
1051
|
setCommandCursor(0);
|
|
814
1052
|
}
|
|
815
1053
|
}
|
|
1054
|
+
function openAppearance() {
|
|
1055
|
+
openForm({
|
|
1056
|
+
kind: "appearance",
|
|
1057
|
+
title: "Appearance",
|
|
1058
|
+
values: { ...appearance },
|
|
1059
|
+
});
|
|
1060
|
+
setNotice(
|
|
1061
|
+
"Live preview · ← → change · Ctrl+S save · Esc cancel · 0 defaults",
|
|
1062
|
+
);
|
|
1063
|
+
}
|
|
816
1064
|
function openForm(next) {
|
|
817
1065
|
setForm({
|
|
818
1066
|
...next,
|
|
@@ -890,6 +1138,46 @@ function App({ initialConfig, flags, root }) {
|
|
|
890
1138
|
values: { body: "" },
|
|
891
1139
|
});
|
|
892
1140
|
}
|
|
1141
|
+
async function externalEdit() {
|
|
1142
|
+
const descriptor = fieldsFor(form)[field];
|
|
1143
|
+
if (!descriptor?.multiline) {
|
|
1144
|
+
setNotice("Ctrl+E opens the editor for a multiline text field.");
|
|
1145
|
+
return;
|
|
1146
|
+
}
|
|
1147
|
+
const original = form;
|
|
1148
|
+
await task(async () => {
|
|
1149
|
+
clearTimeout(draftTimer.current);
|
|
1150
|
+
await queueDraft(() => drafts.save(original));
|
|
1151
|
+
let value;
|
|
1152
|
+
await suspendTerminal(async () => {
|
|
1153
|
+
value = await editExternal(original.values[descriptor.key] || "", {
|
|
1154
|
+
max: descriptor.max,
|
|
1155
|
+
});
|
|
1156
|
+
});
|
|
1157
|
+
const updated = {
|
|
1158
|
+
...original,
|
|
1159
|
+
values: { ...original.values, [descriptor.key]: value },
|
|
1160
|
+
};
|
|
1161
|
+
setForm(updated);
|
|
1162
|
+
setCursor(graphemes(value).length);
|
|
1163
|
+
await queueDraft(() => drafts.save(updated));
|
|
1164
|
+
setDraftSaved(true);
|
|
1165
|
+
setNotice("Text returned from editor. Preview before publishing.");
|
|
1166
|
+
});
|
|
1167
|
+
}
|
|
1168
|
+
async function reconnect() {
|
|
1169
|
+
await task(async () => {
|
|
1170
|
+
clearTimeout(draftTimer.current);
|
|
1171
|
+
draftGeneration.current++;
|
|
1172
|
+
if (form && drafts) await queueDraft(() => drafts.save(form));
|
|
1173
|
+
resetAccount();
|
|
1174
|
+
setConfig(null);
|
|
1175
|
+
setPhase("auth");
|
|
1176
|
+
setNotice(
|
|
1177
|
+
"Draft saved locally. Enter reconnects; restore it from Local drafts after signing in.",
|
|
1178
|
+
);
|
|
1179
|
+
});
|
|
1180
|
+
}
|
|
893
1181
|
async function lineFeedback() {
|
|
894
1182
|
const p = current?.post;
|
|
895
1183
|
if (!p?.code) {
|
|
@@ -937,6 +1225,18 @@ function App({ initialConfig, flags, root }) {
|
|
|
937
1225
|
}
|
|
938
1226
|
async function prepare() {
|
|
939
1227
|
if (!form) return;
|
|
1228
|
+
if (form.kind === "appearance") {
|
|
1229
|
+
await task(async () => {
|
|
1230
|
+
const saved = await saveAppearance(form.values);
|
|
1231
|
+
setAppearance(saved);
|
|
1232
|
+
setForm(null);
|
|
1233
|
+
setFocus("content");
|
|
1234
|
+
setNotice(
|
|
1235
|
+
"Appearance saved locally. Your account and other devices are unchanged.",
|
|
1236
|
+
);
|
|
1237
|
+
});
|
|
1238
|
+
return;
|
|
1239
|
+
}
|
|
940
1240
|
await task(async () => {
|
|
941
1241
|
if (
|
|
942
1242
|
form.unavailableAudience &&
|
|
@@ -1138,21 +1438,7 @@ function App({ initialConfig, flags, root }) {
|
|
|
1138
1438
|
setNotice("Event opened in your browser.");
|
|
1139
1439
|
return;
|
|
1140
1440
|
}
|
|
1141
|
-
|
|
1142
|
-
navigate({
|
|
1143
|
-
view: "feedback",
|
|
1144
|
-
postId: item.post_id,
|
|
1145
|
-
id: item.code_review_id,
|
|
1146
|
-
page: 1,
|
|
1147
|
-
targetReply: item.feedback_reply_id || null,
|
|
1148
|
-
});
|
|
1149
|
-
else
|
|
1150
|
-
navigate({
|
|
1151
|
-
view: "post",
|
|
1152
|
-
id: item.post_id,
|
|
1153
|
-
tab: item.reply_id ? "replies" : "overview",
|
|
1154
|
-
page: 1,
|
|
1155
|
-
});
|
|
1441
|
+
navigate(await notificationRoute(api, item));
|
|
1156
1442
|
});
|
|
1157
1443
|
return;
|
|
1158
1444
|
}
|
|
@@ -1399,6 +1685,10 @@ function App({ initialConfig, flags, root }) {
|
|
|
1399
1685
|
);
|
|
1400
1686
|
return;
|
|
1401
1687
|
}
|
|
1688
|
+
if (parsed.command === "appearance") {
|
|
1689
|
+
openAppearance();
|
|
1690
|
+
return;
|
|
1691
|
+
}
|
|
1402
1692
|
if (parsed.command === "open") {
|
|
1403
1693
|
navigate({ view: "post", id: parsed.arg, tab: "overview", page: 1 });
|
|
1404
1694
|
return;
|
|
@@ -1419,6 +1709,10 @@ function App({ initialConfig, flags, root }) {
|
|
|
1419
1709
|
}
|
|
1420
1710
|
}
|
|
1421
1711
|
function askQuit() {
|
|
1712
|
+
if (form?.kind === "appearance") {
|
|
1713
|
+
exit();
|
|
1714
|
+
return;
|
|
1715
|
+
}
|
|
1422
1716
|
if (form) {
|
|
1423
1717
|
askConfirm(
|
|
1424
1718
|
"Leave this draft?",
|
|
@@ -1486,6 +1780,10 @@ function App({ initialConfig, flags, root }) {
|
|
|
1486
1780
|
return;
|
|
1487
1781
|
}
|
|
1488
1782
|
if (busy.current) return;
|
|
1783
|
+
if (keyEvent.ctrl && input === "l") {
|
|
1784
|
+
reconnect();
|
|
1785
|
+
return;
|
|
1786
|
+
}
|
|
1489
1787
|
if (actionIndex !== null) {
|
|
1490
1788
|
if (keyEvent.escape || input === "x" || input === "X")
|
|
1491
1789
|
setActionIndex(null);
|
|
@@ -1496,11 +1794,12 @@ function App({ initialConfig, flags, root }) {
|
|
|
1496
1794
|
return;
|
|
1497
1795
|
}
|
|
1498
1796
|
if (reading) {
|
|
1499
|
-
const lines =
|
|
1797
|
+
const lines = markdownRows(
|
|
1500
1798
|
reading.deleted_at
|
|
1501
1799
|
? "[deleted — discussion preserved]"
|
|
1502
1800
|
: reading.body || "",
|
|
1503
1801
|
contentWidth,
|
|
1802
|
+
colors,
|
|
1504
1803
|
),
|
|
1505
1804
|
maximum = Math.max(0, lines.length - contentHeight);
|
|
1506
1805
|
if (keyEvent.escape || keyEvent.return || input === " ") {
|
|
@@ -1549,6 +1848,21 @@ function App({ initialConfig, flags, root }) {
|
|
|
1549
1848
|
return;
|
|
1550
1849
|
}
|
|
1551
1850
|
if (form) {
|
|
1851
|
+
if (form.kind === "appearance" && keyEvent.escape) {
|
|
1852
|
+
setForm(null);
|
|
1853
|
+
setFocus("content");
|
|
1854
|
+
setNotice("Appearance preview cancelled. Saved preferences kept.");
|
|
1855
|
+
return;
|
|
1856
|
+
}
|
|
1857
|
+
if (form.kind === "appearance" && input === "0") {
|
|
1858
|
+
setForm((old) => ({ ...old, values: { ...defaultAppearance } }));
|
|
1859
|
+
setNotice("Default preview restored. Ctrl+S saves; Esc cancels.");
|
|
1860
|
+
return;
|
|
1861
|
+
}
|
|
1862
|
+
if (keyEvent.ctrl && input === "e") {
|
|
1863
|
+
externalEdit();
|
|
1864
|
+
return;
|
|
1865
|
+
}
|
|
1552
1866
|
if (
|
|
1553
1867
|
keyEvent.ctrl &&
|
|
1554
1868
|
["o", "g"].includes(input) &&
|
|
@@ -1595,20 +1909,20 @@ function App({ initialConfig, flags, root }) {
|
|
|
1595
1909
|
keyEvent.downArrow
|
|
1596
1910
|
) {
|
|
1597
1911
|
const opts = active.options,
|
|
1598
|
-
|
|
1912
|
+
step = keyEvent.leftArrow || keyEvent.upArrow ? -1 : 1;
|
|
1913
|
+
setForm((old) => {
|
|
1914
|
+
const i = Math.max(
|
|
1599
1915
|
0,
|
|
1600
1916
|
opts.findIndex(
|
|
1601
1917
|
(item) =>
|
|
1602
|
-
String(item[0]) === String(
|
|
1918
|
+
String(item[0]) === String(old.values[active.key] || ""),
|
|
1603
1919
|
),
|
|
1604
|
-
)
|
|
1605
|
-
|
|
1606
|
-
next
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
values: { ...old.values, [active.key]: next[0] },
|
|
1611
|
-
}));
|
|
1920
|
+
);
|
|
1921
|
+
const next = opts[(i + step + opts.length) % opts.length];
|
|
1922
|
+
return next
|
|
1923
|
+
? { ...old, values: { ...old.values, [active.key]: next[0] } }
|
|
1924
|
+
: old;
|
|
1925
|
+
});
|
|
1612
1926
|
} else if (keyEvent.return) changeField(field + 1);
|
|
1613
1927
|
return;
|
|
1614
1928
|
}
|
|
@@ -1682,6 +1996,10 @@ function App({ initialConfig, flags, root }) {
|
|
|
1682
1996
|
else if (keyEvent.rightArrow) setFocus("content");
|
|
1683
1997
|
return;
|
|
1684
1998
|
}
|
|
1999
|
+
if (input === ",") {
|
|
2000
|
+
openAppearance();
|
|
2001
|
+
return;
|
|
2002
|
+
}
|
|
1685
2003
|
if (input === "x" || input === "X" || input === "?") {
|
|
1686
2004
|
setActionIndex(0);
|
|
1687
2005
|
return;
|
|
@@ -1768,15 +2086,14 @@ function App({ initialConfig, flags, root }) {
|
|
|
1768
2086
|
) {
|
|
1769
2087
|
const step =
|
|
1770
2088
|
keyEvent.pageUp || keyEvent.pageDown
|
|
1771
|
-
? Math.max(1, Math.floor(contentHeight /
|
|
2089
|
+
? Math.max(1, Math.floor(contentHeight / stride))
|
|
1772
2090
|
: 1,
|
|
1773
2091
|
delta = keyEvent.upArrow || keyEvent.pageUp ? -step : step;
|
|
1774
2092
|
if (prose) {
|
|
1775
2093
|
const maximum = Math.max(
|
|
1776
2094
|
0,
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
).length - contentHeight,
|
|
2095
|
+
documentRows(route, current || {}, contentWidth, colors).length -
|
|
2096
|
+
contentHeight,
|
|
1780
2097
|
);
|
|
1781
2098
|
const amount =
|
|
1782
2099
|
keyEvent.pageUp || keyEvent.pageDown
|
|
@@ -1900,7 +2217,8 @@ function App({ initialConfig, flags, root }) {
|
|
|
1900
2217
|
: loading
|
|
1901
2218
|
? "Loading…"
|
|
1902
2219
|
: "Shared account · shared conversations · same permissions");
|
|
1903
|
-
const spin =
|
|
2220
|
+
const spin =
|
|
2221
|
+
effectiveAppearance.motion === "reduced" ? "·" : ["◐", "◓", "◑", "◒"][tick];
|
|
1904
2222
|
let center;
|
|
1905
2223
|
if (width < 60 || rows < 18)
|
|
1906
2224
|
center = h(
|
|
@@ -1964,7 +2282,7 @@ function App({ initialConfig, flags, root }) {
|
|
|
1964
2282
|
center = h(
|
|
1965
2283
|
Box,
|
|
1966
2284
|
{
|
|
1967
|
-
borderStyle:
|
|
2285
|
+
borderStyle: colors.frame,
|
|
1968
2286
|
borderColor: colors.purple,
|
|
1969
2287
|
flexDirection: "column",
|
|
1970
2288
|
paddingX: 2,
|
|
@@ -1983,7 +2301,7 @@ function App({ initialConfig, flags, root }) {
|
|
|
1983
2301
|
center = h(
|
|
1984
2302
|
Box,
|
|
1985
2303
|
{
|
|
1986
|
-
borderStyle:
|
|
2304
|
+
borderStyle: colors.frame,
|
|
1987
2305
|
borderColor: colors.purple,
|
|
1988
2306
|
flexDirection: "column",
|
|
1989
2307
|
paddingX: 2,
|
|
@@ -2018,11 +2336,12 @@ function App({ initialConfig, flags, root }) {
|
|
|
2018
2336
|
} else {
|
|
2019
2337
|
let body;
|
|
2020
2338
|
if (reading) {
|
|
2021
|
-
const lines =
|
|
2339
|
+
const lines = markdownRows(
|
|
2022
2340
|
reading.deleted_at
|
|
2023
2341
|
? "[deleted — discussion preserved]"
|
|
2024
2342
|
: reading.body || "",
|
|
2025
2343
|
contentWidth,
|
|
2344
|
+
colors,
|
|
2026
2345
|
);
|
|
2027
2346
|
body = h(FrameRows, {
|
|
2028
2347
|
rows: lines,
|
|
@@ -2079,7 +2398,7 @@ function App({ initialConfig, flags, root }) {
|
|
|
2079
2398
|
),
|
|
2080
2399
|
);
|
|
2081
2400
|
} else if (form)
|
|
2082
|
-
body = h(FormView, {
|
|
2401
|
+
body = h(form.kind === "appearance" ? AppearanceView : FormView, {
|
|
2083
2402
|
form,
|
|
2084
2403
|
active: field,
|
|
2085
2404
|
cursor,
|
|
@@ -2145,12 +2464,35 @@ function App({ initialConfig, flags, root }) {
|
|
|
2145
2464
|
line: reviewLine,
|
|
2146
2465
|
anchor: reviewAnchor,
|
|
2147
2466
|
horizontal: reviewHorizontal,
|
|
2148
|
-
|
|
2467
|
+
layout: effectiveAppearance.reviewLayout,
|
|
2468
|
+
stride,
|
|
2469
|
+
rows: (w) =>
|
|
2470
|
+
listRows(
|
|
2471
|
+
route,
|
|
2472
|
+
current,
|
|
2473
|
+
index,
|
|
2474
|
+
w,
|
|
2475
|
+
colors,
|
|
2476
|
+
effectiveAppearance.density,
|
|
2477
|
+
),
|
|
2478
|
+
});
|
|
2479
|
+
else if (error && !current)
|
|
2480
|
+
body = h(FrameRows, {
|
|
2481
|
+
rows: [
|
|
2482
|
+
{ text: "Could not open this view", bold: true, color: colors.red },
|
|
2483
|
+
"",
|
|
2484
|
+
...wrap(error, contentWidth),
|
|
2485
|
+
"",
|
|
2486
|
+
...wrap(
|
|
2487
|
+
"Shift+R retries this view · Esc goes back · Ctrl+L reconnects",
|
|
2488
|
+
contentWidth,
|
|
2489
|
+
),
|
|
2490
|
+
],
|
|
2491
|
+
width: contentWidth,
|
|
2492
|
+
height: contentHeight,
|
|
2149
2493
|
});
|
|
2150
2494
|
else {
|
|
2151
|
-
const lines =
|
|
2152
|
-
wrap(line, contentWidth),
|
|
2153
|
-
);
|
|
2495
|
+
const lines = documentRows(route, current || {}, contentWidth, colors);
|
|
2154
2496
|
if (lines.length)
|
|
2155
2497
|
body = h(FrameRows, {
|
|
2156
2498
|
rows: lines,
|
|
@@ -2182,12 +2524,16 @@ function App({ initialConfig, flags, root }) {
|
|
|
2182
2524
|
),
|
|
2183
2525
|
);
|
|
2184
2526
|
else {
|
|
2185
|
-
const visible = Math.max(1, Math.floor(contentHeight /
|
|
2527
|
+
const visible = Math.max(1, Math.floor(contentHeight / stride)),
|
|
2186
2528
|
first = Math.max(0, index - visible + 1),
|
|
2187
|
-
rows = listRows(
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2529
|
+
rows = listRows(
|
|
2530
|
+
route,
|
|
2531
|
+
current,
|
|
2532
|
+
index,
|
|
2533
|
+
contentWidth,
|
|
2534
|
+
colors,
|
|
2535
|
+
effectiveAppearance.density,
|
|
2536
|
+
).slice(first * stride, first * stride + contentHeight);
|
|
2191
2537
|
body = h(
|
|
2192
2538
|
Box,
|
|
2193
2539
|
{
|
|
@@ -2227,7 +2573,7 @@ function App({ initialConfig, flags, root }) {
|
|
|
2227
2573
|
const pane = h(
|
|
2228
2574
|
Box,
|
|
2229
2575
|
{
|
|
2230
|
-
borderStyle:
|
|
2576
|
+
borderStyle: colors.frame,
|
|
2231
2577
|
borderColor: focus === "nav" && !compact ? colors.line : colors.purple,
|
|
2232
2578
|
backgroundColor: colors.panel,
|
|
2233
2579
|
flexDirection: "column",
|
|
@@ -2278,7 +2624,9 @@ function App({ initialConfig, flags, root }) {
|
|
|
2278
2624
|
)
|
|
2279
2625
|
: h(TextLine, {
|
|
2280
2626
|
text: form
|
|
2281
|
-
?
|
|
2627
|
+
? (form.kind === "appearance"
|
|
2628
|
+
? "Local preferences · Preview now · Ctrl+S save · Esc cancel"
|
|
2629
|
+
: "Tab next field · Ctrl+E editor · Ctrl+S preview") +
|
|
2282
2630
|
(draftSaved ? " · Draft saved locally" : "")
|
|
2283
2631
|
: preview
|
|
2284
2632
|
? "Scroll to review · ← → choose · Enter confirms"
|
|
@@ -2301,7 +2649,7 @@ function App({ initialConfig, flags, root }) {
|
|
|
2301
2649
|
? h(
|
|
2302
2650
|
Box,
|
|
2303
2651
|
{
|
|
2304
|
-
borderStyle:
|
|
2652
|
+
borderStyle: colors.frame,
|
|
2305
2653
|
borderColor: focus === "nav" ? colors.blue : colors.line,
|
|
2306
2654
|
backgroundColor: colors.panel,
|
|
2307
2655
|
flexDirection: "column",
|
|
@@ -2354,97 +2702,103 @@ function App({ initialConfig, flags, root }) {
|
|
|
2354
2702
|
);
|
|
2355
2703
|
}
|
|
2356
2704
|
const hint =
|
|
2357
|
-
|
|
2358
|
-
? "
|
|
2359
|
-
:
|
|
2360
|
-
? "
|
|
2361
|
-
:
|
|
2362
|
-
? "
|
|
2363
|
-
:
|
|
2364
|
-
? "
|
|
2365
|
-
:
|
|
2366
|
-
? "
|
|
2367
|
-
:
|
|
2368
|
-
? "
|
|
2369
|
-
:
|
|
2370
|
-
? "
|
|
2371
|
-
:
|
|
2372
|
-
? "
|
|
2373
|
-
: route.view === "
|
|
2374
|
-
? "
|
|
2375
|
-
:
|
|
2705
|
+
form?.kind === "appearance"
|
|
2706
|
+
? "← → choose · Tab next · Ctrl+S save · Esc cancel · 0 default preview"
|
|
2707
|
+
: actionIndex !== null
|
|
2708
|
+
? "Actions are scoped to the selected item · Esc closes"
|
|
2709
|
+
: reviewing && !form && !preview && !reading
|
|
2710
|
+
? "W code / discussion · Space select · C comment · X actions · F filter"
|
|
2711
|
+
: picker
|
|
2712
|
+
? "Project files stay local until publication is confirmed"
|
|
2713
|
+
: reading
|
|
2714
|
+
? "↑↓ scroll · R reply · E edit · D delete · Esc close"
|
|
2715
|
+
: preview
|
|
2716
|
+
? "← → choose · Enter confirm · Esc edit"
|
|
2717
|
+
: form
|
|
2718
|
+
? "Tab field · Ctrl+E editor · Ctrl+O files · Ctrl+G diff · Ctrl+S preview"
|
|
2719
|
+
: phase !== "ready"
|
|
2720
|
+
? "Enter connect / open browser · Ctrl+C exit"
|
|
2721
|
+
: route.view === "feedback"
|
|
2722
|
+
? "↑↓ select · R reply · E edit · D delete · S resolve · ! report"
|
|
2723
|
+
: route.view === "post"
|
|
2724
|
+
? "←→ tabs · ↑↓ select · R reply · E edit · D delete · Space read"
|
|
2725
|
+
: "↑↓ select · Enter open · Tab sections · [ ] pages · N new · / search";
|
|
2376
2726
|
return h(
|
|
2377
|
-
|
|
2378
|
-
{
|
|
2379
|
-
flexDirection: "column",
|
|
2380
|
-
width,
|
|
2381
|
-
height,
|
|
2382
|
-
overflow: "hidden",
|
|
2383
|
-
backgroundColor: colors.bg,
|
|
2384
|
-
},
|
|
2727
|
+
ThemeContext.Provider,
|
|
2728
|
+
{ value: colors },
|
|
2385
2729
|
h(
|
|
2386
2730
|
Box,
|
|
2387
2731
|
{
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
paddingX: 1,
|
|
2392
|
-
justifyContent: "space-between",
|
|
2732
|
+
flexDirection: "column",
|
|
2733
|
+
width,
|
|
2734
|
+
height,
|
|
2393
2735
|
overflow: "hidden",
|
|
2736
|
+
backgroundColor: colors.bg,
|
|
2394
2737
|
},
|
|
2395
2738
|
h(
|
|
2396
|
-
|
|
2397
|
-
{
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2739
|
+
Box,
|
|
2740
|
+
{
|
|
2741
|
+
height: 3,
|
|
2742
|
+
borderStyle: colors.frame,
|
|
2743
|
+
borderColor: colors.line,
|
|
2744
|
+
paddingX: 1,
|
|
2745
|
+
justifyContent: "space-between",
|
|
2746
|
+
overflow: "hidden",
|
|
2747
|
+
},
|
|
2748
|
+
h(
|
|
2749
|
+
Text,
|
|
2750
|
+
{ color: colors.purple, bold: true },
|
|
2751
|
+
clip("◈ DEVDEVIAN · " + basename(root), Math.max(10, width - 36)),
|
|
2752
|
+
),
|
|
2753
|
+
h(
|
|
2754
|
+
Text,
|
|
2755
|
+
{ color: colors.blue },
|
|
2756
|
+
clip(
|
|
2757
|
+
user
|
|
2758
|
+
? "@" +
|
|
2759
|
+
user.username +
|
|
2760
|
+
" · " +
|
|
2761
|
+
unread +
|
|
2762
|
+
" unread · " +
|
|
2763
|
+
(live ? "● Live" : "○ Paused")
|
|
2764
|
+
: "CLI · ALPHA",
|
|
2765
|
+
32,
|
|
2766
|
+
),
|
|
2413
2767
|
),
|
|
2414
2768
|
),
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2769
|
+
center,
|
|
2770
|
+
h(TextLine, {
|
|
2771
|
+
text: clip((pending || loading ? spin + " " : "") + status, width),
|
|
2772
|
+
width,
|
|
2773
|
+
color: error ? colors.red : notice ? colors.green : colors.muted,
|
|
2774
|
+
}),
|
|
2775
|
+
h(
|
|
2776
|
+
Box,
|
|
2777
|
+
{
|
|
2778
|
+
height: 3,
|
|
2779
|
+
borderStyle: colors.frame,
|
|
2780
|
+
borderColor: focus === "prompt" ? colors.blue : colors.line,
|
|
2781
|
+
paddingX: 1,
|
|
2782
|
+
overflow: "hidden",
|
|
2783
|
+
},
|
|
2784
|
+
h(Text, { color: colors.purple, bold: true }, "› "),
|
|
2785
|
+
focus === "prompt"
|
|
2786
|
+
? h(InputText, {
|
|
2787
|
+
value: command,
|
|
2788
|
+
cursor: commandCursor,
|
|
2789
|
+
width: Math.max(1, width - 6),
|
|
2790
|
+
})
|
|
2791
|
+
: h(
|
|
2792
|
+
Text,
|
|
2793
|
+
{ color: colors.muted },
|
|
2794
|
+
clip(
|
|
2795
|
+
"Search or /command · Press / to type · X actions · , appearance",
|
|
2796
|
+
width - 6,
|
|
2797
|
+
),
|
|
2444
2798
|
),
|
|
2445
|
-
|
|
2799
|
+
),
|
|
2800
|
+
h(TextLine, { text: hint, width, color: colors.muted }),
|
|
2446
2801
|
),
|
|
2447
|
-
h(TextLine, { text: hint, width, color: colors.muted }),
|
|
2448
2802
|
);
|
|
2449
2803
|
}
|
|
2450
2804
|
export async function startTui(flags = {}) {
|
|
@@ -2456,13 +2810,24 @@ export async function startTui(flags = {}) {
|
|
|
2456
2810
|
if (flags.host && config && originOf(flags.host) !== config.origin)
|
|
2457
2811
|
config = null;
|
|
2458
2812
|
const root = await workspaceRoot(flags.project || process.cwd());
|
|
2459
|
-
const
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2813
|
+
const loaded = await loadAppearance();
|
|
2814
|
+
const initialAppearance = {
|
|
2815
|
+
...loaded,
|
|
2816
|
+
settings: normalizeAppearance({
|
|
2817
|
+
...loaded.settings,
|
|
2818
|
+
...appearanceFlags(flags),
|
|
2819
|
+
}),
|
|
2820
|
+
};
|
|
2821
|
+
const app = render(
|
|
2822
|
+
h(App, { initialConfig: config, flags, root, initialAppearance }),
|
|
2823
|
+
{
|
|
2824
|
+
alternateScreen: true,
|
|
2825
|
+
exitOnCtrlC: false,
|
|
2826
|
+
patchConsole: true,
|
|
2827
|
+
maxFps: 20,
|
|
2828
|
+
incrementalRendering: false,
|
|
2829
|
+
},
|
|
2830
|
+
);
|
|
2466
2831
|
try {
|
|
2467
2832
|
await app.waitUntilExit();
|
|
2468
2833
|
} finally {
|