@grackle-ai/web-components 0.107.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.rush/temp/3ae72563f781afd72723475938136f113846603e.untar.log +10 -0
- package/.rush/temp/bc1d5bf9201ce71abeaeaddd096deb9b0805d703.untar.log +10 -0
- package/.rush/temp/operation/_phase_build/all.log +18 -0
- package/.rush/temp/operation/_phase_build/log-chunks.jsonl +18 -0
- package/.rush/temp/operation/_phase_build/state.json +3 -0
- package/.rush/temp/operation/_phase_test/all.log +121 -0
- package/.rush/temp/operation/_phase_test/log-chunks.jsonl +121 -0
- package/.rush/temp/operation/_phase_test/state.json +3 -0
- package/.rush/temp/shrinkwrap-deps.json +938 -0
- package/.storybook/main.ts +22 -0
- package/.storybook/preview.tsx +30 -0
- package/config/rig.json +4 -0
- package/config/rush-project.json +12 -0
- package/dist/index.css +1 -0
- package/dist/index.js +39221 -0
- package/eslint.config.cjs +5 -0
- package/package.json +83 -0
- package/rush-logs/web-components._phase_build.cache.log +4 -0
- package/rush-logs/web-components._phase_test.cache.log +4 -0
- package/src/components/chat/ChatInput.module.scss +81 -0
- package/src/components/chat/ChatInput.stories.tsx +91 -0
- package/src/components/chat/ChatInput.tsx +168 -0
- package/src/components/chat/index.ts +6 -0
- package/src/components/dag/DagView.module.scss +149 -0
- package/src/components/dag/DagView.stories.tsx +125 -0
- package/src/components/dag/DagView.tsx +109 -0
- package/src/components/dag/TaskNode.stories.tsx +133 -0
- package/src/components/dag/TaskNode.tsx +40 -0
- package/src/components/dag/useDagLayout.ts +139 -0
- package/src/components/display/Breadcrumbs.module.scss +71 -0
- package/src/components/display/Breadcrumbs.stories.tsx +80 -0
- package/src/components/display/Breadcrumbs.tsx +46 -0
- package/src/components/display/Button.module.scss +110 -0
- package/src/components/display/Button.stories.tsx +88 -0
- package/src/components/display/Button.tsx +40 -0
- package/src/components/display/ConfirmDialog.module.scss +67 -0
- package/src/components/display/ConfirmDialog.stories.tsx +81 -0
- package/src/components/display/ConfirmDialog.tsx +88 -0
- package/src/components/display/CopyButton.module.scss +41 -0
- package/src/components/display/CopyButton.stories.tsx +78 -0
- package/src/components/display/CopyButton.tsx +64 -0
- package/src/components/display/DemoBanner.module.scss +37 -0
- package/src/components/display/DemoBanner.stories.tsx +40 -0
- package/src/components/display/DemoBanner.tsx +23 -0
- package/src/components/display/EventHoverRow.module.scss +102 -0
- package/src/components/display/EventHoverRow.stories.tsx +99 -0
- package/src/components/display/EventHoverRow.tsx +154 -0
- package/src/components/display/EventRenderer.module.scss +272 -0
- package/src/components/display/EventRenderer.stories.tsx +186 -0
- package/src/components/display/EventRenderer.tsx +271 -0
- package/src/components/display/EventStream.module.scss +93 -0
- package/src/components/display/EventStream.stories.tsx +249 -0
- package/src/components/display/EventStream.tsx +369 -0
- package/src/components/display/FloatingActionBar.module.scss +107 -0
- package/src/components/display/FloatingActionBar.stories.tsx +122 -0
- package/src/components/display/FloatingActionBar.tsx +119 -0
- package/src/components/display/SessionAttemptSelector.module.scss +50 -0
- package/src/components/display/SessionAttemptSelector.stories.tsx +78 -0
- package/src/components/display/SessionAttemptSelector.tsx +49 -0
- package/src/components/display/SessionPicker.module.scss +200 -0
- package/src/components/display/SessionPicker.stories.tsx +169 -0
- package/src/components/display/SessionPicker.tsx +214 -0
- package/src/components/display/Skeleton.module.scss +58 -0
- package/src/components/display/Skeleton.stories.tsx +94 -0
- package/src/components/display/Skeleton.tsx +127 -0
- package/src/components/display/Spinner.module.scss +41 -0
- package/src/components/display/Spinner.stories.tsx +66 -0
- package/src/components/display/Spinner.tsx +32 -0
- package/src/components/display/SplashScreen.module.scss +20 -0
- package/src/components/display/SplashScreen.stories.tsx +26 -0
- package/src/components/display/SplashScreen.tsx +16 -0
- package/src/components/display/SplitButton.module.scss +166 -0
- package/src/components/display/SplitButton.stories.tsx +95 -0
- package/src/components/display/SplitButton.tsx +128 -0
- package/src/components/display/Tooltip.module.scss +84 -0
- package/src/components/display/Tooltip.stories.tsx +240 -0
- package/src/components/display/Tooltip.tsx +184 -0
- package/src/components/display/extractText.test.tsx +48 -0
- package/src/components/display/index.ts +20 -0
- package/src/components/editable/EditableCheckbox.stories.tsx +54 -0
- package/src/components/editable/EditableCheckbox.tsx +39 -0
- package/src/components/editable/EditableField.module.scss +135 -0
- package/src/components/editable/EditableSelect.tsx +164 -0
- package/src/components/editable/EditableTextArea.stories.tsx +50 -0
- package/src/components/editable/EditableTextArea.tsx +148 -0
- package/src/components/editable/EditableTextField.stories.tsx +62 -0
- package/src/components/editable/EditableTextField.tsx +153 -0
- package/src/components/editable/EnvironmentSelect.module.scss +17 -0
- package/src/components/editable/EnvironmentSelect.stories.tsx +61 -0
- package/src/components/editable/EnvironmentSelect.tsx +87 -0
- package/src/components/editable/index.ts +13 -0
- package/src/components/editable/useEditableField.test.tsx +233 -0
- package/src/components/editable/useEditableField.ts +173 -0
- package/src/components/index.ts +20 -0
- package/src/components/knowledge/KnowledgeDetailPanel.module.scss +162 -0
- package/src/components/knowledge/KnowledgeDetailPanel.stories.tsx +208 -0
- package/src/components/knowledge/KnowledgeDetailPanel.tsx +122 -0
- package/src/components/knowledge/KnowledgeGraph.module.scss +110 -0
- package/src/components/knowledge/KnowledgeGraph.stories.tsx +180 -0
- package/src/components/knowledge/KnowledgeGraph.tsx +455 -0
- package/src/components/knowledge/KnowledgeNav.module.scss +130 -0
- package/src/components/knowledge/KnowledgeNav.stories.tsx +108 -0
- package/src/components/knowledge/KnowledgeNav.tsx +138 -0
- package/src/components/knowledge/index.ts +3 -0
- package/src/components/layout/AppNav.module.scss +82 -0
- package/src/components/layout/AppNav.stories.tsx +115 -0
- package/src/components/layout/AppNav.tsx +133 -0
- package/src/components/layout/BottomStatusBar.module.scss +58 -0
- package/src/components/layout/BottomStatusBar.stories.tsx +35 -0
- package/src/components/layout/BottomStatusBar.tsx +206 -0
- package/src/components/layout/Sidebar.module.scss +60 -0
- package/src/components/layout/Sidebar.stories.tsx +46 -0
- package/src/components/layout/Sidebar.tsx +84 -0
- package/src/components/layout/StatusBar.module.scss +108 -0
- package/src/components/layout/StatusBar.stories.tsx +119 -0
- package/src/components/layout/StatusBar.tsx +70 -0
- package/src/components/layout/index.ts +9 -0
- package/src/components/lists/EnvironmentNav.module.scss +118 -0
- package/src/components/lists/EnvironmentNav.stories.tsx +121 -0
- package/src/components/lists/EnvironmentNav.tsx +133 -0
- package/src/components/lists/FindingsNav.module.scss +126 -0
- package/src/components/lists/FindingsNav.tsx +146 -0
- package/src/components/lists/TaskList.module.scss +206 -0
- package/src/components/lists/TaskList.stories.tsx +401 -0
- package/src/components/lists/TaskList.tsx +509 -0
- package/src/components/lists/index.ts +6 -0
- package/src/components/lists/listHelpers.tsx +130 -0
- package/src/components/notifications/Callout.module.scss +83 -0
- package/src/components/notifications/Callout.stories.tsx +81 -0
- package/src/components/notifications/Callout.tsx +64 -0
- package/src/components/notifications/Toast.module.scss +86 -0
- package/src/components/notifications/Toast.stories.tsx +71 -0
- package/src/components/notifications/Toast.tsx +51 -0
- package/src/components/notifications/ToastContainer.module.scss +23 -0
- package/src/components/notifications/ToastContainer.stories.tsx +66 -0
- package/src/components/notifications/ToastContainer.tsx +29 -0
- package/src/components/notifications/UpdateBanner.stories.tsx +77 -0
- package/src/components/notifications/UpdateBanner.test.tsx +64 -0
- package/src/components/notifications/UpdateBanner.tsx +44 -0
- package/src/components/notifications/index.ts +8 -0
- package/src/components/panels/AboutPanel.stories.tsx +70 -0
- package/src/components/panels/AboutPanel.tsx +66 -0
- package/src/components/panels/AppearancePanel.stories.tsx +45 -0
- package/src/components/panels/AppearancePanel.tsx +97 -0
- package/src/components/panels/CredentialProvidersPanel.stories.tsx +62 -0
- package/src/components/panels/CredentialProvidersPanel.tsx +111 -0
- package/src/components/panels/EnvironmentEditPanel.module.scss +170 -0
- package/src/components/panels/EnvironmentEditPanel.stories.tsx +206 -0
- package/src/components/panels/EnvironmentEditPanel.tsx +785 -0
- package/src/components/panels/FindingsPanel.module.scss +94 -0
- package/src/components/panels/FindingsPanel.stories.tsx +109 -0
- package/src/components/panels/FindingsPanel.tsx +76 -0
- package/src/components/panels/KeyboardShortcutsPanel.module.scss +65 -0
- package/src/components/panels/KeyboardShortcutsPanel.stories.tsx +40 -0
- package/src/components/panels/KeyboardShortcutsPanel.tsx +104 -0
- package/src/components/panels/PluginsPanel.tsx +77 -0
- package/src/components/panels/SettingsPanel.module.scss +336 -0
- package/src/components/panels/TaskActionButtons.module.scss +22 -0
- package/src/components/panels/TaskActionButtons.stories.tsx +125 -0
- package/src/components/panels/TaskActionButtons.tsx +87 -0
- package/src/components/panels/TaskEditPanel.module.scss +202 -0
- package/src/components/panels/TaskEditPanel.stories.tsx +75 -0
- package/src/components/panels/TaskEditPanel.tsx +328 -0
- package/src/components/panels/TaskOverviewPanel.module.scss +236 -0
- package/src/components/panels/TaskOverviewPanel.stories.tsx +219 -0
- package/src/components/panels/TaskOverviewPanel.tsx +270 -0
- package/src/components/panels/TokensPanel.stories.tsx +131 -0
- package/src/components/panels/TokensPanel.tsx +143 -0
- package/src/components/panels/WorkpadPanel.module.scss +39 -0
- package/src/components/panels/WorkpadPanel.stories.tsx +56 -0
- package/src/components/panels/WorkpadPanel.tsx +63 -0
- package/src/components/panels/index.ts +13 -0
- package/src/components/personas/McpToolSelector.module.scss +109 -0
- package/src/components/personas/McpToolSelector.stories.tsx +129 -0
- package/src/components/personas/McpToolSelector.tsx +180 -0
- package/src/components/personas/PersonaManager.module.scss +233 -0
- package/src/components/personas/PersonaManager.stories.tsx +139 -0
- package/src/components/personas/PersonaManager.tsx +122 -0
- package/src/components/schedules/ScheduleManager.module.scss +98 -0
- package/src/components/schedules/ScheduleManager.stories.tsx +78 -0
- package/src/components/schedules/ScheduleManager.tsx +160 -0
- package/src/components/settings/SettingsNav.module.scss +82 -0
- package/src/components/settings/SettingsNav.stories.tsx +83 -0
- package/src/components/settings/SettingsNav.tsx +104 -0
- package/src/components/streams/StreamDetailPanel.module.scss +206 -0
- package/src/components/streams/StreamDetailPanel.stories.tsx +132 -0
- package/src/components/streams/StreamDetailPanel.tsx +119 -0
- package/src/components/streams/StreamList.module.scss +92 -0
- package/src/components/streams/StreamList.stories.tsx +99 -0
- package/src/components/streams/StreamList.tsx +114 -0
- package/src/components/streams/index.ts +10 -0
- package/src/components/tools/AgentToolCard.module.scss +118 -0
- package/src/components/tools/AgentToolCard.stories.tsx +304 -0
- package/src/components/tools/AgentToolCard.tsx +247 -0
- package/src/components/tools/FileEditCard.stories.tsx +138 -0
- package/src/components/tools/FileEditCard.tsx +160 -0
- package/src/components/tools/FileReadCard.stories.tsx +120 -0
- package/src/components/tools/FileReadCard.tsx +106 -0
- package/src/components/tools/FindingCard.stories.tsx +124 -0
- package/src/components/tools/FindingCard.tsx +178 -0
- package/src/components/tools/GenericToolCard.stories.tsx +80 -0
- package/src/components/tools/GenericToolCard.tsx +111 -0
- package/src/components/tools/IpcCard.stories.tsx +129 -0
- package/src/components/tools/IpcCard.tsx +178 -0
- package/src/components/tools/KnowledgeCard.stories.tsx +112 -0
- package/src/components/tools/KnowledgeCard.tsx +165 -0
- package/src/components/tools/MetadataCard.stories.tsx +32 -0
- package/src/components/tools/MetadataCard.tsx +39 -0
- package/src/components/tools/SearchCard.stories.tsx +74 -0
- package/src/components/tools/SearchCard.tsx +86 -0
- package/src/components/tools/ShellCard.stories.tsx +112 -0
- package/src/components/tools/ShellCard.tsx +106 -0
- package/src/components/tools/TaskCard.stories.tsx +123 -0
- package/src/components/tools/TaskCard.tsx +203 -0
- package/src/components/tools/TodoCard.module.scss +131 -0
- package/src/components/tools/TodoCard.stories.tsx +202 -0
- package/src/components/tools/TodoCard.tsx +200 -0
- package/src/components/tools/ToolCard.stories.tsx +177 -0
- package/src/components/tools/ToolCard.tsx +60 -0
- package/src/components/tools/ToolCardProps.ts +20 -0
- package/src/components/tools/ToolSearchCard.stories.tsx +81 -0
- package/src/components/tools/ToolSearchCard.tsx +86 -0
- package/src/components/tools/WorkpadCard.stories.tsx +106 -0
- package/src/components/tools/WorkpadCard.tsx +125 -0
- package/src/components/tools/classifyTool.test.ts +44 -0
- package/src/components/tools/classifyTool.ts +134 -0
- package/src/components/tools/parseDiff.ts +95 -0
- package/src/components/tools/parseShellOutput.ts +28 -0
- package/src/components/tools/toolCardHelpers.test.ts +53 -0
- package/src/components/tools/toolCards.module.scss +234 -0
- package/src/components/workspace/WorkspaceBoard.module.scss +238 -0
- package/src/components/workspace/WorkspaceBoard.stories.tsx +240 -0
- package/src/components/workspace/WorkspaceBoard.tsx +232 -0
- package/src/components/workspace/WorkspaceFormFields.module.scss +79 -0
- package/src/components/workspace/WorkspaceFormFields.stories.tsx +133 -0
- package/src/components/workspace/WorkspaceFormFields.tsx +185 -0
- package/src/context/GrackleContext.ts +28 -0
- package/src/context/GrackleContextTypes.ts +64 -0
- package/src/context/SidebarContext.tsx +53 -0
- package/src/context/ThemeContext.tsx +21 -0
- package/src/context/ToastContext.tsx +56 -0
- package/src/hooks/types.ts +864 -0
- package/src/hooks/useEventSelection.test.ts +204 -0
- package/src/hooks/useEventSelection.ts +158 -0
- package/src/hooks/useSmartScroll.ts +151 -0
- package/src/hooks/useTheme.ts +228 -0
- package/src/index.ts +210 -0
- package/src/mocks/MockGrackleProvider.tsx +1397 -0
- package/src/mocks/mockData.ts +1966 -0
- package/src/mocks/mockKnowledgeData.ts +294 -0
- package/src/scss.d.ts +12 -0
- package/src/styles/global.scss +244 -0
- package/src/styles/mixins.scss +278 -0
- package/src/styles/prism-theme.scss +148 -0
- package/src/styles/theme.scss +1102 -0
- package/src/test-utils/storybook-decorators.tsx +50 -0
- package/src/test-utils/storybook-helpers.ts +262 -0
- package/src/themes.ts +142 -0
- package/src/utils/boardColumns.ts +141 -0
- package/src/utils/breadcrumbs.test.ts +285 -0
- package/src/utils/breadcrumbs.ts +222 -0
- package/src/utils/dashboard.test.ts +156 -0
- package/src/utils/dashboard.ts +195 -0
- package/src/utils/eventContent.test.ts +353 -0
- package/src/utils/eventContent.ts +209 -0
- package/src/utils/findingCategory.ts +33 -0
- package/src/utils/format.ts +27 -0
- package/src/utils/iconSize.ts +18 -0
- package/src/utils/navigation.ts +205 -0
- package/src/utils/route-config.test.ts +128 -0
- package/src/utils/scrollUtils.test.ts +65 -0
- package/src/utils/scrollUtils.ts +49 -0
- package/src/utils/sessionEvents.test.ts +302 -0
- package/src/utils/sessionEvents.ts +233 -0
- package/src/utils/taskStatus.tsx +137 -0
- package/src/utils/time.ts +92 -0
- package/tsconfig.json +8 -0
- package/vite.config.ts +20 -0
- package/vitest.config.ts +10 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
@use '../../styles/mixins' as *;
|
|
2
|
+
|
|
3
|
+
// =============================================================================
|
|
4
|
+
// Task Edit Panel — full-panel create/edit form for tasks
|
|
5
|
+
// =============================================================================
|
|
6
|
+
|
|
7
|
+
.container {
|
|
8
|
+
flex: 1;
|
|
9
|
+
display: flex;
|
|
10
|
+
flex-direction: column;
|
|
11
|
+
overflow: hidden;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Header
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
.header {
|
|
19
|
+
@include surface-panel;
|
|
20
|
+
padding: var(--space-sm) var(--space-md);
|
|
21
|
+
border-bottom: 1px solid var(--border-subtle);
|
|
22
|
+
display: flex;
|
|
23
|
+
align-items: center;
|
|
24
|
+
justify-content: space-between;
|
|
25
|
+
gap: var(--space-md);
|
|
26
|
+
flex-shrink: 0;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.headerTitle {
|
|
30
|
+
font-size: var(--font-size-sm);
|
|
31
|
+
color: var(--text-secondary);
|
|
32
|
+
font-family: var(--font-mono);
|
|
33
|
+
display: flex;
|
|
34
|
+
align-items: center;
|
|
35
|
+
gap: var(--space-sm);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.badge {
|
|
39
|
+
background: var(--accent-green-dim);
|
|
40
|
+
color: var(--accent-green);
|
|
41
|
+
border: 1px solid var(--accent-green);
|
|
42
|
+
border-radius: var(--radius-full);
|
|
43
|
+
padding: 1px var(--space-sm);
|
|
44
|
+
font-size: var(--font-size-xs);
|
|
45
|
+
font-family: var(--font-mono);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.headerActions {
|
|
49
|
+
display: flex;
|
|
50
|
+
align-items: center;
|
|
51
|
+
gap: var(--space-sm);
|
|
52
|
+
flex-shrink: 0;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
// Form body
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
|
|
59
|
+
.body {
|
|
60
|
+
flex: 1;
|
|
61
|
+
overflow: auto;
|
|
62
|
+
padding: var(--space-lg);
|
|
63
|
+
width: 100%;
|
|
64
|
+
|
|
65
|
+
@include mobile {
|
|
66
|
+
padding: var(--space-md);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.formContent {
|
|
71
|
+
display: flex;
|
|
72
|
+
flex-direction: column;
|
|
73
|
+
gap: var(--space-lg);
|
|
74
|
+
max-width: 680px;
|
|
75
|
+
|
|
76
|
+
@include mobile {
|
|
77
|
+
max-width: 100%;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.section {
|
|
82
|
+
display: flex;
|
|
83
|
+
flex-direction: column;
|
|
84
|
+
gap: var(--space-sm);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.label {
|
|
88
|
+
font-size: 11px;
|
|
89
|
+
color: var(--text-tertiary);
|
|
90
|
+
text-transform: uppercase;
|
|
91
|
+
letter-spacing: 0.05em;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.titleInput {
|
|
95
|
+
@include input-field;
|
|
96
|
+
font-size: var(--font-size-lg);
|
|
97
|
+
font-weight: var(--font-weight-bold);
|
|
98
|
+
color: var(--text-primary);
|
|
99
|
+
padding: var(--space-sm) var(--space-md);
|
|
100
|
+
width: 100%;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.descriptionTextarea {
|
|
104
|
+
@include input-field;
|
|
105
|
+
font-size: var(--font-size-sm);
|
|
106
|
+
color: var(--text-secondary);
|
|
107
|
+
resize: vertical;
|
|
108
|
+
min-height: 160px;
|
|
109
|
+
padding: var(--space-sm) var(--space-md);
|
|
110
|
+
width: 100%;
|
|
111
|
+
font-family: var(--font-mono);
|
|
112
|
+
line-height: 1.5;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.parentContext {
|
|
116
|
+
display: flex;
|
|
117
|
+
align-items: center;
|
|
118
|
+
gap: var(--space-sm);
|
|
119
|
+
font-size: var(--font-size-sm);
|
|
120
|
+
color: var(--text-tertiary);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.parentLabel {
|
|
124
|
+
color: var(--text-tertiary);
|
|
125
|
+
font-size: var(--font-size-xs);
|
|
126
|
+
text-transform: uppercase;
|
|
127
|
+
letter-spacing: 0.05em;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.parentName {
|
|
131
|
+
color: var(--text-secondary);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ---------------------------------------------------------------------------
|
|
135
|
+
// Persona select
|
|
136
|
+
// ---------------------------------------------------------------------------
|
|
137
|
+
|
|
138
|
+
.personaSelect {
|
|
139
|
+
@include input-field;
|
|
140
|
+
font-size: var(--font-size-sm);
|
|
141
|
+
color: var(--text-secondary);
|
|
142
|
+
padding: var(--space-xs) var(--space-sm);
|
|
143
|
+
width: 100%;
|
|
144
|
+
max-width: 320px;
|
|
145
|
+
cursor: pointer;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// ---------------------------------------------------------------------------
|
|
149
|
+
// Dependencies multi-select
|
|
150
|
+
// ---------------------------------------------------------------------------
|
|
151
|
+
|
|
152
|
+
.depList {
|
|
153
|
+
display: flex;
|
|
154
|
+
flex-direction: column;
|
|
155
|
+
gap: var(--space-xs);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.depItem {
|
|
159
|
+
display: flex;
|
|
160
|
+
align-items: center;
|
|
161
|
+
gap: var(--space-sm);
|
|
162
|
+
font-size: var(--font-size-sm);
|
|
163
|
+
cursor: pointer;
|
|
164
|
+
padding: var(--space-xs) var(--space-sm);
|
|
165
|
+
border-radius: var(--radius-sm);
|
|
166
|
+
transition: background var(--transition-fast);
|
|
167
|
+
color: var(--text-secondary);
|
|
168
|
+
user-select: none;
|
|
169
|
+
|
|
170
|
+
&:hover {
|
|
171
|
+
background: var(--bg-overlay);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
input[type="checkbox"] {
|
|
175
|
+
accent-color: var(--accent-green);
|
|
176
|
+
width: 14px;
|
|
177
|
+
height: 14px;
|
|
178
|
+
flex-shrink: 0;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.depItemSelected {
|
|
183
|
+
color: var(--accent-green);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.noDeps {
|
|
187
|
+
font-size: var(--font-size-sm);
|
|
188
|
+
color: var(--text-tertiary);
|
|
189
|
+
font-style: italic;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// ---------------------------------------------------------------------------
|
|
193
|
+
// Buttons
|
|
194
|
+
// ---------------------------------------------------------------------------
|
|
195
|
+
|
|
196
|
+
.btnPrimary {
|
|
197
|
+
@include btn-primary;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.btnGhost {
|
|
201
|
+
@include btn-ghost(var(--text-secondary));
|
|
202
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { expect, fn } from "@storybook/test";
|
|
3
|
+
import type { Workspace } from "../../hooks/types.js";
|
|
4
|
+
import { buildWorkspace, buildPersona } from "../../test-utils/storybook-helpers.js";
|
|
5
|
+
import { TaskEditPanel } from "./TaskEditPanel.js";
|
|
6
|
+
|
|
7
|
+
const defaultWorkspace: Workspace = buildWorkspace({ id: "ws-001", name: "Test Workspace" });
|
|
8
|
+
|
|
9
|
+
const meta: Meta<typeof TaskEditPanel> = {
|
|
10
|
+
title: "App/Panels/TaskEditPanel",
|
|
11
|
+
component: TaskEditPanel,
|
|
12
|
+
args: {
|
|
13
|
+
mode: "new",
|
|
14
|
+
workspaceId: "ws-001",
|
|
15
|
+
tasks: [],
|
|
16
|
+
workspaces: [defaultWorkspace],
|
|
17
|
+
personas: [buildPersona({ id: "p-1", name: "Coder" })],
|
|
18
|
+
onCreateTask: fn(),
|
|
19
|
+
onUpdateTask: fn(),
|
|
20
|
+
onShowToast: fn(),
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export default meta;
|
|
25
|
+
|
|
26
|
+
type Story = StoryObj<typeof TaskEditPanel>;
|
|
27
|
+
|
|
28
|
+
/** New task form shows title and description fields plus a disabled Create button. */
|
|
29
|
+
export const FormFieldsVisible: Story = {
|
|
30
|
+
play: async ({ canvas }) => {
|
|
31
|
+
// Title input
|
|
32
|
+
const titleInput = canvas.getByTestId("task-edit-title");
|
|
33
|
+
await expect(titleInput).toBeInTheDocument();
|
|
34
|
+
|
|
35
|
+
// Description textarea
|
|
36
|
+
const descriptionInput = canvas.getByTestId("task-edit-description");
|
|
37
|
+
await expect(descriptionInput).toBeInTheDocument();
|
|
38
|
+
|
|
39
|
+
// Save/Create button present and disabled (no title entered)
|
|
40
|
+
const saveButton = canvas.getByTestId("task-edit-save");
|
|
41
|
+
await expect(saveButton).toBeInTheDocument();
|
|
42
|
+
await expect(saveButton).toBeDisabled();
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/** The Cancel button is visible in the new task form. */
|
|
47
|
+
export const CancelButtonVisible: Story = {
|
|
48
|
+
play: async ({ canvas }) => {
|
|
49
|
+
await expect(canvas.getByTestId("task-edit-title")).toBeInTheDocument();
|
|
50
|
+
|
|
51
|
+
const cancelButton = canvas.getByRole("button", { name: "Cancel" });
|
|
52
|
+
await expect(cancelButton).toBeInTheDocument();
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* The task creation form has no environment dropdown -- environment is
|
|
58
|
+
* assigned at start time, not creation time.
|
|
59
|
+
*/
|
|
60
|
+
export const NoEnvironmentDropdown: Story = {
|
|
61
|
+
play: async ({ canvas }) => {
|
|
62
|
+
await expect(canvas.getByTestId("task-edit-title")).toBeInTheDocument();
|
|
63
|
+
|
|
64
|
+
// There should be no element mentioning "Default env" or "test-local"
|
|
65
|
+
// as environment options in the form.
|
|
66
|
+
const allSelects = canvas.getAllByRole("combobox");
|
|
67
|
+
for (const select of allSelects) {
|
|
68
|
+
// None of the selects should contain environment-like options
|
|
69
|
+
const options = select.querySelectorAll("option");
|
|
70
|
+
for (const option of options) {
|
|
71
|
+
await expect(option.textContent ?? "").not.toMatch(/Default env|test-local/);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
};
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
import { useState, useEffect, useRef, type JSX } from "react";
|
|
2
|
+
import type { ToastVariant } from "../../context/ToastContext.js";
|
|
3
|
+
import type { TaskData, Workspace, PersonaData } from "../../hooks/types.js";
|
|
4
|
+
import { taskUrl, workspaceUrl, useAppNavigate } from "../../utils/navigation.js";
|
|
5
|
+
import styles from "./TaskEditPanel.module.scss";
|
|
6
|
+
|
|
7
|
+
/** Props for the TaskEditPanel component. */
|
|
8
|
+
interface Props {
|
|
9
|
+
mode: "new" | "edit";
|
|
10
|
+
/** Task ID — required in edit mode. */
|
|
11
|
+
taskId?: string;
|
|
12
|
+
/** Workspace ID — required in new mode. */
|
|
13
|
+
workspaceId?: string;
|
|
14
|
+
/** Parent task ID — optional in new mode. */
|
|
15
|
+
parentTaskId?: string;
|
|
16
|
+
/** Environment ID — used for scoped navigation. */
|
|
17
|
+
environmentId?: string;
|
|
18
|
+
/** All tasks (used for lookups and sibling dependency list). */
|
|
19
|
+
tasks: TaskData[];
|
|
20
|
+
/** All workspaces (used for workspace selector and environment resolution). */
|
|
21
|
+
workspaces: Workspace[];
|
|
22
|
+
/** All personas (used for persona dropdown). */
|
|
23
|
+
personas: PersonaData[];
|
|
24
|
+
/** Callback to create a new task. */
|
|
25
|
+
onCreateTask: (
|
|
26
|
+
workspaceId: string, title: string, description?: string,
|
|
27
|
+
dependsOn?: string[], parentTaskId?: string, defaultPersonaId?: string,
|
|
28
|
+
canDecompose?: boolean, onSuccess?: () => void, onError?: (message: string) => void,
|
|
29
|
+
) => void;
|
|
30
|
+
/** Callback to update an existing task. */
|
|
31
|
+
onUpdateTask: (
|
|
32
|
+
taskId: string, title: string, description: string,
|
|
33
|
+
dependsOn: string[], defaultPersonaId?: string,
|
|
34
|
+
) => void;
|
|
35
|
+
/** Optional callback invoked when inline edit completes (save or cancel). When provided, navigation is skipped. */
|
|
36
|
+
onEditDone?: () => void;
|
|
37
|
+
/** Display a toast notification. */
|
|
38
|
+
onShowToast?: (message: string, variant?: ToastVariant) => void;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Full-panel create/edit form for tasks.
|
|
43
|
+
*
|
|
44
|
+
* - new: blank form; calls createTask on save, then navigates back to
|
|
45
|
+
* the workspace view.
|
|
46
|
+
* - edit: pre-populated form; calls updateTask on save, then navigates
|
|
47
|
+
* back to the task overview.
|
|
48
|
+
*/
|
|
49
|
+
export function TaskEditPanel({ mode, taskId, workspaceId: workspaceIdProp, parentTaskId: parentTaskIdProp, environmentId: environmentIdProp, tasks, workspaces, personas, onCreateTask, onUpdateTask, onEditDone, onShowToast }: Props): JSX.Element {
|
|
50
|
+
const navigate = useAppNavigate();
|
|
51
|
+
|
|
52
|
+
const isEdit = mode === "edit";
|
|
53
|
+
const existingTask = isEdit && taskId ? tasks.find((t) => t.id === taskId) : undefined;
|
|
54
|
+
|
|
55
|
+
const initialWorkspaceId = isEdit
|
|
56
|
+
? (existingTask?.workspaceId ?? "")
|
|
57
|
+
: (workspaceIdProp ?? "");
|
|
58
|
+
|
|
59
|
+
const [selectedWorkspaceId, setSelectedWorkspaceId] = useState(initialWorkspaceId);
|
|
60
|
+
const workspaceId = initialWorkspaceId || selectedWorkspaceId;
|
|
61
|
+
|
|
62
|
+
/** Resolve environmentId from prop, or from the workspace's first linked environment. */
|
|
63
|
+
const resolvedWorkspace = workspaces.find((w) => w.id === workspaceId);
|
|
64
|
+
const environmentId = environmentIdProp ?? resolvedWorkspace?.linkedEnvironmentIds[0];
|
|
65
|
+
|
|
66
|
+
/** Whether the workspace dropdown should be shown (new mode without pre-set workspace). */
|
|
67
|
+
const showWorkspaceSelector = !isEdit && !workspaceIdProp;
|
|
68
|
+
|
|
69
|
+
const parentTaskId = isEdit
|
|
70
|
+
? (existingTask?.parentTaskId ?? "")
|
|
71
|
+
: (parentTaskIdProp ?? "");
|
|
72
|
+
|
|
73
|
+
const parentTask = parentTaskId ? tasks.find((t) => t.id === parentTaskId) : undefined;
|
|
74
|
+
|
|
75
|
+
const [title, setTitle] = useState(existingTask?.title ?? "");
|
|
76
|
+
const [description, setDescription] = useState(existingTask?.description ?? "");
|
|
77
|
+
const [selectedDeps, setSelectedDeps] = useState<string[]>(existingTask?.dependsOn ?? []);
|
|
78
|
+
const [defaultPersonaId, setDefaultPersonaId] = useState(existingTask?.defaultPersonaId ?? "");
|
|
79
|
+
const [canDecompose, setCanDecompose] = useState(existingTask?.canDecompose ?? false);
|
|
80
|
+
const [creating, setCreating] = useState(false);
|
|
81
|
+
|
|
82
|
+
// In edit mode, tasks may not have loaded yet at mount time. Sync form state
|
|
83
|
+
// the first time existingTask becomes available so the form is pre-populated,
|
|
84
|
+
// but do not re-apply on subsequent refreshes — that would discard in-progress
|
|
85
|
+
// user edits whenever a background update replaces the task object.
|
|
86
|
+
const hasHydratedRef = useRef(false);
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
if (isEdit && existingTask && !hasHydratedRef.current) {
|
|
89
|
+
hasHydratedRef.current = true;
|
|
90
|
+
setTitle(existingTask.title);
|
|
91
|
+
setDescription(existingTask.description);
|
|
92
|
+
setSelectedDeps(existingTask.dependsOn);
|
|
93
|
+
setDefaultPersonaId(existingTask.defaultPersonaId);
|
|
94
|
+
setCanDecompose(existingTask.canDecompose);
|
|
95
|
+
}
|
|
96
|
+
}, [isEdit, existingTask]);
|
|
97
|
+
|
|
98
|
+
// All tasks in the same workspace, excluding the task being edited (self)
|
|
99
|
+
const siblingTasks = tasks.filter(
|
|
100
|
+
(t) =>
|
|
101
|
+
t.workspaceId === workspaceId &&
|
|
102
|
+
(!isEdit || t.id !== taskId) &&
|
|
103
|
+
t.id !== parentTaskId,
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
// In edit mode, also require that task data has loaded before allowing save
|
|
107
|
+
// to prevent overwriting server data with blank form values.
|
|
108
|
+
// Workspace is required: the user must pick one from the dropdown.
|
|
109
|
+
const canSave = title.trim().length > 0
|
|
110
|
+
&& (!isEdit || existingTask !== undefined)
|
|
111
|
+
&& workspaceId.length > 0;
|
|
112
|
+
|
|
113
|
+
const toggleDep = (depId: string): void => {
|
|
114
|
+
setSelectedDeps((prev) =>
|
|
115
|
+
prev.includes(depId) ? prev.filter((d) => d !== depId) : [...prev, depId],
|
|
116
|
+
);
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const handleSave = (): void => {
|
|
120
|
+
if (!canSave || creating) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (isEdit && existingTask === undefined) {
|
|
124
|
+
// Guard: task data not yet loaded — do not overwrite with blank values.
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (isEdit && taskId) {
|
|
128
|
+
onUpdateTask(taskId, title.trim(), description, selectedDeps, defaultPersonaId);
|
|
129
|
+
onShowToast?.("Task updated", "success");
|
|
130
|
+
if (onEditDone) {
|
|
131
|
+
onEditDone();
|
|
132
|
+
} else {
|
|
133
|
+
navigate(taskUrl(taskId, undefined, workspaceId, environmentId), { replace: true });
|
|
134
|
+
}
|
|
135
|
+
} else {
|
|
136
|
+
setCreating(true);
|
|
137
|
+
onCreateTask(
|
|
138
|
+
workspaceId,
|
|
139
|
+
title.trim(),
|
|
140
|
+
description,
|
|
141
|
+
selectedDeps.length > 0 ? selectedDeps : undefined,
|
|
142
|
+
parentTaskId || undefined,
|
|
143
|
+
defaultPersonaId,
|
|
144
|
+
canDecompose,
|
|
145
|
+
() => {
|
|
146
|
+
onShowToast?.("Task created", "success");
|
|
147
|
+
navigate(workspaceIdProp ? workspaceUrl(workspaceIdProp, environmentId) : "/tasks", { replace: true });
|
|
148
|
+
},
|
|
149
|
+
(message: string) => {
|
|
150
|
+
onShowToast?.(message, "error");
|
|
151
|
+
setCreating(false);
|
|
152
|
+
},
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const handleCancel = (): void => {
|
|
158
|
+
if (onEditDone) {
|
|
159
|
+
onEditDone();
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
if (isEdit && taskId) {
|
|
163
|
+
navigate(taskUrl(taskId, undefined, workspaceId, environmentId));
|
|
164
|
+
} else {
|
|
165
|
+
navigate(workspaceIdProp ? workspaceUrl(workspaceIdProp, environmentId) : "/tasks");
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const modeLabel = isEdit ? "edit task" : (parentTaskId ? "child task" : "new task");
|
|
170
|
+
|
|
171
|
+
return (
|
|
172
|
+
<div className={styles.container}>
|
|
173
|
+
{/* Header */}
|
|
174
|
+
<div className={styles.header}>
|
|
175
|
+
<div className={styles.headerTitle}>
|
|
176
|
+
<span className={styles.badge}>{modeLabel}</span>
|
|
177
|
+
{parentTask && (
|
|
178
|
+
<span className={styles.parentContext}>
|
|
179
|
+
<span className={styles.parentLabel}>Child of</span>
|
|
180
|
+
<span className={styles.parentName}>{parentTask.title}</span>
|
|
181
|
+
</span>
|
|
182
|
+
)}
|
|
183
|
+
</div>
|
|
184
|
+
<div className={styles.headerActions}>
|
|
185
|
+
<button
|
|
186
|
+
onClick={handleSave}
|
|
187
|
+
disabled={!canSave || creating}
|
|
188
|
+
className={styles.btnPrimary}
|
|
189
|
+
data-testid="task-edit-save"
|
|
190
|
+
>
|
|
191
|
+
{creating ? "Creating\u2026" : isEdit ? "Save Changes" : "Create"}
|
|
192
|
+
</button>
|
|
193
|
+
<button onClick={handleCancel} className={styles.btnGhost}>
|
|
194
|
+
Cancel
|
|
195
|
+
</button>
|
|
196
|
+
</div>
|
|
197
|
+
</div>
|
|
198
|
+
|
|
199
|
+
{/* Form body */}
|
|
200
|
+
<div className={styles.body}>
|
|
201
|
+
<div className={styles.formContent}>
|
|
202
|
+
{/* Workspace selector (shown when creating from global Tasks view) */}
|
|
203
|
+
{showWorkspaceSelector && (
|
|
204
|
+
<div className={styles.section}>
|
|
205
|
+
<label className={styles.label} htmlFor="task-edit-workspace">
|
|
206
|
+
Workspace
|
|
207
|
+
</label>
|
|
208
|
+
<select
|
|
209
|
+
id="task-edit-workspace"
|
|
210
|
+
value={selectedWorkspaceId}
|
|
211
|
+
onChange={(e) => setSelectedWorkspaceId(e.target.value)}
|
|
212
|
+
className={styles.personaSelect}
|
|
213
|
+
data-testid="task-edit-workspace"
|
|
214
|
+
>
|
|
215
|
+
<option value="">Select a workspace...</option>
|
|
216
|
+
{workspaces.map((w) => (
|
|
217
|
+
<option key={w.id} value={w.id}>{w.name}</option>
|
|
218
|
+
))}
|
|
219
|
+
</select>
|
|
220
|
+
</div>
|
|
221
|
+
)}
|
|
222
|
+
|
|
223
|
+
{/* Title */}
|
|
224
|
+
<div className={styles.section}>
|
|
225
|
+
<label className={styles.label} htmlFor="task-edit-title">
|
|
226
|
+
Title
|
|
227
|
+
</label>
|
|
228
|
+
<input
|
|
229
|
+
id="task-edit-title"
|
|
230
|
+
type="text"
|
|
231
|
+
value={title}
|
|
232
|
+
onChange={(e) => setTitle(e.target.value)}
|
|
233
|
+
placeholder="Task title..."
|
|
234
|
+
autoFocus
|
|
235
|
+
className={styles.titleInput}
|
|
236
|
+
data-testid="task-edit-title"
|
|
237
|
+
onKeyDown={(e) => {
|
|
238
|
+
if (e.key === "Enter" && canSave) {
|
|
239
|
+
handleSave();
|
|
240
|
+
}
|
|
241
|
+
}}
|
|
242
|
+
/>
|
|
243
|
+
</div>
|
|
244
|
+
|
|
245
|
+
{/* Description */}
|
|
246
|
+
<div className={styles.section}>
|
|
247
|
+
<label className={styles.label} htmlFor="task-edit-description">
|
|
248
|
+
Description
|
|
249
|
+
</label>
|
|
250
|
+
<textarea
|
|
251
|
+
id="task-edit-description"
|
|
252
|
+
value={description}
|
|
253
|
+
onChange={(e) => setDescription(e.target.value)}
|
|
254
|
+
placeholder="Describe the task... (markdown supported)"
|
|
255
|
+
className={styles.descriptionTextarea}
|
|
256
|
+
data-testid="task-edit-description"
|
|
257
|
+
rows={8}
|
|
258
|
+
/>
|
|
259
|
+
</div>
|
|
260
|
+
|
|
261
|
+
{/* Default Persona */}
|
|
262
|
+
<div className={styles.section}>
|
|
263
|
+
<label className={styles.label} htmlFor="task-edit-persona">
|
|
264
|
+
Default Persona
|
|
265
|
+
</label>
|
|
266
|
+
<select
|
|
267
|
+
id="task-edit-persona"
|
|
268
|
+
value={defaultPersonaId}
|
|
269
|
+
onChange={(e) => setDefaultPersonaId(e.target.value)}
|
|
270
|
+
className={styles.personaSelect}
|
|
271
|
+
data-testid="task-edit-persona"
|
|
272
|
+
>
|
|
273
|
+
<option value="">(Inherit)</option>
|
|
274
|
+
{personas.map((p) => (
|
|
275
|
+
<option key={p.id} value={p.id}>{p.name}</option>
|
|
276
|
+
))}
|
|
277
|
+
</select>
|
|
278
|
+
</div>
|
|
279
|
+
|
|
280
|
+
{/* Can Decompose */}
|
|
281
|
+
{!isEdit && (
|
|
282
|
+
<div className={styles.section}>
|
|
283
|
+
<label className={styles.depItem} data-testid="task-edit-can-decompose">
|
|
284
|
+
<input
|
|
285
|
+
type="checkbox"
|
|
286
|
+
checked={canDecompose}
|
|
287
|
+
onChange={(e) => setCanDecompose(e.target.checked)}
|
|
288
|
+
/>
|
|
289
|
+
Can spawn subtasks
|
|
290
|
+
</label>
|
|
291
|
+
</div>
|
|
292
|
+
)}
|
|
293
|
+
|
|
294
|
+
{/* Dependencies */}
|
|
295
|
+
<div className={styles.section}>
|
|
296
|
+
<div className={styles.label}>Dependencies</div>
|
|
297
|
+
{siblingTasks.length === 0 ? (
|
|
298
|
+
<div className={styles.noDeps}>No other tasks in this workspace</div>
|
|
299
|
+
) : (
|
|
300
|
+
<div className={styles.depList}>
|
|
301
|
+
{siblingTasks.map((t) => {
|
|
302
|
+
const isChecked = selectedDeps.includes(t.id);
|
|
303
|
+
return (
|
|
304
|
+
<label
|
|
305
|
+
key={t.id}
|
|
306
|
+
className={`${styles.depItem} ${isChecked ? styles.depItemSelected : ""}`}
|
|
307
|
+
data-testid={`dep-option-${t.id}`}
|
|
308
|
+
>
|
|
309
|
+
<input
|
|
310
|
+
type="checkbox"
|
|
311
|
+
checked={isChecked}
|
|
312
|
+
onChange={() => toggleDep(t.id)}
|
|
313
|
+
/>
|
|
314
|
+
{t.title}
|
|
315
|
+
<span style={{ opacity: 0.5, fontSize: "11px", marginLeft: "4px" }}>
|
|
316
|
+
({t.status})
|
|
317
|
+
</span>
|
|
318
|
+
</label>
|
|
319
|
+
);
|
|
320
|
+
})}
|
|
321
|
+
</div>
|
|
322
|
+
)}
|
|
323
|
+
</div>
|
|
324
|
+
</div>
|
|
325
|
+
</div>
|
|
326
|
+
</div>
|
|
327
|
+
);
|
|
328
|
+
}
|