@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,206 @@
1
+ @use '../../styles/mixins' as *;
2
+
3
+ // =============================================================================
4
+ // TaskList — global task tree sidebar view
5
+ // =============================================================================
6
+
7
+ .container {
8
+ padding: var(--space-sm) 0;
9
+ }
10
+
11
+ .header {
12
+ @include section-label;
13
+ padding: var(--space-xs) var(--space-md);
14
+ display: flex;
15
+ align-items: center;
16
+ justify-content: space-between;
17
+ }
18
+
19
+ .headerActions {
20
+ display: flex;
21
+ align-items: center;
22
+ gap: var(--space-xs);
23
+ }
24
+
25
+ .searchInput {
26
+ @include input-field;
27
+ display: block;
28
+ margin: 0 var(--space-md) var(--space-xs);
29
+ font-size: 11px;
30
+ padding: 3px var(--space-sm);
31
+ width: calc(100% - 2 * var(--space-md));
32
+ }
33
+
34
+ .searchHighlight {
35
+ background: none;
36
+ color: var(--accent-green);
37
+ font-weight: 600;
38
+ }
39
+
40
+ .groupToggle {
41
+ @include btn-ghost;
42
+ font-size: var(--font-size-md);
43
+ line-height: 1;
44
+ padding: 1px 5px;
45
+ }
46
+
47
+ .groupToggleActive {
48
+ color: var(--accent-green);
49
+ }
50
+
51
+ .addButton {
52
+ @include btn-ghost;
53
+ font-size: var(--font-size-sm);
54
+ line-height: 1;
55
+ padding: 1px 5px;
56
+ }
57
+
58
+ .taskRow {
59
+ @include hover-accent;
60
+ padding: 3px var(--space-md) 3px var(--task-indent, 16px);
61
+ font-size: var(--font-size-sm);
62
+ cursor: pointer;
63
+ color: var(--text-secondary);
64
+ display: flex;
65
+ align-items: center;
66
+ gap: var(--space-sm);
67
+ border-radius: var(--radius-sm);
68
+ margin: 0 var(--space-xs);
69
+ animation: fadeIn 200ms ease forwards;
70
+
71
+ &.selected {
72
+ background: var(--bg-overlay);
73
+ color: var(--text-primary);
74
+ }
75
+
76
+ @include mobile {
77
+ padding-left: calc(var(--task-indent, 16px) * 0.6);
78
+ }
79
+ }
80
+
81
+ .expandArrow {
82
+ color: var(--text-secondary);
83
+ font-size: var(--font-size-xs);
84
+ width: 12px;
85
+ display: inline-block;
86
+ transition: transform var(--transition-fast);
87
+
88
+ &.expanded {
89
+ transform: rotate(90deg);
90
+ }
91
+ }
92
+
93
+ .leafSpacer {
94
+ width: 12px;
95
+ display: inline-block;
96
+ }
97
+
98
+ .taskStatusIcon {
99
+ font-size: 11px;
100
+ }
101
+
102
+ .taskTitle {
103
+ flex: 1;
104
+ overflow: hidden;
105
+ text-overflow: ellipsis;
106
+ white-space: nowrap;
107
+ }
108
+
109
+ .workspaceBadge {
110
+ @include surface-inset;
111
+ font-size: 10px;
112
+ color: var(--text-tertiary);
113
+ border-radius: var(--radius-full);
114
+ padding: 1px var(--space-xs);
115
+ max-width: 80px;
116
+ overflow: hidden;
117
+ text-overflow: ellipsis;
118
+ white-space: nowrap;
119
+ flex-shrink: 0;
120
+ }
121
+
122
+ .childCountBadge {
123
+ @include surface-inset;
124
+ font-size: 10px;
125
+ color: var(--text-tertiary);
126
+ border-radius: var(--radius-full);
127
+ padding: 1px var(--space-xs);
128
+ }
129
+
130
+ .dependencyBadge {
131
+ @include surface-inset;
132
+ font-size: 10px;
133
+ color: var(--text-tertiary);
134
+ border-radius: var(--radius-full);
135
+ padding: 1px var(--space-xs);
136
+
137
+ &.blockedBadge {
138
+ color: var(--accent-yellow);
139
+ border-color: var(--accent-yellow);
140
+ }
141
+ }
142
+
143
+ .addChildButton {
144
+ @include btn-ghost;
145
+ font-size: var(--font-size-xs);
146
+ line-height: 1;
147
+ padding: 1px var(--space-xs);
148
+ opacity: 0;
149
+ transition: opacity var(--transition-fast);
150
+
151
+ .taskRow:hover & {
152
+ opacity: 1;
153
+ }
154
+
155
+ .taskRow:focus-within & {
156
+ opacity: 1;
157
+ }
158
+
159
+ &:focus-visible {
160
+ opacity: 1;
161
+ }
162
+
163
+ @include mobile {
164
+ opacity: 1;
165
+ }
166
+ }
167
+
168
+ .emptyState {
169
+ padding: var(--space-md);
170
+ color: var(--text-tertiary);
171
+ font-size: var(--font-size-sm);
172
+ text-align: center;
173
+ }
174
+
175
+ // ---------------------------------------------------------------------------
176
+ // Status group accordion
177
+ // ---------------------------------------------------------------------------
178
+
179
+ .statusGroupHeader {
180
+ @include hover-accent;
181
+ padding: var(--space-sm) var(--space-md) var(--space-sm) 16px;
182
+ font-size: var(--font-size-sm);
183
+ display: flex;
184
+ align-items: center;
185
+ gap: var(--space-sm);
186
+ cursor: pointer;
187
+ border-radius: var(--radius-sm);
188
+ margin: 0 var(--space-xs);
189
+ }
190
+
191
+ .statusGroupIcon {
192
+ font-size: 11px;
193
+ }
194
+
195
+ .statusGroupLabel {
196
+ flex: 1;
197
+ font-weight: 500;
198
+ }
199
+
200
+ .statusGroupCount {
201
+ @include surface-inset;
202
+ font-size: 10px;
203
+ color: var(--text-tertiary);
204
+ border-radius: var(--radius-full);
205
+ padding: 1px var(--space-xs);
206
+ }
@@ -0,0 +1,401 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { expect, userEvent, waitFor } from "@storybook/test";
3
+ import { TaskList } from "./TaskList.js";
4
+ import type { Workspace } from "../../hooks/types.js";
5
+ import { buildTask, buildWorkspace } from "../../test-utils/storybook-helpers.js";
6
+
7
+ const WORKSPACE_ID: string = "ws-tasklist";
8
+
9
+ const defaultWorkspace: Workspace = buildWorkspace({ id: WORKSPACE_ID, name: "Test Workspace" });
10
+
11
+ const meta: Meta<typeof TaskList> = {
12
+ title: "Grackle/Lists/TaskList",
13
+ tags: ["autodocs"],
14
+ component: TaskList,
15
+ decorators: [
16
+ (Story) => {
17
+ // Clear localStorage to prevent state pollution between stories.
18
+ // TaskList persists groupByStatus and stream direction in localStorage.
19
+ localStorage.removeItem("grackle-task-group-by-status");
20
+ localStorage.removeItem("grackle-stream-direction");
21
+ return (
22
+ <div style={{ width: "300px", height: "600px", overflow: "auto" }}>
23
+ <Story />
24
+ </div>
25
+ );
26
+ },
27
+ ],
28
+ args: {
29
+ workspaces: [defaultWorkspace],
30
+ tasks: [],
31
+ },
32
+ };
33
+
34
+ export default meta;
35
+
36
+ type Story = StoryObj<typeof TaskList>;
37
+
38
+ // ---------------------------------------------------------------------------
39
+ // multi-task.spec.ts: "tasks sidebar shows multiple tasks"
40
+ // ---------------------------------------------------------------------------
41
+
42
+ /**
43
+ * Multiple tasks appear in the sidebar task list.
44
+ * Migrated from multi-task.spec.ts: "tasks sidebar shows multiple tasks".
45
+ */
46
+ export const MultipleTasks: Story = {
47
+ name: "Sidebar shows multiple tasks",
48
+ args: {
49
+ tasks: [
50
+ buildTask({ id: "t-alpha", workspaceId: WORKSPACE_ID, title: "task-alpha", sortOrder: 1 }),
51
+ buildTask({ id: "t-bravo", workspaceId: WORKSPACE_ID, title: "task-bravo", sortOrder: 2 }),
52
+ buildTask({ id: "t-charlie", workspaceId: WORKSPACE_ID, title: "task-charlie", sortOrder: 3 }),
53
+ ],
54
+ },
55
+ play: async ({ canvas }) => {
56
+ await expect(canvas.getByText("task-alpha")).toBeInTheDocument();
57
+ await expect(canvas.getByText("task-bravo")).toBeInTheDocument();
58
+ await expect(canvas.getByText("task-charlie")).toBeInTheDocument();
59
+ },
60
+ };
61
+
62
+ // ---------------------------------------------------------------------------
63
+ // group-by-status.spec.ts
64
+ // ---------------------------------------------------------------------------
65
+
66
+ /**
67
+ * Toggling the group-by-status button shows status group headers.
68
+ * Migrated from group-by-status.spec.ts: "toggle switches to grouped view with status group headers".
69
+ */
70
+ export const GroupByStatusToggle: Story = {
71
+ name: "Group-by-status toggle shows headers",
72
+ args: {
73
+ tasks: [
74
+ buildTask({ id: "t-a", workspaceId: WORKSPACE_ID, title: "task-a", status: "not_started", sortOrder: 1 }),
75
+ buildTask({ id: "t-b", workspaceId: WORKSPACE_ID, title: "task-b", status: "not_started", sortOrder: 2 }),
76
+ ],
77
+ },
78
+ play: async ({ canvas }) => {
79
+ // Click the group-by-status toggle
80
+ const toggle = canvas.getByTestId("task-group-by-status-toggle");
81
+ await userEvent.click(toggle);
82
+
83
+ // Status group header for not_started should appear
84
+ const notStartedGroup = canvas.getByTestId("status-group-not_started");
85
+ await expect(notStartedGroup).toBeInTheDocument();
86
+
87
+ // Tasks should still be visible within the group
88
+ await expect(canvas.getByText("task-a")).toBeInTheDocument();
89
+ await expect(canvas.getByText("task-b")).toBeInTheDocument();
90
+ },
91
+ };
92
+
93
+ /**
94
+ * Clicking a status group header collapses and re-expands its task list.
95
+ * Migrated from group-by-status.spec.ts: "collapse and expand a status group".
96
+ */
97
+ export const GroupCollapseExpand: Story = {
98
+ name: "Collapse and expand status group",
99
+ args: {
100
+ tasks: [
101
+ buildTask({ id: "t-collapse", workspaceId: WORKSPACE_ID, title: "collapse-task", status: "not_started", sortOrder: 1 }),
102
+ ],
103
+ },
104
+ play: async ({ canvas }) => {
105
+ // Enable group-by-status
106
+ const toggle = canvas.getByTestId("task-group-by-status-toggle");
107
+ await userEvent.click(toggle);
108
+
109
+ const groupHeader = canvas.getByTestId("status-group-not_started");
110
+ await expect(groupHeader).toBeInTheDocument();
111
+ await expect(canvas.getByText("collapse-task")).toBeInTheDocument();
112
+
113
+ // Click the header's role="button" to collapse
114
+ const collapseButton = groupHeader.querySelector('[role="button"]') as HTMLElement;
115
+ await userEvent.click(collapseButton);
116
+
117
+ // Task should be hidden after collapse (wait for exit animation to complete)
118
+ await waitFor(async () => {
119
+ await expect(canvas.queryByText("collapse-task")).not.toBeInTheDocument();
120
+ });
121
+
122
+ // Click again to expand
123
+ await userEvent.click(collapseButton);
124
+
125
+ // Task should reappear
126
+ await expect(await canvas.findByText("collapse-task")).toBeInTheDocument();
127
+ },
128
+ };
129
+
130
+ /**
131
+ * Empty status groups are not rendered (only groups with tasks are shown).
132
+ * Migrated from group-by-status.spec.ts: "empty status groups are hidden".
133
+ */
134
+ export const EmptyGroupsHidden: Story = {
135
+ name: "Empty status groups are hidden",
136
+ args: {
137
+ tasks: [
138
+ buildTask({ id: "t-only", workspaceId: WORKSPACE_ID, title: "only-not-started", status: "not_started", sortOrder: 1 }),
139
+ ],
140
+ },
141
+ play: async ({ canvas }) => {
142
+ // Enable group-by-status
143
+ const toggle = canvas.getByTestId("task-group-by-status-toggle");
144
+ await userEvent.click(toggle);
145
+
146
+ // The not_started group should exist
147
+ await expect(canvas.getByTestId("status-group-not_started")).toBeInTheDocument();
148
+
149
+ // Other groups should NOT exist (no tasks in those statuses)
150
+ await expect(canvas.queryByTestId("status-group-working")).not.toBeInTheDocument();
151
+ await expect(canvas.queryByTestId("status-group-complete")).not.toBeInTheDocument();
152
+ await expect(canvas.queryByTestId("status-group-failed")).not.toBeInTheDocument();
153
+ },
154
+ };
155
+
156
+ /**
157
+ * Toggling group-by-status off restores the tree view.
158
+ * Migrated from group-by-status.spec.ts: "toggle back restores tree view".
159
+ */
160
+ export const ToggleBackRestoresTree: Story = {
161
+ name: "Toggle off restores tree view",
162
+ args: {
163
+ tasks: [
164
+ buildTask({ id: "t-restore", workspaceId: WORKSPACE_ID, title: "restore-parent", status: "not_started", sortOrder: 1 }),
165
+ ],
166
+ },
167
+ play: async ({ canvas }) => {
168
+ const toggle = canvas.getByTestId("task-group-by-status-toggle");
169
+
170
+ // Enable grouped view
171
+ await userEvent.click(toggle);
172
+ await expect(canvas.getByTestId("status-group-not_started")).toBeInTheDocument();
173
+
174
+ // Disable grouped view
175
+ await userEvent.click(toggle);
176
+
177
+ // Status groups should be gone
178
+ await expect(canvas.queryByTestId("status-group-not_started")).not.toBeInTheDocument();
179
+
180
+ // Tree tasks should be visible
181
+ await expect(canvas.getByText("restore-parent")).toBeInTheDocument();
182
+ },
183
+ };
184
+
185
+ /**
186
+ * Tasks are navigable from grouped view (clicking a task row).
187
+ * Migrated from group-by-status.spec.ts: "task navigation from grouped view".
188
+ */
189
+ export const TaskNavigationFromGroupedView: Story = {
190
+ name: "Task navigation from grouped view",
191
+ args: {
192
+ tasks: [
193
+ buildTask({ id: "t-nav", workspaceId: WORKSPACE_ID, title: "nav-target", status: "not_started", sortOrder: 1 }),
194
+ ],
195
+ },
196
+ play: async ({ canvas }) => {
197
+ // Enable grouped view
198
+ const toggle = canvas.getByTestId("task-group-by-status-toggle");
199
+ await userEvent.click(toggle);
200
+
201
+ await expect(canvas.getByTestId("status-group-not_started")).toBeInTheDocument();
202
+
203
+ // The task should be visible and clickable
204
+ const taskElement = canvas.getByText("nav-target");
205
+ await expect(taskElement).toBeInTheDocument();
206
+ await userEvent.click(taskElement);
207
+ },
208
+ };
209
+
210
+ // ---------------------------------------------------------------------------
211
+ // keyboard interaction
212
+ // ---------------------------------------------------------------------------
213
+
214
+ /**
215
+ * Pressing Enter on a status group header toggles collapse/expand.
216
+ */
217
+ export const KeyboardToggleGroup: Story = {
218
+ name: "Enter toggles group collapse",
219
+ args: {
220
+ tasks: [
221
+ buildTask({ id: "t-kb", workspaceId: WORKSPACE_ID, title: "kb-toggle-task", status: "not_started", sortOrder: 1 }),
222
+ ],
223
+ },
224
+ play: async ({ canvas }) => {
225
+ // Enable group-by-status
226
+ const toggle = canvas.getByTestId("task-group-by-status-toggle");
227
+ await userEvent.click(toggle);
228
+
229
+ const groupHeader = canvas.getByTestId("status-group-not_started");
230
+ await expect(groupHeader).toBeInTheDocument();
231
+
232
+ // Focus the collapse button and press Enter
233
+ const collapseButton = groupHeader.querySelector('[role="button"]') as HTMLElement;
234
+ collapseButton.focus();
235
+ await userEvent.keyboard("{Enter}");
236
+
237
+ // Task should be hidden
238
+ await waitFor(async () => {
239
+ await expect(canvas.queryByText("kb-toggle-task")).not.toBeInTheDocument();
240
+ });
241
+
242
+ // Press Enter again to expand
243
+ await userEvent.keyboard("{Enter}");
244
+ await expect(await canvas.findByText("kb-toggle-task")).toBeInTheDocument();
245
+ },
246
+ };
247
+
248
+ // ---------------------------------------------------------------------------
249
+ // sidebar-search.spec.ts
250
+ // ---------------------------------------------------------------------------
251
+
252
+ /**
253
+ * The search input is visible when tasks exist.
254
+ * Migrated from sidebar-search.spec.ts: "search input is visible when tasks exist".
255
+ */
256
+ export const SearchInputVisible: Story = {
257
+ name: "Search input visible when tasks exist",
258
+ args: {
259
+ tasks: [
260
+ buildTask({ id: "t-vis", workspaceId: WORKSPACE_ID, title: "visible-task", sortOrder: 1 }),
261
+ ],
262
+ },
263
+ play: async ({ canvas }) => {
264
+ const searchInput = canvas.getByTestId("sidebar-search");
265
+ await expect(searchInput).toBeInTheDocument();
266
+ },
267
+ };
268
+
269
+ /**
270
+ * Typing in the search input filters tasks by title.
271
+ * Migrated from sidebar-search.spec.ts: "typing filters tasks by title".
272
+ */
273
+ export const SearchFiltersTasksByTitle: Story = {
274
+ name: "Typing filters tasks by title",
275
+ args: {
276
+ tasks: [
277
+ buildTask({ id: "t-alpha", workspaceId: WORKSPACE_ID, title: "alpha-task", sortOrder: 1 }),
278
+ buildTask({ id: "t-beta", workspaceId: WORKSPACE_ID, title: "beta-task", sortOrder: 2 }),
279
+ ],
280
+ },
281
+ play: async ({ canvas }) => {
282
+ const searchInput = canvas.getByTestId("sidebar-search");
283
+ await expect(searchInput).toBeInTheDocument();
284
+
285
+ // Type a filter matching only one task
286
+ await userEvent.clear(searchInput);
287
+ await userEvent.type(searchInput, "alpha");
288
+
289
+ // Only the matching task should remain.
290
+ // After search, HighlightedText splits text into <mark>/<span> fragments,
291
+ // so use the title attribute on the task title span instead of getByText.
292
+ await waitFor(async () => {
293
+ await expect(canvas.getByTitle("alpha-task")).toBeInTheDocument();
294
+ });
295
+ await expect(canvas.queryByTitle("beta-task")).not.toBeInTheDocument();
296
+ },
297
+ };
298
+
299
+ /**
300
+ * Clearing the search filter restores the full task list.
301
+ * Migrated from sidebar-search.spec.ts: "clearing filter restores full list".
302
+ */
303
+ export const ClearingFilterRestoresList: Story = {
304
+ name: "Clearing filter restores full list",
305
+ args: {
306
+ tasks: [
307
+ buildTask({ id: "t-ca", workspaceId: WORKSPACE_ID, title: "clear-alpha", sortOrder: 1 }),
308
+ buildTask({ id: "t-cb", workspaceId: WORKSPACE_ID, title: "clear-beta", sortOrder: 2 }),
309
+ ],
310
+ },
311
+ play: async ({ canvas }) => {
312
+ const searchInput = canvas.getByTestId("sidebar-search");
313
+
314
+ // Filter to one task
315
+ await userEvent.clear(searchInput);
316
+ await userEvent.type(searchInput, "clear-alpha");
317
+ await expect(canvas.queryByText("clear-beta")).not.toBeInTheDocument();
318
+
319
+ // Clear the filter
320
+ await userEvent.clear(searchInput);
321
+
322
+ // Both tasks should be visible again
323
+ await expect(canvas.getByText("clear-alpha")).toBeInTheDocument();
324
+ await expect(canvas.getByText("clear-beta")).toBeInTheDocument();
325
+ },
326
+ };
327
+
328
+ /**
329
+ * Matching text in task titles is highlighted with a <mark> element.
330
+ * Migrated from sidebar-search.spec.ts: "matching text in task titles is highlighted".
331
+ */
332
+ export const SearchHighlightsMatches: Story = {
333
+ name: "Search highlights matching text",
334
+ args: {
335
+ tasks: [
336
+ buildTask({ id: "t-hl", workspaceId: WORKSPACE_ID, title: "Fix login bug", sortOrder: 1 }),
337
+ ],
338
+ },
339
+ play: async ({ canvas }) => {
340
+ const searchInput = canvas.getByTestId("sidebar-search");
341
+
342
+ await userEvent.clear(searchInput);
343
+ await userEvent.type(searchInput, "login");
344
+
345
+ // The task should be visible with "login" highlighted in a <mark> element
346
+ const marks = canvas.getAllByText("login");
347
+ // At least one should be a <mark> element
348
+ const markElement = marks.find((el) => el.tagName === "MARK");
349
+ await expect(markElement).toBeTruthy();
350
+ },
351
+ };
352
+
353
+ // ---------------------------------------------------------------------------
354
+ // task-tree.spec.ts: breadcrumbs
355
+ // ---------------------------------------------------------------------------
356
+
357
+ /**
358
+ * Breadcrumb rendering for a nested child task shows the ancestor chain.
359
+ * Migrated from task-tree.spec.ts: "breadcrumbs show ancestor chain for nested task".
360
+ *
361
+ * NOTE: Breadcrumbs are rendered by the task detail page (not TaskList itself).
362
+ * This story instead verifies the tree structure: parent with expand arrow and
363
+ * child visible in the tree hierarchy.
364
+ */
365
+ export const TreeWithParentAndChild: Story = {
366
+ name: "Tree structure with parent and child",
367
+ args: {
368
+ tasks: [
369
+ buildTask({
370
+ id: "t-root",
371
+ workspaceId: WORKSPACE_ID,
372
+ title: "bc-root",
373
+ status: "not_started",
374
+ canDecompose: true,
375
+ childTaskIds: ["t-child"],
376
+ sortOrder: 1,
377
+ }),
378
+ buildTask({
379
+ id: "t-child",
380
+ workspaceId: WORKSPACE_ID,
381
+ title: "bc-child",
382
+ status: "not_started",
383
+ parentTaskId: "t-root",
384
+ depth: 1,
385
+ sortOrder: 1,
386
+ }),
387
+ ],
388
+ },
389
+ play: async ({ canvas }) => {
390
+ // Parent task should be visible
391
+ await expect(canvas.getByText("bc-root")).toBeInTheDocument();
392
+
393
+ // Child task should be visible (auto-expanded via useEffect, wait for async render)
394
+ await expect(await canvas.findByText("bc-child")).toBeInTheDocument();
395
+
396
+ // Parent row should show a child count badge "0/1" (0 complete out of 1)
397
+ const parentRow = canvas.getByText("bc-root").closest("[data-task-id]");
398
+ await expect(parentRow).toBeInTheDocument();
399
+ await expect(parentRow).toHaveTextContent(/0\/1/);
400
+ },
401
+ };