.nornir/assets/ui_grpc_flow.svg
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1180 660" font-family="'DejaVu Sans', 'Segoe UI', sans-serif">
<defs>
<linearGradient id="board" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#0b1c2e"/><stop offset="1" stop-color="#06111d"/>
</linearGradient>
<linearGradient id="chip" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#14384f"/><stop offset="1" stop-color="#0e2a3e"/>
</linearGradient>
<linearGradient id="rail" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#173d57"/><stop offset="1" stop-color="#0c2438"/>
</linearGradient>
<linearGradient id="gate" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#3a2a14"/><stop offset="1" stop-color="#2a1d0c"/>
</linearGradient>
<marker id="arr" markerWidth="9" markerHeight="9" refX="7" refY="3" orient="auto"><path d="M0 0 L7 3 L0 6 Z" fill="#6fd3f5"/></marker>
<marker id="arrd" markerWidth="9" markerHeight="9" refX="7" refY="3" orient="auto"><path d="M0 0 L7 3 L0 6 Z" fill="#7fb0cc"/></marker>
<marker id="arrs" markerWidth="9" markerHeight="9" refX="7" refY="3" orient="auto"><path d="M0 0 L7 3 L0 6 Z" fill="#e0b86a"/></marker>
</defs>
<rect x="0" y="0" width="1180" height="660" fill="url(#board)"/>
<g stroke="#143a52" stroke-width="1"><line x1="0" y1="74" x2="1180" y2="74"/><line x1="0" y1="620" x2="1180" y2="620"/></g>
<g fill="#16415c"><circle cx="40" cy="40" r="2"/><circle cx="1140" cy="40" r="2"/><circle cx="40" cy="640" r="2"/><circle cx="1140" cy="640" r="2"/></g>
<text x="40" y="44" fill="#eaf6ff" font-size="22" font-weight="700">Building a UI client over gRPC</text>
<text x="430" y="44" fill="#9fc9e0" font-size="14.5">tonic channel + auth interceptor → RepositoryService / ArchiveService → FastRoutes → backend → ZnippyReader</text>
<!-- UI client (3 faces) -->
<rect x="36" y="120" width="206" height="150" rx="10" fill="url(#chip)" stroke="#7fc8ea" stroke-width="2.5"/>
<text x="139" y="150" fill="#eaf6ff" font-size="15" font-weight="700" text-anchor="middle">UI client</text>
<text x="139" y="174" fill="#9fc9e0" font-size="11.5" text-anchor="middle">egui panel</text>
<text x="139" y="194" fill="#9fc9e0" font-size="11.5" text-anchor="middle">web (grpc-web)</text>
<text x="139" y="214" fill="#9fc9e0" font-size="11.5" text-anchor="middle">CLI / SDK</text>
<text x="139" y="244" fill="#86b3cc" font-size="10" text-anchor="middle">tab → RPC → backend</text>
<text x="139" y="259" fill="#86b3cc" font-size="10" text-anchor="middle">render-from-rows</text>
<!-- non-rust native path -->
<rect x="36" y="470" width="206" height="92" rx="10" fill="url(#chip)" stroke="#5aa6cf" stroke-width="2"/>
<text x="139" y="500" fill="#eaf6ff" font-size="13.5" font-weight="700" text-anchor="middle">native tool</text>
<text x="139" y="520" fill="#9fc9e0" font-size="10.5" text-anchor="middle">cargo / pip / npm / docker</text>
<text x="139" y="538" fill="#86b3cc" font-size="10" text-anchor="middle">HTTP/2 registry protocol</text>
<text x="139" y="554" fill="#86b3cc" font-size="10" text-anchor="middle">→ handle_http2_request</text>
<!-- tonic channel + auth interceptor -->
<rect x="300" y="120" width="214" height="150" rx="10" fill="url(#chip)" stroke="#5aa6cf" stroke-width="2"/>
<text x="407" y="150" fill="#eaf6ff" font-size="14.5" font-weight="700" text-anchor="middle">tonic Channel</text>
<text x="407" y="171" fill="#9fc9e0" font-size="10.5" text-anchor="middle">Channel::from_shared(url)</text>
<text x="407" y="188" fill="#9fc9e0" font-size="10.5" text-anchor="middle">.tls_config(…) · .connect()</text>
<rect x="318" y="202" width="178" height="56" rx="8" fill="url(#gate)" stroke="#e0b86a" stroke-width="2"/>
<text x="407" y="223" fill="#ffe9c2" font-size="12" font-weight="700" text-anchor="middle">auth interceptor</text>
<text x="407" y="240" fill="#e9cfa0" font-size="9.5" text-anchor="middle">Bearer <token> (OIDC)</text>
<text x="407" y="252" fill="#e9cfa0" font-size="9.5" text-anchor="middle">or mTLS client cert → CN</text>
<!-- holger services -->
<rect x="572" y="108" width="226" height="96" rx="10" fill="url(#chip)" stroke="#7fc8ea" stroke-width="2.5"/>
<text x="685" y="136" fill="#eaf6ff" font-size="14.5" font-weight="700" text-anchor="middle">RepositoryService</text>
<text x="685" y="156" fill="#9fc9e0" font-size="10" text-anchor="middle">FetchArtifact · ListArtifacts</text>
<text x="685" y="172" fill="#9fc9e0" font-size="10" text-anchor="middle">PutArtifact · StreamArtifact</text>
<text x="685" y="192" fill="#86b3cc" font-size="9.5" text-anchor="middle">unary + server-stream (64 KB chunks)</text>
<rect x="572" y="216" width="226" height="74" rx="10" fill="url(#chip)" stroke="#5aa6cf" stroke-width="2"/>
<text x="685" y="242" fill="#eaf6ff" font-size="14" font-weight="700" text-anchor="middle">ArchiveService</text>
<text x="685" y="261" fill="#9fc9e0" font-size="10" text-anchor="middle">ListArchiveFiles · ArchiveInfo</text>
<text x="685" y="279" fill="#86b3cc" font-size="9.5" text-anchor="middle">browse a znippy archive's tree</text>
<rect x="572" y="302" width="226" height="62" rx="10" fill="url(#chip)" stroke="#5aa6cf" stroke-width="2"/>
<text x="685" y="326" fill="#eaf6ff" font-size="14" font-weight="700" text-anchor="middle">AdminService</text>
<text x="685" y="345" fill="#9fc9e0" font-size="10" text-anchor="middle">Health · ListRepositories</text>
<!-- FastRoutes -->
<rect x="852" y="160" width="170" height="96" rx="10" fill="url(#chip)" stroke="#7fc8ea" stroke-width="2.5"/>
<text x="937" y="190" fill="#eaf6ff" font-size="14.5" font-weight="700" text-anchor="middle">FastRoutes</text>
<text x="937" y="210" fill="#9fc9e0" font-size="10.5" text-anchor="middle">lookup(repo_name)</text>
<text x="937" y="228" fill="#86b3cc" font-size="9.5" text-anchor="middle">O(1) first-byte bucket</text>
<text x="937" y="244" fill="#86b3cc" font-size="9.5" text-anchor="middle">→ Arc<dyn Backend></text>
<!-- backend -->
<rect x="852" y="300" width="170" height="86" rx="10" fill="url(#chip)" stroke="#5aa6cf" stroke-width="2"/>
<text x="937" y="328" fill="#eaf6ff" font-size="14" font-weight="700" text-anchor="middle">backend</text>
<text x="937" y="347" fill="#9fc9e0" font-size="10" text-anchor="middle">RepositoryBackendTrait</text>
<text x="937" y="364" fill="#86b3cc" font-size="9.5" text-anchor="middle">fetch / put · id → path</text>
<text x="937" y="379" fill="#86b3cc" font-size="9.5" text-anchor="middle">is_writable() gate</text>
<!-- storage rail -->
<rect x="300" y="572" width="722" height="52" rx="10" fill="url(#rail)" stroke="#7fc8ea" stroke-width="2"/>
<text x="326" y="597" fill="#dff1ff" font-size="15" font-weight="700">ZnippyReader / storage</text>
<text x="540" y="596" fill="#bcd9ec" font-size="12">extract_file(path) • random access • no full decompress • blake3-verified</text>
<text x="540" y="614" fill="#7fa8c4" font-size="10" font-family="'DejaVu Sans Mono', monospace">immutable znippy archive (read) · {repo}/{path} tree (file-rust, writable)</text>
<!-- ── arrows: request path (cyan) ── -->
<g stroke="#6fd3f5" stroke-width="3" fill="none">
<path d="M242 195 H298" marker-end="url(#arr)"/>
<path d="M514 178 H540 V156 H570" marker-end="url(#arr)"/>
<path d="M514 210 H548 V253 H570" marker-end="url(#arr)"/>
<path d="M514 230 H560 V333 H570" marker-end="url(#arr)"/>
<path d="M798 156 H826 V208 H850" marker-end="url(#arr)"/>
<path d="M937 256 V298" marker-end="url(#arr)"/>
<path d="M937 386 V572" marker-end="url(#arr)"/>
</g>
<!-- streaming path label (amber dash, RepositoryService.StreamArtifact) -->
<path d="M852 330 H540 V366 H685 V206" stroke="#e0b86a" stroke-width="2.5" fill="none" stroke-dasharray="2 5"/>
<text x="468" y="402" fill="#e0b86a" font-size="10.5">stream: chunk-by-chunk → progress bar</text>
<!-- native-tool door into backend (bypasses tonic) -->
<path d="M242 510 H826 V392" stroke="#5aa6cf" stroke-width="2" fill="none" stroke-dasharray="6 4" marker-end="url(#arr)"/>
<text x="470" y="528" fill="#86b3cc" font-size="10.5">native path: HTTP/2 gateway → same backend (no tonic stubs)</text>
<!-- return bytes (dashed back up to UI) -->
<path d="M850 350 H812 V452 H407 V270" stroke="#7fb0cc" stroke-width="2.5" fill="none" stroke-dasharray="6 4" marker-end="url(#arrd)"/>
<text x="430" y="446" fill="#86b3cc" font-size="11">bytes / rows returned → render in the UI</text>
</svg>