@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,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
|
+
};
|