@cueloop/client 0.1.0-alpha.32 → 0.1.0-alpha.34

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 (101) hide show
  1. package/package.json +27 -26
  2. package/src/App.autoclose.test.tsx +7 -4
  3. package/src/App.diff.test.tsx +131 -5
  4. package/src/App.export.test.tsx +23 -5
  5. package/src/App.plan-review.test.tsx +133 -32
  6. package/src/App.readonly.test.tsx +35 -13
  7. package/src/App.rendering.test.tsx +14 -4
  8. package/src/App.review-panel.test.tsx +27 -15
  9. package/src/App.submit-confirm.test.tsx +14 -13
  10. package/src/App.test.tsx +50 -13
  11. package/src/App.toast.test.tsx +75 -0
  12. package/src/App.tsx +505 -101
  13. package/src/App.walk.test.tsx +31 -11
  14. package/src/attribution.ts +8 -2
  15. package/src/clipboard.ts +5 -1
  16. package/src/components/AnnotationCard.stories.tsx +29 -10
  17. package/src/components/AnnotationCard.tsx +57 -37
  18. package/src/components/Breadcrumb.tsx +7 -1
  19. package/src/components/CodeBlock.stories.tsx +19 -2
  20. package/src/components/CodeBlock.tsx +24 -9
  21. package/src/components/CompletionOverlay.tsx +13 -3
  22. package/src/components/ConfirmCard.stories.tsx +22 -3
  23. package/src/components/ConfirmCard.tsx +23 -5
  24. package/src/components/ConfirmDialog.tsx +10 -2
  25. package/src/components/DiffSheet.scroll.test.tsx +123 -0
  26. package/src/components/DiffSheet.stories.tsx +35 -1
  27. package/src/components/DiffSheet.syntax.test.tsx +62 -0
  28. package/src/components/DiffSheet.tsx +213 -101
  29. package/src/components/InboxList.stories.tsx +16 -3
  30. package/src/components/InboxList.tsx +17 -10
  31. package/src/components/KeybindsDialog.stories.tsx +31 -0
  32. package/src/components/KeybindsDialog.tsx +58 -0
  33. package/src/components/MarkerPopover.stories.tsx +58 -0
  34. package/src/components/MarkerPopover.tsx +102 -0
  35. package/src/components/MenuBar.stories.tsx +37 -0
  36. package/src/components/MenuBar.tsx +74 -0
  37. package/src/components/PlanSheet.stories.tsx +35 -3
  38. package/src/components/PlanSheet.tsx +160 -100
  39. package/src/components/PromptDialog.tsx +19 -3
  40. package/src/components/ReviewPanel.stories.tsx +17 -19
  41. package/src/components/ReviewPanel.tsx +17 -32
  42. package/src/components/ReviewRail.stories.tsx +159 -47
  43. package/src/components/ReviewRail.tsx +206 -62
  44. package/src/components/SettingsDialog.stories.tsx +51 -3
  45. package/src/components/SettingsDialog.tsx +11 -5
  46. package/src/components/SettingsRows.stories.tsx +1 -1
  47. package/src/components/SettingsRows.tsx +19 -3
  48. package/src/components/Toast.stories.tsx +12 -0
  49. package/src/components/Toast.tsx +44 -0
  50. package/src/components/WalkWizard.stories.tsx +13 -2
  51. package/src/components/WalkWizard.tsx +9 -3
  52. package/src/components/__snapshots__/stories.test.tsx.snap +853 -623
  53. package/src/components/diff-sheet-layout.test.ts +127 -0
  54. package/src/components/diff-sheet-layout.ts +167 -0
  55. package/src/components/primitives/Button.stories.tsx +2 -2
  56. package/src/components/primitives/Button.tsx +5 -1
  57. package/src/components/primitives/Dialog.tsx +12 -1
  58. package/src/components/primitives/Tabs.tsx +40 -39
  59. package/src/components/stories-app.tsx +34 -6
  60. package/src/components/story-fixtures.ts +11 -3
  61. package/src/components/story.ts +3 -1
  62. package/src/components/syntax-highlight.ts +47 -12
  63. package/src/components/theme-context.stories.tsx +6 -1
  64. package/src/components/theme-context.tsx +7 -1
  65. package/src/config.test.ts +178 -2
  66. package/src/config.ts +121 -23
  67. package/src/diff-hunk-curate.test.ts +315 -0
  68. package/src/diff-hunk-curate.ts +233 -0
  69. package/src/diff-intraline.test.ts +164 -0
  70. package/src/diff-intraline.ts +132 -0
  71. package/src/diff-syntax.test.ts +96 -0
  72. package/src/diff-syntax.ts +154 -0
  73. package/src/editor.test.ts +13 -4
  74. package/src/editor.ts +31 -5
  75. package/src/herdr.ts +8 -2
  76. package/src/index.ts +9 -1
  77. package/src/integrations.ts +3 -1
  78. package/src/intent-dispatch.test.ts +193 -9
  79. package/src/intent-dispatch.ts +151 -18
  80. package/src/key-bindings.test.ts +19 -9
  81. package/src/key-bindings.ts +77 -16
  82. package/src/keymap.test.ts +171 -26
  83. package/src/keymap.ts +83 -16
  84. package/src/review-panel.test.ts +3 -1
  85. package/src/review-panel.ts +9 -2
  86. package/src/serve.test.ts +13 -5
  87. package/src/session-controller.curate.test.ts +253 -0
  88. package/src/session-controller.share.test.ts +77 -16
  89. package/src/session-controller.ts +319 -26
  90. package/src/share.ts +41 -8
  91. package/src/test-support.ts +97 -23
  92. package/src/theme-presets.ts +168 -0
  93. package/src/theme.ts +1 -3
  94. package/src/version.ts +5 -0
  95. package/src/view-diff.ts +15 -4
  96. package/src/view-plan.test.ts +40 -6
  97. package/src/view-plan.ts +71 -28
  98. package/src/walk.test.ts +7 -2
  99. package/src/walk.ts +7 -2
  100. package/src/components/ComposeBar.stories.tsx +0 -18
  101. package/src/components/ComposeBar.tsx +0 -54
