@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,233 @@
1
+ @use '../../styles/mixins' as *;
2
+
3
+ // =============================================================================
4
+ // PersonaManager — CRUD for AI personas
5
+ // =============================================================================
6
+
7
+ .container {
8
+ padding: var(--space-lg);
9
+ max-width: 800px;
10
+ color: var(--text-primary);
11
+ }
12
+
13
+ .header {
14
+ display: flex;
15
+ justify-content: space-between;
16
+ align-items: center;
17
+ margin-bottom: var(--space-lg);
18
+
19
+ h2 {
20
+ margin: 0;
21
+ font-size: var(--font-size-xl);
22
+ font-weight: var(--font-weight-bold);
23
+ }
24
+ }
25
+
26
+ .form {
27
+ @include surface-card;
28
+ padding: var(--space-lg);
29
+ margin-bottom: var(--space-lg);
30
+
31
+ h3 {
32
+ margin: 0 0 var(--space-md);
33
+ font-size: var(--font-size-lg);
34
+ font-weight: var(--font-weight-medium);
35
+ }
36
+
37
+ label {
38
+ display: block;
39
+ margin-bottom: var(--space-sm);
40
+ font-size: var(--font-size-sm);
41
+ color: var(--text-secondary);
42
+
43
+ input,
44
+ select,
45
+ textarea {
46
+ @include input-field;
47
+ display: block;
48
+ width: 100%;
49
+ margin-top: var(--space-xs);
50
+ box-sizing: border-box;
51
+ }
52
+
53
+ textarea {
54
+ resize: vertical;
55
+ font-family: var(--font-mono);
56
+ }
57
+ }
58
+ }
59
+
60
+ .formActions {
61
+ display: flex;
62
+ flex-wrap: wrap;
63
+ gap: var(--space-sm);
64
+ margin-top: var(--space-md);
65
+ }
66
+
67
+ .editableSection {
68
+ display: flex;
69
+ flex-direction: column;
70
+ gap: var(--space-md);
71
+
72
+ label {
73
+ display: flex;
74
+ flex-direction: column;
75
+ gap: var(--space-xs);
76
+ }
77
+ }
78
+
79
+ .list {
80
+ display: flex;
81
+ flex-direction: column;
82
+ gap: var(--space-sm);
83
+ }
84
+
85
+ .card {
86
+ @include surface-card;
87
+ padding: var(--space-md);
88
+
89
+ &.active {
90
+ border-color: var(--accent-green);
91
+ }
92
+ }
93
+
94
+ .cardHeader {
95
+ display: flex;
96
+ justify-content: space-between;
97
+ align-items: center;
98
+
99
+ strong {
100
+ font-size: var(--font-size-md);
101
+ color: var(--text-primary);
102
+ }
103
+ }
104
+
105
+ .cardTitle {
106
+ display: flex;
107
+ align-items: center;
108
+ gap: var(--space-sm);
109
+ }
110
+
111
+ .defaultBadge {
112
+ @include surface-inset;
113
+ padding: 2px var(--space-sm);
114
+ font-size: var(--font-size-xs);
115
+ font-weight: var(--font-weight-medium);
116
+ color: var(--accent-green);
117
+ border-radius: var(--radius-sm);
118
+ }
119
+
120
+ .cardActions {
121
+ display: flex;
122
+ gap: var(--space-xs);
123
+ }
124
+
125
+ .description {
126
+ margin: var(--space-sm) 0 0;
127
+ font-size: var(--font-size-sm);
128
+ color: var(--text-secondary);
129
+ }
130
+
131
+ .meta {
132
+ display: flex;
133
+ gap: var(--space-md);
134
+ margin-top: var(--space-sm);
135
+ font-size: var(--font-size-xs);
136
+ color: var(--text-tertiary);
137
+
138
+ span {
139
+ @include surface-inset;
140
+ padding: 2px var(--space-sm);
141
+ }
142
+ }
143
+
144
+ .promptDetails {
145
+ margin-top: var(--space-sm);
146
+
147
+ summary {
148
+ cursor: pointer;
149
+ font-size: var(--font-size-xs);
150
+ color: var(--text-tertiary);
151
+
152
+ &:hover {
153
+ color: var(--accent-green);
154
+ }
155
+ }
156
+ }
157
+
158
+ .promptText {
159
+ margin: var(--space-sm) 0 0;
160
+ @include surface-inset;
161
+ padding: var(--space-sm);
162
+ font-size: var(--font-size-xs);
163
+ font-family: var(--font-mono);
164
+ white-space: pre-wrap;
165
+ word-break: break-word;
166
+ max-height: 300px;
167
+ overflow-y: auto;
168
+ }
169
+
170
+ .empty {
171
+ text-align: center;
172
+ color: var(--text-tertiary);
173
+ padding: var(--space-xl);
174
+ }
175
+
176
+ .btnPrimary {
177
+ @include btn-primary;
178
+ font-size: var(--font-size-sm);
179
+ }
180
+
181
+ .btnSecondary {
182
+ @include btn-outline;
183
+ font-size: var(--font-size-sm);
184
+ padding: var(--space-xs) var(--space-md);
185
+ }
186
+
187
+ .btnSmall {
188
+ @include btn-ghost;
189
+ font-size: var(--font-size-xs);
190
+ padding: 2px var(--space-sm);
191
+ }
192
+
193
+ .btnDanger {
194
+ @include btn-danger;
195
+ font-size: var(--font-size-xs);
196
+ padding: 2px var(--space-sm);
197
+ }
198
+
199
+ .typeToggle {
200
+ display: flex;
201
+ gap: var(--space-md);
202
+ margin-bottom: var(--space-md);
203
+
204
+ label {
205
+ display: flex;
206
+ align-items: center;
207
+ gap: var(--space-xs);
208
+ cursor: pointer;
209
+ font-size: var(--font-size-sm);
210
+ color: var(--text-primary);
211
+ }
212
+ }
213
+
214
+ .typeBadge {
215
+ @include surface-inset;
216
+ padding: 2px var(--space-sm);
217
+ font-size: var(--font-size-xs);
218
+ font-weight: var(--font-weight-medium);
219
+ color: var(--text-tertiary);
220
+ border-radius: var(--radius-sm);
221
+ }
222
+
223
+ .scriptEditor {
224
+ font-family: var(--font-mono) !important;
225
+ font-size: var(--font-size-sm) !important;
226
+ line-height: 1.5;
227
+ tab-size: 2;
228
+ }
229
+
230
+ .optional {
231
+ font-weight: normal;
232
+ color: var(--text-tertiary);
233
+ }
@@ -0,0 +1,139 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { expect, fn, userEvent, within } from "@storybook/test";
3
+ import { PersonaManager } from "./PersonaManager.js";
4
+ import { buildPersona } from "../../test-utils/storybook-helpers.js";
5
+
6
+ const meta: Meta<typeof PersonaManager> = {
7
+ title: "Grackle/Personas/PersonaManager",
8
+ tags: ["autodocs"],
9
+ component: PersonaManager,
10
+ args: {
11
+ personas: [],
12
+ appDefaultPersonaId: "",
13
+ onDeletePersona: fn(),
14
+ onSetAppDefaultPersonaId: fn(),
15
+ onNavigateToNew: fn(),
16
+ onNavigateToPersona: fn(),
17
+ },
18
+ };
19
+
20
+ export default meta;
21
+ type Story = StoryObj<typeof PersonaManager>;
22
+
23
+ /** Empty state shows a message and a "+ New Persona" button. */
24
+ export const EmptyState: Story = {
25
+ play: async ({ canvas }) => {
26
+ await expect(
27
+ canvas.getByText("No personas yet. Create one to get started."),
28
+ ).toBeInTheDocument();
29
+ await expect(
30
+ canvas.getByRole("button", { name: "+ New Persona" }),
31
+ ).toBeInTheDocument();
32
+ },
33
+ };
34
+
35
+ /** Renders persona cards with names, type badges, and action buttons. */
36
+ export const WithPersonas: Story = {
37
+ args: {
38
+ personas: [
39
+ buildPersona({
40
+ id: "p-1",
41
+ name: "Frontend Engineer",
42
+ description: "React and TypeScript specialist",
43
+ runtime: "claude-code",
44
+ model: "sonnet",
45
+ type: "agent",
46
+ }),
47
+ buildPersona({
48
+ id: "p-2",
49
+ name: "Nightly Report",
50
+ description: "Generates daily summaries",
51
+ runtime: "genaiscript",
52
+ type: "script",
53
+ script: 'script({ model: "openai:gpt-4o" });',
54
+ }),
55
+ ],
56
+ appDefaultPersonaId: "p-1",
57
+ },
58
+ play: async ({ canvas }) => {
59
+ await expect(canvas.getByText("Frontend Engineer")).toBeInTheDocument();
60
+ await expect(canvas.getByText("Nightly Report")).toBeInTheDocument();
61
+
62
+ // Type badges
63
+ await expect(canvas.getByTestId("persona-type-badge-p-1")).toHaveTextContent("Agent");
64
+ await expect(canvas.getByTestId("persona-type-badge-p-2")).toHaveTextContent("Script");
65
+
66
+ // Default badge on p-1
67
+ await expect(canvas.getByTestId("persona-default-badge-p-1")).toBeInTheDocument();
68
+ },
69
+ };
70
+
71
+ /** Clicking "+ New Persona" calls onNavigateToNew. */
72
+ export const ClickNewPersona: Story = {
73
+ play: async ({ canvas, args }) => {
74
+ const newButton = canvas.getByRole("button", { name: "+ New Persona" });
75
+ await userEvent.click(newButton);
76
+
77
+ await expect(args.onNavigateToNew).toHaveBeenCalled();
78
+ },
79
+ };
80
+
81
+ /** Clicking a persona card calls onNavigateToPersona. */
82
+ export const ClickPersonaCard: Story = {
83
+ args: {
84
+ personas: [
85
+ buildPersona({
86
+ id: "p-edit",
87
+ name: "Code Reviewer",
88
+ description: "Reviews pull requests",
89
+ type: "agent",
90
+ }),
91
+ ],
92
+ },
93
+ play: async ({ canvas, args }) => {
94
+ const card = canvas.getByTestId("persona-card-p-edit");
95
+ await userEvent.click(card);
96
+
97
+ await expect(args.onNavigateToPersona).toHaveBeenCalledWith("p-edit");
98
+ },
99
+ };
100
+
101
+ /** Delete flow: clicking "Delete" shows a confirmation dialog. */
102
+ export const DeletePersonaFlow: Story = {
103
+ args: {
104
+ personas: [
105
+ buildPersona({ id: "p-del", name: "Disposable Persona" }),
106
+ ],
107
+ },
108
+ play: async ({ canvas, args }) => {
109
+ // Click Delete within the card actions
110
+ const deleteButton = canvas.getByTestId("persona-delete-p-del");
111
+ await userEvent.click(deleteButton);
112
+
113
+ // Confirmation dialog should appear
114
+ await expect(canvas.getByText("Delete Persona?")).toBeInTheDocument();
115
+
116
+ const dialog = canvas.getByRole("dialog", { name: "Delete Persona?" });
117
+ await userEvent.click(within(dialog).getByRole("button", { name: "Delete" }));
118
+
119
+ await expect(args.onDeletePersona).toHaveBeenCalledWith("p-del");
120
+ },
121
+ };
122
+
123
+ /** "Set Default" button is visible for non-default personas and hidden for the default. */
124
+ export const SetDefaultButton: Story = {
125
+ args: {
126
+ personas: [
127
+ buildPersona({ id: "p-default", name: "Default Persona" }),
128
+ buildPersona({ id: "p-other", name: "Other Persona" }),
129
+ ],
130
+ appDefaultPersonaId: "p-default",
131
+ },
132
+ play: async ({ canvas }) => {
133
+ // The default persona should NOT have a "Set Default" button
134
+ await expect(canvas.queryByTestId("persona-set-default-p-default")).not.toBeInTheDocument();
135
+
136
+ // The non-default persona should have a "Set Default" button
137
+ await expect(canvas.getByTestId("persona-set-default-p-other")).toBeInTheDocument();
138
+ },
139
+ };
@@ -0,0 +1,122 @@
1
+ import { useState, type JSX } from "react";
2
+ import type { PersonaData } from "../../hooks/types.js";
3
+ import { Button } from "../display/Button.js";
4
+ import { ConfirmDialog } from "../display/index.js";
5
+ import styles from "./PersonaManager.module.scss";
6
+
7
+ /** Props for the PersonaManager component. */
8
+ export interface PersonaManagerProps {
9
+ /** All personas. */
10
+ personas: PersonaData[];
11
+ /** The app-level default persona ID. */
12
+ appDefaultPersonaId: string;
13
+ /** Callback to delete a persona. */
14
+ onDeletePersona: (personaId: string) => Promise<void>;
15
+ /** Callback to set the app-level default persona. */
16
+ onSetAppDefaultPersonaId: (personaId: string) => Promise<void>;
17
+ /** Navigate to the new persona page. */
18
+ onNavigateToNew: () => void;
19
+ /** Navigate to a persona's detail page for editing. */
20
+ onNavigateToPersona: (personaId: string) => void;
21
+ }
22
+
23
+ /** Persona list view — shows cards and navigates to detail pages for create/edit. */
24
+ export function PersonaManager({
25
+ personas, appDefaultPersonaId,
26
+ onDeletePersona, onSetAppDefaultPersonaId,
27
+ onNavigateToNew, onNavigateToPersona,
28
+ }: PersonaManagerProps): JSX.Element {
29
+ const [confirmDelete, setConfirmDelete] = useState<string | null>(null);
30
+ const personaToDelete = confirmDelete ? personas.find((p) => p.id === confirmDelete) : undefined;
31
+
32
+ const handleDelete = async (id: string): Promise<void> => {
33
+ await onDeletePersona(id);
34
+ setConfirmDelete(null);
35
+ };
36
+
37
+ return (
38
+ <div className={styles.container}>
39
+ <div className={styles.header}>
40
+ <h2>Personas</h2>
41
+ <Button variant="primary" size="md" onClick={onNavigateToNew} data-testid="persona-new-button">
42
+ + New Persona
43
+ </Button>
44
+ </div>
45
+
46
+ {personas.length === 0 ? (
47
+ <p className={styles.empty}>No personas yet. Create one to get started.</p>
48
+ ) : (
49
+ <div className={styles.list}>
50
+ {personas.map((p) => {
51
+ const isAppDefault = appDefaultPersonaId === p.id;
52
+ const isScript = p.type === "script";
53
+ return (
54
+ <div
55
+ key={p.id}
56
+ className={styles.card}
57
+ data-testid={`persona-card-${p.id}`}
58
+ onClick={() => onNavigateToPersona(p.id)}
59
+ role="button"
60
+ tabIndex={0}
61
+ onKeyDown={(e) => {
62
+ if (e.currentTarget === e.target && (e.key === "Enter" || e.key === " ")) {
63
+ e.preventDefault();
64
+ onNavigateToPersona(p.id);
65
+ }
66
+ }}
67
+ >
68
+ <div className={styles.cardHeader}>
69
+ <span className={styles.cardTitle}>
70
+ <strong>{p.name}</strong>
71
+ <span className={styles.typeBadge} data-testid={`persona-type-badge-${p.id}`}>
72
+ {isScript ? "Script" : "Agent"}
73
+ </span>
74
+ {isAppDefault && (
75
+ <span className={styles.defaultBadge} data-testid={`persona-default-badge-${p.id}`}>App Default</span>
76
+ )}
77
+ </span>
78
+ <div className={styles.cardActions} onClick={(e) => e.stopPropagation()}>
79
+ {!isAppDefault && (
80
+ <Button
81
+ variant="ghost"
82
+ size="sm"
83
+ onClick={() => {
84
+ onSetAppDefaultPersonaId(p.id).catch(() => undefined);
85
+ }}
86
+ data-testid={`persona-set-default-${p.id}`}
87
+ title="Set as app default persona"
88
+ >
89
+ Set Default
90
+ </Button>
91
+ )}
92
+ <Button variant="ghost" size="sm" onClick={() => onNavigateToPersona(p.id)}>Edit</Button>
93
+ <Button variant="ghost" size="sm" onClick={() => setConfirmDelete(p.id)} data-testid={`persona-delete-${p.id}`}>Delete</Button>
94
+ </div>
95
+ </div>
96
+ {p.description && <p className={styles.description}>{p.description}</p>}
97
+ <div className={styles.meta}>
98
+ {p.runtime && <span>Runtime: {p.runtime}</span>}
99
+ {p.model && <span>Model: {p.model}</span>}
100
+ {p.maxTurns > 0 && <span>Max turns: {p.maxTurns}</span>}
101
+ </div>
102
+ </div>
103
+ );
104
+ })}
105
+ </div>
106
+ )}
107
+
108
+ <ConfirmDialog
109
+ isOpen={confirmDelete !== null}
110
+ title="Delete Persona?"
111
+ description={`"${personaToDelete?.name ?? ""}" will be permanently removed.`}
112
+ confirmLabel="Delete"
113
+ onConfirm={() => {
114
+ if (confirmDelete) {
115
+ handleDelete(confirmDelete).catch(() => undefined);
116
+ }
117
+ }}
118
+ onCancel={() => setConfirmDelete(null)}
119
+ />
120
+ </div>
121
+ );
122
+ }
@@ -0,0 +1,98 @@
1
+ @use '../../styles/mixins' as *;
2
+
3
+ // =============================================================================
4
+ // ScheduleManager — CRUD for cron schedules
5
+ // =============================================================================
6
+
7
+ .container {
8
+ padding: var(--space-lg);
9
+ max-width: 800px;
10
+ color: var(--text-primary);
11
+ }
12
+
13
+ .header {
14
+ display: flex;
15
+ justify-content: space-between;
16
+ align-items: center;
17
+ margin-bottom: var(--space-lg);
18
+
19
+ h2 {
20
+ margin: 0;
21
+ font-size: var(--font-size-xl);
22
+ font-weight: var(--font-weight-bold);
23
+ }
24
+ }
25
+
26
+ .list {
27
+ display: flex;
28
+ flex-direction: column;
29
+ gap: var(--space-sm);
30
+ }
31
+
32
+ .card {
33
+ @include surface-card;
34
+ padding: var(--space-md);
35
+ cursor: pointer;
36
+
37
+ &:hover {
38
+ border-color: var(--border-hover);
39
+ }
40
+ }
41
+
42
+ .cardHeader {
43
+ display: flex;
44
+ justify-content: space-between;
45
+ align-items: center;
46
+ }
47
+
48
+ .cardTitle {
49
+ display: flex;
50
+ align-items: center;
51
+ gap: var(--space-sm);
52
+
53
+ strong {
54
+ font-size: var(--font-size-md);
55
+ color: var(--text-primary);
56
+ }
57
+ }
58
+
59
+ .statusBadge {
60
+ @include surface-inset;
61
+ padding: 2px var(--space-sm);
62
+ font-size: var(--font-size-xs);
63
+ font-weight: var(--font-weight-medium);
64
+ border-radius: var(--radius-sm);
65
+
66
+ &.enabled {
67
+ color: var(--accent-green);
68
+ }
69
+
70
+ &.disabled {
71
+ color: var(--text-tertiary);
72
+ }
73
+ }
74
+
75
+ .cardActions {
76
+ display: flex;
77
+ gap: var(--space-xs);
78
+ }
79
+
80
+ .cardMeta {
81
+ display: flex;
82
+ flex-wrap: wrap;
83
+ gap: var(--space-md);
84
+ margin-top: var(--space-sm);
85
+ font-size: var(--font-size-xs);
86
+ color: var(--text-tertiary);
87
+
88
+ span {
89
+ @include surface-inset;
90
+ padding: 2px var(--space-sm);
91
+ }
92
+ }
93
+
94
+ .empty {
95
+ text-align: center;
96
+ color: var(--text-tertiary);
97
+ padding: var(--space-xl);
98
+ }
@@ -0,0 +1,78 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { expect, fn, userEvent, within } from "@storybook/test";
3
+ import { ScheduleManager } from "./ScheduleManager.js";
4
+ import { makeSchedule, makePersona } from "../../test-utils/storybook-helpers.js";
5
+
6
+ const MOCK_PERSONAS: ReturnType<typeof makePersona>[] = [
7
+ makePersona({ id: "persona-1", name: "Nightly Reviewer" }),
8
+ makePersona({ id: "persona-2", name: "CI Monitor" }),
9
+ ];
10
+
11
+ const meta: Meta<typeof ScheduleManager> = {
12
+ title: "Grackle/Schedules/ScheduleManager",
13
+ tags: ["autodocs"],
14
+ component: ScheduleManager,
15
+ args: {
16
+ schedules: [],
17
+ personas: MOCK_PERSONAS,
18
+ onDeleteSchedule: fn().mockResolvedValue(undefined),
19
+ onToggleEnabled: fn().mockResolvedValue(undefined),
20
+ onNavigateToNew: fn(),
21
+ onNavigateToSchedule: fn(),
22
+ },
23
+ };
24
+
25
+ export default meta;
26
+ type Story = StoryObj<typeof ScheduleManager>;
27
+
28
+ export const Empty: Story = {
29
+ args: { schedules: [] },
30
+ play: async ({ canvasElement }) => {
31
+ const canvas = within(canvasElement);
32
+ await expect(canvas.getByTestId("schedule-empty-state")).toBeInTheDocument();
33
+ },
34
+ };
35
+
36
+ export const WithSchedules: Story = {
37
+ args: {
38
+ schedules: [
39
+ makeSchedule({ id: "s1", title: "Nightly Review", scheduleExpression: "0 21 * * *", personaId: "persona-1", enabled: true, runCount: 42, lastRunAt: new Date(Date.now() - 3600_000).toISOString() }),
40
+ makeSchedule({ id: "s2", title: "CI Monitor", scheduleExpression: "5m", personaId: "persona-2", enabled: true, runCount: 8, lastRunAt: new Date(Date.now() - 300_000).toISOString() }),
41
+ makeSchedule({ id: "s3", title: "Stale PR Cleanup", scheduleExpression: "1d", personaId: "persona-1", enabled: false, runCount: 0, lastRunAt: "" }),
42
+ ],
43
+ },
44
+ play: async ({ canvasElement }) => {
45
+ const canvas = within(canvasElement);
46
+ await expect(canvas.getByTestId("schedule-card-s1")).toBeInTheDocument();
47
+ await expect(canvas.getByTestId("schedule-card-s2")).toBeInTheDocument();
48
+ await expect(canvas.getByTestId("schedule-card-s3")).toBeInTheDocument();
49
+ await expect(canvas.getByTestId("schedule-status-badge-s1")).toHaveTextContent("Enabled");
50
+ await expect(canvas.getByTestId("schedule-status-badge-s3")).toHaveTextContent("Disabled");
51
+ await expect(canvas.getByTestId("schedule-expression-s2")).toHaveTextContent("5m");
52
+ await expect(canvas.getByTestId("schedule-run-count-s1")).toHaveTextContent("42");
53
+ },
54
+ };
55
+
56
+ export const DeleteConfirmation: Story = {
57
+ args: {
58
+ schedules: [makeSchedule({ id: "del-1", title: "Delete Me" })],
59
+ },
60
+ play: async ({ canvasElement }) => {
61
+ const canvas = within(canvasElement);
62
+ await userEvent.click(canvas.getByTestId("schedule-delete-del-1"));
63
+ const dialog = document.querySelector('[role="dialog"]');
64
+ await expect(dialog).toBeInTheDocument();
65
+ },
66
+ };
67
+
68
+ export const ToggleEnabled: Story = {
69
+ args: {
70
+ schedules: [makeSchedule({ id: "tog-1", title: "Togglable", enabled: true })],
71
+ onToggleEnabled: fn().mockResolvedValue({}),
72
+ },
73
+ play: async ({ canvasElement, args }) => {
74
+ const canvas = within(canvasElement);
75
+ await userEvent.click(canvas.getByTestId("schedule-toggle-tog-1"));
76
+ await expect(args.onToggleEnabled).toHaveBeenCalledWith("tog-1", { enabled: false });
77
+ },
78
+ };