feat(web): reshape workshop from 'tune a harness' to 'design a domain node'
The board is now a Claude-powered ZeroClaw agent with expert skills that teams talk to directly (its own dashboard / Telegram / voice). The frontend was built on the older sensor+harness model; this repoints it. - State/DTO: AddLayers remapped to the 5 design layers (Domain/Skills/Policies/ Harness/Loops); added session.domain (+ mirrored through TeamSnapshot, sync, and the SQLite store with a guarded migration); dropped Harness/Provider/RunMode; persist v2 migrate resets stale state. - Removed the harness/sim/failover machinery (HarnessProviderSelect, HarnessTuner, HarnessTomlPreview, ResiliencePanel, TriggerButtons, harness.ts, useSerial, LiveFeed/serial classifier) and every sim-vs-live branch. - Onboarding: EnvSetup rebuilt into 'Meet your node' — Open-your-node hero (new reusable OpenYourNode CTA), say-hi-to-your-agent, a free-text DomainPicker (domain drives L1-L5), Telegram/voice pointers, and the open->locked lockdown policy step. Domain is required to proceed. - Modules repointed: M1 Domain & events (L1), M2 Skills & Policies (L2/L3, skills reference + actuation-gate framing), M3 Harness (reasoning+tiering) & Loops (cadence) (L4/L5). BuildFlash reframed as a guided 'ask your node to build X' that hands off to the node dashboard. - Judge rubric -> Domain fit/Skills/Policies/Harness/Loops; Lecture + Landing re-storied to the Claude-node + talk-to-your-node narrative. Verified: web tsc + 181 tests, api tsc + 58 tests, dead-ref sweep clean. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
3be44ec215
commit
6d94833bb9
+13
-47
@@ -14,6 +14,7 @@ vi.mock('@/lib/api', async (orig) => ({
|
||||
return () => {}
|
||||
},
|
||||
getNodeStatus: vi.fn().mockResolvedValue({ teamId: 't', online: false }),
|
||||
sendPrompt: vi.fn().mockResolvedValue(undefined),
|
||||
}))
|
||||
|
||||
function renderPage() {
|
||||
@@ -32,63 +33,28 @@ describe('Module2', () => {
|
||||
|
||||
it('renders the phase strip set to m2 and the heading', () => {
|
||||
renderPage()
|
||||
expect(screen.getByRole('heading', { name: /harness engineering/i })).toBeInTheDocument()
|
||||
expect(screen.getByRole('heading', { name: /skills.*policies/i })).toBeInTheDocument()
|
||||
expect(
|
||||
screen.getByTestId('phase-strip').querySelector('[data-phase="m2"]'),
|
||||
).toHaveAttribute('data-state', 'active')
|
||||
})
|
||||
|
||||
it('tunes a threshold and reflects it in the TOML preview', async () => {
|
||||
const user = userEvent.setup()
|
||||
it('shows the live board feed and the build & flash panel', () => {
|
||||
renderPage()
|
||||
const input = screen.getByLabelText(/threshold · g/i)
|
||||
await user.clear(input)
|
||||
await user.type(input, '1.2')
|
||||
expect(useSession.getState().harness.thresholdG).toBe(1.2)
|
||||
expect(screen.getByTestId('harness-toml')).toHaveTextContent('threshold_g = 1.2')
|
||||
expect(screen.getByTestId('live-board-feed')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('activity-log')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('fires a trigger that classifies against the tuned threshold', async () => {
|
||||
const user = userEvent.setup()
|
||||
renderPage()
|
||||
// tune low so the impact frame is unambiguously critical
|
||||
const g = screen.getByLabelText(/threshold · g/i)
|
||||
await user.clear(g)
|
||||
await user.type(g, '0.5')
|
||||
await user.click(screen.getByRole('button', { name: /impact/i }))
|
||||
expect(useSession.getState().stats.critical).toBe(1)
|
||||
})
|
||||
|
||||
it('gates Proceed until a trigger fired and L2 + L3 are filled', async () => {
|
||||
const user = userEvent.setup()
|
||||
it('gates Proceed until the board acted and L2 + L3 are filled', async () => {
|
||||
useSession.getState().setAddLayer('L2', 'escalate on critical')
|
||||
useSession.getState().setAddLayer('L3', 'drive damper on critical')
|
||||
renderPage()
|
||||
const proceed = screen.getByRole('button', { name: /proceed/i })
|
||||
expect(proceed).toBeDisabled()
|
||||
|
||||
await user.type(screen.getByLabelText(/layer 2/i), 'escalate on critical')
|
||||
await user.type(screen.getByLabelText(/layer 3/i), 'drive damper on critical')
|
||||
expect(proceed).toBeDisabled() // no trigger fired yet
|
||||
|
||||
await user.click(screen.getByRole('button', { name: /shake/i }))
|
||||
expect(proceed).toBeEnabled()
|
||||
})
|
||||
|
||||
describe('live mode (real node feed)', () => {
|
||||
it('replaces triggers with the live board feed and gates on real activity', async () => {
|
||||
useSession.getState().setMode('live')
|
||||
useSession.getState().setAddLayer('L2', 'escalate on critical')
|
||||
useSession.getState().setAddLayer('L3', 'drive damper on critical')
|
||||
renderPage()
|
||||
|
||||
expect(await screen.findByTestId('live-board-feed')).toBeInTheDocument()
|
||||
expect(screen.queryByTestId('trigger-buttons')).toBeNull() // no synthetic triggers in live mode
|
||||
const proceed = screen.getByRole('button', { name: /proceed/i })
|
||||
expect(proceed).toBeDisabled()
|
||||
|
||||
// the board acts (via Build & flash) → activity streams from the node
|
||||
act(() => emit({ type: 'node:activity', teamId: 'x', kind: 'response', label: 'Agent finished', ts: '' }))
|
||||
await waitFor(() => expect(proceed).toBeEnabled())
|
||||
})
|
||||
// the board acts (via Build & flash) → activity streams from the node
|
||||
act(() => emit({ type: 'node:activity', teamId: 'x', kind: 'response', label: 'Agent finished', ts: '' }))
|
||||
await waitFor(() => expect(proceed).toBeEnabled())
|
||||
})
|
||||
|
||||
it('marks m2 complete on Proceed', async () => {
|
||||
@@ -96,8 +62,8 @@ describe('Module2', () => {
|
||||
renderPage()
|
||||
await user.type(screen.getByLabelText(/layer 2/i), 'L2 text')
|
||||
await user.type(screen.getByLabelText(/layer 3/i), 'L3 text')
|
||||
await user.click(screen.getByRole('button', { name: /impact/i }))
|
||||
await user.click(screen.getByRole('button', { name: /proceed/i }))
|
||||
act(() => emit({ type: 'node:activity', teamId: 'x', kind: 'response', label: 'Agent finished', ts: '' }))
|
||||
await user.click(await screen.findByRole('button', { name: /proceed/i }))
|
||||
expect(useSession.getState().phases.m2).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user