@shepai/cli 1.45.0 → 1.47.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.
Files changed (90) hide show
  1. package/dist/src/presentation/web/app/actions/create-feature.d.ts +2 -0
  2. package/dist/src/presentation/web/app/actions/create-feature.d.ts.map +1 -1
  3. package/dist/src/presentation/web/app/actions/create-feature.js +3 -1
  4. package/dist/src/presentation/web/app/actions/get-workflow-defaults.d.ts +11 -0
  5. package/dist/src/presentation/web/app/actions/get-workflow-defaults.d.ts.map +1 -0
  6. package/dist/src/presentation/web/app/actions/get-workflow-defaults.js +15 -0
  7. package/dist/src/presentation/web/app/page.d.ts.map +1 -1
  8. package/dist/src/presentation/web/app/page.js +18 -10
  9. package/dist/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.d.ts +5 -1
  10. package/dist/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.d.ts.map +1 -1
  11. package/dist/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.js +25 -7
  12. package/dist/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.stories.d.ts +17 -0
  13. package/dist/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.stories.d.ts.map +1 -1
  14. package/dist/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.stories.js +54 -2
  15. package/dist/src/presentation/web/components/features/control-center/control-center-inner.d.ts.map +1 -1
  16. package/dist/src/presentation/web/components/features/control-center/control-center-inner.js +15 -1
  17. package/dist/src/presentation/web/lib/layout-with-dagre.d.ts.map +1 -1
  18. package/dist/src/presentation/web/lib/layout-with-dagre.js +3 -0
  19. package/dist/tsconfig.build.tsbuildinfo +1 -1
  20. package/package.json +1 -1
  21. package/web/.next/BUILD_ID +1 -1
  22. package/web/.next/build-manifest.json +2 -2
  23. package/web/.next/cache/.previewinfo +1 -1
  24. package/web/.next/cache/.rscinfo +1 -1
  25. package/web/.next/cache/.tsbuildinfo +1 -1
  26. package/web/.next/cache/config.json +3 -3
  27. package/web/.next/fallback-build-manifest.json +2 -2
  28. package/web/.next/prerender-manifest.json +3 -3
  29. package/web/.next/required-server-files.js +1 -1
  30. package/web/.next/required-server-files.json +1 -1
  31. package/web/.next/server/app/_global-error.html +2 -2
  32. package/web/.next/server/app/_global-error.rsc +1 -1
  33. package/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  34. package/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  35. package/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  36. package/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  37. package/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  38. package/web/.next/server/app/page/server-reference-manifest.json +37 -22
  39. package/web/.next/server/app/page.js +1 -1
  40. package/web/.next/server/app/page.js.nft.json +1 -1
  41. package/web/.next/server/app/page_client-reference-manifest.js +1 -1
  42. package/web/.next/server/chunks/ssr/[root-of-the-server]__03a0dba7._.js +4 -4
  43. package/web/.next/server/chunks/ssr/[root-of-the-server]__03a0dba7._.js.map +1 -1
  44. package/web/.next/server/chunks/ssr/[root-of-the-server]__249c74f6._.js +1 -1
  45. package/web/.next/server/chunks/ssr/[root-of-the-server]__d6d75c22._.js +9 -0
  46. package/web/.next/server/chunks/ssr/[root-of-the-server]__d6d75c22._.js.map +1 -0
  47. package/web/.next/server/chunks/ssr/src_presentation_web_ed0934e5._.js +1 -1
  48. package/web/.next/server/chunks/ssr/src_presentation_web_ed0934e5._.js.map +1 -1
  49. package/web/.next/server/pages/500.html +2 -2
  50. package/web/.next/server/server-reference-manifest.js +1 -1
  51. package/web/.next/server/server-reference-manifest.json +38 -23
  52. package/web/.next/standalone/src/presentation/web/.next/BUILD_ID +1 -1
  53. package/web/.next/standalone/src/presentation/web/.next/build-manifest.json +2 -2
  54. package/web/.next/standalone/src/presentation/web/.next/prerender-manifest.json +3 -3
  55. package/web/.next/standalone/src/presentation/web/.next/required-server-files.json +1 -1
  56. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.html +2 -2
  57. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.rsc +1 -1
  58. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  59. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  60. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  61. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  62. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  63. package/web/.next/standalone/src/presentation/web/.next/server/app/page/server-reference-manifest.json +37 -22
  64. package/web/.next/standalone/src/presentation/web/.next/server/app/page.js +1 -1
  65. package/web/.next/standalone/src/presentation/web/.next/server/app/page.js.nft.json +1 -1
  66. package/web/.next/standalone/src/presentation/web/.next/server/app/page_client-reference-manifest.js +1 -1
  67. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__03a0dba7._.js +4 -4
  68. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__249c74f6._.js +1 -1
  69. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__d6d75c22._.js +9 -0
  70. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_ed0934e5._.js +1 -1
  71. package/web/.next/standalone/src/presentation/web/.next/server/pages/500.html +2 -2
  72. package/web/.next/standalone/src/presentation/web/.next/server/server-reference-manifest.js +1 -1
  73. package/web/.next/standalone/src/presentation/web/.next/server/server-reference-manifest.json +38 -23
  74. package/web/.next/standalone/src/presentation/web/app/actions/create-feature.ts +5 -1
  75. package/web/.next/standalone/src/presentation/web/app/actions/get-workflow-defaults.ts +28 -0
  76. package/web/.next/standalone/src/presentation/web/app/page.tsx +19 -10
  77. package/web/.next/standalone/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.stories.tsx +74 -1
  78. package/web/.next/standalone/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.tsx +55 -5
  79. package/web/.next/standalone/src/presentation/web/components/features/control-center/control-center-inner.tsx +31 -0
  80. package/web/.next/standalone/src/presentation/web/lib/layout-with-dagre.ts +3 -0
  81. package/web/.next/standalone/src/presentation/web/server.js +1 -1
  82. package/web/.next/static/chunks/{22f9e39aab128063.js → 779c99975c85389e.js} +4 -4
  83. package/web/.next/trace +1 -1
  84. package/web/.next/trace-build +1 -1
  85. package/web/.next/server/chunks/ssr/[root-of-the-server]__4e8ca89c._.js +0 -9
  86. package/web/.next/server/chunks/ssr/[root-of-the-server]__4e8ca89c._.js.map +0 -1
  87. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__4e8ca89c._.js +0 -9
  88. /package/web/.next/static/{z34pr57Jw4sBfoDeMMuGn → wQ3vPV9I8JeacVNyp4HFg}/_buildManifest.js +0 -0
  89. /package/web/.next/static/{z34pr57Jw4sBfoDeMMuGn → wQ3vPV9I8JeacVNyp4HFg}/_clientMiddlewareManifest.json +0 -0
  90. /package/web/.next/static/{z34pr57Jw4sBfoDeMMuGn → wQ3vPV9I8JeacVNyp4HFg}/_ssgManifest.js +0 -0
