@shepai/cli 1.122.0 → 1.122.1-pr361.35a9abd

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 (148) hide show
  1. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/feature-agent-graph.d.ts.map +1 -1
  2. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/feature-agent-graph.js +21 -4
  3. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/validate.node.d.ts +7 -1
  4. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/validate.node.d.ts.map +1 -1
  5. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/validate.node.js +24 -2
  6. package/dist/src/presentation/web/components/common/control-center-drawer/feature-drawer-client.d.ts.map +1 -1
  7. package/dist/src/presentation/web/components/common/control-center-drawer/feature-drawer-client.js +4 -0
  8. package/dist/src/presentation/web/components/features/control-center/control-center-inner.d.ts.map +1 -1
  9. package/dist/src/presentation/web/components/features/control-center/control-center-inner.js +53 -11
  10. package/dist/src/presentation/web/components/features/control-center/use-control-center-state.d.ts.map +1 -1
  11. package/dist/src/presentation/web/components/features/control-center/use-control-center-state.js +6 -2
  12. package/dist/src/presentation/web/hooks/use-viewport-persistence.d.ts +2 -0
  13. package/dist/src/presentation/web/hooks/use-viewport-persistence.d.ts.map +1 -1
  14. package/dist/src/presentation/web/hooks/use-viewport-persistence.js +19 -6
  15. package/dist/src/presentation/web/lib/layout-with-dagre.d.ts.map +1 -1
  16. package/dist/src/presentation/web/lib/layout-with-dagre.js +11 -2
  17. package/dist/tsconfig.build.tsbuildinfo +1 -1
  18. package/package.json +1 -1
  19. package/web/.next/BUILD_ID +1 -1
  20. package/web/.next/build-manifest.json +2 -2
  21. package/web/.next/fallback-build-manifest.json +2 -2
  22. package/web/.next/prerender-manifest.json +3 -3
  23. package/web/.next/required-server-files.js +3 -3
  24. package/web/.next/required-server-files.json +3 -3
  25. package/web/.next/server/app/(dashboard)/@drawer/create/page/server-reference-manifest.json +22 -22
  26. package/web/.next/server/app/(dashboard)/@drawer/create/page.js.nft.json +1 -1
  27. package/web/.next/server/app/(dashboard)/@drawer/create/page_client-reference-manifest.js +1 -1
  28. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/[tab]/page/server-reference-manifest.json +28 -28
  29. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/[tab]/page.js.nft.json +1 -1
  30. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/[tab]/page_client-reference-manifest.js +1 -1
  31. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page/server-reference-manifest.json +28 -28
  32. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page.js.nft.json +1 -1
  33. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page_client-reference-manifest.js +1 -1
  34. package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page/server-reference-manifest.json +20 -20
  35. package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page.js.nft.json +1 -1
  36. package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page_client-reference-manifest.js +1 -1
  37. package/web/.next/server/app/(dashboard)/create/page/server-reference-manifest.json +22 -22
  38. package/web/.next/server/app/(dashboard)/create/page.js.nft.json +1 -1
  39. package/web/.next/server/app/(dashboard)/create/page_client-reference-manifest.js +1 -1
  40. package/web/.next/server/app/(dashboard)/feature/[featureId]/[tab]/page/server-reference-manifest.json +28 -28
  41. package/web/.next/server/app/(dashboard)/feature/[featureId]/[tab]/page.js.nft.json +1 -1
  42. package/web/.next/server/app/(dashboard)/feature/[featureId]/[tab]/page_client-reference-manifest.js +1 -1
  43. package/web/.next/server/app/(dashboard)/feature/[featureId]/page/server-reference-manifest.json +28 -28
  44. package/web/.next/server/app/(dashboard)/feature/[featureId]/page.js.nft.json +1 -1
  45. package/web/.next/server/app/(dashboard)/feature/[featureId]/page_client-reference-manifest.js +1 -1
  46. package/web/.next/server/app/(dashboard)/page/server-reference-manifest.json +20 -20
  47. package/web/.next/server/app/(dashboard)/page.js.nft.json +1 -1
  48. package/web/.next/server/app/(dashboard)/page_client-reference-manifest.js +1 -1
  49. package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page/server-reference-manifest.json +20 -20
  50. package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page.js.nft.json +1 -1
  51. package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page_client-reference-manifest.js +1 -1
  52. package/web/.next/server/app/_global-error.html +2 -2
  53. package/web/.next/server/app/_global-error.rsc +1 -1
  54. package/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  55. package/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  56. package/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  57. package/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  58. package/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  59. package/web/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  60. package/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  61. package/web/.next/server/app/settings/page/server-reference-manifest.json +6 -6
  62. package/web/.next/server/app/settings/page.js.nft.json +1 -1
  63. package/web/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  64. package/web/.next/server/app/skills/page/server-reference-manifest.json +6 -6
  65. package/web/.next/server/app/skills/page_client-reference-manifest.js +1 -1
  66. package/web/.next/server/app/tools/page/server-reference-manifest.json +6 -6
  67. package/web/.next/server/app/tools/page_client-reference-manifest.js +1 -1
  68. package/web/.next/server/app/version/page/server-reference-manifest.json +1 -1
  69. package/web/.next/server/app/version/page_client-reference-manifest.js +1 -1
  70. package/web/.next/server/chunks/403f9_next_dist_esm_build_templates_app-route_370c43b1.js +1 -1
  71. package/web/.next/server/chunks/403f9_next_dist_esm_build_templates_app-route_370c43b1.js.map +1 -1
  72. package/web/.next/server/chunks/[root-of-the-server]__a402b567._.js +1 -1
  73. package/web/.next/server/chunks/ssr/744ca_web_components_common_control-center-drawer_create-drawer-client_tsx_5e26fc0a._.js +1 -1
  74. package/web/.next/server/chunks/ssr/744ca_web_components_common_control-center-drawer_create-drawer-client_tsx_5e26fc0a._.js.map +1 -1
  75. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_f30d0343.js +1 -1
  76. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_f30d0343.js.map +1 -1
  77. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_9f1775bb.js +1 -1
  78. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_9f1775bb.js.map +1 -1
  79. package/web/.next/server/chunks/ssr/[root-of-the-server]__06fef644._.js +1 -1
  80. package/web/.next/server/chunks/ssr/[root-of-the-server]__2698e9f4._.js +1 -1
  81. package/web/.next/server/chunks/ssr/[root-of-the-server]__2698e9f4._.js.map +1 -1
  82. package/web/.next/server/chunks/ssr/[root-of-the-server]__545792ab._.js +1 -1
  83. package/web/.next/server/chunks/ssr/[root-of-the-server]__545792ab._.js.map +1 -1
  84. package/web/.next/server/chunks/ssr/[root-of-the-server]__6b17a22d._.js +1 -1
  85. package/web/.next/server/chunks/ssr/[root-of-the-server]__6b17a22d._.js.map +1 -1
  86. package/web/.next/server/chunks/ssr/[root-of-the-server]__6bb00d90._.js +2 -2
  87. package/web/.next/server/chunks/ssr/[root-of-the-server]__6bb00d90._.js.map +1 -1
  88. package/web/.next/server/chunks/ssr/[root-of-the-server]__804c006d._.js +1 -1
  89. package/web/.next/server/chunks/ssr/[root-of-the-server]__804c006d._.js.map +1 -1
  90. package/web/.next/server/chunks/ssr/[root-of-the-server]__8c553503._.js +2 -2
  91. package/web/.next/server/chunks/ssr/[root-of-the-server]__8e3bf5d7._.js +1 -1
  92. package/web/.next/server/chunks/ssr/[root-of-the-server]__8e3bf5d7._.js.map +1 -1
  93. package/web/.next/server/chunks/ssr/[root-of-the-server]__a022445f._.js +2 -2
  94. package/web/.next/server/chunks/ssr/[root-of-the-server]__a7de2711._.js +2 -2
  95. package/web/.next/server/chunks/ssr/[root-of-the-server]__c07f9f79._.js +1 -1
  96. package/web/.next/server/chunks/ssr/[root-of-the-server]__c07f9f79._.js.map +1 -1
  97. package/web/.next/server/chunks/ssr/[root-of-the-server]__c605a375._.js +1 -1
  98. package/web/.next/server/chunks/ssr/[root-of-the-server]__c605a375._.js.map +1 -1
  99. package/web/.next/server/chunks/ssr/_0dc2cfd3._.js +1 -1
  100. package/web/.next/server/chunks/ssr/_0dc2cfd3._.js.map +1 -1
  101. package/web/.next/server/chunks/ssr/{_fffd4212._.js → _132dd4b8._.js} +2 -2
  102. package/web/.next/server/chunks/ssr/{_fffd4212._.js.map → _132dd4b8._.js.map} +1 -1
  103. package/web/.next/server/chunks/ssr/_135edb9c._.js +1 -1
  104. package/web/.next/server/chunks/ssr/_135edb9c._.js.map +1 -1
  105. package/web/.next/server/chunks/ssr/_2f6f48b8._.js +1 -1
  106. package/web/.next/server/chunks/ssr/_2f6f48b8._.js.map +1 -1
  107. package/web/.next/server/chunks/ssr/{_9f1ab4ee._.js → _30c9af21._.js} +2 -2
  108. package/web/.next/server/chunks/ssr/{_9f1ab4ee._.js.map → _30c9af21._.js.map} +1 -1
  109. package/web/.next/server/chunks/ssr/{_6e403a1e._.js → _32c5c20d._.js} +2 -2
  110. package/web/.next/server/chunks/ssr/{_6e403a1e._.js.map → _32c5c20d._.js.map} +1 -1
  111. package/web/.next/server/chunks/ssr/_a37f5f18._.js +1 -1
  112. package/web/.next/server/chunks/ssr/_cc77a143._.js +1 -1
  113. package/web/.next/server/chunks/ssr/_cc77a143._.js.map +1 -1
  114. package/web/.next/server/chunks/ssr/b1a17_presentation_web_components_features_settings_settings-page-client_tsx_6ed9d5f8._.js +1 -1
  115. package/web/.next/server/chunks/ssr/b1a17_presentation_web_components_features_settings_settings-page-client_tsx_6ed9d5f8._.js.map +1 -1
  116. package/web/.next/server/chunks/ssr/src_presentation_web_5f039db1._.js +2 -2
  117. package/web/.next/server/chunks/ssr/src_presentation_web_5f039db1._.js.map +1 -1
  118. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_(dashboard)_page_actions_2a52b697.js +1 -1
  119. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_(dashboard)_page_actions_2a52b697.js.map +1 -1
  120. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_skills_page_actions_1fa8042e.js +1 -1
  121. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_skills_page_actions_1fa8042e.js.map +1 -1
  122. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_tools_page_actions_24684200.js +1 -1
  123. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_tools_page_actions_24684200.js.map +1 -1
  124. package/web/.next/server/chunks/ssr/src_presentation_web_c4337169._.js +2 -2
  125. package/web/.next/server/chunks/ssr/src_presentation_web_c4337169._.js.map +1 -1
  126. package/web/.next/server/chunks/ssr/src_presentation_web_ca99d62d._.js +1 -1
  127. package/web/.next/server/chunks/ssr/src_presentation_web_ca99d62d._.js.map +1 -1
  128. package/web/.next/server/chunks/ssr/src_presentation_web_components_bf50c527._.js +1 -1
  129. package/web/.next/server/chunks/ssr/src_presentation_web_components_de814675._.js +1 -1
  130. package/web/.next/server/chunks/ssr/src_presentation_web_components_features_control-center_7ac3562e._.js +1 -1
  131. package/web/.next/server/chunks/ssr/src_presentation_web_components_features_control-center_7ac3562e._.js.map +1 -1
  132. package/web/.next/server/pages/500.html +2 -2
  133. package/web/.next/server/server-reference-manifest.js +1 -1
  134. package/web/.next/server/server-reference-manifest.json +34 -34
  135. package/web/.next/static/chunks/{9c2f294bf748b4ba.js → 0cd4694791225f54.js} +2 -2
  136. package/web/.next/static/chunks/{2429cf8757ddad34.js → 1a71a1628a38996f.js} +1 -1
  137. package/web/.next/static/chunks/{0dfc3b1cff4bdcc2.js → 1b819d33e9d131dc.js} +2 -2
  138. package/web/.next/static/chunks/{1c206da59596f056.js → 56a5153f06d64eca.js} +1 -1
  139. package/web/.next/static/chunks/{7bc5806d9f82cc79.js → 717af1bcb9dadef9.js} +1 -1
  140. package/web/.next/static/chunks/82f917cc65ec7d42.js +1 -0
  141. package/web/.next/static/chunks/{447e720f6cf54767.js → a8704e79171c33bf.js} +1 -1
  142. package/web/.next/static/chunks/{3e9320301a0cac8b.js → ab24252e992d6b8c.js} +1 -1
  143. package/web/.next/static/chunks/{39dc15636654a1e3.js → c7bd0ef3d9b78f56.js} +1 -1
  144. package/web/.next/static/chunks/{53e9eda219a7f61c.js → d0cdb752198e0ce2.js} +1 -1
  145. package/web/.next/static/chunks/9076710a2df38d14.js +0 -1
  146. /package/web/.next/static/{t8J3jMT2l7HfCuusAPC-b → HLY1lOE_eWQBTqZqPvvsU}/_buildManifest.js +0 -0
  147. /package/web/.next/static/{t8J3jMT2l7HfCuusAPC-b → HLY1lOE_eWQBTqZqPvvsU}/_clientMiddlewareManifest.json +0 -0
  148. /package/web/.next/static/{t8J3jMT2l7HfCuusAPC-b → HLY1lOE_eWQBTqZqPvvsU}/_ssgManifest.js +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"feature-agent-graph.d.ts","sourceRoot":"","sources":["../../../../../../../../packages/core/src/infrastructure/services/agents/feature-agent/feature-agent-graph.ts"],"names":[],"mappings":"AAAA,OAAO,EAA0B,KAAK,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AACxF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,+DAA+D,CAAC;AAOpG,OAAO,EAAmB,KAAK,aAAa,EAAE,MAAM,6BAA6B,CAAC;AASlF,OAAO,EAAE,sBAAsB,EAAE,KAAK,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAE5E;;;GAGG;AACH,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,cAAc,CAAC;IACzB,aAAa,CAAC,EAAE,IAAI,CAAC,aAAa,EAAE,UAAU,CAAC,CAAC;CACjD;AA0HD;;;;;;;;;;;;GAYG;AACH,wBAAgB,uBAAuB,CACrC,cAAc,EAAE,qBAAqB,GAAG,cAAc,EACtD,YAAY,CAAC,EAAE,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAwEnC"}
