agentic-orchestrator 0.1.25 → 0.1.26

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 (177) hide show
  1. package/agentic/orchestrator/defaults/policy.defaults.yaml +1 -0
  2. package/agentic/orchestrator/policy.yaml +1 -0
  3. package/agentic/orchestrator/schemas/policy.schema.json +4 -0
  4. package/agentic/orchestrator/schemas/policy.user.schema.json +4 -0
  5. package/apps/control-plane/src/application/services/activity-monitor-service.ts +42 -0
  6. package/apps/control-plane/src/application/services/collision-queue-service.ts +48 -0
  7. package/apps/control-plane/src/application/services/cost-tracking-service.ts +80 -0
  8. package/apps/control-plane/src/application/services/feature-deletion-service.ts +57 -0
  9. package/apps/control-plane/src/application/services/feature-lifecycle-service.ts +65 -0
  10. package/apps/control-plane/src/application/services/feature-state-service.ts +87 -0
  11. package/apps/control-plane/src/application/services/gate-selection-service.ts +28 -0
  12. package/apps/control-plane/src/application/services/gate-service.ts +148 -0
  13. package/apps/control-plane/src/application/services/issue-tracker-service.ts +28 -0
  14. package/apps/control-plane/src/application/services/lock-service.ts +88 -0
  15. package/apps/control-plane/src/application/services/merge-service.ts +63 -0
  16. package/apps/control-plane/src/application/services/notifier-service.ts +70 -0
  17. package/apps/control-plane/src/application/services/patch-service.ts +95 -0
  18. package/apps/control-plane/src/application/services/performance-analytics-service.ts +90 -0
  19. package/apps/control-plane/src/application/services/plan-service.ts +184 -0
  20. package/apps/control-plane/src/application/services/pr-monitor-service.ts +58 -0
  21. package/apps/control-plane/src/application/services/qa-index-service.ts +68 -0
  22. package/apps/control-plane/src/application/services/reactions-service.ts +77 -0
  23. package/apps/control-plane/src/application/services/reporting-service.ts +79 -0
  24. package/apps/control-plane/src/application/services/run-lease-service.ts +79 -0
  25. package/apps/control-plane/src/application/tools/tool-router.ts +76 -0
  26. package/apps/control-plane/src/mcp/tool-runtime.ts +4 -0
  27. package/apps/control-plane/src/providers/providers.ts +96 -0
  28. package/apps/control-plane/src/providers/worker-provider-factory.ts +4 -0
  29. package/apps/control-plane/src/supervisor/build-wave-executor.ts +4 -0
  30. package/apps/control-plane/src/supervisor/planning-wave-executor.ts +4 -0
  31. package/apps/control-plane/src/supervisor/qa-wave-executor.ts +4 -0
  32. package/apps/control-plane/src/supervisor/run-coordinator.ts +4 -0
  33. package/apps/control-plane/src/supervisor/runtime.ts +4 -0
  34. package/apps/control-plane/src/supervisor/session-orchestrator.ts +4 -0
  35. package/apps/control-plane/test/dashboard-api.integration.spec.ts +310 -1
  36. package/apps/control-plane/test/dashboard-client.spec.ts +136 -1
  37. package/apps/control-plane/test/dashboard-live-feed.spec.ts +153 -0
  38. package/apps/control-plane/test/dashboard-ui-utils.spec.ts +132 -0
  39. package/apps/control-plane/test/mcp.spec.ts +96 -0
  40. package/apps/control-plane/test/tool-runtime.spec.ts +68 -0
  41. package/config/agentic/orchestrator/policy.yaml +1 -0
  42. package/dist/apps/control-plane/application/services/activity-monitor-service.d.ts +42 -0
  43. package/dist/apps/control-plane/application/services/activity-monitor-service.js +42 -0
  44. package/dist/apps/control-plane/application/services/activity-monitor-service.js.map +1 -1
  45. package/dist/apps/control-plane/application/services/collision-queue-service.d.ts +48 -0
  46. package/dist/apps/control-plane/application/services/collision-queue-service.js +48 -0
  47. package/dist/apps/control-plane/application/services/collision-queue-service.js.map +1 -1
  48. package/dist/apps/control-plane/application/services/cost-tracking-service.d.ts +79 -0
  49. package/dist/apps/control-plane/application/services/cost-tracking-service.js +76 -0
  50. package/dist/apps/control-plane/application/services/cost-tracking-service.js.map +1 -1
  51. package/dist/apps/control-plane/application/services/feature-deletion-service.d.ts +57 -0
  52. package/dist/apps/control-plane/application/services/feature-deletion-service.js +57 -0
  53. package/dist/apps/control-plane/application/services/feature-deletion-service.js.map +1 -1
  54. package/dist/apps/control-plane/application/services/feature-lifecycle-service.d.ts +64 -0
  55. package/dist/apps/control-plane/application/services/feature-lifecycle-service.js +61 -0
  56. package/dist/apps/control-plane/application/services/feature-lifecycle-service.js.map +1 -1
  57. package/dist/apps/control-plane/application/services/feature-state-service.d.ts +86 -0
  58. package/dist/apps/control-plane/application/services/feature-state-service.js +83 -0
  59. package/dist/apps/control-plane/application/services/feature-state-service.js.map +1 -1
  60. package/dist/apps/control-plane/application/services/gate-selection-service.d.ts +28 -0
  61. package/dist/apps/control-plane/application/services/gate-selection-service.js +28 -0
  62. package/dist/apps/control-plane/application/services/gate-selection-service.js.map +1 -1
  63. package/dist/apps/control-plane/application/services/gate-service.d.ts +148 -0
  64. package/dist/apps/control-plane/application/services/gate-service.js +120 -0
  65. package/dist/apps/control-plane/application/services/gate-service.js.map +1 -1
  66. package/dist/apps/control-plane/application/services/issue-tracker-service.d.ts +28 -0
  67. package/dist/apps/control-plane/application/services/issue-tracker-service.js +28 -0
  68. package/dist/apps/control-plane/application/services/issue-tracker-service.js.map +1 -1
  69. package/dist/apps/control-plane/application/services/lock-service.d.ts +88 -0
  70. package/dist/apps/control-plane/application/services/lock-service.js +64 -0
  71. package/dist/apps/control-plane/application/services/lock-service.js.map +1 -1
  72. package/dist/apps/control-plane/application/services/merge-service.d.ts +62 -0
  73. package/dist/apps/control-plane/application/services/merge-service.js +59 -0
  74. package/dist/apps/control-plane/application/services/merge-service.js.map +1 -1
  75. package/dist/apps/control-plane/application/services/notifier-service.d.ts +70 -0
  76. package/dist/apps/control-plane/application/services/notifier-service.js +70 -0
  77. package/dist/apps/control-plane/application/services/notifier-service.js.map +1 -1
  78. package/dist/apps/control-plane/application/services/patch-service.d.ts +94 -0
  79. package/dist/apps/control-plane/application/services/patch-service.js +91 -0
  80. package/dist/apps/control-plane/application/services/patch-service.js.map +1 -1
  81. package/dist/apps/control-plane/application/services/performance-analytics-service.d.ts +89 -0
  82. package/dist/apps/control-plane/application/services/performance-analytics-service.js +86 -0
  83. package/dist/apps/control-plane/application/services/performance-analytics-service.js.map +1 -1
  84. package/dist/apps/control-plane/application/services/plan-service.d.ts +287 -0
  85. package/dist/apps/control-plane/application/services/plan-service.js +207 -0
  86. package/dist/apps/control-plane/application/services/plan-service.js.map +1 -1
  87. package/dist/apps/control-plane/application/services/pr-monitor-service.d.ts +58 -0
  88. package/dist/apps/control-plane/application/services/pr-monitor-service.js +58 -0
  89. package/dist/apps/control-plane/application/services/pr-monitor-service.js.map +1 -1
  90. package/dist/apps/control-plane/application/services/qa-index-service.d.ts +67 -0
  91. package/dist/apps/control-plane/application/services/qa-index-service.js +64 -0
  92. package/dist/apps/control-plane/application/services/qa-index-service.js.map +1 -1
  93. package/dist/apps/control-plane/application/services/reactions-service.d.ts +76 -0
  94. package/dist/apps/control-plane/application/services/reactions-service.js +73 -0
  95. package/dist/apps/control-plane/application/services/reactions-service.js.map +1 -1
  96. package/dist/apps/control-plane/application/services/reporting-service.d.ts +78 -0
  97. package/dist/apps/control-plane/application/services/reporting-service.js +75 -0
  98. package/dist/apps/control-plane/application/services/reporting-service.js.map +1 -1
  99. package/dist/apps/control-plane/application/services/run-lease-service.d.ts +79 -0
  100. package/dist/apps/control-plane/application/services/run-lease-service.js +79 -0
  101. package/dist/apps/control-plane/application/services/run-lease-service.js.map +1 -1
  102. package/dist/apps/control-plane/application/tools/tool-router.d.ts +76 -0
  103. package/dist/apps/control-plane/application/tools/tool-router.js +62 -0
  104. package/dist/apps/control-plane/application/tools/tool-router.js.map +1 -1
  105. package/dist/apps/control-plane/mcp/tool-runtime.d.ts +3 -0
  106. package/dist/apps/control-plane/mcp/tool-runtime.js +3 -0
  107. package/dist/apps/control-plane/mcp/tool-runtime.js.map +1 -1
  108. package/dist/apps/control-plane/providers/providers.d.ts +88 -0
  109. package/dist/apps/control-plane/providers/providers.js.map +1 -1
  110. package/dist/apps/control-plane/providers/worker-provider-factory.d.ts +3 -0
  111. package/dist/apps/control-plane/providers/worker-provider-factory.js.map +1 -1
  112. package/dist/apps/control-plane/supervisor/build-wave-executor.d.ts +3 -0
  113. package/dist/apps/control-plane/supervisor/build-wave-executor.js +3 -0
  114. package/dist/apps/control-plane/supervisor/build-wave-executor.js.map +1 -1
  115. package/dist/apps/control-plane/supervisor/planning-wave-executor.d.ts +3 -0
  116. package/dist/apps/control-plane/supervisor/planning-wave-executor.js +3 -0
  117. package/dist/apps/control-plane/supervisor/planning-wave-executor.js.map +1 -1
  118. package/dist/apps/control-plane/supervisor/qa-wave-executor.d.ts +3 -0
  119. package/dist/apps/control-plane/supervisor/qa-wave-executor.js +3 -0
  120. package/dist/apps/control-plane/supervisor/qa-wave-executor.js.map +1 -1
  121. package/dist/apps/control-plane/supervisor/run-coordinator.d.ts +3 -0
  122. package/dist/apps/control-plane/supervisor/run-coordinator.js +3 -0
  123. package/dist/apps/control-plane/supervisor/run-coordinator.js.map +1 -1
  124. package/dist/apps/control-plane/supervisor/runtime.d.ts +3 -0
  125. package/dist/apps/control-plane/supervisor/runtime.js +3 -0
  126. package/dist/apps/control-plane/supervisor/runtime.js.map +1 -1
  127. package/dist/apps/control-plane/supervisor/session-orchestrator.d.ts +3 -0
  128. package/dist/apps/control-plane/supervisor/session-orchestrator.js +3 -0
  129. package/dist/apps/control-plane/supervisor/session-orchestrator.js.map +1 -1
  130. package/package.json +1 -1
  131. package/packages/web-dashboard/src/app/analytics/page.tsx +451 -0
  132. package/packages/web-dashboard/src/app/api/analytics/route.ts +62 -0
  133. package/packages/web-dashboard/src/app/api/collisions/route.ts +63 -0
  134. package/packages/web-dashboard/src/app/api/features/[id]/cost/route.ts +29 -0
  135. package/packages/web-dashboard/src/app/api/features/[id]/review-brief/route.ts +31 -0
  136. package/packages/web-dashboard/src/app/api/features/[id]/route.ts +5 -8
  137. package/packages/web-dashboard/src/app/api/features/[id]/test-index/route.ts +31 -0
  138. package/packages/web-dashboard/src/app/api/flaky/route.ts +20 -0
  139. package/packages/web-dashboard/src/app/api/policy/budget/route.ts +31 -0
  140. package/packages/web-dashboard/src/app/api/run/route.ts +162 -0
  141. package/packages/web-dashboard/src/app/api/status/route.ts +2 -5
  142. package/packages/web-dashboard/src/app/feature/[id]/page.tsx +112 -0
  143. package/packages/web-dashboard/src/app/page.tsx +379 -77
  144. package/packages/web-dashboard/src/components/agent-pipeline-stepper.tsx +100 -0
  145. package/packages/web-dashboard/src/components/dependency-chains.tsx +58 -0
  146. package/packages/web-dashboard/src/components/detail-panel.tsx +215 -5
  147. package/packages/web-dashboard/src/components/feature-card.tsx +5 -0
  148. package/packages/web-dashboard/src/components/feature-cost-panel.tsx +66 -0
  149. package/packages/web-dashboard/src/components/feature-list-view.tsx +336 -0
  150. package/packages/web-dashboard/src/components/gate-step-drilldown.tsx +92 -0
  151. package/packages/web-dashboard/src/components/kanban-board.tsx +3 -0
  152. package/packages/web-dashboard/src/components/live-event-feed.tsx +60 -0
  153. package/packages/web-dashboard/src/components/lock-resource-map.tsx +73 -0
  154. package/packages/web-dashboard/src/components/plan-revision-timeline.tsx +55 -0
  155. package/packages/web-dashboard/src/components/plan-risk-annotations.tsx +63 -0
  156. package/packages/web-dashboard/src/components/plan-scope-tree.tsx +285 -0
  157. package/packages/web-dashboard/src/components/qa-coverage-map.tsx +174 -0
  158. package/packages/web-dashboard/src/components/quick-launch-panel.tsx +70 -0
  159. package/packages/web-dashboard/src/components/review-brief-panel.tsx +75 -0
  160. package/packages/web-dashboard/src/components/run-health-panel.tsx +85 -0
  161. package/packages/web-dashboard/src/components/summary-bar.tsx +59 -2
  162. package/packages/web-dashboard/src/components/verification-signals.tsx +167 -0
  163. package/packages/web-dashboard/src/lib/analytics-utils.ts +3 -0
  164. package/packages/web-dashboard/src/lib/aop-client.ts +510 -11
  165. package/packages/web-dashboard/src/lib/api-envelope.ts +48 -0
  166. package/packages/web-dashboard/src/lib/authz-adapter.ts +33 -0
  167. package/packages/web-dashboard/src/lib/dashboard-utils.ts +304 -0
  168. package/packages/web-dashboard/src/lib/live-feed.ts +218 -0
  169. package/packages/web-dashboard/src/lib/policy-reader.ts +22 -0
  170. package/packages/web-dashboard/src/lib/types.ts +114 -0
  171. package/packages/web-dashboard/src/styles/dashboard.module.css +341 -1
  172. package/spec-files/{outstanding → completed}/agentic_orchestrator_dashboard_advanced_ux_spec.md +1 -1
  173. package/spec-files/outstanding/agentic_orchestrator_evidence_integrity_doctor_spec.md +561 -0
  174. package/spec-files/outstanding/agentic_orchestrator_kernel_simplification_spec.md +834 -0
  175. package/spec-files/outstanding/agentic_orchestrator_observability_integrity_diagnostics_spec.md +12 -5
  176. package/spec-files/outstanding/agentic_orchestrator_observability_replay_spec.md +442 -0
  177. package/spec-files/progress.md +174 -1