@@ -13,6 +13,8 @@ interface CreateFeatureInput {
13
13
  allowPlan: boolean;
14
14
  allowMerge?: boolean;
15
15
  };
16
+ push?: boolean;
17
+ openPr?: boolean;
16
18
  }
17
19
  export declare function createFeature(input: CreateFeatureInput): Promise<{
18
20
  feature?: Feature;
@@ -1 +1 @@
1
- {"version":3,"file":"create-feature.d.ts","sourceRoot":"","sources":["../../../../../../src/presentation/web/app/actions/create-feature.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sCAAsC,CAAC;AAEpE,UAAU,UAAU;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAQD,UAAU,kBAAkB;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,UAAU,EAAE,CAAC;IAC3B,aAAa,CAAC,EAAE;QACd,QAAQ,EAAE,OAAO,CAAC;QAClB,SAAS,EAAE,OAAO,CAAC;QACnB,UAAU,CAAC,EAAE,OAAO,CAAC;KACtB,CAAC;CACH;AAqBD,wBAAsB,aAAa,CACjC,KAAK,EAAE,kBAAkB,GACxB,OAAO,CAAC;IAAE,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CA8BhD"}
1
+ {"version":3,"file":"create-feature.d.ts","sourceRoot":"","sources":["../../../../../../src/presentation/web/app/actions/create-feature.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sCAAsC,CAAC;AAEpE,UAAU,UAAU;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAQD,UAAU,kBAAkB;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,UAAU,EAAE,CAAC;IAC3B,aAAa,CAAC,EAAE;QACd,QAAQ,EAAE,OAAO,CAAC;QAClB,SAAS,EAAE,OAAO,CAAC;QACnB,UAAU,CAAC,EAAE,OAAO,CAAC;KACtB,CAAC;IACF,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAqBD,wBAAsB,aAAa,CACjC,KAAK,EAAE,kBAAkB,GACxB,OAAO,CAAC;IAAE,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAgChD"}
@@ -12,7 +12,7 @@ function composeUserInput(name, description, attachments) {
12
12
  return userInput;
13
13
  }
14
14
  export async function createFeature(input) {
15
- const { name, description, repositoryPath, attachments, approvalGates } = input;
15
+ const { name, description, repositoryPath, attachments, approvalGates, push, openPr } = input;
16
16
  if (!name?.trim()) {
17
17
  return { error: 'name is required' };
18
18
  }
@@ -31,6 +31,8 @@ export async function createFeature(input) {
31
31
  userInput,
32
32
  repositoryPath,
33
33
  approvalGates: gates,
34
+ push: push ?? false,
35
+ openPr: openPr ?? false,
34
36
  });
35
37
  return { feature: result.feature };
36
38
  }
@@ -0,0 +1,11 @@
1
+ export interface WorkflowDefaults {
2
+ approvalGates: {
3
+ allowPrd: boolean;
4
+ allowPlan: boolean;
5
+ allowMerge: boolean;
6
+ };
7
+ push: boolean;
8
+ openPr: boolean;
9
+ }
10
+ export declare function getWorkflowDefaults(): Promise<WorkflowDefaults>;
11
+ //# sourceMappingURL=get-workflow-defaults.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-workflow-defaults.d.ts","sourceRoot":"","sources":["../../../../../../src/presentation/web/app/actions/get-workflow-defaults.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,gBAAgB;IAC/B,aAAa,EAAE;QACb,QAAQ,EAAE,OAAO,CAAC;QAClB,SAAS,EAAE,OAAO,CAAC;QACnB,UAAU,EAAE,OAAO,CAAC;KACrB,CAAC;IACF,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,gBAAgB,CAAC,CAarE"}
@@ -0,0 +1,15 @@
1
+ 'use server';
2
+ import { getSettings } from '../../../../../packages/core/src/infrastructure/services/settings.service.js';
3
+ export async function getWorkflowDefaults() {
4
+ const settings = getSettings();
5
+ const { workflow } = settings;
6
+ return {
7
+ approvalGates: {
8
+ allowPrd: workflow.approvalGateDefaults.allowPrd,
9
+ allowPlan: workflow.approvalGateDefaults.allowPlan,
10
+ allowMerge: workflow.approvalGateDefaults.allowMerge,
11
+ },
12
+ push: workflow.approvalGateDefaults.pushOnImplementationComplete,
13
+ openPr: workflow.openPrOnImplementationComplete,
14
+ };
15
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"page.d.ts","sourceRoot":"","sources":["../../../../../src/presentation/web/app/page.tsx"],"names":[],"mappings":"AAcA,uFAAuF;AACvF,eAAO,MAAM,OAAO,kBAAkB,CAAC;AAqBvC,wBAA8B,QAAQ,qDA+GrC"}
1
+ {"version":3,"file":"page.d.ts","sourceRoot":"","sources":["../../../../../src/presentation/web/app/page.tsx"],"names":[],"mappings":"AAcA,uFAAuF;AACvF,eAAO,MAAM,OAAO,kBAAkB,CAAC;AAqBvC,wBAA8B,QAAQ,qDAwHrC"}
@@ -93,19 +93,27 @@ export default async function HomePage() {
93
93
  ranksep: 200,
94
94
  nodesep: 15,
95
95
  });
96
- // Position "+ Add Repository" vertically centered with the bottom-most feature node,
97
- // mirroring how Dagre centers repo nodes with their connected features.
96
+ // Position "+ Add Repository" below the last repo node (no-features case) or
97
+ // vertically centered with a bottom feature node (features case).
98
98
  const repoNodes = laid.nodes.filter((n) => n.type === 'repositoryNode');
99
99
  const featureNodes = laid.nodes.filter((n) => n.type === 'featureNode');
100
- const sortedFeatures = [...featureNodes].sort((a, b) => a.position.y - b.position.y);
101
- // Pick the feature node just below center (one past the midpoint index)
102
- const centerIdx = Math.floor(sortedFeatures.length / 2);
103
- const targetFeature = sortedFeatures[centerIdx + 1] ?? sortedFeatures[sortedFeatures.length - 1];
104
100
  const repoX = repoNodes[0]?.position.x ?? 0;
105
- // Center the add-repo node (h=50) with the target feature node (h=140)
106
- const addRepoPosition = targetFeature
107
- ? { x: repoX, y: targetFeature.position.y + 70 - 25 }
108
- : { x: repoX, y: (repoNodes[0]?.position.y ?? 0) + 150 };
101
+ let addRepoPosition;
102
+ if (featureNodes.length > 0) {
103
+ // Mirror how dagre centers repo nodes with their connected features
104
+ const sortedFeatures = [...featureNodes].sort((a, b) => a.position.y - b.position.y);
105
+ const centerIdx = Math.floor(sortedFeatures.length / 2);
106
+ const targetFeature = sortedFeatures[centerIdx + 1] ?? sortedFeatures[sortedFeatures.length - 1];
107
+ // Center the add-repo node (h=50) with the target feature node (h=140)
108
+ addRepoPosition = { x: repoX, y: targetFeature.position.y + 70 - 25 };
109
+ }
110
+ else {
111
+ // No features — place below the bottom-most repo node with a gap
112
+ const lastRepoY = repoNodes.length > 0 ? Math.max(...repoNodes.map((n) => n.position.y)) : 0;
113
+ const repoHeight = 50;
114
+ const gap = 200;
115
+ addRepoPosition = { x: repoX, y: lastRepoY + repoHeight + gap };
116
+ }
109
117
  laid.nodes.push({
110
118
  id: 'add-repository',
111
119
  type: 'addRepositoryNode',
@@ -1,4 +1,5 @@
1
1
  import type { FileAttachment } from '../../../../../../packages/core/src/infrastructure/services/file-dialog.service.js';
2
+ import type { WorkflowDefaults } from '../../../app/actions/get-workflow-defaults.js';
2
3
  export type { FileAttachment } from '../../../../../../packages/core/src/infrastructure/services/file-dialog.service.js';
3
4
  export interface FeatureCreatePayload {
4
5
  name: string;
@@ -10,6 +11,8 @@ export interface FeatureCreatePayload {
10
11
  allowPlan: boolean;
11
12
  allowMerge: boolean;
12
13
  };
14
+ push: boolean;
15
+ openPr: boolean;
13
16
  }
14
17
  export interface FeatureCreateDrawerProps {
15
18
  open: boolean;
@@ -17,6 +20,7 @@ export interface FeatureCreateDrawerProps {
17
20
  onSubmit: (data: FeatureCreatePayload) => void;
18
21
  repositoryPath: string;
19
22
  isSubmitting?: boolean;
23
+ workflowDefaults?: WorkflowDefaults;
20
24
  }
21
- export declare function FeatureCreateDrawer({ open, onClose, onSubmit, repositoryPath, isSubmitting, }: FeatureCreateDrawerProps): import("react/jsx-runtime").JSX.Element;
25
+ export declare function FeatureCreateDrawer({ open, onClose, onSubmit, repositoryPath, isSubmitting, workflowDefaults, }: FeatureCreateDrawerProps): import("react/jsx-runtime").JSX.Element;
22
26
  //# sourceMappingURL=feature-create-drawer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"feature-create-drawer.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.tsx"],"names":[],"mappings":"AA6BA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0DAA0D,CAAC;AAG/F,YAAY,EAAE,cAAc,EAAE,MAAM,0DAA0D,CAAC;AAE/F,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,cAAc,EAAE,CAAC;IAC9B,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE;QACb,QAAQ,EAAE,OAAO,CAAC;QAClB,SAAS,EAAE,OAAO,CAAC;QACnB,UAAU,EAAE,OAAO,CAAC;KACrB,CAAC;CACH;AAcD,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,EAAE,CAAC,IAAI,EAAE,oBAAoB,KAAK,IAAI,CAAC;IAC/C,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,wBAAgB,mBAAmB,CAAC,EAClC,IAAI,EACJ,OAAO,EACP,QAAQ,EACR,cAAc,EACd,YAAoB,GACrB,EAAE,wBAAwB,2CA+L1B"}
1
+ {"version":3,"file":"feature-create-drawer.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.tsx"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0DAA0D,CAAC;AAC/F,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qCAAqC,CAAC;AAG5E,YAAY,EAAE,cAAc,EAAE,MAAM,0DAA0D,CAAC;AAE/F,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,cAAc,EAAE,CAAC;IAC9B,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE;QACb,QAAQ,EAAE,OAAO,CAAC;QAClB,SAAS,EAAE,OAAO,CAAC;QACnB,UAAU,EAAE,OAAO,CAAC;KACrB,CAAC;IACF,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;CACjB;AAcD,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,EAAE,CAAC,IAAI,EAAE,oBAAoB,KAAK,IAAI,CAAC;IAC/C,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;CACrC;AAED,wBAAgB,mBAAmB,CAAC,EAClC,IAAI,EACJ,OAAO,EACP,QAAQ,EACR,cAAc,EACd,YAAoB,EACpB,gBAAgB,GACjB,EAAE,wBAAwB,2CA2O1B"}
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useState, useCallback } from 'react';
3
+ import { useState, useCallback, useEffect } from 'react';
4
4
  import { XIcon, PlusIcon, FileIcon, FileTextIcon, ImageIcon, CodeIcon, Trash2Icon, } from 'lucide-react';
5
5
  import { cn } from '../../../lib/utils.js';
6
6
  import { Drawer, DrawerContent, DrawerHeader, DrawerTitle, DrawerDescription, DrawerFooter, } from '../../ui/drawer.js';
@@ -9,6 +9,7 @@ import { Input } from '../../ui/input.js';
9
9
  import { Textarea } from '../../ui/textarea.js';
10
10
  import { Label } from '../../ui/label.js';
11
11
  import { CheckboxGroup } from '../../ui/checkbox-group.js';
12
+ import { CheckboxGroupItem } from '../../ui/checkbox-group-item.js';
12
13
  import { Separator } from '../../ui/separator.js';
13
14
  import { Badge } from '../../ui/badge.js';
14
15
  import { pickFiles } from './pick-files.js';
@@ -22,17 +23,32 @@ const EMPTY_GATES = {
22
23
  allowPlan: false,
23
24
  allowMerge: false,
24
25
  };
25
- export function FeatureCreateDrawer({ open, onClose, onSubmit, repositoryPath, isSubmitting = false, }) {
26
+ export function FeatureCreateDrawer({ open, onClose, onSubmit, repositoryPath, isSubmitting = false, workflowDefaults, }) {
27
+ const defaultGates = workflowDefaults?.approvalGates ?? EMPTY_GATES;
28
+ const defaultPush = workflowDefaults?.push ?? false;
29
+ const defaultOpenPr = workflowDefaults?.openPr ?? false;
26
30
  const [name, setName] = useState('');
27
31
  const [description, setDescription] = useState('');
28
32
  const [attachments, setAttachments] = useState([]);
29
- const [approvalGates, setApprovalGates] = useState({ ...EMPTY_GATES });
33
+ const [approvalGates, setApprovalGates] = useState({ ...defaultGates });
34
+ const [push, setPush] = useState(defaultPush);
35
+ const [openPr, setOpenPr] = useState(defaultOpenPr);
36
+ // Sync state when workflowDefaults load asynchronously
37
+ useEffect(() => {
38
+ if (workflowDefaults) {
39
+ setApprovalGates({ ...workflowDefaults.approvalGates });
40
+ setPush(workflowDefaults.push);
41
+ setOpenPr(workflowDefaults.openPr);
42
+ }
43
+ }, [workflowDefaults]);
30
44
  const resetForm = useCallback(() => {
31
45
  setName('');
32
46
  setDescription('');
33
47
  setAttachments([]);
34
- setApprovalGates({ ...EMPTY_GATES });
35
- }, []);
48
+ setApprovalGates({ ...defaultGates });
49
+ setPush(defaultPush);
50
+ setOpenPr(defaultOpenPr);
51
+ }, [defaultGates, defaultPush, defaultOpenPr]);
36
52
  const handleOpenChange = useCallback((nextOpen) => {
37
53
  if (!nextOpen) {
38
54
  resetForm();
@@ -54,8 +70,10 @@ export function FeatureCreateDrawer({ open, onClose, onSubmit, repositoryPath, i
54
70
  allowPlan: approvalGates.allowPlan ?? false,
55
71
  allowMerge: approvalGates.allowMerge ?? false,
56
72
  },
73
+ push: push || openPr,
74
+ openPr,
57
75
  });
58
- }, [name, description, attachments, approvalGates, repositoryPath, onSubmit]);
76
+ }, [name, description, attachments, approvalGates, repositoryPath, onSubmit, push, openPr]);
59
77
  const handleAddFiles = useCallback(async () => {
60
78
  try {
61
79
  const files = await pickFiles();
@@ -70,7 +88,7 @@ export function FeatureCreateDrawer({ open, onClose, onSubmit, repositoryPath, i
70
88
  const handleRemoveFile = useCallback((path) => {
71
89
  setAttachments((prev) => prev.filter((f) => f.path !== path));
72
90
  }, []);
73
- return (_jsx(Drawer, { direction: "right", modal: false, handleOnly: true, open: open, onOpenChange: handleOpenChange, children: _jsxs(DrawerContent, { direction: "right", className: "w-96", showCloseButton: false, children: [_jsx("button", { type: "button", "aria-label": "Close", onClick: onClose, className: "ring-offset-background focus:ring-ring absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden", children: _jsx(XIcon, { className: "size-4" }) }), _jsxs(DrawerHeader, { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "h-2.5 w-2.5 shrink-0 rounded-full bg-blue-500" }), _jsx(DrawerTitle, { children: "NEW FEATURE" })] }), _jsx(DrawerDescription, { asChild: true, children: _jsx("div", { children: _jsx(Badge, { variant: "secondary", children: "Creating..." }) }) })] }), _jsx(Separator, {}), _jsx("div", { className: "flex-1 overflow-y-auto p-4", children: _jsxs("form", { id: "create-feature-form", onSubmit: handleSubmit, className: "flex flex-col gap-4", children: [_jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(Label, { htmlFor: "feature-name", className: "text-muted-foreground text-xs font-semibold tracking-wider", children: "FEATURE NAME" }), _jsx(Input, { id: "feature-name", placeholder: "e.g. GitHub OAuth Login", value: name, onChange: (e) => setName(e.target.value), required: true, disabled: isSubmitting })] }), _jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(Label, { htmlFor: "feature-description", className: "text-muted-foreground text-xs font-semibold tracking-wider", children: "DESCRIPTION" }), _jsx(Textarea, { id: "feature-description", placeholder: "Describe what this feature does...", value: description, onChange: (e) => setDescription(e.target.value), rows: 4, disabled: isSubmitting })] }), _jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(Label, { className: "text-muted-foreground text-xs font-semibold tracking-wider", children: "APPROVE" }), _jsx(CheckboxGroup, { label: "Autonomous Mode", description: "YOLO!", parentAriaLabel: "Auto approve all", options: AUTO_APPROVE_OPTIONS, value: approvalGates, onValueChange: setApprovalGates, disabled: isSubmitting })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs(Label, { className: "text-muted-foreground text-xs font-semibold tracking-wider", children: ["ATTACHMENTS", attachments.length > 0 && (_jsxs("span", { className: "text-muted-foreground/60 ml-1.5", children: ["(", attachments.length, ")"] }))] }), _jsxs(Button, { type: "button", variant: "outline", size: "xs", onClick: handleAddFiles, disabled: isSubmitting, children: [_jsx(PlusIcon, { className: "size-3" }), "Add Files"] })] }), attachments.length > 0 && (_jsx("div", { className: "flex flex-col gap-2", children: attachments.map((file) => (_jsx(AttachmentCard, { file: file, onRemove: () => handleRemoveFile(file.path), disabled: isSubmitting }, file.path))) }))] })] }) }), _jsx(Separator, {}), _jsxs(DrawerFooter, { className: "flex-row justify-end gap-2", children: [_jsx(Button, { variant: "outline", onClick: onClose, disabled: isSubmitting, children: "Cancel" }), _jsx(Button, { type: "submit", form: "create-feature-form", disabled: !name.trim() || isSubmitting, children: isSubmitting ? 'Creating...' : '+ Create Feature' })] })] }) }));
91
+ return (_jsx(Drawer, { direction: "right", modal: false, handleOnly: true, open: open, onOpenChange: handleOpenChange, children: _jsxs(DrawerContent, { direction: "right", className: "w-96", showCloseButton: false, children: [_jsx("button", { type: "button", "aria-label": "Close", onClick: onClose, className: "ring-offset-background focus:ring-ring absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden", children: _jsx(XIcon, { className: "size-4" }) }), _jsxs(DrawerHeader, { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "h-2.5 w-2.5 shrink-0 rounded-full bg-blue-500" }), _jsx(DrawerTitle, { children: "NEW FEATURE" })] }), _jsx(DrawerDescription, { asChild: true, children: _jsx("div", { children: _jsx(Badge, { variant: "secondary", children: "Creating..." }) }) })] }), _jsx(Separator, {}), _jsx("div", { className: "flex-1 overflow-y-auto p-4", children: _jsxs("form", { id: "create-feature-form", onSubmit: handleSubmit, className: "flex flex-col gap-4", children: [_jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(Label, { htmlFor: "feature-name", className: "text-muted-foreground text-xs font-semibold tracking-wider", children: "FEATURE NAME" }), _jsx(Input, { id: "feature-name", placeholder: "e.g. GitHub OAuth Login", value: name, onChange: (e) => setName(e.target.value), required: true, disabled: isSubmitting })] }), _jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(Label, { htmlFor: "feature-description", className: "text-muted-foreground text-xs font-semibold tracking-wider", children: "DESCRIPTION" }), _jsx(Textarea, { id: "feature-description", placeholder: "Describe what this feature does...", value: description, onChange: (e) => setDescription(e.target.value), rows: 4, disabled: isSubmitting })] }), _jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(Label, { className: "text-muted-foreground text-xs font-semibold tracking-wider", children: "APPROVE" }), _jsx(CheckboxGroup, { label: "Autonomous Mode", description: "YOLO!", parentAriaLabel: "Auto approve all", options: AUTO_APPROVE_OPTIONS, value: approvalGates, onValueChange: setApprovalGates, disabled: isSubmitting })] }), _jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(Label, { className: "text-muted-foreground text-xs font-semibold tracking-wider", children: "GIT" }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(CheckboxGroupItem, { id: "push", label: "Push", description: "Push branch to remote after implementation.", checked: push || openPr, onCheckedChange: setPush, disabled: openPr || isSubmitting }), _jsx(CheckboxGroupItem, { id: "open-pr", label: "Create PR", description: "Open a pull request after pushing.", checked: openPr, onCheckedChange: setOpenPr, disabled: isSubmitting })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs(Label, { className: "text-muted-foreground text-xs font-semibold tracking-wider", children: ["ATTACHMENTS", attachments.length > 0 && (_jsxs("span", { className: "text-muted-foreground/60 ml-1.5", children: ["(", attachments.length, ")"] }))] }), _jsxs(Button, { type: "button", variant: "outline", size: "xs", onClick: handleAddFiles, disabled: isSubmitting, children: [_jsx(PlusIcon, { className: "size-3" }), "Add Files"] })] }), attachments.length > 0 && (_jsx("div", { className: "flex flex-col gap-2", children: attachments.map((file) => (_jsx(AttachmentCard, { file: file, onRemove: () => handleRemoveFile(file.path), disabled: isSubmitting }, file.path))) }))] })] }) }), _jsx(Separator, {}), _jsxs(DrawerFooter, { className: "flex-row justify-end gap-2", children: [_jsx(Button, { variant: "outline", onClick: onClose, disabled: isSubmitting, children: "Cancel" }), _jsx(Button, { type: "submit", form: "create-feature-form", disabled: !name.trim() || isSubmitting, children: isSubmitting ? 'Creating...' : '+ Create Feature' })] })] }) }));
74
92
  }