1
+ {"version":3,"file":"feature-agent-graph.d.ts","sourceRoot":"","sources":["../../../../../../../../packages/core/src/infrastructure/services/agents/feature-agent/feature-agent-graph.ts"],"names":[],"mappings":"AAAA,OAAO,EAA0B,KAAK,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AACxF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,+DAA+D,CAAC;AAOpG,OAAO,EAAmB,KAAK,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAclF,OAAO,EAAE,sBAAsB,EAAE,KAAK,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAE5E;;;GAGG;AACH,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,cAAc,CAAC;IACzB,aAAa,CAAC,EAAE,IAAI,CAAC,aAAa,EAAE,UAAU,CAAC,CAAC;CACjD;AAwID;;;;;;;;;;;;GAYG;AACH,wBAAgB,uBAAuB,CACrC,cAAc,EAAE,qBAAqB,GAAG,cAAc,EACtD,YAAY,CAAC,EAAE,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAgFnC"}
@@ -11,7 +11,7 @@ import { createRepairNode } from './nodes/repair.node.js';
11
11
  import { validateSpecAnalyze, validateSpecRequirements } from './nodes/schemas/spec.schema.js';
12
12
  import { validateResearch } from './nodes/schemas/research.schema.js';
13
13
  import { validatePlan, validateTasks } from './nodes/schemas/plan.schema.js';
