diff --git a/README.md b/README.md
index 5579bce..cd00f18 100644
--- a/README.md
+++ b/README.md
@@ -156,27 +156,7 @@ This will:
## Architecture
-```
- Kind Cluster
-
- +-----------+ +----------+ +-------------+ +------------+
- | Frontend | | Rust API | | PostGraphile| | JupyterHub |
- | Next.js | | Axum | | GraphQL | | |
- | :31000 | | :31001 | | :31002 | | :31003 |
- +-----+-----+ +----+-----+ +------+------+ +------+-----+
- | | | |
- | +----+---------------+----+ |
- | | PostgreSQL 16 | |
- | | :5432 | |
- | +-------------------------+ |
- | | |
- | +----+-----------+ |
- | | Model Runner | +------------+ |
- | | Pods (ephemeral| | User | |
- | | - Python | | Notebook | |
- | | - Rust | | Pods | |
- | +----------------+ +------------+ |
-```
+
### Components
diff --git a/docs/diagrams/architecture.svg b/docs/diagrams/architecture.svg
new file mode 100644
index 0000000..be8beec
--- /dev/null
+++ b/docs/diagrams/architecture.svg
@@ -0,0 +1,113 @@
+
\ No newline at end of file
diff --git a/docs/diagrams/generate-architecture.js b/docs/diagrams/generate-architecture.js
new file mode 100644
index 0000000..eeb9d4e
--- /dev/null
+++ b/docs/diagrams/generate-architecture.js
@@ -0,0 +1,333 @@
+#!/usr/bin/env node
+/**
+ * OpenModelStudio Architecture Diagram Generator
+ *
+ * Generates a clean, professional SVG architecture diagram with
+ * orthogonal (elbow) connectors and strict grid alignment.
+ *
+ * Run: node docs/diagrams/generate-architecture.js
+ * Output: docs/diagrams/architecture.svg
+ */
+
+const fs = require("fs");
+const path = require("path");
+
+// ── Canvas ────────────────────────────────────────────────────────────
+const W = 1100;
+const H = 700;
+
+// ── Colors ────────────────────────────────────────────────────────────
+const BG = "#0d1117";
+const TEXT_PRIMARY = "#e6edf3";
+const TEXT_SEC = "rgba(230,237,243,0.5)";
+const TEXT_DIM = "rgba(230,237,243,0.3)";
+const LINE_COLOR = "rgba(230,237,243,0.12)";
+
+const C = {
+ violet: { bg: "rgba(139,92,246,0.10)", border: "rgba(139,92,246,0.35)", text: "#a78bfa" },
+ blue: { bg: "rgba(59,130,246,0.10)", border: "rgba(59,130,246,0.35)", text: "#60a5fa" },
+ teal: { bg: "rgba(20,184,166,0.10)", border: "rgba(20,184,166,0.35)", text: "#2dd4bf" },
+ amber: { bg: "rgba(245,158,11,0.08)", border: "rgba(245,158,11,0.30)", text: "#fbbf24" },
+ emerald: { bg: "rgba(16,185,129,0.08)", border: "rgba(16,185,129,0.30)", text: "#34d399" },
+ slate: { bg: "rgba(148,163,184,0.06)", border: "rgba(148,163,184,0.20)", text: "#94a3b8" },
+ storage: { bg: "rgba(255,255,255,0.025)", border: "rgba(255,255,255,0.06)", text: TEXT_SEC },
+};
+
+// ── SVG primitives ────────────────────────────────────────────────────
+const p = []; // SVG parts accumulator
+
+function rect(x, y, w, h, color, { rx = 8, dash = false } = {}) {
+ const d = dash ? ` stroke-dasharray="6,4"` : "";
+ p.push(``);
+}
+
+function txt(x, y, str, { size = 12, fill = TEXT_PRIMARY, bold = false, anchor = "middle", mono = false } = {}) {
+ const fam = mono
+ ? `'JetBrains Mono','SF Mono','Fira Code',monospace`
+ : `'Inter','Segoe UI',system-ui,sans-serif`;
+ const fw = bold ? 600 : 400;
+ p.push(`${str}`);
+}
+
+function label(x, y, str) {
+ txt(x, y, str.toUpperCase(), { size: 9, fill: TEXT_DIM, bold: true, anchor: "start" });
+}
+
+// Service box: colored rect with title, subtitle, optional port
+function svc(x, y, w, h, title, sub, port, color) {
+ rect(x, y, w, h, color);
+ txt(x + w / 2, y + (port ? 20 : h / 2 - 3), title, { size: 12, fill: color.text, bold: true });
+ txt(x + w / 2, y + (port ? 35 : h / 2 + 12), sub, { size: 9, fill: TEXT_SEC });
+ if (port) txt(x + w / 2, y + 50, port, { size: 8, fill: TEXT_DIM, mono: true });
+}
+
+function pill(x, y, w, h, title, color, sub) {
+ rect(x, y, w, h, color, { rx: 6 });
+ txt(x + w / 2, y + (sub ? h / 2 - 4 : h / 2 + 4), title, { size: 10, fill: color.text, bold: true });
+ if (sub) txt(x + w / 2, y + h / 2 + 10, sub, { size: 8, fill: TEXT_DIM });
+}
+
+// ── Elbow connectors ──────────────────────────────────────────────────
+// All connectors use orthogonal paths (only horizontal + vertical segments)
+
+function elbowV(x1, y1, x2, y2, lbl, { color = LINE_COLOR, dash = false, arrow = "end", lblPos = "right" } = {}) {
+ // Vertical-first elbow: go down to midY, then horizontal, then down
+ const midY = (y1 + y2) / 2;
+ const d = `M${x1},${y1} V${midY} H${x2} V${y2}`;
+ const da = dash ? ` stroke-dasharray="4,3"` : "";
+ let markers = "";
+ if (arrow === "end") markers = ` marker-end="url(#ah)"`;
+ if (arrow === "both") markers = ` marker-start="url(#ah-r)" marker-end="url(#ah)"`;
+ p.push(``);
+ if (lbl) {
+ const lx = lblPos === "right" ? Math.max(x1, x2) + 6 : Math.min(x1, x2) - 6;
+ const anc = lblPos === "right" ? "start" : "end";
+ txt(lx, midY + 3, lbl, { size: 8, fill: TEXT_DIM, anchor: anc });
+ }
+}
+
+function elbowH(x1, y1, x2, y2, lbl, { color = LINE_COLOR, dash = false, arrow = "end" } = {}) {
+ // Horizontal-first elbow: go right to midX, then vertical, then right
+ const midX = (x1 + x2) / 2;
+ const d = `M${x1},${y1} H${midX} V${y2} H${x2}`;
+ const da = dash ? ` stroke-dasharray="4,3"` : "";
+ let markers = "";
+ if (arrow === "end") markers = ` marker-end="url(#ah)"`;
+ if (arrow === "both") markers = ` marker-start="url(#ah-r)" marker-end="url(#ah)"`;
+ p.push(``);
+ if (lbl) {
+ txt(midX, Math.min(y1, y2) - 5, lbl, { size: 8, fill: TEXT_DIM });
+ }
+}
+
+function lineH(x1, y, x2, lbl, { color = LINE_COLOR, arrow = "end" } = {}) {
+ let markers = "";
+ if (arrow === "end") markers = ` marker-end="url(#ah)"`;
+ if (arrow === "both") markers = ` marker-start="url(#ah-r)" marker-end="url(#ah)"`;
+ p.push(``);
+ if (lbl) txt((x1 + x2) / 2, y - 6, lbl, { size: 8, fill: TEXT_DIM });
+}
+
+function lineV(x, y1, y2, lbl, { color = LINE_COLOR, arrow = "end", dash = false, lblPos = "right" } = {}) {
+ const da = dash ? ` stroke-dasharray="4,3"` : "";
+ let markers = "";
+ if (arrow === "end") markers = ` marker-end="url(#ah)"`;
+ if (arrow === "both") markers = ` marker-start="url(#ah-r)" marker-end="url(#ah)"`;
+ p.push(``);
+ if (lbl) {
+ const lx = lblPos === "right" ? x + 7 : x - 7;
+ const anc = lblPos === "right" ? "start" : "end";
+ txt(lx, (y1 + y2) / 2 + 3, lbl, { size: 8, fill: TEXT_DIM, anchor: anc });
+ }
+}
+
+// ── GENERATE ──────────────────────────────────────────────────────────
+
+function generate() {
+ p.push(`");
+ return p.join("\n");
+}
+
+// ── Main ──────────────────────────────────────────────────────────────
+const svg = generate();
+const outPath = path.join(__dirname, "architecture.svg");
+fs.writeFileSync(outPath, svg);
+console.log(`Generated ${outPath} (${(svg.length / 1024).toFixed(1)} KB)`);