75
93
  /* ---------------------------------------------------------------------------
76
94
  * Private sub-components & utilities
@@ -67,6 +67,23 @@ export declare const PrdOnly: Story;
67
67
  * Manual review for requirements and planning, auto-approve merge to Done.
68
68
  */
69
69
  export declare const MergeOnly: Story;
70
+ /**
71
+ * Push checkbox checked — the "Push" option is enabled so the branch will be
72
+ * pushed to remote after implementation. "Create PR" remains unchecked.
73
+ */
74
+ export declare const PushOnly: Story;
75
+ /**
76
+ * Create PR checked — checking "Create PR" auto-checks and disables "Push"
77
+ * because a PR cannot be created without pushing. Both checkboxes appear
78
+ * checked, but "Push" is non-interactive (disabled).
79
+ */
80
+ export declare const PrChecked: Story;
81
+ /**
82
+ * Drawer pre-populated from workflow settings — PRD and Plan approval gates
83
+ * are checked, Push and Create PR are enabled. These values come from
84
+ * `shep settings workflow` and are read at mount time via `getWorkflowDefaults()`.
85
+ */
86
+ export declare const WithWorkflowDefaults: Story;
70
87
  /**
71
88
  * Fully interactive story — open the drawer, fill the form, toggle checkboxes,
72
89
  * and click "Add Files" to attach files via the native OS file picker.
@@ -1 +1 @@
1
- {"version":3,"file":"feature-create-drawer.stories.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.stories.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEvD,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAQ9D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,OAAO,mBAAmB,CAyB1C,CAAC;AAEF,eAAe,IAAI,CAAC;AACpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,mBAAmB,CAAC,CAAC;AA0ClD,uFAAuF;AACvF,eAAO,MAAM,OAAO,EAAE,KAErB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,SAAS,EAAE,KAiBvB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,kBAAkB,EAAE,KAMhC,CAAC;AAEF,4DAA4D;AAC5D,eAAO,MAAM,SAAS,EAAE,KAMvB,CAAC;AAMF;;;GAGG;AACH,eAAO,MAAM,YAAY,EAAE,KAM1B,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,UAAU,EAAE,KAUxB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,OAAO,EAAE,KAUrB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,SAAS,EAAE,KAUvB,CAAC;AAMF;;;;;;;;;GASG;AACH,eAAO,MAAM,WAAW,EAAE,KAsCzB,CAAC"}
1
+ {"version":3,"file":"feature-create-drawer.stories.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.stories.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEvD,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAS9D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,OAAO,mBAAmB,CAyB1C,CAAC;AAEF,eAAe,IAAI,CAAC;AACpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAiDlD,uFAAuF;AACvF,eAAO,MAAM,OAAO,EAAE,KAErB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,SAAS,EAAE,KAiBvB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,kBAAkB,EAAE,KAMhC,CAAC;AAEF,4DAA4D;AAC5D,eAAO,MAAM,SAAS,EAAE,KAMvB,CAAC;AAMF;;;GAGG;AACH,eAAO,MAAM,YAAY,EAAE,KAM1B,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,UAAU,EAAE,KAUxB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,OAAO,EAAE,KAUrB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,SAAS,EAAE,KAUvB,CAAC;AAMF;;;GAGG;AACH,eAAO,MAAM,QAAQ,EAAE,KAUtB,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,SAAS,EAAE,KAUvB,CAAC;AAYF;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,EAAE,KAWlC,CAAC;AAMF;;;;;;;;;GASG;AACH,eAAO,MAAM,WAAW,EAAE,KAsCzB,CAAC"}
@@ -70,7 +70,7 @@ const logClose = fn().mockName('onClose');
70
70
  * Trigger wrapper — every story uses this so nothing auto-opens on Docs page
71
71
  * ------------------------------------------------------------------------- */