@@ -0,0 +1,20 @@
1
+ import { okEnvelope } from '@/lib/api-envelope.js';
2
+ import { readDashboardStatus, resolveProjectRoot } from '@/lib/aop-client.js';
3
+
4
+ export const dynamic = 'force-dynamic';
5
+
6
+ export async function GET(req: Request): Promise<Response> {
7
+ const project = new URL(req.url).searchParams.get('project');
8
+ const repoRoot = await resolveProjectRoot(project);
9
+ const payload = await readDashboardStatus(repoRoot);
10
+ const suspects = Array.isArray(payload.flaky_suspects) ? payload.flaky_suspects : [];
11
+ return Response.json(
12
+ okEnvelope(
13
+ {
14
+ suspects,
15
+ available: suspects.length > 0,
16
+ },
17
+ { source: 'derived' },
18
+ ),
19
+ );
20
+ }
@@ -0,0 +1,31 @@
1
+ import { errorEnvelope, okEnvelope } from '@/lib/api-envelope.js';
2
+ import { resolveProjectRoot } from '@/lib/aop-client.js';
3
+ import { readPolicyYaml } from '@/lib/policy-reader.js';
4
+
5
+ export const dynamic = 'force-dynamic';
6
+
7
+ export async function GET(req: Request): Promise<Response> {
8
+ const project = new URL(req.url).searchParams.get('project');
9
+ const repoRoot = await resolveProjectRoot(project);
10
+ const policy = await readPolicyYaml(repoRoot);
11
+ const budget =
12
+ policy && typeof policy.budget === 'object' ? (policy.budget as Record<string, unknown>) : null;
13
+
14
+ if (!budget) {
15
+ return Response.json(
16
+ errorEnvelope('policy_not_configured', 'Budget policy is not configured for this project'),
17
+ { status: 404 },
18
+ );
19
+ }
20
+
21
+ return Response.json(
22
+ okEnvelope(
23
+ {
24
+ per_feature_limit_usd:
25
+ typeof budget.per_feature_limit_usd === 'number' ? budget.per_feature_limit_usd : null,
26
+ alert_threshold: typeof budget.alert_threshold === 'number' ? budget.alert_threshold : 0.8,
27
+ },
28
+ { source: 'artifact' },
29
+ ),
30
+ );
31
+ }
@@ -0,0 +1,162 @@
1
+ import { authorizeDashboardAction } from '@/lib/authz-adapter.js';
2
+ import { errorEnvelope, okEnvelope } from '@/lib/api-envelope.js';
3
+ import { resolveProjectRoot } from '@/lib/aop-client.js';
4
+ import { callOrchestratorTool } from '@/lib/orchestrator-tools.js';
5
+ import { readPolicyYaml } from '@/lib/policy-reader.js';
6
+
7
+ export const dynamic = 'force-dynamic';
8
+
9
+ interface RunRequestBody {
10
+ feature_id?: string;
11
+ goal?: string;
12
+ confirm_launch?: boolean;
13
+ }
14
+
15
+ interface QuickLaunchAvailability {
16
+ available: boolean;
17
+ enabled: boolean;
18
+ authorized: boolean;
19
+ reason: string | null;
20
+ }
21
+
22
+ const FEATURE_ID_PATTERN = /^[a-z0-9](?:[a-z0-9_-]{1,126}[a-z0-9])?$/;
23
+
24
+ function quickLaunchEnabled(policy: Record<string, unknown> | null): boolean {
25
+ if (process.env.AOP_DASHBOARD_QUICK_LAUNCH === 'true') {
26
+ return true;
27
+ }
28
+ const dashboard =
29
+ policy && typeof policy.dashboard === 'object'
30
+ ? (policy.dashboard as Record<string, unknown>)
31
+ : null;
32
+ return dashboard?.quick_launch === true;
33
+ }
34
+
35
+ async function getAvailability(
36
+ req: Request,
37
+ project: string | null,
38
+ policy: Record<string, unknown> | null,
39
+ ): Promise<QuickLaunchAvailability> {
40
+ const enabled = quickLaunchEnabled(policy);
41
+ if (!enabled) {
42
+ return {
43
+ available: false,
44
+ enabled: false,
45
+ authorized: false,
46
+ reason:
47
+ 'Quick launch is disabled by policy. Enable dashboard.quick_launch or AOP_DASHBOARD_QUICK_LAUNCH=true.',
48
+ };
49
+ }
50
+
51
+ const auth = await authorizeDashboardAction({
52
+ action: 'dashboard.quick_launch',
53
+ project,
54
+ request: req,
55
+ });
56
+
57
+ if (!auth.allowed) {
58
+ return {
59
+ available: false,
60
+ enabled: true,
61
+ authorized: false,
62
+ reason: auth.message ?? 'Action not authorized',
63
+ };
64
+ }
65
+
66
+ return {
67
+ available: true,
68
+ enabled: true,
69
+ authorized: true,
70
+ reason: null,
71
+ };
72
+ }
73
+
74
+ export async function GET(req: Request): Promise<Response> {
75
+ const url = new URL(req.url);
76
+ const project = url.searchParams.get('project');
77
+ const repoRoot = await resolveProjectRoot(project);
78
+ const policy = await readPolicyYaml(repoRoot);
79
+ const availability = await getAvailability(req, project, policy);
80
+ return Response.json(okEnvelope(availability, { source: 'artifact' }));
81
+ }
82
+
83
+ export async function POST(req: Request): Promise<Response> {
84
+ const url = new URL(req.url);
85
+ const project = url.searchParams.get('project');
86
+ const repoRoot = await resolveProjectRoot(project);
87
+ const policy = await readPolicyYaml(repoRoot);
88
+ const availability = await getAvailability(req, project, policy);
89
+ if (!availability.available) {
90
+ return Response.json(
91
+ errorEnvelope('unauthorized_action', availability.reason ?? 'Action not authorized'),
92
+ { status: 403 },
93
+ );
94
+ }
95
+
96
+ const body = (await req.json()) as RunRequestBody;
97
+ const featureId = typeof body.feature_id === 'string' ? body.feature_id.trim() : '';
98
+ const goal = typeof body.goal === 'string' ? body.goal.trim() : '';
99
+ const confirmed = body.confirm_launch === true;
100
+
101
+ if (!confirmed) {
102
+ return Response.json(
103
+ errorEnvelope(
104
+ 'invalid_input',
105
+ 'confirm_launch=true is required to prevent accidental feature launches.',
106
+ ),
107
+ { status: 400 },
108
+ );
109
+ }
110
+
111
+ if (!FEATURE_ID_PATTERN.test(featureId)) {
112
+ return Response.json(
113
+ errorEnvelope(
114
+ 'invalid_input',
115
+ 'feature_id must match /^[a-z0-9](?:[a-z0-9_-]{1,126}[a-z0-9])?$/',
116
+ ),
117
+ { status: 400 },
118
+ );
119
+ }
120
+
121
+ if (goal.length === 0) {
122
+ return Response.json(errorEnvelope('invalid_input', 'goal is required'), {
123
+ status: 400,
124
+ });
125
+ }
126
+
127
+ const result = await callOrchestratorTool(
128
+ 'feature.init',
129
+ {
130
+ feature_id: featureId,
131
+ goal,
132
+ },
133
+ {
134
+ mutating: true,
135
+ featureId,
136
+ repoRoot,
137
+ },
138
+ );
139
+
140
+ if (!result.ok) {
141
+ return Response.json(
142
+ errorEnvelope(
143
+ result.error?.code ?? 'tool_unavailable',
144
+ result.error?.message ?? 'Unable to launch feature',
145
+ { retryable: result.error?.code === 'tool_timeout' },
146
+ ),
147
+ { status: 409 },
148
+ );
149
+ }
150
+
151
+ return Response.json(
152
+ okEnvelope(
153
+ {
154
+ feature_id: featureId,
155
+ goal,
156
+ status: 'initialized',
157
+ },
158
+ { source: 'tool' },
159
+ ),
160
+ { status: 201 },
161
+ );
162
+ }
@@ -1,4 +1,5 @@
1
1
  import { readDashboardStatus, resolveProjectRoot } from '@/lib/aop-client.js';
