@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
|
@@ -0,0 +1,362 @@
|
|
|
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 "../theme";
|
|
12
|
+
import { useFrameMeasure } from "../use-frame-measure";
|
|
13
|
+
import { truncateTitle } from "./truncate-title";
|
|
14
|
+
import { PanelColumn } from "./PanelColumn";
|
|
15
|
+
import { IconButton } from "./primitives/IconButton";
|
|
16
|
+
import { NERD, HEADER_UNDERLINE_CHARS } from "./primitives/icons";
|
|
17
|
+
import { TooltipProvider } from "./Tooltip";
|
|
18
|
+
import { RootOverlayProvider } from "./RootOverlay";
|
|
19
|
+
import type { ExtensionUIContext } from "@cueloop/extension-api/client";
|
|
20
|
+
import { ClientExtensionRegistry } from "../client-extension-registry";
|
|
21
|
+
import { ExtensionZone, useClientExtensionSnapshot } from "./ExtensionZone";
|
|
22
|
+
import { WorkspacePanels } from "./WorkspacePanels";
|
|
23
|
+
|
|
24
|
+
const EMPTY_EXTENSION_REGISTRY = new ClientExtensionRegistry();
|
|
25
|
+
const EMPTY_EXTENSION_CONTEXT: ExtensionUIContext = { workspace: null, threadId: null };
|
|
26
|
+
|
|
27
|
+
interface ExtensionInputs {
|
|
28
|
+
registry: ClientExtensionRegistry;
|
|
29
|
+
context: ExtensionUIContext;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function extensionInputs(
|
|
33
|
+
registry?: ClientExtensionRegistry,
|
|
34
|
+
context?: ExtensionUIContext,
|
|
35
|
+
): ExtensionInputs {
|
|
36
|
+
return {
|
|
37
|
+
registry: registry ?? EMPTY_EXTENSION_REGISTRY,
|
|
38
|
+
context: context ?? EMPTY_EXTENSION_CONTEXT,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type ProjectPanelMode = "changes" | "tree";
|
|
43
|
+
|
|
44
|
+
/** The thread title on one line: it shrinks with the header and tails off in an ellipsis rather than wrapping. */
|
|
45
|
+
function HeaderTitle({ title, color }: { title: string; color: string }): React.ReactNode {
|
|
46
|
+
const boxRef = useRef<BoxRenderable | null>(null);
|
|
47
|
+
const width = useFrameMeasure(
|
|
48
|
+
() => boxRef.current?.width ?? 0,
|
|
49
|
+
(left, right) => left === right,
|
|
50
|
+
0,
|
|
51
|
+
);
|
|
52
|
+
const clipped = width > 0 ? truncateTitle(title, width) : title;
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<box ref={boxRef} style={{ flexShrink: 1, minWidth: 0 }}>
|
|
56
|
+
<text fg={color} wrapMode="none">
|
|
57
|
+
{clipped}
|
|
58
|
+
</text>
|
|
59
|
+
</box>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface AppShellProps {
|
|
64
|
+
sidebarOpen: boolean;
|
|
65
|
+
onToggleSidebar: () => void;
|
|
66
|
+
onOpenMenu: () => void;
|
|
67
|
+
threadsPanel: React.ReactNode;
|
|
68
|
+
/** The Thread header title; mirrors the selected sidebar thread, blank on a bare launch. */
|
|
69
|
+
threadTitle: string;
|
|
70
|
+
/** Owner actions at the right edge of the Thread header (Edit/Share) - the only thread-header controls. */
|
|
71
|
+
threadActions?: React.ReactNode;
|
|
72
|
+
threadPanel: React.ReactNode;
|
|
73
|
+
changesOpen: boolean;
|
|
74
|
+
projectOpen: boolean;
|
|
75
|
+
/** Toggle the Changes editor (also switches the Project tree to changed-files mode). */
|
|
76
|
+
onToggleChanges: () => void;
|
|
77
|
+
/** Toggle the Project tree to the full project view. */
|
|
78
|
+
onToggleProject: () => void;
|
|
79
|
+
/** Open or close the whole right region (the right sidebar). */
|
|
80
|
+
onToggleRight: () => void;
|
|
81
|
+
projectMode: ProjectPanelMode;
|
|
82
|
+
/** The Changes editor grid; rendered only when changesOpen. */
|
|
83
|
+
changesPanel?: React.ReactNode;
|
|
84
|
+
/** The thread footer, shown beneath the Changes grid while the Thread pane is zoomed away. */
|
|
85
|
+
changesFooter?: React.ReactNode;
|
|
86
|
+
projectPanel: React.ReactNode;
|
|
87
|
+
/** Hide the Thread pane so Changes fills the middle (zoom); the sidebars stay. */
|
|
88
|
+
zoomHideThread?: boolean;
|
|
89
|
+
footer?: React.ReactNode;
|
|
90
|
+
children?: React.ReactNode;
|
|
91
|
+
theme?: Theme;
|
|
92
|
+
threadsWidth?: number;
|
|
93
|
+
projectWidth?: number;
|
|
94
|
+
onFocusPane?: (pane: FocusPane) => void;
|
|
95
|
+
focusedPane?: FocusPane;
|
|
96
|
+
extensionRegistry?: ClientExtensionRegistry;
|
|
97
|
+
extensionContext?: ExtensionUIContext;
|
|
98
|
+
onExtensionError?: (message: string) => void;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export type FocusPane = "threads" | "thread" | "changes" | "project";
|
|
102
|
+
|
|
103
|
+
/** The right region's reserved width: the open project pane, nothing when the region is closed, else the collapsed rail. */
|
|
104
|
+
function rightRegionColumns(
|
|
105
|
+
projectOpen: boolean,
|
|
106
|
+
projectWidth: number,
|
|
107
|
+
rightRegionClosed: boolean,
|
|
108
|
+
collapsedRailWidth: number,
|
|
109
|
+
): number {
|
|
110
|
+
if (projectOpen) return projectWidth;
|
|
111
|
+
|
|
112
|
+
return rightRegionClosed ? 0 : collapsedRailWidth;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** The Thread header's right cluster: the owner actions, plus the reopen control when the region is closed. */
|
|
116
|
+
function threadHeaderRight(
|
|
117
|
+
threadActions: React.ReactNode,
|
|
118
|
+
reopenControl: React.ReactNode,
|
|
119
|
+
showReopen: boolean,
|
|
120
|
+
): React.ReactNode {
|
|
121
|
+
if (!showReopen) return threadActions;
|
|
122
|
+
|
|
123
|
+
return (
|
|
124
|
+
<box style={{ flexDirection: "row", alignItems: "center" }}>
|
|
125
|
+
{threadActions ? <box style={{ paddingRight: 2 }}>{threadActions}</box> : null}
|
|
126
|
+
{reopenControl}
|
|
127
|
+
</box>
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function AppShell({
|
|
132
|
+
sidebarOpen,
|
|
133
|
+
onToggleSidebar,
|
|
134
|
+
onOpenMenu,
|
|
135
|
+
threadsPanel,
|
|
136
|
+
threadTitle,
|
|
137
|
+
threadActions,
|
|
138
|
+
threadPanel,
|
|
139
|
+
changesOpen,
|
|
140
|
+
projectOpen,
|
|
141
|
+
onToggleChanges,
|
|
142
|
+
onToggleProject,
|
|
143
|
+
onToggleRight,
|
|
144
|
+
projectMode,
|
|
145
|
+
changesPanel,
|
|
146
|
+
changesFooter,
|
|
147
|
+
projectPanel,
|
|
148
|
+
zoomHideThread,
|
|
149
|
+
footer,
|
|
150
|
+
children,
|
|
151
|
+
theme,
|
|
152
|
+
threadsWidth = 30,
|
|
153
|
+
projectWidth = 32,
|
|
154
|
+
onFocusPane,
|
|
155
|
+
focusedPane,
|
|
156
|
+
extensionRegistry,
|
|
157
|
+
extensionContext,
|
|
158
|
+
onExtensionError,
|
|
159
|
+
}: AppShellProps): React.ReactNode {
|
|
160
|
+
const { registry, context } = extensionInputs(extensionRegistry, extensionContext);
|
|
161
|
+
const extensionSnapshot = useClientExtensionSnapshot(registry);
|
|
162
|
+
useEffect(() => {
|
|
163
|
+
if (extensionSnapshot.lastError) onExtensionError?.(extensionSnapshot.lastError);
|
|
164
|
+
}, [extensionSnapshot.lastError, onExtensionError]);
|
|
165
|
+
|
|
166
|
+
const tokens = theme ?? DARK;
|
|
167
|
+
const { width: terminalWidth } = useTerminalDimensions();
|
|
168
|
+
// with both panels closed the right region collapses to nothing: the reopen toggle
|
|
169
|
+
// rides the thread header, and the middle reclaims the width the rail would have taken
|
|
170
|
+
const rightRegionClosed = !projectOpen && !changesOpen;
|
|
171
|
+
// Thread and Changes would otherwise split the middle as two flexBasis-0 items, and Yoga adds the
|
|
172
|
+
// Changes left border on top of its share: at even widths the halves come out fractional, the
|
|
173
|
+
// Changes side rounds up, and the row overflows a cell under the Project border. Sizing the Thread
|
|
174
|
+
// pane to a whole number leaves Changes the lone flex item, which lays out exactly.
|
|
175
|
+
const collapsedRailWidth = 4;
|
|
176
|
+
const rightRegionWidth = rightRegionColumns(
|
|
177
|
+
projectOpen,
|
|
178
|
+
projectWidth,
|
|
179
|
+
rightRegionClosed,
|
|
180
|
+
collapsedRailWidth,
|
|
181
|
+
);
|
|
182
|
+
const middleWidth = terminalWidth - (sidebarOpen ? threadsWidth : 0) - rightRegionWidth;
|
|
183
|
+
const threadPaneWidth =
|
|
184
|
+
changesOpen && !zoomHideThread ? Math.max(20, Math.floor(middleWidth / 2)) : undefined;
|
|
185
|
+
|
|
186
|
+
// gear + sidebar toggle + product mark: global chrome, in the Threads header when open, else at the
|
|
187
|
+
// left of the Thread header when the Threads pane is collapsed
|
|
188
|
+
const brandChrome = (
|
|
189
|
+
<box style={{ flexDirection: "row", flexShrink: 0 }}>
|
|
190
|
+
<box onMouseUp={onOpenMenu} style={{ paddingRight: 2 }}>
|
|
191
|
+
<text fg={tokens.textMuted}>{NERD.settings}</text>
|
|
192
|
+
</box>
|
|
193
|
+
<IconButton
|
|
194
|
+
glyph={sidebarOpen ? NERD.sidebarLeft : NERD.sidebarLeftOff}
|
|
195
|
+
onPress={onToggleSidebar}
|
|
196
|
+
tip="Toggle Threads"
|
|
197
|
+
marginRight={2}
|
|
198
|
+
theme={tokens}
|
|
199
|
+
/>
|
|
200
|
+
<text fg={tokens.accent}>cueloop</text>
|
|
201
|
+
</box>
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
// the reopen control - a divider tick then the toggle - shared by the collapsed
|
|
205
|
+
// rail and, when the rail is suppressed, the thread header
|
|
206
|
+
const reopenRightControl = (
|
|
207
|
+
<>
|
|
208
|
+
<text fg={tokens.border}>{"│"}</text>
|
|
209
|
+
<IconButton
|
|
210
|
+
glyph={NERD.sidebarRightOff}
|
|
211
|
+
onPress={onToggleRight}
|
|
212
|
+
tip="Toggle Sidebar"
|
|
213
|
+
marginLeft={1}
|
|
214
|
+
theme={tokens}
|
|
215
|
+
/>
|
|
216
|
+
</>
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
return (
|
|
220
|
+
<box
|
|
221
|
+
style={{
|
|
222
|
+
flexDirection: "column",
|
|
223
|
+
width: "100%",
|
|
224
|
+
height: "100%",
|
|
225
|
+
backgroundColor: tokens.background,
|
|
226
|
+
}}
|
|
227
|
+
>
|
|
228
|
+
<RootOverlayProvider>
|
|
229
|
+
<TooltipProvider theme={tokens}>
|
|
230
|
+
<box style={{ flexGrow: 1, flexDirection: "row" }}>
|
|
231
|
+
{sidebarOpen ? (
|
|
232
|
+
<PanelColumn
|
|
233
|
+
width={threadsWidth}
|
|
234
|
+
border="right"
|
|
235
|
+
header={brandChrome}
|
|
236
|
+
onFocus={() => onFocusPane?.("threads")}
|
|
237
|
+
theme={tokens}
|
|
238
|
+
>
|
|
239
|
+
{threadsPanel}
|
|
240
|
+
<ExtensionZone
|
|
241
|
+
zone="threads.sidebar"
|
|
242
|
+
registry={registry}
|
|
243
|
+
context={context}
|
|
244
|
+
onError={onExtensionError}
|
|
245
|
+
/>
|
|
246
|
+
</PanelColumn>
|
|
247
|
+
) : null}
|
|
248
|
+
{!zoomHideThread ? (
|
|
249
|
+
<PanelColumn
|
|
250
|
+
width={threadPaneWidth}
|
|
251
|
+
header={
|
|
252
|
+
<box style={{ flexDirection: "row", minWidth: 0 }}>
|
|
253
|
+
{!sidebarOpen ? (
|
|
254
|
+
<box style={{ paddingRight: 2, flexShrink: 0 }}>{brandChrome}</box>
|
|
255
|
+
) : null}
|
|
256
|
+
{threadTitle ? (
|
|
257
|
+
<HeaderTitle title={threadTitle} color={tokens.textDim} />
|
|
258
|
+
) : null}
|
|
259
|
+
</box>
|
|
260
|
+
}
|
|
261
|
+
// with the region closed, the reopen toggle rides the thread header instead of an empty column
|
|
262
|
+
headerRight={threadHeaderRight(
|
|
263
|
+
extensionSnapshot.actions.length > 0 ? (
|
|
264
|
+
<box style={{ flexDirection: "row", alignItems: "center" }}>
|
|
265
|
+
{threadActions}
|
|
266
|
+
<ExtensionZone
|
|
267
|
+
zone="thread.header"
|
|
268
|
+
registry={registry}
|
|
269
|
+
context={context}
|
|
270
|
+
onError={onExtensionError}
|
|
271
|
+
/>
|
|
272
|
+
</box>
|
|
273
|
+
) : (
|
|
274
|
+
threadActions
|
|
275
|
+
),
|
|
276
|
+
reopenRightControl,
|
|
277
|
+
rightRegionClosed,
|
|
278
|
+
)}
|
|
279
|
+
onFocus={() => onFocusPane?.("thread")}
|
|
280
|
+
theme={tokens}
|
|
281
|
+
>
|
|
282
|
+
{threadPanel}
|
|
283
|
+
</PanelColumn>
|
|
284
|
+
) : null}
|
|
285
|
+
{changesOpen ? (
|
|
286
|
+
<box
|
|
287
|
+
onMouseDown={() => onFocusPane?.("changes")}
|
|
288
|
+
style={{
|
|
289
|
+
flexDirection: "column",
|
|
290
|
+
flexGrow: 1,
|
|
291
|
+
flexBasis: 0,
|
|
292
|
+
minWidth: 0,
|
|
293
|
+
borderStyle: "single",
|
|
294
|
+
// zoom drops the Thread pane, whose sidebar rule already divides here, so drop this
|
|
295
|
+
// one then to avoid a double border
|
|
296
|
+
border: zoomHideThread ? [] : ["left"],
|
|
297
|
+
borderColor: tokens.border,
|
|
298
|
+
}}
|
|
299
|
+
>
|
|
300
|
+
<box style={{ flexGrow: 1, minHeight: 0 }}>{changesPanel}</box>
|
|
301
|
+
{zoomHideThread ? changesFooter : null}
|
|
302
|
+
</box>
|
|
303
|
+
) : null}
|
|
304
|
+
{projectOpen ? (
|
|
305
|
+
<WorkspacePanels
|
|
306
|
+
width={projectWidth}
|
|
307
|
+
projectMode={projectMode}
|
|
308
|
+
projectPanel={projectPanel}
|
|
309
|
+
onToggleChanges={onToggleChanges}
|
|
310
|
+
onToggleProject={onToggleProject}
|
|
311
|
+
onToggleRight={onToggleRight}
|
|
312
|
+
onFocus={() => onFocusPane?.("project")}
|
|
313
|
+
focused={focusedPane === "project"}
|
|
314
|
+
registry={registry}
|
|
315
|
+
context={context}
|
|
316
|
+
theme={tokens}
|
|
317
|
+
onExtensionError={onExtensionError}
|
|
318
|
+
/>
|
|
319
|
+
) : rightRegionClosed ? null : (
|
|
320
|
+
// Changes open but Project closed: a divider tick above the continuous header underline holds the reopen toggle;
|
|
321
|
+
// the rule lives in the header only, never running the pane's full height. The extra column
|
|
322
|
+
// of right padding keeps the reopen icon off the terminal's last column, which squeezes it
|
|
323
|
+
<box style={{ flexDirection: "column", width: 4 }}>
|
|
324
|
+
<box
|
|
325
|
+
customBorderChars={HEADER_UNDERLINE_CHARS}
|
|
326
|
+
style={{
|
|
327
|
+
flexDirection: "row",
|
|
328
|
+
alignItems: "center",
|
|
329
|
+
height: 2,
|
|
330
|
+
paddingRight: 1,
|
|
331
|
+
backgroundColor: tokens.panel,
|
|
332
|
+
borderStyle: "single",
|
|
333
|
+
border: ["bottom"],
|
|
334
|
+
borderColor: tokens.border,
|
|
335
|
+
}}
|
|
336
|
+
>
|
|
337
|
+
{reopenRightControl}
|
|
338
|
+
</box>
|
|
339
|
+
</box>
|
|
340
|
+
)}
|
|
341
|
+
</box>
|
|
342
|
+
{footer !== undefined ? (
|
|
343
|
+
<box
|
|
344
|
+
style={{
|
|
345
|
+
flexDirection: "row",
|
|
346
|
+
height: 1,
|
|
347
|
+
paddingLeft: 1,
|
|
348
|
+
paddingRight: 1,
|
|
349
|
+
borderStyle: "single",
|
|
350
|
+
border: ["top"],
|
|
351
|
+
borderColor: tokens.border,
|
|
352
|
+
}}
|
|
353
|
+
>
|
|
354
|
+
{footer}
|
|
355
|
+
</box>
|
|
356
|
+
) : null}
|
|
357
|
+
{children}
|
|
358
|
+
</TooltipProvider>
|
|
359
|
+
</RootOverlayProvider>
|
|
360
|
+
</box>
|
|
361
|
+
);
|
|
362
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
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 "./EditorGrid";
|
|
14
|
+
import { changesTab, makeGroup } from "./editor-grid";
|
|
15
|
+
import { NERD } from "./primitives/icons";
|
|
16
|
+
import { allowEventLoopUpdates } from "../test-support";
|
|
17
|
+
|
|
18
|
+
const grid = makeGroup([changesTab()]);
|
|
19
|
+
|
|
20
|
+
async function zoomIconColumn(
|
|
21
|
+
width: number,
|
|
22
|
+
zoomed: boolean,
|
|
23
|
+
sidebarOpen: boolean,
|
|
24
|
+
): Promise<number> {
|
|
25
|
+
const setup = await testRender(
|
|
26
|
+
<AppShell
|
|
27
|
+
sidebarOpen={sidebarOpen}
|
|
28
|
+
onToggleSidebar={() => {}}
|
|
29
|
+
onOpenMenu={() => {}}
|
|
30
|
+
threadsPanel={<text>threads</text>}
|
|
31
|
+
threadTitle="thread"
|
|
32
|
+
threadPanel={<text>body</text>}
|
|
33
|
+
changesOpen
|
|
34
|
+
projectOpen
|
|
35
|
+
onToggleChanges={() => {}}
|
|
36
|
+
onToggleProject={() => {}}
|
|
37
|
+
onToggleRight={() => {}}
|
|
38
|
+
projectMode="changes"
|
|
39
|
+
zoomHideThread={zoomed}
|
|
40
|
+
projectPanel={<text>project</text>}
|
|
41
|
+
changesPanel={
|
|
42
|
+
<EditorGrid
|
|
43
|
+
tree={grid}
|
|
44
|
+
focusedGroupId={grid.id}
|
|
45
|
+
onFocusGroup={() => {}}
|
|
46
|
+
onActivateTab={() => {}}
|
|
47
|
+
onCloseTab={() => {}}
|
|
48
|
+
onSplit={() => {}}
|
|
49
|
+
onZoom={() => {}}
|
|
50
|
+
zoomed={zoomed}
|
|
51
|
+
renderTab={() => <text>diff</text>}
|
|
52
|
+
/>
|
|
53
|
+
}
|
|
54
|
+
/>,
|
|
55
|
+
{ width, height: 8 },
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
allowEventLoopUpdates();
|
|
59
|
+
await setup.waitForVisualIdle();
|
|
60
|
+
const column = setup.captureCharFrame().split("\n")[0]!.indexOf(NERD.zoom);
|
|
61
|
+
|
|
62
|
+
setup.renderer.destroy();
|
|
63
|
+
|
|
64
|
+
return column;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
describe("zoom icon in the shell", () => {
|
|
68
|
+
// even widths are where the fractional split used to overflow; odd widths never did
|
|
69
|
+
for (const width of [160, 161]) {
|
|
70
|
+
for (const sidebarOpen of [false, true]) {
|
|
71
|
+
test(`stays in its column when zoom toggles (width ${width}, sidebar ${sidebarOpen ? "open" : "closed"})`, async () => {
|
|
72
|
+
// Act
|
|
73
|
+
const unzoomed = await zoomIconColumn(width, false, sidebarOpen);
|
|
74
|
+
const zoomed = await zoomIconColumn(width, true, sidebarOpen);
|
|
75
|
+
|
|
76
|
+
// Assert
|
|
77
|
+
expect(unzoomed).toBeGreaterThan(0);
|
|
78
|
+
expect(zoomed).toBe(unzoomed);
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
test("the footer rides the Changes pane only while the Thread pane is zoomed away", async () => {
|
|
84
|
+
async function footerVisible(zoomed: boolean): Promise<boolean> {
|
|
85
|
+
const setup = await testRender(
|
|
86
|
+
<AppShell
|
|
87
|
+
sidebarOpen={false}
|
|
88
|
+
onToggleSidebar={() => {}}
|
|
89
|
+
onOpenMenu={() => {}}
|
|
90
|
+
threadsPanel={<text>threads</text>}
|
|
91
|
+
threadTitle="thread"
|
|
92
|
+
threadPanel={<text>body</text>}
|
|
93
|
+
changesOpen
|
|
94
|
+
projectOpen
|
|
95
|
+
onToggleChanges={() => {}}
|
|
96
|
+
onToggleProject={() => {}}
|
|
97
|
+
onToggleRight={() => {}}
|
|
98
|
+
projectMode="changes"
|
|
99
|
+
zoomHideThread={zoomed}
|
|
100
|
+
projectPanel={<text>project</text>}
|
|
101
|
+
changesFooter={<text>send message</text>}
|
|
102
|
+
changesPanel={<text>diff</text>}
|
|
103
|
+
/>,
|
|
104
|
+
{ width: 160, height: 8 },
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
allowEventLoopUpdates();
|
|
108
|
+
await setup.waitForVisualIdle();
|
|
109
|
+
const shown = setup.captureCharFrame().includes("send message");
|
|
110
|
+
|
|
111
|
+
setup.renderer.destroy();
|
|
112
|
+
|
|
113
|
+
return shown;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Act
|
|
117
|
+
const [zoomed, unzoomed] = await Promise.all([footerVisible(true), footerVisible(false)]);
|
|
118
|
+
|
|
119
|
+
// Assert - the Thread pane carries the footer normally, so it must not double up in Changes
|
|
120
|
+
expect(zoomed).toBe(true);
|
|
121
|
+
expect(unzoomed).toBe(false);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("the icon clears the Project border by its padding in both states", async () => {
|
|
125
|
+
// Act
|
|
126
|
+
const setupColumns = await Promise.all([
|
|
127
|
+
zoomIconColumn(160, false, false),
|
|
128
|
+
zoomIconColumn(160, true, false),
|
|
129
|
+
]);
|
|
130
|
+
|
|
131
|
+
// Assert - width 160, project pane 32: the border sits at 128 and the glyph one padding cell
|
|
132
|
+
// before it, the same inset the right-sidebar icon keeps from its own edge
|
|
133
|
+
for (const column of setupColumns) expect(128 - column).toBe(2);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { DARK } from "../theme";
|
|
3
|
+
import type { Story, StoryMeta } from "./story";
|
|
4
|
+
import { BareWorkbenchFileView, type WorkbenchCommenter } from "./BareWorkbenchFileView";
|
|
5
|
+
|
|
6
|
+
export const meta: StoryMeta = { title: "Surfaces/BareWorkbenchFileView" };
|
|
7
|
+
|
|
8
|
+
const SAMPLE = `export function add(a: number, b: number): number {\n return a + b;\n}\n`;
|
|
9
|
+
|
|
10
|
+
const controller: WorkbenchCommenter = {
|
|
11
|
+
repoReadFile: () => Promise.resolve(SAMPLE),
|
|
12
|
+
commentOnWorkbench: () => Promise.resolve(),
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const Loaded: Story = {
|
|
16
|
+
render: () => (
|
|
17
|
+
<BareWorkbenchFileView
|
|
18
|
+
path="src/add.ts"
|
|
19
|
+
controller={controller}
|
|
20
|
+
quickActions={[]}
|
|
21
|
+
onExit={() => {}}
|
|
22
|
+
theme={DARK}
|
|
23
|
+
/>
|
|
24
|
+
),
|
|
25
|
+
expectedColors: [DARK.textDim],
|
|
26
|
+
size: { width: 60, height: 12 },
|
|
27
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// A project file in the bare launch shell: annotatable like any file, but no thread exists yet. The
|
|
2
|
+
// first comment find-or-creates the per-repo workbench thread (controller.commentOnWorkbench), which
|
|
3
|
+
// opens it - so this ephemeral view lives only until that first note promotes it to a real thread.
|
|
4
|
+
|
|
5
|
+
import React from "react";
|
|
6
|
+
import { SCHEMA_VERSION, type Anchor, type AnnotationTarget, type Thread } from "@cueloop/schema";
|
|
7
|
+
import type { Theme } from "../theme";
|
|
8
|
+
import type { QuickAction } from "../config";
|
|
9
|
+
import { AnnotatableFileView } from "./AnnotatableFileView";
|
|
10
|
+
|
|
11
|
+
/** The controller slice this view needs: read a file, and route a comment to the workbench thread. */
|
|
12
|
+
export interface WorkbenchCommenter {
|
|
13
|
+
repoReadFile(path: string): Promise<string | null>;
|
|
14
|
+
commentOnWorkbench(anchor: Anchor, target: AnnotationTarget, body: string): Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** An empty, unsaved thread: it holds no notes, just enough shape for the annotation surface. */
|
|
18
|
+
export function draftThread(): Thread {
|
|
19
|
+
return {
|
|
20
|
+
schemaVersion: SCHEMA_VERSION,
|
|
21
|
+
id: "workbench-draft",
|
|
22
|
+
workspace: { repoRoot: "", branch: "main" },
|
|
23
|
+
artifact: { type: "diff", content: "", meta: {} },
|
|
24
|
+
revisions: [],
|
|
25
|
+
annotations: [],
|
|
26
|
+
message: null,
|
|
27
|
+
status: "pending",
|
|
28
|
+
createdAt: "1970-01-01T00:00:00.000Z",
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface BareWorkbenchFileViewProps {
|
|
33
|
+
path: string;
|
|
34
|
+
controller: WorkbenchCommenter;
|
|
35
|
+
quickActions: QuickAction[];
|
|
36
|
+
/** Reports whether the composer is open, so the shell suspends its inbox keys while typing. */
|
|
37
|
+
onComposingChange?: (composing: boolean) => void;
|
|
38
|
+
onExit: () => void;
|
|
39
|
+
theme?: Theme;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function BareWorkbenchFileView(props: BareWorkbenchFileViewProps): React.ReactNode {
|
|
43
|
+
return (
|
|
44
|
+
<AnnotatableFileView
|
|
45
|
+
path={props.path}
|
|
46
|
+
loadContents={(path) => props.controller.repoReadFile(path)}
|
|
47
|
+
session={draftThread()}
|
|
48
|
+
quickActions={props.quickActions}
|
|
49
|
+
observer={false}
|
|
50
|
+
onComposingChange={props.onComposingChange}
|
|
51
|
+
onAddComment={(anchor: Anchor, body: string) =>
|
|
52
|
+
void props.controller.commentOnWorkbench(
|
|
53
|
+
anchor,
|
|
54
|
+
{ kind: "file", path: props.path, rev: "worktree" },
|
|
55
|
+
body,
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
onReply={() => {}}
|
|
59
|
+
onUpdateAnnotation={() => {}}
|
|
60
|
+
onExit={props.onExit}
|
|
61
|
+
theme={props.theme}
|
|
62
|
+
/>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { DARK } from "../theme";
|
|
3
|
+
import type { Story, StoryMeta } from "./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 "../theme";
|
|
9
|
+
import { useComponentTheme } from "./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
|
+
}
|