72
72
  /** Starts closed — click button to open. Actions are logged. */
73
- function CreateDrawerTrigger({ label = 'Open Create Feature' }) {
73
+ function CreateDrawerTrigger({ label = 'Open Create Feature', workflowDefaults, }) {
74
74
  const [open, setOpen] = useState(false);
75
75
  return (_jsxs("div", { className: "flex h-screen items-start p-4", children: [_jsx(Button, { variant: "outline", onClick: () => setOpen(true), children: label }), _jsx(FeatureCreateDrawer, { open: open, onClose: () => {
76
76
  setOpen(false);
@@ -78,7 +78,7 @@ function CreateDrawerTrigger({ label = 'Open Create Feature' }) {
78
78
  }, onSubmit: (data) => {
79
79
  logSubmit(data);
80
80
  setOpen(false);
81
- }, repositoryPath: "/Users/dev/my-repo" })] }));
81
+ }, repositoryPath: "/Users/dev/my-repo", workflowDefaults: workflowDefaults })] }));
82
82
  }
83
83
  /* ---------------------------------------------------------------------------
84
84
  * Per-state stories
@@ -179,6 +179,58 @@ export const MergeOnly = {
179
179
  await userEvent.click(merge);
180
180
  },
181
181
  };
182
+ /* ---------------------------------------------------------------------------
183
+ * Git checkbox stories
184
+ * ------------------------------------------------------------------------- */
185
+ /**
186
+ * Push checkbox checked — the "Push" option is enabled so the branch will be
187
+ * pushed to remote after implementation. "Create PR" remains unchecked.
188
+ */
189
+ export const PushOnly = {
190
+ render: () => _jsx(CreateDrawerTrigger, { label: "Open (Push Only)" }),
191
+ play: async ({ canvasElement }) => {
192
+ const canvas = within(canvasElement);
193
+ await userEvent.click(canvas.getByRole('button', { name: 'Open (Push Only)' }));
194
+ const body = within(canvasElement.ownerDocument.body);
195
+ const pushCheckbox = await body.findByLabelText('Push');
196
+ await userEvent.click(pushCheckbox);
197
+ },
198
+ };
199
+ /**
200
+ * Create PR checked — checking "Create PR" auto-checks and disables "Push"
201
+ * because a PR cannot be created without pushing. Both checkboxes appear
202
+ * checked, but "Push" is non-interactive (disabled).
203
+ */
204
+ export const PrChecked = {
205
+ render: () => _jsx(CreateDrawerTrigger, { label: "Open (PR Checked)" }),
206
+ play: async ({ canvasElement }) => {
207
+ const canvas = within(canvasElement);
208
+ await userEvent.click(canvas.getByRole('button', { name: 'Open (PR Checked)' }));
209
+ const body = within(canvasElement.ownerDocument.body);
210
+ const prCheckbox = await body.findByLabelText('Create PR');
211
+ await userEvent.click(prCheckbox);
212
+ },
213
+ };
214
+ /* ---------------------------------------------------------------------------
215
+ * Workflow defaults stories
216
+ * ------------------------------------------------------------------------- */
217
+ const SAMPLE_WORKFLOW_DEFAULTS = {
218
+ approvalGates: { allowPrd: true, allowPlan: true, allowMerge: false },
219
+ push: true,
220
+ openPr: true,
221
+ };
222
+ /**
223
+ * Drawer pre-populated from workflow settings — PRD and Plan approval gates
224
+ * are checked, Push and Create PR are enabled. These values come from
225
+ * `shep settings workflow` and are read at mount time via `getWorkflowDefaults()`.
226
+ */
227
+ export const WithWorkflowDefaults = {
228
+ render: () => (_jsx(CreateDrawerTrigger, { label: "Open (Workflow Defaults)", workflowDefaults: SAMPLE_WORKFLOW_DEFAULTS })),
229
+ play: async ({ canvasElement }) => {
230
+ const canvas = within(canvasElement);
231
+ await userEvent.click(canvas.getByRole('button', { name: 'Open (Workflow Defaults)' }));
232
+ },
233
+ };
182
234
  /* ---------------------------------------------------------------------------
183
235
  * Interactive story — full paths shown in submitted data panel
184
236
  * ------------------------------------------------------------------------- */
