@grackle-ai/web-components 0.107.2
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/.rush/temp/3ae72563f781afd72723475938136f113846603e.untar.log +10 -0
- package/.rush/temp/bc1d5bf9201ce71abeaeaddd096deb9b0805d703.untar.log +10 -0
- package/.rush/temp/operation/_phase_build/all.log +18 -0
- package/.rush/temp/operation/_phase_build/log-chunks.jsonl +18 -0
- package/.rush/temp/operation/_phase_build/state.json +3 -0
- package/.rush/temp/operation/_phase_test/all.log +121 -0
- package/.rush/temp/operation/_phase_test/log-chunks.jsonl +121 -0
- package/.rush/temp/operation/_phase_test/state.json +3 -0
- package/.rush/temp/shrinkwrap-deps.json +938 -0
- package/.storybook/main.ts +22 -0
- package/.storybook/preview.tsx +30 -0
- package/config/rig.json +4 -0
- package/config/rush-project.json +12 -0
- package/dist/index.css +1 -0
- package/dist/index.js +39221 -0
- package/eslint.config.cjs +5 -0
- package/package.json +83 -0
- package/rush-logs/web-components._phase_build.cache.log +4 -0
- package/rush-logs/web-components._phase_test.cache.log +4 -0
- package/src/components/chat/ChatInput.module.scss +81 -0
- package/src/components/chat/ChatInput.stories.tsx +91 -0
- package/src/components/chat/ChatInput.tsx +168 -0
- package/src/components/chat/index.ts +6 -0
- package/src/components/dag/DagView.module.scss +149 -0
- package/src/components/dag/DagView.stories.tsx +125 -0
- package/src/components/dag/DagView.tsx +109 -0
- package/src/components/dag/TaskNode.stories.tsx +133 -0
- package/src/components/dag/TaskNode.tsx +40 -0
- package/src/components/dag/useDagLayout.ts +139 -0
- package/src/components/display/Breadcrumbs.module.scss +71 -0
- package/src/components/display/Breadcrumbs.stories.tsx +80 -0
- package/src/components/display/Breadcrumbs.tsx +46 -0
- package/src/components/display/Button.module.scss +110 -0
- package/src/components/display/Button.stories.tsx +88 -0
- package/src/components/display/Button.tsx +40 -0
- package/src/components/display/ConfirmDialog.module.scss +67 -0
- package/src/components/display/ConfirmDialog.stories.tsx +81 -0
- package/src/components/display/ConfirmDialog.tsx +88 -0
- package/src/components/display/CopyButton.module.scss +41 -0
- package/src/components/display/CopyButton.stories.tsx +78 -0
- package/src/components/display/CopyButton.tsx +64 -0
- package/src/components/display/DemoBanner.module.scss +37 -0
- package/src/components/display/DemoBanner.stories.tsx +40 -0
- package/src/components/display/DemoBanner.tsx +23 -0
- package/src/components/display/EventHoverRow.module.scss +102 -0
- package/src/components/display/EventHoverRow.stories.tsx +99 -0
- package/src/components/display/EventHoverRow.tsx +154 -0
- package/src/components/display/EventRenderer.module.scss +272 -0
- package/src/components/display/EventRenderer.stories.tsx +186 -0
- package/src/components/display/EventRenderer.tsx +271 -0
- package/src/components/display/EventStream.module.scss +93 -0
- package/src/components/display/EventStream.stories.tsx +249 -0
- package/src/components/display/EventStream.tsx +369 -0
- package/src/components/display/FloatingActionBar.module.scss +107 -0
- package/src/components/display/FloatingActionBar.stories.tsx +122 -0
- package/src/components/display/FloatingActionBar.tsx +119 -0
- package/src/components/display/SessionAttemptSelector.module.scss +50 -0
- package/src/components/display/SessionAttemptSelector.stories.tsx +78 -0
- package/src/components/display/SessionAttemptSelector.tsx +49 -0
- package/src/components/display/SessionPicker.module.scss +200 -0
- package/src/components/display/SessionPicker.stories.tsx +169 -0
- package/src/components/display/SessionPicker.tsx +214 -0
- package/src/components/display/Skeleton.module.scss +58 -0
- package/src/components/display/Skeleton.stories.tsx +94 -0
- package/src/components/display/Skeleton.tsx +127 -0
- package/src/components/display/Spinner.module.scss +41 -0
- package/src/components/display/Spinner.stories.tsx +66 -0
- package/src/components/display/Spinner.tsx +32 -0
- package/src/components/display/SplashScreen.module.scss +20 -0
- package/src/components/display/SplashScreen.stories.tsx +26 -0
- package/src/components/display/SplashScreen.tsx +16 -0
- package/src/components/display/SplitButton.module.scss +166 -0
- package/src/components/display/SplitButton.stories.tsx +95 -0
- package/src/components/display/SplitButton.tsx +128 -0
- package/src/components/display/Tooltip.module.scss +84 -0
- package/src/components/display/Tooltip.stories.tsx +240 -0
- package/src/components/display/Tooltip.tsx +184 -0
- package/src/components/display/extractText.test.tsx +48 -0
- package/src/components/display/index.ts +20 -0
- package/src/components/editable/EditableCheckbox.stories.tsx +54 -0
- package/src/components/editable/EditableCheckbox.tsx +39 -0
- package/src/components/editable/EditableField.module.scss +135 -0
- package/src/components/editable/EditableSelect.tsx +164 -0
- package/src/components/editable/EditableTextArea.stories.tsx +50 -0
- package/src/components/editable/EditableTextArea.tsx +148 -0
- package/src/components/editable/EditableTextField.stories.tsx +62 -0
- package/src/components/editable/EditableTextField.tsx +153 -0
- package/src/components/editable/EnvironmentSelect.module.scss +17 -0
- package/src/components/editable/EnvironmentSelect.stories.tsx +61 -0
- package/src/components/editable/EnvironmentSelect.tsx +87 -0
- package/src/components/editable/index.ts +13 -0
- package/src/components/editable/useEditableField.test.tsx +233 -0
- package/src/components/editable/useEditableField.ts +173 -0
- package/src/components/index.ts +20 -0
- package/src/components/knowledge/KnowledgeDetailPanel.module.scss +162 -0
- package/src/components/knowledge/KnowledgeDetailPanel.stories.tsx +208 -0
- package/src/components/knowledge/KnowledgeDetailPanel.tsx +122 -0
- package/src/components/knowledge/KnowledgeGraph.module.scss +110 -0
- package/src/components/knowledge/KnowledgeGraph.stories.tsx +180 -0
- package/src/components/knowledge/KnowledgeGraph.tsx +455 -0
- package/src/components/knowledge/KnowledgeNav.module.scss +130 -0
- package/src/components/knowledge/KnowledgeNav.stories.tsx +108 -0
- package/src/components/knowledge/KnowledgeNav.tsx +138 -0
- package/src/components/knowledge/index.ts +3 -0
- package/src/components/layout/AppNav.module.scss +82 -0
- package/src/components/layout/AppNav.stories.tsx +115 -0
- package/src/components/layout/AppNav.tsx +133 -0
- package/src/components/layout/BottomStatusBar.module.scss +58 -0
- package/src/components/layout/BottomStatusBar.stories.tsx +35 -0
- package/src/components/layout/BottomStatusBar.tsx +206 -0
- package/src/components/layout/Sidebar.module.scss +60 -0
- package/src/components/layout/Sidebar.stories.tsx +46 -0
- package/src/components/layout/Sidebar.tsx +84 -0
- package/src/components/layout/StatusBar.module.scss +108 -0
- package/src/components/layout/StatusBar.stories.tsx +119 -0
- package/src/components/layout/StatusBar.tsx +70 -0
- package/src/components/layout/index.ts +9 -0
- package/src/components/lists/EnvironmentNav.module.scss +118 -0
- package/src/components/lists/EnvironmentNav.stories.tsx +121 -0
- package/src/components/lists/EnvironmentNav.tsx +133 -0
- package/src/components/lists/FindingsNav.module.scss +126 -0
- package/src/components/lists/FindingsNav.tsx +146 -0
- package/src/components/lists/TaskList.module.scss +206 -0
- package/src/components/lists/TaskList.stories.tsx +401 -0
- package/src/components/lists/TaskList.tsx +509 -0
- package/src/components/lists/index.ts +6 -0
- package/src/components/lists/listHelpers.tsx +130 -0
- package/src/components/notifications/Callout.module.scss +83 -0
- package/src/components/notifications/Callout.stories.tsx +81 -0
- package/src/components/notifications/Callout.tsx +64 -0
- package/src/components/notifications/Toast.module.scss +86 -0
- package/src/components/notifications/Toast.stories.tsx +71 -0
- package/src/components/notifications/Toast.tsx +51 -0
- package/src/components/notifications/ToastContainer.module.scss +23 -0
- package/src/components/notifications/ToastContainer.stories.tsx +66 -0
- package/src/components/notifications/ToastContainer.tsx +29 -0
- package/src/components/notifications/UpdateBanner.stories.tsx +77 -0
- package/src/components/notifications/UpdateBanner.test.tsx +64 -0
- package/src/components/notifications/UpdateBanner.tsx +44 -0
- package/src/components/notifications/index.ts +8 -0
- package/src/components/panels/AboutPanel.stories.tsx +70 -0
- package/src/components/panels/AboutPanel.tsx +66 -0
- package/src/components/panels/AppearancePanel.stories.tsx +45 -0
- package/src/components/panels/AppearancePanel.tsx +97 -0
- package/src/components/panels/CredentialProvidersPanel.stories.tsx +62 -0
- package/src/components/panels/CredentialProvidersPanel.tsx +111 -0
- package/src/components/panels/EnvironmentEditPanel.module.scss +170 -0
- package/src/components/panels/EnvironmentEditPanel.stories.tsx +206 -0
- package/src/components/panels/EnvironmentEditPanel.tsx +785 -0
- package/src/components/panels/FindingsPanel.module.scss +94 -0
- package/src/components/panels/FindingsPanel.stories.tsx +109 -0
- package/src/components/panels/FindingsPanel.tsx +76 -0
- package/src/components/panels/KeyboardShortcutsPanel.module.scss +65 -0
- package/src/components/panels/KeyboardShortcutsPanel.stories.tsx +40 -0
- package/src/components/panels/KeyboardShortcutsPanel.tsx +104 -0
- package/src/components/panels/PluginsPanel.tsx +77 -0
- package/src/components/panels/SettingsPanel.module.scss +336 -0
- package/src/components/panels/TaskActionButtons.module.scss +22 -0
- package/src/components/panels/TaskActionButtons.stories.tsx +125 -0
- package/src/components/panels/TaskActionButtons.tsx +87 -0
- package/src/components/panels/TaskEditPanel.module.scss +202 -0
- package/src/components/panels/TaskEditPanel.stories.tsx +75 -0
- package/src/components/panels/TaskEditPanel.tsx +328 -0
- package/src/components/panels/TaskOverviewPanel.module.scss +236 -0
- package/src/components/panels/TaskOverviewPanel.stories.tsx +219 -0
- package/src/components/panels/TaskOverviewPanel.tsx +270 -0
- package/src/components/panels/TokensPanel.stories.tsx +131 -0
- package/src/components/panels/TokensPanel.tsx +143 -0
- package/src/components/panels/WorkpadPanel.module.scss +39 -0
- package/src/components/panels/WorkpadPanel.stories.tsx +56 -0
- package/src/components/panels/WorkpadPanel.tsx +63 -0
- package/src/components/panels/index.ts +13 -0
- package/src/components/personas/McpToolSelector.module.scss +109 -0
- package/src/components/personas/McpToolSelector.stories.tsx +129 -0
- package/src/components/personas/McpToolSelector.tsx +180 -0
- package/src/components/personas/PersonaManager.module.scss +233 -0
- package/src/components/personas/PersonaManager.stories.tsx +139 -0
- package/src/components/personas/PersonaManager.tsx +122 -0
- package/src/components/schedules/ScheduleManager.module.scss +98 -0
- package/src/components/schedules/ScheduleManager.stories.tsx +78 -0
- package/src/components/schedules/ScheduleManager.tsx +160 -0
- package/src/components/settings/SettingsNav.module.scss +82 -0
- package/src/components/settings/SettingsNav.stories.tsx +83 -0
- package/src/components/settings/SettingsNav.tsx +104 -0
- package/src/components/streams/StreamDetailPanel.module.scss +206 -0
- package/src/components/streams/StreamDetailPanel.stories.tsx +132 -0
- package/src/components/streams/StreamDetailPanel.tsx +119 -0
- package/src/components/streams/StreamList.module.scss +92 -0
- package/src/components/streams/StreamList.stories.tsx +99 -0
- package/src/components/streams/StreamList.tsx +114 -0
- package/src/components/streams/index.ts +10 -0
- package/src/components/tools/AgentToolCard.module.scss +118 -0
- package/src/components/tools/AgentToolCard.stories.tsx +304 -0
- package/src/components/tools/AgentToolCard.tsx +247 -0
- package/src/components/tools/FileEditCard.stories.tsx +138 -0
- package/src/components/tools/FileEditCard.tsx +160 -0
- package/src/components/tools/FileReadCard.stories.tsx +120 -0
- package/src/components/tools/FileReadCard.tsx +106 -0
- package/src/components/tools/FindingCard.stories.tsx +124 -0
- package/src/components/tools/FindingCard.tsx +178 -0
- package/src/components/tools/GenericToolCard.stories.tsx +80 -0
- package/src/components/tools/GenericToolCard.tsx +111 -0
- package/src/components/tools/IpcCard.stories.tsx +129 -0
- package/src/components/tools/IpcCard.tsx +178 -0
- package/src/components/tools/KnowledgeCard.stories.tsx +112 -0
- package/src/components/tools/KnowledgeCard.tsx +165 -0
- package/src/components/tools/MetadataCard.stories.tsx +32 -0
- package/src/components/tools/MetadataCard.tsx +39 -0
- package/src/components/tools/SearchCard.stories.tsx +74 -0
- package/src/components/tools/SearchCard.tsx +86 -0
- package/src/components/tools/ShellCard.stories.tsx +112 -0
- package/src/components/tools/ShellCard.tsx +106 -0
- package/src/components/tools/TaskCard.stories.tsx +123 -0
- package/src/components/tools/TaskCard.tsx +203 -0
- package/src/components/tools/TodoCard.module.scss +131 -0
- package/src/components/tools/TodoCard.stories.tsx +202 -0
- package/src/components/tools/TodoCard.tsx +200 -0
- package/src/components/tools/ToolCard.stories.tsx +177 -0
- package/src/components/tools/ToolCard.tsx +60 -0
- package/src/components/tools/ToolCardProps.ts +20 -0
- package/src/components/tools/ToolSearchCard.stories.tsx +81 -0
- package/src/components/tools/ToolSearchCard.tsx +86 -0
- package/src/components/tools/WorkpadCard.stories.tsx +106 -0
- package/src/components/tools/WorkpadCard.tsx +125 -0
- package/src/components/tools/classifyTool.test.ts +44 -0
- package/src/components/tools/classifyTool.ts +134 -0
- package/src/components/tools/parseDiff.ts +95 -0
- package/src/components/tools/parseShellOutput.ts +28 -0
- package/src/components/tools/toolCardHelpers.test.ts +53 -0
- package/src/components/tools/toolCards.module.scss +234 -0
- package/src/components/workspace/WorkspaceBoard.module.scss +238 -0
- package/src/components/workspace/WorkspaceBoard.stories.tsx +240 -0
- package/src/components/workspace/WorkspaceBoard.tsx +232 -0
- package/src/components/workspace/WorkspaceFormFields.module.scss +79 -0
- package/src/components/workspace/WorkspaceFormFields.stories.tsx +133 -0
- package/src/components/workspace/WorkspaceFormFields.tsx +185 -0
- package/src/context/GrackleContext.ts +28 -0
- package/src/context/GrackleContextTypes.ts +64 -0
- package/src/context/SidebarContext.tsx +53 -0
- package/src/context/ThemeContext.tsx +21 -0
- package/src/context/ToastContext.tsx +56 -0
- package/src/hooks/types.ts +864 -0
- package/src/hooks/useEventSelection.test.ts +204 -0
- package/src/hooks/useEventSelection.ts +158 -0
- package/src/hooks/useSmartScroll.ts +151 -0
- package/src/hooks/useTheme.ts +228 -0
- package/src/index.ts +210 -0
- package/src/mocks/MockGrackleProvider.tsx +1397 -0
- package/src/mocks/mockData.ts +1966 -0
- package/src/mocks/mockKnowledgeData.ts +294 -0
- package/src/scss.d.ts +12 -0
- package/src/styles/global.scss +244 -0
- package/src/styles/mixins.scss +278 -0
- package/src/styles/prism-theme.scss +148 -0
- package/src/styles/theme.scss +1102 -0
- package/src/test-utils/storybook-decorators.tsx +50 -0
- package/src/test-utils/storybook-helpers.ts +262 -0
- package/src/themes.ts +142 -0
- package/src/utils/boardColumns.ts +141 -0
- package/src/utils/breadcrumbs.test.ts +285 -0
- package/src/utils/breadcrumbs.ts +222 -0
- package/src/utils/dashboard.test.ts +156 -0
- package/src/utils/dashboard.ts +195 -0
- package/src/utils/eventContent.test.ts +353 -0
- package/src/utils/eventContent.ts +209 -0
- package/src/utils/findingCategory.ts +33 -0
- package/src/utils/format.ts +27 -0
- package/src/utils/iconSize.ts +18 -0
- package/src/utils/navigation.ts +205 -0
- package/src/utils/route-config.test.ts +128 -0
- package/src/utils/scrollUtils.test.ts +65 -0
- package/src/utils/scrollUtils.ts +49 -0
- package/src/utils/sessionEvents.test.ts +302 -0
- package/src/utils/sessionEvents.ts +233 -0
- package/src/utils/taskStatus.tsx +137 -0
- package/src/utils/time.ts +92 -0
- package/tsconfig.json +8 -0
- package/vite.config.ts +20 -0
- package/vitest.config.ts +10 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Floating action bar shown at the bottom of EventStream during multi-select mode.
|
|
3
|
+
*
|
|
4
|
+
* Displays selection count, select all/deselect all toggle, Copy button,
|
|
5
|
+
* Forward button, and Cancel.
|
|
6
|
+
* Pure presentational component -- no useGrackle().
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { type JSX } from "react";
|
|
10
|
+
import { Copy, Forward, X } from "lucide-react";
|
|
11
|
+
import { motion } from "motion/react";
|
|
12
|
+
import { ICON_SM } from "../../utils/iconSize.js";
|
|
13
|
+
import { Tooltip } from "./Tooltip.js";
|
|
14
|
+
import styles from "./FloatingActionBar.module.scss";
|
|
15
|
+
|
|
16
|
+
/** Props for the FloatingActionBar component. */
|
|
17
|
+
export interface FloatingActionBarProps {
|
|
18
|
+
/** Number of currently selected events. */
|
|
19
|
+
selectedCount: number;
|
|
20
|
+
/** Total number of selectable (content-bearing) events. */
|
|
21
|
+
totalSelectable: number;
|
|
22
|
+
/** Called when "Select all" is clicked. */
|
|
23
|
+
onSelectAll: () => void;
|
|
24
|
+
/** Called when "Deselect all" is clicked. */
|
|
25
|
+
onDeselectAll: () => void;
|
|
26
|
+
/** Called when "Copy" is clicked. */
|
|
27
|
+
onCopy: () => void;
|
|
28
|
+
/** Called when "Forward" is clicked. Omit to hide the Forward button. */
|
|
29
|
+
onForward?: () => void;
|
|
30
|
+
/** When true, the Forward button is rendered but disabled. */
|
|
31
|
+
forwardDisabled?: boolean;
|
|
32
|
+
/** Called when "Cancel" (X) is clicked. */
|
|
33
|
+
onCancel: () => void;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Floating action bar for multi-select mode in EventStream.
|
|
38
|
+
*
|
|
39
|
+
* Positioned absolutely at the bottom of the EventStream wrapper.
|
|
40
|
+
* Uses motion.div for animated entrance/exit.
|
|
41
|
+
*/
|
|
42
|
+
export function FloatingActionBar({
|
|
43
|
+
selectedCount,
|
|
44
|
+
totalSelectable,
|
|
45
|
+
onSelectAll,
|
|
46
|
+
onDeselectAll,
|
|
47
|
+
onCopy,
|
|
48
|
+
onForward,
|
|
49
|
+
forwardDisabled = false,
|
|
50
|
+
onCancel,
|
|
51
|
+
}: FloatingActionBarProps): JSX.Element {
|
|
52
|
+
const allSelected = selectedCount > 0 && selectedCount >= totalSelectable;
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<motion.div
|
|
56
|
+
className={styles.bar}
|
|
57
|
+
initial={{ opacity: 0, y: 12 }}
|
|
58
|
+
animate={{ opacity: 1, y: 0 }}
|
|
59
|
+
exit={{ opacity: 0, y: 12 }}
|
|
60
|
+
transition={{ duration: 0.15 }}
|
|
61
|
+
data-testid="floating-action-bar"
|
|
62
|
+
>
|
|
63
|
+
<div className={styles.left}>
|
|
64
|
+
<span className={styles.count} data-testid="floating-bar-count">
|
|
65
|
+
{selectedCount} selected
|
|
66
|
+
</span>
|
|
67
|
+
<button
|
|
68
|
+
type="button"
|
|
69
|
+
className={styles.toggleButton}
|
|
70
|
+
onClick={allSelected ? onDeselectAll : onSelectAll}
|
|
71
|
+
data-testid="floating-bar-select-all"
|
|
72
|
+
>
|
|
73
|
+
{allSelected ? "Deselect all" : "Select all"}
|
|
74
|
+
</button>
|
|
75
|
+
</div>
|
|
76
|
+
<div className={styles.right}>
|
|
77
|
+
<button
|
|
78
|
+
type="button"
|
|
79
|
+
className={styles.copyButton}
|
|
80
|
+
onClick={onCopy}
|
|
81
|
+
disabled={selectedCount === 0}
|
|
82
|
+
data-testid="floating-bar-copy"
|
|
83
|
+
>
|
|
84
|
+
<Copy size={ICON_SM} aria-hidden="true" />
|
|
85
|
+
Copy
|
|
86
|
+
</button>
|
|
87
|
+
{onForward !== undefined && (
|
|
88
|
+
<Tooltip
|
|
89
|
+
text={forwardDisabled ? "No active sessions to forward to" : "Forward to another session"}
|
|
90
|
+
>
|
|
91
|
+
<button
|
|
92
|
+
type="button"
|
|
93
|
+
className={styles.forwardButton}
|
|
94
|
+
aria-disabled={forwardDisabled || selectedCount === 0}
|
|
95
|
+
onClick={() => {
|
|
96
|
+
if (!forwardDisabled && selectedCount > 0) {
|
|
97
|
+
onForward();
|
|
98
|
+
}
|
|
99
|
+
}}
|
|
100
|
+
data-testid="floating-bar-forward"
|
|
101
|
+
>
|
|
102
|
+
<Forward size={ICON_SM} aria-hidden="true" />
|
|
103
|
+
Forward
|
|
104
|
+
</button>
|
|
105
|
+
</Tooltip>
|
|
106
|
+
)}
|
|
107
|
+
<button
|
|
108
|
+
type="button"
|
|
109
|
+
className={styles.cancelButton}
|
|
110
|
+
onClick={onCancel}
|
|
111
|
+
aria-label="Cancel selection"
|
|
112
|
+
data-testid="floating-bar-cancel"
|
|
113
|
+
>
|
|
114
|
+
<X size={ICON_SM} aria-hidden="true" />
|
|
115
|
+
</button>
|
|
116
|
+
</div>
|
|
117
|
+
</motion.div>
|
|
118
|
+
);
|
|
119
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
@use '../../styles/mixins' as *;
|
|
2
|
+
|
|
3
|
+
.attemptSelector {
|
|
4
|
+
display: flex;
|
|
5
|
+
align-items: center;
|
|
6
|
+
gap: var(--space-xs);
|
|
7
|
+
padding: var(--space-xs) var(--space-md);
|
|
8
|
+
border-bottom: 1px solid var(--border-subtle);
|
|
9
|
+
font-size: var(--font-size-xs);
|
|
10
|
+
font-family: var(--font-mono);
|
|
11
|
+
color: var(--text-tertiary);
|
|
12
|
+
|
|
13
|
+
@include mobile {
|
|
14
|
+
flex-wrap: wrap;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.attemptLabel {
|
|
19
|
+
margin-right: var(--space-xs);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.attemptButton {
|
|
23
|
+
background: var(--bg-overlay);
|
|
24
|
+
border: 1px solid var(--border-subtle);
|
|
25
|
+
border-radius: var(--radius-sm);
|
|
26
|
+
padding: 2px var(--space-sm);
|
|
27
|
+
font-size: var(--font-size-xs);
|
|
28
|
+
font-family: var(--font-mono);
|
|
29
|
+
color: var(--text-tertiary);
|
|
30
|
+
cursor: pointer;
|
|
31
|
+
transition: all var(--transition-fast);
|
|
32
|
+
|
|
33
|
+
&:hover {
|
|
34
|
+
color: var(--text-secondary);
|
|
35
|
+
border-color: var(--border-primary);
|
|
36
|
+
background: var(--bg-surface);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.attemptActive {
|
|
41
|
+
color: var(--accent-green);
|
|
42
|
+
border-color: var(--accent-green);
|
|
43
|
+
background: var(--accent-green-dim);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.attemptStatus {
|
|
47
|
+
font-size: 10px;
|
|
48
|
+
margin-left: 2px;
|
|
49
|
+
opacity: 0.7;
|
|
50
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { expect, fn, userEvent } from "@storybook/test";
|
|
3
|
+
import { SessionAttemptSelector } from "./SessionAttemptSelector.js";
|
|
4
|
+
import type { Session } from "../../hooks/types.js";
|
|
5
|
+
import { makeSession } from "../../test-utils/storybook-helpers.js";
|
|
6
|
+
|
|
7
|
+
const stoppedCompleted: Session = makeSession({
|
|
8
|
+
id: "sess-a",
|
|
9
|
+
status: "stopped",
|
|
10
|
+
endReason: "completed",
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const running: Session = makeSession({
|
|
14
|
+
id: "sess-b",
|
|
15
|
+
status: "running",
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const stoppedFailed: Session = makeSession({
|
|
19
|
+
id: "sess-c",
|
|
20
|
+
status: "stopped",
|
|
21
|
+
endReason: "error",
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const meta: Meta<typeof SessionAttemptSelector> = {
|
|
25
|
+
title: "Primitives/Display/SessionAttemptSelector",
|
|
26
|
+
component: SessionAttemptSelector,
|
|
27
|
+
tags: ["autodocs"],
|
|
28
|
+
args: {
|
|
29
|
+
taskSessions: [stoppedCompleted, running],
|
|
30
|
+
selectedSessionId: running.id,
|
|
31
|
+
onSelect: fn(),
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export default meta;
|
|
36
|
+
type Story = StoryObj<typeof SessionAttemptSelector>;
|
|
37
|
+
|
|
38
|
+
/** Component returns nothing when there is only one session. */
|
|
39
|
+
export const HiddenForSingleSession: Story = {
|
|
40
|
+
args: {
|
|
41
|
+
taskSessions: [running],
|
|
42
|
+
selectedSessionId: running.id,
|
|
43
|
+
},
|
|
44
|
+
play: async ({ canvas }) => {
|
|
45
|
+
await expect(canvas.queryByTestId("attempt-selector")).toBeNull();
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/** Two attempts are rendered with correct labels. */
|
|
50
|
+
export const TwoAttempts: Story = {
|
|
51
|
+
play: async ({ canvas }) => {
|
|
52
|
+
await expect(canvas.getByTestId("attempt-selector")).toBeInTheDocument();
|
|
53
|
+
await expect(canvas.getByTestId("attempt-1")).toBeInTheDocument();
|
|
54
|
+
await expect(canvas.getByTestId("attempt-2")).toBeInTheDocument();
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/** The selected attempt is highlighted via aria-pressed. */
|
|
59
|
+
export const ActiveAttemptHighlighted: Story = {
|
|
60
|
+
args: {
|
|
61
|
+
taskSessions: [stoppedCompleted, running, stoppedFailed],
|
|
62
|
+
selectedSessionId: running.id,
|
|
63
|
+
},
|
|
64
|
+
play: async ({ canvas }) => {
|
|
65
|
+
await expect(canvas.getByTestId("attempt-2")).toHaveAttribute("aria-pressed", "true");
|
|
66
|
+
await expect(canvas.getByTestId("attempt-1")).toHaveAttribute("aria-pressed", "false");
|
|
67
|
+
await expect(canvas.getByTestId("attempt-3")).toHaveAttribute("aria-pressed", "false");
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/** Clicking an attempt fires the onSelect callback with the session id. */
|
|
72
|
+
export const ClickFiresOnSelect: Story = {
|
|
73
|
+
play: async ({ canvas, args }) => {
|
|
74
|
+
const btn = canvas.getByTestId("attempt-1");
|
|
75
|
+
await userEvent.click(btn);
|
|
76
|
+
await expect(args.onSelect).toHaveBeenCalledWith(stoppedCompleted.id);
|
|
77
|
+
},
|
|
78
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { JSX } from "react";
|
|
2
|
+
import type { Session } from "../../hooks/types.js";
|
|
3
|
+
import styles from "./SessionAttemptSelector.module.scss";
|
|
4
|
+
|
|
5
|
+
/** Props for {@link SessionAttemptSelector}. */
|
|
6
|
+
export interface SessionAttemptSelectorProps {
|
|
7
|
+
/** All sessions (attempts) for a task, ordered chronologically. */
|
|
8
|
+
taskSessions: Session[];
|
|
9
|
+
/** The currently selected session id. */
|
|
10
|
+
selectedSessionId: string | undefined;
|
|
11
|
+
/** Called when the user clicks an attempt button. */
|
|
12
|
+
onSelect: (sessionId: string) => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Displays a row of numbered attempt buttons when a task has multiple sessions.
|
|
17
|
+
* Returns `undefined` when fewer than two sessions exist.
|
|
18
|
+
*/
|
|
19
|
+
export function SessionAttemptSelector({ taskSessions, selectedSessionId, onSelect }: SessionAttemptSelectorProps): JSX.Element | undefined {
|
|
20
|
+
if (taskSessions.length < 2) {
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
return (
|
|
24
|
+
<div className={styles.attemptSelector} data-testid="attempt-selector">
|
|
25
|
+
<span className={styles.attemptLabel}>Attempts:</span>
|
|
26
|
+
{taskSessions.map((s, i) => {
|
|
27
|
+
const isActive = s.id === selectedSessionId;
|
|
28
|
+
const statusIcon = s.status === "stopped" && s.endReason === "completed" ? "\u2713"
|
|
29
|
+
: s.status === "stopped" ? "\u2717"
|
|
30
|
+
: s.status === "running" || s.status === "idle" ? "\u25CF"
|
|
31
|
+
: "";
|
|
32
|
+
return (
|
|
33
|
+
<button
|
|
34
|
+
key={s.id}
|
|
35
|
+
className={`${styles.attemptButton} ${isActive ? styles.attemptActive : ""}`}
|
|
36
|
+
onClick={() => onSelect(s.id)}
|
|
37
|
+
title={`Attempt #${i + 1} \u2014 ${s.status}`}
|
|
38
|
+
aria-label={`Attempt #${i + 1}, ${s.status}`}
|
|
39
|
+
aria-pressed={isActive}
|
|
40
|
+
data-testid={`attempt-${i + 1}`}
|
|
41
|
+
>
|
|
42
|
+
#{i + 1}
|
|
43
|
+
{statusIcon && <span className={styles.attemptStatus}>{statusIcon}</span>}
|
|
44
|
+
</button>
|
|
45
|
+
);
|
|
46
|
+
})}
|
|
47
|
+
</div>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
@use '../../styles/mixins' as *;
|
|
2
|
+
|
|
3
|
+
// =============================================================================
|
|
4
|
+
// SessionPicker -- modal dialog for selecting a forward-to session
|
|
5
|
+
// =============================================================================
|
|
6
|
+
|
|
7
|
+
.overlay {
|
|
8
|
+
position: fixed;
|
|
9
|
+
inset: 0;
|
|
10
|
+
z-index: 200;
|
|
11
|
+
display: flex;
|
|
12
|
+
align-items: center;
|
|
13
|
+
justify-content: center;
|
|
14
|
+
background: var(--overlay-bg, rgba(0, 0, 0, 0.55));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.dialog {
|
|
18
|
+
@include surface-card;
|
|
19
|
+
width: min(480px, calc(100vw - var(--space-xl)));
|
|
20
|
+
max-height: min(480px, calc(100vh - var(--space-xl)));
|
|
21
|
+
display: flex;
|
|
22
|
+
flex-direction: column;
|
|
23
|
+
overflow: hidden;
|
|
24
|
+
border-radius: var(--radius-lg);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.header {
|
|
28
|
+
display: flex;
|
|
29
|
+
align-items: center;
|
|
30
|
+
justify-content: space-between;
|
|
31
|
+
padding: var(--space-md) var(--space-md) var(--space-sm);
|
|
32
|
+
flex-shrink: 0;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.title {
|
|
36
|
+
font-size: var(--font-size-md);
|
|
37
|
+
font-weight: var(--font-weight-bold);
|
|
38
|
+
color: var(--text-primary);
|
|
39
|
+
margin: 0;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.closeButton {
|
|
43
|
+
@include btn-ghost(var(--text-secondary));
|
|
44
|
+
display: flex;
|
|
45
|
+
align-items: center;
|
|
46
|
+
justify-content: center;
|
|
47
|
+
width: 28px;
|
|
48
|
+
height: 28px;
|
|
49
|
+
padding: 0;
|
|
50
|
+
border-radius: var(--radius-sm);
|
|
51
|
+
flex-shrink: 0;
|
|
52
|
+
|
|
53
|
+
&:hover {
|
|
54
|
+
color: var(--text-primary);
|
|
55
|
+
background: var(--bg-overlay);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.filterRow {
|
|
60
|
+
display: flex;
|
|
61
|
+
align-items: center;
|
|
62
|
+
gap: var(--space-xs);
|
|
63
|
+
padding: 0 var(--space-md) var(--space-sm);
|
|
64
|
+
flex-shrink: 0;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.searchIcon {
|
|
68
|
+
color: var(--text-secondary);
|
|
69
|
+
flex-shrink: 0;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.filterInput {
|
|
73
|
+
flex: 1;
|
|
74
|
+
background: var(--bg-overlay);
|
|
75
|
+
border: 1px solid var(--border-subtle);
|
|
76
|
+
border-radius: var(--radius-sm);
|
|
77
|
+
color: var(--text-primary);
|
|
78
|
+
font-size: var(--font-size-sm);
|
|
79
|
+
padding: var(--space-xs) var(--space-sm);
|
|
80
|
+
outline: none;
|
|
81
|
+
|
|
82
|
+
&::placeholder {
|
|
83
|
+
color: var(--text-muted);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
&:focus {
|
|
87
|
+
border-color: var(--accent-blue);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.list {
|
|
92
|
+
list-style: none;
|
|
93
|
+
margin: 0;
|
|
94
|
+
padding: 0 var(--space-sm) var(--space-sm);
|
|
95
|
+
overflow-y: auto;
|
|
96
|
+
flex: 1;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.emptyItem {
|
|
100
|
+
padding: var(--space-md);
|
|
101
|
+
color: var(--text-secondary);
|
|
102
|
+
font-size: var(--font-size-sm);
|
|
103
|
+
text-align: center;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.sessionRow {
|
|
107
|
+
@include btn-ghost(var(--text-primary));
|
|
108
|
+
display: flex;
|
|
109
|
+
flex-direction: column;
|
|
110
|
+
align-items: flex-start;
|
|
111
|
+
gap: var(--space-xs);
|
|
112
|
+
width: 100%;
|
|
113
|
+
padding: var(--space-sm) var(--space-md);
|
|
114
|
+
border-radius: var(--radius-sm);
|
|
115
|
+
text-align: left;
|
|
116
|
+
|
|
117
|
+
&:hover {
|
|
118
|
+
background: var(--bg-overlay);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.sessionMain {
|
|
123
|
+
display: flex;
|
|
124
|
+
align-items: center;
|
|
125
|
+
gap: var(--space-sm);
|
|
126
|
+
width: 100%;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.envName {
|
|
130
|
+
font-size: var(--font-size-sm);
|
|
131
|
+
font-weight: var(--font-weight-bold);
|
|
132
|
+
color: var(--text-primary);
|
|
133
|
+
flex: 1;
|
|
134
|
+
overflow: hidden;
|
|
135
|
+
text-overflow: ellipsis;
|
|
136
|
+
white-space: nowrap;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.personaName {
|
|
140
|
+
font-size: 11px;
|
|
141
|
+
font-weight: var(--font-weight-medium);
|
|
142
|
+
color: var(--text-secondary);
|
|
143
|
+
border: 1px solid var(--border-subtle);
|
|
144
|
+
border-radius: var(--radius-sm);
|
|
145
|
+
padding: 1px var(--space-xs);
|
|
146
|
+
flex-shrink: 0;
|
|
147
|
+
max-width: 120px;
|
|
148
|
+
overflow: hidden;
|
|
149
|
+
text-overflow: ellipsis;
|
|
150
|
+
white-space: nowrap;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.statusBadge {
|
|
154
|
+
font-size: 11px;
|
|
155
|
+
font-weight: var(--font-weight-medium);
|
|
156
|
+
padding: 1px var(--space-xs);
|
|
157
|
+
border-radius: var(--radius-sm);
|
|
158
|
+
text-transform: uppercase;
|
|
159
|
+
letter-spacing: 0.04em;
|
|
160
|
+
flex-shrink: 0;
|
|
161
|
+
|
|
162
|
+
&.status_running {
|
|
163
|
+
background: color-mix(in srgb, var(--accent-green) 20%, transparent);
|
|
164
|
+
color: var(--accent-green);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
&.status_idle {
|
|
168
|
+
background: color-mix(in srgb, var(--accent-blue) 20%, transparent);
|
|
169
|
+
color: var(--accent-blue);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
&.status_other {
|
|
173
|
+
background: var(--bg-overlay);
|
|
174
|
+
color: var(--text-secondary);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.sessionPrompt {
|
|
179
|
+
font-size: var(--font-size-xs);
|
|
180
|
+
color: var(--text-secondary);
|
|
181
|
+
overflow: hidden;
|
|
182
|
+
text-overflow: ellipsis;
|
|
183
|
+
white-space: nowrap;
|
|
184
|
+
width: 100%;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.noSessions {
|
|
188
|
+
display: flex;
|
|
189
|
+
flex-direction: column;
|
|
190
|
+
align-items: center;
|
|
191
|
+
justify-content: center;
|
|
192
|
+
gap: var(--space-sm);
|
|
193
|
+
padding: var(--space-xl);
|
|
194
|
+
color: var(--text-secondary);
|
|
195
|
+
font-size: var(--font-size-sm);
|
|
196
|
+
|
|
197
|
+
p {
|
|
198
|
+
margin: 0;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { expect, fn, userEvent } from "@storybook/test";
|
|
3
|
+
import { SessionPicker } from "./SessionPicker.js";
|
|
4
|
+
import type { Environment, Session } from "../../hooks/types.js";
|
|
5
|
+
import { makeSession, makeEnvironment } from "../../test-utils/storybook-helpers.js";
|
|
6
|
+
|
|
7
|
+
const mockEnvironments: Environment[] = [
|
|
8
|
+
makeEnvironment({ id: "env-1", displayName: "Production" }),
|
|
9
|
+
makeEnvironment({ id: "env-2", displayName: "Staging" }),
|
|
10
|
+
makeEnvironment({ id: "env-3", displayName: "Dev Box" }),
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
const mockSessions: Session[] = [
|
|
14
|
+
makeSession({ id: "sess-1", environmentId: "env-1", status: "running", prompt: "Refactor the authentication module and update tests" }),
|
|
15
|
+
makeSession({ id: "sess-2", environmentId: "env-2", status: "idle", prompt: "Fix the login redirect bug in the frontend" }),
|
|
16
|
+
makeSession({ id: "sess-3", environmentId: "env-3", status: "running", prompt: "Add dark mode support to the settings page" }),
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
const meta: Meta<typeof SessionPicker> = {
|
|
20
|
+
component: SessionPicker,
|
|
21
|
+
title: "Grackle/Display/SessionPicker",
|
|
22
|
+
tags: ["autodocs"],
|
|
23
|
+
args: {
|
|
24
|
+
isOpen: true,
|
|
25
|
+
sessions: mockSessions,
|
|
26
|
+
environments: mockEnvironments,
|
|
27
|
+
onSelect: fn(),
|
|
28
|
+
onCancel: fn(),
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
export default meta;
|
|
32
|
+
type Story = StoryObj<typeof meta>;
|
|
33
|
+
|
|
34
|
+
/** Default state: multiple active sessions available. */
|
|
35
|
+
export const Default: Story = {
|
|
36
|
+
play: async ({ canvas }) => {
|
|
37
|
+
const dialog = canvas.getByTestId("session-picker-dialog");
|
|
38
|
+
await expect(dialog).toBeInTheDocument();
|
|
39
|
+
|
|
40
|
+
const list = canvas.getByTestId("session-picker-list");
|
|
41
|
+
await expect(list).toBeInTheDocument();
|
|
42
|
+
|
|
43
|
+
// All three sessions should be listed
|
|
44
|
+
await expect(canvas.getByTestId("session-picker-item-sess-1")).toBeInTheDocument();
|
|
45
|
+
await expect(canvas.getByTestId("session-picker-item-sess-2")).toBeInTheDocument();
|
|
46
|
+
await expect(canvas.getByTestId("session-picker-item-sess-3")).toBeInTheDocument();
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/** Status badges are rendered for each session. */
|
|
51
|
+
export const StatusBadges: Story = {
|
|
52
|
+
play: async ({ canvas }) => {
|
|
53
|
+
const runningBadge = canvas.getByTestId("session-picker-status-sess-1");
|
|
54
|
+
await expect(runningBadge).toHaveTextContent("running");
|
|
55
|
+
|
|
56
|
+
const idleBadge = canvas.getByTestId("session-picker-status-sess-2");
|
|
57
|
+
await expect(idleBadge).toHaveTextContent("idle");
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/** Clicking a session row calls onSelect with the session ID. */
|
|
62
|
+
export const SelectSession: Story = {
|
|
63
|
+
play: async ({ canvas, args }) => {
|
|
64
|
+
const sessionRow = canvas.getByTestId("session-picker-item-sess-2");
|
|
65
|
+
await userEvent.click(sessionRow);
|
|
66
|
+
await expect(args.onSelect).toHaveBeenCalledWith("sess-2");
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/** Clicking the close button calls onCancel. */
|
|
71
|
+
export const CloseButton: Story = {
|
|
72
|
+
play: async ({ canvas, args }) => {
|
|
73
|
+
const closeBtn = canvas.getByTestId("session-picker-close");
|
|
74
|
+
await userEvent.click(closeBtn);
|
|
75
|
+
await expect(args.onCancel).toHaveBeenCalled();
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/** Empty session list shows a no-sessions message. */
|
|
80
|
+
export const NoSessions: Story = {
|
|
81
|
+
args: {
|
|
82
|
+
sessions: [],
|
|
83
|
+
},
|
|
84
|
+
play: async ({ canvas }) => {
|
|
85
|
+
const noSessions = canvas.getByTestId("session-picker-no-sessions");
|
|
86
|
+
await expect(noSessions).toBeInTheDocument();
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/** Filter input appears only when there are more than 4 sessions. */
|
|
91
|
+
export const FilterInput: Story = {
|
|
92
|
+
args: {
|
|
93
|
+
sessions: [
|
|
94
|
+
makeSession({ id: "sess-a", environmentId: "env-1", status: "running", prompt: "Task alpha" }),
|
|
95
|
+
makeSession({ id: "sess-b", environmentId: "env-1", status: "idle", prompt: "Task beta" }),
|
|
96
|
+
makeSession({ id: "sess-c", environmentId: "env-2", status: "running", prompt: "Task gamma" }),
|
|
97
|
+
makeSession({ id: "sess-d", environmentId: "env-2", status: "idle", prompt: "Task delta" }),
|
|
98
|
+
makeSession({ id: "sess-e", environmentId: "env-3", status: "running", prompt: "Task epsilon" }),
|
|
99
|
+
],
|
|
100
|
+
},
|
|
101
|
+
play: async ({ canvas }) => {
|
|
102
|
+
const filter = canvas.getByTestId("session-picker-filter");
|
|
103
|
+
await expect(filter).toBeInTheDocument();
|
|
104
|
+
|
|
105
|
+
// Type to filter by environment name
|
|
106
|
+
await userEvent.type(filter, "Staging");
|
|
107
|
+
|
|
108
|
+
// Only staging sessions should appear (sess-c and sess-d use env-2)
|
|
109
|
+
await expect(canvas.getByTestId("session-picker-item-sess-c")).toBeInTheDocument();
|
|
110
|
+
await expect(canvas.getByTestId("session-picker-item-sess-d")).toBeInTheDocument();
|
|
111
|
+
// Production sessions should be hidden
|
|
112
|
+
await expect(canvas.queryByTestId("session-picker-item-sess-a")).not.toBeInTheDocument();
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
/** Filter by prompt text. */
|
|
117
|
+
export const FilterByPrompt: Story = {
|
|
118
|
+
args: {
|
|
119
|
+
sessions: [
|
|
120
|
+
makeSession({ id: "sess-a", environmentId: "env-1", status: "running", prompt: "Fix authentication bug" }),
|
|
121
|
+
makeSession({ id: "sess-b", environmentId: "env-1", status: "idle", prompt: "Add dark mode" }),
|
|
122
|
+
makeSession({ id: "sess-c", environmentId: "env-2", status: "running", prompt: "Fix database migration" }),
|
|
123
|
+
makeSession({ id: "sess-d", environmentId: "env-2", status: "idle", prompt: "Update dependencies" }),
|
|
124
|
+
makeSession({ id: "sess-e", environmentId: "env-3", status: "running", prompt: "Fix CSS layout" }),
|
|
125
|
+
],
|
|
126
|
+
},
|
|
127
|
+
play: async ({ canvas }) => {
|
|
128
|
+
const filter = canvas.getByTestId("session-picker-filter");
|
|
129
|
+
await userEvent.type(filter, "Fix");
|
|
130
|
+
|
|
131
|
+
// Only "Fix" sessions should appear
|
|
132
|
+
await expect(canvas.getByTestId("session-picker-item-sess-a")).toBeInTheDocument();
|
|
133
|
+
await expect(canvas.getByTestId("session-picker-item-sess-c")).toBeInTheDocument();
|
|
134
|
+
await expect(canvas.getByTestId("session-picker-item-sess-e")).toBeInTheDocument();
|
|
135
|
+
// Non-fix sessions hidden
|
|
136
|
+
await expect(canvas.queryByTestId("session-picker-item-sess-b")).not.toBeInTheDocument();
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
/** No match shows empty state message. */
|
|
141
|
+
export const FilterNoMatch: Story = {
|
|
142
|
+
args: {
|
|
143
|
+
sessions: [
|
|
144
|
+
makeSession({ id: "sess-a", environmentId: "env-1", status: "running", prompt: "Task alpha" }),
|
|
145
|
+
makeSession({ id: "sess-b", environmentId: "env-1", status: "idle", prompt: "Task beta" }),
|
|
146
|
+
makeSession({ id: "sess-c", environmentId: "env-2", status: "running", prompt: "Task gamma" }),
|
|
147
|
+
makeSession({ id: "sess-d", environmentId: "env-2", status: "idle", prompt: "Task delta" }),
|
|
148
|
+
makeSession({ id: "sess-e", environmentId: "env-3", status: "running", prompt: "Task epsilon" }),
|
|
149
|
+
],
|
|
150
|
+
},
|
|
151
|
+
play: async ({ canvas }) => {
|
|
152
|
+
const filter = canvas.getByTestId("session-picker-filter");
|
|
153
|
+
await userEvent.type(filter, "xyzzy-no-match");
|
|
154
|
+
|
|
155
|
+
const empty = canvas.getByTestId("session-picker-empty");
|
|
156
|
+
await expect(empty).toBeInTheDocument();
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
/** Picker is hidden when isOpen is false. */
|
|
161
|
+
export const Closed: Story = {
|
|
162
|
+
args: {
|
|
163
|
+
isOpen: false,
|
|
164
|
+
},
|
|
165
|
+
play: async ({ canvas }) => {
|
|
166
|
+
const overlay = canvas.queryByTestId("session-picker-overlay");
|
|
167
|
+
await expect(overlay).not.toBeInTheDocument();
|
|
168
|
+
},
|
|
169
|
+
};
|