@@ -2,14 +2,14 @@
2
2
  * The pending-review inbox: a cursor-driven session list. Selection stays with
3
3
  * the keyboard grammar; the list only renders the snapshot. The selected row
4
4
  * carries a [delete] word-button (click, or `d` in the grammar) that asks the
5
- * caller to confirm removal.
5
+ * caller to confirm removal. App supplies the surrounding header and MenuBar
6
+ * chrome (the same as plan/diff review); this is the flex-growing body.
6
7
  */
7
8
 
8
9
  import React from "react";
9
10
  import type { ReviewSession } from "@cueloop/schema";
10
11
  import type { Theme } from "../theme";
11
12
  import { useComponentTheme } from "./theme-context";
12
- import { StatusBar } from "./primitives/StatusBar";
13
13
  import { Button } from "./primitives/Button";
14
14
 
15
15
  export interface InboxListProps {
@@ -20,12 +20,15 @@ export interface InboxListProps {
20
20
  theme?: Theme;
21
21
  }
22
22
 
23
- export function InboxList({ inbox, cursor, onRequestDelete, theme }: InboxListProps): React.ReactNode {
23
+ export function InboxList({
24
+ inbox,
25
+ cursor,
26
+ onRequestDelete,
27
+ theme,
28
+ }: InboxListProps): React.ReactNode {
24
29
  const tokens = useComponentTheme(theme);
25
30
  return (
26
- <box style={{ flexDirection: "column", width: "100%", height: "100%", backgroundColor: tokens.background, padding: 1 }}>
27
- <text fg={tokens.accent}>cueloop · inbox ({inbox.length} pending)</text>
28
- <text> </text>
31
+ <box style={{ flexGrow: 1, flexDirection: "column", paddingLeft: 1, paddingTop: 1 }}>
29
32
  {inbox.length === 0 ? (
30
33
  <text fg={tokens.textDim}>nothing waiting for review</text>
31
34
  ) : (
@@ -33,9 +36,15 @@ export function InboxList({ inbox, cursor, onRequestDelete, theme }: InboxListPr
33
36
  const selected = index === cursor;
34
37
  const title = session.artifact.meta.title ?? session.id;
35
38
  return (
36
- <box key={session.id} style={{ flexDirection: "row", backgroundColor: selected ? tokens.cursorBackground : undefined }}>
39
+ <box
40
+ key={session.id}
41
+ style={{
42
+ flexDirection: "row",
43
+ backgroundColor: selected ? tokens.cursorBackground : undefined,
44
+ }}
45
+ >
37
46
  <text fg={selected ? tokens.text : tokens.textMuted}>
38
- {selected ? "▸ " : " "}
47
+ {selected ? "> " : " "}
39
48
  {title} · {session.workspace.branch} · {session.artifact.type}
40
49
  </text>
41
50
  <box style={{ flexGrow: 1 }} />
@@ -48,8 +57,6 @@ export function InboxList({ inbox, cursor, onRequestDelete, theme }: InboxListPr
48
57
  );
49
58
  })
50
59
  )}
51
- <box style={{ flexGrow: 1 }} />
52
- <StatusBar theme={theme}>j/k move · ⏎ open · d delete · q quit</StatusBar>
53
60
  </box>
54
61
  );
55
62
  }
@@ -0,0 +1,31 @@
1
+ import React from "react";
2
+ import { DARK } from "../theme";
3
+ import type { Story, StoryMeta } from "./story";
4
+ import { KeybindsDialog } from "./KeybindsDialog";
5
+
6
+ export const meta: StoryMeta = { title: "KeybindsDialog" };
7
+
8
+ const SECTIONS = [
9
+ {
10
+ title: "Review",
11
+ entries: [
12
+ { keys: "j / k", label: "move" },
13
+ { keys: "v", label: "span" },
14
+ { keys: "c", label: "comment" },
15
+ { keys: "enter", label: "submit" },
16
+ ],
17
+ },
18
+ {
19
+ title: "Submit",
20
+ entries: [
21
+ { keys: "← / →", label: "verdict" },
22
+ { keys: "esc", label: "cancel" },
23
+ ],
24
+ },
25
+ ];
26
+
27
+ export const Cheatsheet: Story = {
28
+ render: () => <KeybindsDialog sections={SECTIONS} />,
29
+ expectedColors: [DARK.accent],
30
+ size: { width: 70, height: 24 },
31
+ };
@@ -0,0 +1,58 @@
1
+ import React from "react";
2
+ import { useTerminalDimensions } from "@opentui/react";
3
+ import type { Theme } from "../theme";
4
+ import { useComponentTheme } from "./theme-context";
5
+ import { Dialog } from "./primitives/Dialog";
6
+ import type { CheatsheetSection } from "../key-bindings";
7
+
8
+ export interface KeybindsDialogProps {
9
+ sections: CheatsheetSection[];
10
+ theme?: Theme;
11
+ }
12
+
13
+ export function KeybindsDialog({ sections, theme }: KeybindsDialogProps): React.ReactNode {
14
+ const tokens = useComponentTheme(theme);
15
+ const { width: terminalWidth, height: terminalHeight } = useTerminalDimensions();
16
+ const contentRows =
17
+ sections.reduce((sum, section) => sum + 1 + section.entries.length + 1, 0) + 1;
18
+ const width = Math.min(52, terminalWidth - 6);
19
+ const height = Math.min(terminalHeight - 4, contentRows + 3);
20
+ return (
21
+ <Dialog
22
+ isOpen
23
+ title=" keybinds "
24
+ width={width}
25
+ height={height}
26
+ background={tokens.elevated}
27
+ theme={theme}
28
+ >
29
+ <box
30
+ style={{
31
+ flexDirection: "column",
32
+ flexGrow: 1,
33
+ paddingLeft: 1,
34
+ paddingRight: 1,
35
+ paddingTop: 1,
36
+ }}
37
+ >
38
+ {sections.map((section) => (
39
+ <box key={section.title} style={{ flexDirection: "column" }}>
40
+ <text fg={tokens.accent}>{section.title}</text>
41
+ {section.entries.map((entry, index) => (
42
+ <text key={index}>
43
+ <span fg={tokens.text}>{entry.keys.padEnd(12)}</span>
44
+ <span fg={tokens.textMuted}>{entry.label}</span>
45
+ </text>
46
+ ))}
47
+ <text> </text>
48
+ </box>
49
+ ))}
50
+ <box style={{ flexGrow: 1 }} />
51
+ <box style={{ flexDirection: "row" }}>
52
+ <box style={{ flexGrow: 1 }} />
53
+ <text fg={tokens.textDim}>[esc]</text>
54
+ </box>
55
+ </box>
56
+ </Dialog>
57
+ );
58
+ }
@@ -0,0 +1,58 @@
1
+ import React from "react";
2
+ import { DARK } from "../theme";
3
+ import { DEFAULT_QUICK_ACTIONS } from "../config";
4
+ import type { Story, StoryMeta } from "./story";
5
+ import { MarkerPopover } from "./MarkerPopover";
6
+
7
+ export const meta: StoryMeta = { title: "MarkerPopover" };
8
+
9
+ const callbacks = {
10
+ onComment: () => {},
11
+ onCut: () => {},
12
+ onOpenActions: () => {},
13
+ onClose: () => {},
14
+ onPickAction: () => {},
15
+ onBack: () => {},
16
+ };
17
+
18
+ export const Toolbar: Story = {
19
+ render: () => (
20
+ <MarkerPopover
21
+ view="toolbar"
22
+ actions={DEFAULT_QUICK_ACTIONS}
23
+ actionIndex={0}
24
+ canCut
25
+ {...callbacks}
26
+ />
27
+ ),
28
+ expectedColors: [DARK.accent, DARK.red, DARK.textMuted, DARK.textDim],
29
+ size: { width: 60, height: 4 },
30
+ };
31
+
32
+ export const ToolbarNoCut: Story = {
33
+ render: () => (
34
+ <MarkerPopover
35
+ view="toolbar"
36
+ actions={DEFAULT_QUICK_ACTIONS}
37
+ actionIndex={0}
38
+ canCut={false}
39
+ {...callbacks}
40
+ />
41
+ ),
42
+ expectedColors: [DARK.accent, DARK.textMuted, DARK.textDim],
43
+ size: { width: 60, height: 4 },
44
+ };
45
+
46
+ export const ActionsList: Story = {
47
+ render: () => (
48
+ <MarkerPopover
49
+ view="actions"
50
+ actions={DEFAULT_QUICK_ACTIONS}
51
+ actionIndex={2}
52
+ canCut
53
+ {...callbacks}
54
+ />
55
+ ),
56
+ expectedColors: [DARK.accent, DARK.textMuted, DARK.textDim],
57
+ size: { width: 60, height: 8 },
58
+ };
@@ -0,0 +1,102 @@
1
+ /**
2
+ * The marker-actions popover: the visible form of plan span mode, floated as a
3
+ * bordered card above the marked block. The toolbar card is always shown -
4
+ * `comment · cut · actions · [x]`, each label keyboard-shortcut-backed and
5
+ * clickable. Pressing `actions` drops a second card below it listing the
6
+ * quick-actions; picking one inserts its preset comment. Plan-only: the diff
7
+ * review has no span.
8
+ */
9
+
10
+ import React from "react";
11
+ import type { Theme } from "../theme";
12
+ import type { QuickAction } from "../config";
13
+ import { useComponentTheme } from "./theme-context";
14
+ import { cardHeight } from "./primitives/Card";
15
+ import { FRAME_BORDER_STYLE } from "./primitives/frame";
16
+
17
+ export interface MarkerPopoverProps {
18
+ /** "toolbar" is the bar alone; "actions" also drops the quick-actions card. */
19
+ view: "toolbar" | "actions";
20
+ actions: QuickAction[];
21
+ /** Highlighted action in the dropdown (accent); ignored in toolbar view. */
22
+ actionIndex: number;
23
+ /** Show the cut affordance: owner-only, like every other plan-edit control. */
24
+ canCut: boolean;
25
+ onComment: () => void;
26
+ onCut: () => void;
27
+ onOpenActions: () => void;
28
+ onClose: () => void;
29
+ onPickAction: (index: number) => void;
30
+ onBack: () => void;
31
+ theme?: Theme;
32
+ }
33
+
34
+ export function MarkerPopover({
35
+ view,
36
+ actions,
37
+ actionIndex,
38
+ canCut,
39
+ onComment,
40
+ onCut,
41
+ onOpenActions,
42
+ onClose,
43
+ onPickAction,
44
+ onBack,
45
+ theme,
46
+ }: MarkerPopoverProps): React.ReactNode {
47
+ const tokens = useComponentTheme(theme);
48
+ const cardStyle = {
49
+ border: true,
50
+ borderStyle: FRAME_BORDER_STYLE,
51
+ borderColor: tokens.border,
52
+ backgroundColor: tokens.elevated,
53
+ flexDirection: "column" as const,
54
+ paddingLeft: 1,
55
+ paddingRight: 1,
56
+ };
57
+ const open = view === "actions";
58
+ return (
59
+ <box style={{ flexDirection: "column", alignItems: "flex-start" }}>
60
+ <box style={{ ...cardStyle, height: cardHeight(1) }}>
61
+ <box style={{ flexDirection: "row" }}>
62
+ <text fg={tokens.accent} onMouseUp={onComment}>
63
+ comment
64
+ </text>
65
+ <text fg={tokens.textDim}> · </text>
66
+ {canCut ? (
67
+ <>
68
+ <text fg={tokens.red} onMouseUp={onCut}>
69
+ cut
70
+ </text>
71
+ <text fg={tokens.textDim}> · </text>
72
+ </>
73
+ ) : null}
74
+ <text fg={open ? tokens.accent : tokens.textMuted} onMouseUp={onOpenActions}>
75
+ actions
76
+ </text>
77
+ <text fg={tokens.textDim}> · </text>
78
+ <text fg={tokens.textDim} onMouseUp={onClose}>
79
+ [x]
80
+ </text>
81
+ </box>
82
+ </box>
83
+ {open ? (
84
+ <box style={{ ...cardStyle, height: cardHeight(actions.length + 1) }}>
85
+ {actions.map((action, index) => (
86
+ <text
87
+ key={index}
88
+ fg={index === actionIndex ? tokens.accent : tokens.textMuted}
89
+ onMouseUp={() => onPickAction(index)}
90
+ >
91
+ {index === actionIndex ? "› " : " "}
92
+ {action.prompt}
93
+ </text>
94
+ ))}
95
+ <text fg={tokens.textDim} onMouseUp={onBack}>
96
+ {" "}‹ back
97
+ </text>
98
+ </box>
99
+ ) : null}
100
+ </box>
101
+ );
102
+ }
@@ -0,0 +1,37 @@
1
+ import React from "react";
2
+ import { DARK } from "../theme";
3
+ import type { Story, StoryMeta } from "./story";
4
+ import { MenuBar } from "./MenuBar";
5
+
6
+ export const meta: StoryMeta = { title: "MenuBar" };
7
+
8
+ const callbacks = { onToggle: () => {}, onSettings: () => {}, onKeybinds: () => {} };
9
+
10
+ function BottomFrame({ children }: { children: React.ReactNode }): React.ReactNode {
11
+ return (
12
+ <box
13
+ style={{ flexDirection: "column", width: "100%", height: "100%", justifyContent: "flex-end" }}
14
+ >
15
+ {children}
16
+ </box>
17
+ );
18
+ }
19
+
20
+ export const Closed: Story = {
21
+ render: () => (
22
+ <BottomFrame>
23
+ <MenuBar open={false} version="0.1.0-alpha.32" {...callbacks} />
24
+ </BottomFrame>
25
+ ),
26
+ size: { width: 60, height: 8 },
27
+ };
28
+
29
+ export const Open: Story = {
30
+ render: () => (
31
+ <BottomFrame>
32
+ <MenuBar open version="0.1.0-alpha.32" {...callbacks} />
33
+ </BottomFrame>
34
+ ),
35
+ expectedColors: [DARK.text],
36
+ size: { width: 60, height: 8 },
37
+ };
@@ -0,0 +1,74 @@
1
+ import React from "react";
2
+ import { useTerminalDimensions } from "@opentui/react";
3
+ import type { Theme } from "../theme";
4
+ import { useComponentTheme } from "./theme-context";
5
+ import { FRAME_BORDER_STYLE } from "./primitives/frame";
6
+
7
+ export interface MenuBarProps {
8
+ open: boolean;
9
+ version: string;
10
+ /** The transient status line, shown between the menu and the version. */
11
+ status?: string;
12
+ onToggle: () => void;
13
+ onSettings: () => void;
14
+ onKeybinds: () => void;
15
+ theme?: Theme;
16
+ }
17
+
18
+ const DROPUP_OPTION_ROWS = 2;
19
+
20
+ export function MenuBar({
21
+ open,
22
+ version,
23
+ status,
24
+ onToggle,
25
+ onSettings,
26
+ onKeybinds,
27
+ theme,
28
+ }: MenuBarProps): React.ReactNode {
29
+ const tokens = useComponentTheme(theme);
30
+ const { height: terminalHeight } = useTerminalDimensions();
31
+ const dropupHeight = DROPUP_OPTION_ROWS + 2;
32
+ return (
33
+ <>
34
+ {open ? (
35
+ <box
36
+ style={{
37
+ position: "absolute",
38
+ left: 1,
39
+ top: Math.max(0, terminalHeight - 1 - dropupHeight),
40
+ width: 16,
41
+ height: dropupHeight,
42
+ border: true,
43
+ borderStyle: FRAME_BORDER_STYLE,
44
+ borderColor: tokens.text,
45
+ flexDirection: "column",
46
+ }}
47
+ >
48
+ <box onMouseUp={onSettings}>
49
+ <text fg={tokens.text}> Settings</text>
50
+ </box>
51
+ <box onMouseUp={onKeybinds}>
52
+ <text fg={tokens.text}> Keybinds</text>
53
+ </box>
54
+ </box>
55
+ ) : null}
56
+ <box
57
+ style={{
58
+ flexDirection: "row",
59
+ height: 1,
60
+ backgroundColor: tokens.panel,
61
+ paddingLeft: 1,
62
+ paddingRight: 1,
63
+ }}
64
+ >
65
+ <box onMouseUp={onToggle}>
66
+ <text fg={tokens.textDim}>menu</text>
67
+ </box>
68
+ {status ? <text fg={tokens.accent}>{` ${status}`}</text> : null}
69
+ <box style={{ flexGrow: 1 }} />
70
+ <text fg={tokens.textDim}>v{version}</text>
71
+ </box>
72
+ </>
73
+ );
74
+ }
@@ -1,12 +1,16 @@
1
1
  import React from "react";
2
2
  import { DARK } from "../theme";
3
3
  import type { Story, StoryMeta } from "./story";
4
+ import { buildDisplay } from "../view-plan";
4
5
  import { PlanSheet } from "./PlanSheet";
5
- import { fixtureDisplay, fixtureMarks, fixturePlanSession } from "./story-fixtures";
6
+ import { FIXTURE_PLAN, fixtureDisplay, fixtureMarks, fixturePlanSession } from "./story-fixtures";
6
7
 
7
8
  export const meta: StoryMeta = { title: "PlanSheet" };
8
9
 
9
- const callbacks = { onLineActivate: () => {}, onEditRequest: () => {}, onShareRequest: () => {}, canEdit: true };
10
+ const callbacks = { onLineActivate: () => {} };
11
+
12
+ // a working copy with the context paragraph cut, so it renders as a del block
13
+ const CUT_WORKING = FIXTURE_PLAN.replace("The daemon persists sessions to disk atomically.\n", "");
10
14
 
11
15
  export const AnnotatedPlan: Story = {
12
16
  render: () => (
@@ -17,11 +21,12 @@ export const AnnotatedPlan: Story = {
17
21
  cursor={2}
18
22
  activeSpan={null}
19
23
  compose={null}
24
+ popover={null}
20
25
  editOrphanCount={0}
21
26
  {...callbacks}
22
27
  />
23
28
  ),
24
- expectedColors: [DARK.markCommentBackground, DARK.markSuggestionBackground, DARK.cursorBackground],
29
+ expectedColors: [DARK.markCommentBackground, DARK.cursorBackground],
25
30
  size: { width: 90, height: 28 },
26
31
  };
27
32
 
@@ -34,6 +39,7 @@ export const SpanSelection: Story = {
34
39
  cursor={2}
35
40
  activeSpan={{ displayIndex: 2, start: 0, end: 10 }}
36
41
  compose={null}
42
+ popover={null}
37
43
  editOrphanCount={0}
38
44
  {...callbacks}
39
45
  />
@@ -56,6 +62,7 @@ export const InlineCompose: Story = {
56
62
  quote: "The daemon",
57
63
  draft: { text: "Which daemon?", onInput: () => {}, onSave: () => {}, onCancel: () => {} },
58
64
  }}
65
+ popover={null}
59
66
  editOrphanCount={0}
60
67
  {...callbacks}
61
68
  />
@@ -63,6 +70,30 @@ export const InlineCompose: Story = {
63
70
  size: { width: 90, height: 30 },
64
71
  };
65
72
 
73
+ /** Span mode shows the marker-actions toolbar inline below the marked block. */
74
+ // The marker popover floats as an absolute overlay; the virtual terminal used
75
+ // for story snapshots cannot composite it, so it is covered by the standalone
76
+ // MarkerPopover stories and a functional App test rather than a PlanSheet snapshot.
77
+
78
+ /** A cut block renders struck-through and grayed inline - no red, no [cut] tag. */
79
+ export const WithCutBlock: Story = {
80
+ render: () => (
81
+ <PlanSheet
82
+ session={fixturePlanSession()}
83
+ display={buildDisplay(FIXTURE_PLAN, CUT_WORKING)}
84
+ marks={new Map()}
85
+ cursor={0}
86
+ activeSpan={null}
87
+ compose={null}
88
+ popover={null}
89
+ editOrphanCount={0}
90
+ {...callbacks}
91
+ />
92
+ ),
93
+ expectedColors: [DARK.textDim],
94
+ size: { width: 90, height: 28 },
95
+ };
96
+
66
97
  export const OrphanBanner: Story = {
67
98
  render: () => (
68
99
  <PlanSheet
@@ -72,6 +103,7 @@ export const OrphanBanner: Story = {
72
103
  cursor={0}
73
104
  activeSpan={null}
74
105
  compose={null}
106
+ popover={null}
75
107
  editOrphanCount={1}
76
108
  {...callbacks}
77
109
  />