@@ -1 +1 @@
1
- {"version":3,"file":"control-center-inner.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/features/control-center/control-center-inner.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAO1C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAC;AAS5E,UAAU,uBAAuB;IAC/B,YAAY,EAAE,cAAc,EAAE,CAAC;IAC/B,YAAY,EAAE,IAAI,EAAE,CAAC;CACtB;AAED,wBAAgB,kBAAkB,CAAC,EAAE,YAAY,EAAE,YAAY,EAAE,EAAE,uBAAuB,2CA8OzF"}
1
+ {"version":3,"file":"control-center-inner.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/features/control-center/control-center-inner.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAS1C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAC;AAS5E,UAAU,uBAAuB;IAC/B,YAAY,EAAE,cAAc,EAAE,CAAC;IAC/B,YAAY,EAAE,IAAI,EAAE,CAAC;CACtB;AAED,wBAAgB,kBAAkB,CAAC,EAAE,YAAY,EAAE,YAAY,EAAE,EAAE,uBAAuB,2CA2QzF"}
@@ -5,6 +5,7 @@ import { toast } from 'sonner';
5
5
  import { approveFeature } from '../../../app/actions/approve-feature.js';
6
6
  import { getFeatureArtifact } from '../../../app/actions/get-feature-artifact.js';
7
7
  import { getResearchArtifact } from '../../../app/actions/get-research-artifact.js';
