@cueloop/client 0.1.0-alpha.65 → 0.1.0-alpha.66
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.diff.test.tsx +8 -73
- package/src/App.plan-review.test.tsx +4 -185
- package/src/App.prototype.test.tsx +1 -8
- package/src/App.readonly.test.tsx +6 -28
- package/src/App.submit-confirm.test.tsx +19 -72
- package/src/App.test.tsx +70 -24
- package/src/App.tsx +177 -254
- package/src/App.walk.test.tsx +1 -1
- package/src/app-screens.tsx +40 -51
- package/src/app-view-model.ts +2 -4
- package/src/components/AnnotationCard.stories.tsx +1 -1
- package/src/components/AppShell.stories.tsx +84 -0
- package/src/components/AppShell.tsx +127 -0
- package/src/components/Breadcrumb.stories.tsx +1 -1
- package/src/components/ChangesColumn.stories.tsx +39 -0
- package/src/components/ChangesColumn.tsx +147 -0
- package/src/components/CodeBlock.stories.tsx +1 -1
- package/src/components/CompletionOverlay.stories.tsx +1 -1
- package/src/components/ConfirmCard.stories.tsx +1 -1
- package/src/components/ConfirmCard.tsx +3 -4
- package/src/components/ConfirmDialog.stories.tsx +1 -1
- package/src/components/DiffSheet.stories.tsx +1 -1
- package/src/components/InboxList.stories.tsx +23 -8
- package/src/components/InboxList.tsx +45 -19
- package/src/components/KeybindsDialog.stories.tsx +1 -1
- package/src/components/MarkerPopover.stories.tsx +1 -1
- package/src/components/MenuBar.stories.tsx +3 -12
- package/src/components/MenuBar.tsx +25 -56
- package/src/components/PromptDialog.stories.tsx +1 -1
- package/src/components/PrototypeSheet.stories.tsx +1 -1
- package/src/components/ReviewPanel.stories.tsx +1 -1
- package/src/components/ReviewRail.stories.tsx +1 -1
- package/src/components/SettingsDialog.stories.tsx +7 -1
- package/src/components/SettingsDialog.tsx +112 -66
- package/src/components/SettingsRows.stories.tsx +1 -1
- package/src/components/ShellHeader.stories.tsx +50 -0
- package/src/components/ShellHeader.tsx +137 -0
- package/src/components/ThreadFooter.stories.tsx +30 -0
- package/src/components/ThreadFooter.tsx +49 -0
- package/src/components/ThreadView.slash.test.tsx +113 -0
- package/src/components/ThreadView.stories.tsx +1 -1
- package/src/components/ThreadView.tsx +99 -11
- package/src/components/ThreadsSidebar.stories.tsx +36 -0
- package/src/components/ThreadsSidebar.tsx +57 -0
- package/src/components/Toast.stories.tsx +1 -1
- package/src/components/TreePane.stories.tsx +1 -1
- package/src/components/WalkWizard.stories.tsx +1 -1
- package/src/components/__snapshots__/stories.test.tsx.snap +348 -138
- package/src/components/agent-launcher.stories.tsx +1 -1
- package/src/components/file-tree.test.ts +45 -0
- package/src/components/file-tree.ts +57 -0
- package/src/components/pages/Workspace.stories.tsx +181 -0
- package/src/components/primitives/Button.stories.tsx +1 -1
- package/src/components/primitives/Card.stories.tsx +1 -1
- package/src/components/primitives/Dialog.stories.tsx +1 -1
- package/src/components/primitives/IconButton.stories.tsx +36 -0
- package/src/components/primitives/IconButton.tsx +43 -0
- package/src/components/primitives/StatusBar.stories.tsx +1 -1
- package/src/components/primitives/Tabs.stories.tsx +1 -1
- package/src/components/primitives/Toolbar.stories.tsx +1 -1
- package/src/components/primitives/Tree.stories.tsx +124 -0
- package/src/components/primitives/Tree.tsx +87 -0
- package/src/components/primitives/icons.ts +38 -0
- package/src/components/primitives/tree-model.test.ts +99 -0
- package/src/components/primitives/tree-model.ts +137 -0
- package/src/components/quick-actions-editor.stories.tsx +1 -1
- package/src/components/session-tree.test.ts +114 -0
- package/src/components/session-tree.ts +94 -0
- package/src/components/stories-app.tsx +142 -82
- package/src/components/story-tree.test.ts +100 -0
- package/src/components/story.ts +55 -0
- package/src/components/theme-context.stories.tsx +1 -1
- package/src/session-controller.ts +9 -4
- package/src/theme.ts +4 -4
- package/src/use-settings-dialog.tsx +9 -0
- package/src/App.review-panel.test.tsx +0 -140
- package/src/App.tree.test.tsx +0 -224
package/src/App.tsx
CHANGED
|
@@ -18,7 +18,7 @@ import React, {
|
|
|
18
18
|
useSyncExternalStore,
|
|
19
19
|
} from "react";
|
|
20
20
|
import { useKeyboard, useRenderer, useTerminalDimensions } from "@opentui/react";
|
|
21
|
-
import type { Clock
|
|
21
|
+
import type { Clock } from "@opentui/core";
|
|
22
22
|
import { marksByDisplay, type Mark } from "./view-plan";
|
|
23
23
|
import { dimmedTheme } from "./theme";
|
|
24
24
|
import {
|
|
@@ -39,13 +39,19 @@ import {
|
|
|
39
39
|
import type { Theme } from "./theme";
|
|
40
40
|
import { createReviewController, type ShareTransport } from "./session-controller";
|
|
41
41
|
import type { SessionClient } from "@cueloop/daemon/client";
|
|
42
|
-
import { launchHarnessInSplit } from "@cueloop/daemon/herdr-split";
|
|
43
42
|
import { createIntentDispatch, type Mode } from "./intent-dispatch";
|
|
44
43
|
import { reduceKey, type KeyState } from "./keymap";
|
|
45
44
|
import { KeyBindings, type CheatsheetSection } from "./key-bindings";
|
|
46
45
|
import { ThemeProvider } from "./components/theme-context";
|
|
47
46
|
import { Button } from "./components/primitives/Button";
|
|
48
47
|
import { Toolbar } from "./components/primitives/Toolbar";
|
|
48
|
+
import { NERD } from "./components/primitives/icons";
|
|
49
|
+
import { groupInbox, projectName } from "./components/session-tree";
|
|
50
|
+
import { ThreadsSidebar } from "./components/ThreadsSidebar";
|
|
51
|
+
import { ChangesColumn, DiffChangesToggle, useDiffColumns } from "./components/ChangesColumn";
|
|
52
|
+
import { IconButton } from "./components/primitives/IconButton";
|
|
53
|
+
import { ThreadFooter } from "./components/ThreadFooter";
|
|
54
|
+
import { ConfirmCard } from "./components/ConfirmCard";
|
|
49
55
|
import { Breadcrumb } from "./components/Breadcrumb";
|
|
50
56
|
import { THREAD_VIEW_CHEATSHEET, ThreadView } from "./components/ThreadView";
|
|
51
57
|
import {
|
|
@@ -58,23 +64,12 @@ import { DiffSheet } from "./components/DiffSheet";
|
|
|
58
64
|
import { PrototypeSheet } from "./components/PrototypeSheet";
|
|
59
65
|
import type { PrototypeElement } from "./prototype-browser";
|
|
60
66
|
import { type RailTab, type ReviewRailHandle } from "./components/ReviewRail";
|
|
61
|
-
import type
|
|
62
|
-
import { ReviewPanel } from "./components/ReviewPanel";
|
|
67
|
+
import { REVIEW_DEFAULT_WIDTH, type ReviewPanelMode } from "./review-panel";
|
|
63
68
|
import {
|
|
64
|
-
REVIEW_DEFAULT_WIDTH,
|
|
65
|
-
resolveReviewWidth,
|
|
66
|
-
toggleReviewPanelMode,
|
|
67
|
-
widthFromMouseColumn,
|
|
68
|
-
type ReviewPanelMode,
|
|
69
|
-
} from "./review-panel";
|
|
70
|
-
import {
|
|
71
|
-
buildCardEditState,
|
|
72
69
|
buildDiffComposeState,
|
|
73
70
|
buildHeaderItems,
|
|
74
71
|
buildRenderFlags,
|
|
75
72
|
buildSubmitConfirmState,
|
|
76
|
-
computePendingCount,
|
|
77
|
-
computeRailFootprint,
|
|
78
73
|
computeRoleCapabilities,
|
|
79
74
|
deriveReviewFlags,
|
|
80
75
|
isCompletionOverlayPhase,
|
|
@@ -129,17 +124,23 @@ export interface AppProps {
|
|
|
129
124
|
}
|
|
130
125
|
|
|
131
126
|
/** True while the drop-up or one of its dialogs is open and owns the keyboard. */
|
|
132
|
-
function menuChromeOpen(
|
|
133
|
-
return
|
|
127
|
+
function menuChromeOpen(menuDialog: "keybinds" | "settings" | null): boolean {
|
|
128
|
+
return menuDialog !== null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** True while a menu or an overlay owns the keyboard instead of the thread view. */
|
|
132
|
+
function keyboardOwnedElsewhere(menuOwnsKeyboard: boolean, overlay: KeyState["overlay"]): boolean {
|
|
133
|
+
return menuOwnsKeyboard || overlay !== "none";
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
-
/**
|
|
137
|
-
function
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
136
|
+
/** The footer submit fires only for the owner of an unresolved review, never an observer. */
|
|
137
|
+
function canSubmitReview(isOwner: boolean, resolved: boolean, observer: boolean): boolean {
|
|
138
|
+
return isOwner && !resolved && !observer;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** The left panel toggle glyph: the mirrored sidebar icon, filled when the column is open. */
|
|
142
|
+
function sidebarToggleGlyph(open: boolean): string {
|
|
143
|
+
return open ? NERD.sidebarLeft : NERD.sidebarLeftOff;
|
|
143
144
|
}
|
|
144
145
|
|
|
145
146
|
/** The keybinds dialog content: the thread grammar while the thread view owns the keys. */
|
|
@@ -215,9 +216,13 @@ export function App({
|
|
|
215
216
|
// ── view state ──────────────────────────────
|
|
216
217
|
const [cursor, setCursor] = useState(0);
|
|
217
218
|
const [inboxCursor, setInboxCursor] = useState(0);
|
|
219
|
+
// Projects and Threads grouping; ordered is the flat sequence the inbox cursor walks
|
|
220
|
+
const grouped = useMemo(() => groupInbox(inbox ?? []), [inbox]);
|
|
221
|
+
// the left Projects and Threads column; collapsed by default per the context
|
|
222
|
+
// matrix (the thread owns the width), toggled open to jump between threads
|
|
223
|
+
const [sidebarOpen, setSidebarOpen] = useState(false);
|
|
218
224
|
const [mode, setMode] = useState<Mode>({ type: "normal" });
|
|
219
|
-
// the
|
|
220
|
-
const [menuOpen, setMenuOpen] = useState(false);
|
|
225
|
+
// the top-left settings gear drop-down and the centered dialog it opens
|
|
221
226
|
const [menuDialog, setMenuDialog] = useState<"keybinds" | "settings" | null>(null);
|
|
222
227
|
const [autoClose, setAutoClose] = useState<AutoClose>("off");
|
|
223
228
|
const [focusedAnnotationId, setFocusedAnnotationId] = useState<string | undefined>(undefined);
|
|
@@ -225,14 +230,11 @@ export function App({
|
|
|
225
230
|
const [railTab, setRailTab] = useState<RailTab>("review");
|
|
226
231
|
// the tree row the reviewer stands on in the rail's Tree tab
|
|
227
232
|
const [selectedEntryId, setSelectedEntryId] = useState<string | undefined>(undefined);
|
|
228
|
-
// A running in-tab agent terminal (embedded harness); while set, keys route to it.
|
|
229
|
-
const [agentTerminal, setAgentTerminal] = useState<AgentTerminalHandle | null>(null);
|
|
230
233
|
// review panel layout: mode + expanded width are client view state, loaded
|
|
231
234
|
// from and persisted to the user config so they survive a restart. The ref
|
|
232
235
|
// mirrors the width so the drag-end persist reads the latest value.
|
|
233
236
|
const [reviewMode, setReviewMode] = useState<ReviewPanelMode>("expanded");
|
|
234
237
|
const [reviewWidth, setReviewWidth] = useState(REVIEW_DEFAULT_WIDTH);
|
|
235
|
-
const [dividerDragging, setDividerDragging] = useState(false);
|
|
236
238
|
const reviewWidthRef = useRef(REVIEW_DEFAULT_WIDTH);
|
|
237
239
|
// ~2s focus pulse on the document highlight when a rail card is activated
|
|
238
240
|
const [pulsedAnnotationId, setPulsedAnnotationId] = useState<string | null>(null);
|
|
@@ -320,8 +322,8 @@ export function App({
|
|
|
320
322
|
// ── derived view model ──────────────────────
|
|
321
323
|
const display = controller.display();
|
|
322
324
|
const rows = controller.rows();
|
|
325
|
+
const diffColumns = useDiffColumns({ session, rows, cursor, setCursor });
|
|
323
326
|
const rejectedRows = controller.rejectedRows();
|
|
324
|
-
const curationItems = controller.curationItems();
|
|
325
327
|
const marks = useMemo(
|
|
326
328
|
() =>
|
|
327
329
|
session
|
|
@@ -347,79 +349,17 @@ export function App({
|
|
|
347
349
|
// sort position per annotation so the rail interleaves annotation and removal
|
|
348
350
|
// cards in one line-ordered stack: a diff row carries its blockIndex; a plan
|
|
349
351
|
// annotation resolves to the display index it marked
|
|
350
|
-
const annotationPositions = useMemo(() => {
|
|
351
|
-
const positions = new Map<string, number>();
|
|
352
|
-
|
|
353
|
-
if (!session) return positions;
|
|
354
|
-
if (isDiff) {
|
|
355
|
-
for (const annotation of session.annotations) {
|
|
356
|
-
const blockIndex =
|
|
357
|
-
"blockIndex" in annotation.anchor ? annotation.anchor.blockIndex : undefined;
|
|
358
|
-
|
|
359
|
-
if (blockIndex !== undefined) positions.set(annotation.id, blockIndex);
|
|
360
|
-
}
|
|
361
|
-
} else {
|
|
362
|
-
for (const [displayIndex, blockMarks] of marks) {
|
|
363
|
-
for (const mark of blockMarks)
|
|
364
|
-
if (mark.annotationId) positions.set(mark.annotationId, displayIndex);
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
return positions;
|
|
369
|
-
}, [session, isDiff, marks]);
|
|
370
|
-
|
|
371
352
|
// ── the guided walk's view model ────────────
|
|
372
353
|
const walkFileList = controller.files();
|
|
373
354
|
const walking = isWalking(isDiff, walk);
|
|
374
355
|
const viewedPaths = useMemo(() => new Set(session?.viewedPaths ?? []), [session]);
|
|
375
356
|
|
|
376
357
|
// ── selection symmetry: one selected id, both sides ──
|
|
377
|
-
const pulse = (id: string): void => {
|
|
378
|
-
setPulsedAnnotationId(id);
|
|
379
|
-
if (pulseTimer.current) clearTimeout(pulseTimer.current);
|
|
380
|
-
pulseTimer.current = setTimeout(() => setPulsedAnnotationId(null), 2000);
|
|
381
|
-
};
|
|
382
|
-
|
|
383
|
-
/** Card activation scrolls the document to the anchor and pulses it. */
|
|
384
|
-
const revealAnchor = (annotationId: string): void => {
|
|
385
|
-
for (const [displayIndex, blockMarks] of marks) {
|
|
386
|
-
if (!blockMarks.some((mark) => mark.annotationId === annotationId)) continue;
|
|
387
|
-
setCursor(displayIndex);
|
|
388
|
-
|
|
389
|
-
return;
|
|
390
|
-
}
|
|
391
|
-
};
|
|
392
|
-
|
|
393
358
|
const selectCardFromDocument = (annotationId: string): void => {
|
|
394
359
|
setFocusedAnnotationId(annotationId);
|
|
395
360
|
railRef.current?.revealCard(annotationId);
|
|
396
361
|
};
|
|
397
362
|
|
|
398
|
-
const selectCardFromRail = (annotationId: string): void => {
|
|
399
|
-
setFocusedAnnotationId(annotationId);
|
|
400
|
-
pulse(annotationId);
|
|
401
|
-
revealAnchor(annotationId);
|
|
402
|
-
};
|
|
403
|
-
|
|
404
|
-
// selecting a removal reveals its source line, so the reader sees what the
|
|
405
|
-
// pending undo would bring back: the diff sheet follows the cursor; the plan
|
|
406
|
-
// sheet also needs an explicit scroll to the cut block
|
|
407
|
-
const selectCurationFromRail = (curationId: string): void => {
|
|
408
|
-
setSelectedCurationId(curationId);
|
|
409
|
-
const item = curationItems.find((candidate) => candidate.id === curationId);
|
|
410
|
-
|
|
411
|
-
if (!item) return;
|
|
412
|
-
setCursor(item.revealIndex);
|
|
413
|
-
};
|
|
414
|
-
|
|
415
|
-
// the selected removal card's undo button: same restore path as the u key,
|
|
416
|
-
// including the observer read-only guard the keyboard path gets via reduceKey
|
|
417
|
-
const undoCurationFromRail = (curationId: string): void => {
|
|
418
|
-
if (observer) return controller.setStatus("observer - read-only");
|
|
419
|
-
controller.restoreCuration(curationId);
|
|
420
|
-
setSelectedCurationId(undefined);
|
|
421
|
-
};
|
|
422
|
-
|
|
423
363
|
const openCardEdit = (annotationId: string): void => {
|
|
424
364
|
if (observer) return controller.setStatus("observer - read-only");
|
|
425
365
|
const annotation = session?.annotations.find((candidate) => candidate.id === annotationId);
|
|
@@ -459,7 +399,7 @@ export function App({
|
|
|
459
399
|
display,
|
|
460
400
|
rows,
|
|
461
401
|
cursor,
|
|
462
|
-
inbox,
|
|
402
|
+
inbox: inbox === null ? null : grouped.ordered,
|
|
463
403
|
inboxCursor,
|
|
464
404
|
mode,
|
|
465
405
|
session,
|
|
@@ -495,10 +435,10 @@ export function App({
|
|
|
495
435
|
|
|
496
436
|
const overlay = resolveOverlay(mode, completion.phase, walking);
|
|
497
437
|
|
|
498
|
-
const menuOwnsKeyboard = menuChromeOpen(
|
|
438
|
+
const menuOwnsKeyboard = menuChromeOpen(menuDialog);
|
|
499
439
|
// an overlay (submit, walk, prompt, confirm) or the menu takes the keyboard
|
|
500
440
|
// from the thread view; the view suspends its own grammar meanwhile
|
|
501
|
-
const threadViewSuspended = keyboardOwnedElsewhere(menuOwnsKeyboard, overlay
|
|
441
|
+
const threadViewSuspended = keyboardOwnedElsewhere(menuOwnsKeyboard, overlay);
|
|
502
442
|
|
|
503
443
|
useKeyboard((key) => {
|
|
504
444
|
// The thread view owns the document grammar while active (its own
|
|
@@ -520,16 +460,8 @@ export function App({
|
|
|
520
460
|
// The prototype compose textarea owns the keyboard while open: let it receive
|
|
521
461
|
// the typed note instead of the global keymap acting on each letter.
|
|
522
462
|
if (prototypeComposing) return;
|
|
523
|
-
// A running in-tab agent terminal owns the keyboard: forward every key to it,
|
|
524
|
-
// with ctrl+] as the detach chord back to the review.
|
|
525
|
-
if (agentTerminal) {
|
|
526
|
-
if (key.ctrl && key.name === "]") return void agentTerminal.detach();
|
|
527
|
-
|
|
528
|
-
return void agentTerminal.write(key.sequence);
|
|
529
|
-
}
|
|
530
463
|
if (menuDialog === "settings") return void handleSettingsKey(key.name);
|
|
531
464
|
if (menuDialog) return void (key.name === "escape" && setMenuDialog(null));
|
|
532
|
-
if (menuOpen) return void (key.name === "escape" && setMenuOpen(false));
|
|
533
465
|
// the toast is non-modal: escape only dismisses it when nothing else owns
|
|
534
466
|
// escape, so an open overlay (compose, submit, prompt, walk) still cancels
|
|
535
467
|
if (toast && key.name === "escape" && overlay === "none" && mode.type !== "span")
|
|
@@ -552,8 +484,14 @@ export function App({
|
|
|
552
484
|
display,
|
|
553
485
|
});
|
|
554
486
|
|
|
555
|
-
keyBindings.setContext({
|
|
556
|
-
|
|
487
|
+
keyBindings.setContext({
|
|
488
|
+
overlay: state.overlay,
|
|
489
|
+
spanMode: state.spanMode,
|
|
490
|
+
});
|
|
491
|
+
const action = keyBindings.resolveAction({
|
|
492
|
+
name: key.name,
|
|
493
|
+
shift: !!key.shift,
|
|
494
|
+
});
|
|
557
495
|
|
|
558
496
|
for (const intent of reduceKey(
|
|
559
497
|
state,
|
|
@@ -567,12 +505,8 @@ export function App({
|
|
|
567
505
|
// reused by the inbox and by plan/diff review so the two never drift ──
|
|
568
506
|
const menuChrome = (
|
|
569
507
|
<MenuChrome
|
|
570
|
-
menuOpen={menuOpen}
|
|
571
508
|
menuDialog={menuDialog}
|
|
572
|
-
status={status}
|
|
573
509
|
theme={theme}
|
|
574
|
-
setMenuOpen={setMenuOpen}
|
|
575
|
-
setMenuDialog={setMenuDialog}
|
|
576
510
|
keybindsSections={cheatsheetFor(keyBindings, threadViewActive)}
|
|
577
511
|
settingsCategories={settingsCategories}
|
|
578
512
|
settingsValues={settingsValues}
|
|
@@ -587,13 +521,14 @@ export function App({
|
|
|
587
521
|
if (!session)
|
|
588
522
|
return inbox ? (
|
|
589
523
|
<InboxScreen
|
|
590
|
-
|
|
524
|
+
rows={grouped.rows}
|
|
591
525
|
inboxCursor={inboxCursor}
|
|
592
526
|
mode={mode}
|
|
593
527
|
theme={theme}
|
|
594
528
|
controller={controller}
|
|
595
529
|
setMode={setMode}
|
|
596
530
|
menuChrome={menuChrome}
|
|
531
|
+
onOpenMenu={() => setMenuDialog("settings")}
|
|
597
532
|
/>
|
|
598
533
|
) : (
|
|
599
534
|
<ConnectingScreen theme={theme} />
|
|
@@ -630,21 +565,21 @@ export function App({
|
|
|
630
565
|
setMode,
|
|
631
566
|
dispatch,
|
|
632
567
|
});
|
|
633
|
-
const
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
568
|
+
const headerItems = buildHeaderItems({
|
|
569
|
+
session: activeSession,
|
|
570
|
+
resolved,
|
|
571
|
+
observer,
|
|
572
|
+
role,
|
|
573
|
+
});
|
|
574
|
+
const { showOwnerActions, prototypeCanComment, chromeHidden, prototypePath } = buildRenderFlags({
|
|
575
|
+
session: activeSession,
|
|
576
|
+
isOwner,
|
|
577
|
+
isDiff,
|
|
578
|
+
isPrototype,
|
|
579
|
+
resolved,
|
|
580
|
+
menuDialog,
|
|
581
|
+
resolvedIds,
|
|
582
|
+
});
|
|
648
583
|
|
|
649
584
|
const onEditRequest = (): void => {
|
|
650
585
|
// A share viewer/observer has no Edit affordance (the button is hidden), so
|
|
@@ -668,19 +603,6 @@ export function App({
|
|
|
668
603
|
dispatch({ type: "openSubmit" });
|
|
669
604
|
};
|
|
670
605
|
|
|
671
|
-
// the clickable chevron toggles expanded <-> compact; the keybinding (b)
|
|
672
|
-
// cycles all three including hidden
|
|
673
|
-
const onToggleReviewPanel = (): void => {
|
|
674
|
-
const next = toggleReviewPanelMode(reviewMode);
|
|
675
|
-
|
|
676
|
-
setReviewMode(next);
|
|
677
|
-
controller.saveReviewPanel({ mode: next });
|
|
678
|
-
};
|
|
679
|
-
// grabbing the divider only arms a drag when there is a width to drag
|
|
680
|
-
const onDividerGrab = (): void => {
|
|
681
|
-
if (reviewMode === "expanded") setDividerDragging(true);
|
|
682
|
-
};
|
|
683
|
-
|
|
684
606
|
return (
|
|
685
607
|
<ThemeProvider theme={theme}>
|
|
686
608
|
<box
|
|
@@ -690,26 +612,25 @@ export function App({
|
|
|
690
612
|
height: "100%",
|
|
691
613
|
backgroundColor: theme.background,
|
|
692
614
|
}}
|
|
693
|
-
onMouseDrag={(event: MouseEvent) => {
|
|
694
|
-
if (!dividerDragging || reviewMode !== "expanded") return;
|
|
695
|
-
const next = widthFromMouseColumn(event.x, terminalWidth);
|
|
696
|
-
|
|
697
|
-
// many raw mouse-moves land in the same column: only re-render when the
|
|
698
|
-
// width actually changes, so the drag does not reconcile per pixel
|
|
699
|
-
if (next === reviewWidthRef.current) return;
|
|
700
|
-
reviewWidthRef.current = next;
|
|
701
|
-
setReviewWidth(next);
|
|
702
|
-
}}
|
|
703
|
-
onMouseUp={() => {
|
|
704
|
-
if (!dividerDragging) return;
|
|
705
|
-
setDividerDragging(false);
|
|
706
|
-
controller.saveReviewPanel({ width: reviewWidthRef.current });
|
|
707
|
-
}}
|
|
708
615
|
>
|
|
709
616
|
<box
|
|
710
|
-
style={{
|
|
617
|
+
style={{
|
|
618
|
+
flexDirection: "row",
|
|
619
|
+
height: 2,
|
|
620
|
+
paddingTop: 1,
|
|
621
|
+
backgroundColor: theme.panel,
|
|
622
|
+
}}
|
|
711
623
|
>
|
|
712
624
|
<box style={{ flexGrow: 1, flexDirection: "row", paddingRight: 1 }}>
|
|
625
|
+
<box onMouseUp={() => setMenuDialog("settings")} style={{ paddingRight: 2 }}>
|
|
626
|
+
<text fg={theme.textMuted}>{NERD.settings}</text>
|
|
627
|
+
</box>
|
|
628
|
+
<IconButton
|
|
629
|
+
glyph={sidebarToggleGlyph(sidebarOpen)}
|
|
630
|
+
onPress={() => setSidebarOpen((open) => !open)}
|
|
631
|
+
marginRight={2}
|
|
632
|
+
theme={theme}
|
|
633
|
+
/>
|
|
713
634
|
<Breadcrumb items={headerItems} />
|
|
714
635
|
<box style={{ flexGrow: 1 }} />
|
|
715
636
|
{showOwnerActions ? (
|
|
@@ -722,111 +643,98 @@ export function App({
|
|
|
722
643
|
</Button>
|
|
723
644
|
</Toolbar>
|
|
724
645
|
) : null}
|
|
646
|
+
<DiffChangesToggle
|
|
647
|
+
isDiff={isDiff}
|
|
648
|
+
open={diffColumns.changesOpen}
|
|
649
|
+
onToggle={diffColumns.toggleChanges}
|
|
650
|
+
theme={theme}
|
|
651
|
+
/>
|
|
725
652
|
</box>
|
|
726
|
-
<box style={{ width: railFootprint }} />
|
|
727
653
|
</box>
|
|
728
654
|
<box style={{ flexGrow: 1, flexDirection: "row" }}>
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
655
|
+
<ThreadsSidebar
|
|
656
|
+
open={sidebarOpen}
|
|
657
|
+
rows={grouped.rows}
|
|
658
|
+
cursor={inboxCursor}
|
|
659
|
+
activeId={session.id}
|
|
660
|
+
onSelect={(id) => controller.open(id)}
|
|
661
|
+
theme={theme}
|
|
662
|
+
/>
|
|
663
|
+
<box style={{ flexGrow: 1, flexDirection: "column" }}>
|
|
664
|
+
<box style={{ flexGrow: 1, flexDirection: "row" }}>
|
|
665
|
+
{isPrototype ? (
|
|
666
|
+
<PrototypeSheet
|
|
667
|
+
prototypePath={prototypePath}
|
|
668
|
+
quickActions={quickActions}
|
|
669
|
+
canComment={prototypeCanComment}
|
|
670
|
+
onCommentElement={onCommentPrototype}
|
|
671
|
+
onComposingChange={setPrototypeComposing}
|
|
672
|
+
hidden={chromeHidden}
|
|
673
|
+
/>
|
|
674
|
+
) : isDiff ? (
|
|
675
|
+
// the sheet dims to reading-quiet colors while the wizard has focus
|
|
676
|
+
<DiffSheet
|
|
677
|
+
rows={rows}
|
|
678
|
+
cursor={cursor}
|
|
679
|
+
annotations={activeSession.annotations}
|
|
680
|
+
focusedAnnotationId={focusedAnnotationId}
|
|
681
|
+
rejectedRows={rejectedRows}
|
|
682
|
+
compose={diffComposeState}
|
|
683
|
+
theme={walking ? dimmedTheme(theme) : undefined}
|
|
684
|
+
/>
|
|
685
|
+
) : (
|
|
686
|
+
<ThreadView
|
|
687
|
+
session={activeSession}
|
|
688
|
+
suspended={threadViewSuspended}
|
|
689
|
+
editOrphanCount={editOrphanCount}
|
|
690
|
+
onComposingChange={setThreadComposing}
|
|
691
|
+
resolved={resolved}
|
|
692
|
+
onObserverBlocked={(reason) =>
|
|
693
|
+
controller.setStatus(
|
|
694
|
+
reason === "observer"
|
|
695
|
+
? "observer - read-only"
|
|
696
|
+
: "review submitted - read-only",
|
|
697
|
+
)
|
|
698
|
+
}
|
|
699
|
+
onCursorChange={setCursor}
|
|
700
|
+
focusedAnnotationId={focusedAnnotationId}
|
|
701
|
+
onFocusAnnotation={setFocusedAnnotationId}
|
|
702
|
+
display={display}
|
|
703
|
+
marks={marks}
|
|
704
|
+
quickActions={quickActions}
|
|
705
|
+
observer={observer}
|
|
706
|
+
onAnnotate={(span, body) =>
|
|
707
|
+
void controller.annotate(
|
|
708
|
+
"comment",
|
|
709
|
+
span.start.blockIndex,
|
|
710
|
+
span.start.char,
|
|
711
|
+
span.end.char,
|
|
712
|
+
body,
|
|
713
|
+
span.end.blockIndex,
|
|
714
|
+
)
|
|
715
|
+
}
|
|
716
|
+
onReply={(rootAnnotationId, body) =>
|
|
717
|
+
void controller.reply(rootAnnotationId, body)
|
|
718
|
+
}
|
|
719
|
+
onUpdateAnnotation={(id, body) => controller.updateAnnotation(id, body)}
|
|
720
|
+
onExit={() => onExit?.(0)}
|
|
721
|
+
/>
|
|
722
|
+
)}
|
|
723
|
+
</box>
|
|
724
|
+
<ThreadFooter
|
|
725
|
+
repo={projectName(activeSession.workspace)}
|
|
726
|
+
branch={activeSession.workspace.branch}
|
|
727
|
+
onSubmit={onSubmitRequest}
|
|
728
|
+
canSubmit={canSubmitReview(isOwner, resolved, observer)}
|
|
729
|
+
theme={theme}
|
|
781
730
|
/>
|
|
782
|
-
|
|
783
|
-
<
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
rail={{
|
|
790
|
-
session: activeSession,
|
|
791
|
-
authorNames,
|
|
792
|
-
selectedId: focusedAnnotationId,
|
|
793
|
-
resolvedIds: railResolvedIds,
|
|
794
|
-
curationItems,
|
|
795
|
-
selectedCurationId,
|
|
796
|
-
annotationPositions,
|
|
797
|
-
railTab,
|
|
798
|
-
pendingCount,
|
|
799
|
-
cardEdit: cardEditState,
|
|
800
|
-
submitConfirm: submitConfirmState,
|
|
801
|
-
onTabChange: setRailTab,
|
|
802
|
-
onSelectCard: selectCardFromRail,
|
|
803
|
-
onActivateCard: openCardEdit,
|
|
804
|
-
onSelectCuration: selectCurationFromRail,
|
|
805
|
-
onUndoCuration: undoCurationFromRail,
|
|
806
|
-
onSubmitRequest,
|
|
807
|
-
onLaunchHarness: (command, seedText) =>
|
|
808
|
-
launchHarnessInSplit({
|
|
809
|
-
command,
|
|
810
|
-
cwd: activeSession.artifact.meta.cwd ?? process.cwd(),
|
|
811
|
-
seedText,
|
|
812
|
-
}),
|
|
813
|
-
onAgentTerminal: setAgentTerminal,
|
|
814
|
-
tree: {
|
|
815
|
-
rows: controller.treeRows(),
|
|
816
|
-
selectedEntryId,
|
|
817
|
-
canMove: isOwner && !resolved,
|
|
818
|
-
canFork: isOwner,
|
|
819
|
-
onSelect: setSelectedEntryId,
|
|
820
|
-
onGo: (entryId) => {
|
|
821
|
-
setSelectedEntryId(entryId);
|
|
822
|
-
dispatch({ type: "treeGo" });
|
|
823
|
-
},
|
|
824
|
-
onBranch: () => dispatch({ type: "treeBranch" }),
|
|
825
|
-
onLabel: () => dispatch({ type: "treeLabel" }),
|
|
826
|
-
onFork: () => dispatch({ type: "treeFork" }),
|
|
827
|
-
onForkAndShare: () => dispatch({ type: "treeForkShare" }),
|
|
828
|
-
},
|
|
829
|
-
}}
|
|
731
|
+
</box>
|
|
732
|
+
<ChangesColumn
|
|
733
|
+
open={diffColumns.changesOpen}
|
|
734
|
+
files={activeSession.artifact.files}
|
|
735
|
+
selectedPath={diffColumns.currentFilePath}
|
|
736
|
+
onSelectFile={diffColumns.scrollToFile}
|
|
737
|
+
theme={theme}
|
|
830
738
|
/>
|
|
831
739
|
</box>
|
|
832
740
|
<TrailingOverlays
|
|
@@ -842,6 +750,21 @@ export function App({
|
|
|
842
750
|
setMode={setMode}
|
|
843
751
|
dispatch={dispatch}
|
|
844
752
|
/>
|
|
753
|
+
{submitConfirmState !== null ? (
|
|
754
|
+
<box
|
|
755
|
+
style={{
|
|
756
|
+
position: "absolute",
|
|
757
|
+
left: 0,
|
|
758
|
+
top: 0,
|
|
759
|
+
width: "100%",
|
|
760
|
+
height: "100%",
|
|
761
|
+
justifyContent: "center",
|
|
762
|
+
alignItems: "center",
|
|
763
|
+
}}
|
|
764
|
+
>
|
|
765
|
+
<ConfirmCard {...submitConfirmState} theme={theme} />
|
|
766
|
+
</box>
|
|
767
|
+
) : null}
|
|
845
768
|
{menuChrome}
|
|
846
769
|
</box>
|
|
847
770
|
</ThemeProvider>
|
package/src/App.walk.test.tsx
CHANGED
|
@@ -157,7 +157,7 @@ describe("the guided walk", () => {
|
|
|
157
157
|
await press(setup, "enter");
|
|
158
158
|
|
|
159
159
|
// Assert
|
|
160
|
-
await waitForText(setup, "
|
|
160
|
+
await waitForText(setup, "send message");
|
|
161
161
|
expect(setup.captureCharFrame()).toContain("3/3 files viewed");
|
|
162
162
|
|
|
163
163
|
// Act
|