Orchestrata Ultimate › Plans › plan_vizint_phase_6 › sprint-1-resilience

Sprint 1: Resilience Foundation

Source: plans/plan_vizint_phase_6/sprint-1-resilience.md at 8a2cb42ad · last changed 2026-07-27 · first committed 2026-05-26 · 309 lines.

Prose pending.

Plan text

Sprint 1: Resilience Foundation

Duration: Week 1 (40 hours)
Goal: Close all 20 Article IX (Graceful Degradation) items + 10 Article VIII (Observability) items
Target: 30 constitutional items verified


Day 1: Error Boundaries & RPC Queue (8h)

Task 1.1: ErrorBoundary Class (6h) ✅ DONE

File: vizint-ui/lib/error-boundary.js (new)

export class ErrorBoundary {
  constructor(container, onRetry) {
    this._container = container;
    this._onRetry = onRetry;
    this._hasError = false;
  }

  async guard(asyncFn) {
    if (this._hasError) {
      this._renderError();
      return;
    }
    try {
      return await asyncFn();
    } catch (err) {
      this._hasError = true;
      logger('error', 'Gadget error caught by boundary', { error: err.message });
      this._renderError(err);
    }
  }

  _renderError(err) {
    this._container.innerHTML = `
      <div class="error-boundary">
        <div class="error-boundary__icon">⚠️</div>
        <div class="error-boundary__message">Something went wrong</div>
        <button class="error-boundary__retry" data-action="retry">Retry</button>
      </div>
    `;
    this._container.querySelector('[data-action="retry"]')
      .addEventListener('click', () => this._onRetry());
  }

  reset() {
    this._hasError = false;
    this._container.innerHTML = '';
  }
}

CSS in styles/command-center.css:

.error-boundary {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.75rem;
  padding: 2rem;
  color: var(--text-primary);
}
.error-boundary__icon { font-size: 2rem; }
.error-boundary__message { font-size: 0.875rem; }
.error-boundary__retry {
  padding: 0.375rem 0.75rem;
  background: var(--accent);
  color: white;
  border-radius: var(--radius-sm);
}

Integration: Modify app.js _mountGadget() to wrap gadget.mount() in ErrorBoundary.

Tests: tests/unit/error-boundary.test.js — 4 tests (catch error, render error UI, retry callback, reset)

Constitutional Items Closed:

Task 1.2: Bridge RPC Queue Verification (2h) ✅ DONE

File: vizint-ui/bridge/websocket-bridge.js

Verify existing queue behavior:

Test: Add to tests/unit/websocket-bridge.test.js:

Constitutional Items Closed:


Day 2: Auto-Re-subscription & Read-Only Mode (10h)

Task 2.1: Subscription Tracker (6h) ✅ DONE

File: vizint-ui/bridge/websocket-bridge.js

Add _subscriptions Map to track active subscriptions:

this._subscriptions = new Map(); // subId -> { topic, filters, callback }

On subscribe(): store in Map.
On unsubscribe(): remove from Map.
On reconnect: iterate Map and re-subscribe each.

Constitutional Items Closed:

Task 2.2: Read-Only Mode (4h) ✅ DONE

File: vizint-ui/styles/command-center.css

.gadget--readonly input,
.gadget--readonly button,
.gadget--readonly textarea,
.gadget--readonly select {
  opacity: 0.5;
  pointer-events: none;
}
.gadget--readonly::after {
  content: 'Read-only (offline)';
  position: absolute;
  top: 0.25rem;
  right: 0.5rem;
  font-size: 0.625rem;
  color: var(--warning);
  background: var(--bg-secondary);
  padding: 0.125rem 0.375rem;
  border-radius: var(--radius-sm);
}

File: vizint-ui/app.js

In _updateConnectionStatus(): add/remove gadget--readonly class on all chrome wrappers.

Constitutional Items Closed:


Day 3: MetricsCollector Core (8h)

Task 3.1: MetricsCollector Class (8h) ✅ DONE

File: vizint-ui/lib/metrics.js (new)

Track:

