@cueloop/client 0.1.0-alpha.10 → 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 +42 -34
- 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.autoclose.test.tsx → app/App.autoclose.test.tsx} +77 -51
- 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 -88
- package/src/App.plan-review.test.tsx +0 -259
- package/src/App.readonly.test.tsx +0 -148
- package/src/App.rendering.test.tsx +0 -108
- package/src/App.submit-confirm.test.tsx +0 -183
- package/src/App.test.tsx +0 -218
- package/src/App.tsx +0 -1249
- package/src/config.test.ts +0 -116
- package/src/config.ts +0 -150
- package/src/editor.ts +0 -34
- package/src/herdr.test.ts +0 -68
- package/src/herdr.ts +0 -34
- package/src/keymap.test.ts +0 -270
- package/src/keymap.ts +0 -173
- package/src/run.ts +0 -30
- package/src/serve.ts +0 -75
- package/src/session-controller.ts +0 -398
- package/src/syntax.test.ts +0 -41
- package/src/syntax.ts +0 -88
- 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 -364
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
// The one app shell: a single header row over four full-height panes - Threads, Thread, Changes,
|
|
2
|
+
// Project - divided by straight rules; each header cell's thin bottom rule shares the divider gray so
|
|
3
|
+
// the whole header underline reads as one line joining the side rules.
|
|
4
|
+
// The right region (Changes + Project) toggles as a unit: the Project pane is the right sidebar and is
|
|
5
|
+
// always present when the region is on, Changes rides on top of it, and a thin rail holds the sidebar
|
|
6
|
+
// toggle when the region is closed. Each pane owns its own header controls; the thread header never does.
|
|
7
|
+
|
|
8
|
+
import React, { useEffect, useRef } from "react";
|
|
9
|
+
import { useTerminalDimensions } from "@opentui/react";
|
|
10
|
+
import type { BoxRenderable } from "@opentui/core";
|
|
11
|
+
import { DARK, type Theme } from "../../appearance/theme";
|
|
12
|
+
import { useFrameMeasure } from "../../ui/use-frame-measure";
|
|
13
|
+
import { truncateTitle } from "../../ui/components/truncate-title";
|
|
14
|
+
import { PanelColumn } from "./PanelColumn";
|
|
15
|
+
import { IconButton } from "../../ui/components/primitives/IconButton";
|
|
16
|
+
import { NERD, HEADER_UNDERLINE_CHARS } from "../../ui/components/primitives/icons";
|
|
17
|
+
import { TooltipProvider } from "../../ui/components/Tooltip";
|
|
18
|
+
import { RootOverlayProvider } from "../../ui/components/RootOverlay";
|
|
19
|
+
import type { ExtensionUIContext } from "@cueloop/extension-api/client";
|
|
20
|
+
import { ClientExtensionRegistry } from "../../integrations/client-extension-registry";
|
|
21
|
+
import {
|
|
22
|
+
ExtensionZone,
|
|
23
|
+
useClientExtensionSnapshot,
|
|
24
|
+
} from "../../integrations/components/ExtensionZone";
|
|
25
|
+
import { WorkspacePanels } from "./WorkspacePanels";
|
|
26
|
+
|
|
27
|
+
const EMPTY_EXTENSION_REGISTRY = new ClientExtensionRegistry();
|
|
28
|
+
const EMPTY_EXTENSION_CONTEXT: ExtensionUIContext = { workspace: null, threadId: null };
|
|
29
|
+
|
|
30
|
+
interface ExtensionInputs {
|
|
31
|
+
registry: ClientExtensionRegistry;
|
|
32
|
+
context: ExtensionUIContext;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function extensionInputs(
|
|
36
|
+
registry?: ClientExtensionRegistry,
|
|
37
|
+
context?: ExtensionUIContext,
|
|
38
|
+
): ExtensionInputs {
|
|
39
|
+
return {
|
|
40
|
+
registry: registry ?? EMPTY_EXTENSION_REGISTRY,
|
|
41
|
+
context: context ?? EMPTY_EXTENSION_CONTEXT,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export type ProjectPanelMode = "changes" | "tree";
|
|
46
|
+
|
|
47
|
+
/** The thread title on one line: it shrinks with the header and tails off in an ellipsis rather than wrapping. */
|
|
48
|
+
function HeaderTitle({ title, color }: { title: string; color: string }): React.ReactNode {
|
|
49
|
+
const boxRef = useRef<BoxRenderable | null>(null);
|
|
50
|
+
const width = useFrameMeasure(
|
|
51
|
+
() => boxRef.current?.width ?? 0,
|
|
52
|
+
(left, right) => left === right,
|
|
53
|
+
0,
|
|
54
|
+
);
|
|
55
|
+
const clipped = width > 0 ? truncateTitle(title, width) : title;
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<box ref={boxRef} style={{ flexShrink: 1, minWidth: 0 }}>
|
|
59
|
+
<text fg={color} wrapMode="none">
|
|
60
|
+
{clipped}
|
|
61
|
+
</text>
|
|
62
|
+
</box>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface AppShellProps {
|
|
67
|
+
sidebarOpen: boolean;
|
|
68
|
+
onToggleSidebar: () => void;
|
|
69
|
+
onOpenMenu: () => void;
|
|
70
|
+
threadsPanel: React.ReactNode;
|
|
71
|
+
threadTitle: string;
|
|
72
|
+
threadActions?: React.ReactNode;
|
|
73
|
+
threadPanel: React.ReactNode;
|
|
74
|
+
changesOpen: boolean;
|
|
75
|
+
projectOpen: boolean;
|
|
76
|
+
onToggleChanges: () => void;
|
|
77
|
+
onToggleProject: () => void;
|
|
78
|
+
onToggleRight: () => void;
|
|
79
|
+
projectMode: ProjectPanelMode;
|
|
80
|
+
changesPanel?: React.ReactNode;
|
|
81
|
+
changesFooter?: React.ReactNode;
|
|
82
|
+
projectPanel: React.ReactNode;
|
|
83
|
+
zoomHideThread?: boolean;
|
|
84
|
+
footer?: React.ReactNode;
|
|
85
|
+
children?: React.ReactNode;
|
|
86
|
+
theme?: Theme;
|
|
87
|
+
threadsWidth?: number;
|
|
88
|
+
projectWidth?: number;
|
|
89
|
+
onFocusPane?: (pane: FocusPane) => void;
|
|
90
|
+
focusedPane?: FocusPane;
|
|
91
|
+
extensionRegistry?: ClientExtensionRegistry;
|
|
92
|
+
extensionContext?: ExtensionUIContext;
|
|
93
|
+
onExtensionError?: (message: string) => void;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export type FocusPane = "threads" | "thread" | "changes" | "project";
|
|
97
|
+
|
|
98
|
+
/** The right region's reserved width: the open project pane, nothing when the region is closed, else the collapsed rail. */
|
|
99
|
+
function rightRegionColumns(
|
|
100
|
+
projectOpen: boolean,
|
|
101
|
+
projectWidth: number,
|
|
102
|
+
rightRegionClosed: boolean,
|
|
103
|
+
collapsedRailWidth: number,
|
|
104
|
+
): number {
|
|
105
|
+
if (projectOpen) return projectWidth;
|
|
106
|
+
|
|
107
|
+
return rightRegionClosed ? 0 : collapsedRailWidth;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** The Thread header's right cluster: the owner actions, plus the reopen control when the region is closed. */
|
|
111
|
+
function threadHeaderRight(
|
|
112
|
+
threadActions: React.ReactNode,
|
|
113
|
+
reopenControl: React.ReactNode,
|
|
114
|
+
showReopen: boolean,
|
|
115
|
+
): React.ReactNode {
|
|
116
|
+
if (!showReopen) return threadActions;
|
|
117
|
+
|
|
118
|
+
return (
|
|
119
|
+
<box style={{ flexDirection: "row", alignItems: "center" }}>
|
|
120
|
+
{threadActions ? <box style={{ paddingRight: 2 }}>{threadActions}</box> : null}
|
|
121
|
+
{reopenControl}
|
|
122
|
+
</box>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function AppShell({
|
|
127
|
+
sidebarOpen,
|
|
128
|
+
onToggleSidebar,
|
|
129
|
+
onOpenMenu,
|
|
130
|
+
threadsPanel,
|
|
131
|
+
threadTitle,
|
|
132
|
+
threadActions,
|
|
133
|
+
threadPanel,
|
|
134
|
+
changesOpen,
|
|
135
|
+
projectOpen,
|
|
136
|
+
onToggleChanges,
|
|
137
|
+
onToggleProject,
|
|
138
|
+
onToggleRight,
|
|
139
|
+
projectMode,
|
|
140
|
+
changesPanel,
|
|
141
|
+
changesFooter,
|
|
142
|
+
projectPanel,
|
|
143
|
+
zoomHideThread,
|
|
144
|
+
footer,
|
|
145
|
+
children,
|
|
146
|
+
theme,
|
|
147
|
+
threadsWidth = 30,
|
|
148
|
+
projectWidth = 32,
|
|
149
|
+
onFocusPane,
|
|
150
|
+
focusedPane,
|
|
151
|
+
extensionRegistry,
|
|
152
|
+
extensionContext,
|
|
153
|
+
onExtensionError,
|
|
154
|
+
}: AppShellProps): React.ReactNode {
|
|
155
|
+
const { registry, context } = extensionInputs(extensionRegistry, extensionContext);
|
|
156
|
+
const extensionSnapshot = useClientExtensionSnapshot(registry);
|
|
157
|
+
|
|
158
|
+
useEffect(() => {
|
|
159
|
+
if (extensionSnapshot.lastError) onExtensionError?.(extensionSnapshot.lastError);
|
|
160
|
+
}, [extensionSnapshot.lastError, onExtensionError]);
|
|
161
|
+
|
|
162
|
+
const tokens = theme ?? DARK;
|
|
163
|
+
const { width: terminalWidth } = useTerminalDimensions();
|
|
164
|
+
// with both panels closed the right region collapses to nothing: the reopen toggle
|
|
165
|
+
// rides the thread header, and the middle reclaims the width the rail would have taken
|
|
166
|
+
const rightRegionClosed = !projectOpen && !changesOpen;
|
|
167
|
+
// Thread and Changes would otherwise split the middle as two flexBasis-0 items, and Yoga adds the
|
|
168
|
+
// Changes left border on top of its share: at even widths the halves come out fractional, the
|
|
169
|
+
// Changes side rounds up, and the row overflows a cell under the Project border. Sizing the Thread
|
|
170
|
+
// pane to a whole number leaves Changes the lone flex item, which lays out exactly.
|
|
171
|
+
const collapsedRailWidth = 4;
|
|
172
|
+
const rightRegionWidth = rightRegionColumns(
|
|
173
|
+
projectOpen,
|
|
174
|
+
projectWidth,
|
|
175
|
+
rightRegionClosed,
|
|
176
|
+
collapsedRailWidth,
|
|
177
|
+
);
|
|
178
|
+
const middleWidth = terminalWidth - (sidebarOpen ? threadsWidth : 0) - rightRegionWidth;
|
|
179
|
+
const threadPaneWidth =
|
|
180
|
+
changesOpen && !zoomHideThread ? Math.max(20, Math.floor(middleWidth / 2)) : undefined;
|
|
181
|
+
|
|
182
|
+
// gear + sidebar toggle + product mark: global chrome, in the Threads header when open, else at the
|
|
183
|
+
// left of the Thread header when the Threads pane is collapsed
|
|
184
|
+
const brandChrome = (
|
|
185
|
+
<box style={{ flexDirection: "row", flexShrink: 0 }}>
|
|
186
|
+
<box onMouseUp={onOpenMenu} style={{ paddingRight: 2 }}>
|
|
187
|
+
<text fg={tokens.textMuted}>{NERD.settings}</text>
|
|
188
|
+
</box>
|
|
189
|
+
<IconButton
|
|
190
|
+
glyph={sidebarOpen ? NERD.sidebarLeft : NERD.sidebarLeftOff}
|
|
191
|
+
onPress={onToggleSidebar}
|
|
192
|
+
tip="Toggle Threads"
|
|
193
|
+
marginRight={2}
|
|
194
|
+
theme={tokens}
|
|
195
|
+
/>
|
|
196
|
+
<text fg={tokens.accent}>cueloop</text>
|
|
197
|
+
</box>
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
// the reopen control - a divider tick then the toggle - shared by the collapsed
|
|
201
|
+
// rail and, when the rail is suppressed, the thread header
|
|
202
|
+
const reopenRightControl = (
|
|
203
|
+
<>
|
|
204
|
+
<text fg={tokens.border}>{"│"}</text>
|
|
205
|
+
<IconButton
|
|
206
|
+
glyph={NERD.sidebarRightOff}
|
|
207
|
+
onPress={onToggleRight}
|
|
208
|
+
tip="Toggle Sidebar"
|
|
209
|
+
marginLeft={1}
|
|
210
|
+
theme={tokens}
|
|
211
|
+
/>
|
|
212
|
+
</>
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
return (
|
|
216
|
+
<box
|
|
217
|
+
style={{
|
|
218
|
+
flexDirection: "column",
|
|
219
|
+
width: "100%",
|
|
220
|
+
height: "100%",
|
|
221
|
+
backgroundColor: tokens.background,
|
|
222
|
+
}}
|
|
223
|
+
>
|
|
224
|
+
<RootOverlayProvider>
|
|
225
|
+
<TooltipProvider theme={tokens}>
|
|
226
|
+
<box style={{ flexGrow: 1, flexDirection: "row" }}>
|
|
227
|
+
{sidebarOpen ? (
|
|
228
|
+
<PanelColumn
|
|
229
|
+
width={threadsWidth}
|
|
230
|
+
border="right"
|
|
231
|
+
header={brandChrome}
|
|
232
|
+
onFocus={() => onFocusPane?.("threads")}
|
|
233
|
+
theme={tokens}
|
|
234
|
+
>
|
|
235
|
+
{threadsPanel}
|
|
236
|
+
<ExtensionZone
|
|
237
|
+
zone="threads.sidebar"
|
|
238
|
+
registry={registry}
|
|
239
|
+
context={context}
|
|
240
|
+
onError={onExtensionError}
|
|
241
|
+
/>
|
|
242
|
+
</PanelColumn>
|
|
243
|
+
) : null}
|
|
244
|
+
{!zoomHideThread ? (
|
|
245
|
+
<PanelColumn
|
|
246
|
+
width={threadPaneWidth}
|
|
247
|
+
header={
|
|
248
|
+
<box style={{ flexDirection: "row", minWidth: 0 }}>
|
|
249
|
+
{!sidebarOpen ? (
|
|
250
|
+
<box style={{ paddingRight: 2, flexShrink: 0 }}>{brandChrome}</box>
|
|
251
|
+
) : null}
|
|
252
|
+
{threadTitle ? (
|
|
253
|
+
<HeaderTitle title={threadTitle} color={tokens.textDim} />
|
|
254
|
+
) : null}
|
|
255
|
+
</box>
|
|
256
|
+
}
|
|
257
|
+
// with the region closed, the reopen toggle rides the thread header instead of an empty column
|
|
258
|
+
headerRight={threadHeaderRight(
|
|
259
|
+
extensionSnapshot.actions.length > 0 ? (
|
|
260
|
+
<box style={{ flexDirection: "row", alignItems: "center" }}>
|
|
261
|
+
{threadActions}
|
|
262
|
+
<ExtensionZone
|
|
263
|
+
zone="thread.header"
|
|
264
|
+
registry={registry}
|
|
265
|
+
context={context}
|
|
266
|
+
onError={onExtensionError}
|
|
267
|
+
/>
|
|
268
|
+
</box>
|
|
269
|
+
) : (
|
|
270
|
+
threadActions
|
|
271
|
+
),
|
|
272
|
+
reopenRightControl,
|
|
273
|
+
rightRegionClosed,
|
|
274
|
+
)}
|
|
275
|
+
onFocus={() => onFocusPane?.("thread")}
|
|
276
|
+
theme={tokens}
|
|
277
|
+
>
|
|
278
|
+
{threadPanel}
|
|
279
|
+
</PanelColumn>
|
|
280
|
+
) : null}
|
|
281
|
+
{changesOpen ? (
|
|
282
|
+
<box
|
|
283
|
+
onMouseDown={() => onFocusPane?.("changes")}
|
|
284
|
+
style={{
|
|
285
|
+
flexDirection: "column",
|
|
286
|
+
flexGrow: 1,
|
|
287
|
+
flexBasis: 0,
|
|
288
|
+
minWidth: 0,
|
|
289
|
+
borderStyle: "single",
|
|
290
|
+
// zoom drops the Thread pane, whose sidebar rule already divides here, so drop this
|
|
291
|
+
// one then to avoid a double border
|
|
292
|
+
border: zoomHideThread ? [] : ["left"],
|
|
293
|
+
borderColor: tokens.border,
|
|
294
|
+
}}
|
|
295
|
+
>
|
|
296
|
+
<box style={{ flexGrow: 1, minHeight: 0 }}>{changesPanel}</box>
|
|
297
|
+
{zoomHideThread ? changesFooter : null}
|
|
298
|
+
</box>
|
|
299
|
+
) : null}
|
|
300
|
+
{projectOpen ? (
|
|
301
|
+
<WorkspacePanels
|
|
302
|
+
width={projectWidth}
|
|
303
|
+
projectMode={projectMode}
|
|
304
|
+
projectPanel={projectPanel}
|
|
305
|
+
onToggleChanges={onToggleChanges}
|
|
306
|
+
onToggleProject={onToggleProject}
|
|
307
|
+
onToggleRight={onToggleRight}
|
|
308
|
+
onFocus={() => onFocusPane?.("project")}
|
|
309
|
+
focused={focusedPane === "project"}
|
|
310
|
+
registry={registry}
|
|
311
|
+
context={context}
|
|
312
|
+
theme={tokens}
|
|
313
|
+
onExtensionError={onExtensionError}
|
|
314
|
+
/>
|
|
315
|
+
) : rightRegionClosed ? null : (
|
|
316
|
+
// Changes open but Project closed: a divider tick above the continuous header underline holds the reopen toggle;
|
|
317
|
+
// the rule lives in the header only, never running the pane's full height. The extra column
|
|
318
|
+
// of right padding keeps the reopen icon off the terminal's last column, which squeezes it
|
|
319
|
+
<box style={{ flexDirection: "column", width: 4 }}>
|
|
320
|
+
<box
|
|
321
|
+
customBorderChars={HEADER_UNDERLINE_CHARS}
|
|
322
|
+
style={{
|
|
323
|
+
flexDirection: "row",
|
|
324
|
+
alignItems: "center",
|
|
325
|
+
height: 2,
|
|
326
|
+
paddingRight: 1,
|
|
327
|
+
backgroundColor: tokens.panel,
|
|
328
|
+
borderStyle: "single",
|
|
329
|
+
border: ["bottom"],
|
|
330
|
+
borderColor: tokens.border,
|
|
331
|
+
}}
|
|
332
|
+
>
|
|
333
|
+
{reopenRightControl}
|
|
334
|
+
</box>
|
|
335
|
+
</box>
|
|
336
|
+
)}
|
|
337
|
+
</box>
|
|
338
|
+
{footer !== undefined ? (
|
|
339
|
+
<box
|
|
340
|
+
style={{
|
|
341
|
+
flexDirection: "row",
|
|
342
|
+
height: 1,
|
|
343
|
+
paddingLeft: 1,
|
|
344
|
+
paddingRight: 1,
|
|
345
|
+
borderStyle: "single",
|
|
346
|
+
border: ["top"],
|
|
347
|
+
borderColor: tokens.border,
|
|
348
|
+
}}
|
|
349
|
+
>
|
|
350
|
+
{footer}
|
|
351
|
+
</box>
|
|
352
|
+
) : null}
|
|
353
|
+
{children}
|
|
354
|
+
</TooltipProvider>
|
|
355
|
+
</RootOverlayProvider>
|
|
356
|
+
</box>
|
|
357
|
+
);
|
|
358
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regression: the Changes header controls keep their column when zoom hides the Thread
|
|
3
|
+
* pane. Thread and Changes used to split the middle as two flexBasis-0 items, and Yoga
|
|
4
|
+
* added the Changes left border on top of its share, so at even terminal widths the
|
|
5
|
+
* Changes pane overflowed a cell under the Project border and the zoom icon appeared to
|
|
6
|
+
* jump left on zoom. The shell now sizes the Thread pane to a whole number.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { describe, expect, test } from "bun:test";
|
|
10
|
+
import { testRender } from "@opentui/react/test-utils";
|
|
11
|
+
import React from "react";
|
|
12
|
+
import { AppShell } from "./AppShell";
|
|
13
|
+
import { EditorGrid } from "../../workbench/components/EditorGrid";
|
|
14
|
+
import { changesTab, makeGroup, splitGroup } from "../../workbench/components/editor-grid";
|
|
15
|
+
import { NERD } from "../../ui/components/primitives/icons";
|
|
16
|
+
import { allowEventLoopUpdates, settle } from "../../testing/test-support";
|
|
17
|
+
|
|
18
|
+
const grid = makeGroup([changesTab()]);
|
|
19
|
+
|
|
20
|
+
async function zoomIconColumn(
|
|
21
|
+
width: number,
|
|
22
|
+
zoomed: boolean,
|
|
23
|
+
sidebarOpen: boolean,
|
|
24
|
+
splitEditor = false,
|
|
25
|
+
): Promise<number> {
|
|
26
|
+
const editor = splitEditor
|
|
27
|
+
? splitGroup(grid, grid.id, "right")
|
|
28
|
+
: { tree: grid, focusGroupId: grid.id };
|
|
29
|
+
const setup = await testRender(
|
|
30
|
+
<AppShell
|
|
31
|
+
sidebarOpen={sidebarOpen}
|
|
32
|
+
onToggleSidebar={() => {}}
|
|
33
|
+
onOpenMenu={() => {}}
|
|
34
|
+
threadsPanel={<text>threads</text>}
|
|
35
|
+
threadTitle="thread"
|
|
36
|
+
threadPanel={<text>body</text>}
|
|
37
|
+
changesOpen
|
|
38
|
+
projectOpen
|
|
39
|
+
onToggleChanges={() => {}}
|
|
40
|
+
onToggleProject={() => {}}
|
|
41
|
+
onToggleRight={() => {}}
|
|
42
|
+
projectMode="changes"
|
|
43
|
+
zoomHideThread={zoomed}
|
|
44
|
+
projectPanel={<text>project</text>}
|
|
45
|
+
changesPanel={
|
|
46
|
+
<EditorGrid
|
|
47
|
+
tree={editor.tree}
|
|
48
|
+
focusedGroupId={editor.focusGroupId}
|
|
49
|
+
onFocusGroup={() => {}}
|
|
50
|
+
onActivateTab={() => {}}
|
|
51
|
+
onCloseTab={() => {}}
|
|
52
|
+
onSplit={() => {}}
|
|
53
|
+
onZoom={() => {}}
|
|
54
|
+
zoomed={zoomed}
|
|
55
|
+
renderTab={() => <text>diff</text>}
|
|
56
|
+
/>
|
|
57
|
+
}
|
|
58
|
+
/>,
|
|
59
|
+
{ width, height: 8 },
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
// Split widths commit through React after the frame measurement. Renderer idle alone
|
|
63
|
+
// can still expose the initial fractional split, before its whole-cell widths land.
|
|
64
|
+
await settle(setup);
|
|
65
|
+
const column = setup.captureCharFrame().split("\n")[0]!.indexOf(NERD.zoom);
|
|
66
|
+
|
|
67
|
+
setup.renderer.destroy();
|
|
68
|
+
|
|
69
|
+
return column;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
describe("zoom icon in the shell", () => {
|
|
73
|
+
// even widths are where the fractional split used to overflow; odd widths never did
|
|
74
|
+
for (const width of [160, 161]) {
|
|
75
|
+
for (const sidebarOpen of [false, true]) {
|
|
76
|
+
test(`stays in its column when zoom toggles (width ${width}, sidebar ${sidebarOpen ? "open" : "closed"})`, async () => {
|
|
77
|
+
// Act
|
|
78
|
+
const unzoomed = await zoomIconColumn(width, false, sidebarOpen);
|
|
79
|
+
const zoomed = await zoomIconColumn(width, true, sidebarOpen);
|
|
80
|
+
|
|
81
|
+
// Assert
|
|
82
|
+
expect(unzoomed).toBeGreaterThan(0);
|
|
83
|
+
expect(zoomed).toBe(unzoomed);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
test("the footer rides the Changes pane only while the Thread pane is zoomed away", async () => {
|
|
89
|
+
async function footerVisible(zoomed: boolean): Promise<boolean> {
|
|
90
|
+
const setup = await testRender(
|
|
91
|
+
<AppShell
|
|
92
|
+
sidebarOpen={false}
|
|
93
|
+
onToggleSidebar={() => {}}
|
|
94
|
+
onOpenMenu={() => {}}
|
|
95
|
+
threadsPanel={<text>threads</text>}
|
|
96
|
+
threadTitle="thread"
|
|
97
|
+
threadPanel={<text>body</text>}
|
|
98
|
+
changesOpen
|
|
99
|
+
projectOpen
|
|
100
|
+
onToggleChanges={() => {}}
|
|
101
|
+
onToggleProject={() => {}}
|
|
102
|
+
onToggleRight={() => {}}
|
|
103
|
+
projectMode="changes"
|
|
104
|
+
zoomHideThread={zoomed}
|
|
105
|
+
projectPanel={<text>project</text>}
|
|
106
|
+
changesFooter={<text>send message</text>}
|
|
107
|
+
changesPanel={<text>diff</text>}
|
|
108
|
+
/>,
|
|
109
|
+
{ width: 160, height: 8 },
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
allowEventLoopUpdates();
|
|
113
|
+
await setup.waitForVisualIdle();
|
|
114
|
+
const shown = setup.captureCharFrame().includes("send message");
|
|
115
|
+
|
|
116
|
+
setup.renderer.destroy();
|
|
117
|
+
|
|
118
|
+
return shown;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Act
|
|
122
|
+
const [zoomed, unzoomed] = await Promise.all([footerVisible(true), footerVisible(false)]);
|
|
123
|
+
|
|
124
|
+
// Assert - the Thread pane carries the footer normally, so it must not double up in Changes
|
|
125
|
+
expect(zoomed).toBe(true);
|
|
126
|
+
expect(unzoomed).toBe(false);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test("the icon clears the Project border by its padding in both states", async () => {
|
|
130
|
+
// Act
|
|
131
|
+
const setupColumns = await Promise.all([
|
|
132
|
+
zoomIconColumn(160, false, false),
|
|
133
|
+
zoomIconColumn(160, true, false),
|
|
134
|
+
]);
|
|
135
|
+
|
|
136
|
+
// Assert - width 160, project pane 32: the border sits at 128 and the glyph one padding cell
|
|
137
|
+
// before it, the same inset the right-sidebar icon keeps from its own edge
|
|
138
|
+
for (const column of setupColumns) expect(128 - column).toBe(2);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("a split editor keeps the zoom icon clear of the Project border", async () => {
|
|
142
|
+
const columns = await Promise.all([
|
|
143
|
+
zoomIconColumn(160, false, false, true),
|
|
144
|
+
zoomIconColumn(160, true, false, true),
|
|
145
|
+
]);
|
|
146
|
+
|
|
147
|
+
for (const column of columns) expect(128 - column).toBe(2);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { DARK } from "../../appearance/theme";
|
|
3
|
+
import type { Story, StoryMeta } from "../../stories/story";
|
|
4
|
+
import { Breadcrumb } from "./Breadcrumb";
|
|
5
|
+
|
|
6
|
+
export const meta: StoryMeta = { title: "Chrome/Breadcrumb" };
|
|
7
|
+
|
|
8
|
+
export const SessionHeader: Story = {
|
|
9
|
+
render: () => (
|
|
10
|
+
<Breadcrumb
|
|
11
|
+
items={[
|
|
12
|
+
{ label: "cueloop", tone: "accent" },
|
|
13
|
+
{ label: "Migration Plan · rev 2", tone: "dim" },
|
|
14
|
+
{ label: "resolved: approved", tone: "green" },
|
|
15
|
+
]}
|
|
16
|
+
/>
|
|
17
|
+
),
|
|
18
|
+
expectedColors: [DARK.accent, DARK.green],
|
|
19
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One-row breadcrumb trail: toned segments joined by " · " on the panel
|
|
3
|
+
* surface. The app header is a breadcrumb (product · artifact · revision)
|
|
4
|
+
* followed by state badges; each item picks a semantic tone.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React from "react";
|
|
8
|
+
import type { Theme } from "../../appearance/theme";
|
|
9
|
+
import { useComponentTheme } from "../../appearance/components/theme-context";
|
|
10
|
+
|
|
11
|
+
export type BreadcrumbTone = "accent" | "dim" | "text" | "green";
|
|
12
|
+
|
|
13
|
+
export interface BreadcrumbItem {
|
|
14
|
+
label: string;
|
|
15
|
+
tone?: BreadcrumbTone;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface BreadcrumbProps {
|
|
19
|
+
items: BreadcrumbItem[];
|
|
20
|
+
theme?: Theme;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function Breadcrumb({ items, theme }: BreadcrumbProps): React.ReactNode {
|
|
24
|
+
const tokens = useComponentTheme(theme);
|
|
25
|
+
const toneColor = (tone?: BreadcrumbTone): string =>
|
|
26
|
+
tone === "accent"
|
|
27
|
+
? tokens.accent
|
|
28
|
+
: tone === "green"
|
|
29
|
+
? tokens.green
|
|
30
|
+
: tone === "text"
|
|
31
|
+
? tokens.text
|
|
32
|
+
: tokens.textDim;
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<box style={{ height: 1, backgroundColor: tokens.panel, paddingLeft: 1, flexDirection: "row" }}>
|
|
36
|
+
<text fg={tokens.text}>
|
|
37
|
+
{items.map((item, index) => (
|
|
38
|
+
<span key={index} fg={toneColor(item.tone)}>
|
|
39
|
+
{index === 0 ? item.label : ` · ${item.label}`}
|
|
40
|
+
</span>
|
|
41
|
+
))}
|
|
42
|
+
</text>
|
|
43
|
+
</box>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { DARK } from "../../appearance/theme";
|
|
3
|
+
import type { Story, StoryMeta } from "../../stories/story";
|
|
4
|
+
import { MenuBar } from "./MenuBar";
|
|
5
|
+
|
|
6
|
+
export const meta: StoryMeta = { title: "Chrome/MenuBar" };
|
|
7
|
+
|
|
8
|
+
const callbacks = { onSettings: () => {}, onKeybinds: () => {} };
|
|
9
|
+
|
|
10
|
+
function BottomFrame({ children }: { children: React.ReactNode }): React.ReactNode {
|
|
11
|
+
return (
|
|
12
|
+
<box
|
|
13
|
+
style={{ flexDirection: "column", width: "100%", height: "100%", justifyContent: "flex-end" }}
|
|
14
|
+
>
|
|
15
|
+
{children}
|
|
16
|
+
</box>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const Open: Story = {
|
|
21
|
+
render: () => (
|
|
22
|
+
<BottomFrame>
|
|
23
|
+
<MenuBar open {...callbacks} />
|
|
24
|
+
</BottomFrame>
|
|
25
|
+
),
|
|
26
|
+
expectedColors: [DARK.text],
|
|
27
|
+
size: { width: 60, height: 8 },
|
|
28
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { Theme } from "../../appearance/theme";
|
|
3
|
+
import { useComponentTheme } from "../../appearance/components/theme-context";
|
|
4
|
+
import { FRAME_BORDER_STYLE } from "../../ui/components/primitives/frame";
|
|
5
|
+
|
|
6
|
+
export interface MenuBarProps {
|
|
7
|
+
open: boolean;
|
|
8
|
+
onSettings: () => void;
|
|
9
|
+
onKeybinds: () => void;
|
|
10
|
+
theme?: Theme;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const DROPDOWN_OPTION_ROWS = 2;
|
|
14
|
+
|
|
15
|
+
/** The settings gear's drop-down: Settings and Keybinds, below the top-left gear. */
|
|
16
|
+
export function MenuBar({ open, onSettings, onKeybinds, theme }: MenuBarProps): React.ReactNode {
|
|
17
|
+
const tokens = useComponentTheme(theme);
|
|
18
|
+
const dropdownHeight = DROPDOWN_OPTION_ROWS + 2;
|
|
19
|
+
|
|
20
|
+
if (!open) return null;
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<box
|
|
24
|
+
style={{
|
|
25
|
+
position: "absolute",
|
|
26
|
+
left: 1,
|
|
27
|
+
top: 2,
|
|
28
|
+
width: 16,
|
|
29
|
+
height: dropdownHeight,
|
|
30
|
+
border: true,
|
|
31
|
+
borderStyle: FRAME_BORDER_STYLE,
|
|
32
|
+
borderColor: tokens.text,
|
|
33
|
+
flexDirection: "column",
|
|
34
|
+
}}
|
|
35
|
+
>
|
|
36
|
+
<box onMouseUp={onSettings}>
|
|
37
|
+
<text fg={tokens.text}> Settings</text>
|
|
38
|
+
</box>
|
|
39
|
+
<box onMouseUp={onKeybinds}>
|
|
40
|
+
<text fg={tokens.text}> Keybinds</text>
|
|
41
|
+
</box>
|
|
42
|
+
</box>
|
|
43
|
+
);
|
|
44
|
+
}
|