@laitszkin/apollo-toolkit 3.11.0 → 3.11.2

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.
Files changed (40) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/analyse-app-logs/scripts/__pycache__/filter_logs_by_time.cpython-312.pyc +0 -0
  3. package/analyse-app-logs/scripts/__pycache__/log_cli_utils.cpython-312.pyc +0 -0
  4. package/analyse-app-logs/scripts/__pycache__/search_logs.cpython-312.pyc +0 -0
  5. package/docs-to-voice/scripts/__pycache__/docs_to_voice.cpython-312.pyc +0 -0
  6. package/generate-spec/scripts/__pycache__/create-specscpython-312.pyc +0 -0
  7. package/init-project-html/SKILL.md +56 -20
  8. package/init-project-html/agents/openai.yaml +6 -6
  9. package/init-project-html/lib/atlas/assets/architecture.css +27 -6
  10. package/init-project-html/lib/atlas/assets/viewer.client.js +124 -81
  11. package/init-project-html/lib/atlas/cli.js +48 -15
  12. package/init-project-html/lib/atlas/layout.js +112 -11
  13. package/init-project-html/lib/atlas/render.js +131 -33
  14. package/init-project-html/lib/atlas/schema.js +39 -2
  15. package/init-project-html/references/TEMPLATE_SPEC.md +26 -8
  16. package/init-project-html/sample-demo/resources/project-architecture/assets/architecture.css +27 -6
  17. package/init-project-html/sample-demo/resources/project-architecture/assets/viewer.client.js +124 -81
  18. package/init-project-html/sample-demo/resources/project-architecture/atlas/features/get-invite-codes.yaml +17 -4
  19. package/init-project-html/sample-demo/resources/project-architecture/features/get-invite-codes/invite-code-generator.html +23 -7
  20. package/init-project-html/sample-demo/resources/project-architecture/features/get-invite-codes/invite-issuance-service.html +45 -13
  21. package/init-project-html/sample-demo/resources/project-architecture/features/get-invite-codes/postgresql.html +28 -10
  22. package/init-project-html/sample-demo/resources/project-architecture/features/get-invite-codes/public-api.html +33 -13
  23. package/init-project-html/sample-demo/resources/project-architecture/features/get-invite-codes/web-get-invite-ui.html +28 -10
  24. package/init-project-html/sample-demo/resources/project-architecture/features/invite-code-registration/postgresql.html +28 -10
  25. package/init-project-html/sample-demo/resources/project-architecture/features/invite-code-registration/public-api.html +28 -10
  26. package/init-project-html/sample-demo/resources/project-architecture/features/invite-code-registration/registration-service.html +38 -17
  27. package/init-project-html/sample-demo/resources/project-architecture/features/invite-code-registration/web-register-ui.html +29 -11
  28. package/init-project-html/sample-demo/resources/project-architecture/index.html +100 -76
  29. package/init-project-html/scripts/architecture-bootstrap-render.js +16 -0
  30. package/init-project-html/scripts/architecture.js +22 -12
  31. package/katex/scripts/__pycache__/render_katex.cpython-312.pyc +0 -0
  32. package/open-github-issue/scripts/__pycache__/open_github_issue.cpython-312.pyc +0 -0
  33. package/package.json +1 -1
  34. package/read-github-issue/scripts/__pycache__/find_issues.cpython-312.pyc +0 -0
  35. package/read-github-issue/scripts/__pycache__/read_issue.cpython-312.pyc +0 -0
  36. package/resolve-review-comments/scripts/__pycache__/review_threads.cpython-312.pyc +0 -0
  37. package/spec-to-project-html/SKILL.md +25 -16
  38. package/spec-to-project-html/agents/openai.yaml +5 -5
  39. package/spec-to-project-html/references/TEMPLATE_SPEC.md +2 -0
  40. package/text-to-short-video/scripts/__pycache__/enforce_video_aspect_ratio.cpython-312.pyc +0 -0
@@ -74,11 +74,17 @@ p { line-height: 1.55; color: var(--text); }
74
74
  /* ---- SVG macro ---- */
75
75
  .m-cluster__bg { fill: rgba(15, 23, 42, 0.55); stroke: var(--border); stroke-width: 1; }
76
76
  .m-cluster__title { font-family: ui-sans-serif, system-ui, sans-serif; font-size: 14px; fill: var(--text); font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase; }
77
- .m-node rect { fill: var(--panel-soft); stroke: var(--border); stroke-width: 1; transition: stroke 120ms ease; }
78
- .m-node:hover rect { stroke: var(--accent); }
77
+ .m-node { cursor: pointer; }
78
+ .m-node rect { fill: var(--panel-soft); stroke: var(--border); stroke-width: 1; transition: stroke 120ms ease, fill 120ms ease; }
79
+ .m-node:hover rect,
80
+ .m-node:focus rect,
81
+ .m-node:focus-visible rect { stroke: var(--accent); stroke-width: 1.6; fill: rgba(56, 189, 248, 0.08); }
82
+ .m-node:focus { outline: none; }
79
83
  .m-node__title { font-size: 13px; font-weight: 600; fill: var(--text); }
80
84
  .m-node__kind { font-size: 11px; fill: var(--muted); }
81
85
  .m-node__role { font-size: 11px; fill: var(--muted); }
