@shaykec/bridge 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,449 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>ClaudeTeach — Architecture Diagram</title>
7
+ <style>
8
+ * { margin: 0; padding: 0; box-sizing: border-box; }
9
+ body {
10
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
11
+ background: #0d1117;
12
+ color: #c9d1d9;
13
+ display: flex;
14
+ flex-direction: column;
15
+ align-items: center;
16
+ justify-content: center;
17
+ min-height: 100vh;
18
+ padding: 2rem;
19
+ }
20
+ h2 {
21
+ color: #58a6ff;
22
+ margin-bottom: 0.5rem;
23
+ font-size: 1.2rem;
24
+ }
25
+ .caption {
26
+ color: #8b949e;
27
+ font-size: 0.9rem;
28
+ text-align: center;
29
+ max-width: 600px;
30
+ margin-bottom: 1.5rem;
31
+ line-height: 1.5;
32
+ }
33
+ .diagram-container {
34
+ background: #161b22;
35
+ border: 1px solid #30363d;
36
+ border-radius: 12px;
37
+ padding: 2rem;
38
+ max-width: 95vw;
39
+ max-height: 75vh;
40
+ overflow: auto;
41
+ margin-bottom: 1rem;
42
+ position: relative;
43
+ }
44
+ .diagram-container svg {
45
+ max-width: 100%;
46
+ height: auto;
47
+ }
48
+ .controls {
49
+ display: flex;
50
+ gap: 0.5rem;
51
+ margin-top: 0.5rem;
52
+ flex-wrap: wrap;
53
+ justify-content: center;
54
+ }
55
+ .controls button {
56
+ background: #21262d;
57
+ color: #c9d1d9;
58
+ border: 1px solid #30363d;
59
+ padding: 0.4rem 0.8rem;
60
+ border-radius: 6px;
61
+ cursor: pointer;
62
+ font-size: 0.85rem;
63
+ transition: background 0.15s;
64
+ }
65
+ .controls button:hover { background: #30363d; }
66
+ .controls button:focus { outline: 2px solid #58a6ff; outline-offset: 2px; }
67
+ .controls button.active {
68
+ background: #1c2333;
69
+ border-color: #58a6ff;
70
+ color: #58a6ff;
71
+ }
72
+ .legend {
73
+ display: flex;
74
+ gap: 1.5rem;
75
+ flex-wrap: wrap;
76
+ justify-content: center;
77
+ margin-top: 1rem;
78
+ padding: 0.75rem 1rem;
79
+ background: #161b22;
80
+ border: 1px solid #30363d;
81
+ border-radius: 8px;
82
+ font-size: 0.8rem;
83
+ }
84
+ .legend-item {
85
+ display: flex;
86
+ align-items: center;
87
+ gap: 0.4rem;
88
+ }
89
+ .legend-color {
90
+ width: 14px;
91
+ height: 14px;
92
+ border-radius: 3px;
93
+ border: 1px solid #30363d;
94
+ }
95
+ .layer-info {
96
+ margin-top: 0.75rem;
97
+ padding: 0.75rem 1rem;
98
+ background: #1c2333;
99
+ border: 1px solid #30363d;
100
+ border-radius: 8px;
101
+ font-size: 0.9rem;
102
+ line-height: 1.5;
103
+ max-width: 600px;
104
+ text-align: center;
105
+ display: none;
106
+ }
107
+ .layer-info.visible { display: block; }
108
+ .error {
109
+ color: #f85149;
110
+ background: rgba(248, 81, 73, 0.1);
111
+ border: 1px solid #f85149;
112
+ border-radius: 8px;
113
+ padding: 1rem;
114
+ margin-top: 1rem;
115
+ display: none;
116
+ max-width: 600px;
117
+ word-break: break-word;
118
+ }
119
+ .loading {
120
+ color: #8b949e;
121
+ font-size: 0.9rem;
122
+ padding: 2rem;
123
+ }
124
+ </style>
125
+ </head>
126
+ <body>
127
+ <h2 id="title">System Architecture</h2>
128
+ <div class="caption" id="caption"></div>
129
+ <div class="diagram-container" id="diagramContainer">
130
+ <div class="mermaid" id="diagram">
131
+ <div class="loading">Loading architecture diagram...</div>
132
+ </div>
133
+ </div>
134
+ <div class="controls" id="controls">
135
+ <button onclick="zoomIn()" aria-label="Zoom in">Zoom In</button>
136
+ <button onclick="zoomOut()" aria-label="Zoom out">Zoom Out</button>
137
+ <button onclick="resetZoom()" aria-label="Reset zoom">Reset</button>
138
+ <button onclick="fitToScreen()" aria-label="Fit to screen">Fit</button>
139
+ </div>
140
+ <div class="legend" id="legend" style="display: none;"></div>
141
+ <div class="layer-info" id="layerInfo"></div>
142
+ <div class="error" id="error"></div>
143
+
144
+ <script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>
145
+ <script>
146
+ /* --- Bridge connection --- */
147
+ var ws = null;
148
+ function connectBridge() {
149
+ try {
150
+ ws = new WebSocket('ws://' + location.host + '/ws');
151
+ ws.onopen = function() {
152
+ ws.send(JSON.stringify({
153
+ v: 1, type: 'sys:connect',
154
+ payload: { clientType: 'template' },
155
+ source: 'template', timestamp: Date.now()
156
+ }));
157
+ };
158
+ ws.onerror = function() { ws = null; };
159
+ ws.onclose = function() { ws = null; };
160
+ } catch(e) { ws = null; }
161
+ }
162
+ connectBridge();
163
+
164
+ /* --- Mermaid config --- */
165
+ mermaid.initialize({
166
+ startOnLoad: false,
167
+ theme: 'dark',
168
+ themeVariables: {
169
+ darkMode: true,
170
+ background: '#0d1117',
171
+ primaryColor: '#1c2333',
172
+ primaryTextColor: '#c9d1d9',
173
+ primaryBorderColor: '#58a6ff',
174
+ lineColor: '#8b949e',
175
+ secondaryColor: '#161b22',
176
+ tertiaryColor: '#21262d',
177
+ fontFamily: '-apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif'
178
+ },
179
+ flowchart: {
180
+ htmlLabels: true,
181
+ curve: 'basis',
182
+ padding: 15
183
+ },
184
+ securityLevel: 'strict'
185
+ });
186
+
187
+ var currentZoom = 1;
188
+ var diagramData = {
189
+ content: null,
190
+ title: 'System Architecture',
191
+ caption: '',
192
+ layers: [],
193
+ legend: []
194
+ };
195
+
196
+ async function init(data) {
197
+ if (data) diagramData = Object.assign({}, diagramData, data);
198
+
199
+ if (diagramData.title) {
200
+ document.getElementById('title').textContent = diagramData.title;
201
+ }
202
+ if (diagramData.caption) {
203
+ document.getElementById('caption').textContent = diagramData.caption;
204
+ }
205
+
206
+ var content = diagramData.content;
207
+
208
+ // If components/layers are provided instead of raw mermaid, build the content
209
+ if (!content && diagramData.components) {
210
+ content = buildArchDiagram(diagramData.components, diagramData.connections, diagramData.subgraphs);
211
+ }
212
+
213
+ if (!content) {
214
+ // Default demo architecture diagram
215
+ content = 'graph TB\n' +
216
+ ' subgraph Client["Client Layer"]\n' +
217
+ ' A[Browser] --> B[Mobile App]\n' +
218
+ ' end\n' +
219
+ ' subgraph API["API Layer"]\n' +
220
+ ' C[Load Balancer]\n' +
221
+ ' D[API Server]\n' +
222
+ ' E[Auth Service]\n' +
223
+ ' end\n' +
224
+ ' subgraph Data["Data Layer"]\n' +
225
+ ' F[(Database)]\n' +
226
+ ' G[(Cache)]\n' +
227
+ ' end\n' +
228
+ ' Client --> C\n' +
229
+ ' C --> D\n' +
230
+ ' D --> E\n' +
231
+ ' D --> F\n' +
232
+ ' D --> G';
233
+ }
234
+
235
+ var diagramEl = document.getElementById('diagram');
236
+ var errorEl = document.getElementById('error');
237
+
238
+ try {
239
+ diagramEl.textContent = content;
240
+ var result = await mermaid.render('mermaid-arch-svg', content);
241
+ diagramEl.innerHTML = result.svg;
242
+ errorEl.style.display = 'none';
243
+ } catch (err) {
244
+ errorEl.textContent = 'Failed to render architecture diagram: ' + err.message;
245
+ errorEl.style.display = 'block';
246
+ diagramEl.innerHTML = '<div class="loading">Render error</div>';
247
+ }
248
+
249
+ // Setup legend
250
+ if (diagramData.legend && diagramData.legend.length > 0) {
251
+ renderLegend(diagramData.legend);
252
+ }
253
+
254
+ // Setup layer buttons
255
+ if (diagramData.layers && diagramData.layers.length > 0) {
256
+ renderLayerButtons(diagramData.layers);
257
+ }
258
+ }
259
+
260
+ function buildArchDiagram(components, connections, subgraphs) {
261
+ var lines = ['graph TB'];
262
+
263
+ // Render subgraphs (layers)
264
+ if (subgraphs && subgraphs.length > 0) {
265
+ subgraphs.forEach(function(sg) {
266
+ lines.push(' subgraph ' + sg.id + '["' + sg.label + '"]');
267
+ (sg.components || []).forEach(function(comp) {
268
+ var shape = formatComponent(comp);
269
+ lines.push(' ' + shape);
270
+ });
271
+ lines.push(' end');
272
+ });
273
+ }
274
+
275
+ // Render standalone components
276
+ if (components) {
277
+ components.forEach(function(comp) {
278
+ if (!comp.subgraph) {
279
+ lines.push(' ' + formatComponent(comp));
280
+ }
281
+ });
282
+ }
283
+
284
+ // Render connections
285
+ if (connections) {
286
+ connections.forEach(function(conn) {
287
+ var arrow = conn.label
288
+ ? '-->|' + conn.label + '|'
289
+ : conn.style === 'dashed' ? '-.->' : '-->';
290
+ lines.push(' ' + conn.from + ' ' + arrow + ' ' + conn.to);
291
+ });
292
+ }
293
+
294
+ return lines.join('\n');
295
+ }
296
+
297
+ function formatComponent(comp) {
298
+ var id = comp.id || comp.label.replace(/\s+/g, '_');
299
+ var type = comp.type || 'service';
300
+
301
+ switch (type) {
302
+ case 'database':
303
+ case 'db':
304
+ return id + '[(' + comp.label + ')]';
305
+ case 'queue':
306
+ case 'message':
307
+ return id + '>' + comp.label + ']';
308
+ case 'decision':
309
+ return id + '{' + comp.label + '}';
310
+ case 'rounded':
311
+ case 'user':
312
+ return id + '(' + comp.label + ')';
313
+ default:
314
+ return id + '[' + comp.label + ']';
315
+ }
316
+ }
317
+
318
+ function renderLegend(items) {
319
+ var legendEl = document.getElementById('legend');
320
+ legendEl.style.display = 'flex';
321
+ legendEl.innerHTML = '';
322
+
323
+ items.forEach(function(item) {
324
+ var div = document.createElement('div');
325
+ div.className = 'legend-item';
326
+ div.innerHTML = '<span class="legend-color" style="background:' + (item.color || '#58a6ff') + '"></span>' +
327
+ '<span>' + escapeHtml(item.label) + '</span>';
328
+ legendEl.appendChild(div);
329
+ });
330
+ }
331
+
332
+ function renderLayerButtons(layers) {
333
+ var controlsEl = document.getElementById('controls');
334
+
335
+ // Add separator
336
+ var sep = document.createElement('span');
337
+ sep.style.borderLeft = '1px solid #30363d';
338
+ sep.style.height = '24px';
339
+ sep.style.margin = '0 0.25rem';
340
+ controlsEl.appendChild(sep);
341
+
342
+ layers.forEach(function(layer, i) {
343
+ var btn = document.createElement('button');
344
+ btn.textContent = layer.label || layer.name || ('Layer ' + (i + 1));
345
+ btn.setAttribute('aria-label', 'Focus on ' + (layer.label || layer.name));
346
+ btn.addEventListener('click', function() {
347
+ // Toggle active
348
+ controlsEl.querySelectorAll('button').forEach(function(b) { b.classList.remove('active'); });
349
+ btn.classList.add('active');
350
+
351
+ var info = document.getElementById('layerInfo');
352
+ info.textContent = layer.description || '';
353
+ info.classList.toggle('visible', !!layer.description);
354
+
355
+ // Highlight relevant subgraph
356
+ highlightSubgraph(layer.subgraphId || layer.id);
357
+ });
358
+ controlsEl.appendChild(btn);
359
+ });
360
+ }
361
+
362
+ function highlightSubgraph(sgId) {
363
+ // Dim all nodes, brighten the target subgraph
364
+ var allNodes = document.querySelectorAll('#diagram .node rect, #diagram .node polygon, #diagram .node circle');
365
+ allNodes.forEach(function(el) {
366
+ el.style.opacity = '0.3';
367
+ });
368
+
369
+ // Find nodes within the subgraph cluster
370
+ var cluster = document.querySelector('#diagram .cluster#' + sgId + ', #diagram [id*="' + sgId + '"]');
371
+ if (cluster) {
372
+ var nodes = cluster.querySelectorAll('.node rect, .node polygon, .node circle');
373
+ nodes.forEach(function(el) {
374
+ el.style.opacity = '1';
375
+ el.style.filter = 'drop-shadow(0 0 6px rgba(88, 166, 255, 0.4))';
376
+ });
377
+ }
378
+
379
+ // Auto-reset after 3s
380
+ setTimeout(function() {
381
+ allNodes.forEach(function(el) {
382
+ el.style.opacity = '';
383
+ el.style.filter = '';
384
+ });
385
+ }, 3000);
386
+ }
387
+
388
+ /* --- Zoom --- */
389
+ function zoomIn() {
390
+ currentZoom = Math.min(currentZoom + 0.2, 3);
391
+ applyZoom();
392
+ }
393
+ function zoomOut() {
394
+ currentZoom = Math.max(currentZoom - 0.2, 0.3);
395
+ applyZoom();
396
+ }
397
+ function resetZoom() {
398
+ currentZoom = 1;
399
+ applyZoom();
400
+ }
401
+ function fitToScreen() {
402
+ var svg = document.querySelector('#diagram svg');
403
+ var container = document.getElementById('diagramContainer');
404
+ if (svg && container) {
405
+ var svgRect = svg.getBBox ? svg.getBBox() : { width: svg.clientWidth, height: svg.clientHeight };
406
+ var containerW = container.clientWidth - 64;
407
+ var containerH = container.clientHeight - 64;
408
+ var scaleW = containerW / (svgRect.width || 1);
409
+ var scaleH = containerH / (svgRect.height || 1);
410
+ currentZoom = Math.min(scaleW, scaleH, 2);
411
+ applyZoom();
412
+ }
413
+ }
414
+ function applyZoom() {
415
+ var svg = document.querySelector('#diagram svg');
416
+ if (svg) {
417
+ svg.style.transform = 'scale(' + currentZoom + ')';
418
+ svg.style.transformOrigin = 'center top';
419
+ }
420
+ }
421
+
422
+ function escapeHtml(str) {
423
+ var div = document.createElement('div');
424
+ div.textContent = str;
425
+ return div.innerHTML;
426
+ }
427
+
428
+ /* --- Keyboard nav --- */
429
+ document.addEventListener('keydown', function(e) {
430
+ if (e.key === '+' || e.key === '=') zoomIn();
431
+ if (e.key === '-') zoomOut();
432
+ if (e.key === '0') resetZoom();
433
+ });
434
+
435
+ /* --- Initialization --- */
436
+ window.addEventListener('message', function(e) {
437
+ if (e.data && e.data.type === 'diagram-data') {
438
+ init(e.data.payload);
439
+ }
440
+ });
441
+
442
+ if (window.__DIAGRAM_DATA__) {
443
+ init(window.__DIAGRAM_DATA__);
444
+ } else {
445
+ init();
446
+ }
447
+ </script>
448
+ </body>
449
+ </html>