@cueloop/client 0.1.0-alpha.63 → 0.1.0-alpha.65

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.
Files changed (68) hide show
  1. package/package.json +7 -6
  2. package/src/App.autoclose.test.tsx +2 -2
  3. package/src/App.export.test.tsx +2 -2
  4. package/src/App.plan-review.test.tsx +76 -247
  5. package/src/App.readonly.test.tsx +80 -35
  6. package/src/App.rendering.test.tsx +5 -6
  7. package/src/App.review-panel.test.tsx +29 -32
  8. package/src/App.submit-confirm.test.tsx +27 -12
  9. package/src/App.test.tsx +73 -49
  10. package/src/App.toast.test.tsx +34 -22
  11. package/src/App.tree.test.tsx +224 -0
  12. package/src/App.tsx +134 -87
  13. package/src/app-screens.tsx +27 -1
  14. package/src/app-view-model.ts +7 -109
  15. package/src/components/AnnotationCard.tsx +2 -2
  16. package/src/components/DiffSheet.scroll.test.tsx +5 -2
  17. package/src/components/DiffSheet.tsx +1 -6
  18. package/src/components/KeybindsDialog.tsx +61 -17
  19. package/src/components/PrototypeSheet.tsx +5 -9
  20. package/src/components/ReviewPanel.stories.tsx +14 -0
  21. package/src/components/ReviewRail.stories.tsx +14 -0
  22. package/src/components/ReviewRail.tsx +15 -6
  23. package/src/components/ThreadView.selection.test.tsx +812 -0
  24. package/src/components/ThreadView.stories.tsx +26 -0
  25. package/src/components/ThreadView.tsx +1620 -0
  26. package/src/components/TreePane.stories.tsx +106 -0
  27. package/src/components/TreePane.tsx +156 -0
  28. package/src/components/__snapshots__/stories.test.tsx.snap +120 -172
  29. package/src/components/primitives/Tabs.tsx +1 -1
  30. package/src/components/story-fixtures.ts +6 -2
  31. package/src/components/story.ts +16 -4
  32. package/src/components/syntax-highlight.ts +10 -2
  33. package/src/components/terminal-pane.test.tsx +1 -1
  34. package/src/components/terminal-pane.ts +7 -1
  35. package/src/config.ts +112 -99
  36. package/src/diff-hunk-curate.ts +4 -104
  37. package/src/editor.ts +5 -1
  38. package/src/ghostty-terminal.test.ts +1 -1
  39. package/src/herdr.ts +10 -2
  40. package/src/index.ts +1 -0
  41. package/src/intent-dispatch.test.ts +235 -55
  42. package/src/intent-dispatch.ts +104 -34
  43. package/src/key-bindings.test.ts +1 -1
  44. package/src/key-bindings.ts +16 -7
  45. package/src/keymap.test.ts +9 -11
  46. package/src/keymap.ts +14 -7
  47. package/src/kitty-image.test.ts +3 -3
  48. package/src/prototype-browser.ts +7 -6
  49. package/src/serve.ts +1 -1
  50. package/src/session-controller.curate.test.ts +106 -4
  51. package/src/session-controller.share.test.ts +220 -70
  52. package/src/session-controller.tree.test.ts +228 -0
  53. package/src/session-controller.ts +370 -94
  54. package/src/share.ts +117 -17
  55. package/src/test-support.test.ts +193 -1
  56. package/src/test-support.ts +69 -1
  57. package/src/theme-presets.ts +9 -2
  58. package/src/thread-chords.test.ts +154 -0
  59. package/src/thread-chords.ts +144 -0
  60. package/src/thread-selection.test.ts +210 -0
  61. package/src/thread-selection.ts +243 -0
  62. package/src/tree-view.test.ts +128 -0
  63. package/src/tree-view.ts +148 -0
  64. package/src/view-diff.ts +1 -4
  65. package/src/view-plan.ts +35 -19
  66. package/src/components/PlanSheet.stories.tsx +0 -113
  67. package/src/components/PlanSheet.tsx +0 -422
  68. package/src/components/plan-sheet-run-style.ts +0 -90