14
- import { readSpecFile, safeYamlLoad, createNodeLogger } from './nodes/node-helpers.js';
14
+ import { readSpecFile, safeYamlLoad, createNodeLogger, getCompletedPhases, } from './nodes/node-helpers.js';
15
15
  // Re-export state types for consumers
16
16
  export { FeatureAgentAnnotation } from './state.js';
17
17
  /**
@@ -54,6 +54,18 @@ function createPlanTasksValidator() {
54
54
  const log = createNodeLogger('validate:plan+tasks');
55
55
  return async (state) => {
56
56
  log.activate();
57
+ // Skip validation when the successor phase (implement) is already completed.
58
+ // This proves validation already passed and the pipeline progressed beyond it.
59
+ // Same logic as createValidateNode's successorPhase guard.
60
+ if (getCompletedPhases(state.specDir).includes('implement')) {
61
+ log.info("Successor phase 'implement' already completed, skipping validation");
62
+ return {
63
+ lastValidationTarget: 'plan.yaml+tasks.yaml',
64
+ lastValidationErrors: [],
65
+ validationRetries: 0,
66
+ messages: ["[validate:plan+tasks] SKIP (successor 'implement' already completed)"],
67
+ };
68
+ }
57
69
  log.info('Validating plan.yaml and tasks.yaml');
58
70
  const allErrors = [];
59
71
  // Validate plan.yaml
@@ -145,9 +157,14 @@ export function createFeatureAgentGraph(depsOrExecutor, checkpointer) {
145
157
  .addNode('plan', createPlanNode(executor))
146
158
  .addNode('implement', createImplementNode(executor))
147
159
  // --- Validate nodes ---
148
- .addNode('validate_spec_analyze', createValidateNode('spec.yaml', validateSpecAnalyze))
149
- .addNode('validate_spec_requirements', createValidateNode('spec.yaml', validateSpecRequirements))
150
- .addNode('validate_research', createValidateNode('research.yaml', validateResearch))
160
+ // Each validate node receives its SUCCESSOR phase name (the phase that runs
161
+ // after validation passes). On resume, validation is skipped only when the
162
+ // successor is already completed — proving this validation already passed.
163
+ // Using the successor (not the producer) avoids skipping validation when the
164
+ // producer ran successfully but validation itself failed.
165
+ .addNode('validate_spec_analyze', createValidateNode('spec.yaml', validateSpecAnalyze, 'requirements'))
166
+ .addNode('validate_spec_requirements', createValidateNode('spec.yaml', validateSpecRequirements, 'research'))
167
+ .addNode('validate_research', createValidateNode('research.yaml', validateResearch, 'plan'))
151
168
  .addNode('validate_plan_tasks', createPlanTasksValidator())
152
169
  // --- Repair nodes ---
153
170
  .addNode('repair_spec_analyze', createRepairNode('spec.yaml', executor))
@@ -6,7 +6,13 @@ type SchemaValidator = (data: unknown) => ValidationResult;
6
6
  *
7
7
  * On success: resets validationRetries to 0, clears errors.
8
8
  * On failure: increments validationRetries, sets errors for repair node.
9
+ *
10
+ * @param successorPhase - The phase that runs AFTER this validation passes.
11
+ * If provided, skip validation when the successor is already completed — this
12
+ * proves the validation already passed in a prior run and the pipeline progressed
13
+ * beyond it. Checking the successor (not the producer) avoids skipping validation
14
+ * when the producer ran but validation itself failed.
9
15
  */
