SOVEREIGN NOTEBOOK — FRONTEND IMPLEMENTATION GUIDE
Implementation Scaffold & Code Examples
Version: 1.0
Status: READY FOR DEVELOPMENT
Target Framework: Vanilla TypeScript + Web Components
Build Tool: Vite or esbuild
PART 1: PROJECT SETUP
Step 1: Initialize Project Structure
mkdir sovereign-notebook-frontend
cd sovereign-notebook-frontend
# Create directory structure
mkdir -p src/{components,animation,services,types,utils,stores}
mkdir -p styles/{components,theme}
mkdir -p test/{components,animation,integration,e2e}
mkdir -p public/assets
# Initialize package.json
npm init -y
Step 2: Install Dependencies
{
"name": "sovereign-notebook-frontend",
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"test": "vitest",
"test:ui": "vitest --ui",
"preview": "vite preview",
"lint": "eslint src/**/*.ts"
},
"dependencies": {
"highlight.js": "^11.9.0",
"d3": "^7.8.0",
"mathjax": "^3.2.2"
},
"devDependencies": {
"typescript": "^5.3.0",
"vite": "^5.0.0",
"vitest": "^1.1.0",
"@vitest/ui": "^1.1.0",
"@types/d3": "^7.4.0",
"eslint": "^8.56.0",
"prettier": "^3.1.0"
}
}
Step 3: TypeScript Configuration
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}
PART 2: CORE TYPE DEFINITIONS
File: src/types/cell.ts
export type CellType = 'code' | 'markdown' | 'math' | 'raw';
export type Language =
| 'emoji'
| 'python'
| 'javascript'
| 'typescript'
| 'holyc'
| 'ada'
| 'markdown'
| 'latex';
export type ExecutionStatus =
| 'idle'
| 'queued'
| 'running'
| 'success'
| 'error'
| 'timeout'
| 'cancelled';
export type SealStatus =
| 'pending'
| 'sealed'
| 'verified'
| 'revoked';
export interface CellMetadata {
createdAt: number;
createdBy: string;
modifiedAt: number;
executedAt?: number;
executedBy?: string;
executionCount: number;
}
export interface CellOutput {
type: 'text' | 'table' | 'graph' | 'math' | 'error' | 'html';
data: string | Record<string, any>;
mimeType?: string;
hash: string;
receiptId?: string;
}
export interface Cell {
id: string;
type: CellType;
language: Language;
source: string;
output: CellOutput | null;
executionTime?: number; // ms
executionStatus: ExecutionStatus;
dependsOn: string[]; // cell IDs
metadata: CellMetadata;
sealStatus: SealStatus;
tags: string[];
}
export interface CellDependency {
from: string; // source cell ID
to: string; // target cell ID
type: 'data' | 'code' | 'config';
}
export interface NotebookMetadata {
id: string;
title: string;
description: string;
createdAt: number;
modifiedAt: number;
author: string;
trustLevel: 'low' | 'medium' | 'high' | 'sovereign';
version: string;
tags: string[];
}
export interface Notebook {
metadata: NotebookMetadata;
cells: Cell[];
variables: Map<string, any>;
kernelVersion: string;
}
File: src/types/receipt.ts
export interface ReceiptRecord {
id: string;
eventId: string;
cellId: string;
timestamp: number;
isoTimestamp: string;
// Execution details
command: string;
allowlist: string;
exitCode: number | null;
// Cryptographic
outputHash: string;
outputLength: number;
blake3Hash: string;
ed25519Signature: string;
previousHash: string | null;
chainHash: string;
// Verification
verificationStatus: 'pending' | 'authorized' | 'provisional' | 'denied';
verifiedBy?: string;
verifiedAt?: number;
// Agent info
agent: 'sentinel' | 'oracle' | 'builder' | 'archivist' | 'berserker';
trustLevel: 'low' | 'medium' | 'high' | 'sovereign';
}
export interface WormChainVerification {
valid: boolean;
errors: string[];
lastVerifiedAt: number;
integrityScore: number; // 0-100
}
File: src/types/execution.ts
export interface ExecutionRequest {
cellId: string;
source: string;
language: string;
dependencies: string[];
executorAgent: 'builder' | 'oracle' | 'sentinel';
timeout?: number; // milliseconds
metadata?: Record<string, any>;
}
export interface ExecutionResponse {
cellId: string;
status: 'running' | 'success' | 'error' | 'timeout';
output: {
type: string;
data: string;
};
executionTime: number;
receiptId: string;
wormHash: string;
error?: {
code: string;
message: string;
traceback?: string;
};
}
export interface ExecutionTask {
id: string;
cellId: string;
status: 'queued' | 'running' | 'completed' | 'failed';
startedAt?: number;
completedAt?: number;
error?: string;
}
PART 3: REACTIVE STATE MANAGEMENT
File: src/stores/notebook.store.ts
import type { Cell, Notebook, CellDependency, ReceiptRecord } from '../types/cell';
import type { NotebookMetadata } from '../types/cell';
type EventType =
| 'cell:created'
| 'cell:updated'
| 'cell:deleted'
| 'cell:executing'
| 'cell:executed'
| 'receipt:sealed'
| 'graph:updated'
| 'notebook:saved';
type Callback = (data?: any) => void;
export class NotebookStore {
private cells: Map<string, Cell> = new Map();
private receipts: ReceiptRecord[] = [];
private listeners: Map<EventType, Set<Callback>> = new Map();
private metadata: NotebookMetadata;
private isDirty = false;
constructor(initialMetadata: NotebookMetadata) {
this.metadata = initialMetadata;
}
// ========== Subscriptions ==========
subscribe(event: EventType, callback: Callback): () => void {
if (!this.listeners.has(event)) {
this.listeners.set(event, new Set());
}
this.listeners.get(event)!.add(callback);
// Return unsubscribe function
return () => {
this.listeners.get(event)!.delete(callback);
};
}
private emit(event: EventType, data?: any) {
this.listeners.get(event)?.forEach(cb => {
try {
cb(data);
} catch (e) {
console.error(`Error in listener for ${event}:`, e);
}
});
}
// ========== Cell Operations ==========
addCell(cell: Cell): void {
this.cells.set(cell.id, cell);
this.isDirty = true;
this.emit('cell:created', cell);
}
updateCell(cellId: string, updates: Partial<Cell>): void {
const cell = this.cells.get(cellId);
if (!cell) throw new Error(`Cell ${cellId} not found`);
Object.assign(cell, updates);
this.isDirty = true;
this.emit('cell:updated', { cellId, updates });
}
deleteCell(cellId: string): void {
if (!this.cells.has(cellId)) {
throw new Error(`Cell ${cellId} not found`);
}
this.cells.delete(cellId);
this.isDirty = true;
// Remove dependencies
this.cells.forEach((cell) => {
cell.dependsOn = cell.dependsOn.filter(id => id !== cellId);
});
this.emit('cell:deleted', { cellId });
}
getCell(cellId: string): Cell | undefined {
return this.cells.get(cellId);
}
getAllCells(): Cell[] {
return Array.from(this.cells.values());
}
// ========== Cell Dependencies ==========
getCellDependencies(cellId: string): string[] {
return this.cells.get(cellId)?.dependsOn || [];
}
getDependencyGraph(): CellDependency[] {
const graph: CellDependency[] = [];
this.cells.forEach((cell) => {
cell.dependsOn.forEach((depId) => {
graph.push({ from: depId, to: cell.id, type: 'data' });
});
});
return graph;
}
getExecutionOrder(): string[] {
// Topological sort of dependency DAG
const visited = new Set<string>();
const order: string[] = [];
const visit = (id: string, stack: Set<string> = new Set()) => {
if (visited.has(id)) return;
if (stack.has(id)) {
throw new Error(`Circular dependency detected at cell ${id}`);
}
stack.add(id);
const cell = this.cells.get(id);
cell?.dependsOn.forEach(depId => visit(depId, stack));
stack.delete(id);
visited.add(id);
order.push(id);
};
this.cells.forEach((_, id) => {
try {
visit(id);
} catch (e) {
console.error('Dependency resolution error:', e);
}
});
return order;
}
// ========== Execution State ==========
setExecuting(cellId: string): void {
const cell = this.cells.get(cellId);
if (cell) {
cell.executionStatus = 'running';
this.emit('cell:executing', { cellId });
}
}
completeExecution(
cellId: string,
status: 'success' | 'error',
output: any,
executionTime: number
): void {
const cell = this.cells.get(cellId);
if (!cell) return;
cell.executionStatus = status === 'success' ? 'success' : 'error';
cell.output = output;
cell.executionTime = executionTime;
cell.metadata.executedAt = Date.now();
cell.metadata.executionCount++;
this.isDirty = true;
this.emit('cell:executed', { cellId, status, output });
}
// ========== Receipt Management ==========
addReceipt(receipt: ReceiptRecord): void {
this.receipts.push(receipt);
this.isDirty = true;
this.emit('receipt:sealed', receipt);
}
getReceipts(): ReceiptRecord[] {
return [...this.receipts];
}
getReceiptsByCell(cellId: string): ReceiptRecord[] {
return this.receipts.filter(r => r.cellId === cellId);
}
// ========== Metadata ==========
getMetadata(): NotebookMetadata {
return { ...this.metadata };
}
updateMetadata(updates: Partial<NotebookMetadata>): void {
this.metadata = { ...this.metadata, ...updates };
this.isDirty = true;
}
// ========== Persistence ==========
isDirtyCheck(): boolean {
return this.isDirty;
}
markSaved(): void {
this.isDirty = false;
this.metadata.modifiedAt = Date.now();
this.emit('notebook:saved', this.metadata);
}
// ========== Export ==========
toJSON(): Notebook {
return {
metadata: this.metadata,
cells: this.getAllCells(),
variables: new Map(),
kernelVersion: '1.0.0',
};
}
static fromJSON(json: Notebook): NotebookStore {
const store = new NotebookStore(json.metadata);
json.cells.forEach(cell => store.addCell(cell));
return store;
}
}
// Global singleton
export const notebook = new NotebookStore({
id: crypto.randomUUID(),
title: 'Untitled Notebook',
description: '',
createdAt: Date.now(),
modifiedAt: Date.now(),
author: 'Unknown',
trustLevel: 'medium',
version: '1.0.0',
tags: [],
});
PART 4: WEB COMPONENTS
File: src/components/notebook-container.ts
import type { Cell } from '../types/cell';
import { notebook } from '../stores/notebook.store';
export class NotebookContainer extends HTMLElement {
private container: HTMLDivElement | null = null;
private unsubscribers: (() => void)[] = [];
connectedCallback() {
this.render();
this.attachSubscriptions();
}
disconnectedCallback() {
this.unsubscribers.forEach(fn => fn());
}
private attachSubscriptions() {
this.unsubscribers.push(
notebook.subscribe('cell:created', () => this.renderCells()),
notebook.subscribe('cell:deleted', () => this.renderCells()),
notebook.subscribe('receipt:sealed', () => this.updateLedger())
);
}
private render() {
this.innerHTML = `
<div class="notebook-layout">
<div class="notebook-header">
<h1 class="notebook-title">${notebook.getMetadata().title}</h1>
<div class="header-controls">
<button class="btn-run" aria-label="Run all cells">▶ Run All</button>
<button class="btn-save" aria-label="Save notebook">💾 Save</button>
<button class="btn-export" aria-label="Export">⬇ Export</button>
</div>
</div>
<div class="notebook-content">
<aside class="sidebar-left">
<div class="sidebar-section">
<h3>Notebook Info</h3>
<dl>
<dt>Cells</dt>
<dd id="cell-count">${notebook.getAllCells().length}</dd>
<dt>Modified</dt>
<dd id="modified-time">just now</dd>
</dl>
</div>
<div class="sidebar-section">
<h3>Dependency Graph</h3>
<canvas id="graph-mini" width="200" height="200"></canvas>
</div>
<div class="sidebar-section">
<h3>Trust Policies</h3>
<select id="executor-agent">
<option value="builder">Execute as builder</option>
<option value="oracle">Execute as oracle</option>
</select>
</div>
</aside>
<main class="editor-area">
<div class="execution-trace">
<canvas id="trace-canvas" width="1400" height="60"></canvas>
</div>
<div id="cells-container" class="cells-container"></div>
</main>
<aside class="sidebar-right">
<div class="worm-ledger">
<h3>WORM Ledger</h3>
<div id="receipt-list" class="receipt-list"></div>
</div>
</aside>
</div>
</div>
`;
this.container = this.querySelector('.notebook-content') as HTMLDivElement;
this.attachEventListeners();
this.renderCells();
this.updateLedger();
}
private renderCells() {
if (!this.container) return;
const cellsContainer = this.querySelector('#cells-container') as HTMLDivElement;
const cells = notebook.getAllCells();
cellsContainer.innerHTML = cells
.map(cell => `<notebook-cell cell-id="${cell.id}"></notebook-cell>`)
.join('');
// Initialize child components
cells.forEach(cell => {
const component = cellsContainer.querySelector(`notebook-cell[cell-id="${cell.id}"]`);
if (component instanceof NotebookCell) {
component.setState(cell);
}
});
}
private updateLedger() {
const receiptList = this.querySelector('#receipt-list') as HTMLDivElement;
const receipts = notebook.getReceipts();
receiptList.innerHTML = receipts
.slice(-10) // Show last 10
.reverse()
.map(receipt => `
<div class="receipt-item" data-receipt-id="${receipt.id}">
<div class="receipt-header">
<code class="receipt-id">${receipt.id.slice(0, 12)}…</code>
<span class="receipt-status ${receipt.verificationStatus}">
${receipt.verificationStatus}
</span>
</div>
<div class="receipt-meta">
<span>Cell: ${receipt.cellId}</span>
<span>Time: ${new Date(receipt.timestamp).toLocaleTimeString()}</span>
</div>
</div>
`)
.join('');
}
private attachEventListeners() {
this.querySelector('.btn-run')?.addEventListener('click', () => {
this.runAllCells();
});
this.querySelector('.btn-save')?.addEventListener('click', () => {
this.saveNotebook();
});
}
private async runAllCells() {
const order = notebook.getExecutionOrder();
for (const cellId of order) {
const cell = notebook.getCell(cellId);
if (cell) {
await this.executeCell(cellId);
}
}
}
private async executeCell(cellId: string) {
// Implement execution logic
notebook.setExecuting(cellId);
// ... call runtime
}
private saveNotebook() {
// Implement save logic
notebook.markSaved();
console.log('Notebook saved');
}
}
export class NotebookCell extends HTMLElement {
private cellState: Cell | null = null;
connectedCallback() {
this.render();
}
setState(cell: Cell) {
this.cellState = cell;
this.render();
}
private render() {
if (!this.cellState) return;
const cell = this.cellState;
this.innerHTML = `
<div class="cell" data-cell-id="${cell.id}">
<div class="cell-header">
<span class="cell-label">
${cell.type === 'code' ? '⚡' : '📝'} ${cell.id.slice(0, 8)}
</span>
<span class="cell-status ${cell.executionStatus}">
${cell.executionStatus}
</span>
</div>
<cell-editor cell-id="${cell.id}"></cell-editor>
${cell.output ? `<cell-output cell-id="${cell.id}"></cell-output>` : ''}
<div class="cell-footer">
<button class="btn-run" aria-label="Run this cell">▶ Run</button>
<button class="btn-delete" aria-label="Delete cell">🗑</button>
</div>
</div>
`;
this.addEventListener('click', (e) => {
const target = e.target as HTMLElement;
if (target.classList.contains('btn-run')) {
this.dispatchEvent(new CustomEvent('cell:execute', {
detail: { cellId: cell.id },
bubbles: true,
}));
} else if (target.classList.contains('btn-delete')) {
notebook.deleteCell(cell.id);
}
});
}
}
customElements.define('notebook-container', NotebookContainer);
customElements.define('notebook-cell', NotebookCell);
File: src/components/cell-editor.ts
import hljs from 'highlight.js';
import type { Language } from '../types/cell';
export class CellEditor extends HTMLElement {
private textarea: HTMLTextAreaElement | null = null;
private highlighter: HTMLDivElement | null = null;
private language: Language = 'python';
connectedCallback() {
this.language = (this.getAttribute('language') as Language) || 'python';
this.render();
this.setupSyntaxHighlighting();
}
private render() {
this.innerHTML = `
<div class="editor-wrapper">
<div class="line-numbers" id="line-numbers"></div>
<textarea
class="editor-textarea"
spellcheck="false"
placeholder="Enter code here..."
></textarea>
<div class="highlights" id="highlights"></div>
</div>
`;
this.textarea = this.querySelector('textarea') as HTMLTextAreaElement;
this.highlighter = this.querySelector('#highlights') as HTMLDivElement;
this.textarea.addEventListener('input', () => this.updateHighlights());
this.textarea.addEventListener('scroll', () => this.syncScroll());
this.textarea.addEventListener('keydown', (e) => this.handleKeydown(e));
}
private setupSyntaxHighlighting() {
this.updateHighlights();
}
private updateHighlights() {
if (!this.textarea || !this.highlighter) return;
const code = this.textarea.value;
const highlighted = hljs.highlight(code, { language: this.language, ignoreIllegals: true }).value;
this.highlighter.innerHTML = `<pre><code>${highlighted}</code></pre>`;
this.updateLineNumbers();
}
private updateLineNumbers() {
if (!this.textarea) return;
const lines = this.textarea.value.split('\n').length;
const lineNumbers = this.querySelector('#line-numbers') as HTMLDivElement;
if (lineNumbers) {
lineNumbers.innerHTML = Array.from({ length: lines }, (_, i) =>
`<div class="line-number">${i + 1}</div>`
).join('');
}
}
private syncScroll() {
if (!this.textarea || !this.highlighter) return;
this.highlighter.scrollTop = this.textarea.scrollTop;
this.highlighter.scrollLeft = this.textarea.scrollLeft;
}
private handleKeydown(e: KeyboardEvent) {
if (e.key === 'Tab') {
e.preventDefault();
const start = this.textarea!.selectionStart;
const end = this.textarea!.selectionEnd;
this.textarea!.value =
this.textarea!.value.substring(0, start) +
'\t' +
this.textarea!.value.substring(end);
this.textarea!.selectionStart = this.textarea!.selectionEnd = start + 1;
this.updateHighlights();
}
}
getCode(): string {
return this.textarea?.value || '';
}
setCode(code: string) {
if (this.textarea) {
this.textarea.value = code;
this.updateHighlights();
}
}
}
customElements.define('cell-editor', CellEditor);
File: src/components/cell-output.ts
import type { CellOutput } from '../types/cell';
export class CellOutputComponent extends HTMLElement {
private output: CellOutput | null = null;
connectedCallback() {
// Load initial output from store
const cellId = this.getAttribute('cell-id');
if (cellId) {
// TODO: Subscribe to updates
}
}
setOutput(output: CellOutput) {
this.output = output;
this.render();
}
private render() {
if (!this.output) {
this.innerHTML = '';
return;
}
this.innerHTML = `
<div class="cell-output">
<div class="output-header">
<span class="output-type">${this.output.type}</span>
<span class="output-hash" title="${this.output.hash}">
${this.output.hash.slice(0, 12)}…
</span>
</div>
<div class="output-content">
${this.renderContent()}
</div>
</div>
`;
}
private renderContent(): string {
if (!this.output) return '';
switch (this.output.type) {
case 'text':
return `<pre>${this.escapeHtml(String(this.output.data))}</pre>`;
case 'error':
return `<pre class="error">${this.escapeHtml(String(this.output.data))}</pre>`;
case 'table':
return this.renderTable();
case 'html':
return String(this.output.data);
case 'math':
return `<div class="math">${this.output.data}</div>`;
default:
return `<pre>${JSON.stringify(this.output.data, null, 2)}</pre>`;
}
}
private renderTable(): string {
if (!Array.isArray(this.output?.data)) return '';
const data = this.output.data as Record<string, any>[];
if (data.length === 0) return '<table></table>';
const headers = Object.keys(data[0]);
return `
<table class="output-table">
<thead>
<tr>
${headers.map(h => `<th>${h}</th>`).join('')}
</tr>
</thead>
<tbody>
${data.map(row => `
<tr>
${headers.map(h => `<td>${this.escapeHtml(String(row[h]))}</td>`).join('')}
</tr>
`).join('')}
</tbody>
</table>
`;
}
private escapeHtml(text: string): string {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
}
customElements.define('cell-output', CellOutputComponent);
PART 5: ANIMATION ENGINE
File: src/animation/execution-flow.ts
interface Particle {
x: number;
y: number;
vx: number;
vy: number;
glyph: string;
age: number;
lifetime: number;
opacity: number;
}
interface Arrow {
fromX: number;
fromY: number;
toX: number;
toY: number;
progress: number;
duration: number;
startTime: number;
}
export class ExecutionFlowAnimator {
private canvas: HTMLCanvasElement;
private ctx: CanvasRenderingContext2D;
private particles: Particle[] = [];
private arrows: Arrow[] = [];
private animationId: number | null = null;
private glyphs = ['λ', 'Ω', '∑', '⊕', 'φ', '∞', '↔', '⇄'];
constructor(canvas: HTMLCanvasElement) {
this.canvas = canvas;
this.ctx = canvas.getContext('2d')!;
}
startExecution(fromX: number, fromY: number, toX: number, toY: number, duration: number) {
const arrow: Arrow = {
fromX, fromY, toX, toY,
progress: 0,
duration,
startTime: Date.now(),
};
this.arrows.push(arrow);
// Spawn particles
for (let i = 0; i < 3; i++) {
setTimeout(() => {
this.spawnParticle(fromX, fromY, toX, toY, duration);
}, i * 200);
}
this.animate();
}
private spawnParticle(fromX: number, fromY: number, toX: number, toY: number, duration: number) {
const glyph = this.glyphs[Math.floor(Math.random() * this.glyphs.length)];
const distance = Math.sqrt((toX - fromX) ** 2 + (toY - fromY) ** 2);
const speed = distance / duration;
const dx = (toX - fromX) / distance;
const dy = (toY - fromY) / distance;
this.particles.push({
x: fromX,
y: fromY,
vx: dx * speed,
vy: dy * speed,
glyph,
age: 0,
lifetime: duration,
opacity: 1,
});
}
private animate() {
const now = Date.now();
const dt = 1 / 60; // assume 60fps
// Update particles
this.particles.forEach(p => {
p.age += dt;
p.x += p.vx * dt * 1000;
p.y += p.vy * dt * 1000;
p.opacity = Math.max(0, 1 - p.age / p.lifetime);
});
// Remove expired particles
this.particles = this.particles.filter(p => p.age < p.lifetime);
// Update arrows
this.arrows.forEach(a => {
a.progress = (now - a.startTime) / a.duration;
});
// Remove completed arrows
this.arrows = this.arrows.filter(a => a.progress < 1);
// Render
this.render();
if (this.particles.length > 0 || this.arrows.length > 0) {
this.animationId = requestAnimationFrame(() => this.animate());
}
}
private render() {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
// Draw arrows
this.arrows.forEach(arrow => {
const x = arrow.fromX + (arrow.toX - arrow.fromX) * arrow.progress;
const y = arrow.fromY + (arrow.toY - arrow.fromY) * arrow.progress;
// Draw line
this.ctx.strokeStyle = `rgba(126, 249, 255, ${0.6 * (1 - arrow.progress)})`;
this.ctx.lineWidth = 2;
this.ctx.beginPath();
this.ctx.moveTo(arrow.fromX, arrow.fromY);
this.ctx.lineTo(x, y);
this.ctx.stroke();
// Draw arrowhead
this.drawArrowhead(x, y, arrow.fromX, arrow.fromY);
});
// Draw particles
this.particles.forEach(p => {
this.ctx.font = '24px Arial';
this.ctx.fillStyle = `rgba(255, 121, 220, ${p.opacity})`;
this.ctx.textAlign = 'center';
this.ctx.textBaseline = 'middle';
this.ctx.fillText(p.glyph, p.x, p.y);
});
}
private drawArrowhead(toX: number, toY: number, fromX: number, fromY: number) {
const angle = Math.atan2(toY - fromY, toX - fromX);
const headlen = 12;
this.ctx.strokeStyle = 'rgba(126, 249, 255, 0.8)';
this.ctx.lineWidth = 2;
this.ctx.beginPath();
this.ctx.moveTo(toX - headlen * Math.cos(angle - Math.PI / 6), toY - headlen * Math.sin(angle - Math.PI / 6));
this.ctx.lineTo(toX, toY);
this.ctx.lineTo(toX - headlen * Math.cos(angle + Math.PI / 6), toY - headlen * Math.sin(angle + Math.PI / 6));
this.ctx.stroke();
}
stop() {
if (this.animationId) {
cancelAnimationFrame(this.animationId);
this.animationId = null;
}
}
}
PART 6: RUNTIME BRIDGE
File: src/services/runtime-bridge.ts
import type { ExecutionRequest, ExecutionResponse } from '../types/execution';
import type { ReceiptRecord } from '../types/receipt';
import { notebook } from '../stores/notebook.store';
export class RuntimeBridge {
private baseUrl: string;
private wsConnection: WebSocket | null = null;
constructor(baseUrl: string = '/api') {
this.baseUrl = baseUrl;
}
async executeCell(request: ExecutionRequest): Promise<ExecutionResponse> {
notebook.setExecuting(request.cellId);
try {
const response = await fetch(`${this.baseUrl}/execute`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(request),
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const data = (await response.json()) as ExecutionResponse;
// Update notebook state
notebook.completeExecution(
request.cellId,
data.status === 'success' ? 'success' : 'error',
data.output,
data.executionTime
);
// Add receipt
const receipt: ReceiptRecord = {
id: data.receiptId,
eventId: request.cellId,
cellId: request.cellId,
timestamp: Date.now(),
isoTimestamp: new Date().toISOString(),
command: request.source,
allowlist: '',
exitCode: null,
outputHash: '',
outputLength: 0,
blake3Hash: data.wormHash,
ed25519Signature: '',
previousHash: null,
chainHash: '',
verificationStatus: 'pending',
agent: request.executorAgent,
trustLevel: 'medium',
};
notebook.addReceipt(receipt);
return data;
} catch (error) {
console.error('Execution failed:', error);
notebook.completeExecution(
request.cellId,
'error',
{ type: 'error', data: String(error) },
0
);
throw error;
}
}
async verifyReceipt(receiptId: string): Promise<boolean> {
try {
const response = await fetch(`${this.baseUrl}/verify/${receiptId}`);
return response.ok;
} catch {
return false;
}
}
connectWebSocket(handler: (event: any) => void) {
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const url = `${protocol}//${window.location.host}/api/ws`;
this.wsConnection = new WebSocket(url);
this.wsConnection.onmessage = (event) => {
handler(JSON.parse(event.data));
};
return new Promise((resolve) => {
this.wsConnection!.onopen = resolve;
});
}
disconnect() {
if (this.wsConnection) {
this.wsConnection.close();
this.wsConnection = null;
}
}
}
export const runtime = new RuntimeBridge();
PART 7: STYLING SCAFFOLD
File: styles/global.css
/* Design Tokens */
:root {
--color-bg-primary: #070b1e;
--color-bg-secondary: #0f0f2e;
--color-text-primary: #eef9ff;
--color-text-secondary: #a0a7d0;
--color-accent-cyan: #7ef9ff;
--color-accent-magenta: #ff79dc;
--color-accent-gold: #ffe98a;
--color-error: #ff4fd8;
--color-success: #00d9a3;
--color-warning: #ffd86b;
--font-mono: 'Fira Code', 'Courier New', monospace;
--font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
--spacing-xs: 4px;
--spacing-sm: 8px;
--spacing-md: 16px;
--spacing-lg: 24px;
--shadow-md: 0 8px 24px rgba(0, 0, 0, 0.5);
--transition-fast: 150ms ease-out;
}
/* Resets */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html, body {
background: var(--color-bg-primary);
color: var(--color-text-primary);
font-family: var(--font-sans);
font-size: 16px;
line-height: 1.6;
}
/* Layout */
.notebook-layout {
display: flex;
flex-direction: column;
height: 100vh;
}
.notebook-content {
display: grid;
grid-template-columns: 200px 1fr 300px;
flex: 1;
gap: var(--spacing-md);
overflow: hidden;
}
@media (max-width: 768px) {
.notebook-content {
grid-template-columns: 1fr;
}
.sidebar-left,
.sidebar-right {
display: none;
}
}
/* Components */
button {
background: var(--color-bg-secondary);
color: var(--color-text-primary);
border: 1px solid var(--color-accent-cyan);
padding: var(--spacing-sm) var(--spacing-md);
border-radius: 4px;
cursor: pointer;
font-family: inherit;
transition: all var(--transition-fast);
}
button:hover {
background: var(--color-accent-cyan);
color: var(--color-bg-primary);
}
code {
font-family: var(--font-mono);
font-size: 0.9em;
padding: 2px 6px;
background: var(--color-bg-secondary);
border-radius: 3px;
}
PART 8: ENTRY POINT
File: src/index.ts
import { NotebookContainer } from './components/notebook-container';
import './styles/global.css';
document.addEventListener('DOMContentLoaded', () => {
const root = document.getElementById('app');
if (root) {
const app = document.createElement('notebook-container');
root.appendChild(app);
}
});
File: index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Sovereign Notebook</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/atom-one-dark.min.css">
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/index.ts"></script>
</body>
</html>
PART 9: DEVELOPMENT WORKFLOW
Build & Run
# Install dependencies
npm install
# Development server with hot reload
npm run dev
# Build for production
npm build
# Run tests
npm test
# Run tests with UI
npm run test:ui
# Format code
npx prettier --write src/
Key Development Commands
# Quick component prototype
touch src/components/my-component.ts
# Run single test file
npm test src/components/__tests__/my-component.test.ts
# Generate types from notebook JSON
npx ts-node scripts/generate-types.ts
NEXT STEPS
- Setup: Follow Part 1 project initialization
- Types: Define all interfaces (Part 2)
- State: Implement NotebookStore (Part 3)
- Components: Build Web Components (Part 4)
- Animation: Add canvas animations (Part 5)
- Integration: Wire runtime bridge (Part 6)
- Styling: Implement CSS architecture (Part 7)
- Testing: Add unit & integration tests
- Deploy: Build and deploy to production
Document Finalized: 2026-07-27 14:45 UTC
Ready for Development: ✓