@matt82198/aesop 0.1.0-beta.5 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (198) hide show
  1. package/CHANGELOG.md +158 -5
  2. package/LICENSE +66 -21
  3. package/README.md +97 -33
  4. package/aesop.config.example.json +6 -0
  5. package/bin/CLAUDE.md +12 -3
  6. package/bin/cli.js +211 -40
  7. package/daemons/CLAUDE.md +1 -1
  8. package/daemons/backup-fleet.sh +209 -51
  9. package/daemons/run-watchdog.sh +208 -62
  10. package/dash/dash-extra.mjs +73 -4
  11. package/dash/watchdog-gui.sh +41 -35
  12. package/docs/ARCHITECTURE.md +296 -0
  13. package/docs/CONCEPTS.md +254 -0
  14. package/docs/CONFIGURE.md +256 -0
  15. package/docs/FIRST-WAVE.md +276 -0
  16. package/docs/INSTALL.md +224 -0
  17. package/docs/README.md +176 -27
  18. package/docs/autonomous-swe.md +149 -0
  19. package/docs/reproduce.md +121 -0
  20. package/hooks/CLAUDE.md +28 -0
  21. package/hooks/install-waveguard.sh +68 -0
  22. package/hooks/pre-commit-waveguard.sh +26 -0
  23. package/hooks/pre-push-policy.sh +253 -15
  24. package/monitor/CLAUDE.md +3 -3
  25. package/monitor/collect-signals.mjs +133 -20
  26. package/package.json +15 -7
  27. package/scan/CLAUDE.md +30 -0
  28. package/skills/CLAUDE.md +1 -0
  29. package/state_store/ingest.py +18 -1
  30. package/state_store/projections.py +78 -4
  31. package/state_store/store.py +102 -7
  32. package/tools/CLAUDE.md +25 -18
  33. package/tools/alert_bridge.py +14 -10
  34. package/tools/bench_runner.py +438 -0
  35. package/tools/buildlog.py +4 -7
  36. package/tools/ci_merge_wait.py +215 -34
  37. package/tools/common.py +62 -0
  38. package/tools/cost_ceiling.py +191 -0
  39. package/tools/dash.js +102 -0
  40. package/tools/doctor.js +220 -0
  41. package/tools/fleet_ledger.py +189 -17
  42. package/tools/halt.py +172 -0
  43. package/tools/healthcheck.py +24 -21
  44. package/tools/heartbeat.py +4 -7
  45. package/tools/metrics_gate.py +8 -2
  46. package/tools/orchestrator_status.py +4 -9
  47. package/tools/reconcile.py +277 -0
  48. package/tools/rotate_logs.py +152 -62
  49. package/tools/scanner_selftest.py +28 -3
  50. package/tools/secret_scan.py +359 -90
  51. package/tools/self_stats.py +292 -4
  52. package/tools/status.js +187 -0
  53. package/tools/verify_agent_inspector.py +289 -0
  54. package/tools/verify_prboard.py +344 -0
  55. package/tools/watch.js +49 -0
  56. package/ui/CLAUDE.md +2 -0
  57. package/ui/agents.py +332 -33
  58. package/ui/api/tracker.py +15 -7
  59. package/ui/collectors.py +125 -32
  60. package/ui/config.py +22 -2
  61. package/ui/cost.py +98 -3
  62. package/ui/csrf.py +2 -1
  63. package/ui/handler.py +209 -21
  64. package/ui/sse.py +102 -16
  65. package/ui/wave_prs.py +230 -0
  66. package/ui/web/dist/assets/index-0qQYnvMC.js +9 -0
  67. package/ui/web/dist/assets/index-BdIlFieV.css +1 -0
  68. package/ui/web/dist/index.html +2 -2
  69. package/monitor/.signal-state.json +0 -3
  70. package/monitor/ACTIONS.log +0 -1
  71. package/monitor/BRIEF.md +0 -24
  72. package/monitor/SIGNALS.json +0 -54
  73. package/state_store/__pycache__/__init__.cpython-314.pyc +0 -0
  74. package/state_store/__pycache__/api.cpython-314.pyc +0 -0
  75. package/state_store/__pycache__/export.cpython-314.pyc +0 -0
  76. package/state_store/__pycache__/ingest.cpython-314.pyc +0 -0
  77. package/state_store/__pycache__/projections.cpython-314.pyc +0 -0
  78. package/state_store/__pycache__/store.cpython-314.pyc +0 -0
  79. package/tools/__pycache__/alert_bridge.cpython-314.pyc +0 -0
  80. package/tools/__pycache__/buildlog.cpython-314.pyc +0 -0
  81. package/tools/__pycache__/ci_merge_wait.cpython-314.pyc +0 -0
  82. package/tools/__pycache__/ensure_state.cpython-314.pyc +0 -0
  83. package/tools/__pycache__/eod_sweep.cpython-314.pyc +0 -0
  84. package/tools/__pycache__/fleet_ledger.cpython-314.pyc +0 -0
  85. package/tools/__pycache__/fleet_prompt_extractor.cpython-314.pyc +0 -0
  86. package/tools/__pycache__/healthcheck.cpython-314.pyc +0 -0
  87. package/tools/__pycache__/heartbeat.cpython-314.pyc +0 -0
  88. package/tools/__pycache__/inbox_drain.cpython-314.pyc +0 -0
  89. package/tools/__pycache__/launch_tui.cpython-314.pyc +0 -0
  90. package/tools/__pycache__/metrics_gate.cpython-314.pyc +0 -0
  91. package/tools/__pycache__/orchestrator_status.cpython-314.pyc +0 -0
  92. package/tools/__pycache__/power_selftest.cpython-314.pyc +0 -0
  93. package/tools/__pycache__/prepublish_scan.cpython-314.pyc +0 -0
  94. package/tools/__pycache__/rotate_logs.cpython-314.pyc +0 -0
  95. package/tools/__pycache__/scanner_selftest.cpython-314.pyc +0 -0
  96. package/tools/__pycache__/secret_scan.cpython-314.pyc +0 -0
  97. package/tools/__pycache__/self_stats.cpython-314.pyc +0 -0
  98. package/tools/__pycache__/session_usage_summary.cpython-314.pyc +0 -0
  99. package/tools/__pycache__/stall_check.cpython-314.pyc +0 -0
  100. package/tools/__pycache__/transcript_replay.cpython-314.pyc +0 -0
  101. package/tools/__pycache__/transcript_timeline.cpython-314.pyc +0 -0
  102. package/tools/__pycache__/verify_dash.cpython-314.pyc +0 -0
  103. package/tools/__pycache__/verify_submit_encoding.cpython-314.pyc +0 -0
  104. package/ui/__pycache__/agents.cpython-314.pyc +0 -0
  105. package/ui/__pycache__/collectors.cpython-314.pyc +0 -0
  106. package/ui/__pycache__/config.cpython-314.pyc +0 -0
  107. package/ui/__pycache__/cost.cpython-314.pyc +0 -0
  108. package/ui/__pycache__/csrf.cpython-314.pyc +0 -0
  109. package/ui/__pycache__/handler.cpython-314.pyc +0 -0
  110. package/ui/__pycache__/render.cpython-314.pyc +0 -0
  111. package/ui/__pycache__/serve.cpython-314.pyc +0 -0
  112. package/ui/__pycache__/sse.cpython-314.pyc +0 -0
  113. package/ui/api/__pycache__/__init__.cpython-314.pyc +0 -0
  114. package/ui/api/__pycache__/submit.cpython-314.pyc +0 -0
  115. package/ui/api/__pycache__/tracker.cpython-314.pyc +0 -0
  116. package/ui/web/.gitattributes +0 -13
  117. package/ui/web/dist/assets/index-2LZDQirC.js +0 -9
  118. package/ui/web/dist/assets/index-D4M1qyOv.css +0 -1
  119. package/ui/web/index.html +0 -13
  120. package/ui/web/package-lock.json +0 -2225
  121. package/ui/web/package.json +0 -26
  122. package/ui/web/src/App.test.tsx +0 -74
  123. package/ui/web/src/App.tsx +0 -142
  124. package/ui/web/src/CONTRIBUTING-UI.md +0 -49
  125. package/ui/web/src/components/AgentRow.css +0 -187
  126. package/ui/web/src/components/AgentRow.test.tsx +0 -209
  127. package/ui/web/src/components/AgentRow.tsx +0 -207
  128. package/ui/web/src/components/AgentsPanel.css +0 -108
  129. package/ui/web/src/components/AgentsPanel.test.tsx +0 -41
  130. package/ui/web/src/components/AgentsPanel.tsx +0 -58
  131. package/ui/web/src/components/AlertsPanel.css +0 -88
  132. package/ui/web/src/components/AlertsPanel.test.tsx +0 -51
  133. package/ui/web/src/components/AlertsPanel.tsx +0 -67
  134. package/ui/web/src/components/BacklogPanel.test.tsx +0 -126
  135. package/ui/web/src/components/BacklogPanel.tsx +0 -122
  136. package/ui/web/src/components/CostChart.css +0 -110
  137. package/ui/web/src/components/CostChart.test.tsx +0 -144
  138. package/ui/web/src/components/CostChart.tsx +0 -152
  139. package/ui/web/src/components/CostTable.css +0 -93
  140. package/ui/web/src/components/CostTable.test.tsx +0 -165
  141. package/ui/web/src/components/CostTable.tsx +0 -94
  142. package/ui/web/src/components/EventsFeed.css +0 -68
  143. package/ui/web/src/components/EventsFeed.test.tsx +0 -36
  144. package/ui/web/src/components/EventsFeed.tsx +0 -31
  145. package/ui/web/src/components/HealthHeader.css +0 -137
  146. package/ui/web/src/components/HealthHeader.test.tsx +0 -278
  147. package/ui/web/src/components/HealthHeader.tsx +0 -281
  148. package/ui/web/src/components/InboxForm.css +0 -135
  149. package/ui/web/src/components/InboxForm.test.tsx +0 -208
  150. package/ui/web/src/components/InboxForm.tsx +0 -116
  151. package/ui/web/src/components/MessagesTail.module.css +0 -144
  152. package/ui/web/src/components/MessagesTail.test.tsx +0 -176
  153. package/ui/web/src/components/MessagesTail.tsx +0 -94
  154. package/ui/web/src/components/ReposPanel.css +0 -90
  155. package/ui/web/src/components/ReposPanel.test.tsx +0 -45
  156. package/ui/web/src/components/ReposPanel.tsx +0 -67
  157. package/ui/web/src/components/Scorecard.css +0 -106
  158. package/ui/web/src/components/Scorecard.test.tsx +0 -117
  159. package/ui/web/src/components/Scorecard.tsx +0 -85
  160. package/ui/web/src/components/Timeline.module.css +0 -151
  161. package/ui/web/src/components/Timeline.test.tsx +0 -215
  162. package/ui/web/src/components/Timeline.tsx +0 -99
  163. package/ui/web/src/components/TrackerBoard.test.tsx +0 -121
  164. package/ui/web/src/components/TrackerBoard.tsx +0 -107
  165. package/ui/web/src/components/TrackerCard.test.tsx +0 -180
  166. package/ui/web/src/components/TrackerCard.tsx +0 -160
  167. package/ui/web/src/components/TrackerForm.test.tsx +0 -189
  168. package/ui/web/src/components/TrackerForm.tsx +0 -144
  169. package/ui/web/src/lib/api.ts +0 -218
  170. package/ui/web/src/lib/format.test.ts +0 -89
  171. package/ui/web/src/lib/format.ts +0 -103
  172. package/ui/web/src/lib/sanitizeUrl.test.ts +0 -84
  173. package/ui/web/src/lib/sanitizeUrl.ts +0 -38
  174. package/ui/web/src/lib/types.ts +0 -230
  175. package/ui/web/src/lib/useHashRoute.test.ts +0 -60
  176. package/ui/web/src/lib/useHashRoute.ts +0 -23
  177. package/ui/web/src/lib/useSSE.ts +0 -175
  178. package/ui/web/src/main.tsx +0 -10
  179. package/ui/web/src/styles/global.css +0 -179
  180. package/ui/web/src/styles/theme.css +0 -184
  181. package/ui/web/src/styles/work.css +0 -572
  182. package/ui/web/src/test/fixtures.ts +0 -385
  183. package/ui/web/src/test/setup.ts +0 -49
  184. package/ui/web/src/views/Activity.module.css +0 -43
  185. package/ui/web/src/views/Activity.test.tsx +0 -89
  186. package/ui/web/src/views/Activity.tsx +0 -31
  187. package/ui/web/src/views/Cost.css +0 -87
  188. package/ui/web/src/views/Cost.test.tsx +0 -142
  189. package/ui/web/src/views/Cost.tsx +0 -54
  190. package/ui/web/src/views/Overview.css +0 -51
  191. package/ui/web/src/views/Overview.test.tsx +0 -76
  192. package/ui/web/src/views/Overview.tsx +0 -46
  193. package/ui/web/src/views/Work.test.tsx +0 -82
  194. package/ui/web/src/views/Work.tsx +0 -79
  195. package/ui/web/src/vite-env.d.ts +0 -10
  196. package/ui/web/tsconfig.json +0 -22
  197. package/ui/web/vite.config.ts +0 -25
  198. package/ui/web/vitest.config.ts +0 -12