10
- export declare function createValidateNode(filename: string, schema: SchemaValidator): (state: FeatureAgentState) => Promise<Partial<FeatureAgentState>>;
16
+ export declare function createValidateNode(filename: string, schema: SchemaValidator, successorPhase?: string): (state: FeatureAgentState) => Promise<Partial<FeatureAgentState>>;
11
17
  export {};
12
18
  //# sourceMappingURL=validate.node.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"validate.node.d.ts","sourceRoot":"","sources":["../../../../../../../../../packages/core/src/infrastructure/services/agents/feature-agent/nodes/validate.node.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAGhE,KAAK,eAAe,GAAG,CAAC,IAAI,EAAE,OAAO,KAAK,gBAAgB,CAAC;AAE3D;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,eAAe,GACtB,CAAC,KAAK,EAAE,iBAAiB,KAAK,OAAO,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC,CAuDnE"}
1
+ {"version":3,"file":"validate.node.d.ts","sourceRoot":"","sources":["../../../../../../../../../packages/core/src/infrastructure/services/agents/feature-agent/nodes/validate.node.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAQhE,KAAK,eAAe,GAAG,CAAC,IAAI,EAAE,OAAO,KAAK,gBAAgB,CAAC;AAE3D;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,eAAe,EACvB,cAAc,CAAC,EAAE,MAAM,GACtB,CAAC,KAAK,EAAE,iBAAiB,KAAK,OAAO,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC,CAyEnE"}
@@ -1,14 +1,36 @@
1
- import { readSpecFile, safeYamlLoad, createNodeLogger } from './node-helpers.js';
1
+ import { readSpecFile, safeYamlLoad, createNodeLogger, getCompletedPhases, } from './node-helpers.js';
2
2
  /**
3
3
  * Factory that creates a validate node for a specific YAML file.
4
4
  *
5
5
  * On success: resets validationRetries to 0, clears errors.
6
6
  * On failure: increments validationRetries, sets errors for repair node.
7
+ *
8
+ * @param successorPhase - The phase that runs AFTER this validation passes.
9
+ * If provided, skip validation when the successor is already completed — this
10
+ * proves the validation already passed in a prior run and the pipeline progressed
11
+ * beyond it. Checking the successor (not the producer) avoids skipping validation
12
+ * when the producer ran but validation itself failed.
7
13
  */
8
- export function createValidateNode(filename, schema) {
14
+ export function createValidateNode(filename, schema, successorPhase) {
9
15
  const log = createNodeLogger(`validate:${filename}`);
10
16
  return async (state) => {
11
17
  log.activate();
18
+ // Skip validation when the successor phase is already completed.
19
+ // This means: the validation already passed in a prior run AND the next phase
20
+ // ran successfully. On resume, LangGraph replays nodes from the last checkpoint.
21
+ // The producer node skips (via getCompletedPhases), but if we only checked the
22
+ // producer we'd also skip validation when it previously FAILED (since
23
+ // markPhaseComplete runs before validation). Checking the successor is safe:
24
+ // if the successor completed, this validation must have passed to get there.
25
+ if (successorPhase && getCompletedPhases(state.specDir).includes(successorPhase)) {
26
+ log.info(`Successor phase '${successorPhase}' already completed, skipping validation`);
27
+ return {
28
+ lastValidationTarget: filename,
29
+ lastValidationErrors: [],
30
+ validationRetries: 0,
31
+ messages: [`[validate:${filename}] SKIP (successor '${successorPhase}' already completed)`],
32
+ };
33
+ }
12
34
  log.info(`Validating ${filename}`);
13
35
  const content = readSpecFile(state.specDir, filename);
14
36
  if (!content) {
@@ -1 +1 @@
1
- {"version":3,"file":"feature-drawer-client.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/control-center-drawer/feature-drawer-client.tsx"],"names":[],"mappings":"AAuCA,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAI/D,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,UAAU,CAAC;IACjB,8FAA8F;IAC9F,MAAM,CAAC,EAAE,aAAa,CAAC;CACxB;AAED,wBAAgB,mBAAmB,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,EAAE,wBAAwB,2CA+jB1F"}
1
+ {"version":3,"file":"feature-drawer-client.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/control-center-drawer/feature-drawer-client.tsx"],"names":[],"mappings":"AAuCA,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAI/D,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,UAAU,CAAC;IACjB,8FAA8F;IAC9F,MAAM,CAAC,EAAE,aAAa,CAAC;CACxB;AAED,wBAAgB,mBAAmB,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,EAAE,wBAAwB,2CAqkB1F"}
@@ -194,6 +194,10 @@ export function FeatureDrawerClient({ view: initialView, urlTab }) {
194
194
  if (result.iterationWarning) {
195
195
  toast.warning(`Iteration ${result.iteration} — consider approving or adjusting feedback to avoid excessive iterations`);
196
196
  }
197
+ // Optimistically update canvas node before SSE arrives (~500ms delay)
198
+ window.dispatchEvent(new CustomEvent('shep:feature-approved', {
199
+ detail: { featureId: featureNode.featureId },
200
+ }));
197
201
  onClose();
198
202
  onDone?.();
199
203
  }
@@ -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":"AAIA,OAAO,KAAK,EAAE,IAAI,EAAY,MAAM,eAAe,CAAC;AAGpD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAC;AAuB5E,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,2CA2UzF"}
1
+ {"version":3,"file":"control-center-inner.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/features/control-center/control-center-inner.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,IAAI,EAAY,MAAM,eAAe,CAAC;AAGpD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAC;AAuB5E,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,2CAyXzF"}
@@ -8,7 +8,7 @@ import { useSidebarFeaturesContext, mapNodeStateToSidebarStatus, } from '../../.
8
8
  import { useSelectedFeatureId } from '../../../hooks/use-selected-feature-id.js';
9
9
  import { useSoundAction } from '../../../hooks/use-sound-action.js';
10
10
  import { useDrawerCloseGuard } from '../../../hooks/drawer-close-guard.js';
11
- import { useViewportPersistence } from '../../../hooks/use-viewport-persistence.js';
11
+ import { useViewportPersistence, DEFAULT_VIEWPORT } from '../../../hooks/use-viewport-persistence.js';
12
12
  import { ControlCenterEmptyState } from './control-center-empty-state.js';
13
13
  import { useControlCenterState } from './use-control-center-state.js';
14
14
  const AUTO_FOCUS_OPTIONS = {
@@ -23,7 +23,7 @@ export function ControlCenterInner({ initialNodes, initialEdges }) {
23
23
  const selectedFeatureId = useSelectedFeatureId();
24
24
  const clickSound = useSoundAction('click');
25
25
  const { guardedNavigate } = useDrawerCloseGuard();
26
- const { fitView } = useReactFlow();
26
+ const { fitView, setViewport } = useReactFlow();
27
27
  const drawerTimerRef = useRef(null);
28
28
  const { defaultViewport, onMoveEnd: handleViewportChange, resetViewport, } = useViewportPersistence();
29
29
  const { nodes, edges, onNodesChange, handleConnect, handleAddRepository, handleDeleteFeature, handleRetryFeature, handleStartFeature, handleStopFeature, handleDeleteRepository, createFeatureNode, setCallbacks, } = useControlCenterState(initialNodes, initialEdges);
@@ -119,20 +119,27 @@ export function ControlCenterInner({ initialNodes, initialEdges }) {
119
119
  guardedNavigate(() => router.push('/'));
120
120
  }
121
121
  }, [router, pathname, guardedNavigate]);
122
+ // ── Deferred fitView mechanism ──────────────────────────────────────
123
+ // Instead of calling fitView from event handlers (which races with the
124
+ // showCanvas latch and React render cycles), we set a ref flag and let
125
+ // a useEffect consume it once showCanvas is true and nodes are rendered.
126
+ const pendingFitViewRef = useRef(false);
127
+ const pendingDrawerRepoRef = useRef(null);
122
128
  // Listen for global "add repository" events from the top bar button
123
129
  useEffect(() => {
124
130
  const handler = (e) => {
125
131
  const path = e.detail.path;
126
132
  const { wasEmpty, repoPath } = handleAddRepository(path);
133
+ // Always center the viewport on the new repo, ignoring any stored
134
+ // pan/zoom position. This guarantees the repo is visible after adding
135
+ // — even if the user previously panned far away or deleted everything.
136
+ resetViewport();
137
+ // Immediately snap the in-memory viewport so the user never sees
138
+ // the old far-away position (defaultViewport only applies on mount).
139
+ setViewport(DEFAULT_VIEWPORT);
140
+ pendingFitViewRef.current = true;
127
141
  if (wasEmpty) {
128
- // Wait for next render so the repo node exists in the DOM, then auto-focus
129
- setTimeout(() => {
130
- fitView(AUTO_FOCUS_OPTIONS);
131
- // Open the create-feature drawer after the fitView animation completes
132
- drawerTimerRef.current = setTimeout(() => {
133
- guardedNavigate(() => router.push(`/create?repo=${encodeURIComponent(repoPath)}`));
134
- }, AUTO_FOCUS_DRAWER_DELAY_MS);
135
- }, 0);
142
+ pendingDrawerRepoRef.current = repoPath;
136
143
  }
137
144
  };
138
145
  window.addEventListener('shep:add-repository', handler);
@@ -142,7 +149,7 @@ export function ControlCenterInner({ initialNodes, initialEdges }) {
142
149
  clearTimeout(drawerTimerRef.current);
143
150
  }
144
151
  };
145
- }, [handleAddRepository, fitView, guardedNavigate, router]);
152
+ }, [handleAddRepository, resetViewport, setViewport]);
146
153
  // Listen for create events from the create drawer (with real feature ID from server)