86
+ .m-node:hover .m-node__title,
87
+ .m-node:focus .m-node__title { fill: var(--accent); }
82
88
 
83
89
  .m-node--ui rect { stroke: var(--kind-ui); }
84
90
  .m-node--api rect { stroke: var(--kind-api); }
@@ -133,8 +139,23 @@ p { line-height: 1.55; color: var(--text); }
133
139
 
134
140
  .sub-section__empty { color: var(--muted); font-style: italic; font-size: 13px; }
135
141
 
136
- .sub-dataflow__svg { width: 100%; max-width: 480px; }
137
- .sub-dataflow__step rect { fill: var(--panel); stroke: var(--border); }
138
- .sub-dataflow__step text { fill: var(--text); font-size: 13px; }
139
- .sub-dataflow__arrow { stroke: var(--muted); stroke-width: 1.4; }
142
+ .sub-dataflow__canvas { position: relative; background: var(--panel); border: 1px solid var(--border); border-radius: 12px; padding: 16px; }
143
+ .sub-dataflow__toolbar { position: absolute; top: 16px; right: 16px; display: flex; gap: 4px; z-index: 2; }
144
+ .sub-dataflow__toolbar button { background: var(--panel-soft); color: var(--text); border: 1px solid var(--border); padding: 4px 12px; border-radius: 6px; cursor: pointer; font-size: 13px; }
145
+ .sub-dataflow__toolbar button:hover { border-color: var(--accent); color: var(--accent); }
146
+ .sub-dataflow__viewport { width: 100%; max-height: 60vh; overflow: hidden; border-radius: 8px; background: #0b1220; }
147
+ .sub-dataflow__viewport.is-grabbing { cursor: grabbing; }
148
+ .sub-dataflow__viewport:not(.is-grabbing) { cursor: grab; }
149
+ .sub-dataflow__svg { width: 100%; height: auto; max-height: 60vh; display: block; user-select: none; touch-action: none; }
150
+ .sub-dataflow__badge { fill: var(--panel-soft); stroke: var(--accent); stroke-width: 1.4; }
151
+ .sub-dataflow__badge-text { font-family: ui-sans-serif, system-ui, sans-serif; font-size: 13px; font-weight: 600; fill: var(--accent); }
152
+ .sub-dataflow__step .sub-dataflow__box { fill: var(--panel-soft); stroke: var(--border); stroke-width: 1; }
153
+ .sub-dataflow__step:hover .sub-dataflow__box { stroke: var(--accent); }
154
+ .sub-dataflow__text { font-family: ui-sans-serif, system-ui, sans-serif; font-size: 14px; fill: var(--text); }
155
+ .sub-dataflow__arrow { stroke: var(--muted); stroke-width: 1.6; }
156
+ .sub-dataflow__fn-bg { fill: rgba(56, 189, 248, 0.12); stroke: var(--accent); stroke-width: 1; }
157
+ .sub-dataflow__fn-text { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11px; font-weight: 600; fill: var(--accent); letter-spacing: 0.02em; }
158
+ .sub-dataflow__chip { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11px; font-weight: 500; }
159
+ .sub-dataflow__chip--reads { fill: var(--kind-service); }
160
+ .sub-dataflow__chip--writes { fill: var(--kind-db); }
140
161
  .sub-dataflow__empty { color: var(--muted); font-style: italic; }
@@ -1,93 +1,136 @@
1
- /* viewer.client.js — pan/zoom for the macro atlas SVG. No deps; wires
2
- * onto any element marked [data-pan-zoom-viewport] containing one
3
- * [data-atlas-svg] SVG. Mouse wheel zooms around the cursor; drag pans;
4
- * toolbar buttons handle +/-/Fit; keyboard arrows pan. */
1
+ /* viewer.client.js — pan/zoom for every atlas SVG on the page.
2
+ *
3
+ * Each `[data-pan-zoom-viewport]` element gets its own state and
4
+ * handlers, so a single page can host the macro atlas SVG AND
5
+ * sub-module-internal dataflow SVGs simultaneously. Toolbar buttons
6
+ * (`[data-pan-zoom="zoom-in|zoom-out|fit"]`) are scoped to the
7
+ * containing `[data-pan-zoom-container]` (falls back to the viewport's
8
+ * direct parent), so multiple toolbars on one page do not collide.
9
+ * Keyboard shortcuts (`←` / `→` / `↑` / `↓` / `+` / `−` / `0`) drive
10
+ * the page's first viewport — the "primary" diagram of that page
11
+ * (macro SVG on `index.html`; sub-dataflow SVG on a sub-module page).
12
+ */
5
13
 
6
14
  (function () {
7
15
  'use strict';
8
16
 
9
- const viewport = document.querySelector('[data-pan-zoom-viewport]');
10
- if (!viewport) return;
11
- const svg = viewport.querySelector('[data-atlas-svg]');
12
- if (!svg) return;
17
+ const viewports = Array.from(document.querySelectorAll('[data-pan-zoom-viewport]'));
18
+ const controllers = viewports.map(setupViewport).filter(Boolean);
19
+ if (controllers.length === 0) return;
13
20
 
14
- const initial = svg.getAttribute('viewBox');
15
- if (!initial) return;
16
- const [ix, iy, iw, ih] = initial.split(/\s+/).map(Number);
17
- const state = { x: ix, y: iy, w: iw, h: ih };
18
-
19
- function apply() {
20
- svg.setAttribute('viewBox', `${state.x} ${state.y} ${state.w} ${state.h}`);
21
- }
21
+ const primary = controllers[0];
22
+ document.addEventListener('keydown', function (evt) {
23
+ if (evt.target && (evt.target.tagName === 'INPUT' || evt.target.tagName === 'TEXTAREA')) return;
24
+ if (evt.key === 'ArrowLeft') { primary.pan(-1, 0); }
25
+ else if (evt.key === 'ArrowRight') { primary.pan(1, 0); }
26
+ else if (evt.key === 'ArrowUp') { primary.pan(0, -1); }
27
+ else if (evt.key === 'ArrowDown') { primary.pan(0, 1); }
28
+ else if (evt.key === '+' || evt.key === '=') { primary.zoom(1 / 1.2); }
29
+ else if (evt.key === '-' || evt.key === '_') { primary.zoom(1.2); }
30
+ else if (evt.key === '0') { primary.fit(); }
31
+ });
22
32
 
23
- function zoom(factor, cx, cy) {
24
- const newW = Math.max(40, Math.min(state.w * factor, iw * 8));
25
- const newH = newW * (state.h / state.w);
26
- if (cx == null) { cx = state.x + state.w / 2; cy = state.y + state.h / 2; }
27
- state.x = cx - (cx - state.x) * (newW / state.w);
28
- state.y = cy - (cy - state.y) * (newH / state.h);
29
- state.w = newW;
30
- state.h = newH;
31
- apply();
32
- }
33
+ function setupViewport(viewport) {
34
+ const svg = viewport.querySelector('[data-atlas-svg]');
35
+ if (!svg) return null;
36
+ const initial = svg.getAttribute('viewBox');
37
+ if (!initial) return null;
38
+ const [ix, iy, iw, ih] = initial.split(/\s+/).map(Number);
39
+ const state = { x: ix, y: iy, w: iw, h: ih };
33
40
 
34
- function clientToSvg(evt) {
35
- const rect = svg.getBoundingClientRect();
36
- const xRatio = (evt.clientX - rect.left) / rect.width;
37
- const yRatio = (evt.clientY - rect.top) / rect.height;
38
- return { x: state.x + xRatio * state.w, y: state.y + yRatio * state.h };
39
- }
41
+ function apply() {
42
+ svg.setAttribute('viewBox', `${state.x} ${state.y} ${state.w} ${state.h}`);
43
+ }
44
+ function fit() {
45
+ state.x = ix; state.y = iy; state.w = iw; state.h = ih;
46
+ apply();
47
+ }
48
+ function zoom(factor, cx, cy) {
49
+ const newW = Math.max(40, Math.min(state.w * factor, iw * 8));
50
+ const newH = newW * (state.h / state.w);
51
+ if (cx == null) { cx = state.x + state.w / 2; cy = state.y + state.h / 2; }
52
+ state.x = cx - (cx - state.x) * (newW / state.w);
53
+ state.y = cy - (cy - state.y) * (newH / state.h);
54
+ state.w = newW;
55
+ state.h = newH;
56
+ apply();
57
+ }
58
+ function pan(dirX, dirY) {
59
+ const stepX = state.w * 0.08;
60
+ const stepY = state.h * 0.08;
61
+ state.x += dirX * stepX;
62
+ state.y += dirY * stepY;
63
+ apply();
64
+ }
65
+ function clientToSvg(evt) {
66
+ const rect = svg.getBoundingClientRect();
67
+ const xRatio = (evt.clientX - rect.left) / rect.width;
68
+ const yRatio = (evt.clientY - rect.top) / rect.height;
69
+ return { x: state.x + xRatio * state.w, y: state.y + yRatio * state.h };
70
+ }
40
71
 
41
- viewport.addEventListener('wheel', function (evt) {
42
- if (!evt.ctrlKey && !evt.metaKey && Math.abs(evt.deltaY) < 4 && Math.abs(evt.deltaX) < 4) return;
43
- evt.preventDefault();
44
- const factor = evt.deltaY > 0 ? 1.1 : 1 / 1.1;
45
- const pt = clientToSvg(evt);
46
- zoom(factor, pt.x, pt.y);
47
- }, { passive: false });
72
+ viewport.addEventListener('wheel', function (evt) {
73
+ if (!evt.ctrlKey && !evt.metaKey && Math.abs(evt.deltaY) < 4 && Math.abs(evt.deltaX) < 4) return;
74
+ evt.preventDefault();
75
+ const factor = evt.deltaY > 0 ? 1.1 : 1 / 1.1;
76
+ const pt = clientToSvg(evt);
77
+ zoom(factor, pt.x, pt.y);
78
+ }, { passive: false });
48
79
 
49
- let dragging = null;
50
- viewport.addEventListener('pointerdown', function (evt) {
51
- if (evt.button !== 0) return;
52
- dragging = { x: evt.clientX, y: evt.clientY };
53
- viewport.classList.add('is-grabbing');
54
- viewport.setPointerCapture(evt.pointerId);
55
- });
56
- viewport.addEventListener('pointermove', function (evt) {
57
- if (!dragging) return;
58
- const rect = svg.getBoundingClientRect();
59
- const dx = ((evt.clientX - dragging.x) / rect.width) * state.w;
60
- const dy = ((evt.clientY - dragging.y) / rect.height) * state.h;
61
- state.x -= dx;
62
- state.y -= dy;
63
- dragging = { x: evt.clientX, y: evt.clientY };
64
- apply();
65
- });
66
- function endDrag(evt) {
67
- if (!dragging) return;
68
- dragging = null;
69
- viewport.classList.remove('is-grabbing');
70
- try { viewport.releasePointerCapture(evt.pointerId); } catch (e) { /* ignore */ }
71
- }
72
- viewport.addEventListener('pointerup', endDrag);
73
- viewport.addEventListener('pointercancel', endDrag);
74
- viewport.addEventListener('pointerleave', endDrag);
80
+ // Drag-pan: defer the pointer capture (and the "is-grabbing" class)
81
+ // until the pointer actually moves past a small threshold. Without
82
+ // this, a single click on an SVG <a> (sub-module link) would be
83
+ // captured by the viewport and never reach the link.
84
+ const DRAG_THRESHOLD_PX = 4;
85
+ let pending = null;
86
+ let dragging = null;
87
+ viewport.addEventListener('pointerdown', function (evt) {
88
+ if (evt.button !== 0) return;
89
+ pending = { x: evt.clientX, y: evt.clientY, pointerId: evt.pointerId };
90
+ });
91
+ viewport.addEventListener('pointermove', function (evt) {
92
+ if (!dragging && pending && pending.pointerId === evt.pointerId) {
93
+ const dx = evt.clientX - pending.x;
94
+ const dy = evt.clientY - pending.y;
95
+ if (Math.hypot(dx, dy) < DRAG_THRESHOLD_PX) return;
96
+ dragging = { x: pending.x, y: pending.y, pointerId: pending.pointerId };
97
+ pending = null;
98
+ viewport.classList.add('is-grabbing');
99
+ try { viewport.setPointerCapture(dragging.pointerId); } catch (e) { /* ignore */ }
100
+ }
101
+ if (!dragging || dragging.pointerId !== evt.pointerId) return;
102
+ evt.preventDefault();
103
+ const rect = svg.getBoundingClientRect();
104
+ const dx = ((evt.clientX - dragging.x) / rect.width) * state.w;
105
+ const dy = ((evt.clientY - dragging.y) / rect.height) * state.h;
106
+ state.x -= dx;
107
+ state.y -= dy;
108
+ dragging.x = evt.clientX;
109
+ dragging.y = evt.clientY;
110
+ apply();
111
+ });
112
+ function endDrag(evt) {
113
+ pending = null;
114
+ if (!dragging) return;
115
+ const draggedId = dragging.pointerId;
116
+ dragging = null;
117
+ viewport.classList.remove('is-grabbing');
118
+ try { viewport.releasePointerCapture(draggedId); } catch (e) { /* ignore */ }
119
+ // Suppress the synthetic click that would follow a drag-release
120
+ // on top of a sub-module <a>; only the no-movement case should
121
+ // navigate.
122
+ const swallow = (e) => { e.preventDefault(); e.stopPropagation(); };
123
+ viewport.addEventListener('click', swallow, { capture: true, once: true });
124
+ }
125
+ viewport.addEventListener('pointerup', endDrag);
126
+ viewport.addEventListener('pointercancel', endDrag);
127
+ viewport.addEventListener('pointerleave', endDrag);
75
128
 
76
- document.addEventListener('keydown', function (evt) {
77
- if (evt.target && (evt.target.tagName === 'INPUT' || evt.target.tagName === 'TEXTAREA')) return;
78
- const step = state.w * 0.08;
79
- if (evt.key === 'ArrowLeft') { state.x -= step; apply(); }
80
- else if (evt.key === 'ArrowRight') { state.x += step; apply(); }
81
- else if (evt.key === 'ArrowUp') { state.y -= step; apply(); }
82
- else if (evt.key === 'ArrowDown') { state.y += step; apply(); }
83
- else if (evt.key === '+' || evt.key === '=') { zoom(1 / 1.2); }
84
- else if (evt.key === '-' || evt.key === '_') { zoom(1.2); }
85
- else if (evt.key === '0') { state.x = ix; state.y = iy; state.w = iw; state.h = ih; apply(); }
86
- });
129
+ const container = viewport.closest('[data-pan-zoom-container]') || viewport.parentElement || document;
130
+ container.querySelectorAll('[data-pan-zoom="zoom-in"]').forEach((btn) => btn.addEventListener('click', () => zoom(1 / 1.2)));
131
+ container.querySelectorAll('[data-pan-zoom="zoom-out"]').forEach((btn) => btn.addEventListener('click', () => zoom(1.2)));
132
+ container.querySelectorAll('[data-pan-zoom="fit"]').forEach((btn) => btn.addEventListener('click', fit));
87
133
 
88
- document.querySelectorAll('[data-pan-zoom="zoom-in"]').forEach((btn) => btn.addEventListener('click', () => zoom(1 / 1.2)));
89
- document.querySelectorAll('[data-pan-zoom="zoom-out"]').forEach((btn) => btn.addEventListener('click', () => zoom(1.2)));
90
- document.querySelectorAll('[data-pan-zoom="fit"]').forEach((btn) => btn.addEventListener('click', () => {
91
- state.x = ix; state.y = iy; state.w = iw; state.h = ih; apply();
92
- }));
134
+ return { zoom, fit, pan };
135
+ }
93
136
  })();
@@ -83,10 +83,23 @@ submodules:
83
83
  scope: tx
84
84
  purpose: Persisted invite_codes row carrying owner, code, expiry, and consumption state.
85
85
  dataflow:
86
- - Open transaction.
87
- - Generate candidate code via generateCode.
88
- - INSERT invite_codes row (retry on unique-violation).
89
- - Commit and return code.
86
+ - step: Open transaction.
87
+ fn: Issue
88
+ - step: Build candidate code from random bytes.
89
+ fn: generateCode
90
+ writes:
91
+ - code
92
+ - step: Persist row via INSERT (retry on unique-violation).
93
+ fn: Issue
94
+ reads:
95
+ - code
96
+ writes:
97
+ - row
98
+ - step: Commit transaction and return code to caller.
99
+ fn: Issue
100
+ reads:
101
+ - code
102
+ - row
90
103
  errors:
91
104
  - name: ErrCollision
92
105
  when: Unique constraint on code repeatedly violated.
@@ -28,23 +28,39 @@
28
28
  </section>
29
29
  <section class="sub-dataflow" aria-label="Internal data flow">
30
30
  <h2>Internal data flow</h2>
31
- <svg class="sub-dataflow__svg" viewBox="0 0 400 180" role="img" aria-label="Internal dataflow">
32
- <defs><marker id="sub-arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="8" markerHeight="8" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 Z" /></marker></defs>
31
+ <div class="sub-dataflow__canvas" data-pan-zoom-container>
32
+ <div class="sub-dataflow__toolbar" role="toolbar" aria-label="Diagram controls">
33
+ <button type="button" data-pan-zoom="zoom-in" aria-label="Zoom in">+</button>
34
+ <button type="button" data-pan-zoom="zoom-out" aria-label="Zoom out">−</button>
35
+ <button type="button" data-pan-zoom="fit" aria-label="Reset view">Fit</button>
36
+ </div>
37
+ <div class="sub-dataflow__viewport" data-pan-zoom-viewport>
38
+ <svg class="sub-dataflow__svg" data-atlas-svg="sub-dataflow" viewBox="0 0 628 252" role="img" aria-label="Internal dataflow">
39
+ <defs>
40
+ <marker id="sub-arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="9" markerHeight="9" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 Z" /></marker>
41
+ </defs>
33
42
  <g class="sub-dataflow__step">
34
- <rect x="20" y="20" width="360" height="56" rx="8" ry="8" />
35
- <text x="200" y="44" text-anchor="middle">Read 8 random bytes.</text>
43
+ <circle class="sub-dataflow__badge" cx="42" cy="68" r="18" />
44
+ <text class="sub-dataflow__badge-text" x="42" y="73" text-anchor="middle">1</text>
45
+ <rect class="sub-dataflow__box" x="80" y="32" width="520" height="72" rx="14" ry="14" />
46
+ <text class="sub-dataflow__text" x="340" y="74" text-anchor="middle">Read 8 random bytes.</text>
36
47
  </g>
37
- <line class="sub-dataflow__arrow" x1="200" y1="76" x2="200" y2="104" marker-end="url(#sub-arrow)" />
48
+ <line class="sub-dataflow__arrow" x1="340" y1="110" x2="340" y2="140" marker-end="url(#sub-arrow)" />
38
49
  <g class="sub-dataflow__step">
39
- <rect x="20" y="104" width="360" height="56" rx="8" ry="8" />
40
- <text x="200" y="128" text-anchor="middle">Base32 encode without padding.</text>
50
+ <circle class="sub-dataflow__badge" cx="42" cy="184" r="18" />
51
+ <text class="sub-dataflow__badge-text" x="42" y="189" text-anchor="middle">2</text>
52
+ <rect class="sub-dataflow__box" x="80" y="148" width="520" height="72" rx="14" ry="14" />
53
+ <text class="sub-dataflow__text" x="340" y="190" text-anchor="middle">Base32 encode without padding.</text>
41
54
  </g>
42
55
  </svg>
56
+ </div>
57
+ </div>
43
58
  </section>
44
59
  <section class="sub-errors" aria-label="Errors">
45
60
  <h2>Errors</h2>
46
61
  <p class="sub-section__empty">No errors recorded.</p>
47
62
  </section>
48
63
  </main>
64
+ <script src="../../assets/viewer.client.js" defer></script>
49
65
  </body>
50
66
  </html>
@@ -35,28 +35,59 @@
35
35
  </section>
36
36
  <section class="sub-dataflow" aria-label="Internal data flow">
37
37
  <h2>Internal data flow</h2>
38
- <svg class="sub-dataflow__svg" viewBox="0 0 400 348" role="img" aria-label="Internal dataflow">
39
- <defs><marker id="sub-arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="8" markerHeight="8" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 Z" /></marker></defs>
38
+ <div class="sub-dataflow__canvas" data-pan-zoom-container>
39
+ <div class="sub-dataflow__toolbar" role="toolbar" aria-label="Diagram controls">
40
+ <button type="button" data-pan-zoom="zoom-in" aria-label="Zoom in">+</button>
41
+ <button type="button" data-pan-zoom="zoom-out" aria-label="Zoom out">−</button>
42
+ <button type="button" data-pan-zoom="fit" aria-label="Reset view">Fit</button>
43
+ </div>
44
+ <div class="sub-dataflow__viewport" data-pan-zoom-viewport>
45
+ <svg class="sub-dataflow__svg" data-atlas-svg="sub-dataflow" viewBox="0 0 628 626" role="img" aria-label="Internal dataflow">
46
+ <defs>
47
+ <marker id="sub-arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="9" markerHeight="9" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 Z" /></marker>
48
+ </defs>
40
49
  <g class="sub-dataflow__step">
41
- <rect x="20" y="20" width="360" height="56" rx="8" ry="8" />
42
- <text x="200" y="44" text-anchor="middle">Open transaction.</text>
50
+ <circle class="sub-dataflow__badge" cx="42" cy="76" r="18" />
51
+ <text class="sub-dataflow__badge-text" x="42" y="81" text-anchor="middle">1</text>
52
+ <rect class="sub-dataflow__box" x="80" y="32" width="520" height="88" rx="14" ry="14" />
53
+ <rect class="sub-dataflow__fn-bg" x="94" y="46" width="79.2" height="20" rx="10" ry="10" />
54
+ <text class="sub-dataflow__fn-text" x="104" y="60">fn Issue</text>
55
+ <text class="sub-dataflow__text" x="340" y="98" text-anchor="middle">Open transaction.</text>
43
56
  </g>
44
- <line class="sub-dataflow__arrow" x1="200" y1="76" x2="200" y2="104" marker-end="url(#sub-arrow)" />
57
+ <line class="sub-dataflow__arrow" x1="340" y1="126" x2="340" y2="156" marker-end="url(#sub-arrow)" />
45
58
  <g class="sub-dataflow__step">
46
- <rect x="20" y="104" width="360" height="56" rx="8" ry="8" />
47
- <text x="200" y="128" text-anchor="middle">Generate candidate code via generateCode.</text>
59
+ <circle class="sub-dataflow__badge" cx="42" cy="221" r="18" />
60
+ <text class="sub-dataflow__badge-text" x="42" y="226" text-anchor="middle">2</text>
61
+ <rect class="sub-dataflow__box" x="80" y="164" width="520" height="114" rx="14" ry="14" />
62
+ <rect class="sub-dataflow__fn-bg" x="94" y="178" width="131" height="20" rx="10" ry="10" />
63
+ <text class="sub-dataflow__fn-text" x="104" y="192">fn generateCode</text>
64
+ <text class="sub-dataflow__text" x="340" y="230" text-anchor="middle">Build candidate code from random bytes.</text>
65
+ <text class="sub-dataflow__chip sub-dataflow__chip--writes" x="586" y="266" text-anchor="end">→ writes: code</text>
48
66
  </g>
49
- <line class="sub-dataflow__arrow" x1="200" y1="160" x2="200" y2="188" marker-end="url(#sub-arrow)" />
67
+ <line class="sub-dataflow__arrow" x1="340" y1="284" x2="340" y2="314" marker-end="url(#sub-arrow)" />
50
68
  <g class="sub-dataflow__step">
51
- <rect x="20" y="188" width="360" height="56" rx="8" ry="8" />
52
- <text x="200" y="212" text-anchor="middle">INSERT invite_codes row (retry on unique-violation).</text>
69
+ <circle class="sub-dataflow__badge" cx="42" cy="379" r="18" />
70
+ <text class="sub-dataflow__badge-text" x="42" y="384" text-anchor="middle">3</text>
71
+ <rect class="sub-dataflow__box" x="80" y="322" width="520" height="114" rx="14" ry="14" />
72
+ <rect class="sub-dataflow__fn-bg" x="94" y="336" width="79.2" height="20" rx="10" ry="10" />
73
+ <text class="sub-dataflow__fn-text" x="104" y="350">fn Issue</text>
74
+ <text class="sub-dataflow__text" x="340" y="388" text-anchor="middle">Persist row via INSERT (retry on unique-violation).</text>
75
+ <text class="sub-dataflow__chip sub-dataflow__chip--reads" x="94" y="424">← reads: code</text>
76
+ <text class="sub-dataflow__chip sub-dataflow__chip--writes" x="586" y="424" text-anchor="end">→ writes: row</text>
53
77
  </g>
54
- <line class="sub-dataflow__arrow" x1="200" y1="244" x2="200" y2="272" marker-end="url(#sub-arrow)" />
78
+ <line class="sub-dataflow__arrow" x1="340" y1="442" x2="340" y2="472" marker-end="url(#sub-arrow)" />
55
79
  <g class="sub-dataflow__step">
56
- <rect x="20" y="272" width="360" height="56" rx="8" ry="8" />
57
- <text x="200" y="296" text-anchor="middle">Commit and return code.</text>
80
+ <circle class="sub-dataflow__badge" cx="42" cy="537" r="18" />
81
+ <text class="sub-dataflow__badge-text" x="42" y="542" text-anchor="middle">4</text>
82
+ <rect class="sub-dataflow__box" x="80" y="480" width="520" height="114" rx="14" ry="14" />
83
+ <rect class="sub-dataflow__fn-bg" x="94" y="494" width="79.2" height="20" rx="10" ry="10" />
84
+ <text class="sub-dataflow__fn-text" x="104" y="508">fn Issue</text>
85
+ <text class="sub-dataflow__text" x="340" y="546" text-anchor="middle">Commit transaction and return code to caller.</text>
86
+ <text class="sub-dataflow__chip sub-dataflow__chip--reads" x="94" y="582">← reads: code, row</text>
58
87
  </g>
59
88
  </svg>
89
+ </div>
90
+ </div>
60
91
  </section>
61
92
  <section class="sub-errors" aria-label="Errors">
62
93
  <h2>Errors</h2>
@@ -68,5 +99,6 @@
68
99
  </table>
69
100
  </section>
70
101
  </main>
102
+ <script src="../../assets/viewer.client.js" defer></script>
71
103
  </body>
72
104
  </html>
@@ -34,23 +34,40 @@
34
34
  </section>
35
35
  <section class="sub-dataflow" aria-label="Internal data flow">
36
36
  <h2>Internal data flow</h2>
37
- <svg class="sub-dataflow__svg" viewBox="0 0 400 264" role="img" aria-label="Internal dataflow">
38
- <defs><marker id="sub-arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="8" markerHeight="8" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 Z" /></marker></defs>
37
+ <div class="sub-dataflow__canvas" data-pan-zoom-container>
38
+ <div class="sub-dataflow__toolbar" role="toolbar" aria-label="Diagram controls">
39
+ <button type="button" data-pan-zoom="zoom-in" aria-label="Zoom in">+</button>
40
+ <button type="button" data-pan-zoom="zoom-out" aria-label="Zoom out">−</button>
41
+ <button type="button" data-pan-zoom="fit" aria-label="Reset view">Fit</button>
42
+ </div>
43
+ <div class="sub-dataflow__viewport" data-pan-zoom-viewport>
44
+ <svg class="sub-dataflow__svg" data-atlas-svg="sub-dataflow" viewBox="0 0 628 368" role="img" aria-label="Internal dataflow">
45
+ <defs>
46
+ <marker id="sub-arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="9" markerHeight="9" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 Z" /></marker>
47
+ </defs>
39
48
  <g class="sub-dataflow__step">
40
- <rect x="20" y="20" width="360" height="56" rx="8" ry="8" />
41
- <text x="200" y="44" text-anchor="middle">Accept INSERT from invite-issuance-service.</text>
49
+ <circle class="sub-dataflow__badge" cx="42" cy="68" r="18" />
50
+ <text class="sub-dataflow__badge-text" x="42" y="73" text-anchor="middle">1</text>
51
+ <rect class="sub-dataflow__box" x="80" y="32" width="520" height="72" rx="14" ry="14" />
52
+ <text class="sub-dataflow__text" x="340" y="74" text-anchor="middle">Accept INSERT from invite-issuance-service.</text>
42
53
  </g>
43
- <line class="sub-dataflow__arrow" x1="200" y1="76" x2="200" y2="104" marker-end="url(#sub-arrow)" />
54
+ <line class="sub-dataflow__arrow" x1="340" y1="110" x2="340" y2="140" marker-end="url(#sub-arrow)" />
44
55
  <g class="sub-dataflow__step">
45
- <rect x="20" y="104" width="360" height="56" rx="8" ry="8" />
46
- <text x="200" y="128" text-anchor="middle">Enforce unique constraint on code.</text>
56
+ <circle class="sub-dataflow__badge" cx="42" cy="184" r="18" />
57
+ <text class="sub-dataflow__badge-text" x="42" y="189" text-anchor="middle">2</text>
58
+ <rect class="sub-dataflow__box" x="80" y="148" width="520" height="72" rx="14" ry="14" />
59
+ <text class="sub-dataflow__text" x="340" y="190" text-anchor="middle">Enforce unique constraint on code.</text>
47
60
  </g>
48
- <line class="sub-dataflow__arrow" x1="200" y1="160" x2="200" y2="188" marker-end="url(#sub-arrow)" />
61
+ <line class="sub-dataflow__arrow" x1="340" y1="226" x2="340" y2="256" marker-end="url(#sub-arrow)" />
49
62
  <g class="sub-dataflow__step">
50
- <rect x="20" y="188" width="360" height="56" rx="8" ry="8" />
51
- <text x="200" y="212" text-anchor="middle">Return new rowid.</text>
63
+ <circle class="sub-dataflow__badge" cx="42" cy="300" r="18" />
64
+ <text class="sub-dataflow__badge-text" x="42" y="305" text-anchor="middle">3</text>
65
+ <rect class="sub-dataflow__box" x="80" y="264" width="520" height="72" rx="14" ry="14" />
66
+ <text class="sub-dataflow__text" x="340" y="306" text-anchor="middle">Return new rowid.</text>
52
67
  </g>
53
68
  </svg>
69
+ </div>
70
+ </div>
54
71
  </section>
55
72
  <section class="sub-errors" aria-label="Errors">
56
73
  <h2>Errors</h2>
@@ -62,5 +79,6 @@
62
79
  </table>
63
80
  </section>
64
81
  </main>
82
+ <script src="../../assets/viewer.client.js" defer></script>
65
83
  </body>
66
84
  </html>
@@ -33,28 +33,47 @@
33
33
  </section>
34
34
  <section class="sub-dataflow" aria-label="Internal data flow">
35
35
  <h2>Internal data flow</h2>
36
- <svg class="sub-dataflow__svg" viewBox="0 0 400 348" role="img" aria-label="Internal dataflow">
37
- <defs><marker id="sub-arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="8" markerHeight="8" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 Z" /></marker></defs>
36
+ <div class="sub-dataflow__canvas" data-pan-zoom-container>
37
+ <div class="sub-dataflow__toolbar" role="toolbar" aria-label="Diagram controls">
38
+ <button type="button" data-pan-zoom="zoom-in" aria-label="Zoom in">+</button>
39
+ <button type="button" data-pan-zoom="zoom-out" aria-label="Zoom out">−</button>
40
+ <button type="button" data-pan-zoom="fit" aria-label="Reset view">Fit</button>
41
+ </div>
42
+ <div class="sub-dataflow__viewport" data-pan-zoom-viewport>
43
+ <svg class="sub-dataflow__svg" data-atlas-svg="sub-dataflow" viewBox="0 0 628 484" role="img" aria-label="Internal dataflow">
44
+ <defs>
45
+ <marker id="sub-arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="9" markerHeight="9" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 Z" /></marker>
46
+ </defs>
38
47
  <g class="sub-dataflow__step">
39
- <rect x="20" y="20" width="360" height="56" rx="8" ry="8" />
40
- <text x="200" y="44" text-anchor="middle">Validate Authorization header.</text>
48
+ <circle class="sub-dataflow__badge" cx="42" cy="68" r="18" />
49
+ <text class="sub-dataflow__badge-text" x="42" y="73" text-anchor="middle">1</text>
50
+ <rect class="sub-dataflow__box" x="80" y="32" width="520" height="72" rx="14" ry="14" />
51
+ <text class="sub-dataflow__text" x="340" y="74" text-anchor="middle">Validate Authorization header.</text>
41
52
  </g>
42
- <line class="sub-dataflow__arrow" x1="200" y1="76" x2="200" y2="104" marker-end="url(#sub-arrow)" />
53
+ <line class="sub-dataflow__arrow" x1="340" y1="110" x2="340" y2="140" marker-end="url(#sub-arrow)" />
43
54
  <g class="sub-dataflow__step">
44
- <rect x="20" y="104" width="360" height="56" rx="8" ry="8" />
45
- <text x="200" y="128" text-anchor="middle">Resolve userId from token.</text>
55
+ <circle class="sub-dataflow__badge" cx="42" cy="184" r="18" />
56
+ <text class="sub-dataflow__badge-text" x="42" y="189" text-anchor="middle">2</text>
57
+ <rect class="sub-dataflow__box" x="80" y="148" width="520" height="72" rx="14" ry="14" />
58
+ <text class="sub-dataflow__text" x="340" y="190" text-anchor="middle">Resolve userId from token.</text>
46
59
  </g>
47
- <line class="sub-dataflow__arrow" x1="200" y1="160" x2="200" y2="188" marker-end="url(#sub-arrow)" />
60
+ <line class="sub-dataflow__arrow" x1="340" y1="226" x2="340" y2="256" marker-end="url(#sub-arrow)" />
48
61
  <g class="sub-dataflow__step">
49
- <rect x="20" y="188" width="360" height="56" rx="8" ry="8" />
50
- <text x="200" y="212" text-anchor="middle">Call invite-issuance-service.Issue.</text>
62
+ <circle class="sub-dataflow__badge" cx="42" cy="300" r="18" />
63
+ <text class="sub-dataflow__badge-text" x="42" y="305" text-anchor="middle">3</text>
64
+ <rect class="sub-dataflow__box" x="80" y="264" width="520" height="72" rx="14" ry="14" />
65
+ <text class="sub-dataflow__text" x="340" y="306" text-anchor="middle">Call invite-issuance-service.Issue.</text>
51
66
  </g>
52
- <line class="sub-dataflow__arrow" x1="200" y1="244" x2="200" y2="272" marker-end="url(#sub-arrow)" />
67
+ <line class="sub-dataflow__arrow" x1="340" y1="342" x2="340" y2="372" marker-end="url(#sub-arrow)" />
53
68
  <g class="sub-dataflow__step">
54
- <rect x="20" y="272" width="360" height="56" rx="8" ry="8" />
55
- <text x="200" y="296" text-anchor="middle">Serialize response.</text>
69
+ <circle class="sub-dataflow__badge" cx="42" cy="416" r="18" />
70
+ <text class="sub-dataflow__badge-text" x="42" y="421" text-anchor="middle">4</text>
71
+ <rect class="sub-dataflow__box" x="80" y="380" width="520" height="72" rx="14" ry="14" />
72
+ <text class="sub-dataflow__text" x="340" y="422" text-anchor="middle">Serialize response.</text>
56
73
  </g>
57
74
  </svg>
75
+ </div>
76
+ </div>
58
77
  </section>
59
78
  <section class="sub-errors" aria-label="Errors">
60
79
  <h2>Errors</h2>
@@ -66,5 +85,6 @@
66
85
  </table>
67
86
  </section>
68
87
  </main>
88
+ <script src="../../assets/viewer.client.js" defer></script>
69
89
  </body>
70
90
  </html>