@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.
Files changed (279) hide show
  1. package/.rush/temp/3ae72563f781afd72723475938136f113846603e.untar.log +10 -0
  2. package/.rush/temp/bc1d5bf9201ce71abeaeaddd096deb9b0805d703.untar.log +10 -0
  3. package/.rush/temp/operation/_phase_build/all.log +18 -0
  4. package/.rush/temp/operation/_phase_build/log-chunks.jsonl +18 -0
  5. package/.rush/temp/operation/_phase_build/state.json +3 -0
  6. package/.rush/temp/operation/_phase_test/all.log +121 -0
  7. package/.rush/temp/operation/_phase_test/log-chunks.jsonl +121 -0
  8. package/.rush/temp/operation/_phase_test/state.json +3 -0
  9. package/.rush/temp/shrinkwrap-deps.json +938 -0
  10. package/.storybook/main.ts +22 -0
  11. package/.storybook/preview.tsx +30 -0
  12. package/config/rig.json +4 -0
  13. package/config/rush-project.json +12 -0
  14. package/dist/index.css +1 -0
  15. package/dist/index.js +39221 -0
  16. package/eslint.config.cjs +5 -0
  17. package/package.json +83 -0
  18. package/rush-logs/web-components._phase_build.cache.log +4 -0
  19. package/rush-logs/web-components._phase_test.cache.log +4 -0
  20. package/src/components/chat/ChatInput.module.scss +81 -0
  21. package/src/components/chat/ChatInput.stories.tsx +91 -0
  22. package/src/components/chat/ChatInput.tsx +168 -0
  23. package/src/components/chat/index.ts +6 -0
  24. package/src/components/dag/DagView.module.scss +149 -0
  25. package/src/components/dag/DagView.stories.tsx +125 -0
  26. package/src/components/dag/DagView.tsx +109 -0
  27. package/src/components/dag/TaskNode.stories.tsx +133 -0
  28. package/src/components/dag/TaskNode.tsx +40 -0
  29. package/src/components/dag/useDagLayout.ts +139 -0
  30. package/src/components/display/Breadcrumbs.module.scss +71 -0
  31. package/src/components/display/Breadcrumbs.stories.tsx +80 -0
  32. package/src/components/display/Breadcrumbs.tsx +46 -0
  33. package/src/components/display/Button.module.scss +110 -0
  34. package/src/components/display/Button.stories.tsx +88 -0
  35. package/src/components/display/Button.tsx +40 -0
  36. package/src/components/display/ConfirmDialog.module.scss +67 -0
  37. package/src/components/display/ConfirmDialog.stories.tsx +81 -0
  38. package/src/components/display/ConfirmDialog.tsx +88 -0
  39. package/src/components/display/CopyButton.module.scss +41 -0
  40. package/src/components/display/CopyButton.stories.tsx +78 -0
  41. package/src/components/display/CopyButton.tsx +64 -0
  42. package/src/components/display/DemoBanner.module.scss +37 -0
  43. package/src/components/display/DemoBanner.stories.tsx +40 -0
  44. package/src/components/display/DemoBanner.tsx +23 -0
  45. package/src/components/display/EventHoverRow.module.scss +102 -0
  46. package/src/components/display/EventHoverRow.stories.tsx +99 -0
  47. package/src/components/display/EventHoverRow.tsx +154 -0
  48. package/src/components/display/EventRenderer.module.scss +272 -0
  49. package/src/components/display/EventRenderer.stories.tsx +186 -0
  50. package/src/components/display/EventRenderer.tsx +271 -0
  51. package/src/components/display/EventStream.module.scss +93 -0
  52. package/src/components/display/EventStream.stories.tsx +249 -0
  53. package/src/components/display/EventStream.tsx +369 -0
  54. package/src/components/display/FloatingActionBar.module.scss +107 -0
  55. package/src/components/display/FloatingActionBar.stories.tsx +122 -0
  56. package/src/components/display/FloatingActionBar.tsx +119 -0
  57. package/src/components/display/SessionAttemptSelector.module.scss +50 -0
  58. package/src/components/display/SessionAttemptSelector.stories.tsx +78 -0
  59. package/src/components/display/SessionAttemptSelector.tsx +49 -0
  60. package/src/components/display/SessionPicker.module.scss +200 -0
  61. package/src/components/display/SessionPicker.stories.tsx +169 -0
  62. package/src/components/display/SessionPicker.tsx +214 -0
  63. package/src/components/display/Skeleton.module.scss +58 -0
  64. package/src/components/display/Skeleton.stories.tsx +94 -0
  65. package/src/components/display/Skeleton.tsx +127 -0
  66. package/src/components/display/Spinner.module.scss +41 -0
  67. package/src/components/display/Spinner.stories.tsx +66 -0
  68. package/src/components/display/Spinner.tsx +32 -0
  69. package/src/components/display/SplashScreen.module.scss +20 -0
  70. package/src/components/display/SplashScreen.stories.tsx +26 -0
  71. package/src/components/display/SplashScreen.tsx +16 -0
  72. package/src/components/display/SplitButton.module.scss +166 -0
  73. package/src/components/display/SplitButton.stories.tsx +95 -0
  74. package/src/components/display/SplitButton.tsx +128 -0
  75. package/src/components/display/Tooltip.module.scss +84 -0
  76. package/src/components/display/Tooltip.stories.tsx +240 -0
  77. package/src/components/display/Tooltip.tsx +184 -0
  78. package/src/components/display/extractText.test.tsx +48 -0
  79. package/src/components/display/index.ts +20 -0
  80. package/src/components/editable/EditableCheckbox.stories.tsx +54 -0
  81. package/src/components/editable/EditableCheckbox.tsx +39 -0
  82. package/src/components/editable/EditableField.module.scss +135 -0
  83. package/src/components/editable/EditableSelect.tsx +164 -0
  84. package/src/components/editable/EditableTextArea.stories.tsx +50 -0
  85. package/src/components/editable/EditableTextArea.tsx +148 -0
  86. package/src/components/editable/EditableTextField.stories.tsx +62 -0
  87. package/src/components/editable/EditableTextField.tsx +153 -0
  88. package/src/components/editable/EnvironmentSelect.module.scss +17 -0
  89. package/src/components/editable/EnvironmentSelect.stories.tsx +61 -0
  90. package/src/components/editable/EnvironmentSelect.tsx +87 -0
  91. package/src/components/editable/index.ts +13 -0
  92. package/src/components/editable/useEditableField.test.tsx +233 -0
  93. package/src/components/editable/useEditableField.ts +173 -0
  94. package/src/components/index.ts +20 -0
  95. package/src/components/knowledge/KnowledgeDetailPanel.module.scss +162 -0
  96. package/src/components/knowledge/KnowledgeDetailPanel.stories.tsx +208 -0
  97. package/src/components/knowledge/KnowledgeDetailPanel.tsx +122 -0
  98. package/src/components/knowledge/KnowledgeGraph.module.scss +110 -0
  99. package/src/components/knowledge/KnowledgeGraph.stories.tsx +180 -0
  100. package/src/components/knowledge/KnowledgeGraph.tsx +455 -0
  101. package/src/components/knowledge/KnowledgeNav.module.scss +130 -0
  102. package/src/components/knowledge/KnowledgeNav.stories.tsx +108 -0
  103. package/src/components/knowledge/KnowledgeNav.tsx +138 -0
  104. package/src/components/knowledge/index.ts +3 -0
  105. package/src/components/layout/AppNav.module.scss +82 -0
  106. package/src/components/layout/AppNav.stories.tsx +115 -0
  107. package/src/components/layout/AppNav.tsx +133 -0
  108. package/src/components/layout/BottomStatusBar.module.scss +58 -0
  109. package/src/components/layout/BottomStatusBar.stories.tsx +35 -0
  110. package/src/components/layout/BottomStatusBar.tsx +206 -0
  111. package/src/components/layout/Sidebar.module.scss +60 -0
  112. package/src/components/layout/Sidebar.stories.tsx +46 -0
  113. package/src/components/layout/Sidebar.tsx +84 -0
  114. package/src/components/layout/StatusBar.module.scss +108 -0
  115. package/src/components/layout/StatusBar.stories.tsx +119 -0
  116. package/src/components/layout/StatusBar.tsx +70 -0
  117. package/src/components/layout/index.ts +9 -0
  118. package/src/components/lists/EnvironmentNav.module.scss +118 -0
  119. package/src/components/lists/EnvironmentNav.stories.tsx +121 -0
  120. package/src/components/lists/EnvironmentNav.tsx +133 -0
  121. package/src/components/lists/FindingsNav.module.scss +126 -0
  122. package/src/components/lists/FindingsNav.tsx +146 -0
  123. package/src/components/lists/TaskList.module.scss +206 -0
  124. package/src/components/lists/TaskList.stories.tsx +401 -0
  125. package/src/components/lists/TaskList.tsx +509 -0
  126. package/src/components/lists/index.ts +6 -0
  127. package/src/components/lists/listHelpers.tsx +130 -0
  128. package/src/components/notifications/Callout.module.scss +83 -0
  129. package/src/components/notifications/Callout.stories.tsx +81 -0
  130. package/src/components/notifications/Callout.tsx +64 -0
  131. package/src/components/notifications/Toast.module.scss +86 -0
  132. package/src/components/notifications/Toast.stories.tsx +71 -0
  133. package/src/components/notifications/Toast.tsx +51 -0
  134. package/src/components/notifications/ToastContainer.module.scss +23 -0
  135. package/src/components/notifications/ToastContainer.stories.tsx +66 -0
  136. package/src/components/notifications/ToastContainer.tsx +29 -0
  137. package/src/components/notifications/UpdateBanner.stories.tsx +77 -0
  138. package/src/components/notifications/UpdateBanner.test.tsx +64 -0
  139. package/src/components/notifications/UpdateBanner.tsx +44 -0
  140. package/src/components/notifications/index.ts +8 -0
  141. package/src/components/panels/AboutPanel.stories.tsx +70 -0
  142. package/src/components/panels/AboutPanel.tsx +66 -0
  143. package/src/components/panels/AppearancePanel.stories.tsx +45 -0
  144. package/src/components/panels/AppearancePanel.tsx +97 -0
  145. package/src/components/panels/CredentialProvidersPanel.stories.tsx +62 -0
  146. package/src/components/panels/CredentialProvidersPanel.tsx +111 -0
  147. package/src/components/panels/EnvironmentEditPanel.module.scss +170 -0
  148. package/src/components/panels/EnvironmentEditPanel.stories.tsx +206 -0
  149. package/src/components/panels/EnvironmentEditPanel.tsx +785 -0
  150. package/src/components/panels/FindingsPanel.module.scss +94 -0
  151. package/src/components/panels/FindingsPanel.stories.tsx +109 -0
  152. package/src/components/panels/FindingsPanel.tsx +76 -0
  153. package/src/components/panels/KeyboardShortcutsPanel.module.scss +65 -0
  154. package/src/components/panels/KeyboardShortcutsPanel.stories.tsx +40 -0
  155. package/src/components/panels/KeyboardShortcutsPanel.tsx +104 -0
  156. package/src/components/panels/PluginsPanel.tsx +77 -0
  157. package/src/components/panels/SettingsPanel.module.scss +336 -0
  158. package/src/components/panels/TaskActionButtons.module.scss +22 -0
  159. package/src/components/panels/TaskActionButtons.stories.tsx +125 -0
  160. package/src/components/panels/TaskActionButtons.tsx +87 -0
  161. package/src/components/panels/TaskEditPanel.module.scss +202 -0
  162. package/src/components/panels/TaskEditPanel.stories.tsx +75 -0
  163. package/src/components/panels/TaskEditPanel.tsx +328 -0
  164. package/src/components/panels/TaskOverviewPanel.module.scss +236 -0
  165. package/src/components/panels/TaskOverviewPanel.stories.tsx +219 -0
  166. package/src/components/panels/TaskOverviewPanel.tsx +270 -0
  167. package/src/components/panels/TokensPanel.stories.tsx +131 -0
  168. package/src/components/panels/TokensPanel.tsx +143 -0
  169. package/src/components/panels/WorkpadPanel.module.scss +39 -0
  170. package/src/components/panels/WorkpadPanel.stories.tsx +56 -0
  171. package/src/components/panels/WorkpadPanel.tsx +63 -0
  172. package/src/components/panels/index.ts +13 -0
  173. package/src/components/personas/McpToolSelector.module.scss +109 -0
  174. package/src/components/personas/McpToolSelector.stories.tsx +129 -0
  175. package/src/components/personas/McpToolSelector.tsx +180 -0
  176. package/src/components/personas/PersonaManager.module.scss +233 -0
  177. package/src/components/personas/PersonaManager.stories.tsx +139 -0
  178. package/src/components/personas/PersonaManager.tsx +122 -0
  179. package/src/components/schedules/ScheduleManager.module.scss +98 -0
  180. package/src/components/schedules/ScheduleManager.stories.tsx +78 -0
  181. package/src/components/schedules/ScheduleManager.tsx +160 -0
  182. package/src/components/settings/SettingsNav.module.scss +82 -0
  183. package/src/components/settings/SettingsNav.stories.tsx +83 -0
  184. package/src/components/settings/SettingsNav.tsx +104 -0
  185. package/src/components/streams/StreamDetailPanel.module.scss +206 -0
  186. package/src/components/streams/StreamDetailPanel.stories.tsx +132 -0
  187. package/src/components/streams/StreamDetailPanel.tsx +119 -0
  188. package/src/components/streams/StreamList.module.scss +92 -0
  189. package/src/components/streams/StreamList.stories.tsx +99 -0
  190. package/src/components/streams/StreamList.tsx +114 -0
  191. package/src/components/streams/index.ts +10 -0
  192. package/src/components/tools/AgentToolCard.module.scss +118 -0
  193. package/src/components/tools/AgentToolCard.stories.tsx +304 -0
  194. package/src/components/tools/AgentToolCard.tsx +247 -0
  195. package/src/components/tools/FileEditCard.stories.tsx +138 -0
  196. package/src/components/tools/FileEditCard.tsx +160 -0
  197. package/src/components/tools/FileReadCard.stories.tsx +120 -0
  198. package/src/components/tools/FileReadCard.tsx +106 -0
  199. package/src/components/tools/FindingCard.stories.tsx +124 -0
  200. package/src/components/tools/FindingCard.tsx +178 -0
  201. package/src/components/tools/GenericToolCard.stories.tsx +80 -0
  202. package/src/components/tools/GenericToolCard.tsx +111 -0
  203. package/src/components/tools/IpcCard.stories.tsx +129 -0
  204. package/src/components/tools/IpcCard.tsx +178 -0
  205. package/src/components/tools/KnowledgeCard.stories.tsx +112 -0
  206. package/src/components/tools/KnowledgeCard.tsx +165 -0
  207. package/src/components/tools/MetadataCard.stories.tsx +32 -0
  208. package/src/components/tools/MetadataCard.tsx +39 -0
  209. package/src/components/tools/SearchCard.stories.tsx +74 -0
  210. package/src/components/tools/SearchCard.tsx +86 -0
  211. package/src/components/tools/ShellCard.stories.tsx +112 -0
  212. package/src/components/tools/ShellCard.tsx +106 -0
  213. package/src/components/tools/TaskCard.stories.tsx +123 -0
  214. package/src/components/tools/TaskCard.tsx +203 -0
  215. package/src/components/tools/TodoCard.module.scss +131 -0
  216. package/src/components/tools/TodoCard.stories.tsx +202 -0
  217. package/src/components/tools/TodoCard.tsx +200 -0
  218. package/src/components/tools/ToolCard.stories.tsx +177 -0
  219. package/src/components/tools/ToolCard.tsx +60 -0
  220. package/src/components/tools/ToolCardProps.ts +20 -0
  221. package/src/components/tools/ToolSearchCard.stories.tsx +81 -0
  222. package/src/components/tools/ToolSearchCard.tsx +86 -0
  223. package/src/components/tools/WorkpadCard.stories.tsx +106 -0
  224. package/src/components/tools/WorkpadCard.tsx +125 -0
  225. package/src/components/tools/classifyTool.test.ts +44 -0
  226. package/src/components/tools/classifyTool.ts +134 -0
  227. package/src/components/tools/parseDiff.ts +95 -0
  228. package/src/components/tools/parseShellOutput.ts +28 -0
  229. package/src/components/tools/toolCardHelpers.test.ts +53 -0
  230. package/src/components/tools/toolCards.module.scss +234 -0
  231. package/src/components/workspace/WorkspaceBoard.module.scss +238 -0
  232. package/src/components/workspace/WorkspaceBoard.stories.tsx +240 -0
  233. package/src/components/workspace/WorkspaceBoard.tsx +232 -0
  234. package/src/components/workspace/WorkspaceFormFields.module.scss +79 -0
  235. package/src/components/workspace/WorkspaceFormFields.stories.tsx +133 -0
  236. package/src/components/workspace/WorkspaceFormFields.tsx +185 -0
  237. package/src/context/GrackleContext.ts +28 -0
  238. package/src/context/GrackleContextTypes.ts +64 -0
  239. package/src/context/SidebarContext.tsx +53 -0
  240. package/src/context/ThemeContext.tsx +21 -0
  241. package/src/context/ToastContext.tsx +56 -0
  242. package/src/hooks/types.ts +864 -0
  243. package/src/hooks/useEventSelection.test.ts +204 -0
  244. package/src/hooks/useEventSelection.ts +158 -0
  245. package/src/hooks/useSmartScroll.ts +151 -0
  246. package/src/hooks/useTheme.ts +228 -0
  247. package/src/index.ts +210 -0
  248. package/src/mocks/MockGrackleProvider.tsx +1397 -0
  249. package/src/mocks/mockData.ts +1966 -0
  250. package/src/mocks/mockKnowledgeData.ts +294 -0
  251. package/src/scss.d.ts +12 -0
  252. package/src/styles/global.scss +244 -0
  253. package/src/styles/mixins.scss +278 -0
  254. package/src/styles/prism-theme.scss +148 -0
  255. package/src/styles/theme.scss +1102 -0
  256. package/src/test-utils/storybook-decorators.tsx +50 -0
  257. package/src/test-utils/storybook-helpers.ts +262 -0
  258. package/src/themes.ts +142 -0
  259. package/src/utils/boardColumns.ts +141 -0
  260. package/src/utils/breadcrumbs.test.ts +285 -0
  261. package/src/utils/breadcrumbs.ts +222 -0
  262. package/src/utils/dashboard.test.ts +156 -0
  263. package/src/utils/dashboard.ts +195 -0
  264. package/src/utils/eventContent.test.ts +353 -0
  265. package/src/utils/eventContent.ts +209 -0
  266. package/src/utils/findingCategory.ts +33 -0
  267. package/src/utils/format.ts +27 -0
  268. package/src/utils/iconSize.ts +18 -0
  269. package/src/utils/navigation.ts +205 -0
  270. package/src/utils/route-config.test.ts +128 -0
  271. package/src/utils/scrollUtils.test.ts +65 -0
  272. package/src/utils/scrollUtils.ts +49 -0
  273. package/src/utils/sessionEvents.test.ts +302 -0
  274. package/src/utils/sessionEvents.ts +233 -0
  275. package/src/utils/taskStatus.tsx +137 -0
  276. package/src/utils/time.ts +92 -0
  277. package/tsconfig.json +8 -0
  278. package/vite.config.ts +20 -0
  279. package/vitest.config.ts +10 -0
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Storybook decorators for page-level components that call useGrackle().
3
+ */
4
+
5
+ import type { Decorator } from "@storybook/react";
6
+ import { MemoryRouter, Routes, Route } from "react-router";
7
+ import { MockGrackleProvider } from "../mocks/MockGrackleProvider.js";
8
+ import { SidebarProvider } from "../context/SidebarContext.js";
9
+
10
+ /**
11
+ * Decorator for stories that test page-level components which call useGrackle().
12
+ * Apply via `decorators: [withMockGrackle]` in the story meta.
13
+ */
14
+ export const withMockGrackle: Decorator = (Story) => (
15
+ <MockGrackleProvider>
16
+ <SidebarProvider>
17
+ <Story />
18
+ </SidebarProvider>
19
+ </MockGrackleProvider>
20
+ );
21
+
22
+ /**
23
+ * Creates a decorator that wraps the story in MockGrackleProvider + MemoryRouter
24
+ * with the given initial route entries and a catch-all Route for useParams().
25
+ *
26
+ * Use with `parameters: { skipRouter: true }` in the story meta to prevent
27
+ * the global preview.tsx from adding a second MemoryRouter.
28
+ *
29
+ * @example
30
+ * ```tsx
31
+ * const meta = {
32
+ * component: TaskPage,
33
+ * decorators: [withMockGrackleRoute(["/tasks/task-001"], "/tasks/:taskId")],
34
+ * parameters: { skipRouter: true },
35
+ * };
36
+ * ```
37
+ */
38
+ export function withMockGrackleRoute(initialEntries: string[], routePath: string = "*"): Decorator {
39
+ return (Story) => (
40
+ <MockGrackleProvider>
41
+ <MemoryRouter initialEntries={initialEntries}>
42
+ <SidebarProvider>
43
+ <Routes>
44
+ <Route path={routePath} element={<Story />} />
45
+ </Routes>
46
+ </SidebarProvider>
47
+ </MemoryRouter>
48
+ </MockGrackleProvider>
49
+ );
50
+ }
@@ -0,0 +1,262 @@
1
+ /**
2
+ * Mock data builders and helpers for Storybook stories.
3
+ *
4
+ * Provides factory functions that create realistic test data with
5
+ * sensible defaults and optional overrides.
6
+ */
7
+
8
+ import type {
9
+ Environment,
10
+ Session,
11
+ SessionEvent,
12
+ Workspace,
13
+ TaskData,
14
+ FindingData,
15
+ TokenInfo,
16
+ PersonaData,
17
+ ScheduleData,
18
+ CredentialProviderConfig,
19
+ Codespace,
20
+ } from "../hooks/types.js";
21
+ import type { GraphNode, GraphLink } from "../hooks/types.js";
22
+
23
+ export {
24
+ MOCK_ENVIRONMENTS,
25
+ MOCK_SESSIONS,
26
+ MOCK_WORKSPACES,
27
+ MOCK_TASKS,
28
+ MOCK_FINDINGS,
29
+ MOCK_TOKENS,
30
+ MOCK_PERSONAS,
31
+ } from "../mocks/mockData.js";
32
+
33
+ // ─── Factory functions ──────────────────────────────────────────────────────
34
+
35
+ let idCounter: number = 0;
36
+
37
+ /** Generate a unique ID for test entities. */
38
+ function nextId(prefix: string): string {
39
+ return `${prefix}-${++idCounter}`;
40
+ }
41
+
42
+ /** Create an Environment with sensible defaults. */
43
+ export function makeEnvironment(overrides: Partial<Environment> = {}): Environment {
44
+ return {
45
+ id: nextId("env"),
46
+ displayName: "Test Env",
47
+ adapterType: "local",
48
+ adapterConfig: "{}",
49
+ status: "connected",
50
+ bootstrapped: true,
51
+ ...overrides,
52
+ };
53
+ }
54
+
55
+ /** Create a Session with sensible defaults. */
56
+ export function makeSession(overrides: Partial<Session> = {}): Session {
57
+ return {
58
+ id: nextId("sess"),
59
+ environmentId: "env-1",
60
+ runtime: "claude-code",
61
+ status: "idle",
62
+ prompt: "Test prompt",
63
+ startedAt: "2026-01-01T00:00:00Z",
64
+ ...overrides,
65
+ };
66
+ }
67
+
68
+ /** Create a Workspace with sensible defaults. */
69
+ export function makeWorkspace(overrides: Partial<Workspace> = {}): Workspace {
70
+ return {
71
+ id: nextId("ws"),
72
+ name: "Test Workspace",
73
+ description: "",
74
+ repoUrl: "",
75
+ linkedEnvironmentIds: ["env-1"],
76
+ status: "active",
77
+ workingDirectory: "",
78
+ useWorktrees: false,
79
+ defaultPersonaId: "",
80
+ tokenBudget: 0,
81
+ costBudgetMillicents: 0,
82
+ createdAt: "2026-01-01T00:00:00Z",
83
+ updatedAt: "2026-01-01T00:00:00Z",
84
+ ...overrides,
85
+ };
86
+ }
87
+
88
+ /** Create a TaskData with sensible defaults. */
89
+ export function makeTask(overrides: Partial<TaskData> = {}): TaskData {
90
+ return {
91
+ id: nextId("task"),
92
+ workspaceId: "ws-1",
93
+ title: "Test Task",
94
+ description: "",
95
+ status: "not_started",
96
+ branch: "main",
97
+ latestSessionId: "",
98
+ dependsOn: [],
99
+ sortOrder: 0,
100
+ createdAt: "2026-01-01T00:00:00Z",
101
+ parentTaskId: "",
102
+ depth: 0,
103
+ childTaskIds: [],
104
+ canDecompose: false,
105
+ defaultPersonaId: "",
106
+ workpad: "",
107
+ tokenBudget: 0,
108
+ costBudgetMillicents: 0,
109
+ ...overrides,
110
+ };
111
+ }
112
+
113
+ /** Create a FindingData with sensible defaults. */
114
+ export function makeFinding(overrides: Partial<FindingData> = {}): FindingData {
115
+ return {
116
+ id: nextId("finding"),
117
+ workspaceId: "ws-1",
118
+ taskId: "task-1",
119
+ sessionId: "sess-1",
120
+ category: "general",
121
+ title: "Test Finding",
122
+ content: "Finding content",
123
+ tags: [],
124
+ createdAt: "2026-01-01T00:00:00Z",
125
+ ...overrides,
126
+ };
127
+ }
128
+
129
+ /** Create a TokenInfo with sensible defaults. */
130
+ export function makeToken(overrides: Partial<TokenInfo> = {}): TokenInfo {
131
+ return {
132
+ name: "TEST_TOKEN",
133
+ tokenType: "env_var",
134
+ envVar: "TEST_TOKEN",
135
+ filePath: "",
136
+ expiresAt: "",
137
+ ...overrides,
138
+ };
139
+ }
140
+
141
+ /** Create a PersonaData with sensible defaults. */
142
+ export function makePersona(overrides: Partial<PersonaData> = {}): PersonaData {
143
+ return {
144
+ id: nextId("persona"),
145
+ name: "Test Persona",
146
+ description: "A test persona",
147
+ systemPrompt: "You are a test agent.",
148
+ toolConfig: "",
149
+ runtime: "claude-code",
150
+ model: "sonnet",
151
+ maxTurns: 0,
152
+ mcpServers: "",
153
+ createdAt: "2026-01-01T00:00:00Z",
154
+ updatedAt: "2026-01-01T00:00:00Z",
155
+ type: "agent",
156
+ script: "",
157
+ allowedMcpTools: [],
158
+ ...overrides,
159
+ };
160
+ }
161
+
162
+ /** Create a ScheduleData with sensible defaults. */
163
+ export function makeSchedule(overrides: Partial<ScheduleData> = {}): ScheduleData {
164
+ return {
165
+ id: nextId("schedule"),
166
+ title: "Test Schedule",
167
+ description: "A test schedule",
168
+ scheduleExpression: "5m",
169
+ personaId: "persona-1",
170
+ workspaceId: "",
171
+ parentTaskId: "",
172
+ enabled: true,
173
+ lastRunAt: "",
174
+ nextRunAt: new Date(Date.now() + 5 * 60_000).toISOString(),
175
+ runCount: 0,
176
+ createdAt: "2026-01-01T00:00:00Z",
177
+ updatedAt: "2026-01-01T00:00:00Z",
178
+ ...overrides,
179
+ };
180
+ }
181
+
182
+ /** Create a SessionEvent with sensible defaults. */
183
+ export function makeEvent(overrides: Partial<SessionEvent> = {}): SessionEvent {
184
+ return {
185
+ sessionId: "sess-1",
186
+ eventType: "text",
187
+ timestamp: "2026-01-01T00:00:00Z",
188
+ content: "Test event content",
189
+ ...overrides,
190
+ };
191
+ }
192
+
193
+ /** Create a Codespace with sensible defaults. */
194
+ export function makeCodespace(overrides: Partial<Codespace> = {}): Codespace {
195
+ return {
196
+ name: "test-codespace",
197
+ repository: "owner/repo",
198
+ state: "Available",
199
+ gitStatus: "clean",
200
+ ...overrides,
201
+ };
202
+ }
203
+
204
+ /** Default credential provider config (all off). */
205
+ export function makeCredentialProviders(overrides: Partial<CredentialProviderConfig> = {}): CredentialProviderConfig {
206
+ return {
207
+ claude: "off",
208
+ github: "off",
209
+ copilot: "off",
210
+ codex: "off",
211
+ goose: "off",
212
+ ...overrides,
213
+ };
214
+ }
215
+
216
+ /** Create a GraphNode with sensible defaults. */
217
+ export function makeGraphNode(overrides: Partial<GraphNode> = {}): GraphNode {
218
+ return {
219
+ id: nextId("kn"),
220
+ label: "Test Knowledge Node",
221
+ kind: "knowledge",
222
+ category: "concept",
223
+ content: "Test node content.",
224
+ tags: [],
225
+ createdAt: "2026-01-15T10:00:00Z",
226
+ updatedAt: "2026-02-20T14:30:00Z",
227
+ val: 1,
228
+ ...overrides,
229
+ };
230
+ }
231
+
232
+ /** Create a GraphLink with sensible defaults. */
233
+ export function makeGraphLink(overrides: Partial<GraphLink> = {}): GraphLink {
234
+ return {
235
+ source: "kn-1",
236
+ target: "kn-2",
237
+ type: "relates_to",
238
+ ...overrides,
239
+ };
240
+ }
241
+
242
+ /** No-op callback for story args. */
243
+ export const noop = (): void => {};
244
+
245
+ // ─── Storybook decorators ───────────────────────────────────────────────────
246
+
247
+ export { withMockGrackle, withMockGrackleRoute } from "./storybook-decorators.js";
248
+
249
+ // ─── Aliases (some stories use "build" prefix) ──────────────────────────────
250
+
251
+ export const buildEnvironment: typeof makeEnvironment = makeEnvironment;
252
+ export const buildSession: typeof makeSession = makeSession;
253
+ export const buildWorkspace: typeof makeWorkspace = makeWorkspace;
254
+ export const buildTask: typeof makeTask = makeTask;
255
+ export const buildFinding: typeof makeFinding = makeFinding;
256
+ export const buildToken: typeof makeToken = makeToken;
257
+ export const buildPersona: typeof makePersona = makePersona;
258
+ export const buildEvent: typeof makeEvent = makeEvent;
259
+ export const buildCodespace: typeof makeCodespace = makeCodespace;
260
+ export const buildCredentialProviderConfig: typeof makeCredentialProviders = makeCredentialProviders;
261
+ export const buildGraphNode: typeof makeGraphNode = makeGraphNode;
262
+ export const buildGraphLink: typeof makeGraphLink = makeGraphLink;
package/src/themes.ts ADDED
@@ -0,0 +1,142 @@
1
+ // ============================================================================
2
+ // Theme Registry — Grackle Web UI
3
+ // ============================================================================
4
+ // Central registry of available themes. To add a new theme:
5
+ // 1. Add a ThemeDefinition entry to THEMES below
6
+ // 2. Add a matching [data-theme="<id>"] block in theme.scss
7
+ // 3. That's it — the Settings UI, useTheme hook, and persistence all
8
+ // pick up new entries automatically.
9
+ // ============================================================================
10
+
11
+ /** Describes a single UI theme. */
12
+ export interface ThemeDefinition {
13
+ /** Unique identifier — must match the data-theme attribute value in theme.scss. */
14
+ id: string;
15
+ /** Display name shown in Settings. */
16
+ label: string;
17
+ /** Short description shown in Settings. */
18
+ description: string;
19
+ /** Preview colors shown as swatches in Settings (bg, accent, text). */
20
+ swatches?: string[];
21
+ /** If set, this theme has a light variant toggled in Settings. */
22
+ variantLightId?: string;
23
+ /** If set, this theme has a dark variant toggled in Settings. */
24
+ variantDarkId?: string;
25
+ /** If true, this entry is a variant and should not be rendered as a standalone card. */
26
+ hidden?: boolean;
27
+ }
28
+
29
+ /**
30
+ * All available themes. Order determines display order in Settings.
31
+ * Themes with `hidden: true` are valid IDs but rendered as part of
32
+ * their parent's light/dark toggle rather than as separate cards.
33
+ */
34
+ export const THEMES: readonly ThemeDefinition[] = [
35
+ {
36
+ id: "grackle",
37
+ label: "Grackle",
38
+ description: "The default Grackle theme — iridescent purple on clean dark.",
39
+ swatches: ["#0e1218", "#8b5cf6", "#60a5fa", "#e5e7eb", "#34d399"],
40
+ variantLightId: "grackle-light",
41
+ variantDarkId: "grackle-dark",
42
+ },
43
+ {
44
+ id: "grackle-light",
45
+ label: "Grackle Light",
46
+ description: "Light Grackle variant.",
47
+ hidden: true,
48
+ },
49
+ {
50
+ id: "grackle-dark",
51
+ label: "Grackle Dark",
52
+ description: "Dark Grackle variant.",
53
+ hidden: true,
54
+ },
55
+ {
56
+ id: "glass",
57
+ label: "Glassmorphism",
58
+ description: "Dark frosted-glass aesthetic with backdrop blur effects.",
59
+ swatches: ["#0a0c14", "#4ecca3", "#70a1ff", "#e2e8f0", "#a855f7"],
60
+ },
61
+ {
62
+ id: "matrix",
63
+ label: "Matrix",
64
+ description: "Phosphor-green CRT terminal with scanlines and glow.",
65
+ swatches: ["#050505", "#00ff41", "#00bfff", "#33ff77", "#ffb000"],
66
+ },
67
+ {
68
+ id: "brutalist",
69
+ label: "Neubrutalism",
70
+ description: "Thick borders, raw colors, bold type — ugly on purpose.",
71
+ swatches: ["#f5f0e8", "#ff5757", "#5ce1e6", "#1a1a1a", "#ffde59"],
72
+ variantLightId: "brutalist-light",
73
+ variantDarkId: "brutalist-dark",
74
+ },
75
+ {
76
+ id: "brutalist-light",
77
+ label: "Neubrutalism Light",
78
+ description: "Light neubrutalism variant.",
79
+ hidden: true,
80
+ },
81
+ {
82
+ id: "brutalist-dark",
83
+ label: "Neubrutalism Dark",
84
+ description: "Dark neubrutalism variant.",
85
+ hidden: true,
86
+ },
87
+ {
88
+ id: "monokai",
89
+ label: "Monokai",
90
+ description: "Classic warm editor palette — pink, green, and purple.",
91
+ swatches: ["#272822", "#f92672", "#a6e22e", "#f8f8f2", "#ae81ff"],
92
+ variantLightId: "monokai-light",
93
+ variantDarkId: "monokai-dark",
94
+ },
95
+ {
96
+ id: "monokai-dark",
97
+ label: "Monokai Dark",
98
+ description: "Dark Monokai variant.",
99
+ hidden: true,
100
+ },
101
+ {
102
+ id: "monokai-light",
103
+ label: "Monokai Light",
104
+ description: "Light Monokai variant.",
105
+ hidden: true,
106
+ },
107
+ {
108
+ id: "ubuntu",
109
+ label: "Ubuntu",
110
+ description: "Aubergine terminal with the GNOME Tango palette.",
111
+ swatches: ["#300a24", "#8ae234", "#ef2929", "#eeeeec", "#fce94f"],
112
+ },
113
+ {
114
+ id: "sandstone",
115
+ label: "Sandstone",
116
+ description: "Warm terracotta accent on dark brown, in the style of Claude Code.",
117
+ swatches: ["#1a1815", "#C15F3C", "#6b8afd", "#e8e6e3", "#a78bfa"],
118
+ },
119
+ {
120
+ id: "verdigris",
121
+ label: "Verdigris",
122
+ description: "Teal accent on charcoal, in the style of ChatGPT.",
123
+ swatches: ["#141414", "#00a67e", "#3b82f6", "#ececec", "#ab68ff"],
124
+ },
125
+ {
126
+ id: "primer",
127
+ label: "Primer",
128
+ description: "Blue accent on ink-dark grey, in the style of GitHub.",
129
+ swatches: ["#0d1117", "#58a6ff", "#3fb950", "#c9d1d9", "#bc8cff"],
130
+ },
131
+ ] as const;
132
+
133
+ /** The set of valid theme IDs derived from the registry. */
134
+ export const THEME_IDS: ReadonlySet<string> = new Set(THEMES.map((t) => t.id));
135
+
136
+ /** The default theme ID used when nothing is persisted. */
137
+ export const DEFAULT_THEME_ID: string = "grackle";
138
+
139
+ /** Look up a theme definition by ID, returning undefined if not found. */
140
+ export function getThemeById(id: string): ThemeDefinition | undefined {
141
+ return THEMES.find((t) => t.id === id);
142
+ }
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Pure helpers for bucketing tasks into Kanban board columns.
3
+ *
4
+ * Unlike the sidebar `groupTasksByStatus()` (which adds a virtual "blocked"
5
+ * group), the board keeps blocked tasks in their actual-status column and
6
+ * overlays a badge.
7
+ */
8
+
9
+ import type { TaskData } from "../hooks/types.js";
10
+ import { BOARD_COLUMN_ORDER, getStatusStyle, resolveStatus, type TaskStatusKey, type TaskStatusStyle } from "./taskStatus.js";
11
+
12
+ // ---------------------------------------------------------------------------
13
+ // Types
14
+ // ---------------------------------------------------------------------------
15
+
16
+ /** Represents a single column on the Kanban board. */
17
+ export interface BoardColumn {
18
+ /** Canonical status key (e.g. "working"). */
19
+ status: TaskStatusKey;
20
+ /** Human-readable column heading. */
21
+ label: string;
22
+ /** Visual style for the column header. */
23
+ style: TaskStatusStyle;
24
+ /** Tasks in this column, sorted by `sortOrder`. */
25
+ tasks: BoardTask[];
26
+ }
27
+
28
+ /** A task decorated with board-specific computed properties. */
29
+ export interface BoardTask {
30
+ /** Original task data. */
31
+ task: TaskData;
32
+ /** True when the task has unresolved dependencies. */
33
+ isBlocked: boolean;
34
+ /** Number of direct child tasks. */
35
+ childCount: number;
36
+ /** Number of direct child tasks that are complete. */
37
+ doneChildCount: number;
38
+ /** Paused sub-badge label derived from latest session status. */
39
+ pausedSubBadge?: "Needs input" | "Ready to complete";
40
+ }
41
+
42
+ // ---------------------------------------------------------------------------
43
+ // Bucketing
44
+ // ---------------------------------------------------------------------------
45
+
46
+ /** Options for building board columns. */
47
+ interface BuildColumnsOptions {
48
+ /** Flat list of tasks belonging to one workspace. */
49
+ tasks: TaskData[];
50
+ /** Map of taskId → status for all tasks (used for dependency checking). */
51
+ taskStatusById: Map<string, string>;
52
+ /** Map of taskId → latest session status (used for paused sub-badges). */
53
+ sessionStatusByTaskId?: Map<string, string>;
54
+ }
55
+
56
+ /**
57
+ * Bucket tasks into fixed board columns.
58
+ *
59
+ * - Always returns all five columns (empty ones get an empty `tasks` array).
60
+ * - Resolves legacy status aliases to canonical keys.
61
+ * - Sorts tasks within each column by `sortOrder`.
62
+ * - Computes blocked state and child-progress counts.
63
+ * - Derives paused sub-badges from the latest session status.
64
+ */
65
+ export function buildBoardColumns({
66
+ tasks,
67
+ taskStatusById,
68
+ sessionStatusByTaskId,
69
+ }: BuildColumnsOptions): BoardColumn[] {
70
+ // Index children by parent
71
+ const childrenByParent = new Map<string, TaskData[]>();
72
+ for (const t of tasks) {
73
+ if (t.parentTaskId) {
74
+ const list = childrenByParent.get(t.parentTaskId);
75
+ if (list) {
76
+ list.push(t);
77
+ } else {
78
+ childrenByParent.set(t.parentTaskId, [t]);
79
+ }
80
+ }
81
+ }
82
+
83
+ // Pre-build empty buckets keyed by status
84
+ const buckets = new Map<TaskStatusKey, BoardTask[]>(
85
+ BOARD_COLUMN_ORDER.map((s) => [s, []]),
86
+ );
87
+
88
+ for (const task of tasks) {
89
+ const column = resolveStatus(task.status);
90
+ const isBlocked =
91
+ task.dependsOn.length > 0 &&
92
+ task.dependsOn.some((depId) => taskStatusById.get(depId) !== "complete");
93
+
94
+ const children = childrenByParent.get(task.id) ?? [];
95
+ const childCount = children.length;
96
+ const doneChildCount = children.filter((c) => c.status === "complete").length;
97
+
98
+ let pausedSubBadge: BoardTask["pausedSubBadge"];
99
+ if (column === "paused" && sessionStatusByTaskId) {
100
+ const sessionStatus = sessionStatusByTaskId.get(task.id);
101
+ if (sessionStatus === "idle") {
102
+ pausedSubBadge = "Needs input";
103
+ } else if (sessionStatus === "completed") {
104
+ pausedSubBadge = "Ready to complete";
105
+ }
106
+ }
107
+
108
+ const boardTask: BoardTask = {
109
+ task,
110
+ isBlocked,
111
+ childCount,
112
+ doneChildCount,
113
+ pausedSubBadge,
114
+ };
115
+
116
+ const bucket = buckets.get(column);
117
+ if (bucket) {
118
+ bucket.push(boardTask);
119
+ } else {
120
+ // Unknown status → fall back to not_started column
121
+ buckets.get("not_started")!.push(boardTask);
122
+ }
123
+ }
124
+
125
+ // Sort tasks in each bucket by sortOrder
126
+ for (const columnTasks of buckets.values()) {
127
+ columnTasks.sort((a, b) => a.task.sortOrder - b.task.sortOrder);
128
+ }
129
+
130
+ // Build final column array in display order
131
+ return BOARD_COLUMN_ORDER.map((status) => {
132
+ const style = getStatusStyle(status);
133
+
134
+ return {
135
+ status,
136
+ label: style.label,
137
+ style,
138
+ tasks: buckets.get(status) ?? [],
139
+ };
140
+ });
141
+ }