@@ -0,0 +1,106 @@
1
+ import React from "react";
2
+ import { DARK } from "../theme";
3
+ import type { Story, StoryMeta } from "./story";
4
+ import { TreePane } from "./TreePane";
5
+ import type { TreeRow } from "../tree-view";
6
+
7
+ export const meta: StoryMeta = { title: "TreePane" };
8
+
9
+ const callbacks = {
10
+ onSelect: () => {},
11
+ onGo: () => {},
12
+ onBranch: () => {},
13
+ onLabel: () => {},
14
+ onFork: () => {},
15
+ onForkAndShare: () => {},
16
+ };
17
+
18
+ /** rev1 -> comment (labelled, a branch hangs off it) -> the branch's comment -> rev2 as main's tip. */
19
+ const ROWS: TreeRow[] = [
20
+ {
21
+ entryId: "e1",
22
+ depth: 0,
23
+ glyph: "◉",
24
+ text: "revision 1",
25
+ onPath: true,
26
+ tips: [],
27
+ isCurrentTip: false,
28
+ },
29
+ {
30
+ entryId: "e2",
31
+ depth: 0,
32
+ glyph: "·",
33
+ text: "comment",
34
+ onPath: true,
35
+ tips: [],
36
+ isCurrentTip: false,
37
+ label: "start",
38
+ },
39
+ {
40
+ entryId: "e3",
41
+ depth: 1,
42
+ glyph: "·",
43
+ text: "comment",
44
+ onPath: false,
45
+ tips: ["alt"],
46
+ isCurrentTip: false,
47
+ },
48
+ {
49
+ entryId: "e4",
50
+ depth: 0,
51
+ glyph: "◉",
52
+ text: "revision 2",
53
+ onPath: true,
54
+ tips: ["main"],
55
+ isCurrentTip: true,
56
+ },
57
+ ];
58
+
59
+ /** The rail expects a height-constrained parent, like the app's main row. */
60
+ function PaneFrame({ children }: { children: React.ReactNode }): React.ReactNode {
61
+ return (
62
+ <box
63
+ style={{ flexDirection: "column", width: 40, height: "100%", backgroundColor: DARK.panel }}
64
+ >
65
+ {children}
66
+ </box>
67
+ );
68
+ }
69
+
70
+ export const OwnerWithBranch: Story = {
71
+ render: () => (
72
+ <PaneFrame>
73
+ <TreePane rows={ROWS} selectedEntryId="e2" canMove canFork {...callbacks} />
74
+ </PaneFrame>
75
+ ),
76
+ expectedColors: [DARK.accent],
77
+ size: { width: 40, height: 16 },
78
+ };
79
+
80
+ export const CollaboratorReadsOnly: Story = {
81
+ render: () => (
82
+ <PaneFrame>
83
+ <TreePane rows={ROWS} canMove={false} canFork={false} {...callbacks} />
84
+ </PaneFrame>
85
+ ),
86
+ size: { width: 40, height: 16 },
87
+ };
88
+
89
+ /** A resolved review: the owner can still fork, not move. */
90
+ export const ResolvedForkOnly: Story = {
91
+ render: () => (
92
+ <PaneFrame>
93
+ <TreePane rows={ROWS} canMove={false} canFork {...callbacks} />
94
+ </PaneFrame>
95
+ ),
96
+ size: { width: 40, height: 16 },
97
+ };
98
+
99
+ export const NoHistory: Story = {
100
+ render: () => (
101
+ <PaneFrame>
102
+ <TreePane rows={[]} canMove canFork {...callbacks} />
103
+ </PaneFrame>
104
+ ),
105
+ size: { width: 40, height: 8 },
106
+ };
@@ -0,0 +1,156 @@
1
+ /**
2
+ * The rail's Tree tab: the session's history as one row per entry. The active
3
+ * path reads bright, everything else dim; a branch tip carries its name, the
4
+ * current tip a marker, a checkpoint its label. Clicking a row selects it,
5
+ * clicking the selected row goes there; the buttons underneath are the mouse
6
+ * path to the same primitives the option chords reach.
7
+ */
8
+
9
+ import React, { useEffect, useRef } from "react";
10
+ import { createTextAttributes, type ScrollBoxRenderable } from "@opentui/core";
11
+ import type { Theme } from "../theme";
12
+ import type { TreeRow } from "../tree-view";
13
+ import { useComponentTheme } from "./theme-context";
14
+ import { Button } from "./primitives/Button";
15
+ import { Toolbar } from "./primitives/Toolbar";
16
+
17
+ export interface TreePaneProps {
18
+ rows: TreeRow[];
19
+ selectedEntryId?: string;
20
+ /** The owner of an open review may move the tree; everyone else reads it. */
21
+ canMove: boolean;
22
+ /** The owner may fork, a resolved review included. */
23
+ canFork: boolean;
24
+ onSelect: (entryId: string) => void;
25
+ onGo: (entryId: string) => void;
26
+ onBranch: () => void;
27
+ onLabel: () => void;
28
+ onFork: () => void;
29
+ onForkAndShare: () => void;
30
+ theme?: Theme;
31
+ }
32
+
33
+ const DIM = createTextAttributes({ dim: true });
34
+ const BOLD = createTextAttributes({ bold: true });
35
+
36
+ /** The marker column: the current tip stands out, every other row keeps its kind's glyph. */
37
+ function markerFor(row: TreeRow): string {
38
+ return row.isCurrentTip ? "●" : row.glyph;
39
+ }
40
+
41
+ /** What follows the entry's name: the branches whose tip it is, and its checkpoint. */
42
+ function trailerFor(row: TreeRow): string {
43
+ const parts: string[] = [];
44
+
45
+ if (row.tips.length) parts.push(`← ${row.tips.join(", ")}`);
46
+ if (row.label !== undefined) parts.push(`⚑ ${row.label}`);
47
+
48
+ return parts.length ? ` ${parts.join(" ")}` : "";
49
+ }
50
+
51
+ export function TreePane({
52
+ rows,
53
+ selectedEntryId,
54
+ canMove,
55
+ canFork,
56
+ onSelect,
57
+ onGo,
58
+ onBranch,
59
+ onLabel,
60
+ onFork,
61
+ onForkAndShare,
62
+ theme,
63
+ }: TreePaneProps): React.ReactNode {
64
+ const tokens = useComponentTheme(theme);
65
+ const scrollRef = useRef<ScrollBoxRenderable | null>(null);
66
+ const revealId = selectedEntryId ?? rows.find((row) => row.isCurrentTip)?.entryId;
67
+
68
+ // the trunk draws last, so a long history opens with its tip below the fold: keep the row in view
69
+ useEffect(() => {
70
+ if (revealId === undefined) return;
71
+ try {
72
+ scrollRef.current?.scrollChildIntoView(`tree-row-${revealId}`);
73
+ } catch {
74
+ // reveal is best-effort; the selection is already correct
75
+ }
76
+ }, [revealId]);
77
+
78
+ if (rows.length === 0)
79
+ return (
80
+ <box style={{ flexGrow: 1, alignItems: "center", justifyContent: "center" }}>
81
+ <text fg={tokens.textDim}>no history yet</text>
82
+ </box>
83
+ );
84
+
85
+ return (
86
+ <box style={{ flexGrow: 1, flexDirection: "column" }}>
87
+ <scrollbox ref={scrollRef} style={{ flexGrow: 1 }} focused={false}>
88
+ {rows.map((row) => {
89
+ const selected = row.entryId === selectedEntryId;
90
+ const foreground = row.onPath ? tokens.text : tokens.textDim;
91
+
92
+ return (
93
+ <box
94
+ key={row.entryId}
95
+ id={`tree-row-${row.entryId}`}
96
+ style={{
97
+ flexDirection: "row",
98
+ paddingLeft: 1 + row.depth * 2,
99
+ backgroundColor: selected ? tokens.elevated : undefined,
100
+ }}
101
+ onMouseUp={() => (selected && canMove ? onGo(row.entryId) : onSelect(row.entryId))}
102
+ >
103
+ <text
104
+ fg={row.isCurrentTip ? tokens.accent : foreground}
105
+ attributes={row.onPath ? undefined : DIM}
106
+ >
107
+ {markerFor(row)} {row.text}
108
+ </text>
109
+ <text fg={tokens.textMuted} attributes={row.label === undefined ? undefined : BOLD}>
110
+ {trailerFor(row)}
111
+ </text>
112
+ </box>
113
+ );
114
+ })}
115
+ </scrollbox>
116
+ {canMove || canFork ? (
117
+ <box style={{ paddingLeft: 1 }}>
118
+ <Toolbar>
119
+ {canMove ? (
120
+ <Button
121
+ onPress={() => {
122
+ if (selectedEntryId !== undefined) onGo(selectedEntryId);
123
+ }}
124
+ isDisabled={selectedEntryId === undefined}
125
+ marginRight={1}
126
+ theme={theme}
127
+ >
128
+ Go
129
+ </Button>
130
+ ) : null}
131
+ {canMove ? (
132
+ <Button onPress={onBranch} marginRight={1} theme={theme}>
133
+ Branch
134
+ </Button>
135
+ ) : null}
136
+ {canMove ? (
137
+ <Button onPress={onLabel} marginRight={1} theme={theme}>
138
+ Label
139
+ </Button>
140
+ ) : null}
141
+ {canFork ? (
142
+ <Button onPress={onFork} marginRight={1} theme={theme}>
143
+ Fork
144
+ </Button>
145
+ ) : null}
146
+ {canFork ? (
147
+ <Button onPress={onForkAndShare} theme={theme}>
148
+ Fork+share
149
+ </Button>
150
+ ) : null}
151
+ </Toolbar>
152
+ </box>
153
+ ) : null}
154
+ </box>
155
+ );
156
+ }
@@ -1,4 +1,4 @@
1
- // Bun Snapshot v1, https://bun.sh/docs/test/snapshots
1
+ // Bun Snapshot v1, https://goo.gl/fbAQLP
2
2
 
