@shepai/cli 1.122.0-pr392.ff1c944 → 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.
- package/dist/packages/core/src/infrastructure/services/agents/feature-agent/feature-agent-graph.d.ts.map +1 -1
- package/dist/packages/core/src/infrastructure/services/agents/feature-agent/feature-agent-graph.js +21 -4
- package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/validate.node.d.ts +7 -1
- package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/validate.node.d.ts.map +1 -1
- package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/validate.node.js +24 -2
- package/dist/src/presentation/web/components/common/control-center-drawer/feature-drawer-client.d.ts.map +1 -1
- package/dist/src/presentation/web/components/common/control-center-drawer/feature-drawer-client.js +4 -0
- package/dist/src/presentation/web/components/common/feature-drawer-tabs/activity-tab.d.ts.map +1 -1
- package/dist/src/presentation/web/components/common/feature-drawer-tabs/activity-tab.js +27 -94
- 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 +53 -11
- package/dist/src/presentation/web/components/features/control-center/use-control-center-state.d.ts.map +1 -1
- package/dist/src/presentation/web/components/features/control-center/use-control-center-state.js +6 -2
- package/dist/src/presentation/web/hooks/use-viewport-persistence.d.ts +2 -0
- package/dist/src/presentation/web/hooks/use-viewport-persistence.d.ts.map +1 -1
- package/dist/src/presentation/web/hooks/use-viewport-persistence.js +19 -6
- package/dist/src/presentation/web/lib/layout-with-dagre.d.ts.map +1 -1
- package/dist/src/presentation/web/lib/layout-with-dagre.js +11 -2
- 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/fallback-build-manifest.json +2 -2
- package/web/.next/prerender-manifest.json +3 -3
- package/web/.next/required-server-files.js +2 -2
- package/web/.next/required-server-files.json +2 -2
- package/web/.next/server/app/(dashboard)/@drawer/create/page/server-reference-manifest.json +22 -22
- package/web/.next/server/app/(dashboard)/@drawer/create/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/create/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/[tab]/page/server-reference-manifest.json +28 -28
- package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/[tab]/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/[tab]/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page/server-reference-manifest.json +28 -28
- package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page/server-reference-manifest.json +20 -20
- package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/create/page/server-reference-manifest.json +22 -22
- package/web/.next/server/app/(dashboard)/create/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/create/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/feature/[featureId]/[tab]/page/server-reference-manifest.json +28 -28
- package/web/.next/server/app/(dashboard)/feature/[featureId]/[tab]/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/feature/[featureId]/[tab]/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/feature/[featureId]/page/server-reference-manifest.json +28 -28
- package/web/.next/server/app/(dashboard)/feature/[featureId]/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/feature/[featureId]/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/page/server-reference-manifest.json +20 -20
- package/web/.next/server/app/(dashboard)/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page/server-reference-manifest.json +20 -20
- package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page_client-reference-manifest.js +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_client-reference-manifest.js +1 -1
- package/web/.next/server/app/settings/page/server-reference-manifest.json +6 -6
- package/web/.next/server/app/settings/page.js.nft.json +1 -1
- package/web/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/skills/page/server-reference-manifest.json +6 -6
- package/web/.next/server/app/skills/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/tools/page/server-reference-manifest.json +6 -6
- 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_client-reference-manifest.js +1 -1
- package/web/.next/server/chunks/403f9_next_dist_esm_build_templates_app-route_370c43b1.js +1 -1
- package/web/.next/server/chunks/403f9_next_dist_esm_build_templates_app-route_370c43b1.js.map +1 -1
- package/web/.next/server/chunks/[root-of-the-server]__a402b567._.js +1 -1
- package/web/.next/server/chunks/ssr/744ca_web_components_common_control-center-drawer_create-drawer-client_tsx_5e26fc0a._.js +1 -1
- package/web/.next/server/chunks/ssr/744ca_web_components_common_control-center-drawer_create-drawer-client_tsx_5e26fc0a._.js.map +1 -1
- package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_f30d0343.js +1 -1
- package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_f30d0343.js.map +1 -1
- package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_9f1775bb.js +1 -1
- package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_9f1775bb.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__06fef644._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__2698e9f4._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__2698e9f4._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__545792ab._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__545792ab._.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]__6bb00d90._.js +2 -2
- package/web/.next/server/chunks/ssr/[root-of-the-server]__6bb00d90._.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]__8c553503._.js +2 -2
- package/web/.next/server/chunks/ssr/[root-of-the-server]__8e3bf5d7._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__8e3bf5d7._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__9f155c0e._.js +3 -0
- package/web/.next/server/chunks/ssr/[root-of-the-server]__9f155c0e._.js.map +1 -0
- package/web/.next/server/chunks/ssr/[root-of-the-server]__a022445f._.js +2 -2
- package/web/.next/server/chunks/ssr/[root-of-the-server]__a7de2711._.js +2 -2
- package/web/.next/server/chunks/ssr/[root-of-the-server]__c07f9f79._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__c07f9f79._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__c605a375._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__c605a375._.js.map +1 -1
- package/web/.next/server/chunks/ssr/_0dc2cfd3._.js +1 -1
- package/web/.next/server/chunks/ssr/_0dc2cfd3._.js.map +1 -1
- package/web/.next/server/chunks/ssr/{_affb27a3._.js → _132dd4b8._.js} +2 -2
- package/web/.next/server/chunks/ssr/{_affb27a3._.js.map → _132dd4b8._.js.map} +1 -1
- package/web/.next/server/chunks/ssr/_135edb9c._.js +1 -1
- package/web/.next/server/chunks/ssr/_135edb9c._.js.map +1 -1
- package/web/.next/server/chunks/ssr/_2f6f48b8._.js +1 -1
- package/web/.next/server/chunks/ssr/_2f6f48b8._.js.map +1 -1
- package/web/.next/server/chunks/ssr/{_1ced87ef._.js → _30c9af21._.js} +2 -2
- package/web/.next/server/chunks/ssr/{_1ced87ef._.js.map → _30c9af21._.js.map} +1 -1
- package/web/.next/server/chunks/ssr/_32c5c20d._.js +3 -0
- package/web/.next/server/chunks/ssr/_32c5c20d._.js.map +1 -0
- package/web/.next/server/chunks/ssr/_cc77a143._.js +1 -1
- package/web/.next/server/chunks/ssr/_cc77a143._.js.map +1 -1
- package/web/.next/server/chunks/ssr/b1a17_presentation_web_components_features_settings_settings-page-client_tsx_6ed9d5f8._.js +1 -1
- package/web/.next/server/chunks/ssr/b1a17_presentation_web_components_features_settings_settings-page-client_tsx_6ed9d5f8._.js.map +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_5f039db1._.js +2 -2
- package/web/.next/server/chunks/ssr/src_presentation_web_5f039db1._.js.map +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_(dashboard)_page_actions_2a52b697.js +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_(dashboard)_page_actions_2a52b697.js.map +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_skills_page_actions_1fa8042e.js +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_skills_page_actions_1fa8042e.js.map +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_tools_page_actions_24684200.js +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_tools_page_actions_24684200.js.map +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_c4337169._.js +2 -2
- package/web/.next/server/chunks/ssr/src_presentation_web_c4337169._.js.map +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_ca99d62d._.js +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_ca99d62d._.js.map +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_components_bf50c527._.js +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_components_de814675._.js +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_components_features_control-center_7ac3562e._.js +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_components_features_control-center_7ac3562e._.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 +34 -34
- package/web/.next/static/chunks/{5c4ada27bc57d0a4.js → 0cd4694791225f54.js} +2 -2
- package/web/.next/static/chunks/0d2af96e64960ef5.css +1 -0
- package/web/.next/static/chunks/{68c5260cce07b4de.js → 1a71a1628a38996f.js} +1 -1
- package/web/.next/static/chunks/{cce8416ed0cbc011.js → 1b819d33e9d131dc.js} +2 -2
- package/web/.next/static/chunks/4562ee849ea13c3a.js +1 -0
- package/web/.next/static/chunks/56a5153f06d64eca.js +1 -0
- package/web/.next/static/chunks/{780f705bfe91f7fc.js → 717af1bcb9dadef9.js} +1 -1
- package/web/.next/static/chunks/82f917cc65ec7d42.js +1 -0
- package/web/.next/static/chunks/a8704e79171c33bf.js +1 -0
- package/web/.next/static/chunks/{f27126bcef6cbaa9.js → ab24252e992d6b8c.js} +1 -1
- package/web/.next/static/chunks/{aee3590a26cac4f3.js → c7bd0ef3d9b78f56.js} +1 -1
- package/web/.next/static/chunks/{aa9c03b455fc4a49.js → d0cdb752198e0ce2.js} +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__f646b99a._.js +0 -3
- package/web/.next/server/chunks/ssr/[root-of-the-server]__f646b99a._.js.map +0 -1
- package/web/.next/server/chunks/ssr/_cb942e20._.js +0 -3
- package/web/.next/server/chunks/ssr/_cb942e20._.js.map +0 -1
- package/web/.next/static/chunks/04b2108495415052.js +0 -1
- package/web/.next/static/chunks/3911a7175044e784.js +0 -1
- package/web/.next/static/chunks/574116ea7de4c18d.js +0 -1
- package/web/.next/static/chunks/991df1233214b238.css +0 -1
- package/web/.next/static/chunks/a0d1d6263db19600.js +0 -1
- /package/web/.next/static/{VBPTE7xcWBAoQkxtk58nb → HLY1lOE_eWQBTqZqPvvsU}/_buildManifest.js +0 -0
- /package/web/.next/static/{VBPTE7xcWBAoQkxtk58nb → HLY1lOE_eWQBTqZqPvvsU}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/static/{VBPTE7xcWBAoQkxtk58nb → 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;
|
|
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"}
|
package/dist/packages/core/src/infrastructure/services/agents/feature-agent/feature-agent-graph.js
CHANGED
|
@@ -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
|
-
|
|
149
|
-
.
|
|
150
|
-
|
|
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))
|
package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/validate.node.d.ts
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/validate.node.js
CHANGED
|
@@ -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,
|
|
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"}
|
package/dist/src/presentation/web/components/common/control-center-drawer/feature-drawer-client.js
CHANGED
|
@@ -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
|
}
|
package/dist/src/presentation/web/components/common/feature-drawer-tabs/activity-tab.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"activity-tab.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/feature-drawer-tabs/activity-tab.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"activity-tab.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/feature-drawer-tabs/activity-tab.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,eAAe,EACf,qBAAqB,EACtB,MAAM,yCAAyC,CAAC;AA4DjD,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,eAAe,EAAE,GAAG,IAAI,CAAC;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,iBAAiB,CAAC,EAAE,qBAAqB,EAAE,CAAC;CAC7C;AA6BD;;;;;;;GAOG;AACH,MAAM,WAAW,iBAAiB;IAChC,+BAA+B;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,0CAA0C;IAC1C,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,mEAAmE;IACnE,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,8DAA8D;IAC9D,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;CACjC;AAED;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,eAAe,EAAE,EAC1B,QAAQ,CAAC,EAAE,qBAAqB,EAAE,GACjC,iBAAiB,EAAE,CAgErB;AAMD,wBAAgB,WAAW,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,iBAAiB,EAAE,EAAE,gBAAgB,2CAqE3F"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { useState, useEffect, useRef } from 'react';
|
|
4
|
-
import { Loader2, AlertCircle, Clock, Zap, DollarSign
|
|
4
|
+
import { Loader2, AlertCircle, Clock, Zap, DollarSign } from 'lucide-react';
|
|
5
5
|
import { InlineAttachments } from '../../common/inline-attachments/index.js';
|
|
6
6
|
import { formatDuration } from '../../../lib/format-duration.js';
|
|
7
7
|
/**
|
|
@@ -51,13 +51,6 @@ function formatTokens(count) {
|
|
|
51
51
|
return `${(count / 1_000).toFixed(1)}K`;
|
|
52
52
|
return String(count);
|
|
53
53
|
}
|
|
54
|
-
/** Format a timestamp to a short local time string (e.g. "10:05:30 AM"). */
|
|
55
|
-
function formatTimestamp(value) {
|
|
56
|
-
const d = value instanceof Date ? value : new Date(String(value));
|
|
57
|
-
if (Number.isNaN(d.getTime()))
|
|
58
|
-
return '';
|
|
59
|
-
return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
|
60
|
-
}
|
|
61
54
|
/** Format a USD cost with appropriate precision. */
|
|
62
55
|
function formatCost(usd) {
|
|
63
56
|
if (usd >= 1)
|
|
@@ -75,48 +68,13 @@ const NODE_TO_PHASE = {
|
|
|
75
68
|
merge: 'Merging',
|
|
76
69
|
};
|
|
77
70
|
const LIFECYCLE_EVENTS = {
|
|
78
|
-
'run:started': {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
},
|
|
84
|
-
'run:
|
|
85
|
-
label: 'resumed',
|
|
86
|
-
colorClass: 'text-blue-600',
|
|
87
|
-
bgClass: 'bg-blue-50 dark:bg-blue-950/30',
|
|
88
|
-
icon: RotateCcw,
|
|
89
|
-
},
|
|
90
|
-
'run:completed': {
|
|
91
|
-
label: 'completed',
|
|
92
|
-
colorClass: 'text-emerald-600',
|
|
93
|
-
bgClass: 'bg-emerald-50 dark:bg-emerald-950/30',
|
|
94
|
-
icon: CheckCircle2,
|
|
95
|
-
},
|
|
96
|
-
'run:failed': {
|
|
97
|
-
label: 'failed',
|
|
98
|
-
colorClass: 'text-red-600',
|
|
99
|
-
bgClass: 'bg-red-50 dark:bg-red-950/30',
|
|
100
|
-
icon: XCircle,
|
|
101
|
-
},
|
|
102
|
-
'run:stopped': {
|
|
103
|
-
label: 'stopped',
|
|
104
|
-
colorClass: 'text-amber-600',
|
|
105
|
-
bgClass: 'bg-amber-50 dark:bg-amber-950/30',
|
|
106
|
-
icon: Square,
|
|
107
|
-
},
|
|
108
|
-
'run:crashed': {
|
|
109
|
-
label: 'crashed',
|
|
110
|
-
colorClass: 'text-red-600',
|
|
111
|
-
bgClass: 'bg-red-50 dark:bg-red-950/30',
|
|
112
|
-
icon: XCircle,
|
|
113
|
-
},
|
|
114
|
-
'run:rejected': {
|
|
115
|
-
label: 'rejected',
|
|
116
|
-
colorClass: 'text-orange-600',
|
|
117
|
-
bgClass: 'bg-orange-50 dark:bg-orange-950/30',
|
|
118
|
-
icon: Ban,
|
|
119
|
-
},
|
|
71
|
+
'run:started': { label: 'started', colorClass: 'text-blue-600' },
|
|
72
|
+
'run:resumed': { label: 'resumed', colorClass: 'text-blue-600' },
|
|
73
|
+
'run:completed': { label: 'completed', colorClass: 'text-emerald-600' },
|
|
74
|
+
'run:failed': { label: 'failed', colorClass: 'text-red-600' },
|
|
75
|
+
'run:stopped': { label: 'stopped', colorClass: 'text-amber-600' },
|
|
76
|
+
'run:crashed': { label: 'crashed', colorClass: 'text-red-600' },
|
|
77
|
+
'run:rejected': { label: 'rejected', colorClass: 'text-orange-600' },
|
|
120
78
|
};
|
|
121
79
|
function isLifecycleEvent(phase) {
|
|
122
80
|
return phase.startsWith('run:');
|
|
@@ -188,26 +146,6 @@ export function buildLifecycleTimeline(timings, feedback) {
|
|
|
188
146
|
}
|
|
189
147
|
return iterations;
|
|
190
148
|
}
|
|
191
|
-
/** Determine the outcome status of an iteration */
|
|
192
|
-
function getIterationOutcome(iteration) {
|
|
193
|
-
const lastLifecycle = [...iteration.timings].reverse().find((t) => isLifecycleEvent(t.phase));
|
|
194
|
-
if (!lastLifecycle)
|
|
195
|
-
return null;
|
|
196
|
-
switch (lastLifecycle.phase) {
|
|
197
|
-
case 'run:rejected':
|
|
198
|
-
return { label: 'Rejected', colorClass: 'text-orange-600', dotClass: 'bg-orange-500' };
|
|
199
|
-
case 'run:completed':
|
|
200
|
-
return { label: 'Completed', colorClass: 'text-emerald-600', dotClass: 'bg-emerald-500' };
|
|
201
|
-
case 'run:failed':
|
|
202
|
-
return { label: 'Failed', colorClass: 'text-red-600', dotClass: 'bg-red-500' };
|
|
203
|
-
case 'run:crashed':
|
|
204
|
-
return { label: 'Crashed', colorClass: 'text-red-600', dotClass: 'bg-red-500' };
|
|
205
|
-
case 'run:stopped':
|
|
206
|
-
return { label: 'Stopped', colorClass: 'text-amber-600', dotClass: 'bg-amber-500' };
|
|
207
|
-
default:
|
|
208
|
-
return null;
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
149
|
/* ---------------------------------------------------------------------------
|
|
212
150
|
* Component
|
|
213
151
|
* ------------------------------------------------------------------------- */
|
|
@@ -217,10 +155,10 @@ export function ActivityTab({ timings, loading, error, rejectionFeedback }) {
|
|
|
217
155
|
return (_jsx("div", { "data-testid": "activity-tab-loading", className: "flex items-center justify-center p-8", children: _jsx(Loader2, { className: "text-muted-foreground h-6 w-6 animate-spin" }) }));
|
|
218
156
|
}
|
|
219
157
|
if (error) {
|
|
220
|
-
return (_jsxs("div", { className: "flex items-center gap-2 p-4 text-
|
|
158
|
+
return (_jsxs("div", { className: "flex items-center gap-2 p-4 text-sm text-red-600", children: [_jsx(AlertCircle, { className: "h-4 w-4 shrink-0" }), _jsx("span", { children: error })] }));
|
|
221
159
|
}
|
|
222
160
|
if (!timings || timings.length === 0) {
|
|
223
|
-
return (_jsxs("div", { className: "flex flex-col items-center justify-center gap-2 p-8", children: [_jsx(Clock, { className: "text-muted-foreground h-8 w-8" }), _jsx("p", { className: "text-muted-foreground text-
|
|
161
|
+
return (_jsxs("div", { className: "flex flex-col items-center justify-center gap-2 p-8", children: [_jsx(Clock, { className: "text-muted-foreground h-8 w-8" }), _jsx("p", { className: "text-muted-foreground text-sm", children: "No activity recorded yet" })] }));
|
|
224
162
|
}
|
|
225
163
|
const nodeTimings = timings.filter((t) => !isLifecycleEvent(t.phase));
|
|
226
164
|
const maxDurationMs = Math.max(...nodeTimings.map((t) => getEffectiveDuration(t, now)), ...nodeTimings.map((t) => t.approvalWaitMs ?? 0), 0);
|
|
@@ -231,36 +169,35 @@ export function ActivityTab({ timings, loading, error, rejectionFeedback }) {
|
|
|
231
169
|
const totalInputTokens = nodeTimings.reduce((sum, t) => sum + (t.inputTokens ?? 0), 0);
|
|
232
170
|
const totalOutputTokens = nodeTimings.reduce((sum, t) => sum + (t.outputTokens ?? 0), 0);
|
|
233
171
|
const totalCostUsd = nodeTimings.reduce((sum, t) => sum + (t.costUsd ?? 0), 0);
|
|
234
|
-
return (_jsxs("div", { className: "flex flex-col gap-
|
|
172
|
+
return (_jsxs("div", { className: "flex flex-col gap-4 p-4", children: [_jsx("div", { "data-testid": "activity-timings", className: "flex flex-col gap-3", children: iterations.map((iteration) => (_jsx(IterationGroup, { iteration: iteration, showHeader: hasMultipleIterations, maxDurationMs: maxDurationMs, now: now }, iteration.number))) }), totalExecMs > 0 ? (_jsx(SummaryTotals, { totalExecMs: totalExecMs, totalWaitMs: totalWaitMs, totalInputTokens: totalInputTokens, totalOutputTokens: totalOutputTokens, totalCostUsd: totalCostUsd })) : null] }));
|
|
235
173
|
}
|
|
236
174
|
/* ---------------------------------------------------------------------------
|
|
237
175
|
* Iteration group — one cycle of work
|
|
238
176
|
* ------------------------------------------------------------------------- */
|
|
239
177
|
function IterationGroup({ iteration, showHeader, maxDurationMs, now, }) {
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
178
|
+
return (_jsxs("div", { "data-testid": `iteration-${iteration.number}`, className: "flex flex-col gap-1.5", children: [showHeader ? (_jsxs("div", { className: "text-muted-foreground text-xs font-medium", children: ["Iteration ", iteration.number] })) : null, iteration.timings.map((t) => {
|
|
179
|
+
if (isLifecycleEvent(t.phase)) {
|
|
180
|
+
// For run:rejected, the message comes from the iteration, not a separate map
|
|
181
|
+
const isRejection = t.phase === 'run:rejected';
|
|
182
|
+
return (_jsx(LifecycleEventRow, { timing: t, message: isRejection ? iteration.rejectionMessage : undefined, attachments: isRejection ? iteration.rejectionAttachments : undefined }, `${t.agentRunId}-${t.phase}-${t.startedAt}`));
|
|
183
|
+
}
|
|
184
|
+
return (_jsx(NodeTimingRow, { timing: t, maxDurationMs: maxDurationMs, now: now }, `${t.agentRunId}-${t.phase}-${t.startedAt}`));
|
|
185
|
+
})] }));
|
|
248
186
|
}
|
|
249
187
|
/* ---------------------------------------------------------------------------
|
|
250
188
|
* Row components
|
|
251
189
|
* ------------------------------------------------------------------------- */
|
|
252
|
-
function LifecycleEventRow({ timing, message, attachments,
|
|
190
|
+
function LifecycleEventRow({ timing, message, attachments, }) {
|
|
253
191
|
const event = LIFECYCLE_EVENTS[timing.phase];
|
|
254
192
|
if (!event)
|
|
255
193
|
return null;
|
|
256
194
|
const hasAttachments = attachments && attachments.length > 0;
|
|
257
|
-
|
|
258
|
-
return (_jsxs("div", { className: `relative flex flex-col gap-1 px-3 ${isFirst ? 'pt-2' : 'pt-1'} ${isLast ? 'pb-2' : 'pb-1'}`, children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: `relative z-10 flex h-5 w-5 shrink-0 items-center justify-center rounded-full ${event.bgClass}`, children: _jsx(Icon, { className: `h-3 w-3 ${event.colorClass}` }) }), _jsx("span", { className: `text-sm font-medium ${event.colorClass}`, children: event.label }), timing.startedAt &&
|
|
259
|
-
!(typeof timing.startedAt === 'string' && timing.startedAt.startsWith('synthetic')) ? (_jsx("span", { className: "text-muted-foreground/60 ml-auto text-xs tabular-nums", children: formatTimestamp(String(timing.startedAt)) })) : null] }), message ? (_jsxs("div", { className: "ml-[26px] flex items-start gap-1.5 rounded-md bg-orange-50/50 px-2 py-1.5 dark:bg-orange-950/20", children: [_jsx(MessageSquare, { className: "mt-0.5 h-3.5 w-3.5 shrink-0 text-orange-400" }), _jsxs("span", { "data-testid": "rejection-feedback-text", className: "text-muted-foreground text-xs leading-relaxed italic", children: ["\u2014 ", message] })] })) : null, hasAttachments ? (_jsx("div", { "data-testid": "rejection-feedback-attachments", className: "ml-[26px]", children: _jsx(InlineAttachments, { text: "", attachmentPaths: attachments }) })) : null] }));
|
|
195
|
+
return (_jsxs("div", { className: `text-xs ${event.colorClass}`, children: [_jsx("span", { children: event.label }), message ? (_jsxs("span", { "data-testid": "rejection-feedback-text", className: "text-muted-foreground ml-2 font-normal italic", children: ["\u2014 ", message] })) : null, hasAttachments ? (_jsx("div", { "data-testid": "rejection-feedback-attachments", className: "mt-1.5 ml-2", children: _jsx(InlineAttachments, { text: "", attachmentPaths: attachments }) })) : null] }));
|
|
260
196
|
}
|
|
261
197
|
function NodeTimingRow({ timing, maxDurationMs, now, }) {
|
|
262
198
|
const suffix = timing.phase.includes(':') ? timing.phase.split(':')[1] : null;
|
|
263
199
|
const isSubPhase = suffix !== null;
|
|
200
|
+
// Distinguish implementation sub-phases (phase-1, phase-2) from rejection retries (2, 3)
|
|
264
201
|
const isImplPhase = suffix?.startsWith('phase-') ?? false;
|
|
265
202
|
const label = isImplPhase
|
|
266
203
|
? `Phase ${suffix.replace('phase-', '')}`
|
|
@@ -280,21 +217,17 @@ function NodeTimingRow({ timing, maxDurationMs, now, }) {
|
|
|
280
217
|
const totalTokens = timing.inputTokens != null || timing.outputTokens != null
|
|
281
218
|
? (timing.inputTokens ?? 0) + (timing.outputTokens ?? 0)
|
|
282
219
|
: null;
|
|
283
|
-
return (_jsxs("div", { className:
|
|
284
|
-
? 'bg-blue-500'
|
|
285
|
-
: timing.completedAt
|
|
286
|
-
? 'bg-emerald-500'
|
|
287
|
-
: 'bg-muted-foreground/30'}` }) }), _jsx("span", { className: `w-28 shrink-0 truncate text-sm font-medium ${isSubPhase ? 'text-muted-foreground' : 'text-foreground/80'}`, children: label }), _jsx("div", { className: `bg-muted min-w-0 flex-1 overflow-hidden rounded-full ${isSubPhase ? 'h-1.5' : 'h-2'}`, children: _jsx("div", { className: `h-full rounded-full transition-all duration-300 ${barColorClass}${isRunning ? 'animate-pulse' : ''}`, style: { width: `${Math.min(barPercent, 100)}%` } }) }), _jsx("span", { className: "text-muted-foreground w-14 shrink-0 text-right text-sm font-medium tabular-nums", children: formatDuration(durationMs) })] }), _jsxs("div", { className: "ml-[28px] flex items-center gap-3 text-xs", children: [timing.startedAt ? (_jsx("span", { className: "text-muted-foreground/60 tabular-nums", children: formatTimestamp(timing.startedAt) })) : null, totalTokens != null && totalTokens > 0 ? (_jsxs("span", { className: "text-muted-foreground/70 inline-flex items-center gap-0.5", children: [_jsx(Zap, { className: "h-3 w-3 opacity-50" }), formatTokens(totalTokens)] })) : null, timing.costUsd != null && timing.costUsd > 0 ? (_jsxs("span", { className: "text-muted-foreground/70 inline-flex items-center gap-0.5", children: [_jsx(DollarSign, { className: "h-3 w-3 opacity-50" }), formatCost(timing.costUsd)] })) : null] }), timing.approvalWaitMs != null && timing.approvalWaitMs > 0 ? (_jsx(ApprovalWaitRow, { timing: timing, maxDurationMs: maxDurationMs })) : null] }));
|
|
220
|
+
return (_jsxs("div", { className: isSubPhase ? 'flex flex-col gap-1 pl-6' : 'flex flex-col gap-1', children: [_jsxs("div", { "data-testid": `timing-bar-${timing.phase}`, className: "flex items-center gap-2", children: [_jsx("span", { className: `text-muted-foreground shrink-0 text-xs ${isSubPhase ? 'w-18' : 'w-24'}`, children: label }), _jsx("div", { className: `bg-muted min-w-0 flex-1 overflow-hidden rounded-full ${isSubPhase ? 'h-2.5' : 'h-3'}`, children: _jsx("div", { className: `h-full rounded-full ${barColorClass}${isRunning ? 'animate-pulse' : ''}`, style: { width: `${Math.min(barPercent, 100)}%` } }) }), _jsx("span", { className: "text-muted-foreground w-10 shrink-0 text-right text-xs tabular-nums", children: formatDuration(durationMs) }), _jsx("span", { className: "text-muted-foreground w-12 shrink-0 text-right text-[10px] tabular-nums", children: totalTokens != null && totalTokens > 0 ? (_jsxs("span", { className: "inline-flex items-center gap-0.5", children: [_jsx(Zap, { className: "h-2 w-2 opacity-50" }), formatTokens(totalTokens)] })) : null }), _jsx("span", { className: "text-muted-foreground w-14 shrink-0 text-right text-[10px] tabular-nums", children: timing.costUsd != null && timing.costUsd > 0 ? (_jsxs("span", { className: "inline-flex items-center gap-0.5", children: [_jsx(DollarSign, { className: "h-2 w-2 opacity-50" }), formatCost(timing.costUsd)] })) : null })] }), timing.approvalWaitMs != null && timing.approvalWaitMs > 0 ? (_jsx(ApprovalWaitRow, { timing: timing, maxDurationMs: maxDurationMs })) : null] }));
|
|
288
221
|
}
|
|
289
222
|
function ApprovalWaitRow({ timing, maxDurationMs, }) {
|
|
290
223
|
const waitMs = timing.approvalWaitMs ?? 0;
|
|
291
224
|
const barPercent = maxDurationMs > 0 ? Math.max(2, (waitMs / maxDurationMs) * 100) : 2;
|
|
292
|
-
return (_jsxs("div", { "data-testid": `approval-wait-${timing.phase}`, className: "
|
|
225
|
+
return (_jsxs("div", { "data-testid": `approval-wait-${timing.phase}`, className: "flex items-center gap-2 pl-4", children: [_jsx("span", { className: "text-muted-foreground w-20 shrink-0 text-xs", children: "approval" }), _jsx("div", { className: "bg-muted h-2.5 min-w-0 flex-1 overflow-hidden rounded-full", children: _jsx("div", { className: "h-full rounded-full bg-amber-500", style: { width: `${Math.min(barPercent, 100)}%` } }) }), _jsx("span", { className: "text-muted-foreground w-10 shrink-0 text-right text-xs tabular-nums", children: formatDuration(waitMs) }), _jsx("span", { className: "w-12 shrink-0" }), _jsx("span", { className: "w-14 shrink-0" })] }));
|
|
293
226
|
}
|
|
294
227
|
function SummaryTotals({ totalExecMs, totalWaitMs, totalInputTokens, totalOutputTokens, totalCostUsd, }) {
|
|
295
228
|
const totalTokens = totalInputTokens + totalOutputTokens;
|
|
296
|
-
return (_jsxs("div", { "data-testid": "activity-summary", className: "border-border
|
|
229
|
+
return (_jsxs("div", { "data-testid": "activity-summary", className: "border-border flex flex-col gap-1.5 border-t pt-3", children: [_jsx(SummaryRow, { label: "Total execution", value: formatDuration(totalExecMs) }), totalWaitMs > 0 ? (_jsxs(_Fragment, { children: [_jsx(SummaryRow, { label: "Total wait", value: formatDuration(totalWaitMs) }), _jsx(SummaryRow, { label: "Total wall-clock", value: formatDuration(totalExecMs + totalWaitMs) })] })) : null, totalTokens > 0 ? (_jsx(SummaryRow, { label: "Total tokens", value: `${formatTokens(totalTokens)} (${formatTokens(totalInputTokens)} in · ${formatTokens(totalOutputTokens)} out)` })) : null, totalCostUsd > 0 ? _jsx(SummaryRow, { label: "Total cost", value: formatCost(totalCostUsd) }) : null] }));
|
|
297
230
|
}
|
|
298
|
-
function
|
|
299
|
-
return (_jsxs("div", { className:
|
|
231
|
+
function SummaryRow({ label, value }) {
|
|
232
|
+
return (_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("span", { className: "text-muted-foreground text-xs font-medium", children: label }), _jsx("span", { className: "text-xs font-medium", children: value })] }));
|
|
300
233
|
}
|
package/dist/src/presentation/web/components/features/control-center/control-center-inner.d.ts.map
CHANGED
|
@@ -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,
|
|
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"}
|
package/dist/src/presentation/web/components/features/control-center/control-center-inner.js
CHANGED
|
@@ -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
|
-
|
|
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,
|
|
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,
|
|
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"}
|
package/dist/src/presentation/web/components/features/control-center/use-control-center-state.js
CHANGED
|
@@ -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;
|
|
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
|
-
|
|
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
|
|
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,
|
|
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"}
|