@cueloop/client 0.1.0-alpha.31 → 0.1.0-alpha.33
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/package.json +5 -5
- package/src/App.autoclose.test.tsx +4 -3
- package/src/App.diff.test.tsx +5 -2
- package/src/App.export.test.tsx +5 -7
- package/src/App.plan-review.test.tsx +14 -17
- package/src/App.readonly.test.tsx +9 -7
- package/src/App.rendering.test.tsx +6 -2
- package/src/App.review-panel.test.tsx +19 -14
- package/src/App.submit-confirm.test.tsx +6 -7
- package/src/App.test.tsx +8 -5
- package/src/App.toast.test.tsx +68 -0
- package/src/App.tsx +154 -50
- package/src/App.walk.test.tsx +7 -4
- package/src/components/AnnotationCard.stories.tsx +1 -1
- package/src/components/AnnotationCard.tsx +18 -13
- package/src/components/CompletionOverlay.tsx +1 -1
- package/src/components/ConfirmCard.tsx +1 -0
- package/src/components/ConfirmDialog.stories.tsx +1 -1
- package/src/components/ConfirmDialog.tsx +1 -1
- package/src/components/DiffSheet.stories.tsx +21 -3
- package/src/components/DiffSheet.tsx +45 -19
- package/src/components/InboxList.stories.tsx +1 -1
- package/src/components/InboxList.tsx +3 -3
- package/src/components/KeybindsDialog.stories.tsx +31 -0
- package/src/components/KeybindsDialog.tsx +42 -0
- package/src/components/MenuBar.stories.tsx +33 -0
- package/src/components/MenuBar.tsx +58 -0
- package/src/components/PlanSheet.stories.tsx +2 -2
- package/src/components/PlanSheet.tsx +26 -75
- package/src/components/PromptDialog.stories.tsx +2 -2
- package/src/components/PromptDialog.tsx +1 -1
- package/src/components/ReviewPanel.stories.tsx +5 -17
- package/src/components/ReviewPanel.tsx +10 -29
- package/src/components/ReviewRail.stories.tsx +39 -0
- package/src/components/ReviewRail.tsx +62 -57
- package/src/components/SettingsDialog.stories.tsx +1 -1
- package/src/components/SettingsDialog.tsx +4 -3
- package/src/components/SettingsRows.stories.tsx +1 -1
- package/src/components/SettingsRows.tsx +1 -1
- package/src/components/Toast.stories.tsx +12 -0
- package/src/components/Toast.tsx +29 -0
- package/src/components/WalkWizard.stories.tsx +1 -1
- package/src/components/WalkWizard.tsx +1 -1
- package/src/components/__snapshots__/stories.test.tsx.snap +598 -566
- package/src/components/primitives/Button.stories.tsx +2 -2
- package/src/components/primitives/Dialog.stories.tsx +1 -1
- package/src/components/primitives/Dialog.tsx +7 -5
- package/src/components/primitives/Tabs.tsx +19 -38
- package/src/components/stories-app.tsx +1 -1
- package/src/components/stories.test.tsx +2 -0
- package/src/key-bindings.test.ts +4 -4
- package/src/key-bindings.ts +41 -2
- package/src/serve.test.ts +2 -2
- package/src/session-controller.share.test.ts +13 -0
- package/src/session-controller.ts +19 -1
- package/src/test-support.test.ts +55 -0
- package/src/test-support.ts +113 -23
- package/src/theme.ts +16 -16
- package/src/version.ts +5 -0
- package/src/components/ComposeBar.stories.tsx +0 -18
- package/src/components/ComposeBar.tsx +0 -54
package/src/App.tsx
CHANGED
|
@@ -16,21 +16,27 @@ import { type VerdictKind } from "@cueloop/schema";
|
|
|
16
16
|
import { displayText, marksByDisplay, type Mark } from "./view-plan";
|
|
17
17
|
import { noteForFile, viewedCount } from "./walk";
|
|
18
18
|
import { DARK, dimmedTheme } from "./theme";
|
|
19
|
-
import { DEFAULT_KEYS, loadConfig, persistAuthorName } from "./config";
|
|
19
|
+
import { DEFAULT_KEYS, loadConfig, persistAuthorName, persistAutoClose, type AutoClose } from "./config";
|
|
20
20
|
import { returnPaneFor } from "@cueloop/schema";
|
|
21
21
|
import { createReviewController } from "./session-controller";
|
|
22
22
|
import type { SessionClient } from "@cueloop/daemon/client";
|
|
23
23
|
import { createIntentDispatch, reviewerAnnotations, type Mode } from "./intent-dispatch";
|
|
24
24
|
import { reduceKey, type KeyState } from "./keymap";
|
|
25
|
-
import { KeyBindings
|
|
25
|
+
import { KeyBindings } from "./key-bindings";
|
|
26
26
|
import { ThemeProvider } from "./components/theme-context";
|
|
27
|
-
import {
|
|
27
|
+
import { Button } from "./components/primitives/Button";
|
|
28
|
+
import { Toolbar } from "./components/primitives/Toolbar";
|
|
29
|
+
import { MenuBar } from "./components/MenuBar";
|
|
30
|
+
import { KeybindsDialog } from "./components/KeybindsDialog";
|
|
31
|
+
import { SettingsDialog, type SettingsCategory } from "./components/SettingsDialog";
|
|
32
|
+
import { CLIENT_VERSION } from "./version";
|
|
28
33
|
import { Breadcrumb, type BreadcrumbItem } from "./components/Breadcrumb";
|
|
29
34
|
import { PlanSheet, type PlanSheetHandle } from "./components/PlanSheet";
|
|
30
35
|
import { DiffSheet } from "./components/DiffSheet";
|
|
31
36
|
import { annotationBlocking, type ReviewRailHandle } from "./components/ReviewRail";
|
|
32
37
|
import { ReviewPanel } from "./components/ReviewPanel";
|
|
33
38
|
import {
|
|
39
|
+
REVIEW_COMPACT_WIDTH,
|
|
34
40
|
REVIEW_DEFAULT_WIDTH,
|
|
35
41
|
resolveReviewWidth,
|
|
36
42
|
toggleReviewPanelMode,
|
|
@@ -41,14 +47,11 @@ import { CompletionOverlay } from "./components/CompletionOverlay";
|
|
|
41
47
|
import { InboxList } from "./components/InboxList";
|
|
42
48
|
import { ConfirmDialog } from "./components/ConfirmDialog";
|
|
43
49
|
import { PromptDialog } from "./components/PromptDialog";
|
|
44
|
-
import { ComposeBar } from "./components/ComposeBar";
|
|
45
50
|
import { WalkWizard } from "./components/WalkWizard";
|
|
51
|
+
import { Toast } from "./components/Toast";
|
|
46
52
|
|
|
47
|
-
/**
|
|
48
|
-
|
|
49
|
-
* review layout (plan column, divider, rail) gets the rows that remain.
|
|
50
|
-
*/
|
|
51
|
-
const CHROME_ROWS = 2;
|
|
53
|
+
/** A toast clears itself after this idle; esc dismisses it sooner. */
|
|
54
|
+
const TOAST_DISMISS_MS = 4000;
|
|
52
55
|
|
|
53
56
|
export interface AppProps {
|
|
54
57
|
home?: string;
|
|
@@ -94,7 +97,7 @@ export function App({ home, sessionId, readOnly = false, onExit, clock, openClie
|
|
|
94
97
|
controller.connect();
|
|
95
98
|
return () => controller.close();
|
|
96
99
|
}, [controller]);
|
|
97
|
-
const { session, inbox, status, error, completion, editOrphanCount, walk } = useSyncExternalStore(
|
|
100
|
+
const { session, inbox, status, toast, error, completion, editOrphanCount, walk } = useSyncExternalStore(
|
|
98
101
|
controller.subscribe,
|
|
99
102
|
controller.getSnapshot,
|
|
100
103
|
controller.getSnapshot,
|
|
@@ -106,12 +109,21 @@ export function App({ home, sessionId, readOnly = false, onExit, clock, openClie
|
|
|
106
109
|
return controller.startSharePoll();
|
|
107
110
|
}, [isOwner, session?.id, session?.shareId, controller]);
|
|
108
111
|
const renderer = useRenderer();
|
|
109
|
-
const { width: terminalWidth
|
|
112
|
+
const { width: terminalWidth } = useTerminalDimensions();
|
|
110
113
|
|
|
111
114
|
// ── view state ──────────────────────────────
|
|
112
115
|
const [cursor, setCursor] = useState(0);
|
|
113
116
|
const [inboxCursor, setInboxCursor] = useState(0);
|
|
114
117
|
const [mode, setMode] = useState<Mode>({ type: "normal" });
|
|
118
|
+
// the bottom-left menu drop-up and the centered dialog it opens
|
|
119
|
+
const [menuOpen, setMenuOpen] = useState(false);
|
|
120
|
+
const [menuDialog, setMenuDialog] = useState<"keybinds" | "settings" | null>(null);
|
|
121
|
+
const [autoClose, setAutoClose] = useState<AutoClose>("off");
|
|
122
|
+
const [settingsNav, setSettingsNav] = useState<{ categoryId: string; rowIndex: number; zone: "nav" | "body" }>({
|
|
123
|
+
categoryId: "general",
|
|
124
|
+
rowIndex: 0,
|
|
125
|
+
zone: "body",
|
|
126
|
+
});
|
|
115
127
|
const [focusedAnnotationId, setFocusedAnnotationId] = useState<string | undefined>(undefined);
|
|
116
128
|
const [railTab, setRailTab] = useState<"review" | "agent">("review");
|
|
117
129
|
// review panel layout: mode + expanded width are client view state, loaded
|
|
@@ -143,6 +155,7 @@ export function App({ home, sessionId, readOnly = false, onExit, clock, openClie
|
|
|
143
155
|
setReviewWidth(config.ui.reviewWidth);
|
|
144
156
|
reviewWidthRef.current = config.ui.reviewWidth;
|
|
145
157
|
setAuthorNames(config.authors);
|
|
158
|
+
setAutoClose(config.ui.autoClose);
|
|
146
159
|
controller.applyConfig(config);
|
|
147
160
|
}, [session?.workspace.repoRoot, controller, keyBindings]);
|
|
148
161
|
useEffect(
|
|
@@ -152,6 +165,12 @@ export function App({ home, sessionId, readOnly = false, onExit, clock, openClie
|
|
|
152
165
|
[],
|
|
153
166
|
);
|
|
154
167
|
|
|
168
|
+
useEffect(() => {
|
|
169
|
+
if (!toast) return;
|
|
170
|
+
const timer = setTimeout(() => controller.dismissToast(), TOAST_DISMISS_MS);
|
|
171
|
+
return () => clearTimeout(timer);
|
|
172
|
+
}, [toast, controller]);
|
|
173
|
+
|
|
155
174
|
// First open of a share: ask the collaborator for a display name once, unless
|
|
156
175
|
// a past visit already recorded one. esc skips and their notes read anonymous.
|
|
157
176
|
const promptedSelfRef = useRef(false);
|
|
@@ -300,7 +319,57 @@ export function App({ home, sessionId, readOnly = false, onExit, clock, openClie
|
|
|
300
319
|
? "walk"
|
|
301
320
|
: "none";
|
|
302
321
|
|
|
322
|
+
// ── settings dialog: config-backed model, navigation, persistence ──
|
|
323
|
+
const settingsCategories: SettingsCategory[] = [
|
|
324
|
+
{
|
|
325
|
+
id: "general",
|
|
326
|
+
name: "General",
|
|
327
|
+
description: "submission behaviour",
|
|
328
|
+
rows: [{ key: "autoClose", label: "Auto-close on submit", kind: "cycle", options: ["off", "3s", "10s"] }],
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
id: "display",
|
|
332
|
+
name: "Display",
|
|
333
|
+
description: "the review panel",
|
|
334
|
+
rows: [{ key: "reviewPanel", label: "Review panel", kind: "cycle", options: ["expanded", "compact", "hidden"] }],
|
|
335
|
+
},
|
|
336
|
+
];
|
|
337
|
+
const settingsValues = { autoClose: autoClose === "off" ? "off" : `${autoClose}s`, reviewPanel: reviewMode };
|
|
338
|
+
const cycleSetting = (rowKey: string): void => {
|
|
339
|
+
if (rowKey === "autoClose") {
|
|
340
|
+
const next: AutoClose = autoClose === "off" ? 3 : autoClose === 3 ? 10 : "off";
|
|
341
|
+
setAutoClose(next);
|
|
342
|
+
persistAutoClose(next);
|
|
343
|
+
} else if (rowKey === "reviewPanel") {
|
|
344
|
+
const order: ReviewPanelMode[] = ["expanded", "compact", "hidden"];
|
|
345
|
+
const next = order[(order.indexOf(reviewMode) + 1) % order.length]!;
|
|
346
|
+
setReviewMode(next);
|
|
347
|
+
controller.saveReviewPanel({ mode: next });
|
|
348
|
+
}
|
|
349
|
+
};
|
|
350
|
+
const handleSettingsKey = (name: string): void => {
|
|
351
|
+
if (name === "escape") return void setMenuDialog(null);
|
|
352
|
+
const categoryIndex = settingsCategories.findIndex((category) => category.id === settingsNav.categoryId);
|
|
353
|
+
const category = settingsCategories[categoryIndex]!;
|
|
354
|
+
if (settingsNav.zone === "nav") {
|
|
355
|
+
if (name === "j" || name === "down") setSettingsNav({ categoryId: settingsCategories[Math.min(settingsCategories.length - 1, categoryIndex + 1)]!.id, rowIndex: 0, zone: "nav" });
|
|
356
|
+
else if (name === "k" || name === "up") setSettingsNav({ categoryId: settingsCategories[Math.max(0, categoryIndex - 1)]!.id, rowIndex: 0, zone: "nav" });
|
|
357
|
+
else if (name === "l" || name === "tab" || name === "return") setSettingsNav((state) => ({ ...state, zone: "body", rowIndex: 0 }));
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
if (name === "j" || name === "down") setSettingsNav((state) => ({ ...state, rowIndex: Math.min(category.rows.length - 1, state.rowIndex + 1) }));
|
|
361
|
+
else if (name === "k" || name === "up") setSettingsNav((state) => ({ ...state, rowIndex: Math.max(0, state.rowIndex - 1) }));
|
|
362
|
+
else if (name === "h" || name === "tab") setSettingsNav((state) => ({ ...state, zone: "nav" }));
|
|
363
|
+
else if (name === "return" || name === "space") cycleSetting(category.rows[settingsNav.rowIndex]!.key);
|
|
364
|
+
};
|
|
365
|
+
|
|
303
366
|
useKeyboard((key) => {
|
|
367
|
+
if (menuDialog === "settings") return void handleSettingsKey(key.name);
|
|
368
|
+
if (menuDialog) return void (key.name === "escape" && setMenuDialog(null));
|
|
369
|
+
if (menuOpen) return void (key.name === "escape" && setMenuOpen(false));
|
|
370
|
+
// the toast is non-modal: escape only dismisses it when nothing else owns
|
|
371
|
+
// escape, so an open overlay (compose, submit, prompt, walk) still cancels
|
|
372
|
+
if (toast && key.name === "escape" && overlay === "none" && mode.type !== "span") return controller.dismissToast();
|
|
304
373
|
const state: KeyState = {
|
|
305
374
|
keys: keysRef.current,
|
|
306
375
|
readOnly: observer,
|
|
@@ -398,6 +467,24 @@ export function App({ home, sessionId, readOnly = false, onExit, clock, openClie
|
|
|
398
467
|
}
|
|
399
468
|
: null;
|
|
400
469
|
|
|
470
|
+
const diffComposeState =
|
|
471
|
+
mode.type === "compose" && isDiff
|
|
472
|
+
? {
|
|
473
|
+
kind: mode.kind,
|
|
474
|
+
rowIndex: mode.displayIndex,
|
|
475
|
+
quote: rows[mode.displayIndex]?.text.replace(/\n$/, "") ?? "",
|
|
476
|
+
draft: {
|
|
477
|
+
text: mode.text,
|
|
478
|
+
onInput: (text: string) => {
|
|
479
|
+
liveInput.current = text;
|
|
480
|
+
setMode({ ...mode, text });
|
|
481
|
+
},
|
|
482
|
+
onSave: () => dispatch({ type: "saveCompose" }),
|
|
483
|
+
onCancel: () => dispatch({ type: "closeOverlay" }),
|
|
484
|
+
},
|
|
485
|
+
}
|
|
486
|
+
: null;
|
|
487
|
+
|
|
401
488
|
const activeSpan =
|
|
402
489
|
mode.type === "span"
|
|
403
490
|
? { displayIndex: mode.span.displayIndex, start: mode.span.start, end: mode.span.end }
|
|
@@ -484,36 +571,28 @@ export function App({ home, sessionId, readOnly = false, onExit, clock, openClie
|
|
|
484
571
|
}
|
|
485
572
|
: null;
|
|
486
573
|
|
|
574
|
+
// width the review panel occupies (divider + rail), so the header can reserve
|
|
575
|
+
// it and keep Edit/Share pinned to the plan sheet's right edge, not the rail's
|
|
576
|
+
const railFootprint =
|
|
577
|
+
reviewMode === "hidden"
|
|
578
|
+
? 0
|
|
579
|
+
: 1 + (reviewMode === "compact" ? REVIEW_COMPACT_WIDTH : resolveReviewWidth(reviewWidth, terminalWidth));
|
|
580
|
+
|
|
581
|
+
// status badges sit right after the product word so they survive a header
|
|
582
|
+
// that is too narrow for the whole trail (the rail can eat the width)
|
|
487
583
|
const headerItems: BreadcrumbItem[] = [
|
|
488
584
|
{ label: "cueloop", tone: "accent" },
|
|
489
|
-
{ label: `${activeSession.artifact.meta.title ?? activeSession.artifact.meta.planPath ?? activeSession.id} · rev ${activeSession.revisions.length}`, tone: "dim" },
|
|
490
585
|
...(resolved ? [{ label: `resolved: ${activeSession.verdict!.kind.replace("_", " ")}`, tone: "green" as const }] : []),
|
|
491
586
|
...(observer ? [{ label: "observer", tone: "dim" as const }] : []),
|
|
492
587
|
...(role === "collaborator" ? [{ label: "shared · your notes save as you go", tone: "dim" as const }] : []),
|
|
493
|
-
|
|
588
|
+
{ label: `${activeSession.artifact.meta.title ?? activeSession.artifact.meta.planPath ?? activeSession.id} · rev ${activeSession.revisions.length}`, tone: "dim" },
|
|
589
|
+
{ label: `submitted by ${activeSession.artifact.meta.agent ?? "unknown"}`, tone: "dim" },
|
|
494
590
|
];
|
|
495
591
|
|
|
496
|
-
keyBindings.setContext({ overlay, spanMode: mode.type === "span" });
|
|
497
|
-
const hintMode: HintMode = observer
|
|
498
|
-
? "read-only"
|
|
499
|
-
: mode.type === "submit"
|
|
500
|
-
? "submit"
|
|
501
|
-
: mode.type === "span"
|
|
502
|
-
? "span"
|
|
503
|
-
: mode.type === "compose" || mode.type === "railEdit"
|
|
504
|
-
? "compose"
|
|
505
|
-
: walking
|
|
506
|
-
? "walk"
|
|
507
|
-
: focusedAnnotationId !== undefined
|
|
508
|
-
? "card"
|
|
509
|
-
: role === "collaborator"
|
|
510
|
-
? "collaborator"
|
|
511
|
-
: "normal";
|
|
512
|
-
|
|
513
592
|
return (
|
|
514
593
|
<ThemeProvider theme={theme}>
|
|
515
594
|
<box
|
|
516
|
-
style={{ flexDirection: "column", width: "100%", height: "100%", backgroundColor: theme.
|
|
595
|
+
style={{ flexDirection: "column", width: "100%", height: "100%", backgroundColor: theme.background }}
|
|
517
596
|
onMouseDrag={(event: MouseEvent) => {
|
|
518
597
|
if (!dividerDragging || reviewMode !== "expanded") return;
|
|
519
598
|
const next = widthFromMouseColumn(event.x, terminalWidth);
|
|
@@ -526,7 +605,19 @@ export function App({ home, sessionId, readOnly = false, onExit, clock, openClie
|
|
|
526
605
|
controller.saveReviewPanel({ width: reviewWidthRef.current });
|
|
527
606
|
}}
|
|
528
607
|
>
|
|
529
|
-
<
|
|
608
|
+
<box style={{ flexDirection: "row", height: 2, paddingTop: 1, backgroundColor: theme.panel }}>
|
|
609
|
+
<box style={{ flexGrow: 1, flexDirection: "row", paddingRight: 1 }}>
|
|
610
|
+
<Breadcrumb items={headerItems} />
|
|
611
|
+
<box style={{ flexGrow: 1 }} />
|
|
612
|
+
{isOwner && !isDiff && !resolved ? (
|
|
613
|
+
<Toolbar>
|
|
614
|
+
<Button onPress={onEditRequest} theme={theme}>{" Edit "}</Button>
|
|
615
|
+
<Button onPress={onShareRequest} theme={theme}>{" Share "}</Button>
|
|
616
|
+
</Toolbar>
|
|
617
|
+
) : null}
|
|
618
|
+
</box>
|
|
619
|
+
<box style={{ width: railFootprint }} />
|
|
620
|
+
</box>
|
|
530
621
|
<box style={{ flexGrow: 1, flexDirection: "row" }}>
|
|
531
622
|
{isDiff ? (
|
|
532
623
|
// the sheet dims to reading-quiet colors while the wizard has focus
|
|
@@ -535,6 +626,7 @@ export function App({ home, sessionId, readOnly = false, onExit, clock, openClie
|
|
|
535
626
|
cursor={cursor}
|
|
536
627
|
annotations={activeSession.annotations}
|
|
537
628
|
focusedAnnotationId={focusedAnnotationId}
|
|
629
|
+
compose={diffComposeState}
|
|
538
630
|
theme={walking ? dimmedTheme(theme) : undefined}
|
|
539
631
|
/>
|
|
540
632
|
) : (
|
|
@@ -548,16 +640,11 @@ export function App({ home, sessionId, readOnly = false, onExit, clock, openClie
|
|
|
548
640
|
compose={composeState}
|
|
549
641
|
editOrphanCount={editOrphanCount}
|
|
550
642
|
onLineActivate={onLineActivate}
|
|
551
|
-
onEditRequest={onEditRequest}
|
|
552
|
-
onShareRequest={onShareRequest}
|
|
553
|
-
canEdit={isOwner}
|
|
554
643
|
/>
|
|
555
644
|
)}
|
|
556
645
|
<ReviewPanel
|
|
557
646
|
mode={reviewMode}
|
|
558
647
|
width={resolveReviewWidth(reviewWidth, terminalWidth)}
|
|
559
|
-
height={terminalHeight - CHROME_ROWS}
|
|
560
|
-
dragging={dividerDragging}
|
|
561
648
|
onDividerGrab={onDividerGrab}
|
|
562
649
|
onToggle={onToggleReviewPanel}
|
|
563
650
|
railRef={railRef}
|
|
@@ -577,19 +664,21 @@ export function App({ home, sessionId, readOnly = false, onExit, clock, openClie
|
|
|
577
664
|
}}
|
|
578
665
|
/>
|
|
579
666
|
</box>
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
667
|
+
<MenuBar
|
|
668
|
+
open={menuOpen}
|
|
669
|
+
version={CLIENT_VERSION}
|
|
670
|
+
status={status}
|
|
671
|
+
onToggle={() => setMenuOpen((isOpen) => !isOpen)}
|
|
672
|
+
onSettings={() => {
|
|
673
|
+
setMenuOpen(false);
|
|
674
|
+
setMenuDialog("settings");
|
|
675
|
+
}}
|
|
676
|
+
onKeybinds={() => {
|
|
677
|
+
setMenuOpen(false);
|
|
678
|
+
setMenuDialog("keybinds");
|
|
679
|
+
}}
|
|
680
|
+
theme={theme}
|
|
681
|
+
/>
|
|
593
682
|
{walking && walk !== null ? (
|
|
594
683
|
<WalkWizard
|
|
595
684
|
files={walkFileList}
|
|
@@ -630,6 +719,21 @@ export function App({ home, sessionId, readOnly = false, onExit, clock, openClie
|
|
|
630
719
|
theme={theme}
|
|
631
720
|
/>
|
|
632
721
|
) : null}
|
|
722
|
+
{toast ? <Toast title={toast.title} body={toast.body} theme={theme} /> : null}
|
|
723
|
+
{menuDialog === "keybinds" ? <KeybindsDialog sections={keyBindings.cheatsheet()} theme={theme} /> : null}
|
|
724
|
+
{menuDialog === "settings" ? (
|
|
725
|
+
<SettingsDialog
|
|
726
|
+
isOpen
|
|
727
|
+
categories={settingsCategories}
|
|
728
|
+
values={settingsValues}
|
|
729
|
+
activeCategoryId={settingsNav.categoryId}
|
|
730
|
+
activeRowIndex={settingsNav.rowIndex}
|
|
731
|
+
activeZone={settingsNav.zone}
|
|
732
|
+
onCategorySelect={(id) => setSettingsNav({ categoryId: id, rowIndex: 0, zone: "body" })}
|
|
733
|
+
onRowActivate={(row) => cycleSetting(row.key)}
|
|
734
|
+
theme={theme}
|
|
735
|
+
/>
|
|
736
|
+
) : null}
|
|
633
737
|
</box>
|
|
634
738
|
</ThemeProvider>
|
|
635
739
|
);
|
package/src/App.walk.test.tsx
CHANGED
|
@@ -10,7 +10,7 @@ import { DaemonServer } from "@cueloop/daemon";
|
|
|
10
10
|
import type { ReviewSession } from "@cueloop/schema";
|
|
11
11
|
import { App } from "./App";
|
|
12
12
|
import { DARK } from "./theme";
|
|
13
|
-
import { press, waitForState, waitForText, waitForTextGone } from "./test-support";
|
|
13
|
+
import { isolateUserConfig, press, waitForState, waitForText, waitForTextGone } from "./test-support";
|
|
14
14
|
|
|
15
15
|
const PATCH = `diff --git a/src/a.ts b/src/a.ts
|
|
16
16
|
index 111..222 100644
|
|
@@ -38,11 +38,13 @@ index 555..666 100644
|
|
|
38
38
|
`;
|
|
39
39
|
|
|
40
40
|
let home: string;
|
|
41
|
+
let restoreUserConfig: () => void;
|
|
41
42
|
let server: DaemonServer;
|
|
42
43
|
let session: ReviewSession;
|
|
43
44
|
|
|
44
45
|
beforeEach(() => {
|
|
45
46
|
home = mkdtempSync(join(tmpdir(), "cueloop-walk-"));
|
|
47
|
+
restoreUserConfig = isolateUserConfig(home);
|
|
46
48
|
server = new DaemonServer({ home, idleExitMs: 0 });
|
|
47
49
|
server.start();
|
|
48
50
|
session = server.core.sessionCreate({
|
|
@@ -51,6 +53,7 @@ beforeEach(() => {
|
|
|
51
53
|
});
|
|
52
54
|
});
|
|
53
55
|
afterEach(() => {
|
|
56
|
+
restoreUserConfig();
|
|
54
57
|
server.stop();
|
|
55
58
|
rmSync(home, { recursive: true, force: true });
|
|
56
59
|
});
|
|
@@ -187,7 +190,7 @@ describe("the guided walk", () => {
|
|
|
187
190
|
// two full App boots with daemon round-trips need more than the default
|
|
188
191
|
// budget, and the whole-suite publish lane has timed even 15s out; give the
|
|
189
192
|
// double-boot chain generous headroom so runner load cannot flake it.
|
|
190
|
-
},
|
|
193
|
+
}, 60_000);
|
|
191
194
|
|
|
192
195
|
test("the agent-note block renders only for files carrying a note", async () => {
|
|
193
196
|
// Arrange
|
|
@@ -236,7 +239,7 @@ describe("the guided walk", () => {
|
|
|
236
239
|
// Assert
|
|
237
240
|
// before the walk the sheet's added line wears the insertion color
|
|
238
241
|
await waitForText(setup, "+new line");
|
|
239
|
-
expect(foregroundsOf(setup, "+new line")).toContain(DARK.
|
|
242
|
+
expect(foregroundsOf(setup, "+new line")).toContain(DARK.insertedForeground);
|
|
240
243
|
|
|
241
244
|
// Act
|
|
242
245
|
await press(setup, "w");
|
|
@@ -246,7 +249,7 @@ describe("the guided walk", () => {
|
|
|
246
249
|
// dimmed: the sheet line drops to the dim token...
|
|
247
250
|
expect(foregroundsOf(setup, "+new line")).toEqual([DARK.textDim]);
|
|
248
251
|
// ...while the wizard preview keeps the insertion color
|
|
249
|
-
expect(foregroundsOf(setup, "+export const b = 3;")).toContain(DARK.
|
|
252
|
+
expect(foregroundsOf(setup, "+export const b = 3;")).toContain(DARK.insertedForeground);
|
|
250
253
|
});
|
|
251
254
|
|
|
252
255
|
test("walking a resolved review answers read-only", async () => {
|
|
@@ -11,7 +11,7 @@ export const CommentDraft: Story = {
|
|
|
11
11
|
render: () => (
|
|
12
12
|
<AnnotationCard kind="comment" quote="persists sessions" draft={{ text: "Which daemon?", ...draftCallbacks }} />
|
|
13
13
|
),
|
|
14
|
-
expectedColors: [DARK.accent
|
|
14
|
+
expectedColors: [DARK.accent],
|
|
15
15
|
};
|
|
16
16
|
|
|
17
17
|
export const SuggestionDraft: Story = {
|
|
@@ -32,8 +32,10 @@ export interface AnnotationSaved {
|
|
|
32
32
|
isSelected: boolean;
|
|
33
33
|
isOrphan: boolean;
|
|
34
34
|
isBlocking: boolean;
|
|
35
|
-
/**
|
|
35
|
+
/** A collaborator's resolved display name; own notes carry none. Set = name-in-border, blue. */
|
|
36
36
|
authorLabel?: string;
|
|
37
|
+
/** The viewer's own note when collaborators are also present (e.g. "me"). Set = tag-in-border, accent. */
|
|
38
|
+
selfLabel?: string;
|
|
37
39
|
/** Non-null while the card body is being rewritten in place. */
|
|
38
40
|
editing: AnnotationDraft | null;
|
|
39
41
|
onPress: () => void;
|
|
@@ -122,15 +124,15 @@ function DraftEditor({
|
|
|
122
124
|
}}
|
|
123
125
|
style={{
|
|
124
126
|
height: rows,
|
|
125
|
-
backgroundColor:
|
|
126
|
-
focusedBackgroundColor:
|
|
127
|
+
backgroundColor: "transparent",
|
|
128
|
+
focusedBackgroundColor: "transparent",
|
|
127
129
|
textColor: tokens.text,
|
|
128
130
|
focusedTextColor: tokens.text,
|
|
129
131
|
}}
|
|
130
132
|
/>
|
|
131
133
|
<Toolbar>
|
|
132
134
|
<Button variant="solid" marginRight={2} onPress={draft.onSave} theme={theme}>
|
|
133
|
-
{" Save
|
|
135
|
+
{" Save "}
|
|
134
136
|
</Button>
|
|
135
137
|
<Button onPress={draft.onCancel} theme={theme}>
|
|
136
138
|
{" Cancel esc "}
|
|
@@ -160,6 +162,7 @@ export function AnnotationCard({ id, kind, quote, draft, saved, theme }: Annotat
|
|
|
160
162
|
title={` ${verb} "${truncateToSingleLine(quote, 40)}" `}
|
|
161
163
|
contentRows={editorRowCount + 1}
|
|
162
164
|
borderColor={kindColor}
|
|
165
|
+
backgroundColor="transparent"
|
|
163
166
|
marginLeft={2}
|
|
164
167
|
marginRight={2}
|
|
165
168
|
theme={theme}
|
|
@@ -169,24 +172,26 @@ export function AnnotationCard({ id, kind, quote, draft, saved, theme }: Annotat
|
|
|
169
172
|
);
|
|
170
173
|
}
|
|
171
174
|
const card = saved!;
|
|
172
|
-
|
|
173
|
-
const
|
|
174
|
-
const
|
|
175
|
+
const collaboratorName = card.authorLabel !== undefined && card.authorLabel !== "" ? card.authorLabel : undefined;
|
|
176
|
+
const ownTag = card.selfLabel !== undefined && card.selfLabel !== "" ? card.selfLabel : undefined;
|
|
177
|
+
const borderLabel = collaboratorName ?? ownTag;
|
|
178
|
+
const borderColor = ownTag ? tokens.accent : tokens.blue;
|
|
179
|
+
const indent = borderLabel ? "" : " ";
|
|
175
180
|
const marker = card.isSelected ? "▸ " : indent;
|
|
176
181
|
// Fixed content rows (a bordered box must declare a height or it collapses): header + quote + body, or the editor.
|
|
177
182
|
const contentRows = card.editing ? editorRowCount + 3 : 3;
|
|
178
183
|
// The border + its side padding eat this much content width, so text truncates shorter.
|
|
179
|
-
const borderInset =
|
|
184
|
+
const borderInset = borderLabel ? 4 : 0;
|
|
180
185
|
return (
|
|
181
186
|
<box
|
|
182
187
|
id={id}
|
|
183
|
-
title={
|
|
188
|
+
title={borderLabel ? ` ${borderLabel} ` : undefined}
|
|
184
189
|
style={{
|
|
185
190
|
flexDirection: "column",
|
|
186
|
-
marginBottom:
|
|
187
|
-
backgroundColor: card.isSelected ? tokens.elevated : undefined,
|
|
188
|
-
...(
|
|
189
|
-
? { height: cardHeight(contentRows), border: true, borderStyle: FRAME_BORDER_STYLE, borderColor
|
|
191
|
+
marginBottom: 0,
|
|
192
|
+
backgroundColor: card.isSelected && !card.editing ? tokens.elevated : undefined,
|
|
193
|
+
...(borderLabel
|
|
194
|
+
? { height: cardHeight(contentRows), border: true, borderStyle: FRAME_BORDER_STYLE, borderColor, paddingLeft: 1, paddingRight: 1 }
|
|
190
195
|
: {}),
|
|
191
196
|
}}
|
|
192
197
|
onMouseUp={card.onPress}
|
|
@@ -27,7 +27,7 @@ export function CompletionOverlay({ verdict, completion, status, returnsTo, them
|
|
|
27
27
|
style={{
|
|
28
28
|
width: "100%",
|
|
29
29
|
height: "100%",
|
|
30
|
-
backgroundColor: tokens.
|
|
30
|
+
backgroundColor: tokens.background,
|
|
31
31
|
flexDirection: "column",
|
|
32
32
|
justifyContent: "center",
|
|
33
33
|
alignItems: "center",
|
|
@@ -44,7 +44,7 @@ export function ConfirmDialog({
|
|
|
44
44
|
<box style={{ flexGrow: 1 }} />
|
|
45
45
|
<Toolbar>
|
|
46
46
|
<Button variant="solid" onPress={onConfirm} theme={theme}>
|
|
47
|
-
{` ${confirmLabel}
|
|
47
|
+
{` ${confirmLabel} `}
|
|
48
48
|
</Button>
|
|
49
49
|
<Button onPress={onCancel} theme={theme}>
|
|
50
50
|
{` ${cancelLabel} esc `}
|
|
@@ -8,13 +8,31 @@ export const meta: StoryMeta = { title: "DiffSheet" };
|
|
|
8
8
|
|
|
9
9
|
export const SignedRows: Story = {
|
|
10
10
|
render: () => <DiffSheet rows={fixtureDiffRows()} cursor={0} annotations={[]} />,
|
|
11
|
-
expectedColors: [DARK.
|
|
11
|
+
expectedColors: [DARK.insertedForeground, DARK.deletedForeground],
|
|
12
12
|
size: { width: 90, height: 16 },
|
|
13
13
|
};
|
|
14
14
|
|
|
15
15
|
export const CursorOnAddedLine: Story = {
|
|
16
16
|
render: () => <DiffSheet rows={fixtureDiffRows()} cursor={4} annotations={[]} />,
|
|
17
|
-
expectedColors: [DARK.
|
|
17
|
+
expectedColors: [DARK.cursorBackground],
|
|
18
|
+
size: { width: 90, height: 16 },
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const ComposingOnLine: Story = {
|
|
22
|
+
render: () => (
|
|
23
|
+
<DiffSheet
|
|
24
|
+
rows={fixtureDiffRows()}
|
|
25
|
+
cursor={4}
|
|
26
|
+
annotations={[]}
|
|
27
|
+
compose={{
|
|
28
|
+
kind: "comment",
|
|
29
|
+
rowIndex: 4,
|
|
30
|
+
quote: " private items = new Map();",
|
|
31
|
+
draft: { text: "Map needs an eviction story.", onInput: () => {}, onSave: () => {}, onCancel: () => {} },
|
|
32
|
+
}}
|
|
33
|
+
/>
|
|
34
|
+
),
|
|
35
|
+
expectedColors: [DARK.accent],
|
|
18
36
|
size: { width: 90, height: 16 },
|
|
19
37
|
};
|
|
20
38
|
|
|
@@ -39,6 +57,6 @@ export const AnnotatedLine: Story = {
|
|
|
39
57
|
]}
|
|
40
58
|
/>
|
|
41
59
|
),
|
|
42
|
-
expectedColors: [DARK.
|
|
60
|
+
expectedColors: [DARK.markCommentBackground],
|
|
43
61
|
size: { width: 90, height: 16 },
|
|
44
62
|
};
|