8
+ import { getWorkflowDefaults } from '../../../app/actions/get-workflow-defaults.js';
8
9
  import { FeaturesCanvas } from '../../features/features-canvas/index.js';
9
10
  import { FeatureDrawer, FeatureCreateDrawer } from '../../common/index.js';
10
11
  import { PrdQuestionnaireDrawer } from '../../common/prd-questionnaire/index.js';
@@ -14,6 +15,15 @@ import { ControlCenterEmptyState } from './control-center-empty-state.js';
14
15
  import { useControlCenterState } from './use-control-center-state.js';
15
16
  export function ControlCenterInner({ initialNodes, initialEdges }) {
16
17
  const { nodes, edges, selectedNode, isCreateDrawerOpen, isDeleting, pendingRepositoryPath, onNodesChange, handleConnect, handleAddFeature, handleAddFeatureToRepo, handleAddFeatureToFeature, handleAddRepository, handleNodeClick, clearSelection, handleCreateFeatureSubmit, handleDeleteFeature, handleDeleteRepository, closeCreateDrawer, } = useControlCenterState(initialNodes, initialEdges);
18
+ // Workflow defaults for the create-feature drawer
19
+ const [workflowDefaults, setWorkflowDefaults] = useState();
20
+ useEffect(() => {
21
+ getWorkflowDefaults()
22
+ .then(setWorkflowDefaults)
23
+ .catch(() => {
24
+ // Settings unavailable — drawer falls back to all-false defaults
25
+ });
26
+ }, []);
17
27
  // PRD questionnaire drawer state
18
28
  const [prdSelections, setPrdSelections] = useState({});
19
29
  const [isPrdProcessing, setIsPrdProcessing] = useState(false);
@@ -138,11 +148,15 @@ export function ControlCenterInner({ initialNodes, initialEdges }) {
138
148
  cancelled = true;
139
149
  };
140
150
  }, [techDecisionsFeatureId]);
