@cueloop/client 0.1.0-alpha.0 → 0.1.0-alpha.100
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 +41 -27
- package/src/annotations/annotation-palette.ts +62 -0
- package/src/annotations/components/AnnotationCards.stories.tsx +86 -0
- package/src/annotations/components/AnnotationCards.test.tsx +42 -0
- package/src/annotations/components/AnnotationCards.tsx +364 -0
- package/src/annotations/components/DiscussionMarkerRail.stories.tsx +54 -0
- package/src/annotations/components/DiscussionMarkerRail.tsx +186 -0
- package/src/annotations/discussions.ts +77 -0
- package/src/annotations/thread-selection.test.ts +210 -0
- package/src/annotations/thread-selection.ts +242 -0
- package/src/annotations/use-annotation-surface.tsx +1238 -0
- package/src/app/App.appearance.test.tsx +53 -0
- package/src/app/App.autoclose.test.tsx +144 -0
- package/src/app/App.deleted-scroll.test.tsx +187 -0
- package/src/app/App.diff.test.tsx +313 -0
- package/src/app/App.export.test.tsx +105 -0
- package/src/app/App.focus.test.ts +89 -0
- package/src/app/App.identity.test.tsx +84 -0
- package/src/app/App.plan-render.test.tsx +209 -0
- package/src/app/App.plan-review.test.tsx +428 -0
- package/src/app/App.prototype-markdown.test.tsx +128 -0
- package/src/app/App.prototype.test.tsx +176 -0
- package/src/app/App.readonly.test.tsx +210 -0
- package/src/app/App.regression.test.tsx +99 -0
- package/src/app/App.rendering.test.tsx +134 -0
- package/src/app/App.share-dialog.test.tsx +177 -0
- package/src/app/App.submit-confirm.test.tsx +267 -0
- package/src/app/App.test.tsx +496 -0
- package/src/app/App.thread-agent.test.ts +12 -0
- package/src/app/App.toast.test.tsx +124 -0
- package/src/app/App.tsx +1650 -0
- package/src/app/App.visual.test.tsx +59 -0
- package/src/app/App.walk.test.tsx +299 -0
- package/src/app/App.workbench.test.tsx +1149 -0
- package/src/app/app-key-state.ts +61 -0
- package/src/app/app-screens.tsx +606 -0
- package/src/app/app-view-model.ts +168 -0
- package/src/app/components/AppHeader.stories.tsx +50 -0
- package/src/app/components/AppHeader.tsx +81 -0
- package/src/app/components/AppShell.brand.test.tsx +58 -0
- package/src/app/components/AppShell.extensions.test.tsx +191 -0
- package/src/app/components/AppShell.stories.tsx +89 -0
- package/src/app/components/AppShell.tsx +358 -0
- package/src/app/components/AppShell.zoom.test.tsx +149 -0
- package/src/app/components/Breadcrumb.stories.tsx +19 -0
- package/src/app/components/Breadcrumb.tsx +45 -0
- package/src/app/components/MenuBar.stories.tsx +28 -0
- package/src/app/components/MenuBar.tsx +44 -0
- package/src/app/components/PaneGrid.stories.tsx +84 -0
- package/src/app/components/PaneGrid.tsx +128 -0
- package/src/app/components/PanelColumn.stories.tsx +25 -0
- package/src/app/components/PanelColumn.tsx +93 -0
- package/src/app/components/ShellHeader.stories.tsx +50 -0
- package/src/app/components/ShellHeader.tsx +137 -0
- package/src/app/components/WorkspacePanels.stories.tsx +36 -0
- package/src/app/components/WorkspacePanels.tsx +120 -0
- package/src/app/components/pages/Workspace.stories.tsx +181 -0
- package/src/app/intent-dispatch.test.ts +728 -0
- package/src/app/intent-dispatch.ts +687 -0
- package/src/app/launch-layout.test.ts +78 -0
- package/src/app/launch-layout.ts +57 -0
- package/src/app/ready-signal.test.ts +85 -0
- package/src/app/ready-signal.ts +52 -0
- package/src/app/run.ts +109 -0
- package/src/{serve.test.ts → app/serve.test.ts} +24 -12
- package/src/app/serve.ts +111 -0
- package/src/app/version.ts +5 -0
- package/src/appearance/components/theme-context.stories.tsx +46 -0
- package/src/appearance/components/theme-context.tsx +37 -0
- package/src/appearance/theme-presets.test.ts +51 -0
- package/src/appearance/theme-presets.ts +219 -0
- package/src/appearance/theme.ts +112 -0
- package/src/appearance/visual/contrast.test.ts +35 -0
- package/src/appearance/visual/contrast.ts +33 -0
- package/src/diff/components/AnnotatableFileView.stories.tsx +42 -0
- package/src/diff/components/AnnotatableFileView.test.tsx +157 -0
- package/src/diff/components/AnnotatableFileView.tsx +141 -0
- package/src/diff/components/BareWorkbenchFileView.stories.tsx +27 -0
- package/src/diff/components/BareWorkbenchFileView.tsx +63 -0
- package/src/diff/components/DiffContentView.scroll.test.tsx +434 -0
- package/src/diff/components/DiffContentView.split.test.tsx +140 -0
- package/src/diff/components/DiffContentView.stories.tsx +80 -0
- package/src/diff/components/DiffContentView.syntax.test.tsx +82 -0
- package/src/diff/components/DiffContentView.tsx +1055 -0
- package/src/diff/components/WalkWizard.stories.tsx +100 -0
- package/src/diff/components/WalkWizard.tsx +146 -0
- package/src/diff/components/diff-content-view-layout.test.ts +198 -0
- package/src/diff/components/diff-content-view-layout.ts +84 -0
- package/src/diff/diff-fold.test.ts +78 -0
- package/src/diff/diff-fold.ts +126 -0
- package/src/diff/diff-hunk-curate.test.ts +315 -0
- package/src/diff/diff-hunk-curate.ts +152 -0
- package/src/diff/diff-intraline.test.ts +239 -0
- package/src/diff/diff-intraline.ts +238 -0
- package/src/diff/diff-syntax.test.ts +125 -0
- package/src/diff/diff-syntax.ts +186 -0
- package/src/diff/split-diff.test.ts +110 -0
- package/src/diff/split-diff.ts +144 -0
- package/src/diff/view-diff.test.ts +325 -0
- package/src/diff/view-diff.ts +522 -0
- package/src/diff/walk.test.ts +121 -0
- package/src/diff/walk.ts +73 -0
- package/src/index.ts +31 -2
- package/src/integrations/attribution.test.ts +31 -0
- package/src/integrations/attribution.ts +42 -0
- package/src/integrations/client-extension-registry.test.tsx +61 -0
- package/src/integrations/client-extension-registry.ts +159 -0
- package/src/integrations/clipboard.test.ts +23 -0
- package/src/integrations/clipboard.ts +33 -0
- package/src/integrations/components/ExtensionZone.stories.tsx +28 -0
- package/src/integrations/components/ExtensionZone.tsx +145 -0
- package/src/integrations/components/ShareDialog.stories.tsx +121 -0
- package/src/integrations/components/ShareDialog.tsx +550 -0
- package/src/integrations/components/share-dialog-store.ts +55 -0
- package/src/integrations/editor.test.ts +149 -0
- package/src/integrations/editor.ts +160 -0
- package/src/integrations/github-identity.test.ts +58 -0
- package/src/integrations/github-identity.ts +41 -0
- package/src/integrations/herdr.test.ts +43 -0
- package/src/integrations/herdr.ts +38 -0
- package/src/integrations/integrations.test.ts +41 -0
- package/src/integrations/integrations.ts +32 -0
- package/src/integrations/pasted-image.test.ts +67 -0
- package/src/integrations/pasted-image.ts +60 -0
- package/src/integrations/share.ts +238 -0
- package/src/keyboard/components/CompletionOverlay.stories.tsx +37 -0
- package/src/keyboard/components/CompletionOverlay.tsx +75 -0
- package/src/keyboard/components/HandleChipsInput.stories.tsx +24 -0
- package/src/keyboard/components/HandleChipsInput.test.tsx +35 -0
- package/src/keyboard/components/HandleChipsInput.tsx +143 -0
- package/src/keyboard/components/KeybindsDialog.stories.tsx +31 -0
- package/src/keyboard/components/KeybindsDialog.tsx +103 -0
- package/src/keyboard/components/NavModeHint.stories.tsx +40 -0
- package/src/keyboard/components/NavModeHint.tsx +31 -0
- package/src/keyboard/components/SlashComposer.stories.tsx +37 -0
- package/src/keyboard/components/SlashComposer.tsx +87 -0
- package/src/keyboard/key-bindings.test.ts +156 -0
- package/src/keyboard/key-bindings.ts +459 -0
- package/src/keyboard/keymap.test.ts +611 -0
- package/src/keyboard/keymap.ts +395 -0
- package/src/keyboard/skills.test.ts +74 -0
- package/src/keyboard/skills.ts +75 -0
- package/src/keyboard/slash-palette.test.ts +103 -0
- package/src/keyboard/slash-palette.ts +129 -0
- package/src/keyboard/thread-chords.test.ts +79 -0
- package/src/keyboard/thread-chords.ts +163 -0
- package/src/keyboard/use-shared-keyboard.ts +55 -0
- package/src/markdown/__snapshots__/view-plan.regression.test.ts.snap +100 -0
- package/src/markdown/components/MarkdownGridBlock.stories.tsx +33 -0
- package/src/markdown/components/MarkdownGridBlock.tsx +72 -0
- package/src/markdown/components/MarkdownThreadEditor.stories.tsx +15 -0
- package/src/markdown/components/MarkdownThreadEditor.test.tsx +98 -0
- package/src/markdown/components/MarkdownThreadEditor.tsx +160 -0
- package/src/markdown/components/ThreadView.selection.test.tsx +951 -0
- package/src/markdown/components/ThreadView.slash.test.tsx +146 -0
- package/src/markdown/components/ThreadView.stories.tsx +26 -0
- package/src/markdown/components/ThreadView.tsx +560 -0
- package/src/markdown/mark-runs.ts +121 -0
- package/src/markdown/markdown-editor-syntax-style.ts +50 -0
- package/src/markdown/markdown-grid.test.ts +101 -0
- package/src/markdown/markdown-grid.ts +203 -0
- package/src/markdown/markdown-highlight.test.ts +61 -0
- package/src/markdown/markdown-highlight.ts +124 -0
- package/src/markdown/thread-code-syntax.ts +45 -0
- package/src/markdown/use-thread-body-editing.tsx +54 -0
- package/src/markdown/view-plan.regression.test.ts +60 -0
- package/src/markdown/view-plan.test.ts +545 -0
- package/src/markdown/view-plan.ts +1009 -0
- package/src/performance/otlp-trace.test.ts +119 -0
- package/src/performance/otlp-trace.ts +134 -0
- package/src/performance/perf-report.ts +18 -0
- package/src/performance/perf-timings.test.ts +18 -0
- package/src/performance/perf-timings.ts +39 -0
- package/src/prototype/components/PrototypeContentView.stories.tsx +19 -0
- package/src/prototype/components/PrototypeContentView.tsx +442 -0
- package/src/prototype/prototype-browser.test.ts +81 -0
- package/src/prototype/prototype-browser.ts +321 -0
- package/src/prototype/prototype-pixels.tsx +35 -0
- package/src/settings/components/SettingsDialog.stories.tsx +117 -0
- package/src/settings/components/SettingsDialog.tsx +183 -0
- package/src/settings/components/SettingsRows.stories.tsx +18 -0
- package/src/settings/components/SettingsRows.tsx +112 -0
- package/src/settings/components/quick-actions-editor.stories.tsx +58 -0
- package/src/settings/components/quick-actions-editor.test.tsx +120 -0
- package/src/settings/components/quick-actions-editor.tsx +222 -0
- package/src/settings/config-identity.test.ts +78 -0
- package/src/settings/config.test.ts +676 -0
- package/src/settings/config.ts +628 -0
- package/src/settings/use-settings-dialog.tsx +351 -0
- package/src/stories/__snapshots__/stories.test.tsx.snap +2358 -0
- package/src/stories/stories-app.tsx +196 -0
- package/src/stories/stories.test.tsx +74 -0
- package/src/stories/story-fixtures.ts +102 -0
- package/src/stories/story-tree.test.ts +108 -0
- package/src/stories/story.ts +154 -0
- package/src/terminal/ansi-screen.test.ts +28 -0
- package/src/terminal/ansi-screen.ts +72 -0
- package/src/terminal/components/AnsiScreen.stories.tsx +12 -0
- package/src/terminal/components/AnsiScreen.tsx +28 -0
- package/src/terminal/components/terminal-pane.test.tsx +40 -0
- package/src/terminal/components/terminal-pane.ts +196 -0
- package/src/terminal/ghostty-terminal.test.ts +85 -0
- package/src/terminal/ghostty-terminal.ts +197 -0
- package/src/terminal/kitty-image.test.ts +66 -0
- package/src/terminal/kitty-image.ts +137 -0
- package/src/terminal/pty.test.ts +81 -0
- package/src/terminal/pty.ts +246 -0
- package/src/testing/test-support.test.ts +264 -0
- package/src/testing/test-support.ts +367 -0
- package/src/thread/agent-continuation.test.ts +32 -0
- package/src/thread/agent-continuation.ts +13 -0
- package/src/thread/agent-transcript.test.ts +161 -0
- package/src/thread/agent-transcript.ts +336 -0
- package/src/thread/components/AgentConfigControls.stories.tsx +39 -0
- package/src/thread/components/AgentConfigControls.test.tsx +112 -0
- package/src/thread/components/AgentConfigControls.tsx +114 -0
- package/src/thread/components/AgentThreadPane.stories.tsx +105 -0
- package/src/thread/components/AgentThreadPane.test.tsx +829 -0
- package/src/thread/components/AgentThreadPane.tsx +367 -0
- package/src/thread/components/ThreadFooter.stories.tsx +30 -0
- package/src/thread/components/ThreadFooter.test.tsx +49 -0
- package/src/thread/components/ThreadFooter.tsx +77 -0
- package/src/thread/components/ThreadTree.stories.tsx +46 -0
- package/src/thread/components/ThreadTree.test.tsx +41 -0
- package/src/thread/components/ThreadTree.tsx +267 -0
- package/src/thread/components/thread-tree.test.ts +137 -0
- package/src/thread/components/thread-tree.ts +112 -0
- package/src/thread/thread-controller.curate.test.ts +502 -0
- package/src/thread/thread-controller.derivation.test.ts +62 -0
- package/src/thread/thread-controller.live-diff.test.ts +357 -0
- package/src/thread/thread-controller.optimistic.test.ts +69 -0
- package/src/thread/thread-controller.share.test.ts +573 -0
- package/src/thread/thread-controller.switch-cache.test.ts +174 -0
- package/src/thread/thread-controller.tree.test.ts +263 -0
- package/src/thread/thread-controller.ts +2018 -0
- package/src/thread/tree-view.test.ts +128 -0
- package/src/thread/tree-view.ts +146 -0
- package/src/thread/use-thread-agent.test.tsx +208 -0
- package/src/thread/use-thread-agent.ts +111 -0
- package/src/ui/components/CodeBlock.stories.tsx +42 -0
- package/src/ui/components/CodeBlock.tsx +80 -0
- package/src/ui/components/ConfirmCard.stories.tsx +37 -0
- package/src/ui/components/ConfirmCard.tsx +151 -0
- package/src/ui/components/ConfirmDialog.stories.tsx +21 -0
- package/src/ui/components/ConfirmDialog.tsx +72 -0
- package/src/ui/components/OverlayScrollbar.stories.tsx +33 -0
- package/src/ui/components/OverlayScrollbar.test.tsx +139 -0
- package/src/ui/components/OverlayScrollbar.tsx +129 -0
- package/src/ui/components/PromptDialog.stories.tsx +36 -0
- package/src/ui/components/PromptDialog.test.tsx +65 -0
- package/src/ui/components/PromptDialog.tsx +99 -0
- package/src/ui/components/RootOverlay.stories.tsx +45 -0
- package/src/ui/components/RootOverlay.tsx +49 -0
- package/src/ui/components/ScrollArea.stories.tsx +20 -0
- package/src/ui/components/ScrollArea.tsx +61 -0
- package/src/ui/components/Toast.stories.tsx +19 -0
- package/src/ui/components/Toast.tsx +54 -0
- package/src/ui/components/Tooltip.stories.tsx +26 -0
- package/src/ui/components/Tooltip.tsx +72 -0
- package/src/ui/components/menu-control.stories.tsx +35 -0
- package/src/ui/components/menu-control.tsx +43 -0
- package/src/ui/components/primitives/Button.stories.tsx +32 -0
- package/src/ui/components/primitives/Button.tsx +57 -0
- package/src/ui/components/primitives/Card.stories.tsx +25 -0
- package/src/ui/components/primitives/Card.tsx +62 -0
- package/src/ui/components/primitives/Dialog.stories.tsx +24 -0
- package/src/ui/components/primitives/Dialog.test.tsx +38 -0
- package/src/ui/components/primitives/Dialog.tsx +69 -0
- package/src/ui/components/primitives/DialogActions.stories.tsx +20 -0
- package/src/ui/components/primitives/DialogActions.tsx +39 -0
- package/src/ui/components/primitives/IconButton.stories.tsx +36 -0
- package/src/ui/components/primitives/IconButton.tsx +58 -0
- package/src/ui/components/primitives/StatusBar.stories.tsx +11 -0
- package/src/ui/components/primitives/StatusBar.tsx +23 -0
- package/src/ui/components/primitives/Switch.stories.tsx +17 -0
- package/src/ui/components/primitives/Switch.tsx +32 -0
- package/src/ui/components/primitives/Tabs.stories.tsx +29 -0
- package/src/ui/components/primitives/Tabs.tsx +101 -0
- package/src/ui/components/primitives/Toolbar.stories.tsx +17 -0
- package/src/ui/components/primitives/Toolbar.tsx +14 -0
- package/src/ui/components/primitives/Tree.stories.tsx +124 -0
- package/src/ui/components/primitives/Tree.tsx +133 -0
- package/src/ui/components/primitives/frame.test.ts +12 -0
- package/src/ui/components/primitives/frame.ts +17 -0
- package/src/ui/components/primitives/icons.ts +61 -0
- package/src/ui/components/primitives/tree-model.test.ts +99 -0
- package/src/ui/components/primitives/tree-model.ts +137 -0
- package/src/ui/components/syntax-highlight.ts +133 -0
- package/src/ui/components/truncate-text.ts +7 -0
- package/src/ui/components/truncate-title.test.ts +31 -0
- package/src/ui/components/truncate-title.ts +15 -0
- package/src/ui/use-frame-measure.ts +74 -0
- package/src/ui/use-terminal-virtualizer.ts +259 -0
- package/src/workbench/components/ChangesColumn.stories.tsx +44 -0
- package/src/workbench/components/ChangesColumn.tsx +203 -0
- package/src/workbench/components/ChangesFileTree.test.tsx +181 -0
- package/src/workbench/components/EditorGrid.split.test.tsx +118 -0
- package/src/workbench/components/EditorGrid.stories.tsx +86 -0
- package/src/workbench/components/EditorGrid.tabs.test.tsx +118 -0
- package/src/workbench/components/EditorGrid.tsx +544 -0
- package/src/workbench/components/EditorGrid.zoom.test.tsx +132 -0
- package/src/workbench/components/FileContentsView.stories.tsx +16 -0
- package/src/workbench/components/FileContentsView.test.tsx +29 -0
- package/src/workbench/components/FileContentsView.tsx +100 -0
- package/src/workbench/components/GridTabContent.actions.test.tsx +151 -0
- package/src/workbench/components/GridTabContent.stories.tsx +59 -0
- package/src/workbench/components/GridTabContent.tsx +193 -0
- package/src/workbench/components/ProjectTreeView.stories.tsx +25 -0
- package/src/workbench/components/ProjectTreeView.test.tsx +236 -0
- package/src/workbench/components/ProjectTreeView.tsx +151 -0
- package/src/workbench/components/TreePane.stories.tsx +106 -0
- package/src/workbench/components/TreePane.tsx +155 -0
- package/src/workbench/components/WelcomePlayground.stories.tsx +12 -0
- package/src/workbench/components/WelcomePlayground.test.tsx +83 -0
- package/src/workbench/components/WelcomePlayground.tsx +138 -0
- package/src/workbench/components/editor-grid.test.ts +184 -0
- package/src/workbench/components/editor-grid.ts +281 -0
- package/src/workbench/components/file-tree.test.ts +60 -0
- package/src/workbench/components/file-tree.ts +112 -0
- package/src/workbench/use-changes-workbench.ts +205 -0
- package/src/workbench/use-remember-layout.ts +31 -0
- package/src/workbench/use-repo-changes.ts +91 -0
- package/src/workbench/workbench-snapshot.test.ts +99 -0
- package/src/workbench/workbench-snapshot.ts +59 -0
- package/src/App.diff.test.tsx +0 -99
- package/src/App.export.test.tsx +0 -80
- package/src/App.readonly.test.tsx +0 -148
- package/src/App.test.tsx +0 -217
- package/src/App.tsx +0 -0
- package/src/config.test.ts +0 -116
- package/src/config.ts +0 -113
- package/src/editor.ts +0 -34
- package/src/run.ts +0 -28
- package/src/serve.ts +0 -75
- package/src/theme.ts +0 -46
- package/src/view-diff.test.ts +0 -55
- package/src/view-diff.ts +0 -86
- package/src/view.test.ts +0 -157
- package/src/view.ts +0 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Code highlighting tokens: the theme's named colors projected onto tree-sitter
|
|
3
|
+
* capture names. One group -> color map is the single source of truth, feeding
|
|
4
|
+
* both the native code renderable (via a cached SyntaxStyle per theme) and the
|
|
5
|
+
* diff sheet's per-row spans. Unknown languages simply render unstyled -
|
|
6
|
+
* highlighting is an enhancement, never a blocker.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
SyntaxStyle,
|
|
11
|
+
infoStringToFiletype,
|
|
12
|
+
extensionToFiletype,
|
|
13
|
+
basenameToFiletype,
|
|
14
|
+
} from "@opentui/core";
|
|
15
|
+
import type { Theme } from "../../appearance/theme";
|
|
16
|
+
|
|
17
|
+
/** Markdown fence info -> tree-sitter filetype, with the aliases we accept. */
|
|
18
|
+
export function filetypeFor(language?: string): string | undefined {
|
|
19
|
+
const info = (language ?? "").toLowerCase();
|
|
20
|
+
|
|
21
|
+
if (!info) return undefined;
|
|
22
|
+
const aliases: SyntaxAliases = {
|
|
23
|
+
ts: "typescript",
|
|
24
|
+
js: "javascript",
|
|
25
|
+
py: "python",
|
|
26
|
+
rb: "ruby",
|
|
27
|
+
rs: "rust",
|
|
28
|
+
sh: "bash",
|
|
29
|
+
shell: "bash",
|
|
30
|
+
zsh: "bash",
|
|
31
|
+
yml: "yaml",
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
return infoStringToFiletype(aliases[info] ?? info) ?? aliases[info] ?? info;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** A diff/code file path -> tree-sitter filetype, by basename then extension. */
|
|
38
|
+
export function filetypeForPath(path: string): string | undefined {
|
|
39
|
+
const basename = path.split("/").pop() ?? path;
|
|
40
|
+
const byBasename = basenameToFiletype.get(basename);
|
|
41
|
+
|
|
42
|
+
if (byBasename) return byBasename;
|
|
43
|
+
const dotIndex = basename.lastIndexOf(".");
|
|
44
|
+
|
|
45
|
+
if (dotIndex <= 0) return undefined;
|
|
46
|
+
|
|
47
|
+
return extensionToFiletype.get(basename.slice(dotIndex + 1).toLowerCase());
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
interface SyntaxAliases {
|
|
51
|
+
[alias: string]: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
interface SyntaxGroupStyle {
|
|
55
|
+
fg: string;
|
|
56
|
+
italic?: boolean;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
interface SyntaxGroupStyles {
|
|
60
|
+
[group: string]: SyntaxGroupStyle;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** The one group -> style map, keyed by tree-sitter capture name. */
|
|
64
|
+
function syntaxGroupStyles(theme: Theme): SyntaxGroupStyles {
|
|
65
|
+
return {
|
|
66
|
+
default: { fg: theme.textMuted },
|
|
67
|
+
comment: { fg: theme.textDim, italic: true },
|
|
68
|
+
string: { fg: theme.green },
|
|
69
|
+
number: { fg: theme.blue },
|
|
70
|
+
constant: { fg: theme.blue },
|
|
71
|
+
boolean: { fg: theme.blue },
|
|
72
|
+
keyword: { fg: theme.accent },
|
|
73
|
+
operator: { fg: theme.textDim },
|
|
74
|
+
punctuation: { fg: theme.textDim },
|
|
75
|
+
function: { fg: theme.text },
|
|
76
|
+
method: { fg: theme.text },
|
|
77
|
+
type: { fg: theme.blue },
|
|
78
|
+
constructor: { fg: theme.blue },
|
|
79
|
+
variable: { fg: theme.textMuted },
|
|
80
|
+
property: { fg: theme.textMuted },
|
|
81
|
+
parameter: { fg: theme.textMuted },
|
|
82
|
+
tag: { fg: theme.accent },
|
|
83
|
+
attribute: { fg: theme.blue },
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const syntaxStyleCache = new WeakMap<Theme, SyntaxStyle>();
|
|
88
|
+
|
|
89
|
+
export function syntaxStyleFor(theme: Theme): SyntaxStyle {
|
|
90
|
+
const cached = syntaxStyleCache.get(theme);
|
|
91
|
+
|
|
92
|
+
if (cached) return cached;
|
|
93
|
+
const style = SyntaxStyle.fromStyles(syntaxGroupStyles(theme));
|
|
94
|
+
|
|
95
|
+
syntaxStyleCache.set(theme, style);
|
|
96
|
+
|
|
97
|
+
return style;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** A one-group syntax style plus the styleId that paints a "/skill" reference in the accent color. */
|
|
101
|
+
export interface ReferenceStyle {
|
|
102
|
+
style: SyntaxStyle;
|
|
103
|
+
styleId: number;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const referenceStyleCache = new WeakMap<Theme, ReferenceStyle>();
|
|
107
|
+
|
|
108
|
+
export function referenceStyleFor(theme: Theme): ReferenceStyle {
|
|
109
|
+
const cached = referenceStyleCache.get(theme);
|
|
110
|
+
|
|
111
|
+
if (cached) return cached;
|
|
112
|
+
const entry: ReferenceStyle = {
|
|
113
|
+
style: SyntaxStyle.fromStyles({ reference: { fg: theme.accent } }),
|
|
114
|
+
styleId: 0,
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
entry.styleId = entry.style.getStyleId("reference") ?? 0;
|
|
118
|
+
referenceStyleCache.set(theme, entry);
|
|
119
|
+
|
|
120
|
+
return entry;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** The foreground for a tree-sitter capture group; undefined when unstyled. */
|
|
124
|
+
export function colorForSyntaxGroup(group: string, theme: Theme): string | undefined {
|
|
125
|
+
const styles = syntaxGroupStyles(theme);
|
|
126
|
+
const direct = styles[group];
|
|
127
|
+
|
|
128
|
+
if (direct) return direct.fg;
|
|
129
|
+
// capture names are dotted (e.g. "keyword.control"); match the broadest prefix
|
|
130
|
+
const base = group.split(".")[0]!;
|
|
131
|
+
|
|
132
|
+
return styles[base]?.fg;
|
|
133
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Truncate text to one line of at most maxLength characters, ellipsis-terminated. */
|
|
2
|
+
|
|
3
|
+
export function truncateToSingleLine(value: string, maxLength: number): string {
|
|
4
|
+
const oneLine = value.replace(/\n/g, " ");
|
|
5
|
+
|
|
6
|
+
return oneLine.length > maxLength ? oneLine.slice(0, maxLength - 1) + "…" : oneLine;
|
|
7
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { truncateTitle } from "./truncate-title";
|
|
3
|
+
|
|
4
|
+
describe("truncateTitle", () => {
|
|
5
|
+
test("a title that fits is returned unchanged", () => {
|
|
6
|
+
expect(truncateTitle("short", 20)).toBe("short");
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
test("a long title is clipped to the width with a trailing ellipsis", () => {
|
|
10
|
+
const clipped = truncateTitle("Review the accent change in the theme", 20);
|
|
11
|
+
|
|
12
|
+
expect(clipped).toHaveLength(20);
|
|
13
|
+
expect(clipped.endsWith("…")).toBe(true);
|
|
14
|
+
expect(clipped).toBe("Review the accent c…");
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test("newlines collapse to spaces so the row never wraps", () => {
|
|
18
|
+
expect(truncateTitle("line one\nline two", 40)).toBe("line one line two");
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("a zero width yields an empty string", () => {
|
|
22
|
+
expect(truncateTitle("anything", 0)).toBe("");
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("clips by code point, never splitting an astral character into a replacement glyph", () => {
|
|
26
|
+
const clipped = truncateTitle("🚀🚀🚀🚀🚀", 3);
|
|
27
|
+
|
|
28
|
+
expect(clipped).toBe("🚀🚀…");
|
|
29
|
+
expect(clipped).not.toContain("�");
|
|
30
|
+
});
|
|
31
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fits a thread title on one line: unchanged when it fits, otherwise clipped to
|
|
3
|
+
* an ellipsis. Slices by code point, not UTF-16 unit, so an emoji or other
|
|
4
|
+
* astral character is never split into a replacement glyph.
|
|
5
|
+
*/
|
|
6
|
+
export function truncateTitle(title: string, maxWidth: number): string {
|
|
7
|
+
const oneLine = title.replace(/\n/g, " ");
|
|
8
|
+
|
|
9
|
+
if (maxWidth <= 0) return "";
|
|
10
|
+
const characters = [...oneLine];
|
|
11
|
+
|
|
12
|
+
if (characters.length <= maxWidth) return oneLine;
|
|
13
|
+
|
|
14
|
+
return `${characters.slice(0, Math.max(0, maxWidth - 1)).join("")}…`;
|
|
15
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read a layout-dependent value after every rendered frame (and resize),
|
|
3
|
+
* committing only on change. Layout lands after the commit, so a timer
|
|
4
|
+
* would race the visual-idle wait; measuring on the frame event converges
|
|
5
|
+
* deterministically within a frame or two.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { useEffect, useState } from "react";
|
|
9
|
+
import { useRenderer } from "@opentui/react";
|
|
10
|
+
|
|
11
|
+
type Renderer = NonNullable<ReturnType<typeof useRenderer>>;
|
|
12
|
+
type Measure = () => void;
|
|
13
|
+
|
|
14
|
+
interface FrameSubscriptions {
|
|
15
|
+
measures: Set<Measure>;
|
|
16
|
+
notify: Measure;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const subscribers = new WeakMap<Renderer, FrameSubscriptions>();
|
|
20
|
+
|
|
21
|
+
export function subscribeToFrames(renderer: Renderer, measure: Measure): () => void {
|
|
22
|
+
let subscription = subscribers.get(renderer);
|
|
23
|
+
|
|
24
|
+
if (!subscription) {
|
|
25
|
+
const measures = new Set<Measure>();
|
|
26
|
+
const notify = (): void => {
|
|
27
|
+
for (const subscriber of measures) subscriber();
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
subscription = { measures, notify };
|
|
31
|
+
subscribers.set(renderer, subscription);
|
|
32
|
+
renderer.on("frame", notify);
|
|
33
|
+
renderer.on("resize", notify);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const { measures, notify } = subscription;
|
|
37
|
+
|
|
38
|
+
measures.add(measure);
|
|
39
|
+
|
|
40
|
+
return () => {
|
|
41
|
+
measures.delete(measure);
|
|
42
|
+
if (measures.size > 0) return;
|
|
43
|
+
|
|
44
|
+
renderer.off("frame", notify);
|
|
45
|
+
renderer.off("resize", notify);
|
|
46
|
+
subscribers.delete(renderer);
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function useFrameMeasure<T>(
|
|
51
|
+
read: () => T,
|
|
52
|
+
isEqual: (left: T, right: T) => boolean,
|
|
53
|
+
initial: T,
|
|
54
|
+
active = true,
|
|
55
|
+
): T {
|
|
56
|
+
const renderer = useRenderer();
|
|
57
|
+
const [value, setValue] = useState(initial);
|
|
58
|
+
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
// a measurement only needed while a popover is open subscribes no per-frame listener when closed
|
|
61
|
+
if (!active) return;
|
|
62
|
+
const measure = (): void => {
|
|
63
|
+
const next = read();
|
|
64
|
+
|
|
65
|
+
setValue((current) => (isEqual(current, next) ? current : next));
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
measure();
|
|
69
|
+
if (renderer) return subscribeToFrames(renderer, measure);
|
|
70
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
71
|
+
}, [renderer, active]);
|
|
72
|
+
|
|
73
|
+
return value;
|
|
74
|
+
}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Row virtualization for an OpenTUI scrollbox, driven by @tanstack/virtual-core.
|
|
3
|
+
* The core is DOM-free at heart: it only reaches the scroll container through the
|
|
4
|
+
* observe/scroll callbacks given here, so a terminal scrollbox stands in for the
|
|
5
|
+
* element - offsets and sizes are terminal rows, the "scroll event" is the
|
|
6
|
+
* renderer's frame event, and a mounted row's height is read off its renderable.
|
|
7
|
+
* Callers render `items` with a spacer of `items[0].start` rows above and
|
|
8
|
+
* `totalSize - last.end` below, and hand each row's box to `measureRef(index)`.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { useEffect, useLayoutEffect, useRef, useState, type RefObject } from "react";
|
|
12
|
+
import type { BoxRenderable, ScrollBoxRenderable } from "@opentui/core";
|
|
13
|
+
import { useRenderer } from "@opentui/react";
|
|
14
|
+
import { Virtualizer, type VirtualItem } from "@tanstack/virtual-core";
|
|
15
|
+
import { subscribeToFrames } from "./use-frame-measure";
|
|
16
|
+
|
|
17
|
+
export interface TerminalVirtualizerOptions {
|
|
18
|
+
scrollbox: RefObject<ScrollBoxRenderable | null>;
|
|
19
|
+
count: number;
|
|
20
|
+
estimateSize: (index: number) => number;
|
|
21
|
+
overscan: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface TerminalVirtualizer {
|
|
25
|
+
items: VirtualItem[];
|
|
26
|
+
totalSize: number;
|
|
27
|
+
measureRef: (index: number) => (renderable: BoxRenderable | null) => void | (() => void);
|
|
28
|
+
scrollToIndex: (index: number, align?: "auto" | "start" | "center" | "end") => void;
|
|
29
|
+
startOfIndex: (index: number) => number | undefined;
|
|
30
|
+
scrollToOffset: (offset: number) => void;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Visible rows and one-row scrolling for a held mouse mark at a viewport edge. */
|
|
34
|
+
export interface ScrollDragViewport {
|
|
35
|
+
top: number;
|
|
36
|
+
bottom: number;
|
|
37
|
+
scrollBy: (rows: -1 | 1) => boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Adapt an OpenTUI scrollbox to the annotation surface's edge-scroll contract. */
|
|
41
|
+
export function scrollBoxDragViewport(
|
|
42
|
+
scrollbox: ScrollBoxRenderable | null,
|
|
43
|
+
): ScrollDragViewport | null {
|
|
44
|
+
if (!scrollbox) return null;
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
top: scrollbox.viewport.screenY,
|
|
48
|
+
bottom: scrollbox.viewport.screenY + scrollbox.viewport.height - 1,
|
|
49
|
+
scrollBy: (rows) => {
|
|
50
|
+
const before = scrollbox.scrollTop;
|
|
51
|
+
|
|
52
|
+
scrollbox.scrollBy(rows);
|
|
53
|
+
|
|
54
|
+
return scrollbox.scrollTop !== before;
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* The scroll container as the core reads it: the offset and the extents it clamps a scroll
|
|
61
|
+
* target with. These are the `Element` members of the same names, so the view passes as one.
|
|
62
|
+
*/
|
|
63
|
+
interface ScrollContainerView {
|
|
64
|
+
readonly scrollTop: number;
|
|
65
|
+
readonly scrollHeight: number;
|
|
66
|
+
readonly clientHeight: number;
|
|
67
|
+
readonly scrollWidth: number;
|
|
68
|
+
readonly clientWidth: number;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** A mounted row as the core measures it: its height under the `Element` member name. */
|
|
72
|
+
interface RowView {
|
|
73
|
+
readonly clientHeight: number;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
type CoreVirtualizer = Virtualizer<Element, Element>;
|
|
77
|
+
|
|
78
|
+
function scrollContainerView(scrollbox: ScrollBoxRenderable): ScrollContainerView {
|
|
79
|
+
return {
|
|
80
|
+
get scrollTop() {
|
|
81
|
+
return scrollbox.scrollTop;
|
|
82
|
+
},
|
|
83
|
+
get scrollHeight() {
|
|
84
|
+
return scrollbox.scrollHeight;
|
|
85
|
+
},
|
|
86
|
+
get clientHeight() {
|
|
87
|
+
return scrollbox.viewport.height;
|
|
88
|
+
},
|
|
89
|
+
get scrollWidth() {
|
|
90
|
+
return scrollbox.scrollWidth;
|
|
91
|
+
},
|
|
92
|
+
get clientWidth() {
|
|
93
|
+
return scrollbox.viewport.width;
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function rowView(renderable: BoxRenderable): RowView {
|
|
99
|
+
return {
|
|
100
|
+
get clientHeight() {
|
|
101
|
+
return renderable.height;
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* The core's element generics are DOM-typed, but every way it touches the container or a row
|
|
108
|
+
* (rect, offset, scroll, measurement, index lookup) is replaced here with a renderable-aware
|
|
109
|
+
* callback, so a view exposing just the members it reads stands in for the element.
|
|
110
|
+
*/
|
|
111
|
+
function asElement(view: ScrollContainerView | RowView): Element {
|
|
112
|
+
// SAFETY: the core never calls DOM methods on this value; its DOM defaults are all overridden.
|
|
113
|
+
return view as Element;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function useTerminalVirtualizer(options: TerminalVirtualizerOptions): TerminalVirtualizer {
|
|
117
|
+
const renderer = useRenderer();
|
|
118
|
+
const [, setRevision] = useState(0);
|
|
119
|
+
// one stable view per mounted row box, so the core's element cache keeps its identity
|
|
120
|
+
const viewOfRenderable = useRef(new WeakMap<BoxRenderable, Element>());
|
|
121
|
+
const indexOfView = useRef(new WeakMap<Element, number>());
|
|
122
|
+
const container = useRef<{ scrollbox: ScrollBoxRenderable; view: Element } | null>(null);
|
|
123
|
+
|
|
124
|
+
const scrollContainer = (): Element | null => {
|
|
125
|
+
const scrollbox = options.scrollbox.current;
|
|
126
|
+
|
|
127
|
+
if (!scrollbox) return null;
|
|
128
|
+
if (container.current?.scrollbox !== scrollbox) {
|
|
129
|
+
container.current = { scrollbox, view: asElement(scrollContainerView(scrollbox)) };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return container.current.view;
|
|
133
|
+
};
|
|
134
|
+
const viewFor = (renderable: BoxRenderable): Element => {
|
|
135
|
+
const known = viewOfRenderable.current.get(renderable);
|
|
136
|
+
|
|
137
|
+
if (known) return known;
|
|
138
|
+
const view = asElement(rowView(renderable));
|
|
139
|
+
|
|
140
|
+
viewOfRenderable.current.set(renderable, view);
|
|
141
|
+
|
|
142
|
+
return view;
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
// the offset reporter the core registered, so a scroll the core asks for reports at once
|
|
146
|
+
const reportOffset = useRef<(() => void) | null>(null);
|
|
147
|
+
|
|
148
|
+
// a frame-event poll stands in for scroll and resize events: the renderer fires it after every
|
|
149
|
+
// render, and the callbacks only fire the core when the value actually changed
|
|
150
|
+
const onFrame = (read: () => void): (() => void) => {
|
|
151
|
+
read();
|
|
152
|
+
|
|
153
|
+
return renderer ? subscribeToFrames(renderer, read) : () => {};
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const coreOptions = () => ({
|
|
157
|
+
count: options.count,
|
|
158
|
+
overscan: options.overscan,
|
|
159
|
+
estimateSize: options.estimateSize,
|
|
160
|
+
getScrollElement: scrollContainer,
|
|
161
|
+
observeElementRect: (
|
|
162
|
+
_instance: CoreVirtualizer,
|
|
163
|
+
callback: (rect: { width: number; height: number }) => void,
|
|
164
|
+
) => {
|
|
165
|
+
let last = { width: -1, height: -1 };
|
|
166
|
+
|
|
167
|
+
return onFrame(() => {
|
|
168
|
+
const scrollbox = options.scrollbox.current;
|
|
169
|
+
|
|
170
|
+
if (!scrollbox) return;
|
|
171
|
+
const next = { width: scrollbox.viewport.width, height: scrollbox.viewport.height };
|
|
172
|
+
|
|
173
|
+
if (next.width === last.width && next.height === last.height) return;
|
|
174
|
+
last = next;
|
|
175
|
+
callback(next);
|
|
176
|
+
});
|
|
177
|
+
},
|
|
178
|
+
observeElementOffset: (
|
|
179
|
+
_instance: CoreVirtualizer,
|
|
180
|
+
callback: (offset: number, isScrolling: boolean) => void,
|
|
181
|
+
) => {
|
|
182
|
+
let last = -1;
|
|
183
|
+
const report = (): void => {
|
|
184
|
+
const scrollbox = options.scrollbox.current;
|
|
185
|
+
|
|
186
|
+
if (!scrollbox || scrollbox.scrollTop === last) return;
|
|
187
|
+
last = scrollbox.scrollTop;
|
|
188
|
+
callback(last, false);
|
|
189
|
+
};
|
|
190
|
+
const stop = onFrame(report);
|
|
191
|
+
|
|
192
|
+
reportOffset.current = report;
|
|
193
|
+
|
|
194
|
+
return () => {
|
|
195
|
+
reportOffset.current = null;
|
|
196
|
+
stop();
|
|
197
|
+
};
|
|
198
|
+
},
|
|
199
|
+
// report the new offset in the same tick: the item window then follows the scroll at once,
|
|
200
|
+
// instead of one frame later with the target rows still unmounted
|
|
201
|
+
scrollToFn: (offset: number) => {
|
|
202
|
+
options.scrollbox.current?.scrollTo({ x: 0, y: Math.max(0, Math.round(offset)) });
|
|
203
|
+
reportOffset.current?.();
|
|
204
|
+
},
|
|
205
|
+
// a box measures 0 until Yoga has laid it out; keep the estimate rather than collapse the row
|
|
206
|
+
measureElement: (element: Element) =>
|
|
207
|
+
element.clientHeight > 0
|
|
208
|
+
? element.clientHeight
|
|
209
|
+
: options.estimateSize(indexOfView.current.get(element) ?? -1),
|
|
210
|
+
onChange: () => setRevision((revision) => revision + 1),
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
const [virtualizer] = useState<CoreVirtualizer>(() => {
|
|
214
|
+
const instance = new Virtualizer<Element, Element>(coreOptions());
|
|
215
|
+
|
|
216
|
+
// the core looks an item's index up through a DOM attribute; ours is remembered per row view
|
|
217
|
+
instance.indexFromElement = (element) => indexOfView.current.get(element) ?? -1;
|
|
218
|
+
|
|
219
|
+
return instance;
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
virtualizer.setOptions(coreOptions());
|
|
223
|
+
useLayoutEffect(() => {
|
|
224
|
+
virtualizer._willUpdate();
|
|
225
|
+
});
|
|
226
|
+
useEffect(() => virtualizer._didMount(), [virtualizer]);
|
|
227
|
+
// no ResizeObserver here: a mounted row's height settles after layout and grows when a card
|
|
228
|
+
// opens under it, so every frame re-reads the mounted rows (the core only notifies on change)
|
|
229
|
+
useEffect(
|
|
230
|
+
() =>
|
|
231
|
+
onFrame(() => {
|
|
232
|
+
for (const element of virtualizer.elementsCache.values()) {
|
|
233
|
+
virtualizer.measureElement(element);
|
|
234
|
+
}
|
|
235
|
+
}),
|
|
236
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
237
|
+
[virtualizer, renderer],
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
return {
|
|
241
|
+
items: virtualizer.getVirtualItems(),
|
|
242
|
+
totalSize: virtualizer.getTotalSize(),
|
|
243
|
+
measureRef: (index) => (renderable) => {
|
|
244
|
+
if (!renderable) return;
|
|
245
|
+
const view = viewFor(renderable);
|
|
246
|
+
|
|
247
|
+
indexOfView.current.set(view, index);
|
|
248
|
+
virtualizer.measureElement(view);
|
|
249
|
+
|
|
250
|
+
// the row unmounted: forget its view so the frame re-measure stops reading it
|
|
251
|
+
return () => {
|
|
252
|
+
if (virtualizer.elementsCache.get(index) === view) virtualizer.elementsCache.delete(index);
|
|
253
|
+
};
|
|
254
|
+
},
|
|
255
|
+
scrollToIndex: (index, align = "auto") => virtualizer.scrollToIndex(index, { align }),
|
|
256
|
+
startOfIndex: (index) => virtualizer.getOffsetForIndex(index, "start")?.[0],
|
|
257
|
+
scrollToOffset: (offset) => virtualizer.scrollToOffset(offset, { align: "start" }),
|
|
258
|
+
};
|
|
259
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { DiffFileContents } from "@cueloop/schema";
|
|
3
|
+
import { DARK } from "../../appearance/theme";
|
|
4
|
+
import type { Story, StoryMeta } from "../../stories/story";
|
|
5
|
+
import { ChangesColumn } from "./ChangesColumn";
|
|
6
|
+
|
|
7
|
+
export const meta: StoryMeta = { title: "Surfaces/ChangesColumn" };
|
|
8
|
+
|
|
9
|
+
function Frame({ children }: { children: React.ReactNode }): React.ReactNode {
|
|
10
|
+
return <box style={{ width: "100%", height: "100%", flexDirection: "row" }}>{children}</box>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const files: DiffFileContents[] = [
|
|
14
|
+
{ path: "packages/client/src/App.tsx", oldContents: "a", newContents: "b", status: "modified" },
|
|
15
|
+
{
|
|
16
|
+
path: "packages/client/src/components/ChangesColumn.tsx",
|
|
17
|
+
oldContents: "",
|
|
18
|
+
newContents: "c",
|
|
19
|
+
status: "added",
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
path: "packages/daemon/src/thread-review.ts",
|
|
23
|
+
oldContents: "d",
|
|
24
|
+
newContents: "",
|
|
25
|
+
status: "deleted",
|
|
26
|
+
},
|
|
27
|
+
{ path: "README.md", oldContents: "e", newContents: "f", status: "modified" },
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
export const Changed: Story = {
|
|
31
|
+
render: () => (
|
|
32
|
+
<Frame>
|
|
33
|
+
<ChangesColumn
|
|
34
|
+
open
|
|
35
|
+
files={files}
|
|
36
|
+
selectedPath="packages/client/src/App.tsx"
|
|
37
|
+
onSelectFile={() => {}}
|
|
38
|
+
theme={DARK}
|
|
39
|
+
/>
|
|
40
|
+
</Frame>
|
|
41
|
+
),
|
|
42
|
+
expectedColors: [DARK.accent, DARK.border],
|
|
43
|
+
size: { width: 60, height: 12 },
|
|
44
|
+
};
|