147
154
  useEffect(() => {
148
155
  const handler = (e) => {
@@ -208,6 +215,21 @@ export function ControlCenterInner({ initialNodes, initialEdges }) {
208
215
  handleRepositoryClick,
209
216
  handleDeleteRepository,
210
217
  ]);
218
+ // Auto-center whenever the canvas transitions from empty to having nodes.
219
+ // This covers both initial page load AND re-adding repos after deleting
220
+ // everything — the view always centers so the repo is visible, ignoring
221
+ // any previously stored pan/zoom position.
222
+ const hadNodesRef = useRef(nodes.length > 0);
223
+ useEffect(() => {
224
+ const hasNodes = nodes.length > 0;
225
+ if (hasNodes && !hadNodesRef.current) {
226
+ // Went from empty → has nodes — request a deferred fitView
227
+ resetViewport();
228
+ setViewport(DEFAULT_VIEWPORT);
229
+ pendingFitViewRef.current = true;
230
+ }
231
+ hadNodesRef.current = hasNodes;
232
+ }, [nodes.length, resetViewport, setViewport]);
211
233
  const handleMoveEnd = useCallback((_event, viewport) => {
212
234
  handleViewportChange(viewport);
213
235
  }, [handleViewportChange]);
@@ -238,6 +260,26 @@ export function ControlCenterInner({ initialNodes, initialEdges }) {
238
260
  clearTimeout(latchTimerRef.current);
239
261
  };
240
262
  }, [hasRepositories, showCanvas]);