151
+ const hasRepositories = nodes.some((n) => n.type === 'repositoryNode');
141
152
  // Listen for global "open create drawer" events from the sidebar
142
153
  useEffect(() => {
143
154
  const handler = () => handleAddFeature();
144
155
  window.addEventListener('shep:open-create-drawer', handler);
145
156
  return () => window.removeEventListener('shep:open-create-drawer', handler);
146
157
  }, [handleAddFeature]);
147
- return (_jsxs(_Fragment, { children: [_jsx(NotificationPermissionBanner, {}), _jsx(FeaturesCanvas, { nodes: nodes, edges: edges, onNodesChange: onNodesChange, onConnect: handleConnect, onAddFeature: handleAddFeature, onNodeAction: handleAddFeatureToFeature, onNodeClick: handleNodeClick, onPaneClick: clearSelection, onRepositoryAdd: handleAddFeatureToRepo, onRepositoryDelete: handleDeleteRepository, onRepositorySelect: handleAddRepository, emptyState: _jsx(ControlCenterEmptyState, { onRepositorySelect: handleAddRepository }) }), _jsx(FeatureDrawer, { selectedNode: showPrdDrawer || showTechDecisionsDrawer ? null : selectedNode, onClose: clearSelection, onDelete: handleDeleteFeature, isDeleting: isDeleting }), questionnaireData ? (_jsx(PrdQuestionnaireDrawer, { open: showPrdDrawer, onClose: clearSelection, featureName: selectedNode?.name ?? '', featureDescription: selectedNode?.description, featureId: selectedNode?.featureId, repositoryPath: selectedNode?.repositoryPath, branch: selectedNode?.branch, specPath: selectedNode?.specPath, data: questionnaireData, selections: prdSelections, onSelect: handlePrdSelect, onRefine: handlePrdRefine, onApprove: handlePrdApprove, onDelete: handleDeleteFeature, isDeleting: isDeleting, isProcessing: isPrdProcessing || isLoadingQuestionnaire })) : null, techDecisionsData ? (_jsx(TechDecisionsDrawer, { open: showTechDecisionsDrawer, onClose: clearSelection, featureName: selectedNode?.name ?? '', featureId: selectedNode?.featureId, repositoryPath: selectedNode?.repositoryPath, branch: selectedNode?.branch, specPath: selectedNode?.specPath, data: techDecisionsData, onRefine: handleTechDecisionsRefine, onApprove: handleTechDecisionsApprove, onDelete: handleDeleteFeature, isDeleting: isDeleting, isProcessing: isLoadingTechDecisions })) : null, _jsx(FeatureCreateDrawer, { open: isCreateDrawerOpen, onClose: closeCreateDrawer, onSubmit: handleCreateFeatureSubmit, repositoryPath: pendingRepositoryPath })] }));
158
+ if (!hasRepositories) {
159
+ return (_jsxs(_Fragment, { children: [_jsx(NotificationPermissionBanner, {}), _jsx(ControlCenterEmptyState, { onRepositorySelect: handleAddRepository }), _jsx(FeatureCreateDrawer, { open: isCreateDrawerOpen, onClose: closeCreateDrawer, onSubmit: handleCreateFeatureSubmit, repositoryPath: pendingRepositoryPath, workflowDefaults: workflowDefaults })] }));
160
+ }
161
+ return (_jsxs(_Fragment, { children: [_jsx(NotificationPermissionBanner, {}), _jsx(FeaturesCanvas, { nodes: nodes, edges: edges, onNodesChange: onNodesChange, onConnect: handleConnect, onAddFeature: handleAddFeature, onNodeAction: handleAddFeatureToFeature, onNodeClick: handleNodeClick, onPaneClick: clearSelection, onRepositoryAdd: handleAddFeatureToRepo, onRepositoryDelete: handleDeleteRepository, onRepositorySelect: handleAddRepository, emptyState: _jsx(ControlCenterEmptyState, { onRepositorySelect: handleAddRepository }) }), _jsx(FeatureDrawer, { selectedNode: showPrdDrawer || showTechDecisionsDrawer ? null : selectedNode, onClose: clearSelection, onDelete: handleDeleteFeature, isDeleting: isDeleting }), questionnaireData ? (_jsx(PrdQuestionnaireDrawer, { open: showPrdDrawer, onClose: clearSelection, featureName: selectedNode?.name ?? '', featureDescription: selectedNode?.description, featureId: selectedNode?.featureId, repositoryPath: selectedNode?.repositoryPath, branch: selectedNode?.branch, specPath: selectedNode?.specPath, data: questionnaireData, selections: prdSelections, onSelect: handlePrdSelect, onRefine: handlePrdRefine, onApprove: handlePrdApprove, onDelete: handleDeleteFeature, isDeleting: isDeleting, isProcessing: isPrdProcessing || isLoadingQuestionnaire })) : null, techDecisionsData ? (_jsx(TechDecisionsDrawer, { open: showTechDecisionsDrawer, onClose: clearSelection, featureName: selectedNode?.name ?? '', featureId: selectedNode?.featureId, repositoryPath: selectedNode?.repositoryPath, branch: selectedNode?.branch, specPath: selectedNode?.specPath, data: techDecisionsData, onRefine: handleTechDecisionsRefine, onApprove: handleTechDecisionsApprove, onDelete: handleDeleteFeature, isDeleting: isDeleting, isProcessing: isLoadingTechDecisions })) : null, _jsx(FeatureCreateDrawer, { open: isCreateDrawerOpen, onClose: closeCreateDrawer, onSubmit: handleCreateFeatureSubmit, repositoryPath: pendingRepositoryPath, workflowDefaults: workflowDefaults })] }));
148
162
  }
