@cueloop/client 0.1.0-alpha.9 → 0.1.0-alpha.91
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 +1406 -1060
- package/src/App.visual.test.tsx +57 -0
- package/src/App.walk.test.tsx +299 -0
- package/src/App.workbench.test.tsx +787 -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 +29 -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 +78 -0
- package/src/launch-layout.ts +62 -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,134 @@
|
|
|
1
|
+
// A project file shown as its own annotatable surface: the file's lines render through the diff
|
|
2
|
+
// sheet as context rows (one line-number gutter, no +/- sign), so selecting text leaves a comment
|
|
3
|
+
// anchored to the file exactly as a plan or diff comment is. Used where a thread exists to
|
|
4
|
+
// hold the notes; the bare-launch welcome shell uses the read-only FileContentsView instead.
|
|
5
|
+
|
|
6
|
+
import React, { useEffect, useRef, useState } from "react";
|
|
7
|
+
import {
|
|
8
|
+
makeAnchor,
|
|
9
|
+
annotationTarget,
|
|
10
|
+
type Anchor,
|
|
11
|
+
type Annotation,
|
|
12
|
+
type Thread,
|
|
13
|
+
} from "@cueloop/schema";
|
|
14
|
+
import type { Theme } from "../theme";
|
|
15
|
+
import type { QuickAction } from "../config";
|
|
16
|
+
import { useComponentTheme } from "./theme-context";
|
|
17
|
+
import { DiffContentView } from "./DiffContentView";
|
|
18
|
+
import { diffRowBlocks, fileContentsRows, marksByRows } from "../view-diff";
|
|
19
|
+
|
|
20
|
+
export interface AnnotatableFileViewProps {
|
|
21
|
+
path: string;
|
|
22
|
+
loadContents: (path: string) => Promise<string | null>;
|
|
23
|
+
session: Thread;
|
|
24
|
+
quickActions: QuickAction[];
|
|
25
|
+
observer: boolean;
|
|
26
|
+
resolved?: boolean;
|
|
27
|
+
suspended?: boolean;
|
|
28
|
+
focusedAnnotationId?: string;
|
|
29
|
+
onFocusAnnotation?: (annotationId: string | undefined) => void;
|
|
30
|
+
onComposingChange?: (composing: boolean) => void;
|
|
31
|
+
onCursorChange?: (rowIndex: number) => void;
|
|
32
|
+
onObserverBlocked?: (reason: "observer" | "resolved") => void;
|
|
33
|
+
/** Persist a comment on this file; the view built the anchor against the file's own lines. */
|
|
34
|
+
onAddComment: (anchor: Anchor, body: string) => void;
|
|
35
|
+
onReply: (rootAnnotationId: string, body: string) => void;
|
|
36
|
+
onUpdateAnnotation: (id: string, body: string) => void;
|
|
37
|
+
resolveAuthorLabel?: (annotation: Annotation) => string | undefined;
|
|
38
|
+
onExit: () => void;
|
|
39
|
+
theme?: Theme;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** A finished read for a specific path; `lines` is null when the file could not be read. */
|
|
43
|
+
interface FileLoad {
|
|
44
|
+
path: string;
|
|
45
|
+
lines: string[] | null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function AnnotatableFileView(props: AnnotatableFileViewProps): React.ReactNode {
|
|
49
|
+
const tokens = useComponentTheme(props.theme);
|
|
50
|
+
const [loaded, setLoaded] = useState<FileLoad | null>(null);
|
|
51
|
+
const loadRef = useRef(props.loadContents);
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
loadRef.current = props.loadContents;
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
let alive = true;
|
|
58
|
+
void loadRef.current(props.path).then(
|
|
59
|
+
(contents) => {
|
|
60
|
+
if (alive)
|
|
61
|
+
setLoaded({ path: props.path, lines: contents === null ? null : contents.split("\n") });
|
|
62
|
+
},
|
|
63
|
+
() => {
|
|
64
|
+
if (alive) setLoaded({ path: props.path, lines: null });
|
|
65
|
+
},
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
return () => {
|
|
69
|
+
alive = false;
|
|
70
|
+
};
|
|
71
|
+
}, [props.path]);
|
|
72
|
+
|
|
73
|
+
if (loaded === null || loaded.path !== props.path) {
|
|
74
|
+
return (
|
|
75
|
+
<box style={{ flexGrow: 1, paddingLeft: 1, paddingTop: 1 }}>
|
|
76
|
+
<text fg={tokens.textDim}>loading...</text>
|
|
77
|
+
</box>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
if (loaded.lines === null) {
|
|
81
|
+
return (
|
|
82
|
+
<box
|
|
83
|
+
style={{ flexGrow: 1, alignItems: "center", justifyContent: "center", paddingBottom: 2 }}
|
|
84
|
+
>
|
|
85
|
+
<text fg={tokens.textDim}>File deleted</text>
|
|
86
|
+
</box>
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const rows = fileContentsRows(props.path, loaded.lines.join("\n"));
|
|
91
|
+
// the file's worktree notes: these rows are the current file, so a head-side (deleted-line) note
|
|
92
|
+
// must stay orphaned here rather than rebind to similar surviving text
|
|
93
|
+
const fileNotes = props.session.annotations.filter((annotation) => {
|
|
94
|
+
const target = annotationTarget(annotation);
|
|
95
|
+
|
|
96
|
+
return target.kind === "file" && target.path === props.path && target.rev === "worktree";
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<DiffContentView
|
|
101
|
+
rows={rows}
|
|
102
|
+
session={props.session}
|
|
103
|
+
marks={marksByRows(fileNotes, rows, props.focusedAnnotationId)}
|
|
104
|
+
quickActions={props.quickActions}
|
|
105
|
+
observer={props.observer}
|
|
106
|
+
commentsEnabled
|
|
107
|
+
resolved={props.resolved}
|
|
108
|
+
suspended={props.suspended}
|
|
109
|
+
fileView
|
|
110
|
+
onComposingChange={props.onComposingChange}
|
|
111
|
+
onObserverBlocked={props.onObserverBlocked}
|
|
112
|
+
onCursorChange={props.onCursorChange}
|
|
113
|
+
focusedAnnotationId={props.focusedAnnotationId}
|
|
114
|
+
onFocusAnnotation={props.onFocusAnnotation}
|
|
115
|
+
onAnnotate={(span, body) =>
|
|
116
|
+
props.onAddComment(
|
|
117
|
+
makeAnchor(
|
|
118
|
+
diffRowBlocks(rows),
|
|
119
|
+
span.start.blockIndex,
|
|
120
|
+
span.start.char,
|
|
121
|
+
span.end.char,
|
|
122
|
+
span.end.blockIndex,
|
|
123
|
+
),
|
|
124
|
+
body,
|
|
125
|
+
)
|
|
126
|
+
}
|
|
127
|
+
onReply={props.onReply}
|
|
128
|
+
onUpdateAnnotation={props.onUpdateAnnotation}
|
|
129
|
+
resolveAuthorLabel={props.resolveAuthorLabel}
|
|
130
|
+
onExit={props.onExit}
|
|
131
|
+
theme={props.theme}
|
|
132
|
+
/>
|
|
133
|
+
);
|
|
134
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { Annotation } from "@cueloop/schema";
|
|
3
|
+
import { DARK } from "../theme";
|
|
4
|
+
import { annotationPaletteFor } from "../annotation-palette";
|
|
5
|
+
import { slashItemsFrom } from "../slash-palette";
|
|
6
|
+
import { DEFAULT_QUICK_ACTIONS } from "../config";
|
|
7
|
+
import type { Story, StoryMeta } from "./story";
|
|
8
|
+
import { CommentRow, ComposerPalette, DiscussionCard } from "./AnnotationCards";
|
|
9
|
+
|
|
10
|
+
export const meta: StoryMeta = { title: "Chrome/AnnotationCards" };
|
|
11
|
+
|
|
12
|
+
const palette = annotationPaletteFor(DARK);
|
|
13
|
+
|
|
14
|
+
function annotation(id: string, body: string, author?: string): Annotation {
|
|
15
|
+
const comment: Annotation = {
|
|
16
|
+
id,
|
|
17
|
+
kind: "comment",
|
|
18
|
+
anchor: { quote: "the daemon", prefix: "", suffix: "" },
|
|
19
|
+
body,
|
|
20
|
+
createdAt: "2026-09-07T10:00:00.000Z",
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
if (author !== undefined) comment.author = author;
|
|
24
|
+
|
|
25
|
+
return comment;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// A two-voice discussion: the owner's comment then a collaborator's reply, each
|
|
29
|
+
// segment wearing its own accent edge.
|
|
30
|
+
export const TwoVoices: Story = {
|
|
31
|
+
render: () => (
|
|
32
|
+
<DiscussionCard
|
|
33
|
+
tokens={DARK}
|
|
34
|
+
segments={[
|
|
35
|
+
{
|
|
36
|
+
color: palette.cardEdge,
|
|
37
|
+
node: <CommentRow annotation={annotation("a", "Which daemon?")} tokens={DARK} />,
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
color: DARK.text,
|
|
41
|
+
node: (
|
|
42
|
+
<CommentRow
|
|
43
|
+
annotation={annotation("b", "The one the CLI spawns.", "fp:collab")}
|
|
44
|
+
tokens={DARK}
|
|
45
|
+
/>
|
|
46
|
+
),
|
|
47
|
+
},
|
|
48
|
+
]}
|
|
49
|
+
/>
|
|
50
|
+
),
|
|
51
|
+
expectedColors: [palette.cardEdge, DARK.text],
|
|
52
|
+
size: { width: 60, height: 8 },
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export const FocusedCard: Story = {
|
|
56
|
+
render: () => (
|
|
57
|
+
<DiscussionCard
|
|
58
|
+
focused
|
|
59
|
+
tokens={DARK}
|
|
60
|
+
segments={[
|
|
61
|
+
{
|
|
62
|
+
color: palette.cardEdge,
|
|
63
|
+
node: (
|
|
64
|
+
<CommentRow annotation={annotation("a", "Needs an eviction story.")} tokens={DARK} />
|
|
65
|
+
),
|
|
66
|
+
},
|
|
67
|
+
]}
|
|
68
|
+
/>
|
|
69
|
+
),
|
|
70
|
+
expectedColors: [DARK.text],
|
|
71
|
+
size: { width: 60, height: 6 },
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// The palette a leading "/" opens under the composer, with the second item selected.
|
|
75
|
+
export const SlashPaletteOpen: Story = {
|
|
76
|
+
render: () => (
|
|
77
|
+
<ComposerPalette
|
|
78
|
+
slashActive
|
|
79
|
+
slashItems={slashItemsFrom(DEFAULT_QUICK_ACTIONS)}
|
|
80
|
+
slashIndex={1}
|
|
81
|
+
tokens={DARK}
|
|
82
|
+
/>
|
|
83
|
+
),
|
|
84
|
+
expectedColors: [DARK.accent, DARK.textDim],
|
|
85
|
+
size: { width: 90, height: 8 },
|
|
86
|
+
};
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The inline comment pieces every annotated surface renders: the composer (a
|
|
3
|
+
* textarea in a card row), the "/" actions-and-skills palette below it, the
|
|
4
|
+
* edge-segmented discussion card, and one comment row. The plan thread view
|
|
5
|
+
* and the diff sheet both draw their comments with these, so a discussion looks
|
|
6
|
+
* and behaves the same on prose and on code.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import React, { useContext, useLayoutEffect, useRef, useState } from "react";
|
|
10
|
+
import { usePaste } from "@opentui/react";
|
|
11
|
+
import type {
|
|
12
|
+
BoxRenderable,
|
|
13
|
+
KeyBinding,
|
|
14
|
+
MouseEvent as TerminalMouseEvent,
|
|
15
|
+
TextareaRenderable,
|
|
16
|
+
} from "@opentui/core";
|
|
17
|
+
import type { Annotation } from "@cueloop/schema";
|
|
18
|
+
import type { Theme } from "../theme";
|
|
19
|
+
import { useTooltip } from "./Tooltip";
|
|
20
|
+
import { lighten } from "../annotation-palette";
|
|
21
|
+
import { useFrameMeasure } from "../use-frame-measure";
|
|
22
|
+
import { imagePlaceholder, looksLikeBinaryPaste } from "../pasted-image";
|
|
23
|
+
import { skillReferenceRanges, type SlashItem } from "../slash-palette";
|
|
24
|
+
import { PaletteNamesContext } from "../skills";
|
|
25
|
+
import { referenceStyleFor } from "./syntax-highlight";
|
|
26
|
+
|
|
27
|
+
/* -------------------------------------------------------------- composer */
|
|
28
|
+
|
|
29
|
+
// cmd+enter sends (super under the kitty protocol, meta where cmd arrives
|
|
30
|
+
// ESC-prefixed, ctrl as the fallback where the terminal itself consumes
|
|
31
|
+
// cmd+enter); plain enter breaks the line
|
|
32
|
+
const COMPOSE_KEY_BINDINGS: KeyBinding[] = [
|
|
33
|
+
{ name: "return", super: true, action: "submit" },
|
|
34
|
+
{ name: "return", meta: true, action: "submit" },
|
|
35
|
+
{ name: "return", ctrl: true, action: "submit" },
|
|
36
|
+
{ name: "return", action: "newline" },
|
|
37
|
+
{ name: "return", shift: true, action: "newline" },
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
export function composeRowCount(text: string, contentWidth: number): number {
|
|
41
|
+
const usableWidth = contentWidth > 0 ? contentWidth : Number.MAX_SAFE_INTEGER;
|
|
42
|
+
let visualRowCount = 0;
|
|
43
|
+
|
|
44
|
+
for (const line of text.split("\n")) {
|
|
45
|
+
visualRowCount += Math.max(1, Math.ceil(line.length / usableWidth));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// no cap: the card is the discussion, and folding is its containment
|
|
49
|
+
return Math.max(1, visualRowCount);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function Composer({
|
|
53
|
+
seed,
|
|
54
|
+
glyph,
|
|
55
|
+
tokens,
|
|
56
|
+
onSave,
|
|
57
|
+
onReady,
|
|
58
|
+
onInput,
|
|
59
|
+
placeholder,
|
|
60
|
+
}: {
|
|
61
|
+
seed: string;
|
|
62
|
+
glyph: string;
|
|
63
|
+
tokens: Theme;
|
|
64
|
+
onSave: (body: string) => void;
|
|
65
|
+
onReady: () => void;
|
|
66
|
+
onInput: (text: string, caret: number) => void;
|
|
67
|
+
placeholder?: string;
|
|
68
|
+
}): React.ReactNode {
|
|
69
|
+
const editorRef = useRef<TextareaRenderable | null>(null);
|
|
70
|
+
const pastedImageCount = useRef(0);
|
|
71
|
+
const [rows, setRows] = useState(1);
|
|
72
|
+
// action and skill names whose "/name" references paint in the accent color
|
|
73
|
+
const referenceNames = useContext(PaletteNamesContext);
|
|
74
|
+
|
|
75
|
+
usePaste((event) => {
|
|
76
|
+
const editor = editorRef.current;
|
|
77
|
+
|
|
78
|
+
if (!editor || !editor.focused || !looksLikeBinaryPaste(event.bytes)) return;
|
|
79
|
+
event.preventDefault();
|
|
80
|
+
pastedImageCount.current += 1;
|
|
81
|
+
editor.editBuffer.insertText(imagePlaceholder(pastedImageCount.current));
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const paintReferences = (editor: TextareaRenderable): void => {
|
|
85
|
+
const { styleId } = referenceStyleFor(tokens);
|
|
86
|
+
|
|
87
|
+
editor.editBuffer.clearAllHighlights();
|
|
88
|
+
for (const range of skillReferenceRanges(editor.plainText, referenceNames)) {
|
|
89
|
+
editor.editBuffer.addHighlightByCharRange({ ...range, styleId });
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// synchronously at commit, before the next tty key is read: signalling readiness in a
|
|
94
|
+
// post-paint effect lets a fast burst race the mount, splitting input between the seed
|
|
95
|
+
// buffer and this textarea out of order (#365)
|
|
96
|
+
useLayoutEffect(() => {
|
|
97
|
+
const editor = editorRef.current;
|
|
98
|
+
|
|
99
|
+
if (!editor) return;
|
|
100
|
+
editor.cursorOffset = seed.length;
|
|
101
|
+
editor.editBuffer.setSyntaxStyle(referenceStyleFor(tokens).style);
|
|
102
|
+
paintReferences(editor);
|
|
103
|
+
setRows(composeRowCount(editor.plainText, editor.width));
|
|
104
|
+
onReady();
|
|
105
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
106
|
+
}, []);
|
|
107
|
+
|
|
108
|
+
return (
|
|
109
|
+
<box style={{ flexDirection: "row" }}>
|
|
110
|
+
<text selectable={false} fg={tokens.text} style={{ flexShrink: 0 }}>{`${glyph} `}</text>
|
|
111
|
+
<textarea
|
|
112
|
+
ref={editorRef}
|
|
113
|
+
focused
|
|
114
|
+
initialValue={seed}
|
|
115
|
+
placeholder={placeholder}
|
|
116
|
+
placeholderColor={tokens.textDim}
|
|
117
|
+
cursorStyle={{ style: "block", blinking: true }}
|
|
118
|
+
keyBindings={COMPOSE_KEY_BINDINGS}
|
|
119
|
+
onSubmit={() => onSave(editorRef.current?.plainText ?? "")}
|
|
120
|
+
onContentChange={() => {
|
|
121
|
+
const editor = editorRef.current;
|
|
122
|
+
|
|
123
|
+
if (!editor) return;
|
|
124
|
+
paintReferences(editor);
|
|
125
|
+
setRows(composeRowCount(editor.plainText, editor.width));
|
|
126
|
+
onInput(editor.plainText, editor.cursorOffset);
|
|
127
|
+
}}
|
|
128
|
+
style={{
|
|
129
|
+
height: rows,
|
|
130
|
+
flexGrow: 1,
|
|
131
|
+
backgroundColor: "transparent",
|
|
132
|
+
focusedBackgroundColor: "transparent",
|
|
133
|
+
textColor: tokens.text,
|
|
134
|
+
focusedTextColor: tokens.text,
|
|
135
|
+
}}
|
|
136
|
+
/>
|
|
137
|
+
</box>
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/* ---------------------------------------------------------- slash palette */
|
|
142
|
+
|
|
143
|
+
function SlashList({
|
|
144
|
+
items,
|
|
145
|
+
selected,
|
|
146
|
+
tokens,
|
|
147
|
+
}: {
|
|
148
|
+
items: SlashItem[];
|
|
149
|
+
selected: number;
|
|
150
|
+
tokens: Theme;
|
|
151
|
+
}): React.ReactNode {
|
|
152
|
+
const WINDOW = 5;
|
|
153
|
+
const start = Math.max(0, Math.min(selected - 2, items.length - WINDOW));
|
|
154
|
+
const visible = items.slice(start, start + WINDOW);
|
|
155
|
+
|
|
156
|
+
if (items.length === 0) return <text fg={tokens.textDim}>no matching actions</text>;
|
|
157
|
+
|
|
158
|
+
const nameColumn = Math.min(36, Math.max(16, ...visible.map((item) => item.name.length + 5)));
|
|
159
|
+
|
|
160
|
+
return (
|
|
161
|
+
<box style={{ flexDirection: "column" }}>
|
|
162
|
+
{visible.map((item, offset) => {
|
|
163
|
+
const index = start + offset;
|
|
164
|
+
const isSelected = index === selected;
|
|
165
|
+
const name = `${isSelected ? "→ " : " "}/${item.name}`.padEnd(nameColumn);
|
|
166
|
+
|
|
167
|
+
return (
|
|
168
|
+
<text key={item.name} style={{ flexShrink: 0, wrapMode: "none" }}>
|
|
169
|
+
<span fg={isSelected ? tokens.accent : tokens.textMuted}>{name}</span>
|
|
170
|
+
<span fg={tokens.textDim}>{item.description}</span>
|
|
171
|
+
</text>
|
|
172
|
+
);
|
|
173
|
+
})}
|
|
174
|
+
{items.length > WINDOW ? (
|
|
175
|
+
<text fg={tokens.textDim}>{`(${selected + 1}/${items.length})`}</text>
|
|
176
|
+
) : null}
|
|
177
|
+
</box>
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** Below the composer: the actions/skills list while the caret is on a "/word", else nothing. */
|
|
182
|
+
export function ComposerPalette({
|
|
183
|
+
slashActive,
|
|
184
|
+
slashItems,
|
|
185
|
+
slashIndex,
|
|
186
|
+
tokens,
|
|
187
|
+
}: {
|
|
188
|
+
slashActive: boolean;
|
|
189
|
+
slashItems: SlashItem[];
|
|
190
|
+
slashIndex: number;
|
|
191
|
+
tokens: Theme;
|
|
192
|
+
}): React.ReactNode {
|
|
193
|
+
if (!slashActive) return null;
|
|
194
|
+
|
|
195
|
+
return (
|
|
196
|
+
<box style={{ flexDirection: "column", marginLeft: 3 }}>
|
|
197
|
+
<SlashList
|
|
198
|
+
items={slashItems}
|
|
199
|
+
selected={Math.min(slashIndex, Math.max(0, slashItems.length - 1))}
|
|
200
|
+
tokens={tokens}
|
|
201
|
+
/>
|
|
202
|
+
</box>
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/* ------------------------------------------------------------ discussion card */
|
|
207
|
+
|
|
208
|
+
/** One card row group with its own accent-edge color, one per voice. */
|
|
209
|
+
export interface EdgeSegment {
|
|
210
|
+
color: string;
|
|
211
|
+
node: React.ReactNode;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* The comment-card frame. The accent edge is drawn by hand so that (a) its
|
|
216
|
+
* corner cells are half-height glyphs ("╷"/"╵") terminating flush with the
|
|
217
|
+
* hairline rows, and (b) each voice's rows carry their own edge color with
|
|
218
|
+
* zero gap. Focus lightens every segment; the hairlines never change.
|
|
219
|
+
*/
|
|
220
|
+
export function DiscussionCard({
|
|
221
|
+
segments,
|
|
222
|
+
tokens,
|
|
223
|
+
focused = false,
|
|
224
|
+
onFocus,
|
|
225
|
+
}: {
|
|
226
|
+
segments: EdgeSegment[];
|
|
227
|
+
tokens: Theme;
|
|
228
|
+
focused?: boolean;
|
|
229
|
+
onFocus?: () => void;
|
|
230
|
+
}): React.ReactNode {
|
|
231
|
+
const segmentRefs = useRef<Array<BoxRenderable | null>>([]);
|
|
232
|
+
const noHeights: number[] = [];
|
|
233
|
+
const heights = useFrameMeasure(
|
|
234
|
+
() => segmentRefs.current.map((renderable) => renderable?.height ?? 0),
|
|
235
|
+
(left, right) => left.join() === right.join(),
|
|
236
|
+
noHeights,
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
const edgeColor = (color: string): string => (focused ? lighten(color) : color);
|
|
240
|
+
const glyphs = focused
|
|
241
|
+
? { top: "╻", middle: "┃", bottom: "╹" }
|
|
242
|
+
: { top: "╷", middle: "│", bottom: "╵" };
|
|
243
|
+
const edgeRows: Array<{ glyph: string; color: string }> = [
|
|
244
|
+
{
|
|
245
|
+
glyph: glyphs.top,
|
|
246
|
+
color: edgeColor(segments[0]?.color ?? tokens.border),
|
|
247
|
+
},
|
|
248
|
+
];
|
|
249
|
+
|
|
250
|
+
for (let index = 0; index < segments.length; index++) {
|
|
251
|
+
const segmentRows = Math.max(1, heights[index] ?? 1);
|
|
252
|
+
|
|
253
|
+
for (let row = 0; row < segmentRows; row++) {
|
|
254
|
+
edgeRows.push({
|
|
255
|
+
glyph: glyphs.middle,
|
|
256
|
+
color: edgeColor(segments[index]!.color),
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
edgeRows.push({
|
|
261
|
+
glyph: glyphs.bottom,
|
|
262
|
+
color: edgeColor(segments[segments.length - 1]?.color ?? tokens.border),
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
return (
|
|
266
|
+
<box style={{ flexDirection: "row", marginTop: 0, marginLeft: 2 }}>
|
|
267
|
+
<text selectable={false} style={{ flexShrink: 0, width: 1 }}>
|
|
268
|
+
{edgeRows.map((row, index) => (
|
|
269
|
+
<span key={index} fg={row.color}>
|
|
270
|
+
{index < edgeRows.length - 1 ? `${row.glyph}\n` : row.glyph}
|
|
271
|
+
</span>
|
|
272
|
+
))}
|
|
273
|
+
</text>
|
|
274
|
+
<box
|
|
275
|
+
onMouseDown={onFocus}
|
|
276
|
+
style={{
|
|
277
|
+
flexGrow: 1,
|
|
278
|
+
flexDirection: "column",
|
|
279
|
+
border: ["top", "right", "bottom"],
|
|
280
|
+
borderStyle: "single",
|
|
281
|
+
borderColor: tokens.border,
|
|
282
|
+
backgroundColor: "transparent",
|
|
283
|
+
paddingLeft: 1,
|
|
284
|
+
paddingRight: 1,
|
|
285
|
+
}}
|
|
286
|
+
>
|
|
287
|
+
{segments.map((segment, index) => (
|
|
288
|
+
<box
|
|
289
|
+
key={index}
|
|
290
|
+
ref={(renderable: BoxRenderable | null) => {
|
|
291
|
+
segmentRefs.current[index] = renderable;
|
|
292
|
+
}}
|
|
293
|
+
style={{ flexDirection: "column" }}
|
|
294
|
+
>
|
|
295
|
+
{segment.node}
|
|
296
|
+
</box>
|
|
297
|
+
))}
|
|
298
|
+
</box>
|
|
299
|
+
</box>
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
export function CommentRow({
|
|
304
|
+
annotation,
|
|
305
|
+
tokens,
|
|
306
|
+
authorLabel,
|
|
307
|
+
}: {
|
|
308
|
+
annotation: Annotation;
|
|
309
|
+
tokens: Theme;
|
|
310
|
+
/** The author's resolved display name, shown as a tooltip when the dot is hovered. */
|
|
311
|
+
authorLabel?: string;
|
|
312
|
+
}): React.ReactNode {
|
|
313
|
+
// own comments (no author) wear the filled dot, collaborators the outline
|
|
314
|
+
const own = annotation.author === undefined;
|
|
315
|
+
const glyph = own ? "●" : "○";
|
|
316
|
+
const glyphColor = annotation.orphan ? tokens.warning : own ? tokens.text : tokens.textMuted;
|
|
317
|
+
const { showTooltip, hideTooltip } = useTooltip();
|
|
318
|
+
const dotHover = authorLabel
|
|
319
|
+
? {
|
|
320
|
+
onMouseOver: (event: TerminalMouseEvent) => showTooltip(authorLabel, event.x, event.y),
|
|
321
|
+
onMouseOut: hideTooltip,
|
|
322
|
+
}
|
|
323
|
+
: {};
|
|
324
|
+
|
|
325
|
+
return (
|
|
326
|
+
<box style={{ flexDirection: "column" }}>
|
|
327
|
+
{annotation.body.split("\n").map((line, lineIndex) => (
|
|
328
|
+
<box key={lineIndex} style={{ flexDirection: "row" }}>
|
|
329
|
+
<box style={{ flexShrink: 0 }} {...(lineIndex === 0 ? dotHover : {})}>
|
|
330
|
+
<text selectable={false} fg={glyphColor}>
|
|
331
|
+
{lineIndex === 0 ? `${glyph} ` : " "}
|
|
332
|
+
</text>
|
|
333
|
+
</box>
|
|
334
|
+
<text fg={tokens.text} style={{ wrapMode: "word", flexGrow: 1, flexShrink: 1 }}>
|
|
335
|
+
{line}
|
|
336
|
+
</text>
|
|
337
|
+
</box>
|
|
338
|
+
))}
|
|
339
|
+
</box>
|
|
340
|
+
);
|
|
341
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { Story, StoryMeta } from "./story";
|
|
3
|
+
import { AnsiScreen } from "./AnsiScreen";
|
|
4
|
+
|
|
5
|
+
export const meta: StoryMeta = { title: "Primitives/AnsiScreen" };
|
|
6
|
+
|
|
7
|
+
const POSITIONED = "\x1b[2;4HHello from\x1b[4;6Hraw, positioned ANSI";
|
|
8
|
+
|
|
9
|
+
export const Positioned: Story = {
|
|
10
|
+
render: () => <AnsiScreen ansi={POSITIONED} cols={40} rows={6} />,
|
|
11
|
+
size: { width: 44, height: 8 },
|
|
12
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** Paints a raw ANSI screen (cursor-positioned text) as a terminal would, for previewing SSH surfaces. */
|
|
2
|
+
|
|
3
|
+
import React from "react";
|
|
4
|
+
import type { Theme } from "../theme";
|
|
5
|
+
import { ansiScreenToLines } from "../ansi-screen";
|
|
6
|
+
import { useComponentTheme } from "./theme-context";
|
|
7
|
+
|
|
8
|
+
export interface AnsiScreenProps {
|
|
9
|
+
ansi: string;
|
|
10
|
+
cols: number;
|
|
11
|
+
rows: number;
|
|
12
|
+
theme?: Theme;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function AnsiScreen({ ansi, cols, rows, theme }: AnsiScreenProps): React.ReactNode {
|
|
16
|
+
const tokens = useComponentTheme(theme);
|
|
17
|
+
const lines = ansiScreenToLines(ansi, cols, rows);
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<box style={{ flexDirection: "column" }}>
|
|
21
|
+
{lines.map((line, index) => (
|
|
22
|
+
<text key={index} fg={tokens.text} selectable={false} style={{ wrapMode: "none" }}>
|
|
23
|
+
{line === "" ? " " : line}
|
|
24
|
+
</text>
|
|
25
|
+
))}
|
|
26
|
+
</box>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { DARK } from "../theme";
|
|
3
|
+
import type { Story, StoryMeta } from "./story";
|
|
4
|
+
import { AppHeader } from "./AppHeader";
|
|
5
|
+
import { Button } from "./primitives/Button";
|
|
6
|
+
import { Toolbar } from "./primitives/Toolbar";
|
|
7
|
+
|
|
8
|
+
export const meta: StoryMeta = { title: "Chrome/AppHeader" };
|
|
9
|
+
|
|
10
|
+
export const NoThread: Story = {
|
|
11
|
+
render: () => (
|
|
12
|
+
<AppHeader
|
|
13
|
+
onOpenMenu={() => {}}
|
|
14
|
+
sidebarOpen
|
|
15
|
+
onToggleSidebar={() => {}}
|
|
16
|
+
title=""
|
|
17
|
+
changesOpen={false}
|
|
18
|
+
onToggleChanges={() => {}}
|
|
19
|
+
theme={DARK}
|
|
20
|
+
/>
|
|
21
|
+
),
|
|
22
|
+
expectedColors: [DARK.accent],
|
|
23
|
+
size: { width: 80, height: 3 },
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const ThreadWithActions: Story = {
|
|
27
|
+
render: () => (
|
|
28
|
+
<AppHeader
|
|
29
|
+
onOpenMenu={() => {}}
|
|
30
|
+
sidebarOpen
|
|
31
|
+
onToggleSidebar={() => {}}
|
|
32
|
+
title="Review the accent change"
|
|
33
|
+
editShare={
|
|
34
|
+
<Toolbar>
|
|
35
|
+
<Button onPress={() => {}} theme={DARK}>
|
|
36
|
+
{" Edit "}
|
|
37
|
+
</Button>
|
|
38
|
+
<Button onPress={() => {}} theme={DARK}>
|
|
39
|
+
{" Share "}
|
|
40
|
+
</Button>
|
|
41
|
+
</Toolbar>
|
|
42
|
+
}
|
|
43
|
+
changesOpen
|
|
44
|
+
onToggleChanges={() => {}}
|
|
45
|
+
theme={DARK}
|
|
46
|
+
/>
|
|
47
|
+
),
|
|
48
|
+
expectedColors: [DARK.accent],
|
|
49
|
+
size: { width: 80, height: 3 },
|
|
50
|
+
};
|