3
3
  exports[`stories catalog AnnotationCard/CommentDraft renders, snapshots, and carries its colors: AnnotationCard/CommentDraft 1`] = `
4
4
  "╭─ comment on "persists sessions" ─────────────────────────────────────────────╮
@@ -618,168 +618,6 @@ exports[`stories catalog MenuBar/Open renders, snapshots, and carries its colors
618
618
  "
619
619
  `;
620
620
 
621
- exports[`stories catalog PlanSheet/AnnotatedPlan renders, snapshots, and carries its colors: PlanSheet/AnnotatedPlan 1`] = `
622
- "╭────────────────────────────────────────────────────────────────────────────────────────╮
623
- │ Migration Plan │
624
- │ │
625
- │ Context │
626
- │ │
627
- │ ▎ The daemon persists sessions to disk atomically. │
628
- │ │
629
- │ Steps │
630
- │ │
631
- │ - move the store │
632
- │ - add recovery │
633
- │ │
634
- │ ts │
635
- │ │
636
- │ export function persist(session: Session): void { │
637
- │ // one JSON document per session │
638
- │ writeAtomically(pathFor(session.id), serialize(session)); │
639
- │ } │
640
- │ │
641
- │ │
642
- │ │
643
- │ │
644
- │ │
645
- │ │
646
- │ │
647
- │ │
648
- │ │
649
- ╰────────────────────────────────────────────────────────────────────────────────────────╯
650
- "
651
- `;
652
-
653
- exports[`stories catalog PlanSheet/InlineCompose renders, snapshots, and carries its colors: PlanSheet/InlineCompose 1`] = `
654
- "╭────────────────────────────────────────────────────────────────────────────────────────╮
655
- │ Migration Plan │
656
- │ │
657
- │ Context │
658
- │ │
659
- │ ▎ The daemon persists sessions to disk atomically. │
660
- │ ╭─ comment on "The daemon" ───────────────────────────────────────────────────────────╮│
661
- │ │ Which daemon? ││
662
- │ │ Save Cancel ││
663
- │ ╰─────────────────────────────────────────────────────────────────────────────────────╯│
664
- │ │
665
- │ Steps │
666
- │ │
667
- │ - move the store │
668
- │ - add recovery │
669
- │ │
670
- │ ts │
671
- │ │
672
- │ export function persist(session: Session): void { │
673
- │ // one JSON document per session │
674
- │ writeAtomically(pathFor(session.id), serialize(session)); │
675
- │ } │
676
- │ │
677
- │ │
678
- │ │
679
- │ │
680
- │ │
681
- │ │
682
- │ │
683
- ╰────────────────────────────────────────────────────────────────────────────────────────╯
684
- "
685
- `;
686
-
687
- exports[`stories catalog PlanSheet/OrphanBanner renders, snapshots, and carries its colors: PlanSheet/OrphanBanner 1`] = `
688
- "╭────────────────────────────────────────────────────────────────────────────────────────╮
689
- │ 1 annotation no longer match - the passage was removed. │
690
- │ ▎ Migration Plan │
691
- │ │
692
- │ Context │
693
- │ │
694
- │ The daemon persists sessions to disk atomically. │
695
- │ │
696
- │ Steps │
697
- │ │
698
- │ - move the store │
699
- │ - add recovery │
700
- │ │
701
- │ ts │
702
- │ │
703
- │ export function persist(session: Session): void { │
704
- │ // one JSON document per session │
705
- │ writeAtomically(pathFor(session.id), serialize(session)); │
706
- │ } │
707
- │ │
708
- │ │
709
- │ │
710
- │ │
711
- │ │
712
- │ │
713
- │ │
714
- │ │
715
- ╰────────────────────────────────────────────────────────────────────────────────────────╯
716
- "
717
- `;
718
-
719
- exports[`stories catalog PlanSheet/SpanSelection renders, snapshots, and carries its colors: PlanSheet/SpanSelection 1`] = `
720
- "╭────────────────────────────────────────────────────────────────────────────────────────╮
721
- │ Migration Plan │
722
- │ │
723
- │ Context │
724
- │ │
725
- │ ▎ The daemon persists sessions to disk atomically. │
726
- │ │
727
- │ Steps │
728
- │ │
729
- │ - move the store │
730
- │ - add recovery │
731
- │ │
732
- │ ts │
733
- │ │
734
- │ export function persist(session: Session): void { │
735
- │ // one JSON document per session │
736
- │ writeAtomically(pathFor(session.id), serialize(session)); │
737
- │ } │
738
- │ │
739
- │ │
740
- │ │
741
- │ │
742
- │ │
743
- │ │
744
- │ │
745
- │ │
746
- │ │
747
- ╰────────────────────────────────────────────────────────────────────────────────────────╯
748
- "
749
- `;
750
-
751
- exports[`stories catalog PlanSheet/WithCutBlock renders, snapshots, and carries its colors: PlanSheet/WithCutBlock 1`] = `
752
- "╭────────────────────────────────────────────────────────────────────────────────────────╮
753
- │ ▎ Migration Plan │
754
- │ │
755
- │ Context │
756
- │ │
757
- │ The daemon persists sessions to disk atomically. │
758
- │ │
759
- │ Steps │
760
- │ │
761
- │ - move the store │
762
- │ - add recovery │
763
- │ │
764
- │ ts │
765
- │ │
766
- │ export function persist(session: Session): void { │
767
- │ // one JSON document per session │
768
- │ writeAtomically(pathFor(session.id), serialize(session)); │
769
- │ } │
770
- │ │
771
- │ │
772
- │ │
773
- │ │
774
- │ │
775
- │ │
776
- │ │
777
- │ │
778
- │ │
779
- ╰────────────────────────────────────────────────────────────────────────────────────────╯
780
- "
781
- `;
782
-
783
621
  exports[`stories catalog PromptDialog/NameYourself renders, snapshots, and carries its colors: PromptDialog/NameYourself 1`] = `
