@cueloop/client 0.1.0-alpha.102 → 0.1.0-alpha.103
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/annotations/annotation-palette.ts +1 -0
- package/src/annotations/components/AnnotationCards.tsx +4 -0
- package/src/annotations/components/DiscussionMarkerRail.tsx +3 -0
- package/src/annotations/discussions.ts +1 -0
- package/src/annotations/thread-selection.ts +6 -0
- package/src/annotations/use-annotation-surface.tsx +97 -0
- package/src/app/App.deleted-scroll.test.tsx +3 -0
- package/src/app/App.harness-settings.test.tsx +77 -0
- package/src/app/App.plan-render.test.tsx +2 -0
- package/src/app/App.plan-review.test.tsx +1 -0
- package/src/app/App.test.tsx +1 -0
- package/src/app/App.tsx +67 -1
- package/src/app/App.visual.test.tsx +1 -0
- package/src/app/App.walk.test.tsx +1 -0
- package/src/app/App.workbench.test.tsx +1 -0
- package/src/app/app-screens.tsx +1 -0
- package/src/app/app-view-model.ts +7 -0
- package/src/app/components/WorkspacePanels.tsx +1 -0
- package/src/app/intent-dispatch.test.ts +1 -0
- package/src/app/intent-dispatch.ts +26 -0
- package/src/app/ready-signal.ts +2 -0
- package/src/app/run.ts +2 -0
- package/src/app/serve.test.ts +4 -0
- package/src/diff/components/AnnotatableFileView.tsx +1 -0
- package/src/diff/components/DiffContentView.scroll.test.tsx +9 -0
- package/src/diff/components/DiffContentView.syntax.test.tsx +1 -0
- package/src/diff/components/DiffContentView.tsx +25 -0
- package/src/diff/components/diff-content-view-layout.ts +3 -0
- package/src/diff/diff-fold.ts +4 -0
- package/src/diff/diff-hunk-curate.ts +3 -0
- package/src/diff/diff-intraline.ts +6 -0
- package/src/diff/diff-syntax.ts +8 -0
- package/src/diff/split-diff.ts +6 -0
- package/src/diff/view-diff.ts +18 -0
- package/src/diff/walk.ts +3 -0
- package/src/integrations/attribution.ts +2 -0
- package/src/integrations/client-extension-registry.ts +4 -0
- package/src/integrations/clipboard.ts +1 -0
- package/src/integrations/components/ShareDialog.agent.test.tsx +2 -0
- package/src/integrations/components/ShareDialog.tsx +28 -0
- package/src/integrations/editor.ts +3 -0
- package/src/integrations/github-identity.test.ts +1 -0
- package/src/integrations/github-identity.ts +1 -0
- package/src/integrations/herdr.ts +2 -0
- package/src/integrations/pasted-image.ts +2 -0
- package/src/integrations/share.ts +4 -0
- package/src/keyboard/components/HandleChipsInput.tsx +4 -0
- package/src/keyboard/components/SlashComposer.tsx +3 -0
- package/src/keyboard/key-bindings.ts +3 -0
- package/src/keyboard/keymap.ts +51 -0
- package/src/keyboard/skills.ts +2 -0
- package/src/keyboard/slash-palette.ts +8 -0
- package/src/keyboard/thread-chords.ts +5 -0
- package/src/keyboard/use-shared-keyboard.ts +1 -0
- package/src/markdown/components/MarkdownThreadEditor.tsx +3 -0
- package/src/markdown/components/ThreadView.selection.test.tsx +3 -0
- package/src/markdown/components/ThreadView.tsx +13 -0
- package/src/markdown/mark-runs.ts +4 -0
- package/src/markdown/markdown-editor-syntax-style.ts +1 -0
- package/src/markdown/markdown-grid.ts +6 -0
- package/src/markdown/markdown-highlight.ts +1 -0
- package/src/markdown/thread-code-syntax.ts +3 -0
- package/src/markdown/view-plan.ts +52 -0
- package/src/performance/perf-report.ts +1 -0
- package/src/performance/perf-timings.ts +1 -0
- package/src/prototype/components/PrototypeContentView.tsx +17 -0
- package/src/prototype/prototype-browser.ts +11 -0
- package/src/settings/components/SettingsDialog.tsx +3 -0
- package/src/settings/config.test.ts +41 -0
- package/src/settings/config.ts +44 -0
- package/src/settings/use-settings-dialog.test.tsx +98 -0
- package/src/settings/use-settings-dialog.tsx +38 -1
- package/src/settings/use-thread-harness-setting.test.tsx +87 -0
- package/src/settings/use-thread-harness-setting.ts +86 -0
- package/src/stories/stories.test.tsx +4 -0
- package/src/stories/story.ts +6 -0
- package/src/terminal/ansi-screen.ts +5 -0
- package/src/terminal/components/terminal-pane.ts +12 -0
- package/src/terminal/ghostty-terminal.ts +10 -0
- package/src/terminal/kitty-image.ts +3 -0
- package/src/terminal/pty.ts +9 -0
- package/src/testing/test-support.test.ts +2 -0
- package/src/testing/test-support.ts +16 -0
- package/src/thread/agent-continuation.test.ts +2 -0
- package/src/thread/agent-transcript.ts +7 -0
- package/src/thread/components/AgentConfigControls.tsx +1 -0
- package/src/thread/components/AgentThreadPane.test.tsx +31 -0
- package/src/thread/components/AgentThreadPane.tsx +12 -0
- package/src/thread/components/ThreadTree.tsx +1 -0
- package/src/thread/components/thread-tree.test.ts +1 -0
- package/src/thread/thread-controller.curate.test.ts +2 -0
- package/src/thread/thread-controller.ts +123 -0
- package/src/thread/tree-view.ts +4 -0
- package/src/thread/use-thread-agent.test.tsx +1 -0
- package/src/thread/use-thread-agent.ts +5 -0
- package/src/ui/components/OverlayScrollbar.test.tsx +3 -0
- package/src/ui/components/OverlayScrollbar.tsx +5 -0
- package/src/ui/components/PromptDialog.tsx +2 -0
- package/src/ui/components/RootOverlay.tsx +1 -0
- package/src/ui/components/ScrollArea.tsx +2 -0
- package/src/ui/components/primitives/Button.tsx +1 -0
- package/src/ui/components/primitives/Tabs.tsx +1 -0
- package/src/ui/components/primitives/Tree.tsx +4 -0
- package/src/ui/components/primitives/tree-model.ts +7 -0
- package/src/ui/components/syntax-highlight.ts +5 -0
- package/src/ui/components/truncate-title.ts +1 -0
- package/src/ui/use-frame-measure.ts +3 -0
- package/src/ui/use-terminal-virtualizer.ts +6 -0
- package/src/workbench/components/ChangesColumn.tsx +7 -0
- package/src/workbench/components/ChangesFileTree.test.tsx +3 -0
- package/src/workbench/components/EditorGrid.tsx +8 -0
- package/src/workbench/components/FileContentsView.tsx +2 -0
- package/src/workbench/components/GridTabContent.tsx +2 -0
- package/src/workbench/components/ProjectTreeView.test.tsx +3 -0
- package/src/workbench/components/ProjectTreeView.tsx +8 -0
- package/src/workbench/components/TreePane.tsx +2 -0
- package/src/workbench/components/editor-grid.ts +3 -0
- package/src/workbench/components/file-tree.ts +2 -0
- package/src/workbench/use-changes-workbench.ts +5 -0
- package/src/workbench/use-remember-layout.ts +2 -0
- package/src/workbench/use-repo-changes.ts +2 -0
|
@@ -435,11 +435,13 @@ class Controller implements ReviewController {
|
|
|
435
435
|
|
|
436
436
|
constructor(private readonly options: ReviewControllerOptions) {
|
|
437
437
|
this.client = options.initialClient ?? null;
|
|
438
|
+
|
|
438
439
|
if (options.initialSession?.id === options.sessionId && options.initialDiff) {
|
|
439
440
|
this.liveDiff = options.initialDiff;
|
|
440
441
|
this.initialDiffAvailable = true;
|
|
441
442
|
this.store.setState({ session: options.initialSession });
|
|
442
443
|
}
|
|
444
|
+
|
|
443
445
|
this.readOnly = options.readOnly ?? false;
|
|
444
446
|
this.clock = options.clock ?? new SystemClock();
|
|
445
447
|
this.shareTransport = options.shareTransport ?? DEFAULT_SHARE_TRANSPORT;
|
|
@@ -473,12 +475,14 @@ class Controller implements ReviewController {
|
|
|
473
475
|
const client = await openClient();
|
|
474
476
|
|
|
475
477
|
if (this.closed) return void client.close();
|
|
478
|
+
|
|
476
479
|
this.client = client;
|
|
477
480
|
client.onEvent((event) => {
|
|
478
481
|
// another controller/observer changed state: re-fetch the active session's content
|
|
479
482
|
const session = this.snapshot.session;
|
|
480
483
|
|
|
481
484
|
if (session && event.sessionId === session.id) void this.refreshSession(session.id);
|
|
485
|
+
|
|
482
486
|
// only the list-changing events touch the Threads sidebar; refreshing on every
|
|
483
487
|
// content edit (session.updated) would amplify an active review into a request storm
|
|
484
488
|
if (event.event !== "session.updated") void this.refreshInbox();
|
|
@@ -508,6 +512,7 @@ class Controller implements ReviewController {
|
|
|
508
512
|
|
|
509
513
|
private clearCountdown(): void {
|
|
510
514
|
if (this.countdown !== undefined) this.clock.clearTimeout(this.countdown);
|
|
515
|
+
|
|
511
516
|
this.countdown = undefined;
|
|
512
517
|
}
|
|
513
518
|
|
|
@@ -577,9 +582,11 @@ class Controller implements ReviewController {
|
|
|
577
582
|
const cached = this.derived.models.get(path);
|
|
578
583
|
|
|
579
584
|
if (cached) return cached;
|
|
585
|
+
|
|
580
586
|
const contents = this.derived.fileContents.get(path);
|
|
581
587
|
|
|
582
588
|
if (!contents) return undefined;
|
|
589
|
+
|
|
583
590
|
const model = parseFileDiff(contents);
|
|
584
591
|
|
|
585
592
|
this.derived.models.set(path, model);
|
|
@@ -594,6 +601,7 @@ class Controller implements ReviewController {
|
|
|
594
601
|
const liveDiff = frozenDiff ? null : this.liveDiff;
|
|
595
602
|
|
|
596
603
|
if (this.derivedFor === session && this.derivedForLiveDiff === liveDiff) return;
|
|
604
|
+
|
|
597
605
|
// reuse the parsed projection across an update that left the inputs unchanged; only the cheap tree can differ
|
|
598
606
|
if (session !== null && this.reusesDerived(session, liveDiff)) {
|
|
599
607
|
this.derivedFor = session;
|
|
@@ -601,11 +609,13 @@ class Controller implements ReviewController {
|
|
|
601
609
|
|
|
602
610
|
return;
|
|
603
611
|
}
|
|
612
|
+
|
|
604
613
|
// a different thread resets fold state; a same-thread re-derivation (content edit) keeps it
|
|
605
614
|
if (this.derivedFor?.id !== session?.id) {
|
|
606
615
|
this.collapsedFiles.clear();
|
|
607
616
|
this.expandedFiles.clear();
|
|
608
617
|
}
|
|
618
|
+
|
|
609
619
|
// returning to a recently-viewed thread whose inputs are unchanged reuses its parsed projection
|
|
610
620
|
const cached = session !== null ? this.derivedCache.get(session.id) : undefined;
|
|
611
621
|
|
|
@@ -624,9 +634,11 @@ class Controller implements ReviewController {
|
|
|
624
634
|
|
|
625
635
|
return;
|
|
626
636
|
}
|
|
637
|
+
|
|
627
638
|
this.derivedFor = session;
|
|
628
639
|
this.derivedForLiveDiff = liveDiff;
|
|
629
640
|
this.derived = this.buildDerived(session, frozenDiff, liveDiff);
|
|
641
|
+
|
|
630
642
|
if (session !== null) this.rememberDerived(session, liveDiff, this.derived);
|
|
631
643
|
}
|
|
632
644
|
|
|
@@ -646,6 +658,7 @@ class Controller implements ReviewController {
|
|
|
646
658
|
const oldest = this.derivedCache.keys().next().value;
|
|
647
659
|
|
|
648
660
|
if (oldest === undefined) break;
|
|
661
|
+
|
|
649
662
|
this.derivedCache.delete(oldest);
|
|
650
663
|
}
|
|
651
664
|
}
|
|
@@ -671,6 +684,7 @@ class Controller implements ReviewController {
|
|
|
671
684
|
|
|
672
685
|
rows(): DiffRow[] {
|
|
673
686
|
this.ensureDerived();
|
|
687
|
+
|
|
674
688
|
if (this.collapsedFiles.size === 0 && this.expandedFiles.size === 0) return this.derived.rows;
|
|
675
689
|
|
|
676
690
|
return applyFold(this.derived.rows, this.collapsedFiles, this.expandedFiles, this.foldFiles());
|
|
@@ -683,6 +697,7 @@ class Controller implements ReviewController {
|
|
|
683
697
|
} else {
|
|
684
698
|
this.collapsedFiles.delete(file);
|
|
685
699
|
}
|
|
700
|
+
|
|
686
701
|
this.update({});
|
|
687
702
|
}
|
|
688
703
|
|
|
@@ -697,6 +712,7 @@ class Controller implements ReviewController {
|
|
|
697
712
|
} else {
|
|
698
713
|
this.expandedFiles.delete(file);
|
|
699
714
|
}
|
|
715
|
+
|
|
700
716
|
this.update({});
|
|
701
717
|
}
|
|
702
718
|
|
|
@@ -770,9 +786,11 @@ class Controller implements ReviewController {
|
|
|
770
786
|
const session = this.snapshot.session;
|
|
771
787
|
|
|
772
788
|
if (readsFrozenDiff(session)) return session!.artifact.files ?? [];
|
|
789
|
+
|
|
773
790
|
const initialFiles = this.consumeInitialDiffFiles(session);
|
|
774
791
|
|
|
775
792
|
if (initialFiles !== null) return initialFiles;
|
|
793
|
+
|
|
776
794
|
// eager: capture the live working-tree diff so the Changes navigator and its file tabs render a real diff
|
|
777
795
|
if (this.client?.repoDiff !== undefined) {
|
|
778
796
|
const diff = await this.client.repoDiff(
|
|
@@ -784,6 +802,7 @@ class Controller implements ReviewController {
|
|
|
784
802
|
// showing changes from the wrong repo; compare the thread id, not the object, so an unrelated
|
|
785
803
|
// re-render that replaced the snapshot for the SAME thread does not drop its diff to "No changes"
|
|
786
804
|
if (this.snapshot.session?.id !== session?.id) return diff.files;
|
|
805
|
+
|
|
787
806
|
const changed =
|
|
788
807
|
this.liveDiff?.patch !== diff.patch ||
|
|
789
808
|
this.liveDiff.files.length !== diff.files.length ||
|
|
@@ -803,8 +822,10 @@ class Controller implements ReviewController {
|
|
|
803
822
|
|
|
804
823
|
return diff.files;
|
|
805
824
|
}
|
|
825
|
+
|
|
806
826
|
// a peer without the live-diff capability still lists changes, just without a diff body
|
|
807
827
|
if (this.client?.repoChanges === undefined) return [];
|
|
828
|
+
|
|
808
829
|
const changes = await this.client.repoChanges(this.sidebarRepoRoot());
|
|
809
830
|
|
|
810
831
|
return changes.map((change) => ({
|
|
@@ -817,6 +838,7 @@ class Controller implements ReviewController {
|
|
|
817
838
|
|
|
818
839
|
private consumeInitialDiffFiles(session: Thread | null): readonly DiffFileContents[] | null {
|
|
819
840
|
if (!this.initialDiffAvailable) return null;
|
|
841
|
+
|
|
820
842
|
this.initialDiffAvailable = false;
|
|
821
843
|
|
|
822
844
|
return session?.id === this.options.sessionId ? (this.liveDiff?.files ?? []) : null;
|
|
@@ -826,6 +848,7 @@ class Controller implements ReviewController {
|
|
|
826
848
|
const session = this.snapshot.session;
|
|
827
849
|
|
|
828
850
|
if (session?.artifact.type !== "diff" || !this.client?.sessionRefreshDiff) return;
|
|
851
|
+
|
|
829
852
|
try {
|
|
830
853
|
await this.client.sessionRefreshDiff(session.id);
|
|
831
854
|
} catch (cause) {
|
|
@@ -839,6 +862,7 @@ class Controller implements ReviewController {
|
|
|
839
862
|
private async refreshSession(id: string): Promise<void> {
|
|
840
863
|
try {
|
|
841
864
|
if (!this.client) return;
|
|
865
|
+
|
|
842
866
|
const session = await this.client.sessionGet(id);
|
|
843
867
|
|
|
844
868
|
// a rapid switch may have moved on during the fetch; never clobber the now-viewed thread
|
|
@@ -898,11 +922,13 @@ class Controller implements ReviewController {
|
|
|
898
922
|
// copy trails the daemon for a live diff, whose session.updated refreshes the open
|
|
899
923
|
// thread but not the list, so a returned-to diff would otherwise show stale content
|
|
900
924
|
if (cached) this.update({ session: cached });
|
|
925
|
+
|
|
901
926
|
void this.refreshSession(id);
|
|
902
927
|
}
|
|
903
928
|
|
|
904
929
|
async createEmptyThread(): Promise<void> {
|
|
905
930
|
if (this.readOnly || !this.client?.sessionCreate) return;
|
|
931
|
+
|
|
906
932
|
const workspace = this.snapshot.session?.workspace ?? {
|
|
907
933
|
repoRoot: this.options.cwd ?? process.cwd(),
|
|
908
934
|
branch: "detached",
|
|
@@ -927,6 +953,7 @@ class Controller implements ReviewController {
|
|
|
927
953
|
|
|
928
954
|
deleteSession(id: string): void {
|
|
929
955
|
if (this.readOnly) return this.setStatus("observer - read-only");
|
|
956
|
+
|
|
930
957
|
// a deleted thread's links must not outlive it; revoke every one first, best-effort, so an
|
|
931
958
|
// unreachable gateway never blocks the local delete (the blob's 30-day TTL is the backstop)
|
|
932
959
|
for (const link of this.linksFor(this.sessionRecord(id)))
|
|
@@ -952,6 +979,7 @@ class Controller implements ReviewController {
|
|
|
952
979
|
|
|
953
980
|
renameSession(id: string, title: string): void {
|
|
954
981
|
if (this.readOnly) return this.setStatus("observer - read-only");
|
|
982
|
+
|
|
955
983
|
void this.client
|
|
956
984
|
?.sessionSetTitle(id, title)
|
|
957
985
|
.catch((cause: unknown) =>
|
|
@@ -963,6 +991,7 @@ class Controller implements ReviewController {
|
|
|
963
991
|
const session = this.snapshot.session;
|
|
964
992
|
|
|
965
993
|
if (!session) return;
|
|
994
|
+
|
|
966
995
|
this.apply(this.client!.sessionSetSelfName(session.id, name));
|
|
967
996
|
}
|
|
968
997
|
|
|
@@ -970,9 +999,11 @@ class Controller implements ReviewController {
|
|
|
970
999
|
const session = this.snapshot.session;
|
|
971
1000
|
|
|
972
1001
|
if (!session || session.status === "resolved") return;
|
|
1002
|
+
|
|
973
1003
|
const block = this.display()[displayIndex];
|
|
974
1004
|
|
|
975
1005
|
if (!block) return;
|
|
1006
|
+
|
|
976
1007
|
const working = this.working();
|
|
977
1008
|
|
|
978
1009
|
if (start !== undefined && end !== undefined && block.work) {
|
|
@@ -980,6 +1011,7 @@ class Controller implements ReviewController {
|
|
|
980
1011
|
const endBlock = display[endDisplayIndex];
|
|
981
1012
|
|
|
982
1013
|
if (!endBlock?.work) return;
|
|
1014
|
+
|
|
983
1015
|
const exactSourceRange = this.exactCutSourceRange(
|
|
984
1016
|
session,
|
|
985
1017
|
display,
|
|
@@ -1007,6 +1039,7 @@ class Controller implements ReviewController {
|
|
|
1007
1039
|
const content = applyTextCuts(session.artifact.content, textCuts);
|
|
1008
1040
|
|
|
1009
1041
|
if (content === working) return;
|
|
1042
|
+
|
|
1010
1043
|
const expected: Thread = { ...session, workingCopy: content, textCuts };
|
|
1011
1044
|
|
|
1012
1045
|
if (textCuts.length === 0) {
|
|
@@ -1021,10 +1054,12 @@ class Controller implements ReviewController {
|
|
|
1021
1054
|
|
|
1022
1055
|
return;
|
|
1023
1056
|
}
|
|
1057
|
+
|
|
1024
1058
|
const range = renderedSpanToWork(display, displayIndex, endDisplayIndex, start, end);
|
|
1025
1059
|
const content = cutTextRange(working, block.work, range.start, endBlock.work, range.end);
|
|
1026
1060
|
|
|
1027
1061
|
if (content === working) return;
|
|
1062
|
+
|
|
1028
1063
|
this.setWorkingCopy(content);
|
|
1029
1064
|
} else if (block.type === "del") {
|
|
1030
1065
|
this.restoreDelBlock(block, displayIndex);
|
|
@@ -1035,6 +1070,7 @@ class Controller implements ReviewController {
|
|
|
1035
1070
|
|
|
1036
1071
|
private cutWholeBlock(session: Thread, block: DisplayBlock, working: string): void {
|
|
1037
1072
|
if (!block.work) return;
|
|
1073
|
+
|
|
1038
1074
|
const base = session.artifact.content;
|
|
1039
1075
|
const existingCuts = session.textCuts ?? [];
|
|
1040
1076
|
|
|
@@ -1044,6 +1080,7 @@ class Controller implements ReviewController {
|
|
|
1044
1080
|
const content = applyTextCuts(base, textCuts);
|
|
1045
1081
|
|
|
1046
1082
|
if (content === working) return;
|
|
1083
|
+
|
|
1047
1084
|
this.applyOptimistic(
|
|
1048
1085
|
{ ...session, workingCopy: content, textCuts },
|
|
1049
1086
|
this.client!.sessionSetWorkingCopy(session.id, content, textCuts),
|
|
@@ -1051,11 +1088,13 @@ class Controller implements ReviewController {
|
|
|
1051
1088
|
|
|
1052
1089
|
return;
|
|
1053
1090
|
}
|
|
1091
|
+
|
|
1054
1092
|
const workIndex = parseBlocks(working).findIndex(
|
|
1055
1093
|
(candidate) => candidate.lineStart === block.work!.lineStart,
|
|
1056
1094
|
);
|
|
1057
1095
|
|
|
1058
1096
|
if (workIndex === -1) return;
|
|
1097
|
+
|
|
1059
1098
|
this.applyOptimistic(
|
|
1060
1099
|
{ ...session, workingCopy: cutBlock(working, block.work) },
|
|
1061
1100
|
this.client!.sessionCutBlock(session.id, workIndex),
|
|
@@ -1074,10 +1113,12 @@ class Controller implements ReviewController {
|
|
|
1074
1113
|
const existingCuts = session.textCuts ?? [];
|
|
1075
1114
|
|
|
1076
1115
|
if ((session.workingCopy ?? base) !== applyTextCuts(base, existingCuts)) return null;
|
|
1116
|
+
|
|
1077
1117
|
const startBlock = display[displayIndex];
|
|
1078
1118
|
const endBlock = display[endDisplayIndex];
|
|
1079
1119
|
|
|
1080
1120
|
if (!startBlock?.base || !endBlock?.base) return null;
|
|
1121
|
+
|
|
1081
1122
|
if (existingCuts.length === 0) {
|
|
1082
1123
|
const workRange = renderedSpanToWork(
|
|
1083
1124
|
display,
|
|
@@ -1093,6 +1134,7 @@ class Controller implements ReviewController {
|
|
|
1093
1134
|
? null
|
|
1094
1135
|
: { start: sourceStart, end: sourceEnd };
|
|
1095
1136
|
}
|
|
1137
|
+
|
|
1096
1138
|
const startRange = baseRangeForRendered(
|
|
1097
1139
|
blockRuns(startBlock, true),
|
|
1098
1140
|
renderedStart,
|
|
@@ -1118,6 +1160,7 @@ class Controller implements ReviewController {
|
|
|
1118
1160
|
const session = this.snapshot.session;
|
|
1119
1161
|
|
|
1120
1162
|
if (!session || !block.base) return;
|
|
1163
|
+
|
|
1121
1164
|
const working = this.working();
|
|
1122
1165
|
const line = restoreLine(
|
|
1123
1166
|
nextWorkBlock(this.display(), displayIndex),
|
|
@@ -1129,12 +1172,14 @@ class Controller implements ReviewController {
|
|
|
1129
1172
|
);
|
|
1130
1173
|
|
|
1131
1174
|
if (baseIndex === -1) return;
|
|
1175
|
+
|
|
1132
1176
|
const expected: Thread = {
|
|
1133
1177
|
...session,
|
|
1134
1178
|
workingCopy: restoreBlock(base, working, block.base, line),
|
|
1135
1179
|
};
|
|
1136
1180
|
|
|
1137
1181
|
if (expected.workingCopy === undefined) delete expected.workingCopy;
|
|
1182
|
+
|
|
1138
1183
|
this.applyOptimistic(expected, this.client!.sessionRestoreBlock(session.id, baseIndex, line));
|
|
1139
1184
|
}
|
|
1140
1185
|
|
|
@@ -1144,11 +1189,13 @@ class Controller implements ReviewController {
|
|
|
1144
1189
|
const session = this.snapshot.session;
|
|
1145
1190
|
|
|
1146
1191
|
if (!session || session.status === "resolved") return null;
|
|
1192
|
+
|
|
1147
1193
|
if (!session.artifact.files?.length) {
|
|
1148
1194
|
this.setStatus("hunk curation needs full file contents (PR diffs cannot be curated)");
|
|
1149
1195
|
|
|
1150
1196
|
return null;
|
|
1151
1197
|
}
|
|
1198
|
+
|
|
1152
1199
|
const row = this.rows()[rowIndex];
|
|
1153
1200
|
|
|
1154
1201
|
if (!row || row.kind === "file" || row.kind === "hunk") {
|
|
@@ -1156,6 +1203,7 @@ class Controller implements ReviewController {
|
|
|
1156
1203
|
|
|
1157
1204
|
return null;
|
|
1158
1205
|
}
|
|
1206
|
+
|
|
1159
1207
|
const model = this.fileModel(row.file);
|
|
1160
1208
|
|
|
1161
1209
|
if (!model) return null;
|
|
@@ -1167,9 +1215,11 @@ class Controller implements ReviewController {
|
|
|
1167
1215
|
const located = this.curationRow(rowIndex);
|
|
1168
1216
|
|
|
1169
1217
|
if (!located) return;
|
|
1218
|
+
|
|
1170
1219
|
const target = hunkRejectionForRow(located.row.file, located.model, located.row);
|
|
1171
1220
|
|
|
1172
1221
|
if (!target) return this.setStatus("no hunk under the cursor");
|
|
1222
|
+
|
|
1173
1223
|
const wholeHunk = (rejection: HunkRejection): boolean => rejectsWholeHunk(rejection, target);
|
|
1174
1224
|
|
|
1175
1225
|
if (this.rejections.some(wholeHunk)) {
|
|
@@ -1189,14 +1239,18 @@ class Controller implements ReviewController {
|
|
|
1189
1239
|
const located = this.curationRow(rowIndex);
|
|
1190
1240
|
|
|
1191
1241
|
if (!located) return;
|
|
1242
|
+
|
|
1192
1243
|
if (located.row.kind !== "add" && located.row.kind !== "del")
|
|
1193
1244
|
return this.setStatus("move to a changed line to reject a change");
|
|
1245
|
+
|
|
1194
1246
|
const target = changeRejectionForRow(located.row.file, located.model, located.row);
|
|
1195
1247
|
|
|
1196
1248
|
if (!target) return this.setStatus("no change under the cursor");
|
|
1249
|
+
|
|
1197
1250
|
const wholeCovers = this.rejections.some((rejection) => rejectsWholeHunk(rejection, target));
|
|
1198
1251
|
|
|
1199
1252
|
if (wholeCovers) return this.setStatus("the whole hunk is rejected - restore it first");
|
|
1253
|
+
|
|
1200
1254
|
if (this.rejections.some((rejection) => sameRejection(rejection, target))) {
|
|
1201
1255
|
this.curate(this.rejections.filter((rejection) => !sameRejection(rejection, target)));
|
|
1202
1256
|
} else {
|
|
@@ -1209,15 +1263,19 @@ class Controller implements ReviewController {
|
|
|
1209
1263
|
const session = this.snapshot.session;
|
|
1210
1264
|
|
|
1211
1265
|
if (!session) return;
|
|
1266
|
+
|
|
1212
1267
|
const expected: Thread = { ...session, curation: rejections };
|
|
1213
1268
|
|
|
1214
1269
|
if (!rejections.length) delete expected.curation;
|
|
1270
|
+
|
|
1215
1271
|
this.applyOptimistic(expected, this.client!.sessionCurate(session.id, rejections));
|
|
1216
1272
|
}
|
|
1217
1273
|
|
|
1218
1274
|
rejectedRows(): Set<number> {
|
|
1219
1275
|
this.ensureDerived();
|
|
1276
|
+
|
|
1220
1277
|
if (!this.rejections.length) return EMPTY_REJECTED_ROWS;
|
|
1278
|
+
|
|
1221
1279
|
const rejected = new Set<number>();
|
|
1222
1280
|
|
|
1223
1281
|
this.derived.rows.forEach((row, index) => {
|
|
@@ -1244,10 +1302,12 @@ class Controller implements ReviewController {
|
|
|
1244
1302
|
const kept = this.rejections.filter((rejection) => curationItemId(rejection) !== id);
|
|
1245
1303
|
|
|
1246
1304
|
if (kept.length === this.rejections.length) return;
|
|
1305
|
+
|
|
1247
1306
|
this.curate(kept);
|
|
1248
1307
|
|
|
1249
1308
|
return;
|
|
1250
1309
|
}
|
|
1310
|
+
|
|
1251
1311
|
const display = this.display();
|
|
1252
1312
|
const displayIndex = display.findIndex(
|
|
1253
1313
|
(block) => block.type === "del" && block.base && planCutId(block.base) === id,
|
|
@@ -1259,6 +1319,7 @@ class Controller implements ReviewController {
|
|
|
1259
1319
|
/** The curated-out diff rejections as removal cards (newest decisions last). */
|
|
1260
1320
|
private diffCurationItems(): CurationItem[] {
|
|
1261
1321
|
if (!this.rejections.length) return EMPTY_CURATION_ITEMS;
|
|
1322
|
+
|
|
1262
1323
|
const items: CurationItem[] = [];
|
|
1263
1324
|
|
|
1264
1325
|
for (const rejection of this.rejections) {
|
|
@@ -1284,6 +1345,7 @@ class Controller implements ReviewController {
|
|
|
1284
1345
|
|
|
1285
1346
|
this.derived.display.forEach((block, displayIndex) => {
|
|
1286
1347
|
if (block.type !== "del" || !block.base) return;
|
|
1348
|
+
|
|
1287
1349
|
items.push({
|
|
1288
1350
|
id: planCutId(block.base),
|
|
1289
1351
|
source: "plan",
|
|
@@ -1300,10 +1362,12 @@ class Controller implements ReviewController {
|
|
|
1300
1362
|
const model = this.fileModel(rejection.path);
|
|
1301
1363
|
|
|
1302
1364
|
if (!model) return [];
|
|
1365
|
+
|
|
1303
1366
|
const rows: DiffRow[] = [];
|
|
1304
1367
|
|
|
1305
1368
|
for (const row of this.derived.rows) {
|
|
1306
1369
|
if (row.file !== rejection.path || (row.kind !== "add" && row.kind !== "del")) continue;
|
|
1370
|
+
|
|
1307
1371
|
if (rejection.changeIndex === undefined) {
|
|
1308
1372
|
const target = hunkRejectionForRow(rejection.path, model, row);
|
|
1309
1373
|
|
|
@@ -1322,6 +1386,7 @@ class Controller implements ReviewController {
|
|
|
1322
1386
|
const session = this.snapshot.session;
|
|
1323
1387
|
|
|
1324
1388
|
if (!session || session.status === "resolved") return;
|
|
1389
|
+
|
|
1325
1390
|
try {
|
|
1326
1391
|
const result = editInEditor(this.working(), "plan.md", { editor: this.editor });
|
|
1327
1392
|
|
|
@@ -1336,7 +1401,9 @@ class Controller implements ReviewController {
|
|
|
1336
1401
|
const session = this.snapshot.session;
|
|
1337
1402
|
|
|
1338
1403
|
if (!session || session.status === "resolved") return;
|
|
1404
|
+
|
|
1339
1405
|
if (content === this.working()) return;
|
|
1406
|
+
|
|
1340
1407
|
this.setWorkingCopy(content);
|
|
1341
1408
|
this.reconcileAnnotations(session, content);
|
|
1342
1409
|
}
|
|
@@ -1368,6 +1435,7 @@ class Controller implements ReviewController {
|
|
|
1368
1435
|
const session = this.snapshot.session;
|
|
1369
1436
|
|
|
1370
1437
|
if (!session) return undefined;
|
|
1438
|
+
|
|
1371
1439
|
let anchor;
|
|
1372
1440
|
let resolvedTarget = target;
|
|
1373
1441
|
// a file target (a Changes-panel diff note) and a diff artifact both anchor into the diff rows
|
|
@@ -1414,6 +1482,7 @@ class Controller implements ReviewController {
|
|
|
1414
1482
|
workIndexOf(endDisplayIndex),
|
|
1415
1483
|
);
|
|
1416
1484
|
}
|
|
1485
|
+
|
|
1417
1486
|
// absent target means the reviewed artifact, so keep artifact notes free of the field
|
|
1418
1487
|
const wire =
|
|
1419
1488
|
resolvedTarget.kind === "artifact"
|
|
@@ -1431,6 +1500,7 @@ class Controller implements ReviewController {
|
|
|
1431
1500
|
const session = this.snapshot.session;
|
|
1432
1501
|
|
|
1433
1502
|
if (!session) return undefined;
|
|
1503
|
+
|
|
1434
1504
|
const wire =
|
|
1435
1505
|
target.kind === "artifact"
|
|
1436
1506
|
? { id: newAnnotationId(), kind: "comment", anchor, body }
|
|
@@ -1446,7 +1516,9 @@ class Controller implements ReviewController {
|
|
|
1446
1516
|
/** Find-or-create the per-repo workbench thread and adopt it as active; true once a session exists. */
|
|
1447
1517
|
private async ensureWorkbenchSession(): Promise<boolean> {
|
|
1448
1518
|
if (this.snapshot.session) return true;
|
|
1519
|
+
|
|
1449
1520
|
if (this.client?.sessionWorkbench === undefined) return false;
|
|
1521
|
+
|
|
1450
1522
|
let workbench;
|
|
1451
1523
|
|
|
1452
1524
|
try {
|
|
@@ -1466,6 +1538,7 @@ class Controller implements ReviewController {
|
|
|
1466
1538
|
|
|
1467
1539
|
async commentOnWorkbench(anchor: Anchor, target: AnnotationTarget, body: string): Promise<void> {
|
|
1468
1540
|
if (!(await this.ensureWorkbenchSession())) return;
|
|
1541
|
+
|
|
1469
1542
|
this.addComment(anchor, target, body);
|
|
1470
1543
|
}
|
|
1471
1544
|
|
|
@@ -1478,6 +1551,7 @@ class Controller implements ReviewController {
|
|
|
1478
1551
|
body: string,
|
|
1479
1552
|
): Promise<void> {
|
|
1480
1553
|
if (!(await this.ensureWorkbenchSession())) return;
|
|
1554
|
+
|
|
1481
1555
|
// annotate re-derives the file path and rev from the diff row it lands on
|
|
1482
1556
|
this.annotate("comment", displayIndex, start, end, body, endDisplayIndex, {
|
|
1483
1557
|
kind: "file",
|
|
@@ -1491,6 +1565,7 @@ class Controller implements ReviewController {
|
|
|
1491
1565
|
const root = session?.annotations.find((annotation) => annotation.id === rootAnnotationId);
|
|
1492
1566
|
|
|
1493
1567
|
if (!session || !root) return undefined;
|
|
1568
|
+
|
|
1494
1569
|
// a reply to a reply still hangs off the discussion's root comment, and shares its target
|
|
1495
1570
|
// so it renders and resolves on the same surface
|
|
1496
1571
|
const base = {
|
|
@@ -1513,6 +1588,7 @@ class Controller implements ReviewController {
|
|
|
1513
1588
|
const session = this.snapshot.session;
|
|
1514
1589
|
|
|
1515
1590
|
if (!session) return undefined;
|
|
1591
|
+
|
|
1516
1592
|
const anchor = { quote, prefix: "", suffix: "", selector };
|
|
1517
1593
|
const wire = { id: newAnnotationId(), kind: "comment", anchor, body };
|
|
1518
1594
|
const persisted = this.client!.sessionComment(session.id, wire);
|
|
@@ -1527,9 +1603,11 @@ class Controller implements ReviewController {
|
|
|
1527
1603
|
const session = this.snapshot.session;
|
|
1528
1604
|
|
|
1529
1605
|
if (!session) return;
|
|
1606
|
+
|
|
1530
1607
|
const existing = session.annotations.find((annotation) => annotation.id === id);
|
|
1531
1608
|
|
|
1532
1609
|
if (!existing) return;
|
|
1610
|
+
|
|
1533
1611
|
// the daemon's annotate primitive upserts by id: same id, anchor, and
|
|
1534
1612
|
// reply link, new body
|
|
1535
1613
|
const wire: Omit<Annotation, "createdAt"> = {
|
|
@@ -1540,6 +1618,7 @@ class Controller implements ReviewController {
|
|
|
1540
1618
|
};
|
|
1541
1619
|
|
|
1542
1620
|
if (existing.replyTo !== undefined) wire.replyTo = existing.replyTo;
|
|
1621
|
+
|
|
1543
1622
|
const persisted = this.client!.sessionComment(session.id, wire);
|
|
1544
1623
|
|
|
1545
1624
|
this.applyOptimistic(withAnnotationUpserted(session, wire), persisted);
|
|
@@ -1559,6 +1638,7 @@ class Controller implements ReviewController {
|
|
|
1559
1638
|
);
|
|
1560
1639
|
|
|
1561
1640
|
if (links.length === 0) return;
|
|
1641
|
+
|
|
1562
1642
|
void persisted
|
|
1563
1643
|
.then(() => Promise.all(links.map((link) => this.shareTransport.push(link.id, [annotation]))))
|
|
1564
1644
|
.catch(() => {});
|
|
@@ -1568,6 +1648,7 @@ class Controller implements ReviewController {
|
|
|
1568
1648
|
const session = this.snapshot.session;
|
|
1569
1649
|
|
|
1570
1650
|
if (!session) return;
|
|
1651
|
+
|
|
1571
1652
|
this.apply(this.client!.sessionRemoveAnnotation(session.id, id));
|
|
1572
1653
|
}
|
|
1573
1654
|
|
|
@@ -1575,6 +1656,7 @@ class Controller implements ReviewController {
|
|
|
1575
1656
|
const session = this.snapshot.session;
|
|
1576
1657
|
|
|
1577
1658
|
if (!session) return;
|
|
1659
|
+
|
|
1578
1660
|
this.apply(this.client!.sessionSetWorkingCopy(session.id, content));
|
|
1579
1661
|
}
|
|
1580
1662
|
|
|
@@ -1591,9 +1673,11 @@ class Controller implements ReviewController {
|
|
|
1591
1673
|
const session = this.snapshot.session;
|
|
1592
1674
|
|
|
1593
1675
|
if (!session || session.artifact.type !== "diff") return;
|
|
1676
|
+
|
|
1594
1677
|
const files = this.files();
|
|
1595
1678
|
|
|
1596
1679
|
if (files.length === 0) return this.setStatus("nothing to walk - the diff is empty");
|
|
1680
|
+
|
|
1597
1681
|
// resume at the first unviewed file; a finished walk reopens on the end card
|
|
1598
1682
|
this.update({ walk: { index: firstUnviewedIndex(files, this.viewedSet()) } });
|
|
1599
1683
|
}
|
|
@@ -1603,16 +1687,19 @@ class Controller implements ReviewController {
|
|
|
1603
1687
|
const session = this.snapshot.session;
|
|
1604
1688
|
|
|
1605
1689
|
if (!walk || !session) return;
|
|
1690
|
+
|
|
1606
1691
|
const files = this.files();
|
|
1607
1692
|
const current = files[walk.index];
|
|
1608
1693
|
|
|
1609
1694
|
if (!current) return; // already on the end card
|
|
1695
|
+
|
|
1610
1696
|
// advancing IS the viewed mark: the step is complete once you move past
|
|
1611
1697
|
// it; the daemon primitive merges, so only the new path travels
|
|
1612
1698
|
if (!this.viewedSet().has(current.path)) {
|
|
1613
1699
|
this.locallyViewed.add(current.path);
|
|
1614
1700
|
this.apply(this.client!.sessionSetViewed(session.id, [current.path]));
|
|
1615
1701
|
}
|
|
1702
|
+
|
|
1616
1703
|
this.update({ walk: { index: walk.index + 1 } });
|
|
1617
1704
|
}
|
|
1618
1705
|
|
|
@@ -1620,6 +1707,7 @@ class Controller implements ReviewController {
|
|
|
1620
1707
|
const walk = this.snapshot.walk;
|
|
1621
1708
|
|
|
1622
1709
|
if (!walk) return;
|
|
1710
|
+
|
|
1623
1711
|
this.update({ walk: { index: Math.max(0, walk.index - 1) } });
|
|
1624
1712
|
}
|
|
1625
1713
|
|
|
@@ -1631,6 +1719,7 @@ class Controller implements ReviewController {
|
|
|
1631
1719
|
const session = this.snapshot.session;
|
|
1632
1720
|
|
|
1633
1721
|
if (!session) return;
|
|
1722
|
+
|
|
1634
1723
|
this.walkLeave();
|
|
1635
1724
|
const actionBodies = Object.fromEntries(
|
|
1636
1725
|
slashItemsFrom(this.quickActions).map((item) => [item.name, item.body]),
|
|
@@ -1641,14 +1730,17 @@ class Controller implements ReviewController {
|
|
|
1641
1730
|
// The completion overlay heading already states the message, so the
|
|
1642
1731
|
// status line stays empty here - only export/error messages fill it.
|
|
1643
1732
|
this.update({ session: resolved, status: "" });
|
|
1733
|
+
|
|
1644
1734
|
if (message === "comment") {
|
|
1645
1735
|
this.setStatus("comment sent - thread stays open");
|
|
1646
1736
|
|
|
1647
1737
|
return;
|
|
1648
1738
|
}
|
|
1739
|
+
|
|
1649
1740
|
// notes-vault export: guarded by each exporter's policy (default manual = no-op)
|
|
1650
1741
|
for (const exporter of this.exporters) {
|
|
1651
1742
|
if (!exporter.runsOn(message)) continue;
|
|
1743
|
+
|
|
1652
1744
|
void exporter.run(resolved).then((exportResult) => {
|
|
1653
1745
|
this.setStatus(
|
|
1654
1746
|
exportResult.success && exportResult.path
|
|
@@ -1690,9 +1782,11 @@ class Controller implements ReviewController {
|
|
|
1690
1782
|
|
|
1691
1783
|
createShareLink(input: NewShareLink): void {
|
|
1692
1784
|
if (this.readOnly) return this.setStatus("observer - read-only");
|
|
1785
|
+
|
|
1693
1786
|
const session = this.snapshot.session;
|
|
1694
1787
|
|
|
1695
1788
|
if (!session || !this.client) return;
|
|
1789
|
+
|
|
1696
1790
|
const client = this.client;
|
|
1697
1791
|
const shareBranch = session.history?.branch ?? MAIN_BRANCH;
|
|
1698
1792
|
|
|
@@ -1714,6 +1808,7 @@ class Controller implements ReviewController {
|
|
|
1714
1808
|
const id = this.shareTransport.parseShareId(line);
|
|
1715
1809
|
|
|
1716
1810
|
if (!id) throw new Error("gateway returned no share id");
|
|
1811
|
+
|
|
1717
1812
|
const link: ShareLink = {
|
|
1718
1813
|
id,
|
|
1719
1814
|
name: input.name?.trim() || undefined,
|
|
@@ -1723,6 +1818,7 @@ class Controller implements ReviewController {
|
|
|
1723
1818
|
};
|
|
1724
1819
|
|
|
1725
1820
|
if (input.agentEnabled !== undefined) link.agentEnabled = input.agentEnabled;
|
|
1821
|
+
|
|
1726
1822
|
await client.sessionSetShares(session.id, [...this.linksFor(this.snapshot.session), link]);
|
|
1727
1823
|
this.setStatus("");
|
|
1728
1824
|
this.showToast(line, copied ? "link copied" : "link created");
|
|
@@ -1734,21 +1830,26 @@ class Controller implements ReviewController {
|
|
|
1734
1830
|
|
|
1735
1831
|
updateShareLink(id: string, input: NewShareLink): void {
|
|
1736
1832
|
if (this.readOnly) return this.setStatus("observer - read-only");
|
|
1833
|
+
|
|
1737
1834
|
const session = this.snapshot.session;
|
|
1738
1835
|
|
|
1739
1836
|
if (!session || !this.client) return;
|
|
1837
|
+
|
|
1740
1838
|
const links = this.linksFor(session);
|
|
1741
1839
|
|
|
1742
1840
|
if (!links.some((link) => link.id === id)) return;
|
|
1841
|
+
|
|
1743
1842
|
const allowlist = input.requireAuth ? input.allowlist : [];
|
|
1744
1843
|
|
|
1745
1844
|
const policy: SharePolicy = { requireAuth: input.requireAuth, allowlist };
|
|
1746
1845
|
|
|
1747
1846
|
if (input.agentEnabled !== undefined) policy.agentEnabled = input.agentEnabled;
|
|
1847
|
+
|
|
1748
1848
|
// Publish the same policy stored on the local link.
|
|
1749
1849
|
void this.shareTransport.push(id, [], policy).catch(() => {});
|
|
1750
1850
|
const next = links.map((link) => {
|
|
1751
1851
|
if (link.id !== id) return link;
|
|
1852
|
+
|
|
1752
1853
|
const updated = {
|
|
1753
1854
|
...link,
|
|
1754
1855
|
name: input.name?.trim() || undefined,
|
|
@@ -1769,9 +1870,11 @@ class Controller implements ReviewController {
|
|
|
1769
1870
|
|
|
1770
1871
|
deleteShareLink(id: string): void {
|
|
1771
1872
|
if (this.readOnly) return this.setStatus("observer - read-only");
|
|
1873
|
+
|
|
1772
1874
|
const session = this.snapshot.session;
|
|
1773
1875
|
|
|
1774
1876
|
if (!session || !this.client) return;
|
|
1877
|
+
|
|
1775
1878
|
const remaining = this.linksFor(session).filter((link) => link.id !== id);
|
|
1776
1879
|
|
|
1777
1880
|
void this.shareTransport.revoke(id).catch(() => {});
|
|
@@ -1792,10 +1895,12 @@ class Controller implements ReviewController {
|
|
|
1792
1895
|
/** Stop sharing the open thread: revoke every link so none of them resolve. */
|
|
1793
1896
|
unshare(): void {
|
|
1794
1897
|
if (this.readOnly) return this.setStatus("observer - read-only");
|
|
1898
|
+
|
|
1795
1899
|
const session = this.snapshot.session;
|
|
1796
1900
|
const links = this.linksFor(session);
|
|
1797
1901
|
|
|
1798
1902
|
if (!session || links.length === 0) return this.setStatus("this thread is not shared");
|
|
1903
|
+
|
|
1799
1904
|
this.stopShareSync();
|
|
1800
1905
|
void Promise.all(links.map((link) => this.shareTransport.revoke(link.id).catch(() => {})));
|
|
1801
1906
|
void this.client?.sessionSetShares(session.id, []).catch(() => {});
|
|
@@ -1805,10 +1910,13 @@ class Controller implements ReviewController {
|
|
|
1805
1910
|
const session = this.snapshot.session;
|
|
1806
1911
|
|
|
1807
1912
|
if (!session?.history || !this.client) return;
|
|
1913
|
+
|
|
1808
1914
|
const target = entryTarget(session.history, entryId);
|
|
1809
1915
|
|
|
1810
1916
|
if (target === null) return this.setStatus("that entry is not on any branch");
|
|
1917
|
+
|
|
1811
1918
|
if (target.kind === "here") return this.setStatus("already at the tip");
|
|
1919
|
+
|
|
1812
1920
|
if (target.kind === "switch") {
|
|
1813
1921
|
this.moveTree(session, switchBranch(session.history, target.branch), () =>
|
|
1814
1922
|
this.client!.sessionSwitch(session.id, target.branch),
|
|
@@ -1816,6 +1924,7 @@ class Controller implements ReviewController {
|
|
|
1816
1924
|
|
|
1817
1925
|
return;
|
|
1818
1926
|
}
|
|
1927
|
+
|
|
1819
1928
|
// an entry another branch reaches: the daemon stands on that branch first, in the same request
|
|
1820
1929
|
const onBranch = switchBranch(session.history, target.branch);
|
|
1821
1930
|
const options = summary === undefined || summary === "" ? {} : { summary };
|
|
@@ -1832,9 +1941,12 @@ class Controller implements ReviewController {
|
|
|
1832
1941
|
const branchName = name.trim();
|
|
1833
1942
|
|
|
1834
1943
|
if (!session?.history || !this.client) return;
|
|
1944
|
+
|
|
1835
1945
|
if (!branchName) return this.setStatus("a branch needs a name");
|
|
1946
|
+
|
|
1836
1947
|
if (session.history.tips[branchName] !== undefined)
|
|
1837
1948
|
return this.setStatus(`branch ${branchName} exists`);
|
|
1949
|
+
|
|
1838
1950
|
this.moveTree(session, createBranch(session.history, branchName), () =>
|
|
1839
1951
|
this.client!.sessionBranch(session.id, branchName),
|
|
1840
1952
|
);
|
|
@@ -1845,7 +1957,9 @@ class Controller implements ReviewController {
|
|
|
1845
1957
|
const name = label.trim();
|
|
1846
1958
|
|
|
1847
1959
|
if (!session?.history || !this.client) return;
|
|
1960
|
+
|
|
1848
1961
|
if (!name) return this.setStatus("a checkpoint needs a name");
|
|
1962
|
+
|
|
1849
1963
|
this.moveTree(session, labelTip(session.history, name), () =>
|
|
1850
1964
|
this.client!.sessionLabel(session.id, name),
|
|
1851
1965
|
);
|
|
@@ -1855,6 +1969,7 @@ class Controller implements ReviewController {
|
|
|
1855
1969
|
const session = this.snapshot.session;
|
|
1856
1970
|
|
|
1857
1971
|
if (!session || !this.client) return;
|
|
1972
|
+
|
|
1858
1973
|
this.client
|
|
1859
1974
|
.sessionFork(session.id)
|
|
1860
1975
|
.then((fork) => {
|
|
@@ -1869,6 +1984,7 @@ class Controller implements ReviewController {
|
|
|
1869
1984
|
const session = this.snapshot.session;
|
|
1870
1985
|
|
|
1871
1986
|
if (!session || !this.client) return;
|
|
1987
|
+
|
|
1872
1988
|
const client = this.client;
|
|
1873
1989
|
|
|
1874
1990
|
this.setStatus("forking and sharing…");
|
|
@@ -1888,6 +2004,7 @@ class Controller implements ReviewController {
|
|
|
1888
2004
|
await client.sessionSetShares(fork.id, [
|
|
1889
2005
|
{ id, requireAuth: false, allowlist: [], shareBranch },
|
|
1890
2006
|
]);
|
|
2007
|
+
|
|
1891
2008
|
this.setStatus("");
|
|
1892
2009
|
this.showToast(line, copied ? "fork shared - link copied" : "fork shared");
|
|
1893
2010
|
})
|
|
@@ -1962,6 +2079,7 @@ class Controller implements ReviewController {
|
|
|
1962
2079
|
const shareId = this.linksFor(this.snapshot.session)[0]?.id;
|
|
1963
2080
|
|
|
1964
2081
|
if (this.shareRun !== run || this.closed || !shareId) return;
|
|
2082
|
+
|
|
1965
2083
|
void this.pullShared();
|
|
1966
2084
|
this.shareStop = this.shareTransport.watch(shareId, {
|
|
1967
2085
|
onSession: (remote) => {
|
|
@@ -1970,6 +2088,7 @@ class Controller implements ReviewController {
|
|
|
1970
2088
|
},
|
|
1971
2089
|
onClose: () => {
|
|
1972
2090
|
if (this.shareRun !== run || this.closed) return;
|
|
2091
|
+
|
|
1973
2092
|
this.shareStop = null;
|
|
1974
2093
|
this.shareReconnect = this.clock.setTimeout(connect, delay);
|
|
1975
2094
|
delay = Math.min(delay * 2, SHARE_RECONNECT_MAX_MS);
|
|
@@ -1986,7 +2105,9 @@ class Controller implements ReviewController {
|
|
|
1986
2105
|
|
|
1987
2106
|
private stopShareSync(): void {
|
|
1988
2107
|
this.shareRun = null;
|
|
2108
|
+
|
|
1989
2109
|
if (this.shareReconnect !== undefined) this.clock.clearTimeout(this.shareReconnect);
|
|
2110
|
+
|
|
1990
2111
|
this.shareReconnect = undefined;
|
|
1991
2112
|
this.shareStop?.();
|
|
1992
2113
|
this.shareStop = null;
|
|
@@ -1995,6 +2116,7 @@ class Controller implements ReviewController {
|
|
|
1995
2116
|
// ── completion hand-back ────────────────────
|
|
1996
2117
|
private startCounting(remaining: number): void {
|
|
1997
2118
|
if (remaining <= 0) return this.finishReview();
|
|
2119
|
+
|
|
1998
2120
|
this.update({ completion: { phase: "counting", remaining } });
|
|
1999
2121
|
this.countdown = this.clock.setTimeout(() => this.startCounting(remaining - 1), 1000);
|
|
2000
2122
|
}
|
|
@@ -2005,6 +2127,7 @@ class Controller implements ReviewController {
|
|
|
2005
2127
|
const pane = returnPaneFor(this.snapshot.session?.artifact.meta.herdrPane);
|
|
2006
2128
|
|
|
2007
2129
|
if (herdr && pane) focusHerdrPane(herdr.binPath, pane);
|
|
2130
|
+
|
|
2008
2131
|
this.options.onExit?.(0);
|
|
2009
2132
|
}
|
|
2010
2133
|
|