@cueloop/client 0.1.0-alpha.75 → 0.1.0-alpha.76

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 (59) hide show
  1. package/package.json +5 -5
  2. package/src/App.focus.test.ts +147 -0
  3. package/src/App.regression.test.tsx +94 -0
  4. package/src/App.test.tsx +11 -8
  5. package/src/App.tsx +476 -220
  6. package/src/App.visual.test.tsx +57 -0
  7. package/src/App.workbench.test.tsx +41 -22
  8. package/src/app-screens.tsx +168 -123
  9. package/src/components/AppShell.brand.test.tsx +1 -1
  10. package/src/components/AppShell.tsx +14 -1
  11. package/src/components/ChangesColumn.tsx +40 -12
  12. package/src/components/ChangesFileTree.test.tsx +61 -0
  13. package/src/components/ConfirmDialog.tsx +8 -1
  14. package/src/components/DiffContentView.tsx +7 -1
  15. package/src/components/DiscussionMarkerRail.tsx +2 -2
  16. package/src/components/EditorGrid.tsx +84 -26
  17. package/src/components/GridTabContent.tsx +10 -1
  18. package/src/components/PanelColumn.tsx +3 -0
  19. package/src/components/ProjectTreeView.test.tsx +39 -12
  20. package/src/components/ProjectTreeView.tsx +38 -10
  21. package/src/components/PromptDialog.test.tsx +61 -0
  22. package/src/components/PromptDialog.tsx +16 -3
  23. package/src/components/RootOverlay.stories.tsx +1 -0
  24. package/src/components/RootOverlay.tsx +19 -6
  25. package/src/components/SettingsDialog.stories.tsx +3 -3
  26. package/src/components/SettingsDialog.tsx +3 -4
  27. package/src/components/ThreadFooter.test.tsx +12 -7
  28. package/src/components/ThreadFooter.tsx +18 -4
  29. package/src/components/ThreadTree.tsx +79 -36
  30. package/src/components/ThreadView.slash.test.tsx +36 -0
  31. package/src/components/ThreadView.tsx +7 -1
  32. package/src/components/WelcomePlayground.tsx +2 -0
  33. package/src/components/__snapshots__/stories.test.tsx.snap +60 -51
  34. package/src/components/menu-control.stories.tsx +35 -0
  35. package/src/components/menu-control.tsx +43 -0
  36. package/src/components/primitives/Dialog.test.tsx +37 -0
  37. package/src/components/primitives/Dialog.tsx +5 -0
  38. package/src/components/primitives/icons.ts +1 -1
  39. package/src/components/quick-actions-editor.stories.tsx +2 -0
  40. package/src/components/quick-actions-editor.test.tsx +35 -1
  41. package/src/components/quick-actions-editor.tsx +23 -22
  42. package/src/components/session-tree.test.ts +3 -3
  43. package/src/components/session-tree.ts +1 -1
  44. package/src/config.ts +1 -0
  45. package/src/diff-intraline.ts +26 -5
  46. package/src/perf/otlp-trace.test.ts +116 -0
  47. package/src/perf/otlp-trace.ts +130 -0
  48. package/src/perf/perf-report.ts +17 -0
  49. package/src/perf/perf-timings.test.ts +18 -0
  50. package/src/perf/perf-timings.ts +38 -0
  51. package/src/run.ts +22 -2
  52. package/src/thread-chords.test.ts +33 -1
  53. package/src/thread-chords.ts +114 -29
  54. package/src/thread-controller.ts +4 -3
  55. package/src/use-annotation-surface.tsx +22 -0
  56. package/src/use-frame-measure.ts +4 -1
  57. package/src/use-settings-dialog.tsx +9 -7
  58. package/src/visual/contrast.test.ts +35 -0
  59. package/src/visual/contrast.ts +33 -0
