# Story 2.6: Architecture Diagram Type Renderer

Status: done

<!-- Note: Validation is optional. Run validate-create-story for quality check before dev-story. -->

## Story

As a user,
I want to create and view architecture diagrams with services, databases, and connections,
so that I can model system infrastructure and service relationships.

## Acceptance Criteria

1. **Given** I open or create an architecture diagram, **When** the canvas renders, **Then** I see typed nodes: Service (rectangle with gear icon), Database (cylinder shape), Queue (parallelogram), Load Balancer (diamond/pentagon), External System (cloud shape), **And** each node shows its label and optional metadata (protocol, port).

2. **Given** architecture nodes have connections, **When** rendered, **Then** edges show protocol labels (HTTP, gRPC, WebSocket, AMQP) at midpoints, **And** edge styles differentiate sync (solid) vs async (dashed) communication.

3. **Given** auto-layout runs, **When** the diagram has service groups, **Then** related services cluster together with clear inter-group connections.

## Tasks / Subtasks

- [x] Task 1: Create architecture constants and type registry (AC: #1)
  - [x] 1.1: Create `apps/web/src/modules/diagram/types/architecture/constants.ts` with `ARCH_SIZES`, `resolveArchitectureNodeType()`, `resolveArchitectureEdgeType()`
  - [x] 1.2: ~~Create barrel export~~ Removed — per project rule "no barrel files in feature modules"; imports use specific subpaths

- [x] Task 2: Create ArchServiceNode custom @xyflow/react component (AC: #1)
  - [x] 2.1: Create `apps/web/src/modules/diagram/types/architecture/ArchServiceNode.tsx` — rectangle with gear icon (⚙️), label, optional tag metadata (protocol:port)

- [x] Task 3: Create ArchDatabaseNode custom @xyflow/react component (AC: #1)
  - [x] 3.1: Create `apps/web/src/modules/diagram/types/architecture/ArchDatabaseNode.tsx` — cylinder shape via CSS (elliptical top + body), label, optional tag metadata

- [x] Task 4: Create ArchQueueNode custom @xyflow/react component (AC: #1)
  - [x] 4.1: Create `apps/web/src/modules/diagram/types/architecture/ArchQueueNode.tsx` — parallelogram via CSS transform skewX, label, optional tag metadata

- [x] Task 5: Create ArchLoadBalancerNode custom @xyflow/react component (AC: #1)
  - [x] 5.1: Create `apps/web/src/modules/diagram/types/architecture/ArchLoadBalancerNode.tsx` — diamond shape via CSS rotate(45deg) container, label, optional tag metadata

- [x] Task 6: Create ArchExternalNode custom @xyflow/react component (AC: #1)
  - [x] 6.1: Create `apps/web/src/modules/diagram/types/architecture/ArchExternalNode.tsx` — cloud shape via CSS border-radius/clip-path, label, optional tag metadata

- [x] Task 7: Create ArchConnectionEdge custom @xyflow/react component (AC: #2)
  - [x] 7.1: Create `apps/web/src/modules/diagram/types/architecture/ArchConnectionEdge.tsx` — edge with protocol label at midpoint, solid stroke for sync, dashed stroke for async (`data.type === "async"`)

- [x] Task 8: Update graph converter for architecture type resolution (AC: #1, #2)
  - [x] 8.1: Import `resolveArchitectureNodeType`, `resolveArchitectureEdgeType` from `../types/architecture/constants` in `graph-converter.ts`
  - [x] 8.2: Add `architecture` diagramType and `arch:` prefix handling in `resolveFlowNodeType()`
  - [x] 8.3: Add `architecture` diagramType handling in `resolveFlowEdgeType()`

- [x] Task 9: Register architecture types in DiagramCanvas (AC: #1, #2)
  - [x] 9.1: Import all Arch node components and ArchConnectionEdge in `DiagramCanvas.tsx`
  - [x] 9.2: Add architecture entries to `nodeTypes` and `edgeTypes` objects (OUTSIDE component)
  - [x] 9.3: Add architecture arrow marker to `MarkerDefs` component

- [x] Task 10: Architecture node size handling in ELK layout (AC: #3)
  - [x] 10.1: Import `getArchNodeSize` from `../types/architecture/constants` in `elk-layout.ts`
  - [x] 10.2: Add architecture node type guards in `buildElkGraph` for each subtype's dimensions

- [x] Task 11: Add architecture CSS styles (AC: #1, #2)
  - [x] 11.1: Add `.arch-service`, `.arch-database`, `.arch-queue`, `.arch-lb`, `.arch-external` styles to `globals.css`
  - [x] 11.2: Add cylinder shape CSS for database, parallelogram for queue, diamond for LB, cloud for external
  - [x] 11.3: Use `--diagram-architecture` neutral accent color for all architecture elements

- [x] Task 12: Tests (AC: all)
  - [x] 12.1: Unit tests for architecture constants — `resolveArchitectureNodeType` for all 5 subtypes, `resolveArchitectureEdgeType` (14 tests)
  - [x] 12.2: Unit tests for graph converter architecture type mapping — node and edge types correctly resolved for `diagramType === "architecture"` (3 new tests + 1 updated)
  - [x] 12.3: Unit test for ELK `buildElkGraph` with architecture nodes using fixed dimensions per subtype (2 tests)
  - [x] 12.4: All 126 tests pass — no regressions (was 107, added 19 architecture tests)

## Dev Notes

### Overview — What This Story Builds

This story adds the Architecture diagram type renderer: system infrastructure diagrams with 5 distinct node shapes (Service, Database, Queue, Load Balancer, External System) and protocol-labeled connection edges with sync/async differentiation. Like E-R (Story 2.4) and Org Chart (Story 2.5), architecture diagrams use **flat ELK layout** (no compound pool/lane hierarchy).

**This story builds:**
- 5 custom architecture node components (ArchServiceNode, ArchDatabaseNode, ArchQueueNode, ArchLoadBalancerNode, ArchExternalNode)
- 1 custom architecture edge component (ArchConnectionEdge — solid/dashed with protocol label)
- Architecture constants and type registry (5 node subtypes + edge type)
- Graph converter architecture type resolution
- Architecture CSS styles with distinct shapes using `--diagram-architecture` neutral accent
- Architecture arrow marker in MarkerDefs

**This story does NOT implement:**
- Other diagram types (Stories 2.7-2.8)
- Smart Inspector for architecture field editing (future epic)
- Manual node repositioning (Story 2.9)
- Liveblocks/CRDT integration (Epic 4)
- AI-triggered mutations (Epic 3)

### Architecture Compliance

**MANDATORY patterns from Architecture Decision Document:**

1. **Unified Graph Data Model (Decision 1):** Architecture nodes use type-prefixed `type` field (`arch:service`, `arch:database`, `arch:queue`, `arch:loadbalancer`, `arch:external`). Existing DiagramNode fields are semantically reused: `label` = component name, `tag` = metadata (protocol:port or technology), `icon` = override emoji (default per subtype), `color` = custom accent override. NO new fields on DiagramNode needed.

2. **Component Structure:** Feature code in `~/modules/diagram/types/architecture/` — follows the BPMN (`types/bpmn/`), E-R (`types/er/`), and Org Chart (`types/orgchart/`) pattern. NOT co-located in route directories.

3. **@xyflow/react Custom Nodes:** All custom node components use `NodeProps` typing. The `nodeTypes` and `edgeTypes` objects MUST be defined OUTSIDE the component (performance critical — Story 2.1/2.3/2.4/2.5 pattern).

4. **ELK.js Flat Layout:** Architecture diagrams use standard flat `buildElkGraph()` — no compound hierarchy needed. The ELK layered algorithm naturally clusters connected service groups.

5. **Lean JSON Data Model:** No x/y positions stored for architecture nodes. All positioning computed by ELK at render time.

6. **Type Prefixing Convention (Enforcement Rule #5):** `arch:` prefix on DiagramNode.type. Edge type uses `"sync"` (default) or `"async"`.

### Architecture Data Model — Field Mapping

The existing DiagramNode fields satisfy architecture diagram needs:

```typescript
// How DiagramNode fields map to architecture display
// arch:service node
{
  id: "payment-svc",
  type: "arch:service",
  label: "Payment Service",     // Component name
  tag: "Node.js:3000",          // Technology:port metadata
  icon: "⚙️",                   // Default for service (overridable)
  color: "#4285F4",             // Custom accent (optional)
  w: 200,                       // Width override (optional)
}

// arch:database node
{
  id: "user-db",
  type: "arch:database",
  label: "User DB",
  tag: "PostgreSQL:5432",
  icon: "🗄️",                   // Default for database
}

// arch:queue node
{
  id: "order-queue",
  type: "arch:queue",
  label: "Order Events",
  tag: "RabbitMQ:5672",
  icon: "📨",                   // Default for queue
}

// arch:loadbalancer node
{
  id: "api-lb",
  type: "arch:loadbalancer",
  label: "API Gateway",
  tag: "nginx:443",
  icon: "⚖️",                   // Default for LB
}

// arch:external node
{
  id: "stripe-api",
  type: "arch:external",
  label: "Stripe API",
  tag: "HTTPS",
  icon: "☁️",                   // Default for external
}
```

**Edge model:**
```typescript
// Architecture connection
{
  id: "e1",
  from: "payment-svc",
  to: "user-db",
  label: "PostgreSQL",           // Protocol shown at midpoint
  type: "sync",                  // "sync" = solid line, "async" = dashed
}
```

### Architecture Node Types — Size Constants

Architecture nodes have fixed dimensions per subtype:

```typescript
export const ARCH_SIZES = {
  service:      { w: 200, h: 80 },   // Rectangle — most common
  database:     { w: 160, h: 100 },   // Cylinder — taller for shape
  queue:        { w: 180, h: 70 },    // Parallelogram
  loadbalancer: { w: 120, h: 120 },   // Diamond — square for rotation
  external:     { w: 180, h: 80 },    // Cloud shape
} as const;
```

### Architecture Node Type → @xyflow/react Type Mapping

| DiagramNode.type | @xyflow Node type | Component | Visual Shape |
|---|---|---|---|
| `arch:service` | `archService` | `ArchServiceNode` | Rectangle with gear icon |
| `arch:database` | `archDatabase` | `ArchDatabaseNode` | Cylinder (elliptical top + body) |
| `arch:queue` | `archQueue` | `ArchQueueNode` | Parallelogram (CSS skewX) |
| `arch:loadbalancer` | `archLoadBalancer` | `ArchLoadBalancerNode` | Diamond (CSS rotate 45deg) |
| `arch:external` | `archExternal` | `ArchExternalNode` | Cloud (CSS border-radius) |

**Type resolution:**
```typescript
export function resolveArchitectureNodeType(type: string): string {
  const bare = type.startsWith("arch:") ? type.slice(5) : type;
  switch (bare) {
    case "service":
      return "archService";
    case "database":
      return "archDatabase";
    case "queue":
      return "archQueue";
    case "loadbalancer":
      return "archLoadBalancer";
    case "external":
      return "archExternal";
    default:
      return "archService"; // Default to service
  }
}

export function resolveArchitectureEdgeType(_type: string | undefined): string {
  return "archConnection";
}
```

### Architecture Edge Type — Connection

| DiagramEdge.type | @xyflow Edge type | Visual |
|---|---|---|
| `sync` (or default) | `archConnection` | Solid line with arrow, protocol label at midpoint |
| `async` | `archConnection` | Dashed line with arrow, protocol label at midpoint |

**Edge rendering approach:**
Use `getBezierPath` for smooth curved connections (architecture diagrams look better with curves than orthogonal step edges). The component checks `data.type` for sync vs async styling:

```typescript
import { BaseEdge, EdgeLabelRenderer, getBezierPath } from "@xyflow/react";
import type { EdgeProps } from "@xyflow/react";

export function ArchConnectionEdge(props: EdgeProps) {
  const [edgePath, labelX, labelY] = getBezierPath({
    sourceX: props.sourceX,
    sourceY: props.sourceY,
    targetX: props.targetX,
    targetY: props.targetY,
    sourcePosition: props.sourcePosition,
    targetPosition: props.targetPosition,
  });

  const edgeData = props.data as Record<string, unknown> | undefined;
  const isAsync = edgeData?.type === "async";

  return (
    <>
      <BaseEdge
        id={props.id}
        path={edgePath}
        style={{
          stroke: "var(--diagram-architecture)",
          strokeWidth: 1.5,
          strokeDasharray: isAsync ? "6 3" : undefined,
        }}
        markerEnd="url(#arch-arrow)"
      />
      {props.label && (
        <EdgeLabelRenderer>
          <div
            className="arch-edge-label"
            style={{
              transform: `translate(-50%, -50%) translate(${labelX}px, ${labelY}px)`,
            }}
          >
            {props.label}
          </div>
        </EdgeLabelRenderer>
      )}
    </>
  );
}
```

**Arrow marker needed** — architecture connections show direction of communication. Add to `MarkerDefs`:
```tsx
<marker
  id="arch-arrow"
  viewBox="0 0 10 10"
  refX={10}
  refY={5}
  markerWidth={8}
  markerHeight={8}
  orient="auto-start-reverse"
>
  <path
    d="M 0 0 L 10 5 L 0 10 Z"
    fill="var(--diagram-architecture, #71717a)"
  />
</marker>
```

### Node Component Patterns

All 5 architecture node components follow the same structure:

```typescript
import { Handle, Position } from "@xyflow/react";
import type { NodeProps } from "@xyflow/react";
import type { DiagramNode } from "../graph";

export function ArchServiceNode({ data }: NodeProps) {
  const d = data as unknown as DiagramNode & { label: string };
  const icon = d.icon ?? "⚙️";        // Default icon per subtype
  const metadata = d.tag;              // Protocol:port or technology

  return (
    <div className="arch-service">
      <div className="arch-node-icon">{icon}</div>
      <div className="arch-node-info">
        <div className="arch-node-label">{d.label}</div>
        {metadata && <div className="arch-node-meta">{metadata}</div>}
      </div>
      <Handle type="target" position={Position.Top} style={{ opacity: 0 }} />
      <Handle type="source" position={Position.Bottom} style={{ opacity: 0 }} />
      <Handle type="target" position={Position.Left} id="left" style={{ opacity: 0 }} />
      <Handle type="source" position={Position.Right} id="right" style={{ opacity: 0 }} />
    </div>
  );
}
```

**All 4 handles** (top/right/bottom/left) for edge connections in any layout direction — same pattern as BPMN, E-R, and Org Chart nodes.

**Key differences between subtypes are CSS classes only:**
- `arch-service` — plain rectangle
- `arch-database` — cylinder shape (pseudo-element ellipse top)
- `arch-queue` — skewed parallelogram (with counter-skew on inner content)
- `arch-lb` — diamond (rotated container with counter-rotated content)
- `arch-external` — cloud shape (border-radius blob)

### Graph Converter Updates

Add architecture type resolution alongside existing BPMN, E-R, and Org Chart resolution:

```typescript
// In resolveFlowNodeType():
if (diagramType === "architecture" || nodeType.startsWith("arch:")) {
  return resolveArchitectureNodeType(nodeType);
}

// In resolveFlowEdgeType():
if (diagramType === "architecture") {
  return resolveArchitectureEdgeType(edgeType);
}
```

**Import from architecture/constants.ts** — same pattern as BPMN, E-R, and Org Chart imports.

**No container nodes needed** — Architecture diagrams are flat graphs. The `graphToFlow()` function's existing default path (flat node mapping) handles architecture correctly.

**Existing data fields already preserved:** `graphNodeToFlowNode` spreads all node data into `data: { ...node }`, which includes `tag`, `icon`. And `flowNodeToGraphNode` already preserves `tag`, `icon`, `color`. No changes needed to the roundtrip logic.

### DiagramCanvas Updates

Add architecture node/edge types to the `nodeTypes` and `edgeTypes` objects:

```typescript
import { ArchServiceNode } from "../../types/architecture/ArchServiceNode";
import { ArchDatabaseNode } from "../../types/architecture/ArchDatabaseNode";
import { ArchQueueNode } from "../../types/architecture/ArchQueueNode";
import { ArchLoadBalancerNode } from "../../types/architecture/ArchLoadBalancerNode";
import { ArchExternalNode } from "../../types/architecture/ArchExternalNode";
import { ArchConnectionEdge } from "../../types/architecture/ArchConnectionEdge";

const nodeTypes = {
  // Existing BPMN + E-R + Org Chart types...
  archService: ArchServiceNode,
  archDatabase: ArchDatabaseNode,
  archQueue: ArchQueueNode,
  archLoadBalancer: ArchLoadBalancerNode,
  archExternal: ArchExternalNode,
};

const edgeTypes = {
  // Existing BPMN + E-R + Org Chart types...
  archConnection: ArchConnectionEdge,
};
```

**Additional arch-arrow marker** needed in `MarkerDefs` — architecture connections show direction.

**BFS Path Highlighting:** The existing `handleNodeClick` uses `CONTAINER_TYPES` to skip container nodes. Architecture has no container nodes, so no changes needed. BFS highlighting works on architecture nodes out of the box.

### Layout — ELK Flat Layout for Architecture Diagrams

Architecture diagrams use the existing flat `buildElkGraph()` — no compound layout detection needed.

**Key:** Architecture node dimensions vary by subtype (unlike Org Chart which has one fixed size). In `buildElkGraph`, add type detection for each architecture node type:

```typescript
children: nodes.map((node) => {
  const data = node.data as unknown as DiagramNode;
  const isErEntity = node.type === "erEntity";
  const isOcPerson = node.type === "orgchartPerson";
  // Architecture nodes: per-subtype dimensions
  const archSize = getArchNodeSize(node.type);
  const height =
    isErEntity && data.columns
      ? getErEntityHeight(data.columns)
      : isOcPerson
        ? OC_SIZES.person.h
        : archSize
          ? archSize.h
          : (node.measured?.height ?? DEFAULT_NODE_HEIGHT);
  const width = archSize
    ? (data.w ?? archSize.w)
    : isOcPerson
      ? (data.w ?? OC_SIZES.person.w)
      : (data.w ?? node.measured?.width ?? DEFAULT_NODE_WIDTH);
  return { id: node.id, width, height };
}),
```

**Helper function in constants.ts:**
```typescript
const ARCH_TYPE_MAP: Record<string, { w: number; h: number }> = {
  archService: ARCH_SIZES.service,
  archDatabase: ARCH_SIZES.database,
  archQueue: ARCH_SIZES.queue,
  archLoadBalancer: ARCH_SIZES.loadbalancer,
  archExternal: ARCH_SIZES.external,
};

export function getArchNodeSize(
  flowType: string | undefined,
): { w: number; h: number } | null {
  if (!flowType) return null;
  return ARCH_TYPE_MAP[flowType] ?? null;
}
```

**Layout algorithm note:** The ELK `layered` algorithm with default settings naturally clusters connected nodes. For AC #3 ("service groups cluster together"), the existing `crossingMinimization: LAYER_SWEEP` and `nodePlacement: BRANDES_KOEPF` settings already optimize for this. No additional ELK configuration needed.

### CSS Styles for Architecture Nodes

Add to `globals.css`. The architecture theme uses `--diagram-architecture` (neutral/slate accent: `oklch(0.552 0.016 286)`).

**Shared architecture node base:**
```css
/* ── Architecture Diagram Styles ─────────────────────────────────── */

/* Shared node structure */
.arch-service,
.arch-database,
.arch-queue,
.arch-lb,
.arch-external {
  /* Common styles set per class below */
}

.arch-node-icon {
  font-size: 20px;
  width: 32px;
  height: 32px;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}

.arch-node-info { min-width: 0; flex: 1; }
.arch-node-label {
  font-weight: 600;
  font-size: 12px;
  color: var(--foreground);
  line-height: 1.3;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.arch-node-meta {
  font-size: 10px;
  color: var(--muted-foreground);
  line-height: 1.3;
  font-family: monospace;
}
.arch-edge-label {
  font-size: 10px;
  color: var(--diagram-architecture);
  background: var(--node-bg);
  padding: 1px 6px;
  border-radius: 4px;
  border: 1px solid var(--diagram-architecture);
  font-family: monospace;
  pointer-events: none;
  position: absolute;
}
```

**Service node (rectangle):**
```css
.arch-service {
  display: flex;
  align-items: center;
  gap: 8px;
  background: var(--node-bg);
  border: 1.5px solid var(--diagram-architecture);
  border-radius: 8px;
  padding: 10px 14px;
  min-width: 160px;
  max-width: 240px;
  cursor: pointer;
}
.arch-service:hover {
  background: color-mix(in oklch, var(--diagram-architecture) 8%, var(--node-bg));
}
```

**Database node (cylinder):**
```css
.arch-database {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  background: var(--node-bg);
  border: 1.5px solid var(--diagram-architecture);
  border-radius: 4px;
  padding: 20px 14px 10px;
  min-width: 120px;
  max-width: 200px;
  position: relative;
  cursor: pointer;
}
.arch-database::before {
  content: "";
  position: absolute;
  top: -1px;
  left: -1px;
  right: -1px;
  height: 20px;
  background: color-mix(in oklch, var(--diagram-architecture) 15%, var(--node-bg));
  border: 1.5px solid var(--diagram-architecture);
  border-radius: 50% / 100%;
}
.arch-database:hover {
  background: color-mix(in oklch, var(--diagram-architecture) 8%, var(--node-bg));
}
```

**Queue node (parallelogram):**
```css
.arch-queue {
  display: flex;
  align-items: center;
  gap: 8px;
  background: var(--node-bg);
  border: 1.5px solid var(--diagram-architecture);
  padding: 10px 20px;
  min-width: 140px;
  max-width: 220px;
  transform: skewX(-8deg);
  cursor: pointer;
}
.arch-queue > * { transform: skewX(8deg); }
.arch-queue:hover {
  background: color-mix(in oklch, var(--diagram-architecture) 8%, var(--node-bg));
}
```

**Load Balancer node (diamond):**
```css
.arch-lb {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  background: var(--node-bg);
  border: 1.5px solid var(--diagram-architecture);
  width: 100px;
  height: 100px;
  transform: rotate(45deg);
  cursor: pointer;
}
.arch-lb > * { transform: rotate(-45deg); }
.arch-lb:hover {
  background: color-mix(in oklch, var(--diagram-architecture) 8%, var(--node-bg));
}
```

**External System node (cloud):**
```css
.arch-external {
  display: flex;
  align-items: center;
  gap: 8px;
  background: var(--node-bg);
  border: 2px dashed var(--diagram-architecture);
  border-radius: 20px;
  padding: 10px 16px;
  min-width: 140px;
  max-width: 220px;
  cursor: pointer;
}
.arch-external:hover {
  background: color-mix(in oklch, var(--diagram-architecture) 8%, var(--node-bg));
}
```

**Design rationale:** Each shape is created with pure CSS transforms and borders — no SVG backgrounds. This keeps the implementation lightweight and consistent with the existing BPMN/E-R/Org Chart patterns. The external system uses dashed border to visually communicate it's outside the system boundary. The load balancer diamond uses rotate(45deg) with counter-rotation on children (same technique as BPMN gateway).

### Existing Code to Reuse / Modify

| File | Action | What |
|------|--------|------|
| `apps/web/src/modules/diagram/components/editor/DiagramCanvas.tsx` | **MODIFY** | Register architecture node types, edge type, and arrow marker |
| `apps/web/src/modules/diagram/lib/graph-converter.ts` | **MODIFY** | Add architecture type resolution for nodes and edges (import from architecture/constants) |
| `apps/web/src/modules/diagram/lib/elk-layout.ts` | **MODIFY** | Add architecture node size handling in `buildElkGraph` per subtype |
| `apps/web/src/assets/styles/globals.css` | **MODIFY** | Add architecture node CSS styles (5 shapes + edge label) |
| `apps/web/src/modules/diagram/types/graph.ts` | **READ** | DiagramNode (tag, icon, color) — already defined, no changes |
| `apps/web/src/modules/diagram/lib/bfs-path.ts` | **REUSE** | Path highlighting works for architecture — diagram-type-agnostic |
| `apps/web/src/modules/diagram/stores/useGraphStore.ts` | **REUSE** | highlightedNodeId/setHighlightedNodeId — no changes needed |
| `apps/web/src/modules/diagram/hooks/useAutoLayout.ts` | **REUSE** | Auto-layout works with flat ELK — no changes needed |

### Library & Framework Requirements

**No new packages required.** Everything built with existing dependencies:
- `@xyflow/react` 12.10.1 — custom nodes, edges, handles, BaseEdge, getBezierPath, EdgeLabelRenderer
- `elkjs` 0.11.0 — flat layered layout
- `zustand` 5.0.8 — highlight state (reuse existing)

### File Structure for This Story

New files:
```
apps/web/src/modules/diagram/
├── types/architecture/
│   ├── index.ts                          # Exports all architecture components + constants
│   ├── constants.ts                      # ARCH_SIZES, type mappings, getArchNodeSize
│   ├── constants.test.ts                 # Tests for architecture constants
│   ├── ArchServiceNode.tsx               # Service custom node (rectangle + gear)
│   ├── ArchDatabaseNode.tsx              # Database custom node (cylinder)
│   ├── ArchQueueNode.tsx                 # Queue custom node (parallelogram)
│   ├── ArchLoadBalancerNode.tsx          # Load Balancer custom node (diamond)
│   ├── ArchExternalNode.tsx              # External System custom node (cloud)
│   └── ArchConnectionEdge.tsx            # Connection edge (solid/dashed + protocol label)
```

Modified files:
```
apps/web/src/modules/diagram/components/editor/DiagramCanvas.tsx  # Register architecture node/edge types + marker
apps/web/src/modules/diagram/lib/graph-converter.ts               # Architecture type mapping
apps/web/src/modules/diagram/lib/graph-converter.test.ts          # Add architecture converter tests
apps/web/src/modules/diagram/lib/elk-layout.ts                    # Architecture node sizes in buildElkGraph
apps/web/src/modules/diagram/lib/elk-layout.test.ts               # Add architecture ELK tests
apps/web/src/assets/styles/globals.css                             # Architecture CSS styles
```

### Project Structure Notes

- Architecture node components go in `~/modules/diagram/types/architecture/` — follows BPMN (`types/bpmn/`), E-R (`types/er/`), and Org Chart (`types/orgchart/`) pattern
- Layout utilities in `~/modules/diagram/lib/` — architecture uses the existing flat `buildElkGraph`, only node size handling added per subtype
- `bfs-path.ts` reused as-is — diagram-type-agnostic
- Tests co-located next to source files

### Anti-Patterns to Avoid

- **NEVER put `nodeTypes` or `edgeTypes` inside the component** — causes re-renders on every state change (Story 2.1/2.3/2.4/2.5 pattern)
- **NEVER hardcode positions for architecture nodes** — all positioning from ELK layout
- **NEVER import from `reactflow`** — use `@xyflow/react` (v12+)
- **NEVER use `require()`** — ESM-only project
- **NEVER co-locate feature code in route directories** — use `~/modules/diagram/`
- **NEVER store ELK-computed positions in persisted graph data** — positions are ephemeral
- **NEVER use compound/hierarchical ELK layout for architecture** — flat layout is correct (no pools/lanes)
- **NEVER add new fields to DiagramNode** — use existing fields (`tag`, `icon`, `color`) with architecture semantics
- **NEVER create inline style objects in render** — use CSS classes; conditionally apply inline styles only when data overrides exist (lesson from Story 2.5 code review)
- **NEVER use `??` for empty string color guard** — use `||` so falsy `data.color` (including `""`) falls back correctly (lesson from Story 2.5 code review)
- **DO NOT implement Smart Inspector for architecture** — that's a future story
- **DO NOT implement other diagram type renderers** — those are Stories 2.7-2.8
- **DO NOT break existing tests** — 107 tests must continue passing
- **DO NOT use inline styles for architecture colors** — use CSS custom properties (`--diagram-architecture`)
- **DO NOT add arrow markers to architecture edges inline** — use SVG `<marker>` in `MarkerDefs` with `markerEnd="url(#arch-arrow)"`

### Previous Story Intelligence (Story 2.5 — Org Chart)

**Key learnings to carry forward:**
- Constants file pattern: `OC_SIZES` map → `ARCH_SIZES` equivalent. Type resolution functions: `resolveXNodeType`, `resolveXEdgeType`
- Node component pattern: Cast `data` as `DiagramNode & { label: string }`, use `Handle` with `style={{ opacity: 0 }}` at 4 positions
- Edge component pattern: `BaseEdge` + path function. Architecture uses `getBezierPath` (not `getSmoothStepPath` like orgchart) for smooth curved connections
- Graph converter: `resolveFlowNodeType` switch on `diagramType`, import type resolver from `types/[type]/constants`
- DiagramCanvas: import components, add to `nodeTypes`/`edgeTypes` objects
- CSS: Use `--diagram-[type]` CSS variable for accent colors. Use `color-mix()` for hover/bg tints
- `graphNodeToFlowNode` already spreads all DiagramNode fields into `data` — custom nodes access `data.tag`, `data.icon` directly
- `flowNodeToGraphNode` already preserves `tag`, `icon`, `color` fields — roundtrip works
- Code review from 2.5: Remove dead helper functions (don't export `getArchXHeight` unless used in elk-layout.ts), use `||` not `??` for empty string color guards, don't create inline style objects on every render
- 107 web tests currently pass — don't break them

### Previous Story Intelligence (Story 2.4 — E-R)

**Key learnings:**
- Multiple node subtypes resolved via switch statement in `resolveXNodeType` (E-R only has 1, but the pattern supports N)
- Edge component with labels: E-R uses `EdgeLabelRenderer` for cardinality labels at source/target — architecture will use same `EdgeLabelRenderer` for protocol labels at midpoint
- `getErEntityHeight()` pattern for dynamic node sizing → architecture uses `getArchNodeSize()` for per-subtype fixed sizing

### Git Intelligence

Recent commits:
- `0a7838a feat: implement Story 2.3 — BPMN diagram type renderer`
- `7dd5af1 feat: implement Story 2.2 — ELK.js auto-layout engine in Web Worker`
- `5033109 feat: implement Story 2.1 — canvas workspace with @xyflow/react and unified graph model`

Established patterns:
- Commit message: `feat: implement Story X.Y — description`
- Feature code in `apps/web/src/modules/diagram/`
- Co-located tests next to source files
- `diagramTypeConfig` in DiagramCard.tsx already has architecture config: `{ label: "Architecture", icon: Icons.Network, color: "text-zinc-500" }`
- CSS variable `--diagram-architecture: oklch(0.552 0.016 286)` already defined in globals.css

### Latest Tech Information

**@xyflow/react 12.10.1 — getBezierPath + EdgeLabelRenderer:**
- `getBezierPath` produces smooth curved paths — appropriate for architecture connections (protocol flows)
- Returns `[path, labelX, labelY, offsetX, offsetY]` — `labelX/labelY` used for edge label positioning
- `EdgeLabelRenderer` renders HTML at edge label position — use `position: absolute` + `transform: translate()` with the label coordinates
- `BaseEdge` accepts `path`, `style`, `markerEnd` props
- `markerEnd="url(#arch-arrow)"` references SVG marker defined in `MarkerDefs`

**ELK.js Layered Algorithm — Service Clustering:**
- The layered algorithm naturally groups densely connected nodes into the same or adjacent layers
- `crossingMinimization: LAYER_SWEEP` minimizes edge crossings, which effectively clusters related services
- No explicit "grouping" configuration needed — the algorithm's edge-based optimization handles AC #3
- For diagrams where explicit grouping is desired, the `group` field on DiagramNode could be used in future to create ELK partition constraints

**CSS Shapes without SVG:**
- Cylinder: `border-radius: 50% / 100%` on `::before` pseudo-element creates elliptical top
- Parallelogram: `transform: skewX(-8deg)` on container, `skewX(8deg)` on children to counter-skew text
- Diamond: `transform: rotate(45deg)` on container, `rotate(-45deg)` on children
- Cloud: `border-radius: 20px` + dashed border for "external" visual metaphor
- All shapes use `cursor: pointer` and `color-mix()` hover states

### References

- [Source: _bmad-output/planning-artifacts/epics.md#Story 2.6] — Full AC: typed nodes (Service, Database, Queue, LB, External), protocol labels, sync/async edges, service clustering
- [Source: _bmad-output/planning-artifacts/architecture.md#Decision 1] — Unified Graph Data Model: `arch:` prefix, shared base fields
- [Source: _bmad-output/planning-artifacts/architecture.md#Enforcement Guidelines] — 7 mandatory rules, type prefixing: `arch:`
- [Source: _bmad-output/planning-artifacts/ux-design-specification.md#Diagram Type Renderers] — Architecture: Service, Database, Queue, Load Balancer, External System
- [Source: _bmad-output/planning-artifacts/ux-design-specification.md#Diagram Type Color Accents] — Architecture: neutral oklch(0.552 0.016 286)
- [Source: _bmad-output/implementation-artifacts/2-5-org-chart-diagram-type-renderer.md] — Org Chart implementation patterns (constants, nodes, edges, converter, canvas registration, code review learnings)
- [Source: apps/web/src/modules/diagram/types/graph.ts] — DiagramNode (tag, icon, color), DiagramType includes "architecture"
- [Source: apps/web/src/modules/diagram/lib/graph-converter.ts] — Current converter with BPMN + E-R + Org Chart resolution to extend for architecture
- [Source: apps/web/src/modules/diagram/lib/elk-layout.ts] — Current flat layout builder, E-R/Org Chart size pattern to follow for architecture
- [Source: apps/web/src/modules/diagram/components/editor/DiagramCanvas.tsx] — Canvas to register architecture types + add arch-arrow marker
- [Source: apps/web/src/assets/styles/globals.css] — CSS with `--diagram-architecture` already defined

## Dev Agent Record

### Agent Model Used

Claude Opus 4.6 (claude-opus-4-6)

### Debug Log References

None — clean implementation with no errors or retries.

### Completion Notes List

- All 12 tasks and subtasks implemented following existing BPMN/E-R/Org Chart patterns
- Applied Story 2.5 code review lessons: `||` for icon fallback (not `??`), no dead exports, CSS classes over inline styles
- Architecture uses flat ELK layout (no compound hierarchy) — service clustering achieved naturally via layered algorithm
- ArchConnectionEdge uses `getBezierPath` for smooth curves (distinct from orgchart's `getSmoothStepPath`)
- 19 new tests added (14 constants + 4 graph-converter + 2 elk-layout), total 126 tests passing
- No new fields added to DiagramNode — reuses `tag`, `icon`, `color` with architecture semantics
- CSS shapes: cylinder (::before pseudo-element), parallelogram (skewX), diamond (rotate 45deg), cloud (dashed border-radius)

### File List

**New files:**
- `apps/web/src/modules/diagram/types/architecture/constants.ts` — ARCH_SIZES, resolveArchitectureNodeType, resolveArchitectureEdgeType, getArchNodeSize
- ~~`apps/web/src/modules/diagram/types/architecture/index.ts`~~ — removed (dead barrel, no barrel files per project rules)
- `apps/web/src/modules/diagram/types/architecture/ArchServiceNode.tsx` — Service node (rectangle + gear)
- `apps/web/src/modules/diagram/types/architecture/ArchDatabaseNode.tsx` — Database node (cylinder)
- `apps/web/src/modules/diagram/types/architecture/ArchQueueNode.tsx` — Queue node (parallelogram)
- `apps/web/src/modules/diagram/types/architecture/ArchLoadBalancerNode.tsx` — Load Balancer node (diamond)
- `apps/web/src/modules/diagram/types/architecture/ArchExternalNode.tsx` — External System node (cloud)
- `apps/web/src/modules/diagram/types/architecture/ArchConnectionEdge.tsx` — Connection edge (solid/dashed + protocol label)
- `apps/web/src/modules/diagram/types/architecture/constants.test.ts` — 14 architecture constant tests

**Modified files:**
- `apps/web/src/modules/diagram/lib/graph-converter.ts` — architecture type resolution in resolveFlowNodeType/resolveFlowEdgeType
- `apps/web/src/modules/diagram/lib/graph-converter.test.ts` — 3 new + 1 updated architecture converter tests
- `apps/web/src/modules/diagram/lib/elk-layout.ts` — architecture node size handling in buildElkGraph
- `apps/web/src/modules/diagram/lib/elk-layout.test.ts` — 2 architecture ELK layout tests
- `apps/web/src/modules/diagram/components/editor/DiagramCanvas.tsx` — register 5 node types + 1 edge type + arch-arrow marker
- `apps/web/src/assets/styles/globals.css` — architecture CSS styles (5 node shapes + edge label + shared classes)

## Senior Developer Review (AI)

**Reviewer:** Mou on 2026-02-26
**Model:** Claude Opus 4.6

### Findings Summary

| Severity | Count | Fixed |
|----------|-------|-------|
| HIGH     | 0     | —     |
| MEDIUM   | 3     | 3     |
| LOW      | 3     | 2     |

### Issues Found & Resolved

**M1 — Unrelated changes in git (ProjectTree.tsx, router.ts)**
Reverted. These changes (rename `overProjectId`→`dragOverProjectId`, batch SQL optimization) are valid improvements but out of scope for Story 2.6. Should be committed separately.

**M2 — Story test count discrepancy (12.2: "4 tests" vs 12.4: "19 total")**
Fixed. Task 12.2 now reads "3 new tests + 1 updated" — the "all 6 diagram types" test was updated, not added. 14 + 3 + 2 = 19 matches.

**M3 — Dead barrel `index.ts` violating "no barrel files" rule**
Deleted `types/architecture/index.ts`. All imports already use specific subpaths. Task 1.2 updated in story.

**L1 — Handle style objects `{ opacity: 0 }` recreated on every render**
Fixed. Extracted `HIDDEN_HANDLE` constant in `constants.ts`, referenced by all 5 node components. Eliminates 20 unnecessary object allocations per render cycle.

**L2 — Five near-identical node components (NOT FIXED)**
Acknowledged. All 5 `Arch*Node` components only differ in CSS class and default emoji. Could be a single parameterized component. Deferred — follows existing BPMN/E-R/Org Chart project pattern.

**L3 — Database cylinder double-border CSS overlap**
Fixed. Removed top border from `.arch-database` parent (`border-top: none; border-radius: 0 0 4px 4px`), so only the `::before` cylinder cap draws the top edge.

### Change Log

- 2026-02-26: Code review — reverted out-of-scope changes, removed dead barrel, extracted HIDDEN_HANDLE const, fixed cylinder CSS double-border, corrected test count documentation