@@ -1 +1 @@
1
- {"version":3,"file":"layout-with-dagre.d.ts","sourceRoot":"","sources":["../../../../../src/presentation/web/lib/layout-with-dagre.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAEhD,MAAM,MAAM,eAAe,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAExD,MAAM,WAAW,aAAa;IAC5B,iEAAiE;IACjE,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B,0DAA0D;IAC1D,QAAQ,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC7C,qDAAqD;IACrD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,wEAAwE;IACxE,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAmCD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,IAAI,EAC5C,KAAK,EAAE,CAAC,EAAE,EACV,KAAK,EAAE,IAAI,EAAE,EACb,IAAI,GAAE,aAAkB,GACvB;IAAE,KAAK,EAAE,CAAC,EAAE,CAAC;IAAC,KAAK,EAAE,IAAI,EAAE,CAAA;CAAE,CA+E/B"}
1
+ {"version":3,"file":"layout-with-dagre.d.ts","sourceRoot":"","sources":["../../../../../src/presentation/web/lib/layout-with-dagre.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAEhD,MAAM,MAAM,eAAe,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAExD,MAAM,WAAW,aAAa;IAC5B,iEAAiE;IACjE,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B,0DAA0D;IAC1D,QAAQ,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC7C,qDAAqD;IACrD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,wEAAwE;IACxE,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAmCD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,IAAI,EAC5C,KAAK,EAAE,CAAC,EAAE,EACV,KAAK,EAAE,IAAI,EAAE,EACb,IAAI,GAAE,aAAkB,GACvB;IAAE,KAAK,EAAE,CAAC,EAAE,CAAC;IAAC,KAAK,EAAE,IAAI,EAAE,CAAA;CAAE,CAkF/B"}
@@ -83,6 +83,9 @@ export function layoutWithDagre(nodes, edges, opts = {}) {
83
83
  maxY = Math.max(maxY, n.position.y + size.height);
84
84
  minX = Math.min(minX, n.position.x);
85
85
  }
86
+ // When no graph nodes exist (all disconnected), default to origin
87
+ if (minX === Infinity)
88
+ minX = 0;
86
89
  for (let i = 0; i < disconnectedNodes.length; i++) {
87
90
  const node = disconnectedNodes[i];
88
91
  const size = getNodeSize(node, nodeSize);