# Traffic Tree v1 contract `TrafficDataSource` is the integration seam. A host provides one already-sanitized `TrafficSnapshot` for a requested inclusive time window; `buildForest()` is pure and does not perform I/O. ```ts interface TrafficDataSource { getSnapshot(query: TrafficQuery): Promise; } ``` Each `TrafficNode` has a stable opaque ID, a host-approved label, and aggregate visits. Each `TrafficEdge` contains aggregate transitions. `from: null` denotes an entry; `to: null` denotes an exit. Both cannot be null. Nodes and edges must be unique, referenced IDs must exist, and all counts must be non-negative integers. A node may include a non-empty host-owned `group`. Renderers may use this as a stable adjacency hint, but must not assign meaning to its value. This lets each consumer supply a safe vocabulary without coupling the core to Local95, LifeRing, Implicitify, or any other host. `TrafficSnapshot.daily` is optional. When supplied, it contains unique `YYYY-MM-DD` days and aggregate `navigations` for the renderer's ground-line chart. It is an event total, not a unique-visitor count; hosts must apply their disclosure policy before including a daily bucket. The builder creates a virtual root from qualifying entries. Exit transitions are retained as `metrics.exitTransitions`, but never draw a branch. Transitions below 10 by default, cycle-closing transitions, descendants after depth 5, and child branches beyond the default fan-out of four are represented in the parent node's canopy. A route may occur in more than one branch because the source is a graph rather than a URL tree. Renderers may merge occurrences with the same route ID at the same depth into one shared node. `primaryRoot: { id: "home" }` gives one confirmed entry root a single extra visible level. This is intended for the host's controlled `/` route mapping; inferred roots never receive the bonus. The additional level gives the primary tree its own upper canopy without making every entry tree equally deep. If a snapshot has no usable entry for a qualifying route, the builder creates an `inferredRoot`. Renderers must distinguish it from a confirmed entry root. This allows partial time windows and graph cycles without inventing a visitor origin. ## Local95 mapping example Local95's existing aggregate graph can map `(route_type, route_key)` to an opaque ID such as `"businesses_detail:local-95"`. Its `__entry__` and `__exit__` records map to `null` endpoints. The adapter must supply labels from Local95's controlled route vocabulary, rather than reconstructing a raw pathname. ## Non-goals The core does not collect analytics, access PostgreSQL, expose an HTTP endpoint, authenticate an admin, or depend on Astro. Those concerns remain owned by integrations. The `@mygen/traffic-tree/web` export is the optional browser renderer. It is a display-only SVG client: hosts provide the loader, authenticate its endpoint, and restrict snapshots to their approved public-route vocabulary. ## Rendering guarantees The renderer is deterministic: organic variation (limb lengths, sways, branch bends, leaf placement) derives from flow strength and stable hashes of route IDs, never from randomness. In the `botanical` layout a node's height encodes the strength of the flow reaching it, not its depth; the `layered` layout keeps the strict depth-to-height mapping. Graft edges may connect nodes at any relative height. Background refreshes must not disturb the viewer: scroll position and keyboard focus survive a re-render.