@@ -1,142 +0,0 @@
1
- /**
2
- * Cost view tests — composition of CostTable, CostChart, and Scorecard.
3
- * Tests empty state (has_pricing=false) with configure callout.
4
- * Tests full state rendering.
5
- */
6
-
7
- import { describe, it, expect } from 'vitest';
8
- import { render, screen } from '@testing-library/react';
9
- import { Cost } from './Cost';
10
- import { fixtureCost, fixtureCostWithPricing, TESTIDS } from '../test/fixtures';
11
-
12
- describe('Cost view', () => {
13
- it('renders with testid', () => {
14
- render(<Cost cost={fixtureCost} />);
15
- expect(screen.getByTestId(TESTIDS.viewCost)).toBeInTheDocument();
16
- });
17
-
18
- it('renders cost table, chart, and scorecard', () => {
19
- render(<Cost cost={fixtureCostWithPricing} />);
20
- expect(screen.getByTestId(TESTIDS.costTable)).toBeInTheDocument();
21
- expect(screen.getByTestId(TESTIDS.costChart)).toBeInTheDocument();
22
- expect(screen.getByTestId(TESTIDS.scorecard)).toBeInTheDocument();
23
- });
24
-
25
- describe('tokens-only mode (has_pricing=false)', () => {
26
- it('shows "configure pricing" empty-state callout', () => {
27
- render(<Cost cost={fixtureCost} />);
28
- const view = screen.getByTestId(TESTIDS.viewCost);
29
- // Should mention configure or pricing
30
- expect(view.textContent).toMatch(/configure|pricing|aesop\.config/i);
31
- });
32
-
33
- it('callout references aesop.config.json', () => {
34
- render(<Cost cost={fixtureCost} />);
35
- const view = screen.getByTestId(TESTIDS.viewCost);
36
- expect(view.textContent).toContain('aesop.config.json');
37
- });
38
-
39
- it('provides instructions for pricing configuration', () => {
40
- render(<Cost cost={fixtureCost} />);
41
- const view = screen.getByTestId(TESTIDS.viewCost);
42
- // Should have some instructional text
43
- expect(view.textContent?.length).toBeGreaterThan(50);
44
- });
45
-
46
- it('still renders table and chart even without pricing', () => {
47
- render(<Cost cost={fixtureCost} />);
48
- // Should show token data even without pricing
49
- expect(screen.getByTestId(TESTIDS.costTable)).toBeInTheDocument();
50
- expect(screen.getByTestId(TESTIDS.costChart)).toBeInTheDocument();
51
- });
52
- });
53
-
54
- describe('pricing mode (has_pricing=true)', () => {
55
- it('does not show "configure pricing" callout', () => {
56
- render(<Cost cost={fixtureCostWithPricing} />);
57
- const view = screen.getByTestId(TESTIDS.viewCost);
58
- // Should NOT emphasize configuration when pricing exists
59
- expect(view.textContent).not.toMatch(/configure pricing/i);
60
- });
61
-
62
- it('renders cost columns with dollar amounts', () => {
63
- render(<Cost cost={fixtureCostWithPricing} />);
64
- const table = screen.getByTestId(TESTIDS.costTable);
65
- // Should display pricing info
66
- expect(table.innerHTML).toContain('$');
67
- });
68
- });
69
-
70
- it('renders as a proper section element', () => {
71
- render(<Cost cost={fixtureCost} />);
72
- const view = screen.getByTestId(TESTIDS.viewCost) as HTMLElement;
73
- expect(['SECTION', 'DIV'].includes(view.tagName)).toBe(true);
74
- });
75
-
76
- it('has aria-label or heading describing the view', () => {
77
- render(<Cost cost={fixtureCost} />);
78
- const view = screen.getByTestId(TESTIDS.viewCost);
79
- // Should have descriptive content or aria-label
80
- expect(view.getAttribute('aria-label') || view.textContent).toBeTruthy();
81
- });
82
-
83
- it('layout is readable with all three components visible', () => {
84
- render(<Cost cost={fixtureCostWithPricing} />);
85
- // Should have all three sections rendered
86
- expect(screen.getByTestId(TESTIDS.costTable)).toBeInTheDocument();
87
- expect(screen.getByTestId(TESTIDS.costChart)).toBeInTheDocument();
88
- expect(screen.getByTestId(TESTIDS.scorecard)).toBeInTheDocument();
89
- });
90
-
91
- it('scorecard displays verdict statistics', () => {
92
- render(<Cost cost={fixtureCost} />);
93
- // Should show total runs count
94
- expect(screen.getByTestId(TESTIDS.scorecard).textContent).toContain('142');
95
- });
96
-
97
- it('chart shows per-day trend', () => {
98
- render(<Cost cost={fixtureCost} />);
99
- const chart = screen.getByTestId(TESTIDS.costChart);
100
- // Should render 6 bars for the 3 days (2 per day: in/out)
101
- const bars = chart.querySelectorAll('rect[data-day]');
102
- expect(bars.length).toBe(6);
103
- });
104
-
105
- it('table shows per-model breakdown', () => {
106
- render(<Cost cost={fixtureCost} />);
107
- const table = screen.getByTestId(TESTIDS.costTable);
108
- // Should list haiku and sonnet models
109
- expect(table.textContent).toContain('haiku');
110
- expect(table.textContent).toContain('sonnet');
111
- });
112
-
113
- it('handles cost object with skipped_lines footnote', () => {
114
- render(<Cost cost={fixtureCost} />);
115
- // scorecard mentions skipped lines when > 0
116
- // At least the component renders without error
117
- expect(screen.getByTestId(TESTIDS.viewCost)).toBeInTheDocument();
118
- });
119
-
120
- it('empty cost (0 runs) still renders all panels', () => {
121
- const empty = {
122
- ...fixtureCost,
123
- models: {},
124
- daily_totals: {},
125
- overall_scorecard: {
126
- total_runs: 0,
127
- ok_count: 0,
128
- failed_count: 0,
129
- empty_count: 0,
130
- hung_count: 0,
131
- ok_rate: 0,
132
- failed_rate: 0,
133
- empty_rate: 0,
134
- hung_rate: 0,
135
- },
136
- };
137
- render(<Cost cost={empty} />);
138
- expect(screen.getByTestId(TESTIDS.viewCost)).toBeInTheDocument();
139
- expect(screen.getByTestId(TESTIDS.costTable)).toBeInTheDocument();
140
- expect(screen.getByTestId(TESTIDS.costChart)).toBeInTheDocument();
141
- });
142
- });
@@ -1,54 +0,0 @@
1
- /**
2
- * Cost view — composition of cost table, chart, and scorecard.
3
- * Shows per-model breakdowns, per-day trends, and verdict quality metrics.
4
- * When has_pricing=false, shows a "configure pricing" empty-state callout.
5
- * When has_pricing=true, displays dollar estimates alongside tokens.
6
- */
7
-
8
- import type { CostSummary } from '../lib/types';
9
- import { CostTable } from '../components/CostTable';
10
- import { CostChart } from '../components/CostChart';
11
- import { Scorecard } from '../components/Scorecard';
12
- import { TESTIDS } from '../test/fixtures';
13
- import './Cost.css';
14
-
15
- interface CostProps {
16
- cost: CostSummary;
17
- }
18
-
19
- export function Cost({ cost }: CostProps) {
20
- return (
21
- <section className="view-cost" data-testid={TESTIDS.viewCost} aria-label="Cost analytics">
22
- <h2>Cost Analytics</h2>
23
-
24
- {!cost.has_pricing && (
25
- <div className="cost-callout cost-callout--info" role="status">
26
- <h3>Configure Pricing</h3>
27
- <p>
28
- To see cost estimates, add a <code>pricing</code> map to your{' '}
29
- <code>aesop.config.json</code> with per-model input and output rates (e.g.{' '}
30
- <code>{'{input: 0.003, output: 0.015}'}</code>). Without pricing, token counts are
31
- shown; no estimates are computed.
32
- </p>
33
- </div>
34
- )}
35
-
36
- <div className="cost-layout">
37
- <div className="cost-section">
38
- <h3>By Model</h3>
39
- <CostTable cost={cost} />
40
- </div>
41
-
42
- <div className="cost-section">
43
- <h3>Daily Trend</h3>
44
- <CostChart cost={cost} />
45
- </div>
46
-
47
- <div className="cost-section">
48
- <h3>Quality Scorecard</h3>
49
- <Scorecard cost={cost} />
50
- </div>
51
- </div>
52
- </section>
53
- );
54
- }
@@ -1,51 +0,0 @@
1
- .overview {
2
- display: flex;
3
- flex-direction: column;
4
- gap: var(--space-6);
5
- padding: var(--space-6);
6
- max-width: var(--content-max-width);
7
- margin: 0 auto;
8
- width: 100%;
9
- }
10
-
11
- .overview__section {
12
- display: flex;
13
- gap: var(--space-5);
14
- }
15
-
16
- .overview__section--full {
17
- width: 100%;
18
- }
19
-
20
- .overview__section--row {
21
- display: grid;
22
- grid-template-columns: repeat(3, 1fr);
23
- gap: var(--space-5);
24
- }
25
-
26
- @media (max-width: 1200px) {
27
- .overview__section--row {
28
- grid-template-columns: repeat(2, 1fr);
29
- }
30
- }
31
-
32
- @media (max-width: 768px) {
33
- .overview {
34
- gap: var(--space-4);
35
- padding: var(--space-4);
36
- }
37
-
38
- .overview__section {
39
- gap: var(--space-3);
40
- }
41
-
42
- .overview__section--row {
43
- grid-template-columns: 1fr;
44
- }
45
- }
46
-
47
- @media (prefers-reduced-motion: reduce) {
48
- .overview {
49
- scroll-behavior: auto;
50
- }
51
- }
@@ -1,76 +0,0 @@
1
- import { describe, it, expect, vi } from 'vitest';
2
- import { render, screen } from '@testing-library/react';
3
- import { Overview } from './Overview';
4
- import {
5
- fixtureAgents,
6
- fixtureAlerts,
7
- fixtureEvents,
8
- fixtureRepos,
9
- TESTIDS,
10
- } from '../test/fixtures';
11
-
12
- vi.mock('../components/AgentsPanel', () => ({
13
- AgentsPanel: () => <div data-testid="agents-panel-mock">AgentsPanel</div>,
14
- }));
15
-
16
- vi.mock('../components/AlertsPanel', () => ({
17
- AlertsPanel: () => <div data-testid="alerts-panel-mock">AlertsPanel</div>,
18
- }));
19
-
20
- vi.mock('../components/EventsFeed', () => ({
21
- EventsFeed: () => <div data-testid="events-feed-mock">EventsFeed</div>,
22
- }));
23
-
24
- vi.mock('../components/ReposPanel', () => ({
25
- ReposPanel: () => <div data-testid="repos-panel-mock">ReposPanel</div>,
26
- }));
27
-
28
- vi.mock('../components/InboxForm', () => ({
29
- InboxForm: () => <div data-testid="inbox-form-mock">InboxForm</div>,
30
- }));
31
-
32
- describe('Overview', () => {
33
- it('renders overview view with all components', () => {
34
- render(
35
- <Overview
36
- agents={fixtureAgents}
37
- alerts={fixtureAlerts}
38
- events={fixtureEvents}
39
- repos={fixtureRepos}
40
- />
41
- );
42
-
43
- expect(screen.getByTestId(TESTIDS.viewOverview)).toBeInTheDocument();
44
- expect(screen.getByTestId('agents-panel-mock')).toBeInTheDocument();
45
- expect(screen.getByTestId('alerts-panel-mock')).toBeInTheDocument();
46
- expect(screen.getByTestId('events-feed-mock')).toBeInTheDocument();
47
- expect(screen.getByTestId('repos-panel-mock')).toBeInTheDocument();
48
- expect(screen.getByTestId('inbox-form-mock')).toBeInTheDocument();
49
- });
50
-
51
- it('handles null values gracefully', () => {
52
- render(
53
- <Overview
54
- agents={null}
55
- alerts={null}
56
- events={null}
57
- repos={null}
58
- />
59
- );
60
-
61
- expect(screen.getByTestId(TESTIDS.viewOverview)).toBeInTheDocument();
62
- });
63
-
64
- it('renders with empty arrays', () => {
65
- render(
66
- <Overview
67
- agents={[]}
68
- alerts={{ count: 0, lines: [] }}
69
- events={[]}
70
- repos={[]}
71
- />
72
- );
73
-
74
- expect(screen.getByTestId(TESTIDS.viewOverview)).toBeInTheDocument();
75
- });
76
- });
@@ -1,46 +0,0 @@
1
- /**
2
- * Overview view — main dashboard combining all overview components.
3
- *
4
- * Layout:
5
- * - Top: AgentsPanel (full width)
6
- * - Middle row: AlertsPanel | EventsFeed | ReposPanel
7
- * - Bottom: InboxForm
8
- *
9
- * Props passed from App.tsx via SSE state.
10
- */
11
-
12
- import type { Agent, Alert } from '../lib/types';
13
- import { AgentsPanel } from '../components/AgentsPanel';
14
- import { AlertsPanel } from '../components/AlertsPanel';
15
- import { EventsFeed } from '../components/EventsFeed';
16
- import { ReposPanel } from '../components/ReposPanel';
17
- import { InboxForm } from '../components/InboxForm';
18
- import { TESTIDS } from '../test/fixtures';
19
- import './Overview.css';
20
-
21
- interface OverviewProps {
22
- agents: Agent[] | null;
23
- alerts: Alert | null;
24
- events: string[] | null;
25
- repos: any[] | null;
26
- }
27
-
28
- export function Overview({ agents, alerts, events, repos }: OverviewProps) {
29
- return (
30
- <div className="overview" data-testid={TESTIDS.viewOverview}>
31
- <section className="overview__section overview__section--full">
32
- <AgentsPanel agents={agents} />
33
- </section>
34
-
35
- <section className="overview__section overview__section--row">
36
- <AlertsPanel alerts={alerts} />
37
- <EventsFeed events={events} />
38
- <ReposPanel repos={repos} />
39
- </section>
40
-
41
- <section className="overview__section overview__section--full">
42
- <InboxForm />
43
- </section>
44
- </div>
45
- );
46
- }
@@ -1,82 +0,0 @@
1
- import { describe, it, expect, vi } from 'vitest';
2
- import { render, screen } from '@testing-library/react';
3
- import { Work } from './Work';
4
- import { fixtureTrackerItems, fixtureBacklog, TESTIDS } from '../test/fixtures';
5
-
6
- // Mock useSSE hook
7
- vi.mock('../lib/useSSE', () => ({
8
- useSSE: () => ({
9
- tracker: { items: fixtureTrackerItems },
10
- backlog: fixtureBacklog,
11
- data: null,
12
- agents: null,
13
- status: null,
14
- cost: null,
15
- connectionStatus: { status: 'live' as const },
16
- }),
17
- }));
18
-
19
- describe('Work view', () => {
20
- it('renders the work view with correct testid', () => {
21
- render(<Work />);
22
-
23
- expect(screen.getByTestId(TESTIDS.viewWork)).toBeInTheDocument();
24
- });
25
-
26
- it('renders tracker board heading', () => {
27
- render(<Work />);
28
-
29
- expect(screen.getByText('Tracker Kanban')).toBeInTheDocument();
30
- });
31
-
32
- it('renders the add item button', () => {
33
- render(<Work />);
34
-
35
- const addButton = screen.getByRole('button', { name: /Add Item/ });
36
- expect(addButton).toBeInTheDocument();
37
- });
38
-
39
- it('shows form when add item button is clicked', () => {
40
- render(<Work />);
41
-
42
- const addButton = screen.getByRole('button', { name: /Add Item/ });
43
- expect(addButton).toHaveAttribute('aria-expanded', 'false');
44
-
45
- // Form inputs should not be visible initially
46
- expect(screen.queryByLabelText('Title')).not.toBeInTheDocument();
47
- });
48
-
49
- it('displays tracker board with items', () => {
50
- render(<Work />);
51
-
52
- expect(screen.getByTestId(TESTIDS.trackerBoard)).toBeInTheDocument();
53
- });
54
-
55
- it('displays backlog panel', () => {
56
- render(<Work />);
57
-
58
- expect(screen.getByTestId(TESTIDS.backlogPanel)).toBeInTheDocument();
59
- });
60
-
61
- it('renders all tracker lanes', () => {
62
- render(<Work />);
63
-
64
- // Be specific to avoid duplicate "Done" matching
65
- // Lane headers include counts, e.g., "Proposed1", so check by prefix
66
- const sections = screen.getAllByRole('heading', { level: 2 });
67
- const laneTexts = sections.map((s) => s.textContent || '');
68
-
69
- expect(laneTexts.some((t) => t.startsWith('Proposed'))).toBe(true);
70
- expect(laneTexts.some((t) => t.startsWith('Ranked'))).toBe(true);
71
- expect(laneTexts.some((t) => t.startsWith('In Progress'))).toBe(true);
72
- });
73
-
74
- it('renders backlog audit tier headers', () => {
75
- render(<Work />);
76
-
77
- expect(screen.getByText('Audit Backlog')).toBeInTheDocument();
78
- // Use context to avoid matching P0 in tracker cards
79
- const backlogPanel = screen.getByTestId(TESTIDS.backlogPanel);
80
- expect(backlogPanel.textContent).toContain('P0');
81
- });
82
- });
@@ -1,79 +0,0 @@
1
- /**
2
- * Work view — tracker kanban board + backlog panel + form.
3
- * Binds TrackerBoard, TrackerCard, TrackerForm, BacklogPanel.
4
- * Reads tracker + backlog from SSE, allows mutations.
5
- */
6
-
7
- import { useEffect, useState } from 'react';
8
- import { useSSE } from '../lib/useSSE';
9
- import { TrackerBoard } from '../components/TrackerBoard';
10
- import { TrackerForm } from '../components/TrackerForm';
11
- import { BacklogPanel } from '../components/BacklogPanel';
12
- import { TESTIDS } from '../test/fixtures';
13
- import type { TrackerItem, AuditBacklog } from '../lib/types';
14
- import '../styles/work.css';
15
-
16
- export function Work() {
17
- const sse = useSSE();
18
- const [trackerItems, setTrackerItems] = useState<TrackerItem[]>([]);
19
- const [backlog, setBacklog] = useState<AuditBacklog | null>(null);
20
- const [showForm, setShowForm] = useState(false);
21
-
22
- useEffect(() => {
23
- if (sse.tracker?.items !== undefined && sse.tracker?.items !== null) {
24
- setTrackerItems(sse.tracker.items);
25
- } else if (sse.tracker === null) {
26
- // Treat null items as empty array
27
- setTrackerItems([]);
28
- }
29
- }, [sse.tracker]);
30
-
31
- useEffect(() => {
32
- if (sse.backlog) {
33
- setBacklog(sse.backlog);
34
- }
35
- }, [sse.backlog]);
36
-
37
- const handleItemUpdate = (updated: TrackerItem) => {
38
- setTrackerItems((prev) =>
39
- prev.map((item) => (item.id === updated.id ? updated : item))
40
- );
41
- };
42
-
43
- const handleFormSuccess = () => {
44
- setShowForm(false);
45
- // Tracker board will update via SSE
46
- };
47
-
48
- return (
49
- <section className="work-view" data-testid={TESTIDS.viewWork} aria-label="Work view">
50
- <div className="work-container">
51
- <div className="work-board">
52
- <div className="board-header">
53
- <h2>Tracker Kanban</h2>
54
- <button
55
- type="button"
56
- className="add-item-button"
57
- onClick={() => setShowForm(!showForm)}
58
- aria-expanded={showForm}
59
- >
60
- {showForm ? 'Cancel' : '+ Add Item'}
61
- </button>
62
- </div>
63
-
64
- {showForm && (
65
- <div className="form-container">
66
- <TrackerForm onSuccess={handleFormSuccess} />
67
- </div>
68
- )}
69
-
70
- <TrackerBoard items={trackerItems} onUpdate={handleItemUpdate} />
71
- </div>
72
-
73
- <aside className="work-sidebar">
74
- <BacklogPanel backlog={backlog} />
75
- </aside>
76
- </div>
77
- </section>
78
- );
79
- }
@@ -1,10 +0,0 @@
1
- /// <reference types="vite/client" />
2
-
3
- interface Window {
4
- /**
5
- * CSRF token injected by ui/render.py's sentinel substitution in the built
6
- * index.html; absent on the raw Vite dev server (api.ts then falls back to
7
- * GET /api/session).
8
- */
9
- __AESOP_CSRF_TOKEN__?: string;
10
- }
@@ -1,22 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2020",
4
- "useDefineForClassFields": true,
5
- "lib": ["ES2020", "DOM", "DOM.Iterable"],
6
- "module": "ESNext",
7
- "skipLibCheck": true,
8
- "esModuleInterop": true,
9
- "allowSyntheticDefaultImports": true,
10
- "strict": true,
11
- "noUnusedLocals": true,
12
- "noUnusedParameters": true,
13
- "noFallthroughCasesInSwitch": true,
14
- "jsx": "react-jsx",
15
- "jsxImportSource": "react",
16
- "moduleResolution": "bundler",
17
- "resolveJsonModule": true,
18
- "isolatedModules": true
19
- },
20
- "include": ["src"],
21
- "exclude": ["node_modules"]
22
- }
@@ -1,25 +0,0 @@
1
- import { defineConfig } from 'vite';
2
- import react from '@vitejs/plugin-react';
3
-
4
- export default defineConfig({
5
- plugins: [react()],
6
- server: {
7
- port: 5173,
8
- proxy: {
9
- '/data': 'http://localhost:8770',
10
- '/api': 'http://localhost:8770',
11
- '/agent': 'http://localhost:8770',
12
- '/events': 'http://localhost:8770',
13
- '/submit': 'http://localhost:8770',
14
- },
15
- },
16
- build: {
17
- outDir: 'dist',
18
- sourcemap: false,
19
- rollupOptions: {
20
- output: {
21
- manualChunks: undefined,
22
- },
23
- },
24
- },
25
- });
@@ -1,12 +0,0 @@
1
- import { defineConfig } from 'vitest/config';
2
- import react from '@vitejs/plugin-react';
3
-
4
- export default defineConfig({
5
- plugins: [react()],
6
- test: {
7
- environment: 'jsdom',
8
- setupFiles: ['src/test/setup.ts'],
9
- globals: true,
10
- include: ['src/**/*.test.{ts,tsx}'],
11
- },
12
- });