API:

export class MetricsCollector {
  recordRender(gadgetId, duration) {}
  recordRpc(method, duration) {}
  recordEvent(topic, latency) {}
  recordConnection(from, to) {}
  recordReconnect(attempts, duration) {}
  recordSubscription(topic, count) {}
  recordNotification(action) {}
  recordMemory(usedMB) {}
  recordFps(fps) {}
  recordError(gadgetId, error) {}
  getSnapshot() {}
  exportJson() {}
}

Tests: tests/unit/metrics.test.js — 10 tests

Constitutional Items Closed:


Day 4: DebugPanel & PerformanceOverlay (12h)

Task 4.1: DebugPanel Gadget (8h) ✅ DONE

File: vizint-ui/gadgets/debug-panel/gadget.js (new)

Toggleable overlay showing:

Design: Collapsible panel, fixed bottom-right, z-index above all.

Toggle: Ctrl+Shift+D or button in header.

Constitutional Items Closed:

Task 4.2: PerformanceOverlay (4h) ✅ DONE

File: vizint-ui/lib/performance-overlay.js (new)

Small floating panel showing:

Design: Semi-transparent, top-right, collapsible.

Constitutional Items Closed:


Day 5: Gadget Health & Quota Handling (8h)

Task 5.1: GadgetHealthIndicator (4h) ✅ DONE

File: vizint-ui/app.js

Add to chrome titlebar:

Constitutional Items Closed:

Task 5.2: localStorage Quota Handler (4h) ✅ DONE

File: vizint-ui/lib/quota-manager.js (new)

export function checkQuota() {
  const used = new Blob(Object.values(localStorage)).size;
  const max = 5 * 1024 * 1024; // 5MB typical
  if (used > max * 0.9) {
    evictOldest();
  }
}

function evictOldest() {
  // Find oldest commandcenter.state.* key by timestamp
  // Remove it
}

Call checkQuota() before every localStorage.setItem().

Constitutional Items Closed:


Sprint 1 Verification

cd vizint-ui
npm test
npx playwright test tests/e2e/
npm run lint
node scripts/compliance-check.js

Expected: 30 constitutional items checked. All tests pass.


Sprint 1 Completion Summary (2026-05-26)

All 9 tasks completed. Implementation differs from original spec in minor ways:

Task File(s) Tests Notes
ErrorBoundary lib/error-boundary.js tests/unit/error-boundary.test.js (7 tests) Added wrapMount() (async), retry with onRetry callback, vizint:error CustomEvent, metrics integration
RPC Queue bridge/websocket-bridge.js tests/unit/websocket-bridge.test.js (10 tests) Verified 100-item limit + FIFO discard; added explicit queue flush + re-subscription tests
Auto-Re-subscription bridge/websocket-bridge.js same _subSpecs Map tracks topic/filters; _resubscribeAll() called on reconnect
Read-Only Mode app.js, styles/command-center.css — data-readonly attribute toggled on #app root; CSS dims grid and disables inputs
MetricsCollector lib/metrics.js tests/unit/metrics.test.js (7 tests) Tracks renders, RPC, events, memory, FPS, errors; getSnapshot() / getReport() API
DebugPanel gadgets/debug-panel/gadget.js — Dev gadget consuming window.__vizintMetricsCollector snapshot; copy-report + clear-errors actions
PerformanceOverlay lib/performance-overlay.js — Floating HUD for FPS + memory; toggleable via stateSync.get('perfOverlay')
GadgetHealthIndicator app.js, styles/command-center.css — Green/yellow/red dot in chrome titlebar; updated via _updateGadgetHealth()
localStorage Quota bridge/state-sync.js tests/unit/state-sync.test.js (6 tests) _evictOldest() removes oldest commandcenter.state.* entry on QuotaExceededError

Test totals: 226 passing (21 files), 0 failing, 0 ERRORs in test-review. Constitutional items closed: CONST-IX-001, 003, 006, 007, 008, 010, 012, 013, 014 + CONST-VIII-001–010, 015, 016, 017 = ~23 items.