@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,94 @@
|
|
|
1
|
+
@use '../../styles/mixins' as *;
|
|
2
|
+
|
|
3
|
+
// =============================================================================
|
|
4
|
+
// Findings Panel — workspace findings cards with staggered animation
|
|
5
|
+
// =============================================================================
|
|
6
|
+
|
|
7
|
+
.container {
|
|
8
|
+
padding: var(--space-md);
|
|
9
|
+
display: flex;
|
|
10
|
+
flex-direction: column;
|
|
11
|
+
gap: var(--space-sm);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.emptyState {
|
|
15
|
+
padding: var(--space-xl);
|
|
16
|
+
color: var(--text-tertiary);
|
|
17
|
+
text-align: center;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.card {
|
|
21
|
+
@include surface-card;
|
|
22
|
+
@include card-hover;
|
|
23
|
+
padding: var(--space-md);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.cardClickable {
|
|
27
|
+
cursor: pointer;
|
|
28
|
+
|
|
29
|
+
&:focus-visible {
|
|
30
|
+
outline: 2px solid var(--accent-blue);
|
|
31
|
+
outline-offset: 2px;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.cardHeader {
|
|
36
|
+
display: flex;
|
|
37
|
+
align-items: center;
|
|
38
|
+
gap: var(--space-sm);
|
|
39
|
+
margin-bottom: var(--space-xs);
|
|
40
|
+
|
|
41
|
+
@include mobile {
|
|
42
|
+
flex-wrap: wrap;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.categoryBadge {
|
|
47
|
+
border-radius: var(--radius-full);
|
|
48
|
+
padding: 2px var(--space-md);
|
|
49
|
+
font-size: var(--font-size-xs);
|
|
50
|
+
font-weight: var(--font-weight-bold);
|
|
51
|
+
text-transform: uppercase;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.findingTitle {
|
|
55
|
+
font-weight: var(--font-weight-bold);
|
|
56
|
+
color: var(--text-primary);
|
|
57
|
+
font-size: var(--font-size-md);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.findingDate {
|
|
61
|
+
margin-left: auto;
|
|
62
|
+
font-size: var(--font-size-xs);
|
|
63
|
+
color: var(--text-tertiary);
|
|
64
|
+
|
|
65
|
+
@include mobile {
|
|
66
|
+
margin-left: 0;
|
|
67
|
+
width: 100%;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.findingContent {
|
|
72
|
+
font-size: var(--font-size-sm);
|
|
73
|
+
color: var(--text-secondary);
|
|
74
|
+
white-space: pre-wrap;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.tags {
|
|
78
|
+
margin-top: var(--space-xs);
|
|
79
|
+
display: flex;
|
|
80
|
+
gap: var(--space-xs);
|
|
81
|
+
|
|
82
|
+
@include mobile {
|
|
83
|
+
flex-wrap: wrap;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Frosted inset chip for tags — third glass tier with category-tinted border
|
|
88
|
+
.tag {
|
|
89
|
+
@include surface-inset;
|
|
90
|
+
font-size: 10px;
|
|
91
|
+
padding: 1px var(--space-xs);
|
|
92
|
+
border-radius: var(--radius-full);
|
|
93
|
+
color: var(--text-secondary);
|
|
94
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { expect } from "@storybook/test";
|
|
3
|
+
import { FindingsPanel } from "./FindingsPanel.js";
|
|
4
|
+
import { buildFinding } from "../../test-utils/storybook-helpers.js";
|
|
5
|
+
|
|
6
|
+
const meta: Meta<typeof FindingsPanel> = {
|
|
7
|
+
title: "Grackle/Panels/FindingsPanel",
|
|
8
|
+
component: FindingsPanel,
|
|
9
|
+
tags: ["autodocs"],
|
|
10
|
+
args: {
|
|
11
|
+
findings: [],
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export default meta;
|
|
16
|
+
type Story = StoryObj<typeof FindingsPanel>;
|
|
17
|
+
|
|
18
|
+
/** Empty state shows a placeholder message when there are no findings. */
|
|
19
|
+
export const EmptyState: Story = {
|
|
20
|
+
args: {
|
|
21
|
+
findings: [],
|
|
22
|
+
},
|
|
23
|
+
play: async ({ canvas }) => {
|
|
24
|
+
await expect(
|
|
25
|
+
canvas.getByText("No findings yet. Agents will post discoveries here."),
|
|
26
|
+
).toBeInTheDocument();
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/** Renders a single finding card with category badge, title, content, and tags. */
|
|
31
|
+
export const SingleFinding: Story = {
|
|
32
|
+
args: {
|
|
33
|
+
findings: [
|
|
34
|
+
buildFinding({
|
|
35
|
+
id: "f-1",
|
|
36
|
+
category: "architecture",
|
|
37
|
+
title: "Service boundary issue",
|
|
38
|
+
content: "The auth service is tightly coupled to the user service.",
|
|
39
|
+
tags: ["coupling", "refactor"],
|
|
40
|
+
}),
|
|
41
|
+
],
|
|
42
|
+
},
|
|
43
|
+
play: async ({ canvas }) => {
|
|
44
|
+
await expect(canvas.getByText("architecture")).toBeInTheDocument();
|
|
45
|
+
await expect(canvas.getByText("Service boundary issue")).toBeInTheDocument();
|
|
46
|
+
await expect(canvas.getByText(/tightly coupled/)).toBeInTheDocument();
|
|
47
|
+
await expect(canvas.getByText("coupling")).toBeInTheDocument();
|
|
48
|
+
await expect(canvas.getByText("refactor")).toBeInTheDocument();
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/** Renders multiple findings across different categories. */
|
|
53
|
+
export const MultipleFindings: Story = {
|
|
54
|
+
args: {
|
|
55
|
+
findings: [
|
|
56
|
+
buildFinding({
|
|
57
|
+
id: "f-1",
|
|
58
|
+
category: "bug",
|
|
59
|
+
title: "Race condition in session cleanup",
|
|
60
|
+
content: "When two sessions end simultaneously, the cleanup handler may skip one.",
|
|
61
|
+
tags: ["concurrency"],
|
|
62
|
+
}),
|
|
63
|
+
buildFinding({
|
|
64
|
+
id: "f-2",
|
|
65
|
+
category: "api",
|
|
66
|
+
title: "Missing pagination on list endpoints",
|
|
67
|
+
content: "The list_tasks endpoint returns all tasks without pagination support.",
|
|
68
|
+
tags: ["api", "performance"],
|
|
69
|
+
}),
|
|
70
|
+
buildFinding({
|
|
71
|
+
id: "f-3",
|
|
72
|
+
category: "decision",
|
|
73
|
+
title: "Chose SQLite over PostgreSQL",
|
|
74
|
+
content: "SQLite with WAL mode provides sufficient concurrency for single-server deployment.",
|
|
75
|
+
tags: ["database"],
|
|
76
|
+
}),
|
|
77
|
+
],
|
|
78
|
+
},
|
|
79
|
+
play: async ({ canvas }) => {
|
|
80
|
+
// Use getAllByText for "api" since it appears as both a category badge and a tag
|
|
81
|
+
await expect(canvas.getByText("bug")).toBeInTheDocument();
|
|
82
|
+
const apiElements = canvas.getAllByText("api");
|
|
83
|
+
await expect(apiElements.length).toBeGreaterThanOrEqual(1);
|
|
84
|
+
await expect(canvas.getByText("decision")).toBeInTheDocument();
|
|
85
|
+
await expect(canvas.getByText("Race condition in session cleanup")).toBeInTheDocument();
|
|
86
|
+
await expect(canvas.getByText("Missing pagination on list endpoints")).toBeInTheDocument();
|
|
87
|
+
await expect(canvas.getByText("Chose SQLite over PostgreSQL")).toBeInTheDocument();
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/** Long content is truncated at 300 characters with an ellipsis. */
|
|
92
|
+
export const LongContentTruncated: Story = {
|
|
93
|
+
args: {
|
|
94
|
+
findings: [
|
|
95
|
+
buildFinding({
|
|
96
|
+
id: "f-long",
|
|
97
|
+
category: "pattern",
|
|
98
|
+
title: "Verbose finding",
|
|
99
|
+
content: "A".repeat(400),
|
|
100
|
+
tags: [],
|
|
101
|
+
}),
|
|
102
|
+
],
|
|
103
|
+
},
|
|
104
|
+
play: async ({ canvas }) => {
|
|
105
|
+
// The rendered content should end with "..." since it exceeds 300 chars
|
|
106
|
+
const contentEl = canvas.getByText(/A{10,}\.\.\.$/);
|
|
107
|
+
await expect(contentEl).toBeInTheDocument();
|
|
108
|
+
},
|
|
109
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { JSX } from "react";
|
|
2
|
+
import { motion } from "motion/react";
|
|
3
|
+
import type { FindingData } from "../../hooks/types.js";
|
|
4
|
+
import styles from "./FindingsPanel.module.scss";
|
|
5
|
+
import { formatRelativeTime } from "../../utils/time.js";
|
|
6
|
+
import { getCategoryColor } from "../../utils/findingCategory.js";
|
|
7
|
+
|
|
8
|
+
/** Props for the FindingsPanel component. */
|
|
9
|
+
interface Props {
|
|
10
|
+
/** Pre-filtered findings to display. */
|
|
11
|
+
findings: FindingData[];
|
|
12
|
+
/** Optional click handler for finding cards. When provided, cards become clickable. */
|
|
13
|
+
onFindingClick?: (findingId: string) => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Displays workspace findings as styled cards with staggered entrance animation. */
|
|
17
|
+
export function FindingsPanel({ findings, onFindingClick }: Props): JSX.Element {
|
|
18
|
+
if (findings.length === 0) {
|
|
19
|
+
return (
|
|
20
|
+
<div className={styles.emptyState}>
|
|
21
|
+
No findings yet. Agents will post discoveries here.
|
|
22
|
+
</div>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<div className={styles.container}>
|
|
28
|
+
{findings.map((f, index) => {
|
|
29
|
+
const categoryColor = getCategoryColor(f.category);
|
|
30
|
+
const Tag = onFindingClick ? motion.button : motion.div;
|
|
31
|
+
return (
|
|
32
|
+
<Tag
|
|
33
|
+
key={f.id}
|
|
34
|
+
type={onFindingClick ? "button" : undefined}
|
|
35
|
+
className={`${styles.card} ${onFindingClick ? styles.cardClickable : ""}`}
|
|
36
|
+
initial={{ opacity: 0, y: 8 }}
|
|
37
|
+
animate={{ opacity: 1, y: 0 }}
|
|
38
|
+
transition={{ delay: index * 0.05, duration: 0.2 }}
|
|
39
|
+
onClick={onFindingClick ? () => { onFindingClick(f.id); } : undefined}
|
|
40
|
+
>
|
|
41
|
+
<div className={styles.cardHeader}>
|
|
42
|
+
<span
|
|
43
|
+
className={styles.categoryBadge}
|
|
44
|
+
style={{ background: categoryColor.bg, color: categoryColor.text }}
|
|
45
|
+
>
|
|
46
|
+
{f.category}
|
|
47
|
+
</span>
|
|
48
|
+
<span className={styles.findingTitle}>
|
|
49
|
+
{f.title}
|
|
50
|
+
</span>
|
|
51
|
+
<span className={styles.findingDate} title={f.createdAt}>
|
|
52
|
+
{formatRelativeTime(f.createdAt)}
|
|
53
|
+
</span>
|
|
54
|
+
</div>
|
|
55
|
+
<div className={styles.findingContent}>
|
|
56
|
+
{f.content.length > 300 ? f.content.slice(0, 300) + "..." : f.content}
|
|
57
|
+
</div>
|
|
58
|
+
{f.tags.length > 0 && (
|
|
59
|
+
<div className={styles.tags}>
|
|
60
|
+
{f.tags.map((tag) => (
|
|
61
|
+
<span
|
|
62
|
+
key={tag}
|
|
63
|
+
className={styles.tag}
|
|
64
|
+
style={{ color: categoryColor.text, textShadow: `0 0 8px ${categoryColor.text}` }}
|
|
65
|
+
>
|
|
66
|
+
{tag}
|
|
67
|
+
</span>
|
|
68
|
+
))}
|
|
69
|
+
</div>
|
|
70
|
+
)}
|
|
71
|
+
</Tag>
|
|
72
|
+
);
|
|
73
|
+
})}
|
|
74
|
+
</div>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
@use '../../styles/mixins' as *;
|
|
2
|
+
|
|
3
|
+
// =============================================================================
|
|
4
|
+
// Keyboard Shortcuts Panel
|
|
5
|
+
// =============================================================================
|
|
6
|
+
|
|
7
|
+
.group {
|
|
8
|
+
margin-bottom: var(--space-lg);
|
|
9
|
+
|
|
10
|
+
&:last-child {
|
|
11
|
+
margin-bottom: 0;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.groupTitle {
|
|
16
|
+
font-size: var(--font-size-sm);
|
|
17
|
+
font-weight: var(--font-weight-bold);
|
|
18
|
+
color: var(--text-secondary);
|
|
19
|
+
margin: 0 0 var(--space-sm);
|
|
20
|
+
text-transform: uppercase;
|
|
21
|
+
letter-spacing: 0.5px;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.shortcutList {
|
|
25
|
+
display: flex;
|
|
26
|
+
flex-direction: column;
|
|
27
|
+
gap: var(--space-xs);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.shortcutRow {
|
|
31
|
+
@include surface-inset;
|
|
32
|
+
display: flex;
|
|
33
|
+
align-items: center;
|
|
34
|
+
gap: var(--space-md);
|
|
35
|
+
padding: var(--space-xs) var(--space-md);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.keys {
|
|
39
|
+
display: flex;
|
|
40
|
+
gap: 4px;
|
|
41
|
+
min-width: 80px;
|
|
42
|
+
flex-shrink: 0;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.kbd {
|
|
46
|
+
display: inline-flex;
|
|
47
|
+
align-items: center;
|
|
48
|
+
justify-content: center;
|
|
49
|
+
min-width: 24px;
|
|
50
|
+
height: 24px;
|
|
51
|
+
padding: 0 var(--space-xs);
|
|
52
|
+
border-radius: var(--radius-sm);
|
|
53
|
+
border: 1px solid var(--border-subtle);
|
|
54
|
+
background: var(--bg-surface);
|
|
55
|
+
color: var(--text-primary);
|
|
56
|
+
font-family: var(--font-mono);
|
|
57
|
+
font-size: var(--font-size-xs);
|
|
58
|
+
font-weight: var(--font-weight-medium);
|
|
59
|
+
line-height: 1;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.description {
|
|
63
|
+
font-size: var(--font-size-sm);
|
|
64
|
+
color: var(--text-secondary);
|
|
65
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { expect } from "@storybook/test";
|
|
3
|
+
import { KeyboardShortcutsPanel } from "./KeyboardShortcutsPanel.js";
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof KeyboardShortcutsPanel> = {
|
|
6
|
+
title: "App/Panels/KeyboardShortcutsPanel",
|
|
7
|
+
component: KeyboardShortcutsPanel,
|
|
8
|
+
};
|
|
9
|
+
export default meta;
|
|
10
|
+
type Story = StoryObj<typeof meta>;
|
|
11
|
+
|
|
12
|
+
/** Panel renders with all shortcut categories visible. */
|
|
13
|
+
export const Default: Story = {
|
|
14
|
+
play: async ({ canvas }) => {
|
|
15
|
+
await expect(canvas.getByTestId("keyboard-shortcuts-panel")).toBeInTheDocument();
|
|
16
|
+
await expect(canvas.getByText("Keyboard Shortcuts")).toBeInTheDocument();
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/** All category headings are rendered. */
|
|
21
|
+
export const AllCategoriesRendered: Story = {
|
|
22
|
+
play: async ({ canvas }) => {
|
|
23
|
+
await expect(canvas.getByText("Global")).toBeInTheDocument();
|
|
24
|
+
await expect(canvas.getByText("Task Page")).toBeInTheDocument();
|
|
25
|
+
await expect(canvas.getByText("Workspace Page")).toBeInTheDocument();
|
|
26
|
+
await expect(canvas.getByText("Navigation Lists")).toBeInTheDocument();
|
|
27
|
+
await expect(canvas.getByText("Editing")).toBeInTheDocument();
|
|
28
|
+
await expect(canvas.getByText("Chat")).toBeInTheDocument();
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/** Specific shortcut descriptions are present. */
|
|
33
|
+
export const ShortcutDescriptions: Story = {
|
|
34
|
+
play: async ({ canvas }) => {
|
|
35
|
+
await expect(canvas.getByText("Open keyboard shortcuts reference")).toBeInTheDocument();
|
|
36
|
+
await expect(canvas.getByText("Create a new task")).toBeInTheDocument();
|
|
37
|
+
await expect(canvas.getByText("Switch to Overview tab")).toBeInTheDocument();
|
|
38
|
+
await expect(canvas.getByText("Send message (when input is focused)")).toBeInTheDocument();
|
|
39
|
+
},
|
|
40
|
+
};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import type { JSX } from "react";
|
|
2
|
+
import styles from "./SettingsPanel.module.scss";
|
|
3
|
+
import shortcutStyles from "./KeyboardShortcutsPanel.module.scss";
|
|
4
|
+
|
|
5
|
+
/** A single keyboard shortcut definition. */
|
|
6
|
+
interface Shortcut {
|
|
7
|
+
/** Key combination displayed as kbd badges (e.g. ["?"], ["Ctrl", "/"]). */
|
|
8
|
+
keys: string[];
|
|
9
|
+
/** Human-readable description of the action. */
|
|
10
|
+
description: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** A named group of related shortcuts. */
|
|
14
|
+
interface ShortcutGroup {
|
|
15
|
+
/** Section heading. */
|
|
16
|
+
title: string;
|
|
17
|
+
/** Shortcuts in this group. */
|
|
18
|
+
shortcuts: Shortcut[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Static shortcut data — all shortcuts documented in the app. */
|
|
22
|
+
const SHORTCUT_GROUPS: ShortcutGroup[] = [
|
|
23
|
+
{
|
|
24
|
+
title: "Global",
|
|
25
|
+
shortcuts: [
|
|
26
|
+
{ keys: ["?"], description: "Open keyboard shortcuts reference" },
|
|
27
|
+
{ keys: ["N"], description: "Create a new task" },
|
|
28
|
+
{ keys: ["Escape"], description: "Close dialog or cancel editing" },
|
|
29
|
+
],
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
title: "Task Page",
|
|
33
|
+
shortcuts: [
|
|
34
|
+
{ keys: ["1"], description: "Switch to Overview tab" },
|
|
35
|
+
{ keys: ["2"], description: "Switch to Stream tab" },
|
|
36
|
+
{ keys: ["3"], description: "Switch to Findings tab" },
|
|
37
|
+
],
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
title: "Workspace Page",
|
|
41
|
+
shortcuts: [
|
|
42
|
+
{ keys: ["1"], description: "Switch to Graph view" },
|
|
43
|
+
{ keys: ["2"], description: "Switch to Board view" },
|
|
44
|
+
{ keys: ["3"], description: "Switch to Tasks view" },
|
|
45
|
+
],
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
title: "Navigation Lists",
|
|
49
|
+
shortcuts: [
|
|
50
|
+
{ keys: ["\u2190"], description: "Previous tab (horizontal nav)" },
|
|
51
|
+
{ keys: ["\u2192"], description: "Next tab (horizontal nav)" },
|
|
52
|
+
{ keys: ["\u2191"], description: "Previous item (vertical nav)" },
|
|
53
|
+
{ keys: ["\u2193"], description: "Next item (vertical nav)" },
|
|
54
|
+
{ keys: ["J"], description: "Next item (alias for arrow down/right)" },
|
|
55
|
+
{ keys: ["K"], description: "Previous item (alias for arrow up/left)" },
|
|
56
|
+
{ keys: ["Home"], description: "Jump to first item" },
|
|
57
|
+
{ keys: ["End"], description: "Jump to last item" },
|
|
58
|
+
],
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
title: "Editing",
|
|
62
|
+
shortcuts: [
|
|
63
|
+
{ keys: ["Enter"], description: "Activate / save inline edit" },
|
|
64
|
+
{ keys: ["Space"], description: "Activate button or start editing" },
|
|
65
|
+
{ keys: ["Escape"], description: "Cancel edit and discard changes" },
|
|
66
|
+
],
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
title: "Chat",
|
|
70
|
+
shortcuts: [
|
|
71
|
+
{ keys: ["Enter"], description: "Send message (when input is focused)" },
|
|
72
|
+
],
|
|
73
|
+
},
|
|
74
|
+
];
|
|
75
|
+
|
|
76
|
+
/** Settings panel listing all keyboard shortcuts grouped by category. */
|
|
77
|
+
export function KeyboardShortcutsPanel(): JSX.Element {
|
|
78
|
+
return (
|
|
79
|
+
<section className={styles.section} data-testid="keyboard-shortcuts-panel">
|
|
80
|
+
<h3 className={styles.sectionTitle}>Keyboard Shortcuts</h3>
|
|
81
|
+
<p className={styles.sectionDescription}>
|
|
82
|
+
Keyboard shortcuts for navigating and interacting with Grackle. Global shortcuts are
|
|
83
|
+
suppressed while typing in text fields.
|
|
84
|
+
</p>
|
|
85
|
+
{SHORTCUT_GROUPS.map((group) => (
|
|
86
|
+
<div key={group.title} className={shortcutStyles.group}>
|
|
87
|
+
<h4 className={shortcutStyles.groupTitle}>{group.title}</h4>
|
|
88
|
+
<div className={shortcutStyles.shortcutList}>
|
|
89
|
+
{group.shortcuts.map((shortcut) => (
|
|
90
|
+
<div key={shortcut.description} className={shortcutStyles.shortcutRow}>
|
|
91
|
+
<span className={shortcutStyles.keys}>
|
|
92
|
+
{shortcut.keys.map((k) => (
|
|
93
|
+
<kbd key={k} className={shortcutStyles.kbd}>{k}</kbd>
|
|
94
|
+
))}
|
|
95
|
+
</span>
|
|
96
|
+
<span className={shortcutStyles.description}>{shortcut.description}</span>
|
|
97
|
+
</div>
|
|
98
|
+
))}
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
))}
|
|
102
|
+
</section>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { JSX } from "react";
|
|
2
|
+
import type { PluginData } from "../../hooks/types.js";
|
|
3
|
+
import styles from "./SettingsPanel.module.scss";
|
|
4
|
+
|
|
5
|
+
/** Props for the PluginsPanel component. */
|
|
6
|
+
export interface PluginsPanelProps {
|
|
7
|
+
/** List of all known plugins. */
|
|
8
|
+
plugins: PluginData[];
|
|
9
|
+
/** Whether the plugin list is loading. */
|
|
10
|
+
loading: boolean;
|
|
11
|
+
/** Callback invoked when the user toggles a plugin's enabled state. */
|
|
12
|
+
onSetPluginEnabled: (name: string, enabled: boolean) => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Settings panel for managing Grackle plugins. */
|
|
16
|
+
export function PluginsPanel({ plugins, loading, onSetPluginEnabled }: PluginsPanelProps): JSX.Element {
|
|
17
|
+
return (
|
|
18
|
+
<div className={styles.container} data-testid="plugins-panel">
|
|
19
|
+
<h2 className={styles.heading}>Plugins</h2>
|
|
20
|
+
<div className={styles.section}>
|
|
21
|
+
<p className={styles.sectionDescription}>
|
|
22
|
+
Enable or disable optional Grackle plugins. A server restart is required for changes to take effect.
|
|
23
|
+
</p>
|
|
24
|
+
{loading && (
|
|
25
|
+
<p className={styles.emptyState}>Loading plugins...</p>
|
|
26
|
+
)}
|
|
27
|
+
{!loading && plugins.length === 0 && (
|
|
28
|
+
<p className={styles.emptyState}>No plugins found.</p>
|
|
29
|
+
)}
|
|
30
|
+
{!loading && plugins.map((plugin) => {
|
|
31
|
+
const pendingChange = plugin.enabled !== plugin.loaded;
|
|
32
|
+
return (
|
|
33
|
+
<div
|
|
34
|
+
key={plugin.name}
|
|
35
|
+
className={styles.tokenRow}
|
|
36
|
+
data-testid={`plugin-row-${plugin.name}`}
|
|
37
|
+
>
|
|
38
|
+
<div style={{ flex: 1 }}>
|
|
39
|
+
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
|
|
40
|
+
<span className={styles.tokenName}>{plugin.name}</span>
|
|
41
|
+
{plugin.required && (
|
|
42
|
+
<span className={styles.tokenBadge} title="Required — cannot be disabled">
|
|
43
|
+
required
|
|
44
|
+
</span>
|
|
45
|
+
)}
|
|
46
|
+
</div>
|
|
47
|
+
<div className={styles.tokenTarget}>{plugin.description}</div>
|
|
48
|
+
{pendingChange && (
|
|
49
|
+
<div
|
|
50
|
+
style={{ fontSize: "var(--font-size-xs)", color: "var(--accent-yellow)", marginTop: "2px" }}
|
|
51
|
+
data-testid={`plugin-restart-notice-${plugin.name}`}
|
|
52
|
+
>
|
|
53
|
+
Restart Grackle to apply changes
|
|
54
|
+
</div>
|
|
55
|
+
)}
|
|
56
|
+
</div>
|
|
57
|
+
<label
|
|
58
|
+
style={{ display: "flex", alignItems: "center", gap: "6px", cursor: plugin.required ? "not-allowed" : "pointer" }}
|
|
59
|
+
title={plugin.required ? "Core is required and cannot be disabled" : undefined}
|
|
60
|
+
>
|
|
61
|
+
<input
|
|
62
|
+
type="checkbox"
|
|
63
|
+
checked={plugin.enabled}
|
|
64
|
+
disabled={plugin.required}
|
|
65
|
+
onChange={(e) => onSetPluginEnabled(plugin.name, e.target.checked)}
|
|
66
|
+
data-testid={`plugin-toggle-${plugin.name}`}
|
|
67
|
+
aria-label={`Enable or disable ${plugin.name} plugin`}
|
|
68
|
+
style={{ accentColor: "var(--accent-green)", width: "16px", height: "16px" }}
|
|
69
|
+
/>
|
|
70
|
+
</label>
|
|
71
|
+
</div>
|
|
72
|
+
);
|
|
73
|
+
})}
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
);
|
|
77
|
+
}
|