2
+ import { okEnvelope } from '@/lib/api-envelope.js';
2
3
 
3
4
  export const dynamic = 'force-dynamic';
4
5
 
@@ -7,9 +8,5 @@ export async function GET(req: Request): Promise<Response> {
7
8
  const project = url.searchParams.get('project');
8
9
  const repoRoot = await resolveProjectRoot(project);
9
10
  const payload = await readDashboardStatus(repoRoot);
10
- return Response.json({
11
- ok: true,
12
- data: payload,
13
- timestamp: new Date().toISOString(),
14
- });
11
+ return Response.json(okEnvelope(payload, { source: 'derived' }));
15
12
  }
@@ -0,0 +1,112 @@
1
+ 'use client';
2
+
3
+ import { useCallback, useEffect, useMemo, useState } from 'react';
4
+ import Link from 'next/link';
5
+ import { DetailPanel } from '@/components/detail-panel';
6
+ import { ToastStack, useToast } from '@/components/toast';
7
+ import type { DashboardStatusPayload, FeatureDetail } from '@/lib/types.js';
8
+ import styles from '@/styles/dashboard.module.css';
9
+
10
+ function emptyPayload(): DashboardStatusPayload {
11
+ return {
12
+ index: { active: [], blocked: [], merged: [], blocked_queue: [] },
13
+ features: [],
14
+ };
15
+ }
16
+
17
+ async function jsonFetch<T>(input: string, init?: RequestInit): Promise<T> {
18
+ const response = await fetch(input, init);
19
+ return (await response.json()) as T;
20
+ }
21
+
22
+ export default function FeatureFocusPage({ params }: { params: Promise<{ id: string }> }) {
23
+ const [featureId, setFeatureId] = useState<string>('');
24
+ const [project, setProject] = useState<string>('');
25
+ const [detail, setDetail] = useState<FeatureDetail | null>(null);
26
+ const [payload, setPayload] = useState<DashboardStatusPayload>(emptyPayload());
27
+ const { notifications, addToast, dismissToast } = useToast();
28
+
29
+ useEffect(() => {
30
+ void (async () => {
31
+ const resolved = await params;
32
+ setFeatureId(resolved.id);
33
+ })();
34
+ }, [params]);
35
+
36
+ useEffect(() => {
37
+ const current = new URL(window.location.href).searchParams.get('project');
38
+ setProject(current ?? '');
39
+ }, []);
40
+
41
+ const query = useMemo(() => {
42
+ return project.length > 0 ? `?project=${encodeURIComponent(project)}` : '';
43
+ }, [project]);
44
+
45
+ const refreshStatus = useCallback(async () => {
46
+ const response = await jsonFetch<{ ok: boolean; data?: DashboardStatusPayload }>(
47
+ `/api/status${query}`,
48
+ );
49
+ if (response.ok && response.data) {
50
+ setPayload(response.data);
51
+ }
52
+ }, [query]);
53
+
54
+ const refreshDetail = useCallback(
55
+ async (targetFeatureId: string) => {
56
+ const response = await jsonFetch<{ ok: boolean; data?: FeatureDetail }>(
57
+ `/api/features/${targetFeatureId}${query}`,
58
+ );
59
+ if (response.ok && response.data) {
60
+ setDetail(response.data);
61
+ } else {
62
+ setDetail(null);
63
+ }
64
+ },
65
+ [query],
66
+ );
67
+
68
+ useEffect(() => {
69
+ if (featureId.length === 0) {
70
+ return;
71
+ }
72
+ void refreshDetail(featureId);
73
+ void refreshStatus();
74
+ }, [featureId, refreshDetail, refreshStatus]);
75
+
76
+ const statusLabel =
77
+ payload.features.find((feature) => feature.feature_id === featureId)?.status ??
78
+ detail?.feature.status ??
79
+ 'unknown';
80
+
81
+ return (
82
+ <div className={styles.dashboardRoot}>
83
+ <header className={styles.focusHeader}>
84
+ <div className={styles.titleBlock}>
85
+ <h1 className={styles.pageTitle}>{featureId}</h1>
86
+ <p className={styles.pageSubtitle}>Focus review view • Status: {statusLabel}</p>
87
+ </div>
88
+ <div className={styles.inlineActions}>
89
+ <Link href={`/${query.length > 0 ? query : ''}`} className={styles.linkButton}>
90
+ Back To Board
91
+ </Link>
92
+ </div>
93
+ </header>
94
+
95
+ <div className={styles.focusLayout}>
96
+ <DetailPanel
97
+ detail={detail}
98
+ project={project}
99
+ isFocusView
100
+ onClose={() => {
101
+ window.location.href = `/${query.length > 0 ? query : ''}`;
102
+ }}
103
+ refreshDetail={refreshDetail}
104
+ refreshStatus={refreshStatus}
105
+ addToast={addToast}
106
+ />
107
+ </div>
108
+
109
+ <ToastStack notifications={notifications} onDismiss={dismissToast} />
110
+ </div>
111
+ );
112
+ }