@shepai/cli 1.68.0 → 1.69.0
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/dist/src/presentation/web/components/common/control-center-drawer/control-center-drawer.d.ts.map +1 -1
- package/dist/src/presentation/web/components/common/control-center-drawer/control-center-drawer.js +26 -3
- package/dist/src/presentation/web/components/common/feature-node/feature-node-state-config.d.ts +2 -0
- package/dist/src/presentation/web/components/common/feature-node/feature-node-state-config.d.ts.map +1 -1
- package/dist/src/presentation/web/components/features/control-center/control-center-inner.d.ts.map +1 -1
- package/dist/src/presentation/web/components/features/control-center/control-center-inner.js +32 -0
- package/dist/src/presentation/web/components/layouts/app-shell/app-shell.d.ts.map +1 -1
- package/dist/src/presentation/web/components/layouts/app-shell/app-shell.js +7 -2
- package/dist/src/presentation/web/components/layouts/app-sidebar/app-sidebar.d.ts +3 -3
- package/dist/src/presentation/web/components/layouts/app-sidebar/app-sidebar.d.ts.map +1 -1
- package/dist/src/presentation/web/components/layouts/app-sidebar/app-sidebar.js +1 -1
- package/dist/src/presentation/web/components/layouts/app-sidebar/app-sidebar.stories.d.ts.map +1 -1
- package/dist/src/presentation/web/components/layouts/app-sidebar/app-sidebar.stories.js +36 -11
- package/dist/src/presentation/web/hooks/sidebar-features-context.d.ts +26 -0
- package/dist/src/presentation/web/hooks/sidebar-features-context.d.ts.map +1 -0
- package/dist/src/presentation/web/hooks/sidebar-features-context.js +34 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/web/.next/BUILD_ID +1 -1
- package/web/.next/build-manifest.json +2 -2
- package/web/.next/cache/.previewinfo +1 -1
- package/web/.next/cache/.rscinfo +1 -1
- package/web/.next/cache/.tsbuildinfo +1 -1
- package/web/.next/cache/config.json +3 -3
- package/web/.next/fallback-build-manifest.json +2 -2
- package/web/.next/prerender-manifest.json +3 -3
- package/web/.next/required-server-files.js +1 -1
- package/web/.next/required-server-files.json +1 -1
- package/web/.next/server/app/_global-error.html +2 -2
- package/web/.next/server/app/_global-error.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/web/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
- package/web/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/page/server-reference-manifest.json +18 -18
- package/web/.next/server/app/page.js.nft.json +1 -1
- package/web/.next/server/app/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/skills/page/server-reference-manifest.json +5 -5
- package/web/.next/server/app/skills/page.js.nft.json +1 -1
- package/web/.next/server/app/skills/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/tools/page/server-reference-manifest.json +1 -1
- package/web/.next/server/app/tools/page.js.nft.json +1 -1
- package/web/.next/server/app/tools/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/version/page/server-reference-manifest.json +1 -1
- package/web/.next/server/app/version/page.js.nft.json +1 -1
- package/web/.next/server/app/version/page_client-reference-manifest.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__08ba9bd3._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__08ba9bd3._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__249c74f6._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__551fb7e1._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__551fb7e1._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__6b17a22d._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__6b17a22d._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__804c006d._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__804c006d._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__9add7c3a._.js +2 -2
- package/web/.next/server/chunks/ssr/[root-of-the-server]__9add7c3a._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__e41b5eec._.js +2 -2
- package/web/.next/server/chunks/ssr/[root-of-the-server]__e41b5eec._.js.map +1 -1
- package/web/.next/server/chunks/ssr/_49bf495c._.js +1 -1
- package/web/.next/server/chunks/ssr/_49bf495c._.js.map +1 -1
- package/web/.next/server/chunks/ssr/_68b5e0de._.js +6 -0
- package/web/.next/server/chunks/ssr/_68b5e0de._.js.map +1 -0
- package/web/.next/server/chunks/ssr/_725584e5._.js +1 -1
- package/web/.next/server/chunks/ssr/_725584e5._.js.map +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_components_e599bb8c._.js +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_components_e599bb8c._.js.map +1 -1
- package/web/.next/server/pages/500.html +2 -2
- package/web/.next/server/server-reference-manifest.js +1 -1
- package/web/.next/server/server-reference-manifest.json +19 -19
- package/web/.next/standalone/src/presentation/web/.next/BUILD_ID +1 -1
- package/web/.next/standalone/src/presentation/web/.next/build-manifest.json +2 -2
- package/web/.next/standalone/src/presentation/web/.next/prerender-manifest.json +3 -3
- package/web/.next/standalone/src/presentation/web/.next/required-server-files.json +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.html +2 -2
- package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.rsc +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/page/server-reference-manifest.json +18 -18
- package/web/.next/standalone/src/presentation/web/.next/server/app/page.js.nft.json +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/skills/page/server-reference-manifest.json +5 -5
- package/web/.next/standalone/src/presentation/web/.next/server/app/skills/page.js.nft.json +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/skills/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/tools/page/server-reference-manifest.json +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/tools/page.js.nft.json +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/tools/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/version/page/server-reference-manifest.json +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/version/page.js.nft.json +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/version/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__08ba9bd3._.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__249c74f6._.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__551fb7e1._.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__6b17a22d._.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__804c006d._.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__9add7c3a._.js +2 -2
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__e41b5eec._.js +2 -2
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/_49bf495c._.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/_68b5e0de._.js +6 -0
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/_725584e5._.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_components_e599bb8c._.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/pages/500.html +2 -2
- package/web/.next/standalone/src/presentation/web/.next/server/server-reference-manifest.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/server-reference-manifest.json +19 -19
- package/web/.next/standalone/src/presentation/web/components/common/control-center-drawer/control-center-drawer.tsx +102 -45
- package/web/.next/standalone/src/presentation/web/components/common/feature-node/feature-node-state-config.ts +2 -0
- package/web/.next/standalone/src/presentation/web/components/features/control-center/control-center-inner.tsx +44 -0
- package/web/.next/standalone/src/presentation/web/components/layouts/app-shell/app-shell.tsx +18 -2
- package/web/.next/standalone/src/presentation/web/components/layouts/app-sidebar/app-sidebar.stories.tsx +36 -11
- package/web/.next/standalone/src/presentation/web/components/layouts/app-sidebar/app-sidebar.tsx +7 -4
- package/web/.next/standalone/src/presentation/web/hooks/sidebar-features-context.tsx +69 -0
- package/web/.next/standalone/src/presentation/web/server.js +1 -1
- package/web/.next/static/chunks/{0751efba75563e6a.js → 2da52289a4a8f9e2.js} +1 -1
- package/web/.next/static/chunks/{caa2e7e1618e2179.js → 51c94a563ecc565d.js} +1 -1
- package/web/.next/static/chunks/5bc5d426870feb7e.js +1 -0
- package/web/.next/static/chunks/{49feba0d1a871e2b.js → 5ef9d8b7401f2f1f.js} +2 -2
- package/web/.next/static/chunks/{7f6db87dc1fe9c3b.js → faf8ad4c823884d7.js} +1 -1
- package/web/.next/trace +1 -1
- package/web/.next/trace-build +1 -1
- package/web/.next/server/chunks/ssr/_23c92688._.js +0 -6
- package/web/.next/server/chunks/ssr/_23c92688._.js.map +0 -1
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/_23c92688._.js +0 -6
- package/web/.next/static/chunks/33b4e5444019ab64.js +0 -1
- /package/web/.next/static/{ntu60tngTU2e9MZgpdlz0 → 7ktIn83yR3rYLLF00uLSx}/_buildManifest.js +0 -0
- /package/web/.next/static/{ntu60tngTU2e9MZgpdlz0 → 7ktIn83yR3rYLLF00uLSx}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/static/{ntu60tngTU2e9MZgpdlz0 → 7ktIn83yR3rYLLF00uLSx}/_ssgManifest.js +0 -0
package/web/.next/standalone/src/presentation/web/.next/server/server-reference-manifest.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"node": {
|
|
3
|
-
"
|
|
3
|
+
"0047c870d719162f2116e56b9cb492d37d546502e6": {
|
|
4
4
|
"workers": {
|
|
5
5
|
"app/_not-found/page": {
|
|
6
6
|
"moduleId": 66973,
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"filename": "src/presentation/web/app/actions/pick-folder.ts",
|
|
44
44
|
"exportedName": "pickFolder"
|
|
45
45
|
},
|
|
46
|
-
"
|
|
46
|
+
"40bbd46af81361e4188c140ffcb4ec4230c1f5ede1": {
|
|
47
47
|
"workers": {
|
|
48
48
|
"app/page": {
|
|
49
49
|
"moduleId": 80894,
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"filename": "src/presentation/web/app/actions/deploy-feature.ts",
|
|
66
66
|
"exportedName": "deployFeature"
|
|
67
67
|
},
|
|
68
|
-
"
|
|
68
|
+
"402d63b563a07e93686b59fb9cf228c2bff903df52": {
|
|
69
69
|
"workers": {
|
|
70
70
|
"app/page": {
|
|
71
71
|
"moduleId": 80894,
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
"filename": "src/presentation/web/app/actions/deploy-repository.ts",
|
|
88
88
|
"exportedName": "deployRepository"
|
|
89
89
|
},
|
|
90
|
-
"
|
|
90
|
+
"40d0994666333c500668bb580f7fe12f156f447549": {
|
|
91
91
|
"workers": {
|
|
92
92
|
"app/page": {
|
|
93
93
|
"moduleId": 80894,
|
|
@@ -109,7 +109,7 @@
|
|
|
109
109
|
"filename": "src/presentation/web/app/actions/stop-deployment.ts",
|
|
110
110
|
"exportedName": "stopDeployment"
|
|
111
111
|
},
|
|
112
|
-
"
|
|
112
|
+
"400abe699c44fba3a3c251e3a249db94a84ea9375f": {
|
|
113
113
|
"workers": {
|
|
114
114
|
"app/page": {
|
|
115
115
|
"moduleId": 80894,
|
|
@@ -131,7 +131,7 @@
|
|
|
131
131
|
"filename": "src/presentation/web/app/actions/get-deployment-status.ts",
|
|
132
132
|
"exportedName": "getDeploymentStatus"
|
|
133
133
|
},
|
|
134
|
-
"
|
|
134
|
+
"40c8357881b7209754173b264c064e68dc1fccc6c5": {
|
|
135
135
|
"workers": {
|
|
136
136
|
"app/page": {
|
|
137
137
|
"moduleId": 80894,
|
|
@@ -146,7 +146,7 @@
|
|
|
146
146
|
"filename": "src/presentation/web/app/actions/open-ide.ts",
|
|
147
147
|
"exportedName": "openIde"
|
|
148
148
|
},
|
|
149
|
-
"
|
|
149
|
+
"40df943127843d139f09ef0c15e7af7962c1163a3c": {
|
|
150
150
|
"workers": {
|
|
151
151
|
"app/page": {
|
|
152
152
|
"moduleId": 80894,
|
|
@@ -161,7 +161,7 @@
|
|
|
161
161
|
"filename": "src/presentation/web/app/actions/open-shell.ts",
|
|
162
162
|
"exportedName": "openShell"
|
|
163
163
|
},
|
|
164
|
-
"
|
|
164
|
+
"403aacc3b063b935fc97c13a4ddc2e39d5248cfce7": {
|
|
165
165
|
"workers": {
|
|
166
166
|
"app/page": {
|
|
167
167
|
"moduleId": 80894,
|
|
@@ -176,7 +176,7 @@
|
|
|
176
176
|
"filename": "src/presentation/web/app/actions/open-folder.ts",
|
|
177
177
|
"exportedName": "openFolder"
|
|
178
178
|
},
|
|
179
|
-
"
|
|
179
|
+
"609912b2e71530227bd971713e722e2ddae870c834": {
|
|
180
180
|
"workers": {
|
|
181
181
|
"app/page": {
|
|
182
182
|
"moduleId": 80894,
|
|
@@ -191,7 +191,7 @@
|
|
|
191
191
|
"filename": "src/presentation/web/app/actions/approve-feature.ts",
|
|
192
192
|
"exportedName": "approveFeature"
|
|
193
193
|
},
|
|
194
|
-
"
|
|
194
|
+
"6056c8091c66cb6e17e1dc88724cdb36ef658aaa8b": {
|
|
195
195
|
"workers": {
|
|
196
196
|
"app/page": {
|
|
197
197
|
"moduleId": 80894,
|
|
@@ -206,7 +206,7 @@
|
|
|
206
206
|
"filename": "src/presentation/web/app/actions/reject-feature.ts",
|
|
207
207
|
"exportedName": "rejectFeature"
|
|
208
208
|
},
|
|
209
|
-
"
|
|
209
|
+
"403b2febb379fc46c91085af13c602491d955b01e2": {
|
|
210
210
|
"workers": {
|
|
211
211
|
"app/page": {
|
|
212
212
|
"moduleId": 80894,
|
|
@@ -221,7 +221,7 @@
|
|
|
221
221
|
"filename": "src/presentation/web/app/actions/get-feature-artifact.ts",
|
|
222
222
|
"exportedName": "getFeatureArtifact"
|
|
223
223
|
},
|
|
224
|
-
"
|
|
224
|
+
"40ab7b59ff7a66389f880a241d232f328f3926f2fe": {
|
|
225
225
|
"workers": {
|
|
226
226
|
"app/page": {
|
|
227
227
|
"moduleId": 80894,
|
|
@@ -236,7 +236,7 @@
|
|
|
236
236
|
"filename": "src/presentation/web/app/actions/get-research-artifact.ts",
|
|
237
237
|
"exportedName": "getResearchArtifact"
|
|
238
238
|
},
|
|
239
|
-
"
|
|
239
|
+
"40ba6de692514b18656ea1b43673e1bffa26a02bf4": {
|
|
240
240
|
"workers": {
|
|
241
241
|
"app/page": {
|
|
242
242
|
"moduleId": 80894,
|
|
@@ -251,7 +251,7 @@
|
|
|
251
251
|
"filename": "src/presentation/web/app/actions/get-merge-review-data.ts",
|
|
252
252
|
"exportedName": "getMergeReviewData"
|
|
253
253
|
},
|
|
254
|
-
"
|
|
254
|
+
"007efbf78a113f3482394b5e8f8001bde6bdc43bc9": {
|
|
255
255
|
"workers": {
|
|
256
256
|
"app/page": {
|
|
257
257
|
"moduleId": 80894,
|
|
@@ -266,7 +266,7 @@
|
|
|
266
266
|
"filename": "src/presentation/web/app/actions/get-workflow-defaults.ts",
|
|
267
267
|
"exportedName": "getWorkflowDefaults"
|
|
268
268
|
},
|
|
269
|
-
"
|
|
269
|
+
"406caff046f96b31ff916a2d7a56b00e4e0ba42969": {
|
|
270
270
|
"workers": {
|
|
271
271
|
"app/page": {
|
|
272
272
|
"moduleId": 80894,
|
|
@@ -281,7 +281,7 @@
|
|
|
281
281
|
"filename": "src/presentation/web/app/actions/create-feature.ts",
|
|
282
282
|
"exportedName": "createFeature"
|
|
283
283
|
},
|
|
284
|
-
"
|
|
284
|
+
"403d314dfbfa9e924edf1541ef2abe8b817e113d71": {
|
|
285
285
|
"workers": {
|
|
286
286
|
"app/page": {
|
|
287
287
|
"moduleId": 80894,
|
|
@@ -296,7 +296,7 @@
|
|
|
296
296
|
"filename": "src/presentation/web/app/actions/delete-feature.ts",
|
|
297
297
|
"exportedName": "deleteFeature"
|
|
298
298
|
},
|
|
299
|
-
"
|
|
299
|
+
"40cbce1b9c3b4e574ec0572abcf20fed0c5625fa87": {
|
|
300
300
|
"workers": {
|
|
301
301
|
"app/page": {
|
|
302
302
|
"moduleId": 80894,
|
|
@@ -311,7 +311,7 @@
|
|
|
311
311
|
"filename": "src/presentation/web/app/actions/add-repository.ts",
|
|
312
312
|
"exportedName": "addRepository"
|
|
313
313
|
},
|
|
314
|
-
"
|
|
314
|
+
"40f04206c40969efebf2da1c4c41ba53b053aace88": {
|
|
315
315
|
"workers": {
|
|
316
316
|
"app/page": {
|
|
317
317
|
"moduleId": 80894,
|
|
@@ -328,5 +328,5 @@
|
|
|
328
328
|
}
|
|
329
329
|
},
|
|
330
330
|
"edge": {},
|
|
331
|
-
"encryptionKey": "
|
|
331
|
+
"encryptionKey": "KmY8H5oInrxtJtzBQaPPrAIroKymKKDWe5g41Yx3TA8="
|
|
332
332
|
}
|
|
@@ -10,6 +10,8 @@ import {
|
|
|
10
10
|
Code2,
|
|
11
11
|
Terminal,
|
|
12
12
|
FolderOpen,
|
|
13
|
+
Play,
|
|
14
|
+
Square,
|
|
13
15
|
} from 'lucide-react';
|
|
14
16
|
import type {
|
|
15
17
|
PrdApprovalPayload,
|
|
@@ -22,12 +24,16 @@ import { getFeatureArtifact } from '@/app/actions/get-feature-artifact';
|
|
|
22
24
|
import { getResearchArtifact } from '@/app/actions/get-research-artifact';
|
|
23
25
|
import { getMergeReviewData } from '@/app/actions/get-merge-review-data';
|
|
24
26
|
import { cn } from '@/lib/utils';
|
|
27
|
+
import { featureFlags } from '@/lib/feature-flags';
|
|
25
28
|
import { useSoundAction } from '@/hooks/use-sound-action';
|
|
29
|
+
import { useDeployAction } from '@/hooks/use-deploy-action';
|
|
26
30
|
import { BaseDrawer } from '@/components/common/base-drawer';
|
|
31
|
+
import { DeploymentStatusBadge } from '@/components/common/deployment-status-badge';
|
|
27
32
|
import { DrawerTitle, DrawerDescription } from '@/components/ui/drawer';
|
|
28
33
|
import { Button } from '@/components/ui/button';
|
|
29
34
|
import { Badge } from '@/components/ui/badge';
|
|
30
35
|
import { Separator } from '@/components/ui/separator';
|
|
36
|
+
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
|
|
31
37
|
import { CometSpinner } from '@/components/ui/comet-spinner';
|
|
32
38
|
import {
|
|
33
39
|
AlertDialog,
|
|
@@ -303,6 +309,31 @@ export function ControlCenterDrawer({
|
|
|
303
309
|
repoData?.repositoryPath ? { repositoryPath: repoData.repositoryPath } : null
|
|
304
310
|
);
|
|
305
311
|
|
|
312
|
+
// ── Deploy targets ──────────────────────────────────────────────────────
|
|
313
|
+
// Feature deploy is rendered inline in the header; repo deploy uses BaseDrawer's bar.
|
|
314
|
+
|
|
315
|
+
const featureDeployTarget =
|
|
316
|
+
featureNode?.repositoryPath && featureNode.branch
|
|
317
|
+
? {
|
|
318
|
+
targetId: featureNode.featureId,
|
|
319
|
+
targetType: 'feature' as const,
|
|
320
|
+
repositoryPath: featureNode.repositoryPath,
|
|
321
|
+
branch: featureNode.branch,
|
|
322
|
+
}
|
|
323
|
+
: null;
|
|
324
|
+
|
|
325
|
+
const repoDeployTarget = repoData?.repositoryPath
|
|
326
|
+
? {
|
|
327
|
+
targetId: repoData.repositoryPath,
|
|
328
|
+
targetType: 'repository' as const,
|
|
329
|
+
repositoryPath: repoData.repositoryPath,
|
|
330
|
+
}
|
|
331
|
+
: undefined;
|
|
332
|
+
|
|
333
|
+
const deployAction = useDeployAction(featureDeployTarget);
|
|
334
|
+
const isFeatureDeployActive =
|
|
335
|
+
deployAction.status === 'Booting' || deployAction.status === 'Ready';
|
|
336
|
+
|
|
306
337
|
// ── Header ──────────────────────────────────────────────────────────────
|
|
307
338
|
|
|
308
339
|
let header: React.ReactNode = undefined;
|
|
@@ -326,57 +357,82 @@ export function ControlCenterDrawer({
|
|
|
326
357
|
repositoryPath={featureActionsInput.repositoryPath}
|
|
327
358
|
showSpecs={!!featureActionsInput.specPath}
|
|
328
359
|
/>
|
|
329
|
-
{
|
|
360
|
+
{featureFlags.envDeploy && featureDeployTarget ? (
|
|
330
361
|
<>
|
|
331
|
-
<
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
362
|
+
<TooltipProvider>
|
|
363
|
+
<Tooltip>
|
|
364
|
+
<TooltipTrigger asChild>
|
|
365
|
+
<span>
|
|
366
|
+
<ActionButton
|
|
367
|
+
label={isFeatureDeployActive ? 'Stop Dev Server' : 'Start Dev Server'}
|
|
368
|
+
onClick={isFeatureDeployActive ? deployAction.stop : deployAction.deploy}
|
|
369
|
+
loading={deployAction.deployLoading || deployAction.stopLoading}
|
|
370
|
+
error={!!deployAction.deployError}
|
|
371
|
+
icon={isFeatureDeployActive ? Square : Play}
|
|
372
|
+
iconOnly
|
|
373
|
+
variant="outline"
|
|
374
|
+
size="icon-sm"
|
|
375
|
+
/>
|
|
376
|
+
</span>
|
|
377
|
+
</TooltipTrigger>
|
|
378
|
+
<TooltipContent>
|
|
379
|
+
{isFeatureDeployActive ? 'Stop Dev Server' : 'Start Dev Server'}
|
|
380
|
+
</TooltipContent>
|
|
381
|
+
</Tooltip>
|
|
382
|
+
</TooltipProvider>
|
|
383
|
+
{isFeatureDeployActive ? (
|
|
384
|
+
<DeploymentStatusBadge status={deployAction.status} url={deployAction.url} />
|
|
385
|
+
) : null}
|
|
386
|
+
</>
|
|
387
|
+
) : null}
|
|
388
|
+
{onDelete && featureNode.featureId ? (
|
|
389
|
+
<AlertDialog>
|
|
390
|
+
<AlertDialogTrigger asChild>
|
|
391
|
+
<Button
|
|
392
|
+
variant="ghost"
|
|
393
|
+
size="icon-sm"
|
|
394
|
+
aria-label="Delete feature"
|
|
395
|
+
disabled={isDeleting}
|
|
396
|
+
className="text-muted-foreground hover:text-destructive ml-auto"
|
|
397
|
+
data-testid="feature-drawer-delete"
|
|
398
|
+
>
|
|
399
|
+
{isDeleting ? (
|
|
400
|
+
<Loader2 className="size-4 animate-spin" />
|
|
401
|
+
) : (
|
|
402
|
+
<Trash2 className="size-4" />
|
|
403
|
+
)}
|
|
404
|
+
</Button>
|
|
405
|
+
</AlertDialogTrigger>
|
|
406
|
+
<AlertDialogContent>
|
|
407
|
+
<AlertDialogHeader>
|
|
408
|
+
<AlertDialogTitle>Delete feature?</AlertDialogTitle>
|
|
409
|
+
<AlertDialogDescription>
|
|
410
|
+
This will permanently delete <strong>{featureNode.name}</strong> (
|
|
411
|
+
{featureNode.featureId}). This action cannot be undone.
|
|
412
|
+
{featureNode.state === 'running' ? (
|
|
413
|
+
<> This feature has a running agent that will be stopped.</>
|
|
414
|
+
) : null}
|
|
415
|
+
</AlertDialogDescription>
|
|
416
|
+
</AlertDialogHeader>
|
|
417
|
+
<AlertDialogFooter>
|
|
418
|
+
<AlertDialogCancel disabled={isDeleting}>Cancel</AlertDialogCancel>
|
|
419
|
+
<AlertDialogAction
|
|
420
|
+
variant="destructive"
|
|
338
421
|
disabled={isDeleting}
|
|
339
|
-
|
|
340
|
-
data-testid="feature-drawer-delete"
|
|
422
|
+
onClick={() => onDelete(featureNode.featureId)}
|
|
341
423
|
>
|
|
342
424
|
{isDeleting ? (
|
|
343
|
-
|
|
425
|
+
<>
|
|
426
|
+
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
427
|
+
Deleting…
|
|
428
|
+
</>
|
|
344
429
|
) : (
|
|
345
|
-
|
|
430
|
+
'Delete'
|
|
346
431
|
)}
|
|
347
|
-
</
|
|
348
|
-
</
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
<AlertDialogTitle>Delete feature?</AlertDialogTitle>
|
|
352
|
-
<AlertDialogDescription>
|
|
353
|
-
This will permanently delete <strong>{featureNode.name}</strong> (
|
|
354
|
-
{featureNode.featureId}). This action cannot be undone.
|
|
355
|
-
{featureNode.state === 'running' ? (
|
|
356
|
-
<> This feature has a running agent that will be stopped.</>
|
|
357
|
-
) : null}
|
|
358
|
-
</AlertDialogDescription>
|
|
359
|
-
</AlertDialogHeader>
|
|
360
|
-
<AlertDialogFooter>
|
|
361
|
-
<AlertDialogCancel disabled={isDeleting}>Cancel</AlertDialogCancel>
|
|
362
|
-
<AlertDialogAction
|
|
363
|
-
variant="destructive"
|
|
364
|
-
disabled={isDeleting}
|
|
365
|
-
onClick={() => onDelete(featureNode.featureId)}
|
|
366
|
-
>
|
|
367
|
-
{isDeleting ? (
|
|
368
|
-
<>
|
|
369
|
-
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
370
|
-
Deleting…
|
|
371
|
-
</>
|
|
372
|
-
) : (
|
|
373
|
-
'Delete'
|
|
374
|
-
)}
|
|
375
|
-
</AlertDialogAction>
|
|
376
|
-
</AlertDialogFooter>
|
|
377
|
-
</AlertDialogContent>
|
|
378
|
-
</AlertDialog>
|
|
379
|
-
</>
|
|
432
|
+
</AlertDialogAction>
|
|
433
|
+
</AlertDialogFooter>
|
|
434
|
+
</AlertDialogContent>
|
|
435
|
+
</AlertDialog>
|
|
380
436
|
) : null}
|
|
381
437
|
</div>
|
|
382
438
|
) : null}
|
|
@@ -531,6 +587,7 @@ export function ControlCenterDrawer({
|
|
|
531
587
|
size="md"
|
|
532
588
|
modal={false}
|
|
533
589
|
header={header}
|
|
590
|
+
deployTarget={repoDeployTarget}
|
|
534
591
|
data-testid={
|
|
535
592
|
view?.type === 'feature'
|
|
536
593
|
? 'feature-drawer'
|
|
@@ -53,6 +53,8 @@ export interface FeatureNodeData {
|
|
|
53
53
|
branch: string;
|
|
54
54
|
/** Absolute path to the specs folder on disk */
|
|
55
55
|
specPath?: string;
|
|
56
|
+
/** Epoch ms when the current agent run started (for elapsed-time in sidebar) */
|
|
57
|
+
startedAt?: number;
|
|
56
58
|
/** Human-readable runtime for done state (e.g. "2h 15m") */
|
|
57
59
|
runtime?: string;
|
|
58
60
|
/** Feature name this node is blocked by */
|
|
@@ -10,6 +10,10 @@ import type { RepositoryNodeData } from '@/components/common/repository-node';
|
|
|
10
10
|
import { NotificationPermissionBanner } from '@/components/common/notification-permission-banner';
|
|
11
11
|
import { getWorkflowDefaults } from '@/app/actions/get-workflow-defaults';
|
|
12
12
|
import type { WorkflowDefaults } from '@/app/actions/get-workflow-defaults';
|
|
13
|
+
import {
|
|
14
|
+
useSidebarFeaturesContext,
|
|
15
|
+
mapNodeStateToSidebarStatus,
|
|
16
|
+
} from '@/hooks/sidebar-features-context';
|
|
13
17
|
import { ControlCenterEmptyState } from './control-center-empty-state';
|
|
14
18
|
import { useControlCenterState } from './use-control-center-state';
|
|
15
19
|
|
|
@@ -42,6 +46,46 @@ export function ControlCenterInner({ initialNodes, initialEdges }: ControlCenter
|
|
|
42
46
|
pendingParentFeatureId,
|
|
43
47
|
} = useControlCenterState(initialNodes, initialEdges);
|
|
44
48
|
|
|
49
|
+
// Publish sidebar features to context whenever feature node data changes
|
|
50
|
+
const { setFeatures: setSidebarFeatures } = useSidebarFeaturesContext();
|
|
51
|
+
|
|
52
|
+
const sidebarKey = useMemo(() => {
|
|
53
|
+
return nodes
|
|
54
|
+
.filter((n) => n.type === 'featureNode')
|
|
55
|
+
.map((n) => {
|
|
56
|
+
const d = n.data as FeatureNodeData;
|
|
57
|
+
return `${d.featureId}:${d.state}:${d.runtime ?? ''}:${d.startedAt ?? ''}`;
|
|
58
|
+
})
|
|
59
|
+
.sort()
|
|
60
|
+
.join(',');
|
|
61
|
+
}, [nodes]);
|
|
62
|
+
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
const sidebarItems = nodes
|
|
65
|
+
.filter((n) => n.type === 'featureNode')
|
|
66
|
+
.map((n) => {
|
|
67
|
+
const d = n.data as FeatureNodeData;
|
|
68
|
+
const status = mapNodeStateToSidebarStatus(d.state);
|
|
69
|
+
if (!status) return null;
|
|
70
|
+
return {
|
|
71
|
+
featureId: d.featureId,
|
|
72
|
+
name: d.name,
|
|
73
|
+
status,
|
|
74
|
+
...(d.startedAt != null && { startedAt: d.startedAt }),
|
|
75
|
+
...(d.runtime != null && { duration: d.runtime }),
|
|
76
|
+
};
|
|
77
|
+
})
|
|
78
|
+
.filter(Boolean) as {
|
|
79
|
+
featureId: string;
|
|
80
|
+
name: string;
|
|
81
|
+
status: 'action-needed' | 'in-progress' | 'done';
|
|
82
|
+
startedAt?: number;
|
|
83
|
+
duration?: string;
|
|
84
|
+
}[];
|
|
85
|
+
|
|
86
|
+
setSidebarFeatures(sidebarItems);
|
|
87
|
+
}, [sidebarKey, nodes, setSidebarFeatures]);
|
|
88
|
+
|
|
45
89
|
// Feature list for the parent selector in the create drawer
|
|
46
90
|
const featureOptions = useMemo(
|
|
47
91
|
() =>
|
package/web/.next/standalone/src/presentation/web/components/layouts/app-shell/app-shell.tsx
CHANGED
|
@@ -7,6 +7,10 @@ import { AddRepositoryButton } from '@/components/common/add-repository-node';
|
|
|
7
7
|
import { ThemeToggle } from '@/components/common/theme-toggle';
|
|
8
8
|
import { SoundToggle } from '@/components/common/sound-toggle';
|
|
9
9
|
import { AgentEventsProvider } from '@/hooks/agent-events-provider';
|
|
10
|
+
import {
|
|
11
|
+
SidebarFeaturesProvider,
|
|
12
|
+
useSidebarFeaturesContext,
|
|
13
|
+
} from '@/hooks/sidebar-features-context';
|
|
10
14
|
import { useNotifications } from '@/hooks/use-notifications';
|
|
11
15
|
|
|
12
16
|
interface AppShellProps {
|
|
@@ -18,17 +22,27 @@ function AppShellInner({ children }: AppShellProps) {
|
|
|
18
22
|
// Subscribe to agent lifecycle events and dispatch toast/browser notifications
|
|
19
23
|
useNotifications();
|
|
20
24
|
|
|
25
|
+
const { features } = useSidebarFeaturesContext();
|
|
26
|
+
|
|
21
27
|
const handleNewFeature = useCallback(() => {
|
|
22
28
|
window.dispatchEvent(new CustomEvent('shep:open-create-drawer'));
|
|
23
29
|
}, []);
|
|
24
30
|
|
|
31
|
+
const handleFeatureClick = useCallback((featureId: string) => {
|
|
32
|
+
window.dispatchEvent(new CustomEvent('shep:select-feature', { detail: { featureId } }));
|
|
33
|
+
}, []);
|
|
34
|
+
|
|
25
35
|
const handleRepositorySelect = useCallback((path: string) => {
|
|
26
36
|
window.dispatchEvent(new CustomEvent('shep:add-repository', { detail: { path } }));
|
|
27
37
|
}, []);
|
|
28
38
|
|
|
29
39
|
return (
|
|
30
40
|
<SidebarProvider>
|
|
31
|
-
<AppSidebar
|
|
41
|
+
<AppSidebar
|
|
42
|
+
features={features}
|
|
43
|
+
onNewFeature={handleNewFeature}
|
|
44
|
+
onFeatureClick={handleFeatureClick}
|
|
45
|
+
/>
|
|
32
46
|
<SidebarInset>
|
|
33
47
|
<div className="relative h-full">
|
|
34
48
|
<div className="absolute top-3 right-3 z-50 flex gap-1">
|
|
@@ -46,7 +60,9 @@ function AppShellInner({ children }: AppShellProps) {
|
|
|
46
60
|
export function AppShell({ children }: AppShellProps) {
|
|
47
61
|
return (
|
|
48
62
|
<AgentEventsProvider>
|
|
49
|
-
<
|
|
63
|
+
<SidebarFeaturesProvider>
|
|
64
|
+
<AppShellInner>{children}</AppShellInner>
|
|
65
|
+
</SidebarFeaturesProvider>
|
|
50
66
|
</AgentEventsProvider>
|
|
51
67
|
);
|
|
52
68
|
}
|
|
@@ -34,12 +34,27 @@ export default meta;
|
|
|
34
34
|
type Story = StoryObj<typeof meta>;
|
|
35
35
|
|
|
36
36
|
const mockFeatures = [
|
|
37
|
-
{ name: 'Auth Module', status: 'action-needed' as const },
|
|
38
|
-
{ name: 'Payment Flow', status: 'action-needed' as const },
|
|
39
|
-
{
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
37
|
+
{ featureId: 'feat-auth-001', name: 'Auth Module', status: 'action-needed' as const },
|
|
38
|
+
{ featureId: 'feat-payment-002', name: 'Payment Flow', status: 'action-needed' as const },
|
|
39
|
+
{
|
|
40
|
+
featureId: 'feat-dashboard-003',
|
|
41
|
+
name: 'Dashboard',
|
|
42
|
+
status: 'in-progress' as const,
|
|
43
|
+
startedAt: Date.now() - 330_000,
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
featureId: 'feat-api-004',
|
|
47
|
+
name: 'API Gateway',
|
|
48
|
+
status: 'in-progress' as const,
|
|
49
|
+
startedAt: Date.now() - 60_000,
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
featureId: 'feat-settings-005',
|
|
53
|
+
name: 'Settings Page',
|
|
54
|
+
status: 'done' as const,
|
|
55
|
+
duration: '2h',
|
|
56
|
+
},
|
|
57
|
+
{ featureId: 'feat-profile-006', name: 'User Profile', status: 'done' as const, duration: '1h' },
|
|
43
58
|
];
|
|
44
59
|
|
|
45
60
|
export const Default: Story = {
|
|
@@ -66,8 +81,18 @@ export const Empty: Story = {
|
|
|
66
81
|
export const AllInProgress: Story = {
|
|
67
82
|
args: {
|
|
68
83
|
features: [
|
|
69
|
-
{
|
|
70
|
-
|
|
84
|
+
{
|
|
85
|
+
featureId: 'feat-a-001',
|
|
86
|
+
name: 'Feature A',
|
|
87
|
+
status: 'in-progress' as const,
|
|
88
|
+
startedAt: Date.now() - 120_000,
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
featureId: 'feat-b-002',
|
|
92
|
+
name: 'Feature B',
|
|
93
|
+
status: 'in-progress' as const,
|
|
94
|
+
startedAt: Date.now() - 600_000,
|
|
95
|
+
},
|
|
71
96
|
],
|
|
72
97
|
},
|
|
73
98
|
};
|
|
@@ -75,9 +100,9 @@ export const AllInProgress: Story = {
|
|
|
75
100
|
export const AllDone: Story = {
|
|
76
101
|
args: {
|
|
77
102
|
features: [
|
|
78
|
-
{ name: 'Feature A', status: 'done' as const, duration: '30m' },
|
|
79
|
-
{ name: 'Feature B', status: 'done' as const, duration: '1h' },
|
|
80
|
-
{ name: 'Feature C', status: 'done' as const, duration: '3h' },
|
|
103
|
+
{ featureId: 'feat-a-001', name: 'Feature A', status: 'done' as const, duration: '30m' },
|
|
104
|
+
{ featureId: 'feat-b-002', name: 'Feature B', status: 'done' as const, duration: '1h' },
|
|
105
|
+
{ featureId: 'feat-c-003', name: 'Feature C', status: 'done' as const, duration: '3h' },
|
|
81
106
|
],
|
|
82
107
|
},
|
|
83
108
|
};
|
package/web/.next/standalone/src/presentation/web/components/layouts/app-sidebar/app-sidebar.tsx
CHANGED
|
@@ -25,7 +25,8 @@ import type { FeatureStatus } from '@/components/common/feature-status-config';
|
|
|
25
25
|
import { useDeferredMount } from '@/hooks/use-deferred-mount';
|
|
26
26
|
import { featureFlags } from '@/lib/feature-flags';
|
|
27
27
|
|
|
28
|
-
interface FeatureItem {
|
|
28
|
+
export interface FeatureItem {
|
|
29
|
+
featureId: string;
|
|
29
30
|
name: string;
|
|
30
31
|
status: FeatureStatus;
|
|
31
32
|
startedAt?: number;
|
|
@@ -35,7 +36,7 @@ interface FeatureItem {
|
|
|
35
36
|
export interface AppSidebarProps {
|
|
36
37
|
features: FeatureItem[];
|
|
37
38
|
onNewFeature?: () => void;
|
|
38
|
-
onFeatureClick?: (
|
|
39
|
+
onFeatureClick?: (featureId: string) => void;
|
|
39
40
|
onFeaturesFolderClick?: () => void;
|
|
40
41
|
onFeaturesMenuClick?: () => void;
|
|
41
42
|
}
|
|
@@ -119,12 +120,14 @@ export function AppSidebar({
|
|
|
119
120
|
<FeatureStatusGroup key={key} label={label} count={items.length}>
|
|
120
121
|
{items.map((feature) => (
|
|
121
122
|
<FeatureListItem
|
|
122
|
-
key={feature.
|
|
123
|
+
key={feature.featureId}
|
|
123
124
|
name={feature.name}
|
|
124
125
|
status={feature.status}
|
|
125
126
|
startedAt={feature.startedAt}
|
|
126
127
|
duration={feature.duration}
|
|
127
|
-
onClick={
|
|
128
|
+
onClick={
|
|
129
|
+
onFeatureClick ? () => onFeatureClick(feature.featureId) : undefined
|
|
130
|
+
}
|
|
128
131
|
/>
|
|
129
132
|
))}
|
|
130
133
|
</FeatureStatusGroup>
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { createContext, useContext, useState, useMemo, type ReactNode } from 'react';
|
|
4
|
+
import type { FeatureNodeState } from '@/components/common/feature-node/feature-node-state-config';
|
|
5
|
+
import type { FeatureStatus } from '@/components/common/feature-status-config';
|
|
6
|
+
|
|
7
|
+
// Re-export the FeatureItem type so consumers can import from one place.
|
|
8
|
+
// This will gain `featureId` in a later phase.
|
|
9
|
+
export interface SidebarFeatureItem {
|
|
10
|
+
name: string;
|
|
11
|
+
status: FeatureStatus;
|
|
12
|
+
featureId: string;
|
|
13
|
+
startedAt?: number;
|
|
14
|
+
duration?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Pure mapping: FeatureNodeState (6-state) → FeatureStatus (3-state) | null
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
const stateMapping: Record<FeatureNodeState, FeatureStatus | null> = {
|
|
22
|
+
'action-required': 'action-needed',
|
|
23
|
+
running: 'in-progress',
|
|
24
|
+
done: 'done',
|
|
25
|
+
blocked: 'in-progress',
|
|
26
|
+
error: 'in-progress',
|
|
27
|
+
creating: null,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Maps a canvas FeatureNodeState to the sidebar's 3-state FeatureStatus.
|
|
32
|
+
* Returns `null` for `creating` (optimistic UI) — these should be excluded from the sidebar.
|
|
33
|
+
*/
|
|
34
|
+
export function mapNodeStateToSidebarStatus(state: FeatureNodeState): FeatureStatus | null {
|
|
35
|
+
return stateMapping[state];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
// SidebarFeaturesContext
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
|
|
42
|
+
interface SidebarFeaturesContextValue {
|
|
43
|
+
features: SidebarFeatureItem[];
|
|
44
|
+
setFeatures: (features: SidebarFeatureItem[]) => void;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const SidebarFeaturesContext = createContext<SidebarFeaturesContextValue | null>(null);
|
|
48
|
+
|
|
49
|
+
interface SidebarFeaturesProviderProps {
|
|
50
|
+
children: ReactNode;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function SidebarFeaturesProvider({ children }: SidebarFeaturesProviderProps) {
|
|
54
|
+
const [features, setFeatures] = useState<SidebarFeatureItem[]>([]);
|
|
55
|
+
|
|
56
|
+
const value = useMemo<SidebarFeaturesContextValue>(() => ({ features, setFeatures }), [features]);
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<SidebarFeaturesContext.Provider value={value}>{children}</SidebarFeaturesContext.Provider>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function useSidebarFeaturesContext(): SidebarFeaturesContextValue {
|
|
64
|
+
const ctx = useContext(SidebarFeaturesContext);
|
|
65
|
+
if (!ctx) {
|
|
66
|
+
throw new Error('useSidebarFeaturesContext must be used within a <SidebarFeaturesProvider>');
|
|
67
|
+
}
|
|
68
|
+
return ctx;
|
|
69
|
+
}
|