diff --git a/docs/index.html b/docs/index.html index 9d80e48..f3575d9 100644 --- a/docs/index.html +++ b/docs/index.html @@ -9,10 +9,21 @@ :root{--bg:#0f172a;--bg2:#111827;--bg3:#161e2e;--bg4:#1a2535; --border:#1e293b;--border2:#263347;--text:#f1f5f9;--muted:#64748b;--dim:#334155; --sans:'IBM Plex Sans',system-ui,sans-serif;--mono:'JetBrains Mono',monospace} +:root[data-theme="light"]{--bg:#f8fafc;--bg2:#ffffff;--bg3:#ffffff;--bg4:#f1f5f9; +--border:#e2e8f0;--border2:#cbd5e1;--text:#0f172a;--muted:#64748b;--dim:#94a3b8} +:root[data-theme="light"] body{background-image:radial-gradient(circle,#cbd5e1 1.4px,transparent 1.4px);background-size:24px 24px} +:root[data-theme="light"] h1{color:#0f172a} +:root[data-theme="light"] code{color:#0891b2} +:root[data-theme="light"] .arch-wrap{box-shadow:0 1px 3px rgba(15,23,42,.06)} *{box-sizing:border-box;margin:0;padding:0} -body{background:var(--bg);color:var(--text);font-family:var(--sans);font-size:14px;line-height:1.6} +body{background:var(--bg);color:var(--text);font-family:var(--sans);font-size:14px;line-height:1.6;transition:background .25s,color .25s} .page{max-width:1440px;margin:0 auto;padding:40px 32px 80px} h1{font-size:28px;font-weight:700;color:#f8fafc;margin-bottom:4px} +.theme-toggle{position:fixed;top:20px;right:24px;width:40px;height:40px;border-radius:50%; +border:1px solid var(--border);background:var(--bg3);color:var(--text);font-size:17px; +cursor:pointer;z-index:20;display:flex;align-items:center;justify-content:center; +box-shadow:0 2px 8px rgba(0,0,0,.15)} +.theme-toggle:hover{border-color:var(--border2)} .subtitle{color:var(--muted);font-size:14px;margin-bottom:40px} .section{margin-bottom:56px} .section-title{font-size:13px;font-weight:600;letter-spacing:.08em;text-transform:uppercase; @@ -25,17 +36,66 @@ .stat-card .sub{font-size:11px;color:var(--dim);margin-top:2px;font-family:var(--mono)} /* Architecture */ .arch-wrap{background:var(--bg3);border:1px solid var(--border);border-radius:14px; -padding:24px;overflow-x:auto} +padding:24px;overflow-x:auto;position:relative} +.graph-wrap{background:var(--bg3);border:1px solid var(--border);border-radius:14px; +padding:16px;position:relative;height:640px} +.graph-wrap canvas{width:100%;height:100%;display:block;border-radius:10px;cursor:grab} +.graph-toolbar{position:absolute;top:26px;left:26px;right:26px;z-index:2;display:flex; +align-items:center;gap:10px;flex-wrap:wrap} +#graph-search{background:var(--bg2); +border:1px solid var(--border);border-radius:8px;padding:6px 10px;font-size:12px; +color:var(--text);width:180px;font-family:var(--sans)} +#graph-search:focus{outline:none;border-color:var(--border2)} +#graph-hide-isolated-wrap{display:flex;align-items:center;gap:5px;font-size:11px; +color:var(--muted);white-space:nowrap;cursor:pointer;user-select:none} +#graph-isolated-count{color:var(--muted)} +#graph-reset-view{background:var(--bg2);border:1px solid var(--border);border-radius:8px; +padding:6px 10px;font-size:11px;color:var(--muted);font-family:var(--sans);cursor:pointer} +#graph-reset-view:hover{color:var(--text);border-color:var(--border2)} +.graph-legend{position:absolute;bottom:26px;left:26px;right:26px;z-index:2;display:flex;gap:14px 20px; +flex-wrap:wrap;font-size:10.5px;color:var(--muted);background:rgba(0,0,0,.0)} +.graph-legend span{display:flex;align-items:center;gap:5px} +.graph-legend .lg-dot{width:9px;height:9px;border-radius:50%;background:#818cf8;display:inline-block} +.graph-legend .lg-square{width:9px;height:9px;background:#eab308;display:inline-block;border-radius:2px} +.graph-legend .lg-ring{width:9px;height:9px;border-radius:50%;border:1.5px solid var(--muted);display:inline-block} +.graph-tooltip{position:absolute;z-index:3;pointer-events:none;background:var(--bg2);border:1px solid var(--border2); +border-radius:8px;padding:8px 11px;font-size:11px;color:var(--text);box-shadow:0 4px 16px rgba(0,0,0,.25); +display:none;max-width:320px;line-height:1.5} +.graph-tooltip .tt-path{font-family:var(--mono);font-weight:600;word-break:break-all} +.graph-tooltip .tt-meta{color:var(--muted);font-size:10.5px;margin-top:3px} +.derivation{background:var(--bg3);border:1px solid var(--border);border-radius:10px;padding:12px 18px;margin:0 0 12px} +.derivation summary{cursor:pointer;font-size:12px;font-weight:600;color:var(--text);list-style:revert} +.derivation .muted-inline{font-weight:400;color:var(--muted);font-size:11px} +.derivation-body{margin-top:12px} +.deriv-row{display:flex;justify-content:space-between;font-family:var(--mono);font-size:11.5px;color:var(--muted);padding:3px 0} +.deriv-row span:last-child{color:var(--text)} +.deriv-divider{height:1px;background:var(--border);margin:10px 0} +.deriv-label{font-size:11px;font-weight:600;color:var(--muted);margin-bottom:4px} +.deriv-assumptions{font-size:11px;color:var(--muted);line-height:1.6} .arch-layer{margin-bottom:0} .arch-row-label{font-size:10px;font-weight:600;letter-spacing:.1em;text-transform:uppercase; color:#334155;margin-bottom:8px;padding-left:4px} .arch-boxes{display:flex;gap:12px;flex-wrap:wrap;margin-bottom:6px} -.arch-box{border:1.5px solid;border-radius:10px;padding:12px 16px;min-width:160px;flex:1;max-width:280px} +.arch-box{border:1.5px solid;border-radius:10px;padding:12px 16px;min-width:160px;flex:1;max-width:280px;position:relative;color:#f1f5f9} +.arch-box.declared{border-style:dashed;opacity:.72} .arch-box .bname{font-weight:700;font-size:13px;margin-bottom:2px} +.arch-box .bicon{margin-right:6px;font-size:13px} .arch-box .bdesc{font-size:11px;opacity:.7;margin-bottom:4px} .arch-box .bcode{font-family:var(--mono);font-size:10px;opacity:.55;margin-top:2px} -.arch-arrow{text-align:center;color:#2d3f55;font-size:20px;line-height:1;padding:4px 0; -user-select:none} +/* Nested storage groups */ +.arch-group{border:1.5px dashed var(--border2);border-radius:12px;padding:14px 10px 10px; +display:flex;gap:12px;flex-wrap:wrap;position:relative;flex:1;min-width:200px} +.arch-group-label{position:absolute;top:-9px;left:12px;background:var(--bg3);padding:0 8px; +font-size:9px;font-weight:700;letter-spacing:.09em;text-transform:uppercase;color:var(--muted)} +/* Edge overlay: real node-to-node connections evidenced by a flow's step sequence */ +#edge-overlay{position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;overflow:visible} +.edge-path{fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;opacity:.85} +.edge-label{font-family:inherit;font-size:10px;font-weight:600;fill:var(--text);pointer-events:none} +.edge-label-bg{fill:var(--bg2);stroke:var(--border);stroke-width:1;pointer-events:none} +@media(prefers-reduced-motion:no-preference){ +.edge-path.animated{stroke-dasharray:8 6;animation:edgeFlow 1s linear infinite} +} +@keyframes edgeFlow{to{stroke-dashoffset:-28}} /* Flows */ .flows{display:grid;grid-template-columns:repeat(auto-fill,minmax(340px,1fr));gap:20px} .flow-card{background:var(--bg3);border:1px solid var(--border);border-radius:12px;overflow:hidden} @@ -75,9 +135,37 @@ footer{text-align:center;color:var(--dim);font-size:12px;margin-top:60px; padding-top:20px;border-top:1px solid var(--border)} code{font-family:var(--mono);font-size:11px;color:#06b6d4} +/* Animated flow: architecture arrows */ +.arch-arrow{position:relative;height:26px} +.arrow-line{position:absolute;left:50%;top:0;bottom:0;width:2px;background:var(--border2);transform:translateX(-50%)} +.arrow-dot{position:absolute;left:50%;top:0;width:10px;height:10px;border-radius:50%; +background:#60a5fa;box-shadow:0 0 8px 2px #60a5fa,0 0 18px 4px rgba(96,165,250,.55);transform:translateX(-50%);opacity:0} +.arch-boxes{border-radius:14px;padding:6px;margin:-6px -6px 0 -6px} +@media(prefers-reduced-motion:no-preference){ +.arrow-dot{animation:arrowFlow var(--t,4s) linear infinite; +animation-delay:calc(var(--i,0)/var(--n,1)*var(--t,4s))} +.arch-boxes{animation:rowPulse var(--t,4s) ease-in-out infinite; +animation-delay:calc(var(--i,0)/var(--n,1)*var(--t,4s))} +.step-num::after{animation:stepFlow var(--t,3s) linear infinite; +animation-delay:calc(var(--i,0)/var(--n,1)*var(--t,3s))} +} +@keyframes arrowFlow{0%{opacity:0;top:0}5%{opacity:1}45%{top:calc(100% - 10px);opacity:1}62%{opacity:0}100%{opacity:0}} +@keyframes rowPulse{0%,100%{background:transparent;box-shadow:none} +4%{background:rgba(96,165,250,.12);box-shadow:0 0 0 1px rgba(96,165,250,.4)} +22%{background:rgba(96,165,250,.12);box-shadow:0 0 0 1px rgba(96,165,250,.4)} +34%{background:transparent;box-shadow:none}} +/* Animated flow: step connectors */ +.step-num{position:relative} +.step-num::before{content:'';position:absolute;left:50%;bottom:100%;width:2px;height:14px; +background:var(--border2);transform:translateX(-50%)} +.flow-step:first-child .step-num::before{display:none} +.step-num::after{content:'';position:absolute;left:50%;top:-14px;width:5px;height:5px; +border-radius:50%;background:currentColor;opacity:0;transform:translateX(-50%)} +@keyframes stepFlow{0%{opacity:0;top:-14px}8%{opacity:1}92%{top:0;opacity:1}100%{opacity:0}} +

@@ -90,28 +178,93 @@

fastapi-full-stack-template — System Workflow

-

Component communication map · concurrent request capacity · bottleneck analysis · 2026-07-10

+

Component communication map · concurrent request capacity · bottleneck analysis · 2026-07-23

-
1
API Worker Processes
1 workers × 1 replica(s)
~100
Max Concurrent Async I/O
~100 per asyncio worker
~100 concurrent I/O
Practical Throughput
bottleneck: I/O event loop
-

Capacity figures are static-analysis estimates (heuristic: ~100 concurrent tasks per async worker), not load-test results.

+
1
API Worker Processes
1 workers × 1 replica(s)
~100
Max Concurrent Async I/O
~100 per asyncio worker
~100 concurrent I/O
Modeled Throughput
static model, not a load test — bottleneck: I/O event loop
+

Capacity figures are static-analysis estimates (heuristic: ~100 concurrent tasks per async worker), not load-test results.

+ +
+How was "Modeled Throughput" computed? (static model — click to expand) +
+
Worker processes1
Per-worker concurrency (assumed)100 (async — ~100 concurrent tasks/worker is a heuristic, not measured)
1 workers × 100= 100 concurrency ceiling
+
+
Ranked constraints (tightest wins — this is the bottleneck):
+
1. I/O event loop100 concurrent
+
+
Assumptions baked into this model: ~100 concurrent tasks per async worker +is a heuristic, not a measurement; an in-flight LLM call is modeled as occupying its slot for the full +configured timeout (5000s), though real completions are usually +faster. Treat this number as a starting estimate, not a capacity guarantee.
+
+
+
+Verify with a real load test (generated k6 script — click to expand) +
+

Pre-filled with up to 5 detected routes. Run this against a staging deploy to replace the model above with a measurement.

+
import http from 'k6/http';
+import { sleep } from 'k6';
+
+// Generated by workflow-generator. Edit BASE_URL, add auth headers,
+// and replace any `1` path params with real IDs before running.
+// Run: k6 run --env BASE_URL=https://staging.example.com this_file.js
+const BASE_URL = __ENV.BASE_URL || 'http://localhost:8000';
+
+export const options = { vus: 10, duration: '30s' };
+
+export default function () {
+  http.get(`${BASE_URL}/`);
+  http.get(`${BASE_URL}/health-check/`);
+  http.get(`${BASE_URL}/me`);
+  http.get(`${BASE_URL}/1`);
+  http.get(`${BASE_URL}/1`);
+  sleep(1);
+}
+ +
+
+ + +
+
Codebase Graph — 144 modules · 340 real connections
+

Every node is a real file in this repo; every line is an actual import statement, or a pattern-confirmed service call scanned per-file (the same signal behind the summary below, localized to its source). This is static analysis, not live monitoring — particle motion shows dependency direction (importer → imported), not request traffic. Pass --access-log <path> to overlay real per-route request counts instead. Drag a node, scroll to zoom, click to isolate its connections, or search below.

+
+ +
+
+ + + +
+
+entry (HTTP clients) +module (colored by package) +external service (pattern-confirmed) +→ arrow = "imports / calls" (static — not live traffic) +click a node to isolate its call paths (dims everything else); click empty space to clear, type in the search box to highlight by name +
+
+ +
Full System Architecture — 4 Components
-
EXTERNAL SOURCES & CLIENTS
Users / API Clients
HTTPS / WebSocket
JWT
-
↓
-
GATEWAY / REVERSE PROXY
nginx
worker_connections ?
TLS termination
-
↓
-
APPLICATION LAYER
FastAPI
async 1 worker × 1 replica
jwt · oauth
React
UI framework
-
↓
-
PROCESSING & QUEUE LAYER
Auth Middleware
JWT decode · RBAC scope check
-
↓
-
STORAGE & PERSISTENCE
PostgreSQL
Primary database
+

Detection is pattern-based static analysis. Dashed boxes are dependencies declared in your manifest but not matched to a usage pattern in source — may be wired through a custom abstraction. Solid boxes are pattern-confirmed in source. A component not shown here isn't confirmed absent from the project — only undetected.

+
EXTERNAL SOURCES & CLIENTS
🌐Users / API Clients
HTTPS / WebSocket
JWT
+
+
GATEWAY / REVERSE PROXY
🚦nginx
worker_connections ?
TLS termination
+
+
APPLICATION LAYER
⚙️FastAPI
async 1 worker × 1 replica
jwt · oauth
🖥️React
UI framework
+
+
PROCESSING & QUEUE LAYER
🔑Auth Middleware
JWT decode · RBAC scope check
+
+
STORAGE & PERSISTENCE
🗄️PostgreSQL
Primary database
+
Detected API Routes (10+)
DELETE /me
DELETE /{id}
DELETE /{user_id}
GET /
GET /health-check/
GET /me
GET /{id}
GET /{user_id}
PATCH /me
PATCH /me/password
Data Flow Paths — Step by Step
-
HTTP Request Flow
1
Client sends request
HTTPS request to nginx
2
Auth & validation
JWT decode, request validated
3
Handler executes
FastAPI route handler processes request, queries DB
4
Response returned
JSON response sent back to client
+
HTTP Request Flow
1
Client sends request
HTTPS request to nginx
2
Auth & validation
JWT decode, request validated
3
Handler executes
FastAPI route handler processes request, queries DB
4
Response returned
JSON response sent back to client
@@ -124,7 +277,491 @@

fastapi-full-stack-template — System Workflow

I/O event loopCRITICAL
Tightest ceiling among detected constraints: ~100 units of concurrent capacity. Mitigate: add more workers/replicas, or move to an async framework to raise per-worker I/O concurrency.
- +
+ + - \ No newline at end of file + diff --git a/docs/preview.png b/docs/preview.png index 1b35aec..9d30ae9 100644 Binary files a/docs/preview.png and b/docs/preview.png differ