263
+ // ── Consume the pending fitView flag ──────────────────────────────────
264
+ // This effect runs after React renders with showCanvas=true and nodes
265
+ // available, guaranteeing ReactFlow has the nodes in the DOM before
266
+ // fitView calculates bounds. Using requestAnimationFrame ensures the
267
+ // browser has flushed layout so ReactFlow can measure node dimensions.
268
+ useEffect(() => {
269
+ if (!pendingFitViewRef.current || !showCanvas || nodes.length === 0)
270
+ return;
271
+ pendingFitViewRef.current = false;
272
+ const drawerRepo = pendingDrawerRepoRef.current;
273
+ pendingDrawerRepoRef.current = null;
274
+ requestAnimationFrame(() => {
275
+ fitView(AUTO_FOCUS_OPTIONS);
276
+ if (drawerRepo) {
277
+ drawerTimerRef.current = setTimeout(() => {
278
+ guardedNavigate(() => router.push(`/create?repo=${encodeURIComponent(drawerRepo)}`));
279
+ }, AUTO_FOCUS_DRAWER_DELAY_MS);
280
+ }
281
+ });
282
+ }, [showCanvas, nodes.length, fitView, guardedNavigate, router]);
241
283
  // Pulse the "+" button when there's a single repo with no features and the
242
284
  // create-feature drawer is not open — draws attention to the next action.
243
285
  const isCreateDrawerOpen = pathname.startsWith('/create');
