diff --git a/project-management/PJ2026-01/showcase/hwlab-first-principles/app.js b/project-management/PJ2026-01/showcase/hwlab-first-principles/app.js index f595f5cb..cb23b209 100644 --- a/project-management/PJ2026-01/showcase/hwlab-first-principles/app.js +++ b/project-management/PJ2026-01/showcase/hwlab-first-principles/app.js @@ -1,317 +1,475 @@ -const STAGES = [ +const INITIAL_EVENT = { + id: "E00", + phase: "observe", + actor: "AGENT", + title: "任务接管与控制边界绑定", + summary: "ALTA-7 接收目标,并绑定工程、目标板、探针与证据空间。", + input: "任务目标、CONSTAR_workspace、STM32F103、CMSIS-DAP", + decision: "先读取工程事实,不把预设代码差异当作问题结论", + tool: "task.bind", + output: "控制边界建立;下一步读取工程与时钟树", + log: "$ task.bind freq-1khz-accuracy\ncontext workspace=CONSTAR_workspace\ntarget board=STM32F103 probe=CMSIS-DAP\nresult session boundary ready", + observed: "任务、工程和真实硬件已完成上下文绑定", + hypothesis: "待验证:TIM2 时钟推导与板级配置不一致", + action: "等待授权读取工程事实", + next: "检查 Keil 工程与 TIM2 配置", + confidence: 42, + target: "none", +}; + +const RUN_EVENTS = [ { - title: "分析工程", - summary: "识别 TIM2 输入捕获路径与 APB 时钟倍频条件。", - log: "scan TIM2 capture path · APB1 prescaler = 2", + id: "E01", + phase: "observe", + actor: "AGENT", + title: "读取工程事实,而不是直接改代码", + summary: "扫描 Keil 工程、时钟树和 TIM2 输入捕获链路。", + input: ".uvprojx、system_stm32f1xx.c、tim.c、frequency.c", + decision: "优先确认运行时钟来源,避免围绕错误前提修改换算公式", + tool: "workspace.read", + output: "APB1 prescaler=2;TIM2 时钟需应用定时器倍频规则", + log: "$ workspace.read --scope clock,capture\nread 71-FREQ.uvprojx\nread Core/Src/system_stm32f1xx.c\nread Core/Src/tim.c\nfound APB1 prescaler = 2\nfound TIM2 input capture on CH1", + observed: "APB1 分频为 2;TIM2 位于 APB1,输入捕获使用 CH1", + hypothesis: "固定 TIMER_CLOCK_HZ 可能忽略定时器倍频规则", + action: "对照时钟树与频率换算实现", + next: "建立可证伪的误差假设", + confidence: 58, + target: "none", }, { - title: "修改代码", - summary: "修正定时器时钟计算,并注入实板标定偏移。", - log: "patch Core/Src/frequency.c · +5 -3", + id: "E02", + phase: "hypothesize", + actor: "AGENT", + title: "形成可证伪的根因假设", + summary: "把时钟推导错误与捕获偏移拆成两个可独立验证的变量。", + input: "TIM2 实际时钟规则、固定常量、历史 1 kHz 偏差", + decision: "先修正时钟来源,再应用已有板级 capture offset;不改变滤波策略", + tool: "decision.record", + output: "假设 H1:时钟推导是主因;预期修复后误差进入 ±0.10%", + log: "$ decision.record H1\nfact TIM2 clock derives from APB1 x2 rule\nconflict fixed TIMER_CLOCK_HZ bypasses board clock tree\nscope clock derivation + calibrated capture offset\nexclude input filter policy", + observed: "代码使用固定时钟常量,板级配置包含可用标定偏移", + hypothesis: "H1:时钟推导错误是主因,capture offset 是剩余偏差", + action: "记录假设与验收阈值", + next: "制定最小修改与实板验证计划", + confidence: 71, + target: "none", }, { - title: "Keil 编译", - summary: "使用受控工程配置生成可追溯固件产物。", - log: "build 0 error(s), 0 warning(s) · freq.axf", + id: "E03", + phase: "plan", + actor: "AGENT", + title: "提交最小可验证计划", + summary: "代码修改、构建、下载、UART 与独立物理测量形成闭环。", + input: "假设 H1、受控工具清单、真实 HWPOD 资源", + decision: "任何软件输出都不能替代 ioProbe 的独立物理事实", + tool: "plan.commit", + output: "P1 Patch → P2 Build → P3 Flash → P4 UART → P5 ioProbe", + log: "$ plan.commit\nP1 apply minimal clock/capture patch\nP2 build Keil target 71-FREQ\nP3 flash through CMSIS-DAP\nP4 inspect UART runtime facts\nP5 measure FREQ_OUT with ioProbe", + observed: "代码工具和真实硬件工具均已就绪", + hypothesis: "H1 可通过单次受控闭环验证", + action: "锁定计划和证据边界", + next: "生成最小代码变更", + confidence: 74, + target: "none", }, { - title: "下载固件", - summary: "通过 CMSIS-DAP 将本次构建产物写入目标板。", - log: "flash 32.18 KiB · verify OK · reset target", + id: "E04", + phase: "act", + actor: "AGENT", + title: "基于假设生成最小代码变更", + summary: "动态读取定时器时钟,并应用已有板级捕获偏移。", + input: "frequency.c、board_clock、calibration.capture_offset", + decision: "只修改频率换算边界,不触碰输入滤波与其他模块", + tool: "apply_patch", + output: "frequency.c +5 −3;变更绑定假设 H1", + log: "$ apply_patch Core/Src/frequency.c\nremove TIMER_CLOCK_HZ assumption\nadd get_timer_clock_hz()\nadd calibration.capture_offset\nresult +5 -3 · patch_sha=8a91c2d", + observed: "修改范围可限制在 capture_to_hz()", + hypothesis: "最小变更足以消除主偏差,且不会改变滤波行为", + action: "生成并审查代码 Diff", + next: "调用 Keil 构建验证编译事实", + confidence: 79, + target: "none", + patch: true, }, { - title: "UART 采集", - summary: "读取固件运行状态,确认输入捕获链路稳定。", - log: "uart capture stable · sample_count = 128", + id: "E05", + phase: "act", + actor: "TOOL", + title: "AGENT 调用 Keil 构建固件", + summary: "构建工具返回可追溯产物,AGENT 检查错误、警告与摘要。", + input: "71-FREQ.uvprojx、E04 patch_sha=8a91c2d", + decision: "只有构建产物与当前 Patch 一致,才能进入真实板下载", + tool: "keil.build", + output: "0 errors、0 warnings;freq.axf 32.18 KiB", + log: "$ keil.build --target 71-FREQ\ncompile frequency.c\nlink freq.axf\nProgram Size: Code=28964 RO-data=1984 RW-data=312 ZI-data=4168\n0 Error(s), 0 Warning(s)\nartifact sha256:4f7c...9b2a", + observed: "当前 Patch 已生成唯一固件产物", + hypothesis: "软件修复可进入目标板运行验证", + action: "审查构建结果与产物摘要", + next: "通过 CMSIS-DAP 写入目标板", + confidence: 84, + target: "none", + evidence: 3, }, { - title: "物理测量", - summary: "由 ioProbe 独立测量 FREQ_OUT,形成物理事实。", - log: "probe CH1 frequency · 999.8 Hz · error -0.02%", + id: "E06", + phase: "act", + actor: "TOOL", + title: "AGENT 操作 CMSIS-DAP 写入固件", + summary: "目标板擦除、下载、校验和复位均由当前 Session 记录。", + input: "freq.axf sha256:4f7c...9b2a、STM32F103 target", + decision: "Flash verify 必须通过,且写入摘要必须匹配 E05 产物", + tool: "cmsis-dap.flash", + output: "32.18 KiB 写入并校验通过;目标板已复位", + log: "$ cmsis-dap.flash freq.axf\nconnect STM32F103 · SWD 4MHz\nerase sectors 0-31\nwrite 32.18 KiB\nverify OK · artifact matched\nreset target", + observed: "真实目标板已运行 E05 对应固件", + hypothesis: "若 H1 正确,UART 频率输出应接近 1 kHz", + action: "控制调试探针完成下载与校验", + next: "读取 UART 运行事实", + confidence: 87, + target: "probe", + evidence: 4, + }, + { + id: "E07", + phase: "verify", + actor: "HWPOD", + title: "AGENT 读取 UART 运行事实", + summary: "连续采样确认捕获链路稳定,但不把软件自报值当作最终证据。", + input: "UART2 115200、当前目标板运行态", + decision: "UART 可证明固件链路正常;最终精度仍交给 ioProbe 独立判断", + tool: "uart.capture", + output: "128 samples;capture stable;overflow=0", + log: "$ uart.capture --port UART2 --samples 128\n[boot] 71-FREQ rev.C\n[timer] clock=72000000Hz\n[capture] stable samples=128 overflow=0\n[reported] frequency=1000.1Hz", + observed: "固件稳定运行,软件自报频率 1000.1 Hz", + hypothesis: "H1 获得运行态支持,但仍需外部测量排除自证循环", + action: "读取并界定 UART 事实", + next: "调用 ioProbe 测量 FREQ_OUT", + confidence: 91, + target: "uart", + evidence: 5, + }, + { + id: "E08", + phase: "verify", + actor: "HWPOD", + title: "AGENT 调用 ioProbe 独立测量", + summary: "物理测量不依赖固件自报值,直接采集 FREQ_OUT。", + input: "ioProbe CH1、FREQ_OUT、目标 1000.0 Hz", + decision: "以独立物理测量决定是否接受 H1 与当前 Patch", + tool: "ioprobe.measure", + output: "等待物理采样结果", + log: "$ ioprobe.measure --channel CH1 --metric frequency\narm FREQ_OUT\nsample physical waveform\nwindow stable\nresult pending", + observed: "UART 链路通过,物理输出已接入 ioProbe CH1", + hypothesis: "物理频率应落入 1000.0 Hz ±0.10%", + action: "采集真实波形并计算频率", + next: "根据物理事实接受或拒绝封存", + confidence: 93, + target: "ioprobe", + evidence: 6, }, ]; -const EVIDENCE = [ +const SUCCESS_EVENT = { + id: "E09", + phase: "reflect", + actor: "AGENT", + title: "汇总证据并接受当前能力", + summary: "物理结果支持 H1;AGENT 仅在完整证据链通过后允许封存。", + input: "Patch、Build、Flash、UART、ioProbe 999.8 Hz", + decision: "误差 −0.02% 满足阈值,H1 被证据支持,当前 Patch 可复用", + tool: "evidence.aggregate", + output: "Aggregate PASS;允许封存 freq-1khz-accuracy", + log: "$ evidence.aggregate\npatch PASS · E04\nbuild PASS · E05\nflash PASS · E06\nuart PASS · E07\nphysical PASS · 999.8Hz (-0.02%)\nverdict Aggregate PASS", + observed: "ioProbe 实测 999.8 Hz,误差 −0.02%", + hypothesis: "H1 已被软件、运行态和独立物理证据共同支持", + action: "形成 Aggregate PASS 裁决", + next: "封存为可重复回归 Case", + confidence: 99, + target: "none", +}; + +const FAILURE_EVENT = { + id: "E09", + phase: "reflect", + actor: "AGENT", + title: "拒绝封存,并返回诊断循环", + summary: "物理事实推翻当前完成假设;AGENT 阻断结果并生成下一假设。", + input: "Patch、Build、Flash、UART 通过,但 ioProbe 仅测得 972.4 Hz", + decision: "软件链路通过不能覆盖物理失败;拒绝封存并重新打开诊断", + tool: "plan.revise", + output: "Aggregate BLOCKED;新假设 H2:滤波窗口或 capture offset 仍有偏差", + log: "$ evidence.aggregate\nsoftware chain PASS\nphysical FAIL · 972.4Hz (-2.76%)\nverdict Aggregate BLOCKED\n$ plan.revise\nnext H2 inspect filter window + capture offset samples", + observed: "ioProbe 实测 972.4 Hz,与 UART 自报值冲突", + hypothesis: "H2:输入滤波窗口或 capture offset 仍存在板级偏差", + action: "拒绝封存,计划回到工程诊断", + next: "读取滤波窗口与原始 capture 样本", + confidence: 56, + target: "none", +}; + +const EVIDENCE_DETAILS = [ { - idle: "任务尚未进入受控执行。", - passed: "任务 freq-1khz-accuracy 已绑定 Workspace、Board、Probe 与目标指标。", + idle: "E00 尚未进入执行态。", + passed: "E00:任务、Workspace、Board 和 Probe 已绑定当前 Session。", }, { - idle: "尚无 Agent 执行 trace。", - active: "Agent 正在受控工程上下文中分析、修改与执行。", - passed: "Agent trace 已封存:工程分析与代码变更均可回放。", + idle: "尚无 Agent 行动 Trace。", + active: "E01–E09:AGENT 正在持续记录观察、决策、工具与结果。", + passed: "E01–E09:Agent Trace 完整,可回放每次决策与工具调用。", }, { - idle: "尚无本次构建产物。", - passed: "Keil 构建成功:0 error(s),0 warning(s),产物摘要已记录。", + idle: "E04 尚未生成代码变更。", + passed: "E04:Patch +5 −3,与根因假设 H1 建立明确关联。", }, { - idle: "目标板尚未写入本次固件。", - passed: "CMSIS-DAP 下载与校验通过,固件摘要与构建产物一致。", + idle: "E05 尚无构建产物。", + passed: "E05:Keil 0 errors、0 warnings,产物摘要已封存。", }, { - idle: "UART 运行事实尚未采集。", - passed: "UART 连续采样 128 次,输入捕获状态稳定,无溢出。", + idle: "E06 尚未操作真实目标板。", + passed: "E06:CMSIS-DAP 下载、校验和复位通过,产物摘要一致。", }, { - idle: "ioProbe 物理测量尚未开始。", - active: "ioProbe 正在独立采集 FREQ_OUT 波形。", - passed: "ioProbe 实测 999.8 Hz,误差 −0.02%,满足 ±0.10% 阈值。", - failed: "ioProbe 实测 972.4 Hz,误差 −2.76%,超出 ±0.10% 阈值。", + idle: "E07 尚未采集运行事实。", + passed: "E07:UART 连续 128 samples,捕获稳定且 overflow=0。", }, { - idle: "Aggregate 尚未汇总,不能保存为回归 Case。", - passed: "代码、构建、下载、运行与物理测量证据完整:Aggregate PASS。", - blocked: "物理测量未通过:Aggregate BLOCKED,阻止错误结果进入回归集。", + idle: "E08 尚未获得独立物理事实。", + active: "E08:AGENT 正在调用 ioProbe 采集 FREQ_OUT。", + passed: "E08:ioProbe 999.8 Hz,误差 −0.02%,满足阈值。", + failed: "E08:ioProbe 972.4 Hz,误差 −2.76%,推翻完成假设。", + }, + { + idle: "E09 尚未形成聚合裁决。", + passed: "E09:完整证据链 Aggregate PASS,可封存回归 Case。", + blocked: "E09:Aggregate BLOCKED;AGENT 已拒绝封存并返回诊断。", }, ]; -const stageButtons = [...document.querySelectorAll("[data-stage]")]; -const evidenceButtons = [...document.querySelectorAll("[data-evidence]")]; +const PATCH_HTML = ` +
118static float capture_to_hz(uint32_t ticks)
+
119{
+
120- return TIMER_CLOCK_HZ / ticks;
+
121- // APB prescaler assumed to be 1
+
120+ const uint32_t timer_clock = get_timer_clock_hz();
+
121+ const float calibrated_ticks =
+
122+ (float)ticks - calibration.capture_offset;
+
123+ return timer_clock / calibrated_ticks;
+
124}
+
128+ // Evidence link: hypothesis H1 / action E04
+`; + const app = document.querySelector("#app"); const runButton = document.querySelector("#runButton"); const resetButton = document.querySelector("#resetButton"); const failureMode = document.querySelector("#failureMode"); const saveCaseButton = document.querySelector("#saveCaseButton"); -const runState = document.querySelector("#runState"); -const stageDetail = document.querySelector("#stageDetail"); -const runLog = document.querySelector("#runLog"); -const traceCounter = document.querySelector("#traceCounter"); -const evidenceDetail = document.querySelector("#evidenceDetail"); -const actualFrequency = document.querySelector("#actualFrequency"); -const frequencyError = document.querySelector("#frequencyError"); -const scopeStatus = document.querySelector("#scopeStatus"); -const benchState = document.querySelector("#benchState"); -const uartOutput = document.querySelector("#uartOutput"); -const uartIndicator = document.querySelector("#uartIndicator"); -const toast = document.querySelector("#toast"); +const agentState = document.querySelector("#agentState"); +const observedFact = document.querySelector("#observedFact"); +const currentHypothesis = document.querySelector("#currentHypothesis"); +const currentAction = document.querySelector("#currentAction"); +const nextAction = document.querySelector("#nextAction"); +const confidenceMeter = document.querySelector("#confidenceMeter"); +const confidenceLabel = document.querySelector("#confidenceLabel"); +const loopButtons = [...document.querySelectorAll("[data-loop]")]; +const timeline = document.querySelector("#agentTimeline"); +const eventCounter = document.querySelector("#eventCounter"); +const tabButtons = [...document.querySelectorAll("[data-tab]")]; +const inspectorViews = [...document.querySelectorAll("[data-view]")]; +const detailEventId = document.querySelector("#detailEventId"); +const detailTitle = document.querySelector("#detailTitle"); +const detailSummary = document.querySelector("#detailSummary"); +const detailInput = document.querySelector("#detailInput"); +const detailDecision = document.querySelector("#detailDecision"); +const detailTool = document.querySelector("#detailTool"); +const detailOutput = document.querySelector("#detailOutput"); +const patchAction = document.querySelector("#patchAction"); +const patchScore = document.querySelector("#patchScore"); +const codeDiff = document.querySelector("#codeDiff"); +const activeTool = document.querySelector("#activeTool"); +const toolStatus = document.querySelector("#toolStatus"); +const toolOutput = document.querySelector("#toolOutput"); +const hardwareCommand = document.querySelector("#hardwareCommand"); +const benchVerdict = document.querySelector("#benchVerdict"); +const scopeState = document.querySelector("#scopeState"); const scopeCanvas = document.querySelector("#scopeCanvas"); const scopeContext = scopeCanvas.getContext("2d"); - +const actualFrequency = document.querySelector("#actualFrequency"); +const frequencyError = document.querySelector("#frequencyError"); +const boardFact = document.querySelector("#boardFact"); +const uartFact = document.querySelector("#uartFact"); +const evidenceButtons = [...document.querySelectorAll("[data-evidence]")]; +const evidenceDetail = document.querySelector("#evidenceDetail"); +const toast = document.querySelector("#toast"); const reducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)"); + let runToken = 0; -let currentState = "idle"; +let runState = "idle"; +let shownEvents = []; +let selectedEventId = "E00"; let selectedEvidence = -1; -let selectedStage = -1; +let currentLoop = "observe"; +let patchGenerated = false; +let activeTab = "decision"; +let scopeMode = "idle"; let scopeAnimationFrame = 0; let toastTimer = 0; -function delay(milliseconds, token) { - const duration = reducedMotion.matches ? Math.min(milliseconds, 120) : milliseconds; +function wait(milliseconds, token) { + const duration = reducedMotion.matches ? Math.min(milliseconds, 90) : milliseconds; return new Promise((resolve) => { window.setTimeout(() => resolve(token === runToken), duration); }); } function setRunState(state, label) { - currentState = state; + runState = state; app.dataset.runState = state; - runState.textContent = label; + agentState.textContent = label; runButton.disabled = state === "running"; failureMode.disabled = state === "running"; saveCaseButton.disabled = state !== "passed"; - - if (state === "running" && !scopeAnimationFrame) { - animateScope(); - } } -function setStageStatus(index, status) { - const button = stageButtons[index]; - if (!button) return; - if (status === "idle") { - button.removeAttribute("data-status"); - return; +function applyDecisionBoard(event) { + observedFact.textContent = event.observed; + currentHypothesis.textContent = event.hypothesis; + currentAction.textContent = event.action; + nextAction.textContent = event.next; + confidenceMeter.style.width = `${event.confidence}%`; + confidenceMeter.style.background = event.confidence >= 90 ? "var(--green)" : "var(--amber)"; + confidenceLabel.textContent = `${event.confidence >= 90 ? "证据" : "当前"} ${event.confidence}%`; +} + +function setLoop(name, status = "active") { + if (currentLoop && currentLoop !== name) { + const previous = loopButtons.find((button) => button.dataset.loop === currentLoop); + if (previous?.dataset.status === "active") previous.dataset.status = "passed"; } - button.dataset.status = status; + currentLoop = name; + const button = loopButtons.find((item) => item.dataset.loop === name); + if (button) button.dataset.status = status; +} + +function completeLoop(name, status = "passed") { + const button = loopButtons.find((item) => item.dataset.loop === name); + if (button) button.dataset.status = status; +} + +function setHardwareTarget(target, command = "等待工具调用") { + app.dataset.hardwareTarget = target; + hardwareCommand.textContent = command; +} + +function eventClass(status) { + if (status === "active") return "is-active"; + if (status === "failed" || status === "blocked") return "is-failed"; + if (status === "passed") return "is-passed"; + return ""; +} + +function appendEvent(event, status = "active") { + const eventState = { ...event, status }; + shownEvents.push(eventState); + + const item = document.createElement("li"); + item.className = `timeline-event ${eventClass(status)}`.trim(); + item.dataset.eventId = event.id; + const button = document.createElement("button"); + button.type = "button"; + button.innerHTML = ` + ${event.id} · ${event.actor} + + +${String(shownEvents.length - 1).padStart(2, "0")}:${String((shownEvents.length - 1) * 2).padStart(2, "0")} + ${event.title} + ${event.summary} + ${event.tool} + `; + button.addEventListener("click", () => selectEvent(event.id)); + item.append(button); + timeline.append(item); + eventCounter.textContent = `${String(shownEvents.length).padStart(2, "0")} EVENTS`; + selectEvent(event.id, false); + timeline.scrollTop = timeline.scrollHeight; +} + +function updateEventStatus(eventId, status) { + const event = shownEvents.find((item) => item.id === eventId); + if (event) event.status = status; + const item = timeline.querySelector(`[data-event-id="${eventId}"]`); + if (!item) return; + item.classList.remove("is-active", "is-passed", "is-failed"); + const className = eventClass(status); + if (className) item.classList.add(className); +} + +function selectEvent(eventId, switchView = true) { + const event = shownEvents.find((item) => item.id === eventId); + if (!event) return; + selectedEventId = eventId; + timeline.querySelectorAll("button").forEach((button) => { + button.classList.toggle("is-selected", button.parentElement?.dataset.eventId === eventId); + }); + + detailEventId.textContent = `${event.id} · ${event.actor} · ${event.phase.toUpperCase()}`; + detailTitle.textContent = event.title; + detailSummary.textContent = event.summary; + detailInput.textContent = event.input; + detailDecision.textContent = event.decision; + detailTool.textContent = event.tool; + detailOutput.textContent = event.output; + activeTool.textContent = event.tool; + toolStatus.textContent = event.status === "active" ? "RUNNING" : event.status.toUpperCase(); + toolOutput.textContent = event.log; + + if (switchView) selectTab(event.patch ? "patch" : event.actor === "AGENT" ? "decision" : "tool"); +} + +function selectTab(tab) { + activeTab = tab; + tabButtons.forEach((button) => { + const active = button.dataset.tab === tab; + button.classList.toggle("is-active", active); + button.setAttribute("aria-selected", String(active)); + }); + inspectorViews.forEach((view) => { + const active = view.dataset.view === tab; + view.classList.toggle("is-active", active); + view.hidden = !active; + }); +} + +function revealPatch() { + patchGenerated = true; + patchAction.textContent = "E04 · H1 · frequency.c"; + patchScore.textContent = "+5 −3 · REVIEWED"; + codeDiff.innerHTML = PATCH_HTML; } function setEvidenceStatus(index, status, label) { const button = evidenceButtons[index]; if (!button) return; - if (status === "idle") { - button.removeAttribute("data-status"); - } else { - button.dataset.status = status; - } - if (label) { - button.querySelector("span").textContent = label; - } - if (selectedEvidence === index) { - showEvidence(index); - } -} - -function selectStage(index) { - selectedStage = index; - stageButtons.forEach((button, buttonIndex) => { - button.classList.toggle("is-selected", buttonIndex === index); - }); - - const stage = STAGES[index]; - if (!stage) { - stageDetail.innerHTML = ` - READY -

等待开始工程闭环

-

执行过程将绑定代码、构建、下载、串口与物理测量事实。

- `; - return; - } - - const status = stageButtons[index].dataset.status || "idle"; - const statusLabel = { - idle: "PENDING", - active: "RUNNING", - passed: "VERIFIED", - failed: "FAILED", - }[status]; - - stageDetail.innerHTML = ` - ${String(index + 1).padStart(2, "0")} · ${statusLabel} -

${stage.title}

-

${stage.summary}

- `; + if (status === "idle") button.removeAttribute("data-status"); + else button.dataset.status = status; + if (label) button.querySelector("span").textContent = label; + if (selectedEvidence === index) showEvidence(index); } function showEvidence(index) { selectedEvidence = index; evidenceButtons.forEach((button, buttonIndex) => { - button.classList.toggle("is-selected", buttonIndex === index); + button.classList.toggle("is-selected", index === buttonIndex); }); - const status = evidenceButtons[index]?.dataset.status || "idle"; - const detail = EVIDENCE[index]?.[status] || EVIDENCE[index]?.idle; - evidenceDetail.textContent = detail || "暂无证据详情。"; + evidenceDetail.textContent = EVIDENCE_DETAILS[index]?.[status] || EVIDENCE_DETAILS[index]?.idle; } -function appendLog(message, tone = "") { - const line = document.createElement("span"); - line.className = tone ? `log-${tone}` : ""; - line.textContent = `${runLog.textContent.trim() ? "\n" : ""}${message}`; - runLog.append(line); - runLog.scrollTop = runLog.scrollHeight; -} - -function resetDemo({ keepFailure = true } = {}) { - runToken += 1; +function setScopeMode(mode) { + scopeMode = mode; window.cancelAnimationFrame(scopeAnimationFrame); scopeAnimationFrame = 0; - if (!keepFailure) failureMode.checked = false; - - stageButtons.forEach((button) => { - button.removeAttribute("data-status"); - button.classList.remove("is-selected"); - }); - evidenceButtons.forEach((button, index) => { - button.removeAttribute("data-status"); - button.classList.remove("is-selected"); - if (index === 5) button.querySelector("span").textContent = "ioProbe 待测"; - if (index === 6) button.querySelector("span").textContent = "Aggregate 待定"; - }); - - selectedStage = -1; - selectedEvidence = -1; - traceCounter.textContent = "0 / 6"; - runLog.innerHTML = '$ hwlab case run freq-1khz-accuracy\n 尚未执行'; - evidenceDetail.textContent = "选择证据节点查看边界事实。"; - actualFrequency.innerHTML = "— Hz"; - frequencyError.textContent = "—"; - scopeStatus.textContent = "等待采集"; - benchState.textContent = "设备待命"; - uartOutput.textContent = "[idle] waiting for firmware\n[idle] measurement channel ready"; - uartIndicator.classList.remove("is-active"); - setRunState("idle", "等待指令"); - selectStage(-1); - drawScope(0); -} - -async function runDemo() { - if (currentState === "running") return; - - resetDemo(); - const token = runToken; - const shouldFail = failureMode.checked; - setRunState("running", "闭环执行中"); - benchState.textContent = "HWPOD 已锁定"; - scopeStatus.textContent = "同步设备"; - runLog.innerHTML = ""; - appendLog("$ hwlab case run freq-1khz-accuracy", "muted"); - setEvidenceStatus(0, "passed"); - setEvidenceStatus(1, "active"); - - for (let index = 0; index < STAGES.length; index += 1) { - if (token !== runToken) return; - - setStageStatus(index, "active"); - selectStage(index); - traceCounter.textContent = `${index} / 6`; - appendLog(`→ ${STAGES[index].title}`, "active"); - - if (index === 3) benchState.textContent = "正在写入固件"; - if (index === 4) { - benchState.textContent = "固件运行中"; - uartIndicator.classList.add("is-active"); - uartOutput.textContent = "[boot] 71-FREQ rev.C\n[capture] timer input ready"; - } - if (index === 5) { - benchState.textContent = "ioProbe 采集中"; - scopeStatus.textContent = "采集中"; - setEvidenceStatus(5, "active", "ioProbe 采集中"); - } - - const shouldContinue = await delay(index === 5 ? 850 : 650, token); - if (!shouldContinue) return; - - if (index === 5 && shouldFail) { - setStageStatus(index, "failed"); - appendLog("probe CH1 frequency · 972.4 Hz · outside tolerance", "fail"); - traceCounter.textContent = "5 / 6"; - setEvidenceStatus(5, "failed", "ioProbe 972.4 Hz"); - setEvidenceStatus(6, "blocked", "Aggregate BLOCKED"); - actualFrequency.innerHTML = "972.4 Hz"; - frequencyError.textContent = "−2.76%"; - scopeStatus.textContent = "阈值超限"; - benchState.textContent = "物理验证未通过"; - uartOutput.textContent = "[capture] stable, samples=128\n[result] freq=972.4Hz, tolerance=FAIL"; - setRunState("failed", "证据阻断"); - selectStage(index); - showEvidence(6); - drawScope(0); - return; - } - - setStageStatus(index, "passed"); - appendLog(STAGES[index].log, "pass"); - traceCounter.textContent = `${index + 1} / 6`; - selectStage(index); - - if (index === 1) setEvidenceStatus(1, "passed"); - if (index === 2) setEvidenceStatus(2, "passed"); - if (index === 3) setEvidenceStatus(3, "passed"); - if (index === 4) { - setEvidenceStatus(4, "passed"); - uartOutput.textContent = "[capture] stable, samples=128\n[timer] clock=72000000Hz, overflow=0"; - } - } - - setEvidenceStatus(5, "passed", "ioProbe 999.8 Hz"); - setEvidenceStatus(6, "passed", "Aggregate PASS"); - actualFrequency.innerHTML = "999.8 Hz"; - frequencyError.textContent = "−0.02%"; - scopeStatus.textContent = "测量通过"; - benchState.textContent = "验证完成 · PASS"; - uartOutput.textContent = "[capture] stable, samples=128\n[result] freq=999.8Hz, tolerance=PASS"; - appendLog("verdict Aggregate PASS · evidence sealed", "pass"); - setRunState("passed", "Aggregate PASS"); - showEvidence(6); - drawScope(0); -} - -function showToast(message) { - window.clearTimeout(toastTimer); - toast.textContent = message; - toast.classList.add("is-visible"); - toastTimer = window.setTimeout(() => toast.classList.remove("is-visible"), 2600); + if (mode === "sampling") animateScope(); + else drawScope(0); } function drawScope(phase = 0) { @@ -329,12 +487,12 @@ function drawScope(phase = 0) { const cssWidth = width / ratio; const cssHeight = height / ratio; scopeContext.clearRect(0, 0, cssWidth, cssHeight); - scopeContext.fillStyle = "#0c1418"; + scopeContext.fillStyle = "#0b1418"; scopeContext.fillRect(0, 0, cssWidth, cssHeight); - - scopeContext.strokeStyle = "rgba(94, 132, 143, 0.18)"; + scopeContext.strokeStyle = "rgba(95, 135, 145, 0.18)"; scopeContext.lineWidth = 1; - for (let x = 0; x <= cssWidth; x += cssWidth / 10) { + + for (let x = 0; x <= cssWidth; x += cssWidth / 9) { scopeContext.beginPath(); scopeContext.moveTo(x, 0); scopeContext.lineTo(x, cssHeight); @@ -347,32 +505,30 @@ function drawScope(phase = 0) { scopeContext.stroke(); } - const amplitude = cssHeight * 0.27; + const idle = scopeMode === "idle"; + const failed = scopeMode === "failed"; + const amplitude = cssHeight * 0.28; const center = cssHeight * 0.5; - const period = currentState === "failed" ? 51 : 43; - const waveColor = currentState === "failed" ? "#ff766e" : "#53dce1"; - const muted = currentState === "idle"; - + const period = failed ? 52 : 43; scopeContext.beginPath(); for (let x = 0; x <= cssWidth; x += 2) { - const signal = muted - ? Math.sin(x / 25) * 1.2 - : Math.sin((x + phase) * ((Math.PI * 2) / period)) * amplitude; - const noise = currentState === "running" ? Math.sin((x + phase * 1.9) / 5) * 0.8 : 0; + const signal = idle ? Math.sin(x / 24) * 1.1 : Math.sin((x + phase) * ((Math.PI * 2) / period)) * amplitude; + const noise = scopeMode === "sampling" ? Math.sin((x + phase * 1.7) / 5) * 0.8 : 0; const y = center + signal + noise; if (x === 0) scopeContext.moveTo(x, y); else scopeContext.lineTo(x, y); } - scopeContext.strokeStyle = muted ? "#496169" : waveColor; - scopeContext.lineWidth = 1.4; - scopeContext.shadowColor = muted ? "transparent" : waveColor; - scopeContext.shadowBlur = muted ? 0 : 5; + const color = failed ? "#ff766e" : idle ? "#486169" : "#50dbe0"; + scopeContext.strokeStyle = color; + scopeContext.lineWidth = 1.3; + scopeContext.shadowColor = idle ? "transparent" : color; + scopeContext.shadowBlur = idle ? 0 : 5; scopeContext.stroke(); scopeContext.shadowBlur = 0; } function animateScope(timestamp = 0) { - if (currentState !== "running") { + if (scopeMode !== "sampling") { scopeAnimationFrame = 0; return; } @@ -380,8 +536,171 @@ function animateScope(timestamp = 0) { scopeAnimationFrame = window.requestAnimationFrame(animateScope); } -stageButtons.forEach((button, index) => { - button.addEventListener("click", () => selectStage(index)); +function resetDemo({ keepFailure = true } = {}) { + runToken += 1; + if (!keepFailure) failureMode.checked = false; + shownEvents = []; + selectedEventId = "E00"; + selectedEvidence = -1; + currentLoop = "observe"; + patchGenerated = false; + timeline.innerHTML = ""; + loopButtons.forEach((button) => button.removeAttribute("data-status")); + loopButtons.find((button) => button.dataset.loop === "observe").dataset.status = "active"; + evidenceButtons.forEach((button, index) => { + button.removeAttribute("data-status"); + button.classList.remove("is-selected"); + if (index === 7) button.querySelector("span").textContent = "Verdict 待定"; + }); + + patchAction.textContent = "尚未生成变更"; + patchScore.textContent = "WAITING"; + codeDiff.innerHTML = '
NO PATCH YET

AGENT 必须先获得工程事实并验证假设,代码变更才会出现。

'; + evidenceDetail.textContent = "每个证据节点都引用产生它的 AGENT 行动。"; + actualFrequency.innerHTML = "— Hz"; + frequencyError.textContent = "—"; + scopeState.textContent = "等待 AGENT 调用"; + benchVerdict.textContent = "SESSION BOUND"; + boardFact.textContent = "已绑定 · 待操作"; + uartFact.textContent = "等待固件"; + setHardwareTarget("none"); + setScopeMode("idle"); + setRunState("idle", "已接管 · 等待执行"); + applyDecisionBoard(INITIAL_EVENT); + appendEvent(INITIAL_EVENT, "idle"); + selectTab("decision"); +} + +async function runDemo() { + if (runState === "running") return; + resetDemo(); + const token = runToken; + const shouldFail = failureMode.checked; + + setRunState("running", "AGENT 正在观察工程"); + updateEventStatus("E00", "passed"); + setEvidenceStatus(0, "passed"); + setEvidenceStatus(1, "active"); + + for (const event of RUN_EVENTS) { + if (token !== runToken) return; + setLoop(event.phase); + applyDecisionBoard(event); + appendEvent(event, "active"); + setHardwareTarget(event.target, event.target === "none" ? `${event.tool} · local action` : `${event.tool} · command dispatched`); + agentState.textContent = `${event.id} · ${event.title}`; + + if (event.patch) { + revealPatch(); + selectTab("patch"); + } else if (event.actor !== "AGENT") { + selectTab("tool"); + } else { + selectTab("decision"); + } + + if (event.id === "E05") boardFact.textContent = "固件已构建 · 待写入"; + if (event.id === "E06") boardFact.textContent = "正在写入固件"; + if (event.id === "E07") { + boardFact.textContent = "固件运行中"; + uartFact.textContent = "采集 128 samples"; + } + if (event.id === "E08") { + scopeState.textContent = "AGENT 正在采样"; + setEvidenceStatus(6, "active"); + setScopeMode("sampling"); + } + + const continued = await wait(event.id === "E08" ? 720 : 520, token); + if (!continued) return; + + if (event.id === "E08" && shouldFail) { + updateEventStatus(event.id, "failed"); + const failedMeasurement = shownEvents.find((item) => item.id === event.id); + failedMeasurement.output = "ioProbe 972.4 Hz;误差 −2.76%;超出阈值"; + failedMeasurement.log = "$ ioprobe.measure --channel CH1 --metric frequency\narm FREQ_OUT\nsample physical waveform\nactual 972.4 Hz\nerror -2.76%\nresult OUTSIDE TOLERANCE"; + setEvidenceStatus(6, "failed"); + actualFrequency.innerHTML = "972.4 Hz"; + frequencyError.textContent = "−2.76%"; + scopeState.textContent = "物理阈值超限"; + benchVerdict.textContent = "PHYSICAL FAIL"; + setScopeMode("failed"); + selectEvent(event.id, false); + break; + } + + if (event.id === "E08") { + const passedMeasurement = shownEvents.find((item) => item.id === event.id); + passedMeasurement.output = "ioProbe 999.8 Hz;误差 −0.02%;满足阈值"; + passedMeasurement.log = "$ ioprobe.measure --channel CH1 --metric frequency\narm FREQ_OUT\nsample physical waveform\nactual 999.8 Hz\nerror -0.02%\nresult WITHIN TOLERANCE"; + actualFrequency.innerHTML = "999.8 Hz"; + frequencyError.textContent = "−0.02%"; + scopeState.textContent = "独立测量通过"; + setScopeMode("passed"); + } + + updateEventStatus(event.id, "passed"); + if (event.id === "E04") setEvidenceStatus(2, "passed"); + if (event.evidence !== undefined) setEvidenceStatus(event.evidence, "passed"); + selectEvent(event.id, false); + } + + if (token !== runToken) return; + const verdict = shouldFail ? FAILURE_EVENT : SUCCESS_EVENT; + setLoop("reflect"); + applyDecisionBoard(verdict); + appendEvent(verdict, "active"); + selectTab("decision"); + setHardwareTarget("none", shouldFail ? "physical fact rejected completion" : "evidence aggregation"); + agentState.textContent = shouldFail ? "AGENT 正在拒绝错误结果" : "AGENT 正在聚合证据"; + + const continued = await wait(620, token); + if (!continued) return; + + updateEventStatus(verdict.id, "passed"); + completeLoop("reflect", "passed"); + setEvidenceStatus(1, "passed"); + + if (shouldFail) { + setEvidenceStatus(7, "blocked", "Verdict BLOCKED"); + setLoop("hypothesize", "active"); + applyDecisionBoard(FAILURE_EVENT); + benchVerdict.textContent = "BLOCKED · BACK TO DIAGNOSIS"; + boardFact.textContent = "保持现场 · 等待新计划"; + uartFact.textContent = "软件事实保留"; + setRunState("failed", "拒绝封存 · 已返回诊断"); + showEvidence(7); + } else { + setEvidenceStatus(7, "passed", "Verdict PASS"); + actualFrequency.innerHTML = "999.8 Hz"; + frequencyError.textContent = "−0.02%"; + scopeState.textContent = "独立测量通过"; + benchVerdict.textContent = "AGGREGATE PASS"; + boardFact.textContent = "验证完成 · 固件保留"; + uartFact.textContent = "稳定 · 128 samples"; + setScopeMode("passed"); + setRunState("passed", "证据完整 · Aggregate PASS"); + showEvidence(7); + } + selectEvent(verdict.id, false); +} + +function showToast(message) { + window.clearTimeout(toastTimer); + toast.textContent = message; + toast.classList.add("is-visible"); + toastTimer = window.setTimeout(() => toast.classList.remove("is-visible"), 2500); +} + +tabButtons.forEach((button) => { + button.addEventListener("click", () => selectTab(button.dataset.tab)); +}); + +loopButtons.forEach((button) => { + button.addEventListener("click", () => { + const matching = [...shownEvents].reverse().find((event) => event.phase === button.dataset.loop); + if (matching) selectEvent(matching.id); + }); }); evidenceButtons.forEach((button, index) => { @@ -391,14 +710,14 @@ evidenceButtons.forEach((button, index) => { runButton.addEventListener("click", runDemo); resetButton.addEventListener("click", () => resetDemo({ keepFailure: true })); failureMode.addEventListener("change", () => { - if (currentState !== "idle") resetDemo({ keepFailure: true }); - showToast(failureMode.checked ? "失败演示已开启:物理测量将阻断聚合结果" : "已恢复通过路径"); + if (runState !== "idle") resetDemo({ keepFailure: true }); + showToast(failureMode.checked ? "失败演示开启:AGENT 将拒绝封存并返回诊断" : "已恢复通过路径"); }); saveCaseButton.addEventListener("click", () => { - if (currentState !== "passed") return; - showToast("回归 Case 已保存:freq-1khz-accuracy / evidence sealed"); + if (runState !== "passed") return; + showToast("回归 Case 已封存:freq-1khz-accuracy · Agent Trace + Physical Evidence"); }); - window.addEventListener("resize", () => drawScope(0)); window.addEventListener("load", () => drawScope(0)); + resetDemo({ keepFailure: false }); diff --git a/project-management/PJ2026-01/showcase/hwlab-first-principles/index.html b/project-management/PJ2026-01/showcase/hwlab-first-principles/index.html index 5dba0a94..e4414149 100644 --- a/project-management/PJ2026-01/showcase/hwlab-first-principles/index.html +++ b/project-management/PJ2026-01/showcase/hwlab-first-principles/index.html @@ -5,220 +5,253 @@ - HWLAB · 真实硬件研发闭环 + HWLAB · AGENT 真实硬件研发闭环 -
+
-
+
- HWLAB - - 云端真实硬件研发闭环 + HWLAB + + 真实硬件研发闭环
-
- TARGET +
+ ACTIVE TARGET 71-FREQ / STM32F103
-
- NC01 · v03 - HWPOD 已占用 - Session 就绪 +
+ NC01 · v03 + HWPOD LOCKED + SESSION READY
- -
-
-
- ENGINEERING AGENT -

受控工程工作台

+
+
+ - 等待指令 -
+
+ ENGINEERING AGENT · ONLINE +

ALTA-7 Embedded Systems

+
+
+ SESSION + agt_71freq_024 +
+ 已接管 · 等待执行 + -
- -

修复 1 kHz 输入下的频率误差,并在真实板上完成验证

-
+
-
    -
  1. -
  2. -
  3. -
  4. -
  5. -
  6. -
- -
-
-
-
- - -
- +5 −3 -
-
-
118static float capture_to_hz(uint32_t ticks)
-
119{
-
120- return TIMER_CLOCK_HZ / ticks;
-
121- // APB prescaler assumed to be 1
-
120+ const uint32_t timer_clock = get_timer_clock_hz();
-
121+ const float calibrated_ticks =
-
122+ (float)ticks - capture_offset_ticks;
-
123+ return timer_clock / calibrated_ticks;
-
124}
-
125
-
126float frequency_read(void)
-
127{
-
128+ capture_offset_ticks = calibration.capture_offset;
-
129 return capture_to_hz(last_capture_ticks);
-
130}
+
+
+ OBSERVED FACT +

任务、工程和真实硬件已完成上下文绑定

+
+
+ CURRENT HYPOTHESIS +

待验证:TIM2 时钟推导与板级配置不一致

+
+ 初始 42%
+
+
+ CURRENT ACTION +

等待授权读取工程事实

+
+
+ NEXT ACTION +

检查 Keil 工程与 TIM2 配置

+
+
+ + + +
+
+
+
+ LIVE AGENT TRACE +

行动时间线

+
+ 01 EVENT +
+
    - + + + + +
    -
    @@ -226,21 +259,22 @@
    diff --git a/project-management/PJ2026-01/showcase/hwlab-first-principles/styles.css b/project-management/PJ2026-01/showcase/hwlab-first-principles/styles.css index 51dc3b82..29d954b0 100644 --- a/project-management/PJ2026-01/showcase/hwlab-first-principles/styles.css +++ b/project-management/PJ2026-01/showcase/hwlab-first-principles/styles.css @@ -1,24 +1,25 @@ :root { color-scheme: light; - --ink: #172126; - --ink-soft: #4d5d64; - --line: #d7dfe2; - --line-strong: #b8c5ca; - --paper: #f4f7f7; + --ink: #162126; + --ink-2: #415157; + --muted: #718087; + --line: #d4dde0; + --line-strong: #afbec3; + --paper: #f1f5f5; --surface: #ffffff; - --surface-muted: #edf2f3; - --cyan: #0798a4; - --cyan-dark: #087680; - --cyan-pale: #e3f5f5; - --green: #16845b; - --green-pale: #e8f5ef; - --amber: #b36a00; - --amber-pale: #fff1d8; - --red: #bd3e35; - --red-pale: #fae9e7; - --night: #10181c; - --night-2: #182328; - --night-line: #35444a; + --surface-2: #eaf0f1; + --cyan: #058e99; + --cyan-dark: #08737c; + --cyan-pale: #e1f3f4; + --amber: #b66d00; + --amber-pale: #fff2d8; + --green: #11805a; + --green-pale: #e4f4ec; + --red: #bd4038; + --red-pale: #fae8e6; + --night: #0e171b; + --night-2: #162227; + --night-line: #304148; --mono: "JetBrains Mono", "Cascadia Code", "SFMono-Regular", Consolas, monospace; --sans: Bahnschrift, "DIN Alternate", "Noto Sans CJK SC", "Microsoft YaHei", sans-serif; } @@ -29,9 +30,9 @@ html, body { - margin: 0; min-width: 320px; min-height: 100%; + margin: 0; background: var(--paper); color: var(--ink); font-family: var(--sans); @@ -53,7 +54,7 @@ button { button:focus-visible, input:focus-visible, [tabindex]:focus-visible { - outline: 2px solid #0b9da8; + outline: 2px solid #09a5af; outline-offset: 2px; } @@ -69,9 +70,17 @@ input:focus-visible, border: 0; } +.section-kicker { + color: var(--muted); + font-family: var(--mono); + font-size: 8px; + font-weight: 700; + letter-spacing: 0.13em; +} + .app-shell { display: grid; - grid-template-rows: 52px minmax(0, 1fr) 138px; + grid-template-rows: 48px minmax(0, 1fr) 120px; width: 100vw; height: 100dvh; min-height: 560px; @@ -80,719 +89,1173 @@ input:focus-visible, .topbar { display: grid; - grid-template-columns: minmax(310px, auto) minmax(250px, 1fr) auto; + grid-template-columns: minmax(300px, auto) minmax(230px, 1fr) auto; align-items: center; - gap: 24px; - padding: 0 18px; + gap: 22px; + padding: 0 16px; border-bottom: 1px solid var(--line-strong); background: #f9fbfb; } -.brand-block, -.system-status, -.target-context, -.panel-title-row, -.hwpod-heading, -.evidence-heading, -.scope-header, -.uart-panel header, -.surface-header { +.brand, +.top-target, +.top-status, +.agent-presence, +.mission-console, +.pane-header, +.inspector-header, +.hwpod-header, +.scope-panel header, +.evidence-heading { display: flex; align-items: center; } -.brand-block { - min-width: 0; -} - .brand-mark { display: grid; + width: 28px; + height: 28px; place-items: center; - width: 30px; - height: 30px; - border: 1px solid var(--ink); background: var(--ink); color: white; font-family: var(--mono); - font-size: 10px; + font-size: 9px; font-weight: 800; - letter-spacing: -0.06em; + letter-spacing: -0.08em; } -.brand-name { +.brand > strong { margin-left: 9px; font-family: var(--mono); - font-size: 15px; - font-weight: 800; + font-size: 14px; letter-spacing: 0.08em; } -.brand-divider { +.brand-rule { width: 1px; - height: 17px; - margin: 0 12px; + height: 16px; + margin: 0 11px; background: var(--line-strong); } -.brand-tagline { - overflow: hidden; - color: var(--ink-soft); - font-size: 12px; +.brand-caption { + color: var(--ink-2); + font-size: 11px; white-space: nowrap; - text-overflow: ellipsis; } -.target-context { +.top-target { min-width: 0; - gap: 10px; - justify-self: start; - font-size: 12px; + gap: 9px; } -.target-context strong { +.top-target span { + color: #718087; + font-family: var(--mono); + font-size: 8px; + letter-spacing: 0.1em; +} + +.top-target strong { overflow: hidden; font-family: var(--mono); - font-size: 12px; - white-space: nowrap; + font-size: 10px; text-overflow: ellipsis; + white-space: nowrap; } -.context-label, -.eyebrow { - color: #728187; - font-family: var(--mono); - font-size: 9px; - font-weight: 700; - letter-spacing: 0.12em; -} - -.system-status { - gap: 8px; +.top-status { justify-content: flex-end; + gap: 7px; } -.status-pill { +.top-status span { display: inline-flex; align-items: center; - gap: 6px; - padding: 5px 7px; + gap: 5px; + padding: 4px 6px; border: 1px solid var(--line); - color: #415157; + color: #56666c; font-family: var(--mono); - font-size: 9px; + font-size: 8px; white-space: nowrap; } -.status-dot, -.state-dot, -.live-indicator i { - display: inline-block; - width: 6px; - height: 6px; +.top-status i, +.hardware-live i { + width: 5px; + height: 5px; border-radius: 50%; - background: #8d999e; -} - -.status-pill .status-dot, -.state-dot--done { background: var(--green); } -.status-pill--hardware .status-dot, -.state-dot--active { +.top-status span:nth-child(2) i { background: var(--amber); } .workspace { display: grid; - grid-template-columns: 250px minmax(540px, 1fr) minmax(370px, 420px); + grid-template-columns: 220px minmax(680px, 1fr) minmax(330px, 365px); min-width: 0; min-height: 0; overflow: hidden; } -.task-panel, -.agent-panel, -.hwpod-panel { +.context-rail, +.agent-stage, +.hwpod-stage { min-width: 0; min-height: 0; } -.task-panel { +.context-rail { display: flex; flex-direction: column; - padding: 20px 14px 14px; + padding: 16px 12px 12px; border-right: 1px solid var(--line); background: #f7f9f9; } -.task-panel-heading { +.project-heading { position: relative; - min-height: 66px; - padding: 0 8px 14px; + padding: 0 8px 13px; border-bottom: 1px solid var(--line); } -.project-copy h1 { +.project-name h2 { margin: 6px 0 0; font-family: var(--mono); - font-size: 22px; + font-size: 20px; line-height: 1; - letter-spacing: -0.04em; + letter-spacing: -0.05em; } -.project-copy p { +.project-name p { margin: 4px 0 0; - color: var(--ink-soft); - font-size: 12px; + color: var(--ink-2); + font-size: 11px; } -.project-code { +.project-monogram { display: none; } -.task-list { - display: grid; - gap: 4px; - padding: 12px 0; -} - -.task-item { - display: flex; - width: 100%; - min-width: 0; - min-height: 58px; - gap: 10px; - align-items: flex-start; - padding: 9px 8px; - border: 1px solid transparent; - background: transparent; - text-align: left; - cursor: pointer; -} - -.task-item:hover { - border-color: var(--line); - background: var(--surface); -} - -.task-item--active { - border-color: #a6ced1; - background: var(--cyan-pale); - box-shadow: inset 3px 0 0 var(--cyan); -} - -.task-marker { - display: grid; - flex: 0 0 27px; - height: 27px; - place-items: center; - border: 1px solid var(--line-strong); - color: #647278; - font-family: var(--mono); - font-size: 9px; -} - -.task-item--active .task-marker { - border-color: var(--cyan); - color: var(--cyan-dark); -} - -.task-text { - display: grid; - min-width: 0; - gap: 5px; -} - -.task-text strong { - overflow: hidden; - font-size: 12px; - font-weight: 650; - line-height: 1.35; - text-overflow: ellipsis; -} - -.task-text small { - display: flex; - align-items: center; - gap: 6px; - color: #748187; - font-size: 10px; -} - -.context-facts { - margin-top: auto; - padding: 14px 8px 0; - border-top: 1px solid var(--line); -} - -.context-facts dl { +.active-task { display: grid; + grid-template-columns: 34px minmax(0, 1fr); gap: 9px; - margin: 11px 0 0; -} - -.context-facts dl div { - min-width: 0; -} - -.context-facts dt { - color: #7b898e; - font-family: var(--mono); - font-size: 9px; -} - -.context-facts dd { - overflow: hidden; - margin: 2px 0 0; - font-family: var(--mono); - font-size: 10px; - white-space: nowrap; - text-overflow: ellipsis; -} - -.agent-panel { - display: grid; - grid-template-rows: 50px 62px 58px minmax(0, 1fr); - padding: 0 16px 14px; - background: var(--paper); -} - -.panel-title-row, -.hwpod-heading { - justify-content: space-between; -} - -.panel-title-row h2, -.hwpod-heading h2, -.evidence-heading h2 { - margin: 3px 0 0; - font-size: 15px; - line-height: 1; -} - -.run-state { - padding: 4px 7px; - border: 1px solid var(--line); - color: var(--ink-soft); - font-family: var(--mono); - font-size: 9px; -} - -.app-shell[data-run-state="running"] .run-state { - border-color: #e1b660; - background: var(--amber-pale); - color: #895000; -} - -.app-shell[data-run-state="passed"] .run-state { - border-color: #8bc7ad; - background: var(--green-pale); - color: #0d704b; -} - -.app-shell[data-run-state="failed"] .run-state { - border-color: #dda19c; - background: var(--red-pale); - color: #9f3029; -} - -.command-bar { - display: grid; - grid-template-columns: auto minmax(180px, 1fr) auto 34px auto; - align-items: center; - gap: 10px; - padding: 8px 9px; - border: 1px solid var(--line-strong); - background: var(--surface); - box-shadow: 0 2px 8px rgb(31 53 61 / 6%); -} - -.command-symbol { - display: grid; - width: 33px; - height: 33px; - place-items: center; - background: var(--ink); - color: #86edf0; - font-family: var(--mono); - font-size: 13px; - font-weight: 700; -} - -.command-bar p { - overflow: hidden; - margin: 0; - font-size: 12px; - font-weight: 650; - white-space: nowrap; - text-overflow: ellipsis; -} - -.failure-toggle { - display: flex; - align-items: center; - gap: 6px; - color: #66757a; - font-size: 10px; - cursor: pointer; - white-space: nowrap; -} - -.failure-toggle input { - position: absolute; - opacity: 0; - pointer-events: none; -} - -.toggle-track { - display: inline-flex; - width: 26px; - height: 14px; - align-items: center; - padding: 2px; - border: 1px solid var(--line-strong); - border-radius: 8px; - background: #e8edef; - transition: background 140ms ease, border-color 140ms ease; -} - -.toggle-track i { - width: 8px; - height: 8px; - border-radius: 50%; - background: #7c8a8f; - transition: transform 140ms ease, background 140ms ease; -} - -.failure-toggle input:checked + .toggle-track { - border-color: var(--red); - background: var(--red-pale); -} - -.failure-toggle input:checked + .toggle-track i { - transform: translateX(12px); - background: var(--red); -} - -.icon-button, -.run-button, -#saveCaseButton { - height: 34px; - border: 1px solid var(--line-strong); - background: var(--surface); - cursor: pointer; -} - -.icon-button { - width: 34px; - color: #536369; - font-size: 17px; -} - -.run-button { - display: inline-flex; - align-items: center; - gap: 7px; - padding: 0 14px; - border-color: #087b84; - background: var(--cyan-dark); - color: white; - font-size: 11px; - font-weight: 700; -} - -.run-button:hover:not(:disabled) { - background: #05636b; -} - -.run-button:disabled, -#saveCaseButton:disabled { - cursor: not-allowed; - opacity: 0.48; -} - -.stage-strip { - display: grid; - grid-template-columns: repeat(6, minmax(0, 1fr)); - align-items: end; - gap: 4px; - margin: 0; - padding: 8px 0 7px; - list-style: none; -} - -.stage-strip button { - position: relative; - display: grid; - width: 100%; - height: 42px; - grid-template-columns: auto minmax(0, 1fr); - align-items: center; - gap: 6px; - padding: 0 7px; - overflow: hidden; - border: 1px solid var(--line); - background: #f9fbfb; - cursor: pointer; - text-align: left; -} - -.stage-strip button:hover, -.stage-strip button.is-selected { - border-color: #8fbcc0; + margin-top: 12px; + padding: 10px 8px; + border: 1px solid #94c7ca; + border-left: 3px solid var(--cyan); background: var(--cyan-pale); } -.stage-strip button span { - color: #8a979b; +.task-index { + display: grid; + height: 26px; + place-items: center; + border: 1px solid var(--cyan); + color: var(--cyan-dark); font-family: var(--mono); font-size: 8px; } -.stage-strip button strong { - overflow: hidden; - font-size: 10px; - font-weight: 650; - white-space: nowrap; - text-overflow: ellipsis; +.active-task h3 { + margin: 4px 0 5px; + font-size: 11px; + line-height: 1.35; } -.stage-strip button i { - position: absolute; - right: 0; - bottom: 0; - left: 0; - height: 2px; - background: #cbd4d7; -} - -.stage-strip button[data-status="active"] i { - background: var(--amber); - animation: stage-pulse 850ms ease-in-out infinite alternate; -} - -.stage-strip button[data-status="passed"] i { - background: var(--green); -} - -.stage-strip button[data-status="failed"] i { - background: var(--red); -} - -@keyframes stage-pulse { - from { opacity: 0.35; } - to { opacity: 1; } -} - -.work-surface { - display: grid; - grid-template-columns: minmax(0, 1.5fr) minmax(230px, 0.72fr); - min-height: 0; - overflow: hidden; - border: 1px solid var(--line-strong); - background: var(--surface); -} - -.editor-pane, -.trace-pane { - display: grid; - min-width: 0; - min-height: 0; - grid-template-rows: 36px minmax(0, 1fr); -} - -.editor-pane { - border-right: 1px solid var(--line); -} - -.trace-pane { - grid-template-rows: 36px auto minmax(0, 1fr); - background: #fafcfc; -} - -.surface-header { - min-width: 0; - justify-content: space-between; - border-bottom: 1px solid var(--line); - background: #f2f5f5; -} - -.surface-header > strong { - padding-left: 10px; - font-size: 10px; -} - -.surface-header > span { - padding-right: 10px; - color: #718086; - font-family: var(--mono); - font-size: 9px; -} - -.file-tabs { +.active-task p { display: flex; - height: 100%; - min-width: 0; + align-items: center; + gap: 6px; + margin: 0; + color: #6f5b32; + font-size: 8px; } -.file-tab { - position: relative; - min-width: 0; - padding: 0 12px; - overflow: hidden; - border: 0; - border-right: 1px solid var(--line); - background: transparent; - color: #68777d; - font-family: var(--mono); - font-size: 9px; - text-overflow: ellipsis; - white-space: nowrap; - cursor: pointer; -} - -.file-tab--active { - background: white; - color: var(--ink); -} - -.file-tab i { - position: absolute; - top: 8px; - right: 7px; +.active-task p i { width: 5px; height: 5px; border-radius: 50%; background: var(--amber); } -.change-summary b, -.change-summary em { - font-style: normal; - font-weight: 700; -} - -.change-summary b { color: var(--green); } -.change-summary em { color: var(--red); } - -.code-view { - min-width: 0; - overflow: auto; - padding: 8px 0 20px; - background: #fff; - font-family: var(--mono); - font-size: 10px; - line-height: 1.7; -} - -.code-line { - display: grid; - min-width: max-content; - grid-template-columns: 43px auto; - padding-right: 14px; -} - -.code-line > span { - padding-right: 10px; - border-right: 1px solid #edf0f1; - color: #a0aaae; - text-align: right; - user-select: none; -} - -.code-line code { - padding-left: 11px; - white-space: pre; -} - -.code-line--removed { - background: var(--red-pale); - color: #8f312b; -} - -.code-line--added { - background: var(--green-pale); - color: #116844; -} - -.code-line--context { - color: #526167; -} - -.stage-detail { - padding: 12px 11px 10px; +.brief-block { + padding: 15px 8px; border-bottom: 1px solid var(--line); } -.detail-index { +.brief-block p { + margin: 7px 0 0; + color: var(--ink-2); + font-size: 10px; + line-height: 1.55; +} + +.bound-context { + margin-top: auto; + padding: 14px 8px 12px; + border-top: 1px solid var(--line); +} + +.bound-context dl { + display: grid; + gap: 7px; + margin: 9px 0 0; +} + +.bound-context dt { + color: #829096; + font-family: var(--mono); + font-size: 7px; +} + +.bound-context dd { + overflow: hidden; + margin: 2px 0 0; + font-family: var(--mono); + font-size: 9px; + text-overflow: ellipsis; + white-space: nowrap; +} + +.control-boundary { + display: grid; + grid-template-columns: 24px minmax(0, 1fr); + gap: 8px; + padding: 10px 8px 0; + border-top: 1px solid var(--line); +} + +.boundary-icon { + display: grid; + width: 22px; + height: 22px; + place-items: center; + border: 1px solid var(--line-strong); color: var(--cyan-dark); +} + +.control-boundary p { + margin: 4px 0 0; + color: #6c7a80; + font-size: 8px; + line-height: 1.4; +} + +.agent-stage { + display: grid; + grid-template-rows: 48px 54px 74px 43px minmax(0, 1fr); + padding: 0 12px 9px; + background: var(--paper); +} + +.agent-presence { + gap: 9px; + border-bottom: 1px solid var(--line); +} + +.agent-avatar { + position: relative; + display: grid; + flex: 0 0 31px; + height: 31px; + place-items: center; + border: 1px solid var(--ink); + background: var(--ink); + color: #98f0f1; + font-family: var(--mono); + font-size: 9px; + font-weight: 800; +} + +.agent-avatar i { + position: absolute; + top: -3px; + right: -3px; + width: 8px; + height: 8px; + border: 2px solid var(--paper); + border-radius: 50%; + background: var(--green); +} + +.agent-identity { + min-width: 0; +} + +.agent-identity h1 { + margin: 3px 0 0; + font-family: var(--mono); + font-size: 14px; + line-height: 1; + letter-spacing: 0.03em; +} + +.agent-identity h1 small { + margin-left: 5px; + color: var(--ink-2); + font-family: var(--sans); + font-size: 9px; + font-weight: 500; + letter-spacing: 0; +} + +.session-identity { + display: grid; + gap: 2px; + margin-left: auto; + text-align: right; +} + +.session-identity span { + color: #7a898e; + font-family: var(--mono); + font-size: 7px; +} + +.session-identity strong { font-family: var(--mono); font-size: 8px; +} + +.agent-state { + margin-left: 8px; + padding: 4px 7px; + border: 1px solid var(--line-strong); + color: var(--ink-2); + font-size: 8px; + white-space: nowrap; +} + +.app-shell[data-run-state="running"] .agent-state { + border-color: #dfb66f; + background: var(--amber-pale); + color: #8d5300; +} + +.app-shell[data-run-state="passed"] .agent-state { + border-color: #86c3a9; + background: var(--green-pale); + color: #0e704c; +} + +.app-shell[data-run-state="failed"] .agent-state { + border-color: #dda09b; + background: var(--red-pale); + color: #9c302a; +} + +.mission-console { + display: grid; + grid-template-columns: minmax(220px, 1fr) auto 32px auto; + gap: 7px; + align-items: center; + padding: 7px 0; +} + +.mission-command { + display: grid; + min-width: 0; + height: 40px; + grid-template-columns: 35px minmax(0, 1fr); + align-items: center; + gap: 9px; + padding: 0 10px 0 0; + border: 1px solid var(--line-strong); + background: var(--surface); +} + +.prompt-glyph { + display: grid; + height: 100%; + place-items: center; + background: var(--ink); + color: #88ebed; + font-family: var(--mono); + font-size: 11px; +} + +.mission-command div { + min-width: 0; +} + +.mission-command div span { + display: block; + color: #7a898e; + font-family: var(--mono); + font-size: 7px; +} + +.mission-command div strong { + display: block; + overflow: hidden; + margin-top: 3px; + font-size: 10px; + text-overflow: ellipsis; + white-space: nowrap; +} + +.failure-switch { + display: flex; + align-items: center; + gap: 6px; + color: #65757b; + font-size: 9px; + cursor: pointer; + white-space: nowrap; +} + +.failure-switch input { + position: absolute; + opacity: 0; + pointer-events: none; +} + +.switch-track { + display: flex; + width: 26px; + height: 14px; + align-items: center; + padding: 2px; + border: 1px solid var(--line-strong); + border-radius: 8px; + background: #e7edef; +} + +.switch-track i { + width: 8px; + height: 8px; + border-radius: 50%; + background: #7b8a8f; + transition: transform 140ms ease, background 140ms ease; +} + +.failure-switch input:checked + .switch-track { + border-color: var(--red); + background: var(--red-pale); +} + +.failure-switch input:checked + .switch-track i { + transform: translateX(12px); + background: var(--red); +} + +.reset-button, +.run-button, +#saveCaseButton { + height: 32px; + border: 1px solid var(--line-strong); + background: var(--surface); + cursor: pointer; +} + +.reset-button { + width: 32px; + color: var(--ink-2); + font-size: 15px; +} + +.run-button { + display: inline-flex; + align-items: center; + gap: 7px; + padding: 0 12px; + border-color: var(--cyan-dark); + background: var(--cyan-dark); + color: white; + font-size: 9px; + font-weight: 700; +} + +.run-button:hover:not(:disabled) { + background: #075f66; +} + +.run-button:disabled, +#saveCaseButton:disabled { + cursor: not-allowed; + opacity: 0.45; +} + +.decision-board { + display: grid; + grid-template-columns: 1.05fr 1.35fr 1fr 1fr; + border: 1px solid var(--line-strong); + background: var(--surface); +} + +.decision-board article { + position: relative; + min-width: 0; + padding: 9px 10px 7px; + border-left: 1px solid var(--line); +} + +.decision-board article:first-child { + border-left: 0; +} + +.decision-board article::after { + position: absolute; + right: 0; + bottom: 0; + left: 0; + height: 2px; + background: #c5d0d3; + content: ""; +} + +.decision-board article:nth-child(2)::after { + background: var(--amber); +} + +.decision-board article span { + color: #7a898e; + font-family: var(--mono); + font-size: 7px; + font-weight: 700; + letter-spacing: 0.09em; +} + +.decision-board article p { + display: -webkit-box; + margin: 5px 0 0; + overflow: hidden; + font-size: 9px; + font-weight: 650; + line-height: 1.35; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; +} + +.confidence { + display: grid; + height: 8px; + grid-template-columns: minmax(30px, 1fr) auto; + align-items: center; + gap: 6px; + margin-top: 5px; +} + +.confidence::before { + grid-column: 1; + grid-row: 1; + width: 100%; + height: 2px; + background: #d9e0e2; + content: ""; +} + +.confidence i { + z-index: 1; + grid-column: 1; + grid-row: 1; + width: 42%; + height: 2px; + background: var(--amber); + transition: width 240ms ease, background 240ms ease; +} + +.confidence small { + color: #80612c; + font-family: var(--mono); + font-size: 7px; +} + +.agent-loop { + position: relative; + display: grid; + grid-template-columns: repeat(6, minmax(0, 1fr)); + align-items: center; + gap: 4px; + padding: 5px 0; +} + +.agent-loop::before { + position: absolute; + z-index: 0; + top: 50%; + right: 4%; + left: 4%; + height: 1px; + background: #c4cfd2; + content: ""; +} + +.agent-loop button { + position: relative; + z-index: 1; + display: grid; + min-width: 0; + height: 33px; + grid-template-columns: 20px minmax(0, auto); + grid-template-rows: 15px 11px; + align-items: center; + justify-content: center; + column-gap: 5px; + padding: 2px 7px; + border: 1px solid var(--line); + background: #f8fafa; + cursor: pointer; +} + +.agent-loop button i { + display: grid; + grid-row: 1 / 3; + width: 18px; + height: 18px; + place-items: center; + border: 1px solid var(--line-strong); + border-radius: 50%; + background: var(--surface); + color: #718087; + font-family: var(--mono); + font-size: 6px; + font-style: normal; +} + +.agent-loop button span { + overflow: hidden; + font-size: 8px; + font-weight: 700; + text-align: left; + text-overflow: ellipsis; + white-space: nowrap; +} + +.agent-loop button small { + overflow: hidden; + color: #87949a; + font-family: var(--mono); + font-size: 6px; + text-align: left; + text-overflow: ellipsis; + white-space: nowrap; +} + +.agent-loop button[data-status="active"] { + border-color: #d6a853; + background: var(--amber-pale); + box-shadow: inset 0 -2px 0 var(--amber); +} + +.agent-loop button[data-status="active"] i { + border-color: var(--amber); + background: var(--amber); + color: white; + animation: agent-pulse 850ms ease-in-out infinite alternate; +} + +.agent-loop button[data-status="passed"] { + border-color: #92c8b1; + background: var(--green-pale); +} + +.agent-loop button[data-status="passed"] i { + border-color: var(--green); + background: var(--green); + color: white; +} + +.agent-loop button[data-status="failed"] { + border-color: #dda09b; + background: var(--red-pale); +} + +.agent-loop button[data-status="failed"] i { + border-color: var(--red); + background: var(--red); + color: white; +} + +@keyframes agent-pulse { + from { box-shadow: 0 0 0 0 rgb(182 109 0 / 10%); } + to { box-shadow: 0 0 0 4px rgb(182 109 0 / 15%); } +} + +.agent-workbench { + display: grid; + grid-template-columns: minmax(250px, 0.78fr) minmax(390px, 1.22fr); + min-height: 0; + overflow: hidden; + border: 1px solid var(--line-strong); + background: var(--surface); +} + +.timeline-pane, +.inspector-pane { + display: grid; + min-width: 0; + min-height: 0; + grid-template-rows: 39px minmax(0, 1fr); +} + +.timeline-pane { + border-right: 1px solid var(--line); + background: #f8fafa; +} + +.pane-header, +.inspector-header { + min-width: 0; + justify-content: space-between; + padding: 0 9px; + border-bottom: 1px solid var(--line); + background: var(--surface-2); +} + +.pane-header h2, +.inspector-header h2 { + margin: 2px 0 0; + font-size: 10px; + line-height: 1; +} + +.pane-header > span { + color: #6e7d83; + font-family: var(--mono); + font-size: 7px; +} + +.agent-timeline { + min-height: 0; + margin: 0; + padding: 8px 7px 16px 9px; + overflow: auto; + list-style: none; +} + +.timeline-event { + position: relative; + padding: 0 0 8px 23px; + animation: event-enter 220ms ease-out both; +} + +.timeline-event:not(:last-child)::before { + position: absolute; + top: 18px; + bottom: -1px; + left: 8px; + width: 1px; + background: #c8d3d6; + content: ""; +} + +.timeline-event::after { + position: absolute; + top: 9px; + left: 5px; + width: 7px; + height: 7px; + border: 1px solid #8d9da3; + border-radius: 50%; + background: #f8fafa; + content: ""; +} + +.timeline-event.is-active::after { + border-color: var(--amber); + background: var(--amber); + box-shadow: 0 0 0 3px rgb(182 109 0 / 12%); +} + +.timeline-event.is-passed::after { + border-color: var(--green); + background: var(--green); +} + +.timeline-event.is-failed::after { + border-color: var(--red); + background: var(--red); +} + +@keyframes event-enter { + from { transform: translateY(5px); opacity: 0; } + to { transform: translateY(0); opacity: 1; } +} + +.timeline-event button { + display: grid; + width: 100%; + min-width: 0; + grid-template-columns: auto minmax(0, 1fr) auto; + gap: 4px 7px; + padding: 6px 7px; + border: 1px solid var(--line); + background: var(--surface); + cursor: pointer; + text-align: left; +} + +.timeline-event button:hover, +.timeline-event button.is-selected { + border-color: #7fb8bd; + box-shadow: inset 2px 0 0 var(--cyan); +} + +.event-id, +.event-tool, +.event-time { + font-family: var(--mono); + font-size: 7px; +} + +.event-id { + color: var(--cyan-dark); + font-weight: 700; +} + +.event-time { + color: #8b989d; + text-align: right; +} + +.event-title { + grid-column: 1 / 4; + overflow: hidden; + font-size: 9px; + font-weight: 700; + text-overflow: ellipsis; + white-space: nowrap; +} + +.event-summary { + grid-column: 1 / 3; + display: -webkit-box; + overflow: hidden; + color: #68777d; + font-size: 8px; + line-height: 1.35; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; +} + +.event-tool { + align-self: end; + padding: 2px 4px; + border: 1px solid var(--line); + color: #66767c; + white-space: nowrap; +} + +.inspector-pane { + position: relative; +} + +.inspector-header { + gap: 8px; +} + +.inspector-header > div:first-child { + min-width: 0; +} + +.inspector-tabs { + display: flex; + height: 100%; +} + +.inspector-tabs button { + min-width: 45px; + padding: 0 8px; + border: 0; + border-left: 1px solid var(--line); + background: transparent; + color: #6e7d82; + font-size: 8px; + cursor: pointer; +} + +.inspector-tabs button.is-active { + background: var(--surface); + color: var(--cyan-dark); + box-shadow: inset 0 -2px 0 var(--cyan); +} + +.inspector-view { + min-width: 0; + min-height: 0; + overflow: auto; +} + +.inspector-view[hidden] { + display: none; +} + +.decision-statement { + padding: 11px 12px 10px; + border-bottom: 1px solid var(--line); +} + +.decision-statement > span { + color: var(--cyan-dark); + font-family: var(--mono); + font-size: 7px; font-weight: 700; letter-spacing: 0.08em; } -.stage-detail h3 { - margin: 5px 0; +.decision-statement h3 { + margin: 5px 0 4px; font-size: 12px; line-height: 1.25; } -.stage-detail p { +.decision-statement p { margin: 0; - color: #68777c; - font-size: 10px; + color: #5e6f75; + font-size: 9px; + line-height: 1.45; +} + +.action-facts { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + margin: 0; +} + +.action-facts div { + min-width: 0; + min-height: 67px; + padding: 9px 10px; + border-right: 1px solid var(--line); + border-bottom: 1px solid var(--line); +} + +.action-facts div:nth-child(2n) { + border-right: 0; +} + +.action-facts dt { + color: #7a898f; + font-family: var(--mono); + font-size: 7px; +} + +.action-facts dd { + display: -webkit-box; + margin: 5px 0 0; + overflow: hidden; + font-size: 9px; + line-height: 1.45; + -webkit-box-orient: vertical; + -webkit-line-clamp: 3; +} + +.patch-meta, +.tool-call-heading { + display: flex; + height: 44px; + align-items: center; + justify-content: space-between; + padding: 0 11px; + border-bottom: 1px solid var(--line); + background: #f8fafa; +} + +.patch-meta div, +.tool-call-heading div { + display: grid; + min-width: 0; + gap: 3px; +} + +.patch-meta div span, +.tool-call-heading div span { + color: #829096; + font-family: var(--mono); + font-size: 7px; +} + +.patch-meta div strong, +.tool-call-heading div strong { + overflow: hidden; + font-family: var(--mono); + font-size: 8px; + text-overflow: ellipsis; + white-space: nowrap; +} + +.patch-score, +#toolStatus { + padding: 3px 5px; + border: 1px solid var(--line-strong); + color: #65757b; + font-family: var(--mono); + font-size: 7px; +} + +.code-diff { + min-height: calc(100% - 44px); + overflow: auto; + padding: 7px 0 16px; + font-family: var(--mono); + font-size: 8px; + line-height: 1.55; +} + +.empty-change { + display: grid; + min-height: 150px; + place-content: center; + padding: 20px; + text-align: center; +} + +.empty-change span { + color: #87959a; + font-family: var(--mono); + font-size: 8px; + letter-spacing: 0.12em; +} + +.empty-change p { + max-width: 280px; + margin: 8px 0 0; + color: #6d7b80; + font-family: var(--sans); + font-size: 9px; line-height: 1.5; } -.run-log { +.diff-line { + display: grid; + min-width: max-content; + grid-template-columns: 36px auto; + padding-right: 10px; +} + +.diff-line span { + padding-right: 8px; + border-right: 1px solid #e7ecee; + color: #9ca7ab; + text-align: right; + user-select: none; +} + +.diff-line code { + padding-left: 9px; + white-space: pre; +} + +.diff-line.is-remove { + background: var(--red-pale); + color: #91342e; +} + +.diff-line.is-add { + background: var(--green-pale); + color: #126744; +} + +.tool-output { + min-height: calc(100% - 44px); margin: 0; overflow: auto; - padding: 10px 11px 16px; - background: #11191d; - color: #d4e2e5; + padding: 11px 12px 18px; + background: var(--night); + color: #b9c8cc; font-family: var(--mono); - font-size: 9px; + font-size: 8px; line-height: 1.65; white-space: pre-wrap; } -.log-muted { color: #72858d; } -.log-active { color: #ffc86e; } -.log-pass { color: #6ce0ad; } -.log-fail { color: #ff8c84; } +.tool-output span { + color: #6f838b; +} -.hwpod-panel { +.hwpod-stage { + position: relative; display: grid; - grid-template-rows: 50px minmax(180px, 1fr) auto auto; - padding: 0 14px 12px; + grid-template-rows: 45px 31px minmax(150px, 1fr) auto 45px; + padding: 0 10px 9px; overflow: hidden; - border-left: 1px solid #27343a; + border-left: 1px solid #29383e; background: var(--night); - color: #eef5f6; + color: #edf4f5; } -.hwpod-heading .eyebrow { - color: #6f858d; +.hwpod-header { + justify-content: space-between; } -.hwpod-heading h2 { - color: #f3f7f7; +.hwpod-header .section-kicker { + color: #6d838b; } -.live-indicator { +.hwpod-header h2 { + margin: 3px 0 0; + font-size: 13px; + line-height: 1; +} + +.hardware-live { display: flex; align-items: center; - gap: 6px; - color: #6ed2ae; + gap: 5px; + color: #63d9a8; font-family: var(--mono); - font-size: 9px; + font-size: 7px; } -.live-indicator i { - background: #40c58e; - box-shadow: 0 0 0 3px rgb(64 197 142 / 12%); +.hardware-live i { + background: #43c990; + box-shadow: 0 0 0 3px rgb(67 201 144 / 12%); +} + +.agent-link { + position: relative; + display: grid; + grid-template-columns: auto minmax(0, 1fr) auto; + align-items: center; + gap: 7px; + padding: 0 8px; + border: 1px solid var(--night-line); + border-bottom: 0; + background: #132027; + color: #789099; + font-family: var(--mono); + font-size: 7px; +} + +.agent-link::before { + position: absolute; + top: 50%; + left: -19px; + width: 18px; + height: 1px; + background: #385159; + content: ""; +} + +.agent-link strong { + overflow: hidden; + color: #b7c7cc; + font-size: 7px; + text-overflow: ellipsis; + white-space: nowrap; +} + +.agent-link i { + color: #5acdd2; + font-style: normal; +} + +.app-shell[data-hardware-target]:not([data-hardware-target="none"]) .agent-link { + border-color: #39757a; + background: #10282d; + color: #69dce0; + box-shadow: inset 3px 0 0 var(--cyan); +} + +.app-shell[data-hardware-target]:not([data-hardware-target="none"]) .agent-link::before { + background: var(--cyan); + box-shadow: 0 0 7px #35bec5; } .bench-view { @@ -801,15 +1264,7 @@ input:focus-visible, margin: 0; overflow: hidden; border: 1px solid var(--night-line); - background: #111a1f; -} - -.bench-view::after { - position: absolute; - inset: 0; - border: 1px solid rgb(255 255 255 / 4%); - content: ""; - pointer-events: none; + background: #10191e; } .bench-view img { @@ -818,118 +1273,140 @@ input:focus-visible, height: 100%; object-fit: cover; object-position: center; - filter: saturate(0.88) contrast(1.04) brightness(0.78); + filter: saturate(0.82) contrast(1.05) brightness(0.72); + transition: filter 220ms ease; } -.hardware-label, -.bench-state { +.app-shell[data-hardware-target]:not([data-hardware-target="none"]) .bench-view img { + filter: saturate(0.92) contrast(1.06) brightness(0.82); +} + +.hardware-tag, +.bench-verdict { position: absolute; z-index: 1; display: inline-flex; align-items: center; - gap: 5px; - padding: 4px 6px; - border: 1px solid rgb(153 210 215 / 40%); - background: rgb(11 20 24 / 78%); - color: #d3e9eb; + gap: 4px; + padding: 3px 5px; + border: 1px solid rgb(126 183 189 / 42%); + background: rgb(10 19 23 / 82%); + color: #cfe5e7; font-family: var(--mono); - font-size: 8px; - backdrop-filter: blur(3px); + font-size: 6px; } -.hardware-label i { +.hardware-tag i { width: 4px; height: 4px; border-radius: 50%; - background: #5bd6d9; + background: #5ad2d6; } -.hardware-label--board { top: 16%; left: 4%; } -.hardware-label--probe { top: 40%; left: 43%; } -.hardware-label--ioprobe { right: 4%; bottom: 18%; } +.hardware-tag--board { top: 13%; left: 4%; } +.hardware-tag--probe { top: 40%; left: 46%; } +.hardware-tag--uart { right: 4%; top: 18%; } +.hardware-tag--ioprobe { right: 4%; bottom: 19%; } -.bench-state { - right: 8px; - bottom: 8px; - border-color: rgb(255 198 103 / 48%); - color: #ffd089; +.app-shell[data-hardware-target="board"] [data-device="board"], +.app-shell[data-hardware-target="probe"] [data-device="probe"], +.app-shell[data-hardware-target="uart"] [data-device="uart"], +.app-shell[data-hardware-target="ioprobe"] [data-device="ioprobe"] { + border-color: #55e4e7; + background: #07383d; + color: white; + box-shadow: 0 0 0 3px rgb(28 198 204 / 18%), 0 0 12px rgb(41 216 220 / 32%); + animation: hardware-target 700ms ease-in-out infinite alternate; } -.app-shell[data-run-state="passed"] .bench-state { - border-color: rgb(91 214 163 / 50%); - color: #74dfb3; +@keyframes hardware-target { + from { transform: translateY(0); } + to { transform: translateY(-2px); } } -.app-shell[data-run-state="failed"] .bench-state { - border-color: rgb(255 122 112 / 54%); - color: #ff8c84; +.bench-verdict { + right: 6px; + bottom: 6px; + border-color: rgb(224 180 100 / 52%); + color: #f1c675; } -.measurement-panel, -.uart-panel { +.app-shell[data-run-state="passed"] .bench-verdict { + border-color: rgb(90 215 164 / 56%); + color: #6cdeb0; +} + +.app-shell[data-run-state="failed"] .bench-verdict { + border-color: rgb(255 124 113 / 58%); + color: #ff8a82; +} + +.scope-panel { + padding: 8px 9px 7px; border-right: 1px solid var(--night-line); border-bottom: 1px solid var(--night-line); border-left: 1px solid var(--night-line); background: var(--night-2); } -.measurement-panel { - padding: 9px 10px 8px; -} - -.scope-header { +.scope-panel header { justify-content: space-between; font-family: var(--mono); - font-size: 8px; + font-size: 7px; } -.scope-header span { color: #7d929a; } -.scope-header strong { color: #85e3e6; font-weight: 600; } +.scope-panel header span { + color: #6f858d; +} + +.scope-panel header strong { + color: #76dadd; + font-size: 7px; + font-weight: 600; +} #scopeCanvas { display: block; width: 100%; - height: 65px; - margin: 7px 0 8px; - border: 1px solid #304148; - background: #0c1418; + height: 55px; + margin: 6px 0 7px; + border: 1px solid #2d4047; + background: #0b1418; } -.measurement-grid { +.measurements { display: grid; - grid-template-columns: repeat(3, 1fr); - gap: 8px; + grid-template-columns: repeat(3, minmax(0, 1fr)); margin: 0; } -.measurement-grid div { +.measurements div { min-width: 0; padding-left: 8px; - border-left: 1px solid #34454c; + border-left: 1px solid #33454c; } -.measurement-grid div:first-child { +.measurements div:first-child { padding-left: 0; border-left: 0; } -.measurement-grid dt { - color: #71878f; - font-size: 8px; +.measurements dt { + color: #70868e; + font-size: 7px; } -.measurement-grid dd { +.measurements dd { margin: 3px 0 0; - color: #eef6f7; font-family: var(--mono); - font-size: 14px; + font-size: 12px; font-weight: 700; white-space: nowrap; } -.measurement-grid dd small { - color: #748a92; - font-size: 8px; +.measurements dd small { + color: #728890; + font-size: 6px; font-weight: 400; } @@ -943,45 +1420,48 @@ input:focus-visible, color: #ff8178; } -.uart-panel { - min-height: 69px; - padding: 8px 10px; +.device-facts { + display: grid; + grid-template-columns: 1fr 1fr; + border-right: 1px solid var(--night-line); + border-bottom: 1px solid var(--night-line); + border-left: 1px solid var(--night-line); + background: #111c21; } -.uart-panel header { - justify-content: space-between; - color: #72868e; +.device-facts div { + display: grid; + align-content: center; + gap: 3px; + min-width: 0; + padding: 0 8px; + border-left: 1px solid var(--night-line); +} + +.device-facts div:first-child { + border-left: 0; +} + +.device-facts span { + color: #667c84; font-family: var(--mono); - font-size: 8px; + font-size: 6px; } -.uart-panel header i { - width: 6px; - height: 6px; - border-radius: 50%; - background: #596970; -} - -.uart-panel header i.is-active { - background: #58d2a1; - box-shadow: 0 0 0 3px rgb(88 210 161 / 10%); -} - -.uart-panel pre { - margin: 6px 0 0; +.device-facts strong { overflow: hidden; - color: #a9b9be; + color: #a9babf; font-family: var(--mono); - font-size: 8px; - line-height: 1.45; - white-space: pre-wrap; + font-size: 7px; + text-overflow: ellipsis; + white-space: nowrap; } .evidence-band { display: grid; - grid-template-rows: 48px minmax(0, 1fr); + grid-template-rows: 43px minmax(0, 1fr); min-width: 0; - padding: 0 18px 10px; + padding: 0 16px 8px; overflow: hidden; border-top: 1px solid var(--line-strong); background: #f8fafa; @@ -989,11 +1469,17 @@ input:focus-visible, .evidence-heading { min-width: 0; - gap: 18px; + gap: 16px; } .evidence-heading > div { - flex: 0 0 180px; + flex: 0 0 185px; +} + +.evidence-heading h2 { + margin: 3px 0 0; + font-size: 13px; + line-height: 1; } .evidence-heading p { @@ -1001,19 +1487,19 @@ input:focus-visible, flex: 1 1 auto; overflow: hidden; margin: 0; - color: #67767b; - font-size: 10px; - white-space: nowrap; + color: #66767c; + font-size: 8px; text-overflow: ellipsis; + white-space: nowrap; } #saveCaseButton { flex: 0 0 auto; - height: 30px; - padding: 0 12px; + height: 28px; + padding: 0 11px; border-color: var(--cyan-dark); color: var(--cyan-dark); - font-size: 10px; + font-size: 8px; font-weight: 700; } @@ -1024,7 +1510,7 @@ input:focus-visible, .evidence-chain { display: grid; - grid-template-columns: repeat(7, minmax(90px, 1fr)); + grid-template-columns: repeat(8, minmax(80px, 1fr)); min-width: 0; margin: 0; padding: 0; @@ -1039,11 +1525,11 @@ input:focus-visible, .evidence-chain li:not(:last-child)::after { position: absolute; z-index: 0; - top: 16px; + top: 13px; right: -50%; width: 100%; height: 1px; - background: #c7d1d4; + background: #c4d0d3; content: ""; } @@ -1053,12 +1539,12 @@ input:focus-visible, display: grid; width: 100%; min-width: 0; - grid-template-columns: 12px minmax(0, auto); - grid-template-rows: 17px 14px; + grid-template-columns: 11px minmax(0, auto); + grid-template-rows: 14px 12px; align-items: center; justify-content: center; - column-gap: 7px; - padding: 4px 5px; + column-gap: 6px; + padding: 2px 5px; border: 1px solid transparent; background: transparent; cursor: pointer; @@ -1072,9 +1558,9 @@ input:focus-visible, .evidence-chain button i { grid-row: 1 / 3; - width: 10px; - height: 10px; - border: 2px solid #aab6ba; + width: 9px; + height: 9px; + border: 2px solid #a8b5b9; border-radius: 50%; background: #f8fafa; } @@ -1083,25 +1569,25 @@ input:focus-visible, .evidence-chain button small { min-width: 0; overflow: hidden; - white-space: nowrap; text-overflow: ellipsis; + white-space: nowrap; } .evidence-chain button span { - font-size: 10px; + font-size: 8px; font-weight: 700; } .evidence-chain button small { - color: #879399; + color: #849197; font-family: var(--mono); - font-size: 8px; + font-size: 6px; } .evidence-chain button[data-status="active"] i { border-color: var(--amber); - background: var(--amber-pale); - box-shadow: 0 0 0 3px rgb(179 106 0 / 10%); + background: var(--amber); + box-shadow: inset 0 0 0 2px white; } .evidence-chain button[data-status="passed"] i { @@ -1117,25 +1603,25 @@ input:focus-visible, box-shadow: inset 0 0 0 2px white; } -.evidence-final button { +.verdict-evidence button { border-left: 1px solid var(--line-strong); } .toast { position: fixed; - z-index: 20; - right: 18px; - bottom: 154px; - max-width: min(360px, calc(100vw - 36px)); - padding: 10px 12px; - transform: translateY(8px); - border: 1px solid #65b894; + z-index: 30; + right: 16px; + bottom: 133px; + max-width: min(360px, calc(100vw - 32px)); + padding: 9px 11px; + transform: translateY(7px); + border: 1px solid #60b58f; background: #10231c; - color: #dff8ed; - font-size: 11px; + color: #dcf6e9; + font-size: 9px; opacity: 0; pointer-events: none; - transition: opacity 160ms ease, transform 160ms ease; + transition: opacity 150ms ease, transform 150ms ease; } .toast.is-visible { @@ -1145,136 +1631,164 @@ input:focus-visible, @media (max-width: 1180px) and (min-width: 721px) { .topbar { - grid-template-columns: auto minmax(180px, 1fr) auto; - gap: 12px; - padding: 0 10px; + grid-template-columns: auto minmax(170px, 1fr) auto; + gap: 10px; + padding: 0 9px; } - .brand-divider, - .brand-tagline, - .system-status .status-pill:first-child { + .brand-rule, + .brand-caption, + .top-status span:first-child { display: none; } .workspace { - grid-template-columns: 72px minmax(0, 1fr) 320px; + grid-template-columns: 64px minmax(0, 1fr) 310px; } - .task-panel { + .context-rail { align-items: center; - padding: 14px 7px 10px; + padding: 11px 6px 8px; } - .task-panel-heading { + .project-heading { display: grid; width: 100%; - min-height: 48px; place-items: center; - padding: 0 0 10px; + padding: 0 0 9px; } - .task-panel-heading .eyebrow, - .project-copy, - .task-text, - .context-facts { + .project-heading .section-kicker, + .project-name, + .active-task > div, + .brief-block, + .bound-context, + .control-boundary { display: none; } - .project-code { + .project-monogram { display: grid; - width: 34px; - height: 34px; + width: 32px; + height: 32px; place-items: center; border: 1px solid var(--ink); font-family: var(--mono); - font-size: 11px; + font-size: 9px; font-weight: 700; } - .task-list { + .active-task { + display: grid; width: 100%; - } - - .task-item { - min-height: 46px; - justify-content: center; + grid-template-columns: 1fr; + place-items: center; padding: 8px 5px; } - .task-marker { - flex-basis: 29px; - height: 29px; + .task-index { + width: 28px; } - .agent-panel { - padding-right: 10px; - padding-left: 10px; + .agent-stage { + padding-right: 8px; + padding-left: 8px; } - .failure-toggle > span:last-child { + .agent-identity h1 small, + .session-identity, + .failure-switch > span:last-child { display: none; } - .command-bar { - grid-template-columns: auto minmax(100px, 1fr) auto 32px auto; - gap: 6px; + .mission-console { + grid-template-columns: minmax(140px, 1fr) auto 30px auto; + gap: 5px; + } + + .reset-button { + width: 30px; + } + + .run-button { + padding: 0 8px; + } + + .decision-board article { padding-right: 6px; padding-left: 6px; } - .command-symbol, - .icon-button { - width: 32px; + .decision-board article p { + font-size: 8px; } - .run-button { - padding: 0 9px; + .agent-loop button { + grid-template-columns: 18px minmax(0, auto); + padding-right: 4px; + padding-left: 4px; } - .work-surface { - grid-template-columns: minmax(0, 1.35fr) minmax(190px, 0.65fr); + .agent-loop button i { + width: 16px; + height: 16px; } - .hwpod-panel { + .agent-workbench { + grid-template-columns: minmax(210px, 0.8fr) minmax(270px, 1.2fr); + } + + .action-facts div { + min-height: 61px; + padding: 7px 8px; + } + + .hwpod-stage { + padding-right: 7px; + padding-left: 7px; + } + + .hardware-tag--probe { + left: 39%; + } + + .measurements dd { + font-size: 11px; + } + + .evidence-band { padding-right: 9px; padding-left: 9px; } - .hardware-label--probe { - left: 36%; - } - - .measurement-grid { - gap: 4px; - } - - .measurement-grid dd { - font-size: 12px; - } - - .evidence-band { - padding-right: 10px; - padding-left: 10px; - } - .evidence-heading > div { - flex-basis: 155px; + flex-basis: 165px; } } @media (max-width: 900px) and (min-width: 721px) { - .system-status .status-pill:nth-child(2), - .target-context .context-label, - .file-tabs .file-tab:nth-child(2) { + .top-target span, + .top-status span:nth-child(2), + .agent-state { display: none; } - .trace-pane { - grid-template-rows: 36px auto minmax(0, 1fr); + .agent-loop button span, + .agent-loop button small { + font-size: 0; } - .stage-detail p { - display: none; + .agent-loop button { + grid-template-columns: 18px; + } + + .decision-statement p, + .event-summary { + -webkit-line-clamp: 1; + } + + .action-facts dd { + -webkit-line-clamp: 2; } } @@ -1293,19 +1807,19 @@ input:focus-visible, .topbar { position: sticky; - z-index: 10; + z-index: 20; top: 0; display: flex; - min-height: 52px; + min-height: 48px; justify-content: space-between; gap: 8px; - padding: 0 10px; + padding: 0 9px; } - .brand-divider, - .brand-tagline, - .target-context, - .system-status .status-pill:not(:last-child) { + .brand-rule, + .brand-caption, + .top-target, + .top-status span:not(:last-child) { display: none; } @@ -1315,119 +1829,158 @@ input:focus-visible, overflow: visible; } - .task-panel { + .context-rail { display: grid; - grid-template-columns: 122px minmax(0, 1fr); - gap: 10px; - padding: 14px 10px 10px; + grid-template-columns: 112px minmax(0, 1fr); + gap: 9px; + padding: 12px 9px 10px; border-right: 0; border-bottom: 1px solid var(--line); } - .task-panel-heading { - min-height: 0; - padding: 0 8px; - border-bottom: 0; + .project-heading { + padding: 0 8px 0 0; border-right: 1px solid var(--line); + border-bottom: 0; } - .project-copy h1 { - font-size: 18px; + .project-name h2 { + font-size: 17px; } - .task-list { - padding: 0; + .active-task { + margin: 0; + padding: 8px 7px; } - .task-item { - display: none; - min-height: 50px; - } - - .task-item--active { - display: flex; - } - - .context-facts { + .brief-block, + .bound-context, + .control-boundary { display: none; } - .agent-panel { + .agent-stage { display: block; - padding: 0 10px 12px; + padding: 0 9px 12px; } - .panel-title-row { - min-height: 50px; + .agent-presence { + min-height: 52px; } - .command-bar { - grid-template-columns: auto minmax(0, 1fr) 34px; - gap: 7px; - padding: 7px; + .session-identity { + display: none; } - .command-bar p { + .agent-state { + margin-left: auto; + } + + .mission-console { + grid-template-columns: minmax(0, 1fr) 32px; + gap: 6px; + padding: 7px 0; + } + + .mission-command { + height: 46px; + } + + .mission-command div strong { white-space: normal; - line-height: 1.35; + line-height: 1.25; } - .failure-toggle { - grid-column: 1 / 3; + .failure-switch { + grid-column: 1; grid-row: 2; - padding-left: 2px; + padding-left: 3px; } - .icon-button { - grid-column: 3; + .reset-button { + grid-column: 2; grid-row: 2; } .run-button { - grid-column: 3; + grid-column: 2; grid-row: 1; - width: 34px; + width: 32px; justify-content: center; padding: 0; font-size: 0; } .run-button span { - font-size: 11px; + font-size: 9px; } - .stage-strip { + .decision-board { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .decision-board article { + min-height: 79px; + border-bottom: 1px solid var(--line); + } + + .decision-board article:nth-child(3) { + border-left: 0; + } + + .agent-loop { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 5px; + padding: 8px 0; } - .work-surface { + .agent-loop::before { + display: none; + } + + .agent-workbench { display: block; overflow: visible; } - .editor-pane, - .trace-pane { - min-height: 260px; + .timeline-pane, + .inspector-pane { + min-height: 320px; } - .editor-pane { + .timeline-pane { border-right: 0; border-bottom: 1px solid var(--line); } - .trace-pane { - min-height: 240px; + .agent-timeline { + max-height: 390px; } - .hwpod-panel { + .inspector-pane { + min-height: 350px; + } + + .action-facts div { + min-height: 82px; + } + + .hwpod-stage { display: block; - padding: 0 10px 12px; + padding: 0 9px 11px; border-left: 0; } - .hwpod-heading { - min-height: 52px; + .hwpod-header { + min-height: 50px; + } + + .agent-link { + height: 32px; + } + + .agent-link::before { + display: none; } .bench-view { @@ -1435,16 +1988,16 @@ input:focus-visible, } #scopeCanvas { - height: 90px; + height: 82px; } - .uart-panel { - min-height: 76px; + .device-facts { + height: 52px; } .evidence-band { display: block; - padding: 12px 10px 18px; + padding: 12px 9px 18px; overflow: visible; } @@ -1472,25 +2025,23 @@ input:focus-visible, .evidence-chain { display: grid; grid-template-columns: 1fr; - gap: 0; margin-top: 10px; } .evidence-chain li:not(:last-child)::after { - top: 32px; + top: 29px; right: auto; - bottom: -8px; - left: 18px; + left: 16px; width: 1px; - height: 18px; + height: 17px; } .evidence-chain button { - min-height: 48px; - grid-template-columns: 18px 1fr auto; + min-height: 43px; + grid-template-columns: 16px 1fr auto; grid-template-rows: 1fr; justify-content: stretch; - padding: 7px 12px; + padding: 6px 10px; text-align: left; } @@ -1502,14 +2053,14 @@ input:focus-visible, text-align: right; } - .evidence-final button { + .verdict-evidence button { border-top: 1px solid var(--line-strong); border-left: 0; } .toast { - right: 10px; - bottom: 10px; + right: 9px; + bottom: 9px; } }