Spaces:
Sleeping
Sleeping
feat: O2b risk queue live binding + SW v2
Browse files
bot/tests/scientific/test_phase33_ops_stream_scientific.py
CHANGED
|
@@ -49,6 +49,19 @@ class TestOpsSnapshotReusesStudioAndAddsCoStreamer:
|
|
| 49 |
assert routes.build_ops_snapshot(None)["channel"] == "default"
|
| 50 |
|
| 51 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
class TestOpsStreamRouteWired:
|
| 53 |
"""Prove the SSE endpoint is registered and contractually declared."""
|
| 54 |
|
|
|
|
| 49 |
assert routes.build_ops_snapshot(None)["channel"] == "default"
|
| 50 |
|
| 51 |
|
| 52 |
+
class TestOpsSnapshotBindingContract:
|
| 53 |
+
"""O2b: the snapshot sections must match what the panel renderers consume,
|
| 54 |
+
so the SSE can feed the panels (kills load-once) without a shape mismatch."""
|
| 55 |
+
|
| 56 |
+
def test_queue_section_matches_action_queue_endpoint_shape(self):
|
| 57 |
+
# renderActionQueuePayload reads payload.items / payload.summary; the
|
| 58 |
+
# snapshot queue must carry them (it is the /api/action-queue payload).
|
| 59 |
+
queue = routes.build_ops_snapshot("default")["queue"]
|
| 60 |
+
assert "items" in queue, "queue.items missing — binding would render nothing"
|
| 61 |
+
assert "summary" in queue, "queue.summary missing"
|
| 62 |
+
assert queue["ok"] is True
|
| 63 |
+
|
| 64 |
+
|
| 65 |
class TestOpsStreamRouteWired:
|
| 66 |
"""Prove the SSE endpoint is registered and contractually declared."""
|
| 67 |
|
dashboard/features/action-queue/controller.js
CHANGED
|
@@ -199,6 +199,13 @@ export function createActionQueueController({
|
|
| 199 |
bindActionQueueEvents,
|
| 200 |
refreshActionQueue,
|
| 201 |
scheduleActionQueuePolling,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 202 |
setSelectedChannel(channelId) {
|
| 203 |
selectedChannel =
|
| 204 |
String(channelId || "")
|
|
|
|
| 199 |
bindActionQueueEvents,
|
| 200 |
refreshActionQueue,
|
| 201 |
scheduleActionQueuePolling,
|
| 202 |
+
// O2b: render the queue from the live ops snapshot (/api/ops/stream) instead
|
| 203 |
+
// of a fetch — snap.queue is byte-for-byte the /api/action-queue shape.
|
| 204 |
+
renderQueueFromSnapshot(queueSection) {
|
| 205 |
+
if (aqEls && queueSection) {
|
| 206 |
+
renderActionQueuePayload(queueSection, aqEls, decideQueueItem);
|
| 207 |
+
}
|
| 208 |
+
},
|
| 209 |
setSelectedChannel(channelId) {
|
| 210 |
selectedChannel =
|
| 211 |
String(channelId || "")
|
dashboard/main.js
CHANGED
|
@@ -44,6 +44,9 @@ function initFocusMode() {
|
|
| 44 |
let opsMood = { count: 0 };
|
| 45 |
let opsMascotApi = null;
|
| 46 |
let opsPresence = null;
|
|
|
|
|
|
|
|
|
|
| 47 |
|
| 48 |
// action type -> transient spark kind on the clicked card.
|
| 49 |
const SPARK_FOR = {
|
|
@@ -98,6 +101,7 @@ function connectOpsStream() {
|
|
| 98 |
opsMood = deriveOpsMood(snap);
|
| 99 |
if (opsMascotApi) opsMascotApi.render();
|
| 100 |
if (opsPresence) opsPresence.render(snap);
|
|
|
|
| 101 |
document.getElementById("bytePresence")?.classList.add("has-data");
|
| 102 |
} catch (_e) {
|
| 103 |
/* ignore a malformed frame; the next one will arrive */
|
|
@@ -216,6 +220,19 @@ async function bootstrapDashboard() {
|
|
| 216 |
actionQueueController.scheduleActionQueuePolling();
|
| 217 |
clipsController.startPolling();
|
| 218 |
hudController.startPolling();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 219 |
}
|
| 220 |
|
| 221 |
bootstrapDashboard().catch((err) =>
|
|
|
|
| 44 |
let opsMood = { count: 0 };
|
| 45 |
let opsMascotApi = null;
|
| 46 |
let opsPresence = null;
|
| 47 |
+
// O2b: set once the controllers exist; the SSE feeds the panels through it,
|
| 48 |
+
// killing the load-once stale (the O4 debt). Wired in bootstrapDashboard.
|
| 49 |
+
let opsPanelSync = null;
|
| 50 |
|
| 51 |
// action type -> transient spark kind on the clicked card.
|
| 52 |
const SPARK_FOR = {
|
|
|
|
| 101 |
opsMood = deriveOpsMood(snap);
|
| 102 |
if (opsMascotApi) opsMascotApi.render();
|
| 103 |
if (opsPresence) opsPresence.render(snap);
|
| 104 |
+
if (opsPanelSync) opsPanelSync(snap);
|
| 105 |
document.getElementById("bytePresence")?.classList.add("has-data");
|
| 106 |
} catch (_e) {
|
| 107 |
/* ignore a malformed frame; the next one will arrive */
|
|
|
|
| 220 |
actionQueueController.scheduleActionQueuePolling();
|
| 221 |
clipsController.startPolling();
|
| 222 |
hudController.startPolling();
|
| 223 |
+
|
| 224 |
+
// O2b: feed the live ops snapshot into the panels (kills the load-once stale).
|
| 225 |
+
// Incremental + verified: the risk queue first (snap.queue === the
|
| 226 |
+
// /api/action-queue shape, confirmed). More panels follow the same pattern.
|
| 227 |
+
opsPanelSync = (snap) => {
|
| 228 |
+
try {
|
| 229 |
+
if (snap && snap.queue) {
|
| 230 |
+
actionQueueController.renderQueueFromSnapshot(snap.queue);
|
| 231 |
+
}
|
| 232 |
+
} catch (_e) {
|
| 233 |
+
/* a malformed section must never break the others */
|
| 234 |
+
}
|
| 235 |
+
};
|
| 236 |
}
|
| 237 |
|
| 238 |
bootstrapDashboard().catch((err) =>
|
dashboard/sw.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
// dashboard/sw.js — Byte Studio service worker (PWA shell + offline).
|
| 2 |
// Scope "/" (served from /sw.js). Network for the API/SSE; cache-first for the
|
| 3 |
// static shell so the dashboard opens instantly and works offline.
|
| 4 |
-
const CACHE = "byte-studio-
|
| 5 |
const SHELL = [
|
| 6 |
"/studio",
|
| 7 |
"/dashboard/styles/studio.css",
|
|
|
|
| 1 |
// dashboard/sw.js — Byte Studio service worker (PWA shell + offline).
|
| 2 |
// Scope "/" (served from /sw.js). Network for the API/SSE; cache-first for the
|
| 3 |
// static shell so the dashboard opens instantly and works offline.
|
| 4 |
+
const CACHE = "byte-studio-v2";
|
| 5 |
const SHELL = [
|
| 6 |
"/studio",
|
| 7 |
"/dashboard/styles/studio.css",
|
docs/plan/ops-dashboard-redesign.md
CHANGED
|
@@ -229,7 +229,8 @@
|
|
| 229 |
- **Implementação:** reusar herói de saúde + sparklines do studio (`health.js`/`sparkline.js`); bento assimétrico; micro-animações nativas transitórias.
|
| 230 |
- **Critério:**
|
| 231 |
- **O2a (entregue):** [x] **focus mode** (botão + hotkey `F`, ignora digitação; CSS colapsa pra presença + fila; `focus.js` puro, 11 asserts); [x] **empty-state com propósito** (presence bar fica quieta até o 1º snapshot — `has-data`); [x] 60fps/reduced-motion; [x] suíte verde (node 149/150).
|
| 232 |
-
- **O2b:** [x] **View Transitions** na troca de aba (progressive enhancement + reduced-motion); [x] **bento grid 1–2–3** na Operation (
|
|
|
|
| 233 |
- **Validação ao vivo:** [x] render da ops com botão FOCO + presence bar + layout intacto (zero regressão); [ ] colapso do focus mode + bento (render com estado) — pendente.
|
| 234 |
- **Meta-reflexão (O2a):**
|
| 235 |
1. *Claim tentador:* "O2 (wow/bento) pronto". **Verdade:** entregue **focus mode** + polish de empty-state; o **bento** (a manchete da O2) e o **binding dos painéis ao snapshot** **não** — são O2b.
|
|
|
|
| 229 |
- **Implementação:** reusar herói de saúde + sparklines do studio (`health.js`/`sparkline.js`); bento assimétrico; micro-animações nativas transitórias.
|
| 230 |
- **Critério:**
|
| 231 |
- **O2a (entregue):** [x] **focus mode** (botão + hotkey `F`, ignora digitação; CSS colapsa pra presença + fila; `focus.js` puro, 11 asserts); [x] **empty-state com propósito** (presence bar fica quieta até o 1º snapshot — `has-data`); [x] 60fps/reduced-motion; [x] suíte verde (node 149/150).
|
| 232 |
+
- **O2b:** [x] **View Transitions** na troca de aba (progressive enhancement + reduced-motion); [x] **bento grid 1–2–3** na Operation (densidade preservada, render-validado, **deployado ao vivo**); [~] **ligar painéis ao snapshot** (mata o load-once — dívida da O4): **risk queue ligada** (`snap.queue → renderQueueFromSnapshot`; `snap.queue` é byte-a-byte o shape do `/api/action-queue` — verificado + **teste de contrato** guarda contra drift); demais painéis seguem o **mesmo padrão incremental verificado**; [ ] dado faltando/label longo/extremo nos painéis. **Validado ao vivo:** O0→bento deployados + confirmados na Space real; mascote **68px ops / 60px studio** (bug do `className` que comia a classe de tamanho — corrigido + teste de regressão).
|
| 233 |
+
- **Meta-reflexão (O2b binding):** *Claim tentador:* "painéis em tempo real". **Verdade:** só a **risk queue** foi ligada (1 de ~5); as demais seguem o padrão mas **não** estão ligadas ainda. *Método:* cada painel exige **verificar shape `snap.<seção>` == response do endpoint** antes de ligar (senão quebra silencioso) — por isso incremental. *Honesto:* sem token, o SSE dá 403 → o binding só rende ao vivo com o admin token (que o operador tem); a lógica é shape-verificada + contrato. *Dono restante:* demais painéis (observability/autonomy/control-plane) — mesmo padrão.
|
| 234 |
- **Validação ao vivo:** [x] render da ops com botão FOCO + presence bar + layout intacto (zero regressão); [ ] colapso do focus mode + bento (render com estado) — pendente.
|
| 235 |
- **Meta-reflexão (O2a):**
|
| 236 |
1. *Claim tentador:* "O2 (wow/bento) pronto". **Verdade:** entregue **focus mode** + polish de empty-state; o **bento** (a manchete da O2) e o **binding dos painéis ao snapshot** **não** — são O2b.
|