feat(web): two-pane "cockpit" redesign with a live board rail
Restructure the workshop flow into the designer's cockpit: a persistent shell (header + 5-step stepper + sticky instrument rail) wrapping the phase routes via a React-Router layout route, so the rail stays mounted across navigation. - Design system: IBM Plex Mono + Newsreader; the full cockpit token set (light + dark) in index.css; a working light/dark theme toggle (store `theme` + useApplyTheme); a `switch` ui primitive. - Shell: CockpitLayout, Stepper (forward-gated), PanelChrome helpers. Every phase page restyled to the editorial panels + the WORKSHOP-FLOW fixes (channels-after-bind, domain framing + L1 prefill, L2/L3 prefill at 3/3, in-place submission finale). Store gains `tried` + `channels.saidHi` (v4). - Live rail (CockpitRail): real node heartbeat + agent activity log + ADD progress; sim telemetry (useTelemetry) for the waveform/accel/I2C behind a seam, marked SIM. - LED-matrix PIXEL MIRROR (real): the rail shows exactly what the physical matrix displays — API GET /nodes/:team/matrix reads the board's framebuffer off the :9999 relay (readMatrixFrame + the `matrixget` relay command); useMatrixMirror polls it and unpacks the 104 bits. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
6c82b79b01
commit
1a39457940
+66
-84
@@ -1,109 +1,91 @@
|
||||
import { useState } from 'react'
|
||||
import { Link, useNavigate } from 'react-router-dom'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { PhaseStrip } from '@/components/PhaseStrip'
|
||||
import { useEffect } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { AgentChat } from '@/components/AgentChat'
|
||||
import { AddLayerForm } from '@/components/AddLayerForm'
|
||||
import { PanelHeading, PanelCard, ProceedButton } from '@/components/cockpit/PanelChrome'
|
||||
import { useSession } from '@/store/session'
|
||||
|
||||
const L2_PREFILL =
|
||||
'i2c_scan to enumerate both ADXL355Z; matrix_text and matrix_count for on-board readouts; adxl355_stream for live vibration.'
|
||||
const L3_PREFILL =
|
||||
'Report only on exception; escalate ambiguous or high-consequence calls to the cloud model; never suppress a stale-sensor alarm.'
|
||||
|
||||
export function Module2() {
|
||||
const navigate = useNavigate()
|
||||
const l2 = useSession((s) => s.add.L2)
|
||||
const l3 = useSession((s) => s.add.L3)
|
||||
const tried = useSession((s) => s.tried)
|
||||
const setTried = useSession((s) => s.setTried)
|
||||
const setAddLayer = useSession((s) => s.setAddLayer)
|
||||
const completePhase = useSession((s) => s.completePhase)
|
||||
const [tried, setTried] = useState(0)
|
||||
|
||||
const allTried = tried >= 3
|
||||
const ready = allTried && l2.trim().length > 0 && l3.trim().length > 0
|
||||
|
||||
// On reaching 3/3, seed Layers 2 & 3 with a starting draft (only if untouched).
|
||||
useEffect(() => {
|
||||
if (!allTried) return
|
||||
if (!useSession.getState().add.L2.trim()) setAddLayer('L2', L2_PREFILL)
|
||||
if (!useSession.getState().add.L3.trim()) setAddLayer('L3', L3_PREFILL)
|
||||
}, [allTried, setAddLayer])
|
||||
|
||||
const onProceed = () => {
|
||||
completePhase('m2')
|
||||
navigate('/workshop/add')
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="min-h-screen bg-background">
|
||||
<header className="px-8 py-5 border-b border-border flex items-center justify-between">
|
||||
<div className="font-mono text-xs tracking-widest uppercase">
|
||||
APESS <span className="text-primary font-bold">2026</span>
|
||||
<span className="text-muted-foreground"> · Workshop</span>
|
||||
</div>
|
||||
<Link to="/workshop/module1" className="font-mono text-[11px] text-muted-foreground hover:text-foreground tracking-widest uppercase">
|
||||
← Module 1
|
||||
</Link>
|
||||
</header>
|
||||
|
||||
<PhaseStrip active="m2" />
|
||||
|
||||
<section className="px-8 py-10 max-w-3xl mx-auto space-y-6">
|
||||
<div>
|
||||
<Badge variant="secondary" className="font-mono text-[10px] tracking-widest uppercase mb-2">
|
||||
Phase 4 of 5 · ~90 min
|
||||
</Badge>
|
||||
<h1 className="text-3xl font-bold tracking-tight">Module 2 · Skills & policies</h1>
|
||||
<p className="text-sm text-muted-foreground mt-2 max-w-xl">
|
||||
Your agent already ships with expert skills — hardware, the MCU bridge, the LED matrix,
|
||||
flashing, and more. Try them from the chat below: each prompt makes the agent use its
|
||||
skills and tools on your real board.
|
||||
</p>
|
||||
</div>
|
||||
<section>
|
||||
<PanelHeading
|
||||
eyebrow="PHASE 4 OF 5 · ~90 MIN"
|
||||
title="Module 2 · Skills & policies"
|
||||
intro="Talk to your agent and watch it run real tools on your board. Each prompt makes it use a built-in skill — then capture your domain's skills and the policy that governs them."
|
||||
size={46}
|
||||
/>
|
||||
|
||||
<div className="mt-9">
|
||||
<AgentChat onProgress={(done) => setTried(done)} />
|
||||
</div>
|
||||
|
||||
{/* What's next — revealed once all three prompts have run successfully. */}
|
||||
{allTried ? (
|
||||
<div className="space-y-6" data-testid="whats-next">
|
||||
<div className="pt-2">
|
||||
<h2 className="text-lg font-semibold tracking-tight">What’s next</h2>
|
||||
<p className="text-sm text-muted-foreground mt-1 max-w-xl">
|
||||
You just watched the agent enumerate a bus and drive the matrix using its built-in
|
||||
skills. Now capture <span className="font-medium text-foreground">your domain’s</span>{' '}
|
||||
skills and the policy that governs them — Layers 2 and 3 of your Agent Design Document.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<AddLayerForm
|
||||
layer="L2"
|
||||
title="ADD · Layer 2 — Skills"
|
||||
description="What skills can the agent invoke to act on its domain?"
|
||||
placeholder="Flash a sketch to the MCU; scroll a message; drive the damper; sample the IMU."
|
||||
/>
|
||||
<AddLayerForm
|
||||
layer="L3"
|
||||
title="ADD · Layer 3 — Policies & failure"
|
||||
description="The actuation gate — and what happens when things break. For each failure you can name, which way does it fail? A fail-safe must never quietly report 'normal'."
|
||||
placeholder={
|
||||
'Autonomous: log + alert. Needs approval: drive the actuator. E-stop: operator halts actuation at any time.\n\n' +
|
||||
'Failure states → response:\n' +
|
||||
'• sensor disconnected / stuck value / drifting → mark UNKNOWN, never "nominal"\n' +
|
||||
'• reading older than 60 s → treat as no reading\n' +
|
||||
'• cloud unreachable → decide on-board, flag reduced confidence\n' +
|
||||
'• agent unsure → escalate to a human, do not actuate'
|
||||
}
|
||||
/>
|
||||
|
||||
<div className="flex justify-end pt-2">
|
||||
<Button size="lg" disabled={!ready} onClick={onProceed}>
|
||||
Proceed to ADD builder →
|
||||
</Button>
|
||||
</div>
|
||||
{allTried ? (
|
||||
<div className="mt-8 space-y-5" data-testid="whats-next">
|
||||
<div>
|
||||
<h2 className="text-[22px] font-semibold tracking-[-0.01em]">What’s next</h2>
|
||||
<p className="mt-1 max-w-[560px] text-[15px] text-ink-2">
|
||||
You just watched the agent enumerate the bus and drive the matrix with its built-in
|
||||
skills. Now capture <span className="font-medium text-ink">your domain’s</span> skills and
|
||||
the policy that governs them — Layers 2 and 3, drafted below.
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-base text-muted-foreground">What’s next</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-sm text-muted-foreground leading-relaxed">
|
||||
Try all three prompts above. Once your agent has run each one successfully, we’ll
|
||||
capture your domain’s skills and policies here.
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<AddLayerForm
|
||||
layer="L2"
|
||||
title="ADD · Layer 2 — Skills"
|
||||
description="What skills can the agent invoke to act on its domain?"
|
||||
placeholder="Flash a sketch to the MCU; scroll a message; drive the damper; sample the IMU."
|
||||
/>
|
||||
<AddLayerForm
|
||||
layer="L3"
|
||||
title="ADD · Layer 3 — Policies & failure"
|
||||
description="The actuation gate — and what happens when things break. A fail-safe must never quietly report 'normal'."
|
||||
placeholder="Report only on exception; escalate ambiguous calls to the cloud; never suppress a stale-sensor alarm."
|
||||
/>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<ProceedButton disabled={!ready} onClick={onProceed}>
|
||||
Proceed to Module 3 →
|
||||
</ProceedButton>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<PanelCard className="mt-6">
|
||||
<div className="text-[15px] font-semibold text-ink-2">What’s next</div>
|
||||
<p className="mt-2 text-[14px] leading-[1.5] text-ink-3">
|
||||
Try all three prompts above. Once your agent has run each one successfully, we’ll
|
||||
capture your domain’s skills and policies here.
|
||||
</p>
|
||||
</PanelCard>
|
||||
)}
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user