@@ -1 +1 @@
1
- {"version":3,"file":"use-control-center-state.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/features/control-center/use-control-center-state.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAClE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AACxE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qCAAqC,CAAC;AAC9E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAC;AAC5E,OAAO,EAGL,KAAK,eAAe,EACrB,MAAM,yBAAyB,CAAC;AAgBjC,OAAO,EAAiB,KAAK,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAM7E,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,aAAa,EAAE,CAAC,OAAO,EAAE,UAAU,CAAC,cAAc,CAAC,EAAE,KAAK,IAAI,CAAC;IAC/D,aAAa,EAAE,CAAC,UAAU,EAAE,UAAU,KAAK,IAAI,CAAC;IAChD,mBAAmB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK;QAAE,QAAQ,EAAE,OAAO,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IAC/E,YAAY,EAAE,CAAC,SAAS,EAAE,eAAe,KAAK,IAAI,CAAC;IACnD,mBAAmB,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,EAAE,aAAa,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IAC7F,kBAAkB,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD,kBAAkB,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD,iBAAiB,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/C,sBAAsB,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAChE,iBAAiB,EAAE,CACjB,YAAY,EAAE,MAAM,GAAG,IAAI,EAC3B,YAAY,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,EACvC,QAAQ,CAAC,EAAE,MAAM,KACd,MAAM,CAAC;IACZ,wDAAwD;IACxD,wBAAwB,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;IACxE,gDAAgD;IAChD,iBAAiB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,kBAAkB,GAAG,SAAS,CAAC;IACtE,0EAA0E;IAC1E,YAAY,EAAE,CAAC,SAAS,EAAE,cAAc,KAAK,IAAI,CAAC;CACnD;AAQD,wBAAgB,qBAAqB,CACnC,YAAY,EAAE,cAAc,EAAE,EAC9B,YAAY,EAAE,IAAI,EAAE,GACnB,kBAAkB,CAygBpB"}
1
+ {"version":3,"file":"use-control-center-state.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/features/control-center/use-control-center-state.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAClE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AACxE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qCAAqC,CAAC;AAC9E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAC;AAC5E,OAAO,EAGL,KAAK,eAAe,EACrB,MAAM,yBAAyB,CAAC;AAgBjC,OAAO,EAAiB,KAAK,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAM7E,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,aAAa,EAAE,CAAC,OAAO,EAAE,UAAU,CAAC,cAAc,CAAC,EAAE,KAAK,IAAI,CAAC;IAC/D,aAAa,EAAE,CAAC,UAAU,EAAE,UAAU,KAAK,IAAI,CAAC;IAChD,mBAAmB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK;QAAE,QAAQ,EAAE,OAAO,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IAC/E,YAAY,EAAE,CAAC,SAAS,EAAE,eAAe,KAAK,IAAI,CAAC;IACnD,mBAAmB,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,EAAE,aAAa,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IAC7F,kBAAkB,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD,kBAAkB,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD,iBAAiB,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/C,sBAAsB,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAChE,iBAAiB,EAAE,CACjB,YAAY,EAAE,MAAM,GAAG,IAAI,EAC3B,YAAY,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,EACvC,QAAQ,CAAC,EAAE,MAAM,KACd,MAAM,CAAC;IACZ,wDAAwD;IACxD,wBAAwB,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;IACxE,gDAAgD;IAChD,iBAAiB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,kBAAkB,GAAG,SAAS,CAAC;IACtE,0EAA0E;IAC1E,YAAY,EAAE,CAAC,SAAS,EAAE,cAAc,KAAK,IAAI,CAAC;CACnD;AAQD,wBAAgB,qBAAqB,CACnC,YAAY,EAAE,cAAc,EAAE,EAC9B,YAAY,EAAE,IAAI,EAAE,GACnB,kBAAkB,CA6gBpB"}
@@ -115,15 +115,19 @@ export function useControlCenterState(initialNodes, initialEdges) {
115
115
  }
116
116
  }
117
117
  }, [events, updateFeature]);
118
- // Listen for optimistic approval events from the drawer (fires before SSE arrives)
118
+ // Listen for optimistic approval/rejection events from the drawer (fires before SSE arrives).
119
+ // Uses beginMutation + endMutation to prevent stale poll/reconcile data from overwriting
120
+ // the optimistic 'running' state during the transition window (avoids brief "Something went wrong" flash).
119
121
  useEffect(() => {
120
122
  const handler = (e) => {
121
123
  const { featureId } = e.detail;
124
+ beginMutation();
122
125
  updateFeature(`feat-${featureId}`, { state: 'running' });
126
+ endMutation();
123
127
  };
124
128
  window.addEventListener('shep:feature-approved', handler);
125
129
  return () => window.removeEventListener('shep:feature-approved', handler);
126
- }, [updateFeature]);
130
+ }, [updateFeature, beginMutation, endMutation]);
127
131
  // Separate effect: fetch metadata (name + description) when SSE reports it changed
128
132
  const metadataFetchedRef = useRef(new Set());
129
133
  const processedMetadataCountRef = useRef(0);
@@ -5,6 +5,8 @@ export declare const DEBOUNCE_MS = 500;
5
5
  export declare const DEFAULT_VIEWPORT: Viewport;
6
6
  export interface UseViewportPersistenceResult {
7
7
  defaultViewport: Viewport;
8
+ /** True when a valid viewport was restored from localStorage. */
9
+ hasSavedViewport: boolean;
8
10
  onMoveEnd: (viewport: Viewport) => void;
9
11
  resetViewport: () => Viewport;
10
12
  }
