|
| 1 | +--- |
| 2 | +title: Interactive architecture diagrams (React Flow) |
| 3 | +description: >- |
| 4 | + Conventions for narrative @xyflow/react canvases: custom nodes and edges, |
| 5 | + semantic stroke colors, Cloudflare orange shell grouping, readability inside |
| 6 | + tinted frames, handles, and verification before merge. |
| 7 | +priority: 815 |
| 8 | +alwaysApply: false |
| 9 | +files: |
| 10 | + include: |
| 11 | + - "**/*Diagram.tsx" |
| 12 | + - "**/components/diagrams/**/*.tsx" |
| 13 | +--- |
| 14 | + |
| 15 | +# Interactive architecture diagrams (React Flow) |
| 16 | + |
| 17 | +**Goal:** Ship readable, consistent **narrative** diagrams in a React SPA using [`@xyflow/react`](https://reactflow.dev/) (React Flow): fixed layout, animated edges where they help, and prose on a sibling page that explains scope (what is in-band vs out-of-band). |
| 18 | + |
| 19 | +This rule complements **[800-markdown.mdc](800-markdown.mdc)** (Mermaid in Markdown). Use **800** for static docs diagrams; use **815** when the diagram is **TypeScript + React Flow**. |
| 20 | + |
| 21 | +## When this rule applies |
| 22 | + |
| 23 | +Use for **interactive** canvases (not image-only figures). Typical stack: React, TypeScript, Vite, Tailwind, optional React Router. Package manager may be Bun or npm per project. |
| 24 | + |
| 25 | +## File and route layout (SPA) |
| 26 | + |
| 27 | +Adapt paths to your repo, but keep separation of concerns: |
| 28 | + |
| 29 | +- **Diagram**: `**/components/diagrams/<Name>Diagram.tsx` (or your agreed folder) exporting `<Name>Diagram`. |
| 30 | +- **Page**: route-level view that wraps copy + `<Name />` (figure, legend, diagram). |
| 31 | +- **Router and index**: register the path and add a card or link from the app home so deep links are discoverable. |
| 32 | +- **README**: document new routes and any `public/` reference assets. |
| 33 | + |
| 34 | +## React Flow defaults (narrative mode) |
| 35 | + |
| 36 | +- Outer container: bordered, dark background, bounded height (for example `min(88dvh, …)`). |
| 37 | +- `fitView` with tuned `fitViewOptions` (`padding`, `maxZoom`) and sensible `minZoom` / `maxZoom` for the graph size. |
| 38 | +- `proOptions={{ hideAttribution: true }}` where license allows. |
| 39 | +- Read-only story: `nodesDraggable={false}`, `nodesConnectable={false}` unless the product is an editor. |
| 40 | +- Canvas chrome: `react-flow dark` base, `!bg-slate-950`, `[&_.react-flow__node]:overflow-visible` when handles or labels extend past node boxes. |
| 41 | +- **Dots** `Background` and **Controls** styled to match the app (slate theme is common). |
| 42 | +- Wrap custom node and edge components in **`memo()`**; pass stable **`nodeTypes`** / **`edgeTypes`** objects (module scope), not new literals each render. |
| 43 | +- Prefer **`buildNodesAndEdges()`** returning `{ nodes, edges }` at module scope for static graphs so types and data stay stable. |
| 44 | + |
| 45 | +## Cloudflare orange shell (optional) |
| 46 | + |
| 47 | +When the story includes **traffic inside Cloudflare**: |
| 48 | + |
| 49 | +- Reuse a shared **shell** node component (rounded orange border, subtle inner glow, optional header row with logo + title). |
| 50 | +- Parent node: explicit `style.width` / `style.height`, `overflow: 'visible'`, explicit `zIndex`. Insert the **parent before children** in the `nodes` array. |
| 51 | +- Children: `parentId`, `extent: 'parent'`, positions **relative to the parent** (subtract parent world position). |
| 52 | +- **`showHeader: false`** on the shell when an inner banner or grid already titles the zone (avoids double headers). |
| 53 | + |
| 54 | +## Readability inside tinted frames |
| 55 | + |
| 56 | +- Avoid **orange-on-orange** (or any tint-on-same-tint) for primary labels. |
| 57 | +- Prefer an **opaque inner card** (slate background, strong border, light ring) for nodes that must stay legible on an orange or warm shell. |
| 58 | + |
| 59 | +## Edges and meaning |
| 60 | + |
| 61 | +- **Semantic colors**: assign meaning (for example sky = client request path, orange = Cloudflare-internal hop, violet = IdP/JWKS, slate = cache return). |
| 62 | +- Match **arrow head** color to the edge stroke. |
| 63 | +- **Dashed** `strokeDasharray` for secondary, cached, or **out-of-band** flows (for example OIDC token issuance vs JWKS fetch) so readers do not merge stories. |
| 64 | +- Short **edge labels** with `labelStyle` / `labelBgStyle` / `labelBgPadding` for legibility on dark backgrounds. |
| 65 | +- Multiple edges from one side: use **`sourceHandle` / `targetHandle`** and stable **`id`s** on each `Handle`. |
| 66 | + |
| 67 | +## Assets |
| 68 | + |
| 69 | +- Icons: keep under **`public/`** and reference with absolute web paths (for example `/logos/cloudflare.svg`). |
| 70 | +- Large reference PNGs: same; document ownership and usage in README. |
| 71 | + |
| 72 | +## Verification |
| 73 | + |
| 74 | +Run the project's **lint** and **production build** (or typecheck) for the SPA before merge. For Bun-first apps: `bun run lint` and `bun run build` from the app root. |
| 75 | + |
| 76 | +## Agent skill |
| 77 | + |
| 78 | +For a step-by-step playbook (new diagram page, shell, legend copy), use the **`reactflow-architecture-diagrams`** skill under `skills/reactflow-architecture-diagrams/`. |
0 commit comments