784
622
  "
785
623
 
@@ -878,7 +716,7 @@ exports[`stories catalog ReviewPanel/Compact renders, snapshots, and carries its
878
716
 
879
717
  exports[`stories catalog ReviewPanel/ExpandedNarrow renders, snapshots, and carries its colors: ReviewPanel/ExpandedNarrow 1`] = `
880
718
  " The plan takes the width the panel hands back. ╭──────────────────────╮
881
- │ Review Agent │
719
+ │ Review Tree Agent │
882
720
  ╰──────────────────────╯
883
721
  ╭─ COMMENT · me ───────╮
884
722
  │ Whichidaemonsownss" │
@@ -906,7 +744,7 @@ exports[`stories catalog ReviewPanel/ExpandedNarrow renders, snapshots, and carr
906
744
 
907
745
  exports[`stories catalog ReviewPanel/ExpandedWide renders, snapshots, and carries its colors: ReviewPanel/ExpandedWide 1`] = `
908
746
  " The plan takes the width the panel ╭────────────────────────────────────────────────╮
909
- hands back. │ Review Agent │
747
+ hands back. │ Review Tree Agent │
910
748
  ╰────────────────────────────────────────────────╯
911
749
  ╭─ COMMENT · me ─────────────────────────────────╮
912
750
  │ "persists sessions" │
@@ -962,7 +800,7 @@ exports[`stories catalog ReviewPanel/Hidden renders, snapshots, and carries its
962
800
 
963
801
  exports[`stories catalog ReviewRail/AgentTab renders, snapshots, and carries its colors: ReviewRail/AgentTab 1`] = `
964
802
  "╭────────────────────────────────╮
965
- │ Review Agent │
803
+ │ Review Tree Agent │
966
804
  ╰────────────────────────────────╯
967
805
  ╭────────────────────────────────╮
968
806
  │ Claude Code │
@@ -990,7 +828,7 @@ plan context: seed the plan
990
828
 
991
829
  exports[`stories catalog ReviewRail/AnnotationStack renders, snapshots, and carries its colors: ReviewRail/AnnotationStack 1`] = `
992
830
  "╭────────────────────────────────╮
993
- │ Review Agent │
831
+ │ Review Tree Agent │
994
832
  ╰────────────────────────────────╯
995
833
  ╭─ COMMENT · me ─────────────────╮
996
834
  │ "persists sessions" │
@@ -1018,7 +856,7 @@ Submit review
1018
856
 
1019
857
  exports[`stories catalog ReviewRail/InterleavedStack renders, snapshots, and carries its colors: ReviewRail/InterleavedStack 1`] = `
1020
858
  "╭────────────────────────────────╮
1021
- │ Review Agent │
859
+ │ Review Tree Agent │
1022
860
  ╰────────────────────────────────╯
1023
861
  ╭─ CUT · me ─────────────────────╮
1024
862
  │ - add recovery │
@@ -1046,7 +884,7 @@ Submit review
1046
884
 
1047
885
  exports[`stories catalog ReviewRail/MixedOwnershipStack renders, snapshots, and carries its colors: ReviewRail/MixedOwnershipStack 1`] = `
1048
886
  "╭────────────────────────────────╮
1049
- │ Review Agent │
887
+ │ Review Tree Agent │
1050
888
  ╰────────────────────────────────╯
1051
889
  ╭─ COMMENT · me ─────────────────╮
1052
890
  │ "persists sessions" │
@@ -1074,7 +912,7 @@ Submit review
1074
912
 
1075
913
  exports[`stories catalog ReviewRail/Removals renders, snapshots, and carries its colors: ReviewRail/Removals 1`] = `
1076
914
  "╭────────────────────────────────╮
1077
- │ Review Agent │
915
+ │ Review Tree Agent │
1078
916
  ╰────────────────────────────────╯
1079
917
  ╭─ REJECT · me ──────────────────╮
1080
918
  │ - private items = []; │
@@ -1102,7 +940,7 @@ Submit review
1102
940
 
1103
941
  exports[`stories catalog ReviewRail/Resolved renders, snapshots, and carries its colors: ReviewRail/Resolved 1`] = `
1104
942
  "╭────────────────────────────────╮
1105
- │ Review Agent │
943
+ │ Review Tree Agent │
1106
944
  ╰────────────────────────────────╯
1107
945
  ╭─ COMMENT · me · ORPHANED ──────╮
1108
946
  │ "persists sessions" │
@@ -1130,7 +968,7 @@ resolved: approve
1130
968
 
1131
969
  exports[`stories catalog ReviewRail/SubmitConfirmOpen renders, snapshots, and carries its colors: ReviewRail/SubmitConfirmOpen 1`] = `
1132
970
  "╭────────────────────────────────╮
1133
- │ Review Agent │
971
+ │ Review Tree Agent │
1134
972
  ╰────────────────────────────────╯
1135
973
  ╭─ COMMENT · me · ORPHANED ──────╮
1136
974
  │ "persists sessions" │
@@ -1289,6 +1127,44 @@ exports[`stories catalog SettingsRows/AllKinds renders, snapshots, and carries i
1289
1127
 
1290
1128
 
1291
1129
 
1130
+ "
1131
+ `;
1132
+
1133
+ exports[`stories catalog ThreadView/InlineThreads renders, snapshots, and carries its colors: ThreadView/InlineThreads 1`] = `
1134
+ "
1135
+ Migration Plan
1136
+
1137
+ Context
1138
+
1139
+ The daemon persists sessions to disk atomically.
1140
+
1141
+ ╷─────────────────────────────────────────────────────────────────────────────────────────────┐
1142
+ │ ● Which daemon owns this? │
1143
+ ╵─────────────────────────────────────────────────────────────────────────────────────────────┘
1144
+
1145
+ Steps
1146
+
1147
+ · move the store
1148
+
1149
+ ╷─────────────────────────────────────────────────────────────────────────────────────────────┐
1150
+ │ ● move the store behind one interface │ ○
1151
+ ╵─────────────────────────────────────────────────────────────────────────────────────────────┘ ○
1152
+ · add recovery
1153
+
1154
+ ts
1155
+ export function persist(session: Session): void {
1156
+ // one JSON document per session
1157
+ writeAtomically(pathFor(session.id), serialize(session));
1158
+ }
1159
+
1160
+
1161
+
1162
+
1163
+
1164
+
1165
+
1166
+
1167
+
1292
1168
  "
1293
1169
  `;
1294
1170
 
@@ -1308,6 +1184,78 @@ exports[`stories catalog Toast/ShareLink renders, snapshots, and carries its col
1308
1184
  "
1309
1185
  `;
1310
1186
 
1187
+ exports[`stories catalog TreePane/CollaboratorReadsOnly renders, snapshots, and carries its colors: TreePane/CollaboratorReadsOnly 1`] = `
1188
+ " ◉ revision 1
1189
+ · comment ⚑ start
1190
+ · comment ← alt
1191
+ ● revision 2 ← main
1192
+
1193
+
1194
+
1195
+
1196
+
1197
+
1198
+
1199
+
1200
+
1201
+
1202
+
1203
+
1204
+ "
1205
+ `;
1206
+
1207
+ exports[`stories catalog TreePane/NoHistory renders, snapshots, and carries its colors: TreePane/NoHistory 1`] = `
1208
+ "
1209
+
1210
+
1211
+ no history yet
1212
+
1213
+
1214
+
1215
+
1216
+ "
1217
+ `;
1218
+
1219
+ exports[`stories catalog TreePane/OwnerWithBranch renders, snapshots, and carries its colors: TreePane/OwnerWithBranch 1`] = `
1220
+ " ◉ revision 1
1221
+ · comment ⚑ start
1222
+ · comment ← alt
1223
+ ● revision 2 ← main
1224
+
1225
+
1226
+
1227
+
1228
+
1229
+
1230
+
1231
+
1232
+
1233
+
1234
+
1235
+ Go Branch Label Fork Fork+share
1236
+ "
1237
+ `;
1238
+
1239
+ exports[`stories catalog TreePane/ResolvedForkOnly renders, snapshots, and carries its colors: TreePane/ResolvedForkOnly 1`] = `
1240
+ " ◉ revision 1
1241
+ · comment ⚑ start
1242
+ · comment ← alt
1243
+ ● revision 2 ← main
1244
+
1245
+
1246
+
1247
+
1248
+
1249
+
1250
+
1251
+
1252
+
1253
+
1254
+
1255
+ Fork Fork+share
1256
+ "
1257
+ `;
1258
+
1311
1259
  exports[`stories catalog WalkWizard/EndCard renders, snapshots, and carries its colors: WalkWizard/EndCard 1`] = `
1312
1260
  "
1313
1261
 
@@ -65,7 +65,7 @@ export function TabList({ children }: TabListProps): React.ReactNode {
65
65
  const items: { id: string; label: string }[] = [];
66
66
 
67
67
  React.Children.forEach(children, (child) => {
68
- if (React.isValidElement<TabProps>(child) && typeof child.props.id === "string") {
68
+ if (React.isValidElement<TabProps>(child)) {
69
69
  items.push({ id: child.props.id, label: child.props.children });
70
70
  }
71
71
  });