# Archify Renderer Architecture: How Typed Renderers Share Geometry Utilities

> Explore Archify's typed renderer architecture. Discover how five independent renderers leverage shared geometry utilities for consistent diagram layouts across architecture, workflow, sequence, dataflow, and lifecycle diagrams.

- Repository: [tt-a1i/archify](https://github.com/tt-a1i/archify)
- Tags: architecture
- Published: 2026-08-04

---

**Archify implements a typed-renderer architecture where five independent renderers import pure geometry helpers from a shared module to ensure consistent diagram layout across architecture, workflow, sequence, dataflow, and lifecycle diagrams.**

Archify is a JSON-to-diagram rendering engine that transforms intermediate representation (JSON-IR) into self-contained HTML visualizations. The renderer architecture in the `tt-a1i/archify` repository cleanly separates diagram-type concerns from reusable geometric operations, enabling maintainable and performant code generation.

## Overview of the Typed-Renderer Architecture

Archify organizes renderers by diagram type under `archify/renderers/<type>/`. Each renderer is a standalone ES module responsible for one visualization style:

```

archify/
 └─ renderers/
      ├─ architecture/
      │    └─ render‑architecture.mjs
      ├─ workflow/
      │    └─ render‑workflow.mjs
      ├─ sequence/
      │    └─ render‑sequence.mjs
      ├─ dataflow/
      │    └─ render‑dataflow.mjs
      ├─ lifecycle/
      │    └─ render‑lifecycle.mjs
      └─ shared/
           ├─ geometry.mjs
           ├─ utils.mjs
           └─ validator.mjs

```

This structure enforces **single-responsibility** at the directory level while promoting **horizontal reuse** through the `shared/` subdirectory.

## The Shared Geometry Module

All five renderers import from `archify/renderers/shared/geometry.mjs`. This module exports pure, side-effect-free functions for rectangle, segment, and point operations. Because the helpers carry no internal state, renderers can invoke them safely without risk of cross-contamination.

### Core Geometry Functions

| Function | Signature | Purpose |
|----------|-----------|---------|
| `rectsOverlap` | `(a, b, gap?) => boolean` | Detects axis-aligned rectangle intersection with optional clearance |
| `segmentIntersectsRect` | `(segment, rect, gap?) => boolean` | Determines if a line segment touches or crosses a rectangle |
| `segmentRectClearance` | `(segment, rect) => number \| null` | Returns minimum distance between segment and rectangle, or `0` on intersection |
| `segmentRectIntersectionLength` | `(segment, rect) => number` | Computes overlapped length of segment inside rectangle |
| `collectLabelRouteClearance` | `({labels, routedRelations, threshold}) => ClearanceInfo` | Aggregates clearance data for automatic edge-label routing |

These functions enable renderers to **measure, test, and adjust** layout tables before emitting final SVG or HTML output.

## How Renderers Use Shared Geometry Utilities

### Import Pattern

Every renderer follows the same import convention:

```javascript
// From any renderer (e.g., workflow)
import {
  rectsOverlap,
  segmentIntersectsRect,
  segmentRectClearance,
} from '../shared/geometry.mjs';

```

The relative path `../shared/` resolves consistently because all renderers sit at the same directory depth.

### Workflow Renderer Example

In `archify/renderers/workflow/render-workflow.mjs`, collision detection and edge routing rely on shared helpers:

```javascript
// archify/renderers/workflow/render-workflow.mjs
import { rectsOverlap, segmentRectClearance } from '../shared/geometry.mjs';

function placeStep(step, existingNodes) {
  const stepRect = { x: step.x, y: step.y, width: step.w, height: step.h };
  
  for (const node of existingNodes) {
    if (rectsOverlap(stepRect, node.rect, 8)) {
      // Resolve collision by adjusting stepRect position
    }
  }
  return stepRect;
}

function routeEdge(start, end, obstacles) {
  const segment = { start, end };
  const clearance = obstacles.reduce((c, obs) => {
    const cur = segmentRectClearance(segment, obs.rect);
    return cur === null ? c : Math.min(c, cur);
  }, Infinity);
  
  // Bend edge if clearance insufficient
  return clearance < 4 ? bendEdge(segment) : segment;
}

```

### Architecture Renderer Example

The `archify/renderers/architecture/render-architecture.mjs` renderer applies the same helpers for component packing:

```javascript
// archify/renderers/architecture/render-architecture.mjs
import { rectsOverlap, segmentIntersectsRect } from '../shared/geometry.mjs';

export function layoutComponents(components) {
  // Build component rectangles from JSON-IR
  
  // Ensure no two components overlap
  for (let i = 0; i < components.length; ++i) {
    for (let j = i + 1; j < components.length; ++j) {
      if (rectsOverlap(components[i].rect, components[j].rect, 6)) {
        // Adjust positions to resolve collision
      }
    }
  }
}

```

Notice that both renderers import overlapping function subsets—each takes only what it needs, and tree-shaking eliminates dead code in production bundles.

## Benefits of the Shared Geometry Design

Archify's renderer architecture delivers four operational advantages:

1. **Consistency** — All renderers apply identical geometric rules. A gap of 8 pixels means the same visual distance in workflow diagrams as in architecture diagrams.

2. **Maintainability** — Geometry logic centralizes in one file. A bug fix to `rectsOverlap` propagates to every diagram type without individual renderer edits.

3. **Isolation** — Renderers remain independent modules. Developers can test, refactor, or replace `render-sequence.mjs` without touching `render-dataflow.mjs` or the shared layer.

4. **Performance** — Pure functions enable aggressive optimization. JavaScript engines can inline or memoize calls to `segmentRectClearance`, keeping render times low even for complex diagrams.

## Key Source Files

| File | Role |
|------|------|
| `archify/renderers/shared/geometry.mjs` | Pure geometry helpers used by all five renderers |
| `archify/renderers/architecture/render-architecture.mjs` | Architecture diagram renderer |
| `archify/renderers/workflow/render-workflow.mjs` | Workflow diagram renderer |
| `archify/renderers/sequence/render-sequence.mjs` | Sequence diagram renderer |
| `archify/renderers/dataflow/render-dataflow.mjs` | Dataflow diagram renderer |
| `archify/renderers/lifecycle/render-lifecycle.mjs` | Lifecycle diagram renderer |
| `archify/renderers/shared/utils.mjs` | Miscellaneous shared utilities |
| `archify/renderers/shared/validator.mjs` | JSON schema validation across renderers |

## Summary

- Archify uses a **typed-renderer architecture** with five specialized renderers under `archify/renderers/<type>/`
- All renderers import pure geometry functions from `archify/renderers/shared/geometry.mjs`
- Shared helpers include `rectsOverlap`, `segmentRectClearance`, and `collectLabelRouteClearance` for collision detection and edge routing
- The pure-function design guarantees **consistency**, **maintainability**, **isolation**, and **performance**
- Renderers build type-specific layouts then call shared utilities to validate and adjust geometry before output

## Frequently Asked Questions

### What diagram types does Archify support?

Archify supports five diagram types: **architecture**, **workflow**, **sequence**, **dataflow**, and **lifecycle**. Each has a dedicated renderer module under `archify/renderers/`.

### Why use pure functions for geometry operations?

Pure functions eliminate side effects and state leakage between renderers. They also enable JavaScript engine optimizations like inlining and memoization, reducing render time for complex diagrams.

### Can I add a new renderer without modifying existing code?

Yes. Create a new directory under `archify/renderers/<your-type>/` with a `render-<type>.mjs` entry point. Import shared utilities from `../shared/geometry.mjs` following the existing pattern. The typed-renderer architecture requires no changes to other renderers or the shared module.

### How does Archify prevent diagram elements from overlapping?

Renderers call `rectsOverlap(a, b, gap?)` from the shared geometry module to detect collisions during layout. The optional `gap` parameter adds configurable padding between elements.