@@ -793,7 +793,7 @@ exports[`stories catalog Overlays/PromptDialog/NameYourself renders, snapshots,
793
793
  │ Your name (optional) - it attributes the notes │
794
794
  │ you leave: │
795
795
  │ your name │
796
- │ enter save · esc cancel │
796
+ │ enter save esc cancel │
797
797
  └────────────────────────────────────────────────────┘
798
798
 
799
799
 
@@ -813,7 +813,7 @@ exports[`stories catalog Overlays/PromptDialog/RenameAuthor renders, snapshots,
813
813
  │ Display name for this collaborator: │
814
814
  │ Robin │
815
815
  │ │
816
- │ enter save · esc cancel │
816
+ │ enter save esc cancel │
817
817
  └────────────────────────────────────────────────────┘
818
818
 
819
819
 
@@ -878,10 +878,10 @@ exports[`stories catalog Overlays/SettingsDialog/AppearanceThemed renders, snaps
878
878
 
879
879
 
880
880
  ┌─ Settings ───────────────────────────────────────────────────────────────┐
881
- │ General │ Appearance │
882
- │ Display │ the color theme │
881
+ │ General │ Theme Rosé Pine Moon │
882
+ │ Display │ │
883
883
  │ Appearance │ │
884
- │ │ Theme Rosé Pine Moon │
884
+ │ │ │
885
885
  │ │ │
886
886
  │ │ │
887
887
  │ │ │
@@ -912,11 +912,11 @@ exports[`stories catalog Overlays/SettingsDialog/BodyZone renders, snapshots, an
912
912
 
913
913
 
914
914
  ┌─ Settings ───────────────────────────────────────────────────────────────┐
915
- │ General │ Display │
916
- │ Display │ plan width and chrome │
915
+ │ General │ Plan width default │
916
+ │ Display │ Line numbers on │
917
917
  │ Appearance │ │
918
- │ │ Plan width default │
919
- │ │ Line numbers on │
918
+ │ │ │
919
+ │ │ │
920
920
  │ │ │
921
921
  │ │ │
922
922
  │ │ │
@@ -946,11 +946,11 @@ exports[`stories catalog Overlays/SettingsDialog/NavZone renders, snapshots, and
946
946
 
947
947
 
948
948
  ┌─ Settings ───────────────────────────────────────────────────────────────┐
949
- │ General │ General │
950
- │ Display │ identity and submission │
949
+ │ General │ Display name amber-heron │
950
+ │ Display │ Auto-close on submit 3s │
951
951
  │ Appearance │ │
952
- │ │ Display name amber-heron │
953
- │ │ Auto-close on submit 3s │
952
+ │ │ │
953
+ │ │ │
954
954
  │ │ │
955
955
  │ │ │
956
956
  │ │ │
@@ -1044,6 +1044,7 @@ exports[`stories catalog Surfaces/ThreadTree/TwoPending renders, snapshots, and
1044
1044
  " Projects
1045
1045
   widget
1046
1046
  Migration Plan
1047
+
1047
1048
  Threads
1048
1049
  working tree
1049
1050
 
@@ -1052,7 +1053,6 @@ exports[`stories catalog Surfaces/ThreadTree/TwoPending renders, snapshots, and
1052
1053
 
1053
1054
 
1054
1055
 
1055
-
1056
1056
  "
1057
1057
  `;
1058
1058
 
@@ -1098,6 +1098,7 @@ exports[`stories catalog Surfaces/ThreadsSidebar/Open renders, snapshots, and ca
1098
1098
  " Projects │
1099
1099
   widget │
1100
1100
  Migration Plan │
1101
+ │
1101
1102
  Threads │
1102
1103
  working tree │
1103
1104
  │
@@ -1106,7 +1107,6 @@ exports[`stories catalog Surfaces/ThreadsSidebar/Open renders, snapshots, and ca
1106
1107
  │
1107
1108
  │
1108
1109
  │
1109
- │
1110
1110
  "
1111
1111
  `;
1112
1112
 
@@ -1753,54 +1753,54 @@ exports[`stories catalog Primitives/Tree/ProjectsThreads renders, snapshots, and
1753
1753
  `;
1754
1754
 
1755
1755
  exports[`stories catalog Chrome/QuickActionsEditor/Collapsed renders, snapshots, and carries its colors: Chrome/QuickActionsEditor/Collapsed 1`] = `
1756
- " click a prompt to edit its system reset to
1757
- prompt defaults ▸
1756
+ " reset
1758
1757
 
1759
- ▸ Zoom out, research in depth
1760
- Research the broader problem, prior art, an…
1761
- ▸ Restate simplified
1762
- Restate this as the simplest thing that wor…
1763
- ▸ Out of scope
1764
- This is out of scope for the task, so captu…
1765
- ▸ Let's chat about this
1766
- Do not implement yet, and surface the open …
1767
- ▸ Prototype this
1768
- Build a throwaway prototype to answer the q…
1769
- ▸ Ensure 0 regressions
1770
- Characterize the current behavior with test…
1771
- ▸ Consider existing repo patterns
1772
- Follow the nearest existing pattern in this…
1773
- ▸ LGTM
1774
- This looks good to me.
1758
+ Zoom out, research in depth
1759
+ Research the broader problem, prior art, an…
1760
+ Restate simplified
1761
+ Restate this as the simplest thing that wor…
1762
+ Out of scope
1763
+ This is out of scope for the task, so captu…
1764
+ Let's chat about this
1765
+ Do not implement yet, and surface the open …
1766
+ Prototype this
1767
+ Build a throwaway prototype to answer the q…
1768
+ Ensure 0 regressions
1769
+ Characterize the current behavior with test…
1770
+ Consider existing repo patterns
1771
+ Follow the nearest existing pattern in this…
1772
+ LGTM
1773
+ This looks good to me.
1775
1774
  + Add action
1776
1775
 
1777
1776
 
1777
+
1778
1778
  "
1779
1779
  `;
1780
1780
 
1781
1781
  exports[`stories catalog Chrome/QuickActionsEditor/RowExpanded renders, snapshots, and carries its colors: Chrome/QuickActionsEditor/RowExpanded 1`] = `
1782
- " click a prompt to edit its system reset to
1783
- prompt defaults ▸
1782
+ " reset
1784
1783
 
1785
- ▸ Zoom out, research in depth
1786
- Research the broader problem, prior art, an…
1787
- ▾ Restate simplified
1788
- > Restate this as the simplest thing that works
1789
- ▸ Out of scope
1790
- This is out of scope for the task, so captu…
1791
- ▸ Let's chat about this
1792
- Do not implement yet, and surface the open …
1793
- ▸ Prototype this
1794
- Build a throwaway prototype to answer the q…
1795
- ▸ Ensure 0 regressions
1796
- Characterize the current behavior with test…
1797
- ▸ Consider existing repo patterns
1798
- Follow the nearest existing pattern in this…
1799
- ▸ LGTM
1800
- This looks good to me.
1784
+ Zoom out, research in depth
1785
+ Research the broader problem, prior art, an…
1786
+ Restate simplified
1787
+ Restate this as the simplest thing that works and
1788
+ Out of scope
1789
+ This is out of scope for the task, so captu…
1790
+ Let's chat about this
1791
+ Do not implement yet, and surface the open …
1792
+ Prototype this
1793
+ Build a throwaway prototype to answer the q…
1794
+ Ensure 0 regressions
1795
+ Characterize the current behavior with test…
1796
+ Consider existing repo patterns
1797
+ Follow the nearest existing pattern in this…
1798
+ LGTM
1799
+ This looks good to me.
1801
1800
  + Add action
1802
1801
 
1803
1802
 
1803
+
1804
1804
  "
1805
1805
  `;
1806
1806
 
@@ -1885,5 +1885,14 @@ exports[`stories catalog Foundations/Theme/ProviderSwap renders, snapshots, and
1885
1885
 
1886
1886
 
1887
1887
 
1888
+ "
1889
+ `;
1890
+
1891
+ exports[`stories catalog Layout/MenuControl/OneOpenAtATime renders, snapshots, and carries its colors: Layout/MenuControl/OneOpenAtATime 1`] = `
1892
+ "
1893
+ alpha: open
1894
+ beta: closed
1895
+
1896
+
1888
1897
  "
1889
1898
  `;
@@ -0,0 +1,35 @@
1
+ import React from "react";
2
+ import { DARK } from "../theme";
3
+ import type { Story, StoryMeta } from "./story";
4
+ import { MenuControlProvider, useMenuControl } from "./menu-control";
5
+
6
+ export const meta: StoryMeta = { title: "Layout/MenuControl" };
7
+
8
+ // One open menu at a time: with "alpha" open, only its row reads as open.
9
+ function MenuControlDemo(): React.ReactNode {
10
+ const { openMenuId } = useMenuControl();
11
+ const row = (id: string): React.ReactNode => (
12
+ <text fg={openMenuId === id ? DARK.accent : DARK.textDim}>
13
+ {`${id}: ${openMenuId === id ? "open" : "closed"}`}
14
+ </text>
15
+ );
16
+
17
+ return (
18
+ <box style={{ flexDirection: "column" }}>
19
+ {row("alpha")}
20
+ {row("beta")}
21
+ </box>
22
+ );
23
+ }
24
+
25
+ export const OneOpenAtATime: Story = {
26
+ render: () => (
27
+ <MenuControlProvider value={{ openMenuId: "alpha", toggleMenu: () => {}, closeMenu: () => {} }}>
28
+ <box style={{ padding: 1, backgroundColor: DARK.panel }}>
29
+ <MenuControlDemo />
30
+ </box>
31
+ </MenuControlProvider>
32
+ ),
33
+ expectedColors: [DARK.accent],
34
+ size: { width: 30, height: 5 },
35
+ };
@@ -0,0 +1,43 @@
1
+ // One open menu at a time across a shell: the thread row menu and the editor split menu share this
2
+ // single open id, so opening either closes the other. Owned by App and by the no-thread shell, one each.
3
+
4
+ import React, { createContext, useContext, useMemo, useState } from "react";
5
+
6
+ export interface MenuControlApi {
7
+ openMenuId: string | null;
8
+ toggleMenu: (id: string) => void;
9
+ closeMenu: () => void;
10
+ }
11
+
12
+ const MenuControlContext = createContext<MenuControlApi>({
13
+ openMenuId: null,
14
+ toggleMenu: () => {},
15
+ closeMenu: () => {},
16
+ });
17
+
18
+ export function useMenuControl(): MenuControlApi {
19
+ return useContext(MenuControlContext);
20
+ }
21
+
22
+ export function useMenuControlState(): MenuControlApi {
23
+ const [openMenuId, setOpenMenuId] = useState<string | null>(null);
24
+
25
+ return useMemo(
26
+ () => ({
27
+ openMenuId,
28
+ toggleMenu: (id: string) => setOpenMenuId((current) => (current === id ? null : id)),
29
+ closeMenu: () => setOpenMenuId(null),
30
+ }),
31
+ [openMenuId],
32
+ );
33
+ }
34
+
35
+ export function MenuControlProvider({
36
+ value,
37
+ children,
38
+ }: {
39
+ value: MenuControlApi;
40
+ children: React.ReactNode;
41
+ }): React.ReactNode {
42
+ return <MenuControlContext.Provider value={value}>{children}</MenuControlContext.Provider>;
43
+ }
@@ -0,0 +1,37 @@
1
+ /** The dialog dismisses on a backdrop click (outside the panel) but never on a click inside it. */
2
+
3
+ import { test, expect } from "bun:test";
4
+ import React from "react";
5
+ import { testRender } from "@opentui/react/test-utils";
6
+ import { Dialog } from "./Dialog";
7
+ import { settle } from "../../test-support";
8
+ import { DARK } from "../../theme";
9
+
10
+ test("clicking the backdrop dismisses; clicking inside the panel does not", async () => {
11
+ let dismissed = 0;
12
+ const setup = await testRender(
13
+ <Dialog
14
+ isOpen
15
+ title=" test "
16
+ width={20}
17
+ height={5}
18
+ background={DARK.elevated}
19
+ onDismiss={() => (dismissed += 1)}
20
+ theme={DARK}
21
+ >
22
+ <text>body</text>
23
+ </Dialog>,
24
+ { width: 60, height: 20 },
25
+ );
26
+ await settle(setup);
27
+
28
+ // the top-left corner is backdrop, outside the centered panel
29
+ await setup.mockMouse.click(0, 0);
30
+ expect(dismissed).toBe(1);
31
+
32
+ // the screen centre lands inside the panel, so it must not dismiss
33
+ await setup.mockMouse.click(30, 10);
34
+ expect(dismissed).toBe(1);
35
+
36
+ setup.renderer.destroy();
37
+ });
@@ -17,6 +17,8 @@ export interface DialogProps {
17
17
  height: number;
18
18
  /** Solid panel fill for content-heavy dialogs; default transparent. */
19
19
  background?: string;
20
+ /** Dismiss on a click outside the panel. */
21
+ onDismiss?: () => void;
20
22
  theme?: Theme;
21
23
  children: React.ReactNode;
22
24
  }
@@ -27,6 +29,7 @@ export function Dialog({
27
29
  width,
28
30
  height,
29
31
  background,
32
+ onDismiss,
30
33
  theme,
31
34
  children,
32
35
  }: DialogProps): React.ReactNode {
@@ -36,6 +39,7 @@ export function Dialog({
36
39
 
37
40
  return (
38
41
  <box
42
+ onMouseUp={onDismiss}
39
43
  style={{
40
44
  position: "absolute",
41
45
  left: 0,
@@ -48,6 +52,7 @@ export function Dialog({
48
52
  }}
49
53
  >
50
54
  <box
55
+ onMouseUp={(event) => event.stopPropagation()}
51
56
  style={{
52
57
  width,
53
58
  height,
@@ -13,7 +13,7 @@ export const NERD = {
13
13
  copy: "",
14
14
  unfold: "",
15
15
  kebab: "⋮",
16
- pin: "⚑",
16
+ star: "★",
17
17
  close: "✕",
18
18
  listTree: "",
19
19
  sidebarLeft: "",
@@ -24,6 +24,7 @@ export const Collapsed: Story = {
24
24
  actions={DEFAULT_QUICK_ACTIONS}
25
25
  selectedIndex={0}
26
26
  expandedIndex={null}
27
+ expandedField="prompt"
27
28
  onToggleExpand={noop}
28
29
  onEditPrompt={() => {}}
29
30
  onEditMetadata={noop}
@@ -43,6 +44,7 @@ export const RowExpanded: Story = {
43
44
  actions={DEFAULT_QUICK_ACTIONS}
44
45
  selectedIndex={1}
45
46
  expandedIndex={1}
47
+ expandedField="prompt"
46
48
  onToggleExpand={noop}
47
49
  onEditPrompt={() => {}}
48
50
  onEditMetadata={noop}
@@ -4,7 +4,7 @@ import { describe, expect, test } from "bun:test";
4
4
  import React from "react";
5
5
  import { testRender } from "@opentui/react/test-utils";
6
6
  import type { QuickAction } from "../config";
7
- import { settle, typeText } from "../test-support";
7
+ import { locateText, settle, typeText } from "../test-support";
8
8
  import { QuickActionsEditor } from "./quick-actions-editor";
9
9
 
10
10
  const MANY: QuickAction[] = Array.from({ length: 14 }, (_, index) => ({
@@ -17,6 +17,7 @@ async function mount(selectedIndex: number) {
17
17
  actions={MANY}
18
18
  selectedIndex={selectedIndex}
19
19
  expandedIndex={null}
20
+ expandedField="prompt"
20
21
  onToggleExpand={() => {}}
21
22
  onEditPrompt={() => {}}
22
23
  onEditMetadata={() => {}}
@@ -58,6 +59,7 @@ describe("editing an expanded action", () => {
58
59
  actions={[{ prompt: "Ship it", metadata: "be terse" }]}
59
60
  selectedIndex={0}
60
61
  expandedIndex={0}
62
+ expandedField="prompt"
61
63
  onToggleExpand={() => {}}
62
64
  onEditPrompt={(_index, prompt) => prompts.push(prompt)}
63
65
  onEditMetadata={(_index, metadata) => metadatas.push(metadata)}
@@ -80,4 +82,36 @@ describe("editing an expanded action", () => {
80
82
 
81
83
  setup.renderer.destroy();
82
84
  });
85
+
86
+ test("clicking the description row opens the editor on the metadata field directly", async () => {
87
+ const toggles: Array<{ index: number; field: "prompt" | "metadata" }> = [];
88
+ const setup = await testRender(
89
+ <QuickActionsEditor
90
+ actions={[{ prompt: "Ship it", metadata: "be terse" }]}
91
+ selectedIndex={0}
92
+ expandedIndex={null}
93
+ expandedField="prompt"
94
+ onToggleExpand={(index, field) => toggles.push({ index, field })}
95
+ onEditPrompt={() => {}}
96
+ onEditMetadata={() => {}}
97
+ onReset={() => {}}
98
+ onAdd={() => {}}
99
+ />,
100
+ { width: 60, height: 8 },
101
+ );
102
+ await settle(setup);
103
+
104
+ // clicking the title opens the title field; clicking the description opens the metadata field
105
+ const title = locateText(setup, "Ship it");
106
+ await setup.mockMouse.click(title.column, title.row);
107
+ const description = locateText(setup, "be terse");
108
+ await setup.mockMouse.click(description.column, description.row);
109
+
110
+ expect(toggles).toEqual([
111
+ { index: 0, field: "prompt" },
112
+ { index: 0, field: "metadata" },
113
+ ]);
114
+
115
+ setup.renderer.destroy();
116
+ });
83
117
  });
@@ -22,8 +22,10 @@ export interface QuickActionsEditorProps {
22
22
  selectedIndex: number;
23
23
  /** The row whose system-prompt input is open and focused, or null. */
24
24
  expandedIndex: number | null;
25
- /** Select and toggle a row's editor open/closed. */
26
- onToggleExpand: (index: number) => void;
25
+ /** Which field the open editor focuses first - set by the clicked row. */
26
+ expandedField: "prompt" | "metadata";
27
+ /** Select and toggle a row's editor open/closed, focusing the given field. */
28
+ onToggleExpand: (index: number, field: "prompt" | "metadata") => void;
27
29
  onEditPrompt: (index: number, prompt: string) => void;
28
30
  onEditMetadata: (index: number, metadata: string) => void;
29
31
  onReset: () => void;
@@ -35,6 +37,7 @@ export function QuickActionsEditor({
35
37
  actions,
36
38
  selectedIndex,
37
39
  expandedIndex,
40
+ expandedField,
38
41
  onToggleExpand,
39
42
  onEditPrompt,
40
43
  onEditMetadata,
@@ -52,11 +55,9 @@ export function QuickActionsEditor({
52
55
  return (
53
56
  <box style={{ flexDirection: "column", flexGrow: 1 }}>
54
57
  <box style={{ flexDirection: "row" }}>
55
- <box style={{ flexGrow: 1 }}>
56
- <text fg={tokens.textDim}>click a prompt to edit its system prompt</text>
57
- </box>
58
+ <box style={{ flexGrow: 1 }} />
58
59
  <box onMouseUp={onReset}>
59
- <text fg={tokens.textDim}>reset to defaults ▸</text>
60
+ <text fg={tokens.textDim}>reset</text>
60
61
  </box>
61
62
  </box>
62
63
  <box style={{ height: 1 }} />
@@ -68,7 +69,8 @@ export function QuickActionsEditor({
68
69
  action={action}
69
70
  isSelected={index === selectedIndex}
70
71
  isExpanded={index === expandedIndex}
71
- onToggleExpand={() => onToggleExpand(index)}
72
+ initialField={expandedField}
73
+ onToggleExpand={(field) => onToggleExpand(index, field)}
72
74
  onEditPrompt={(prompt) => onEditPrompt(index, prompt)}
73
75
  onEditMetadata={(metadata) => onEditMetadata(index, metadata)}
74
76
  theme={theme}
@@ -91,6 +93,7 @@ function ActionRow({
91
93
  action,
92
94
  isSelected,
93
95
  isExpanded,
96
+ initialField,
94
97
  onToggleExpand,
95
98
  onEditPrompt,
96
99
  onEditMetadata,
@@ -100,7 +103,8 @@ function ActionRow({
100
103
  action: QuickAction;
101
104
  isSelected: boolean;
102
105
  isExpanded: boolean;
103
- onToggleExpand: () => void;
106
+ initialField: "prompt" | "metadata";
107
+ onToggleExpand: (field: "prompt" | "metadata") => void;
104
108
  onEditPrompt: (prompt: string) => void;
105
109
  onEditMetadata: (metadata: string) => void;
106
110
  theme?: Theme;
@@ -113,9 +117,10 @@ function ActionRow({
113
117
  <ActionEditor
114
118
  action={action}
115
119
  isSelected={isSelected}
120
+ initialField={initialField}
116
121
  onEditPrompt={onEditPrompt}
117
122
  onEditMetadata={onEditMetadata}
118
- onDone={onToggleExpand}
123
+ onDone={() => onToggleExpand("prompt")}
119
124
  tokens={tokens}
120
125
  />
121
126
  </box>
@@ -126,12 +131,12 @@ function ActionRow({
126
131
  <box id={rowId} style={{ flexDirection: "column" }}>
127
132
  <box
128
133
  style={{ backgroundColor: isSelected ? tokens.border : undefined }}
129
- onMouseUp={onToggleExpand}
134
+ onMouseUp={() => onToggleExpand("prompt")}
130
135
  >
131
- <text fg={isSelected ? tokens.text : tokens.textMuted}>{`▸ ${action.prompt}`}</text>
136
+ <text fg={isSelected ? tokens.text : tokens.textMuted}>{action.prompt}</text>
132
137
  </box>
133
138
  {action.metadata ? (
134
- <box style={{ paddingLeft: 2 }}>
139
+ <box onMouseUp={() => onToggleExpand("metadata")}>
135
140
  <text fg={tokens.textDim}>{truncateMetadata(action.metadata)}</text>
136
141
  </box>
137
142
  ) : null}
@@ -141,12 +146,13 @@ function ActionRow({
141
146
 
142
147
  /**
143
148
  * The two-field editor for an expanded row: the action title over its system
144
- * prompt. Mounts fresh per expand, so focus starts on the title without a reset
145
- * effect; ⏎ steps title -> description -> close, and a click focuses either.
149
+ * prompt. Mounts fresh per expand, so focus starts on the clicked field without a
150
+ * reset effect; ⏎ steps title -> description -> close, and a click focuses either.
146
151
  */
147
152
  function ActionEditor({
148
153
  action,
149
154
  isSelected,
155
+ initialField,
150
156
  onEditPrompt,
151
157
  onEditMetadata,
152
158
  onDone,
@@ -154,6 +160,7 @@ function ActionEditor({
154
160
  }: {
155
161
  action: QuickAction;
156
162
  isSelected: boolean;
163
+ initialField: "prompt" | "metadata";
157
164
  onEditPrompt: (prompt: string) => void;
158
165
  onEditMetadata: (metadata: string) => void;
159
166
  onDone: () => void;
@@ -161,7 +168,7 @@ function ActionEditor({
161
168
  }): React.ReactNode {
162
169
  const promptRef = useRef<TextareaRenderable | null>(null);
163
170
  const metadataRef = useRef<TextareaRenderable | null>(null);
164
- const [activeField, setActiveField] = useState<"prompt" | "metadata">("prompt");
171
+ const [activeField, setActiveField] = useState<"prompt" | "metadata">(initialField);
165
172
 
166
173
  // the focused field types from its end, not from the caret parked at the start
167
174
  useEffect(() => {
@@ -184,9 +191,6 @@ function ActionEditor({
184
191
  <box
185
192
  style={{ flexDirection: "row", backgroundColor: isSelected ? tokens.border : undefined }}
186
193
  >
187
- <box style={{ width: 2, flexShrink: 0 }}>
188
- <text fg={isSelected ? tokens.text : tokens.textMuted}>{"▾ "}</text>
189
- </box>
190
194
  <textarea
191
195
  ref={promptRef}
192
196
  focused={activeField === "prompt"}
@@ -199,10 +203,7 @@ function ActionEditor({
199
203
  style={fieldStyle}
200
204
  />
201
205
  </box>
202
- <box style={{ flexDirection: "row", paddingLeft: 2 }}>
203
- <box style={{ width: 2, flexShrink: 0 }}>
204
- <text fg={tokens.textDim}>{">"}</text>
205
- </box>
206
+ <box style={{ flexDirection: "row" }}>
206
207
  <textarea
207
208
  ref={metadataRef}
208
209
  focused={activeField === "metadata"}
@@ -112,19 +112,19 @@ describe("groupInbox", () => {
112
112
  expect(rows.some((row) => row.kind === "section" && row.label === "Threads")).toBe(true);
113
113
  });
114
114
 
115
- test("a pinned thread lifts into a Pinned section at the top, out of its normal group", () => {
115
+ test("a pinned thread lifts into a Starred section at the top, out of its normal group", () => {
116
116
  // Arrange - one repo-bound thread, one standalone; pin the repo-bound one
117
117
  const sessions = [session("a", "Pinned one", "root-1"), session("b", "Loose two")];
118
118
 
119
119
  // Act
120
120
  const { rows, ordered } = groupInbox(sessions, new Set(["a"]));
121
121
 
122
- // Assert - Pinned is the first section and holds thread a; a is gone from Projects
122
+ // Assert - Starred is the first section and holds thread a; a is gone from Projects
123
123
  const sections = rows
124
124
  .filter((row) => row.kind === "section")
125
125
  .map((row) => (row.kind === "section" ? row.label : ""));
126
126
 
127
- expect(sections[0]).toBe("Pinned");
127
+ expect(sections[0]).toBe("Starred");
128
128
  expect(sections).not.toContain("Projects"); // its only member was pinned away
129
129
  expect(ordered[0]!.id).toBe("a"); // the cursor walks pinned threads first
130
130
  const pinnedThreadIds = rows.filter((row) => row.kind === "thread").map((row) => row.id);
@@ -84,7 +84,7 @@ export function groupInbox(
84
84
  };
85
85
 
86
86
  if (pinned.length > 0) {
87
- rows.push({ kind: "section", id: "section:pinned", label: "Pinned" });
87
+ rows.push({ kind: "section", id: "section:pinned", label: "Starred" });
88
88
  for (const session of pinned) pushThread(session);
89
89
  }
90
90
 
package/src/config.ts CHANGED
@@ -151,6 +151,7 @@ export const DEFAULT_KEYS: CueloopConfig["keys"] = {
151
151
  share: ["S"],
152
152
  quit: ["q"],
153
153
  walk: ["w"],
154
+ leader: ["ctrl+g"],
154
155
  };
155
156
 
156
157
  const SkillsSectionSchema = v.object({ path: v.optional(v.string()) });