@cueloop/client 0.1.0-alpha.9 → 0.1.0-alpha.90
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/native/build-pty.sh +51 -0
- package/native/build.sh +82 -0
- package/native/darwin-arm64/libcuelooppty.dylib +0 -0
- package/native/darwin-arm64/libcueloopvt.dylib +0 -0
- package/native/src/pty.zig +193 -0
- package/native/src/shim.zig +114 -0
- package/package.json +42 -34
- package/src/App.appearance.test.tsx +53 -0
- package/src/App.autoclose.test.tsx +77 -51
- package/src/App.diff.test.tsx +259 -45
- package/src/App.export.test.tsx +58 -41
- package/src/App.focus.test.ts +89 -0
- package/src/App.identity.test.tsx +78 -0
- package/src/App.plan-render.test.tsx +209 -0
- package/src/App.plan-review.test.tsx +330 -161
- package/src/App.prototype-markdown.test.tsx +128 -0
- package/src/App.prototype.test.tsx +176 -0
- package/src/App.readonly.test.tsx +129 -67
- package/src/App.regression.test.tsx +94 -0
- package/src/App.rendering.test.tsx +61 -35
- package/src/App.share-dialog.test.tsx +172 -0
- package/src/App.submit-confirm.test.tsx +264 -0
- package/src/App.test.tsx +402 -126
- package/src/App.toast.test.tsx +98 -0
- package/src/App.tsx +1402 -1060
- package/src/App.visual.test.tsx +57 -0
- package/src/App.walk.test.tsx +299 -0
- package/src/App.workbench.test.tsx +774 -0
- package/src/__snapshots__/view-plan.regression.test.ts.snap +100 -0
- package/src/annotation-palette.ts +62 -0
- package/src/ansi-screen.test.ts +28 -0
- package/src/ansi-screen.ts +72 -0
- package/src/app-key-state.ts +61 -0
- package/src/app-screens.tsx +577 -0
- package/src/app-view-model.ts +168 -0
- package/src/attribution.test.ts +31 -0
- package/src/attribution.ts +42 -0
- package/src/client-extension-registry.test.tsx +61 -0
- package/src/client-extension-registry.ts +159 -0
- package/src/clipboard.test.ts +23 -0
- package/src/clipboard.ts +33 -0
- package/src/components/AnnotatableFileView.stories.tsx +42 -0
- package/src/components/AnnotatableFileView.test.tsx +102 -0
- package/src/components/AnnotatableFileView.tsx +134 -0
- package/src/components/AnnotationCards.stories.tsx +86 -0
- package/src/components/AnnotationCards.tsx +341 -0
- package/src/components/AnsiScreen.stories.tsx +12 -0
- package/src/components/AnsiScreen.tsx +28 -0
- package/src/components/AppHeader.stories.tsx +50 -0
- package/src/components/AppHeader.tsx +83 -0
- package/src/components/AppShell.brand.test.tsx +58 -0
- package/src/components/AppShell.extensions.test.tsx +191 -0
- package/src/components/AppShell.stories.tsx +89 -0
- package/src/components/AppShell.tsx +362 -0
- package/src/components/AppShell.zoom.test.tsx +135 -0
- package/src/components/BareWorkbenchFileView.stories.tsx +27 -0
- package/src/components/BareWorkbenchFileView.tsx +64 -0
- package/src/components/Breadcrumb.stories.tsx +19 -0
- package/src/components/Breadcrumb.tsx +45 -0
- package/src/components/ChangesColumn.stories.tsx +44 -0
- package/src/components/ChangesColumn.tsx +199 -0
- package/src/components/ChangesFileTree.test.tsx +61 -0
- package/src/components/CodeBlock.stories.tsx +42 -0
- package/src/components/CodeBlock.tsx +84 -0
- package/src/components/CompletionOverlay.stories.tsx +37 -0
- package/src/components/CompletionOverlay.tsx +77 -0
- package/src/components/ConfirmCard.stories.tsx +37 -0
- package/src/components/ConfirmCard.tsx +150 -0
- package/src/components/ConfirmDialog.stories.tsx +21 -0
- package/src/components/ConfirmDialog.tsx +72 -0
- package/src/components/DiffContentView.scroll.test.tsx +149 -0
- package/src/components/DiffContentView.split.test.tsx +140 -0
- package/src/components/DiffContentView.stories.tsx +80 -0
- package/src/components/DiffContentView.syntax.test.tsx +82 -0
- package/src/components/DiffContentView.tsx +906 -0
- package/src/components/DiscussionMarkerRail.stories.tsx +54 -0
- package/src/components/DiscussionMarkerRail.tsx +186 -0
- package/src/components/EditorGrid.stories.tsx +59 -0
- package/src/components/EditorGrid.tabs.test.tsx +118 -0
- package/src/components/EditorGrid.tsx +426 -0
- package/src/components/EditorGrid.zoom.test.tsx +68 -0
- package/src/components/ExtensionZone.stories.tsx +28 -0
- package/src/components/ExtensionZone.tsx +145 -0
- package/src/components/FileContentsView.stories.tsx +16 -0
- package/src/components/FileContentsView.test.tsx +29 -0
- package/src/components/FileContentsView.tsx +98 -0
- package/src/components/GridTabContent.stories.tsx +59 -0
- package/src/components/GridTabContent.tsx +182 -0
- package/src/components/HandleChipsInput.stories.tsx +24 -0
- package/src/components/HandleChipsInput.test.tsx +35 -0
- package/src/components/HandleChipsInput.tsx +145 -0
- package/src/components/KeybindsDialog.stories.tsx +31 -0
- package/src/components/KeybindsDialog.tsx +103 -0
- package/src/components/MarkdownGridBlock.stories.tsx +33 -0
- package/src/components/MarkdownGridBlock.tsx +72 -0
- package/src/components/MarkdownThreadEditor.stories.tsx +15 -0
- package/src/components/MarkdownThreadEditor.test.tsx +98 -0
- package/src/components/MarkdownThreadEditor.tsx +162 -0
- package/src/components/MenuBar.stories.tsx +28 -0
- package/src/components/MenuBar.tsx +44 -0
- package/src/components/NavModeHint.stories.tsx +40 -0
- package/src/components/NavModeHint.tsx +31 -0
- package/src/components/OverlayScrollbar.stories.tsx +33 -0
- package/src/components/OverlayScrollbar.test.tsx +139 -0
- package/src/components/OverlayScrollbar.tsx +129 -0
- package/src/components/PaneGrid.stories.tsx +84 -0
- package/src/components/PaneGrid.tsx +128 -0
- package/src/components/PanelColumn.stories.tsx +25 -0
- package/src/components/PanelColumn.tsx +98 -0
- package/src/components/ProjectTreeView.stories.tsx +25 -0
- package/src/components/ProjectTreeView.test.tsx +75 -0
- package/src/components/ProjectTreeView.tsx +125 -0
- package/src/components/PromptDialog.stories.tsx +36 -0
- package/src/components/PromptDialog.test.tsx +61 -0
- package/src/components/PromptDialog.tsx +99 -0
- package/src/components/PrototypeContentView.stories.tsx +19 -0
- package/src/components/PrototypeContentView.tsx +446 -0
- package/src/components/RootOverlay.stories.tsx +45 -0
- package/src/components/RootOverlay.tsx +48 -0
- package/src/components/ScrollArea.stories.tsx +20 -0
- package/src/components/ScrollArea.tsx +44 -0
- package/src/components/SettingsDialog.stories.tsx +117 -0
- package/src/components/SettingsDialog.tsx +186 -0
- package/src/components/SettingsRows.stories.tsx +18 -0
- package/src/components/SettingsRows.tsx +112 -0
- package/src/components/ShareDialog.stories.tsx +121 -0
- package/src/components/ShareDialog.tsx +552 -0
- package/src/components/ShellHeader.stories.tsx +50 -0
- package/src/components/ShellHeader.tsx +137 -0
- package/src/components/SlashComposer.stories.tsx +37 -0
- package/src/components/SlashComposer.tsx +87 -0
- package/src/components/ThreadFooter.stories.tsx +30 -0
- package/src/components/ThreadFooter.test.tsx +47 -0
- package/src/components/ThreadFooter.tsx +76 -0
- package/src/components/ThreadTree.stories.tsx +46 -0
- package/src/components/ThreadTree.tsx +255 -0
- package/src/components/ThreadView.selection.test.tsx +951 -0
- package/src/components/ThreadView.slash.test.tsx +146 -0
- package/src/components/ThreadView.stories.tsx +26 -0
- package/src/components/ThreadView.tsx +525 -0
- package/src/components/Toast.stories.tsx +19 -0
- package/src/components/Toast.tsx +52 -0
- package/src/components/Tooltip.stories.tsx +26 -0
- package/src/components/Tooltip.tsx +72 -0
- package/src/components/TreePane.stories.tsx +106 -0
- package/src/components/TreePane.tsx +157 -0
- package/src/components/WalkWizard.stories.tsx +100 -0
- package/src/components/WalkWizard.tsx +150 -0
- package/src/components/WelcomePlayground.stories.tsx +12 -0
- package/src/components/WelcomePlayground.test.tsx +49 -0
- package/src/components/WelcomePlayground.tsx +139 -0
- package/src/components/WorkspacePanels.stories.tsx +36 -0
- package/src/components/WorkspacePanels.tsx +116 -0
- package/src/components/__snapshots__/stories.test.tsx.snap +2264 -0
- package/src/components/diff-content-view-layout.test.ts +198 -0
- package/src/components/diff-content-view-layout.ts +84 -0
- package/src/components/editor-grid.test.ts +86 -0
- package/src/components/editor-grid.ts +138 -0
- package/src/components/file-tree.test.ts +60 -0
- package/src/components/file-tree.ts +112 -0
- package/src/components/menu-control.stories.tsx +35 -0
- package/src/components/menu-control.tsx +43 -0
- package/src/components/pages/Workspace.stories.tsx +181 -0
- package/src/components/primitives/Button.stories.tsx +32 -0
- package/src/components/primitives/Button.tsx +59 -0
- package/src/components/primitives/Card.stories.tsx +25 -0
- package/src/components/primitives/Card.tsx +63 -0
- package/src/components/primitives/Dialog.stories.tsx +24 -0
- package/src/components/primitives/Dialog.test.tsx +37 -0
- package/src/components/primitives/Dialog.tsx +71 -0
- package/src/components/primitives/DialogActions.stories.tsx +20 -0
- package/src/components/primitives/DialogActions.tsx +39 -0
- package/src/components/primitives/IconButton.stories.tsx +36 -0
- package/src/components/primitives/IconButton.tsx +62 -0
- package/src/components/primitives/StatusBar.stories.tsx +11 -0
- package/src/components/primitives/StatusBar.tsx +23 -0
- package/src/components/primitives/Switch.stories.tsx +17 -0
- package/src/components/primitives/Switch.tsx +32 -0
- package/src/components/primitives/Tabs.stories.tsx +29 -0
- package/src/components/primitives/Tabs.tsx +102 -0
- package/src/components/primitives/Toolbar.stories.tsx +17 -0
- package/src/components/primitives/Toolbar.tsx +14 -0
- package/src/components/primitives/Tree.stories.tsx +124 -0
- package/src/components/primitives/Tree.tsx +100 -0
- package/src/components/primitives/frame.test.ts +12 -0
- package/src/components/primitives/frame.ts +17 -0
- package/src/components/primitives/icons.ts +61 -0
- package/src/components/primitives/tree-model.test.ts +99 -0
- package/src/components/primitives/tree-model.ts +137 -0
- package/src/components/quick-actions-editor.stories.tsx +58 -0
- package/src/components/quick-actions-editor.test.tsx +117 -0
- package/src/components/quick-actions-editor.tsx +226 -0
- package/src/components/share-dialog-store.ts +58 -0
- package/src/components/stories-app.tsx +193 -0
- package/src/components/stories.test.tsx +74 -0
- package/src/components/story-fixtures.ts +102 -0
- package/src/components/story-tree.test.ts +108 -0
- package/src/components/story.ts +159 -0
- package/src/components/syntax-highlight.ts +133 -0
- package/src/components/terminal-pane.test.tsx +40 -0
- package/src/components/terminal-pane.ts +199 -0
- package/src/components/theme-context.stories.tsx +46 -0
- package/src/components/theme-context.tsx +37 -0
- package/src/components/thread-tree.test.ts +134 -0
- package/src/components/thread-tree.ts +109 -0
- package/src/components/truncate-text.ts +7 -0
- package/src/components/truncate-title.test.ts +31 -0
- package/src/components/truncate-title.ts +15 -0
- package/src/config-identity.test.ts +78 -0
- package/src/config.test.ts +579 -34
- package/src/config.ts +533 -57
- package/src/diff-fold.test.ts +78 -0
- package/src/diff-fold.ts +126 -0
- package/src/diff-hunk-curate.test.ts +315 -0
- package/src/diff-hunk-curate.ts +153 -0
- package/src/diff-intraline.test.ts +238 -0
- package/src/diff-intraline.ts +236 -0
- package/src/diff-syntax.test.ts +112 -0
- package/src/diff-syntax.ts +179 -0
- package/src/discussions.ts +79 -0
- package/src/editor.test.ts +149 -0
- package/src/editor.ts +139 -9
- package/src/ghostty-terminal.test.ts +85 -0
- package/src/ghostty-terminal.ts +199 -0
- package/src/github-identity.test.ts +58 -0
- package/src/github-identity.ts +41 -0
- package/src/herdr.test.ts +9 -34
- package/src/herdr.ts +25 -21
- package/src/index.ts +28 -0
- package/src/integrations.test.ts +41 -0
- package/src/integrations.ts +32 -0
- package/src/intent-dispatch.test.ts +724 -0
- package/src/intent-dispatch.ts +690 -0
- package/src/key-bindings.test.ts +156 -0
- package/src/key-bindings.ts +459 -0
- package/src/keymap.test.ts +456 -115
- package/src/keymap.ts +315 -81
- package/src/kitty-image.test.ts +66 -0
- package/src/kitty-image.ts +138 -0
- package/src/launch-layout.test.ts +67 -0
- package/src/launch-layout.ts +47 -0
- package/src/mark-runs.ts +121 -0
- package/src/markdown-editor-syntax-style.ts +50 -0
- package/src/markdown-grid.test.ts +101 -0
- package/src/markdown-grid.ts +203 -0
- package/src/markdown-highlight.test.ts +61 -0
- package/src/markdown-highlight.ts +122 -0
- package/src/pasted-image.test.ts +67 -0
- package/src/pasted-image.ts +60 -0
- package/src/perf/otlp-trace.test.ts +116 -0
- package/src/perf/otlp-trace.ts +130 -0
- package/src/perf/perf-report.ts +17 -0
- package/src/perf/perf-timings.test.ts +18 -0
- package/src/perf/perf-timings.ts +38 -0
- package/src/prototype-browser.test.ts +81 -0
- package/src/prototype-browser.ts +329 -0
- package/src/prototype-pixels.tsx +35 -0
- package/src/pty.test.ts +81 -0
- package/src/pty.ts +245 -0
- package/src/ready-signal.test.ts +85 -0
- package/src/ready-signal.ts +52 -0
- package/src/run.ts +85 -12
- package/src/serve.test.ts +24 -12
- package/src/serve.ts +52 -10
- package/src/share.ts +237 -0
- package/src/skills.test.ts +74 -0
- package/src/skills.ts +75 -0
- package/src/slash-palette.test.ts +103 -0
- package/src/slash-palette.ts +129 -0
- package/src/split-diff.test.ts +110 -0
- package/src/split-diff.ts +149 -0
- package/src/test-support.test.ts +264 -0
- package/src/test-support.ts +369 -0
- package/src/theme-presets.test.ts +51 -0
- package/src/theme-presets.ts +219 -0
- package/src/theme.ts +88 -19
- package/src/thread-chords.test.ts +79 -0
- package/src/thread-chords.ts +163 -0
- package/src/thread-code-syntax.ts +45 -0
- package/src/thread-controller.curate.test.ts +476 -0
- package/src/thread-controller.derivation.test.ts +62 -0
- package/src/thread-controller.live-diff.test.ts +322 -0
- package/src/thread-controller.optimistic.test.ts +69 -0
- package/src/thread-controller.share.test.ts +561 -0
- package/src/thread-controller.switch-cache.test.ts +176 -0
- package/src/thread-controller.tree.test.ts +261 -0
- package/src/thread-controller.ts +2050 -0
- package/src/thread-selection.test.ts +210 -0
- package/src/thread-selection.ts +243 -0
- package/src/tree-view.test.ts +128 -0
- package/src/tree-view.ts +153 -0
- package/src/use-annotation-surface.tsx +975 -0
- package/src/use-changes-workbench.ts +190 -0
- package/src/use-frame-measure.ts +41 -0
- package/src/use-remember-layout.ts +31 -0
- package/src/use-repo-changes.ts +87 -0
- package/src/use-settings-dialog.tsx +364 -0
- package/src/use-terminal-virtualizer.ts +259 -0
- package/src/use-thread-body-editing.tsx +58 -0
- package/src/version.ts +5 -0
- package/src/view-diff.test.ts +279 -9
- package/src/view-diff.ts +460 -24
- package/src/view-plan.regression.test.ts +60 -0
- package/src/view-plan.test.ts +545 -0
- package/src/view-plan.ts +1019 -0
- package/src/visual/contrast.test.ts +35 -0
- package/src/visual/contrast.ts +33 -0
- package/src/walk.test.ts +121 -0
- package/src/walk.ts +74 -0
- package/src/workbench-snapshot.test.ts +99 -0
- package/src/workbench-snapshot.ts +59 -0
- package/src/session-controller.ts +0 -398
- package/src/syntax.test.ts +0 -41
- package/src/syntax.ts +0 -88
- package/src/view.test.ts +0 -157
- package/src/view.ts +0 -364
package/src/theme.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Semantic theme tokens
|
|
2
|
+
* Semantic theme tokens. Values carry over from the design-system
|
|
3
3
|
* prototypes (tokens.css). One built-in dark theme for now; the theme
|
|
4
4
|
* registry (palette-derived themes, terminal theme, user overrides) layers
|
|
5
5
|
* on top of these slots without touching consumers.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
export interface Theme {
|
|
9
|
-
|
|
9
|
+
background: string;
|
|
10
10
|
panel: string;
|
|
11
11
|
elevated: string;
|
|
12
12
|
border: string;
|
|
@@ -15,32 +15,101 @@ export interface Theme {
|
|
|
15
15
|
textDim: string;
|
|
16
16
|
accent: string;
|
|
17
17
|
accentInk: string;
|
|
18
|
+
warning: string;
|
|
18
19
|
green: string;
|
|
19
20
|
red: string;
|
|
20
21
|
blue: string;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
cursorBackground: string;
|
|
23
|
+
markCommentBackground: string;
|
|
24
|
+
insertedForeground: string;
|
|
25
|
+
deletedForeground: string;
|
|
26
|
+
/** The band behind a whole added line, and the stronger tint under its intra-line changed words. */
|
|
27
|
+
insertedBackground: string;
|
|
28
|
+
insertedEmphasisBackground: string;
|
|
29
|
+
/** The band behind a whole removed line, and the stronger tint under its intra-line changed words. */
|
|
30
|
+
deletedBackground: string;
|
|
31
|
+
deletedEmphasisBackground: string;
|
|
32
|
+
/** Layer behind centered dialogs; transparent keeps the session visible. */
|
|
33
|
+
backdrop: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The dimmed variant behind the walk wizard: every reading color drops to
|
|
38
|
+
* the dim token so the file list stays legible context, not competition.
|
|
39
|
+
* Layout tokens (backgrounds, cursor) keep their values.
|
|
40
|
+
*/
|
|
41
|
+
export function dimmedTheme(theme: Theme): Theme {
|
|
42
|
+
return {
|
|
43
|
+
...theme,
|
|
44
|
+
text: theme.textDim,
|
|
45
|
+
textMuted: theme.textDim,
|
|
46
|
+
accent: theme.textDim,
|
|
47
|
+
warning: theme.textDim,
|
|
48
|
+
green: theme.textDim,
|
|
49
|
+
red: theme.textDim,
|
|
50
|
+
blue: theme.textDim,
|
|
51
|
+
insertedForeground: theme.textDim,
|
|
52
|
+
deletedForeground: theme.textDim,
|
|
53
|
+
insertedBackground: theme.panel,
|
|
54
|
+
insertedEmphasisBackground: theme.elevated,
|
|
55
|
+
deletedBackground: theme.panel,
|
|
56
|
+
deletedEmphasisBackground: theme.elevated,
|
|
57
|
+
};
|
|
26
58
|
}
|
|
27
59
|
|
|
28
60
|
export const DARK: Theme = {
|
|
29
|
-
|
|
30
|
-
|
|
61
|
+
/* Unpainted so the terminal background shows through; override in [theme]. */
|
|
62
|
+
background: "transparent",
|
|
63
|
+
panel: "transparent",
|
|
31
64
|
elevated: "#23232f",
|
|
32
65
|
border: "#3a3a4a",
|
|
33
66
|
text: "#e4e6ec",
|
|
34
67
|
textMuted: "#b1b6c4",
|
|
35
68
|
textDim: "#6b7280",
|
|
36
|
-
accent: "#
|
|
37
|
-
accentInk: "#
|
|
69
|
+
accent: "#cba6f7",
|
|
70
|
+
accentInk: "#17171a",
|
|
71
|
+
warning: "#e5a45c",
|
|
38
72
|
green: "#62d96b",
|
|
39
|
-
red: "#
|
|
40
|
-
blue: "#
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
73
|
+
red: "#f08080",
|
|
74
|
+
blue: "#a9c8f5",
|
|
75
|
+
cursorBackground: "#33262a",
|
|
76
|
+
markCommentBackground: "#3d2a2e",
|
|
77
|
+
insertedForeground: "#62d96b",
|
|
78
|
+
deletedForeground: "#ff6b6b",
|
|
79
|
+
insertedBackground: "#16321d",
|
|
80
|
+
insertedEmphasisBackground: "#245c30",
|
|
81
|
+
deletedBackground: "#341c1f",
|
|
82
|
+
deletedEmphasisBackground: "#5a2a2e",
|
|
83
|
+
backdrop: "transparent",
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* The branded transparent theme for a LIGHT terminal: same unpainted
|
|
88
|
+
* background, but every reading color darkens so text is legible on a white
|
|
89
|
+
* terminal instead of light-on-light. Chosen when the terminal reports a light
|
|
90
|
+
* background; the dark variant above is the default and the fallback.
|
|
91
|
+
*/
|
|
92
|
+
export const LIGHT: Theme = {
|
|
93
|
+
background: "transparent",
|
|
94
|
+
panel: "transparent",
|
|
95
|
+
elevated: "#ececf2",
|
|
96
|
+
border: "#c7c7d2",
|
|
97
|
+
text: "#1c1d24",
|
|
98
|
+
textMuted: "#4b5162",
|
|
99
|
+
textDim: "#868c9c",
|
|
100
|
+
accent: "#5b3ea6",
|
|
101
|
+
accentInk: "#fbfbfa",
|
|
102
|
+
warning: "#a45f12",
|
|
103
|
+
green: "#2f8a3e",
|
|
104
|
+
red: "#c0392b",
|
|
105
|
+
blue: "#2f6fb0",
|
|
106
|
+
cursorBackground: "#e6e2ea",
|
|
107
|
+
markCommentBackground: "#f2e2e8",
|
|
108
|
+
insertedForeground: "#2f8a3e",
|
|
109
|
+
deletedForeground: "#c0392b",
|
|
110
|
+
insertedBackground: "#d8f5dd",
|
|
111
|
+
insertedEmphasisBackground: "#a9e6b6",
|
|
112
|
+
deletedBackground: "#fbdcdc",
|
|
113
|
+
deletedEmphasisBackground: "#f5b7b7",
|
|
114
|
+
backdrop: "transparent",
|
|
115
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { resolveNavKey } from "./thread-chords";
|
|
3
|
+
|
|
4
|
+
const owner = { isOwner: true, resolved: false, treeActive: false };
|
|
5
|
+
const readOnly = { type: "status" as const, message: "observer - read-only" };
|
|
6
|
+
const submitted = { type: "status" as const, message: "review submitted - read-only" };
|
|
7
|
+
|
|
8
|
+
describe("resolveNavKey", () => {
|
|
9
|
+
test("the session commands: submit, edit, share", () => {
|
|
10
|
+
expect(resolveNavKey({ name: "return" }, owner)).toEqual({ type: "openSubmit" });
|
|
11
|
+
expect(resolveNavKey({ name: "enter" }, owner)).toEqual({ type: "openSubmit" });
|
|
12
|
+
expect(resolveNavKey({ name: "e" }, owner)).toEqual({ type: "edit" });
|
|
13
|
+
expect(resolveNavKey({ name: "s" }, owner)).toEqual({ type: "share" });
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test("a diff row's keys win over the shared letters, then fall through to curation", () => {
|
|
17
|
+
const diff = { ...owner, isDiff: true };
|
|
18
|
+
|
|
19
|
+
expect(resolveNavKey({ name: "x" }, diff)).toEqual({ type: "rejectChange" });
|
|
20
|
+
expect(resolveNavKey({ name: "c" }, diff)).toEqual({ type: "foldFile" });
|
|
21
|
+
expect(resolveNavKey({ name: "d" }, diff)).toEqual({ type: "toggleDiffView" });
|
|
22
|
+
expect(resolveNavKey({ name: "k" }, diff)).toEqual({ type: "walkStart" });
|
|
23
|
+
// a letter with no diff meaning still reaches the curation commands
|
|
24
|
+
expect(resolveNavKey({ name: "n" }, diff)).toEqual({ type: "nextAnnotation" });
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("the discussion and curation commands", () => {
|
|
28
|
+
expect(resolveNavKey({ name: "n" }, owner)).toEqual({ type: "nextAnnotation" });
|
|
29
|
+
expect(resolveNavKey({ name: "p" }, owner)).toEqual({ type: "prevAnnotation" });
|
|
30
|
+
expect(resolveNavKey({ name: "backspace" }, owner)).toEqual({ type: "removeAnnotation" });
|
|
31
|
+
expect(resolveNavKey({ name: "r" }, owner)).toEqual({ type: "openRename" });
|
|
32
|
+
expect(resolveNavKey({ name: "x" }, owner)).toEqual({ type: "cut" });
|
|
33
|
+
expect(resolveNavKey({ name: "u" }, owner)).toEqual({ type: "restoreCuration" });
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("the tree commands: move on the Tree tab, go, branch, label, fork, hand off", () => {
|
|
37
|
+
const onTree = { ...owner, treeActive: true };
|
|
38
|
+
|
|
39
|
+
expect(resolveNavKey({ name: "n" }, onTree)).toEqual({ type: "treeMove", direction: 1 });
|
|
40
|
+
expect(resolveNavKey({ name: "p" }, onTree)).toEqual({ type: "treeMove", direction: -1 });
|
|
41
|
+
expect(resolveNavKey({ name: "g" }, owner)).toEqual({ type: "treeGo" });
|
|
42
|
+
expect(resolveNavKey({ name: "b" }, owner)).toEqual({ type: "treeBranch" });
|
|
43
|
+
expect(resolveNavKey({ name: "l" }, owner)).toEqual({ type: "treeLabel" });
|
|
44
|
+
expect(resolveNavKey({ name: "f" }, owner)).toEqual({ type: "treeFork" });
|
|
45
|
+
expect(resolveNavKey({ name: "h" }, owner)).toEqual({ type: "treeForkShare" });
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("a shift letter resolves as its uppercase, which no command claims, so it returns to typing", () => {
|
|
49
|
+
expect(resolveNavKey({ name: "x", shift: true }, { ...owner, isDiff: true })).toBeNull();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("a collaborator is refused the owner's commands; renaming stays open", () => {
|
|
53
|
+
const collaborator = { ...owner, isOwner: false };
|
|
54
|
+
|
|
55
|
+
expect(resolveNavKey({ name: "return" }, collaborator)).toEqual(readOnly);
|
|
56
|
+
expect(resolveNavKey({ name: "e" }, collaborator)).toEqual(readOnly);
|
|
57
|
+
expect(resolveNavKey({ name: "s" }, collaborator)).toEqual(readOnly);
|
|
58
|
+
expect(resolveNavKey({ name: "x" }, collaborator)).toEqual(readOnly);
|
|
59
|
+
expect(resolveNavKey({ name: "g" }, collaborator)).toEqual(readOnly);
|
|
60
|
+
expect(resolveNavKey({ name: "f" }, collaborator)).toEqual(readOnly);
|
|
61
|
+
expect(resolveNavKey({ name: "r" }, collaborator)).toEqual({ type: "openRename" });
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("a resolved review refuses every change and says so; a fork stays open", () => {
|
|
65
|
+
const resolved = { ...owner, resolved: true };
|
|
66
|
+
|
|
67
|
+
expect(resolveNavKey({ name: "return" }, resolved)).toBeNull();
|
|
68
|
+
expect(resolveNavKey({ name: "e" }, resolved)).toEqual(submitted);
|
|
69
|
+
expect(resolveNavKey({ name: "x" }, resolved)).toEqual(submitted);
|
|
70
|
+
expect(resolveNavKey({ name: "b" }, resolved)).toEqual(submitted);
|
|
71
|
+
expect(resolveNavKey({ name: "f" }, resolved)).toEqual({ type: "treeFork" });
|
|
72
|
+
expect(resolveNavKey({ name: "s" }, resolved)).toEqual({ type: "share" });
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("an unmapped key returns null, so the surface returns to typing", () => {
|
|
76
|
+
expect(resolveNavKey({ name: "z" }, owner)).toBeNull();
|
|
77
|
+
expect(resolveNavKey({ name: "j" }, owner)).toBeNull();
|
|
78
|
+
});
|
|
79
|
+
});
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import type { Intent } from "./keymap";
|
|
2
|
+
|
|
3
|
+
export interface NavKey {
|
|
4
|
+
name: string;
|
|
5
|
+
shift?: boolean;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface ThreadNavContext {
|
|
9
|
+
isOwner: boolean;
|
|
10
|
+
resolved: boolean;
|
|
11
|
+
treeActive: boolean;
|
|
12
|
+
isDiff?: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface ChordEntry {
|
|
16
|
+
keys: string;
|
|
17
|
+
label: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function sessionCommandEntries(): ChordEntry[] {
|
|
21
|
+
return [
|
|
22
|
+
{ keys: "⏎", label: "submit the review" },
|
|
23
|
+
{ keys: "e", label: "edit in $EDITOR" },
|
|
24
|
+
{ keys: "s", label: "share" },
|
|
25
|
+
];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function diffCommandEntries(): ChordEntry[] {
|
|
29
|
+
return [
|
|
30
|
+
{ keys: "x", label: "reject the change under the caret" },
|
|
31
|
+
{ keys: "u", label: "restore the last rejection" },
|
|
32
|
+
{ keys: "c", label: "collapse the file to its band" },
|
|
33
|
+
{ keys: "d", label: "split / stacked (when wide)" },
|
|
34
|
+
{ keys: "k", label: "start the guided walk" },
|
|
35
|
+
];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function curationCommandEntries(): ChordEntry[] {
|
|
39
|
+
return [
|
|
40
|
+
{ keys: "n / p", label: "next / previous card" },
|
|
41
|
+
{ keys: "z", label: "fold / unfold the card" },
|
|
42
|
+
{ keys: "⌫", label: "delete the card" },
|
|
43
|
+
{ keys: "r", label: "rename the author" },
|
|
44
|
+
{ keys: "x", label: "cut the block" },
|
|
45
|
+
{ keys: "u", label: "restore the last cut" },
|
|
46
|
+
];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function treeCommandEntries(): ChordEntry[] {
|
|
50
|
+
return [
|
|
51
|
+
{ keys: "g", label: "go to the entry" },
|
|
52
|
+
{ keys: "b", label: "branch off the tip" },
|
|
53
|
+
{ keys: "l", label: "label a checkpoint" },
|
|
54
|
+
{ keys: "f", label: "fork the path" },
|
|
55
|
+
{ keys: "h", label: "fork and hand off" },
|
|
56
|
+
];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const READ_ONLY: Intent = { type: "status", message: "observer - read-only" };
|
|
60
|
+
const RESOLVED: Intent = { type: "status", message: "review submitted - read-only" };
|
|
61
|
+
|
|
62
|
+
function mutating(intent: Intent, context: ThreadNavContext): Intent {
|
|
63
|
+
if (!context.isOwner) return READ_ONLY;
|
|
64
|
+
if (context.resolved) return RESOLVED;
|
|
65
|
+
|
|
66
|
+
return intent;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function resolveDiffKey(name: string, context: ThreadNavContext): Intent | null {
|
|
70
|
+
switch (name) {
|
|
71
|
+
case "x":
|
|
72
|
+
return mutating({ type: "rejectChange" }, context);
|
|
73
|
+
case "c":
|
|
74
|
+
return { type: "foldFile" };
|
|
75
|
+
case "d":
|
|
76
|
+
return { type: "toggleDiffView" };
|
|
77
|
+
case "k":
|
|
78
|
+
return context.resolved ? RESOLVED : { type: "walkStart" };
|
|
79
|
+
default:
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function resolveSessionKey(name: string, context: ThreadNavContext): Intent | null {
|
|
85
|
+
switch (name) {
|
|
86
|
+
case "e":
|
|
87
|
+
return mutating({ type: "edit" }, context);
|
|
88
|
+
case "s":
|
|
89
|
+
return context.isOwner ? { type: "share" } : READ_ONLY;
|
|
90
|
+
default:
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function resolveCurationKey(name: string, context: ThreadNavContext): Intent | null {
|
|
96
|
+
switch (name) {
|
|
97
|
+
case "n":
|
|
98
|
+
return context.treeActive ? { type: "treeMove", direction: 1 } : { type: "nextAnnotation" };
|
|
99
|
+
case "p":
|
|
100
|
+
return context.treeActive ? { type: "treeMove", direction: -1 } : { type: "prevAnnotation" };
|
|
101
|
+
case "g":
|
|
102
|
+
return mutating({ type: "treeGo" }, context);
|
|
103
|
+
case "b":
|
|
104
|
+
return mutating({ type: "treeBranch" }, context);
|
|
105
|
+
case "l":
|
|
106
|
+
return mutating({ type: "treeLabel" }, context);
|
|
107
|
+
case "f":
|
|
108
|
+
return context.isOwner ? { type: "treeFork" } : READ_ONLY;
|
|
109
|
+
case "h":
|
|
110
|
+
return context.isOwner ? { type: "treeForkShare" } : READ_ONLY;
|
|
111
|
+
case "backspace":
|
|
112
|
+
return mutating({ type: "removeAnnotation" }, context);
|
|
113
|
+
case "r":
|
|
114
|
+
return { type: "openRename" };
|
|
115
|
+
case "x":
|
|
116
|
+
return mutating({ type: "cut" }, context);
|
|
117
|
+
case "u":
|
|
118
|
+
return mutating({ type: "restoreCuration" }, context);
|
|
119
|
+
default:
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export interface SessionChordKey {
|
|
125
|
+
name: string;
|
|
126
|
+
ctrl?: boolean;
|
|
127
|
+
meta?: boolean;
|
|
128
|
+
super?: boolean;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function resolveSessionChord(
|
|
132
|
+
key: SessionChordKey,
|
|
133
|
+
context: Pick<ThreadNavContext, "isOwner" | "resolved">,
|
|
134
|
+
): Intent | null {
|
|
135
|
+
if (key.name === "return" || key.name === "enter") {
|
|
136
|
+
if (!(key.ctrl || key.meta || key.super)) return null;
|
|
137
|
+
if (!context.isOwner) return READ_ONLY;
|
|
138
|
+
|
|
139
|
+
return context.resolved ? null : { type: "openSubmit" };
|
|
140
|
+
}
|
|
141
|
+
if (!key.ctrl) return null;
|
|
142
|
+
if (key.name === "e")
|
|
143
|
+
return context.resolved ? RESOLVED : context.isOwner ? { type: "edit" } : READ_ONLY;
|
|
144
|
+
if (key.name === "s") return context.isOwner ? { type: "share" } : READ_ONLY;
|
|
145
|
+
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function resolveNavKey(key: NavKey, context: ThreadNavContext): Intent | null {
|
|
150
|
+
const name = key.shift ? key.name.toUpperCase() : key.name;
|
|
151
|
+
|
|
152
|
+
if (name === "return" || name === "enter") {
|
|
153
|
+
if (!context.isOwner) return READ_ONLY;
|
|
154
|
+
|
|
155
|
+
return context.resolved ? null : { type: "openSubmit" };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return (
|
|
159
|
+
(context.isDiff ? resolveDiffKey(name, context) : null) ??
|
|
160
|
+
resolveSessionKey(name, context) ??
|
|
161
|
+
resolveCurationKey(name, context)
|
|
162
|
+
);
|
|
163
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/** Tree-sitter colors for fenced code blocks in a Thread, in rendered-text offsets. */
|
|
2
|
+
|
|
3
|
+
import { getTreeSitterClient } from "@opentui/core";
|
|
4
|
+
import type { DisplayBlock } from "./view-plan";
|
|
5
|
+
import { renderedText } from "./view-plan";
|
|
6
|
+
import { spansByLine, type SyntaxSpan } from "./diff-syntax";
|
|
7
|
+
import { filetypeFor } from "./components/syntax-highlight";
|
|
8
|
+
|
|
9
|
+
export type { SyntaxSpan } from "./diff-syntax";
|
|
10
|
+
|
|
11
|
+
/** Highlight whole code blocks so multi-line syntax stays intact. */
|
|
12
|
+
export async function highlightThreadCodeBlocks(
|
|
13
|
+
display: DisplayBlock[],
|
|
14
|
+
): Promise<Map<number, SyntaxSpan[]>> {
|
|
15
|
+
const jobs = display.flatMap((block, blockIndex) => {
|
|
16
|
+
if (block.kind !== "code") return [];
|
|
17
|
+
const filetype = filetypeFor(block.work?.lang ?? block.base?.lang);
|
|
18
|
+
|
|
19
|
+
return filetype ? [{ blockIndex, filetype, source: renderedText(block) }] : [];
|
|
20
|
+
});
|
|
21
|
+
const byBlock = new Map<number, SyntaxSpan[]>();
|
|
22
|
+
|
|
23
|
+
if (jobs.length === 0) return byBlock;
|
|
24
|
+
const client = getTreeSitterClient();
|
|
25
|
+
|
|
26
|
+
await client.initialize();
|
|
27
|
+
for (const job of jobs) {
|
|
28
|
+
const result = await client.highlightOnce(job.source, job.filetype);
|
|
29
|
+
|
|
30
|
+
if (!result.highlights) continue;
|
|
31
|
+
const spans: SyntaxSpan[] = [];
|
|
32
|
+
let offset = 0;
|
|
33
|
+
const lines = job.source.split("\n");
|
|
34
|
+
|
|
35
|
+
spansByLine(job.source, result.highlights).forEach((lineSpans, lineIndex) => {
|
|
36
|
+
for (const span of lineSpans) {
|
|
37
|
+
spans.push({ start: offset + span.start, end: offset + span.end, group: span.group });
|
|
38
|
+
}
|
|
39
|
+
offset += lines[lineIndex]!.length + 1;
|
|
40
|
+
});
|
|
41
|
+
byBlock.set(job.blockIndex, spans);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return byBlock;
|
|
45
|
+
}
|