@@ -1 +1 @@
1
- {"version":3,"file":"use-viewport-persistence.d.ts","sourceRoot":"","sources":["../../../../../src/presentation/web/hooks/use-viewport-persistence.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C,YAAY,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C,eAAO,MAAM,WAAW,yBAAyB,CAAC;AAClD,eAAO,MAAM,WAAW,MAAM,CAAC;AAE/B,eAAO,MAAM,gBAAgB,EAAE,QAAuC,CAAC;AAEvE,MAAM,WAAW,4BAA4B;IAC3C,eAAe,EAAE,QAAQ,CAAC;IAC1B,SAAS,EAAE,CAAC,QAAQ,EAAE,QAAQ,KAAK,IAAI,CAAC;IACxC,aAAa,EAAE,MAAM,QAAQ,CAAC;CAC/B;AA6BD,wBAAgB,sBAAsB,IAAI,4BAA4B,CAmCrE"}
1
+ {"version":3,"file":"use-viewport-persistence.d.ts","sourceRoot":"","sources":["../../../../../src/presentation/web/hooks/use-viewport-persistence.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C,YAAY,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C,eAAO,MAAM,WAAW,yBAAyB,CAAC;AAClD,eAAO,MAAM,WAAW,MAAM,CAAC;AAE/B,eAAO,MAAM,gBAAgB,EAAE,QAAuC,CAAC;AAEvE,MAAM,WAAW,4BAA4B;IAC3C,eAAe,EAAE,QAAQ,CAAC;IAC1B,iEAAiE;IACjE,gBAAgB,EAAE,OAAO,CAAC;IAC1B,SAAS,EAAE,CAAC,QAAQ,EAAE,QAAQ,KAAK,IAAI,CAAC;IACxC,aAAa,EAAE,MAAM,QAAQ,CAAC;CAC/B;AA8BD,wBAAgB,sBAAsB,IAAI,4BAA4B,CA8CrE"}
@@ -1,5 +1,5 @@
1
1
  'use client';
2
- import { useCallback, useEffect, useRef } from 'react';
2
+ import { useCallback, useEffect, useRef, useState } from 'react';
3
3
  export const STORAGE_KEY = 'shep-canvas-viewport';
4
4
  export const DEBOUNCE_MS = 500;
5
5
  export const DEFAULT_VIEWPORT = { x: 30, y: 30, zoom: 0.85 };
@@ -20,16 +20,20 @@ function readViewport() {
20
20
  try {
21
21
  const raw = localStorage.getItem(STORAGE_KEY);
22
22
  if (raw == null)
23
- return DEFAULT_VIEWPORT;
23
+ return { viewport: DEFAULT_VIEWPORT, fromStorage: false };
24
24
  const parsed = JSON.parse(raw);
25
- return isValidViewport(parsed) ? parsed : DEFAULT_VIEWPORT;
25
+ if (isValidViewport(parsed))
26
+ return { viewport: parsed, fromStorage: true };
27
+ return { viewport: DEFAULT_VIEWPORT, fromStorage: false };
26
28
  }
27
29
  catch {
28
- return DEFAULT_VIEWPORT;
30
+ return { viewport: DEFAULT_VIEWPORT, fromStorage: false };
29
31
  }
30
32
  }
31
33
  export function useViewportPersistence() {
32
- const defaultViewport = useRef(readViewport()).current;
34
+ const stored = useRef(readViewport()).current;
35
+ const [defaultViewport, setDefaultViewport] = useState(stored.viewport);
36
+ const hasSavedViewport = stored.fromStorage;
33
37
  const timerRef = useRef(null);
34
38
  useEffect(() => {
35
39
  return () => {
@@ -52,13 +56,22 @@ export function useViewportPersistence() {
52
56
  }, DEBOUNCE_MS);
53
57
  }, []);
54
58
  const resetViewport = useCallback(() => {
59
+ // Cancel any pending debounced save so it cannot overwrite the reset
60
+ if (timerRef.current != null) {
61
+ clearTimeout(timerRef.current);
62
+ timerRef.current = null;
63
+ }
55
64
  try {
56
65
  localStorage.removeItem(STORAGE_KEY);
57
66
  }
58
67
  catch {
59
68
  // Silently ignore localStorage errors
60
69
  }
70
+ // Update the defaultViewport so that if ReactFlow re-mounts (e.g. after
71
+ // showCanvas toggles), it initializes at the default position instead of
72
+ // the stale saved viewport.
73
+ setDefaultViewport(DEFAULT_VIEWPORT);
61
74
  return DEFAULT_VIEWPORT;
62
75
  }, []);
63
- return { defaultViewport, onMoveEnd, resetViewport };
76
+ return { defaultViewport, hasSavedViewport, onMoveEnd, resetViewport };
64
77
  }
@@ -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;AAED,+DAA+D;AAC/D,eAAO,MAAM,sBAAsB,EAAE,aAIpC,CAAC;AAkCF;;;;;;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,CAqL/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;AAED,+DAA+D;AAC/D,eAAO,MAAM,sBAAsB,EAAE,aAIpC,CAAC;AAkCF;;;;;;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,CA8L/B"}
@@ -78,6 +78,10 @@ export function layoutWithDagre(nodes, edges, opts = {}) {
78
78
  // Add disconnected nodes to centerMap so they participate in root positioning.
79
79
  // Use the same primary-axis position as connected roots (rank 0) so they align
80
80
  // visually with repos that have features attached.
81
+ // When there are NO connected nodes (e.g. repos with no features), use half the
82
+ // node's own dimension so the leading edge sits at the canvas origin — preventing
83
+ // nodes from rendering off-screen with the default viewport.
84
+ const hasCenterMap = centerMap.size > 0;
81
85
  let rootPrimaryAxis = 0;
82
86
  for (const c of centerMap.values()) {
83
87
  rootPrimaryAxis = isHorizontal ? c.cx : c.cy;
@@ -85,9 +89,14 @@ export function layoutWithDagre(nodes, edges, opts = {}) {
85
89
  }
86
90
  for (const node of disconnectedNodes) {
87
91
  const size = getNodeSize(node, nodeSize);
92
+ const primaryAxisCenter = hasCenterMap
93
+ ? rootPrimaryAxis
94
+ : isHorizontal
95
+ ? size.width / 2
96
+ : size.height / 2;
88
97
  centerMap.set(node.id, {
89
- cx: isHorizontal ? rootPrimaryAxis : 0,
90
- cy: isHorizontal ? 0 : rootPrimaryAxis,
98
+ cx: isHorizontal ? primaryAxisCenter : 0,
99
+ cy: isHorizontal ? 0 : primaryAxisCenter,
91
100
  w: size.width,
92
101
  h: size.height,
93
102
  });