How to Trace Upstream and Downstream Reachability from a Focused Node in Archify

Archify exposes a focus.reachability() API that returns all ancestor (upstream) or descendant (downstream) nodes reachable from any focused node through the diagram's directed edges.

Archify treats every diagram as a directed graph where nodes represent visual elements and edges represent relationships. When you focus a node, you can programmatically query which other nodes are reachable in either direction—essential for understanding dependencies, call chains, or data flow. This guide covers the core API, the underlying graph traversal algorithm, and practical code patterns based on the official source code in tt-a1i/archify.

Core Reachability API

Archify's reachability system centers on three interconnected components: the focus object, the relationship map, and the computation engine.

The Focus Object

Archify.focus holds state for the currently selected node and exposes helper methods. In examples/web-app.html at line 5967, the focus object provides:

  • reachabilitySnapshot() – Returns a serializable state object for deep-linking
  • reachability(direction?) – Initiates reachability computation and UI highlighting

The focus object is automatically updated when a user clicks any node, or you can set it programmatically.

The Relationship Map

Before computing reachability, Archify builds a directed adjacency map. The function reachabilityRelationships() (line 7003 in examples/web-app.html) constructs:

// Returns: { nodeId: [adjacentNodeId, ...], ... }

This map is cached per render and represents the graph structure derived from the diagram's connections.

The Computation Engine

The core algorithm lives in computeReachability(originId, direction, relationships) at line 7016. It performs depth-first search with directional edge following:

  • Upstream: Follows incoming edges using a reverse-lookup map
  • Downstream: Follows outgoing edges from the standard adjacency list

The implementation is pure—no side effects—making it safe to run repeatedly without mutating diagram state.

How to Compute Reachability Programmatically

Triggering Upstream Reachability

To find all ancestor nodes from a focused node:

// Set focus on a specific node
Archify.focus = { id: 'order-123' };

// Compute upstream reachability
const upstream = Archify.focus.reachability('upstream');
// Returns: { direction: 'upstream', nodeIds: ['customer-789', 'account-456', ...] }

// Highlight reachable nodes in the UI
Archify.view.reveal(upstream.nodeIds, { 
  includeNeighbors: false, 
  reason: 'reachability' 
});

The reachability() method internally calls reachabilityFor(id, 'upstream') as implemented at line 7113-7114 of examples/web-app.html.

Triggering Downstream Reachability

Swap the direction parameter:

const downstream = Archify.focus.reachability('downstream');
// Returns: { direction: 'downstream', nodeIds: ['shipment-456', 'invoice-789', ...] }

Archify.view.reveal(downstream.nodeIds, { 
  includeNeighbors: false, 
  reason: 'reachability' 
});

Toggle Behavior

The UI implements toggle semantics: calling reachability('upstream') when upstream mode is already active collapses the view. This matches the button behavior at line 6988 where reachabilityMode is checked and cleared if unchanged.

The snapshot API enables bookmarkable views of reachability explorations:

// After computing reachability
const snap = Archify.focus.reachabilitySnapshot();
// Returns: { direction: 'downstream', nodeIds: ['order-123', 'shipment-456', 'invoice-789'] }

// Build a shareable URL
const url = `${location.origin}${location.pathname}#focus=${encodeURIComponent(Archify.focus.id)}&reach=${snap.direction}`;
// Result: https://example.com/diagram#focus=order-123&reach=downstream

The snapshot implementation at line 7228-7233 in examples/web-app.html serializes only the essential state—direction and node IDs—keeping URLs compact while preserving full context.

Wiring UI Controls

For interactive applications, bind reachability to button controls:

// Upstream button handler
document.getElementById('btn-reach-upstream').addEventListener('click', () => {
  Archify.focus.reachability('upstream');
});

// Downstream button handler  
document.getElementById('btn-reach-downstream').addEventListener('click', () => {
  Archify.focus.reachability('downstream');
});

The button markup at line 4863-4867 in examples/web-app.html includes ARIA labels for accessibility:

<button id="btn-reach-upstream" aria-label="Show upstream dependencies">← Upstream</button>
<button id="btn-reach-downstream" aria-label="Show downstream dependencies">Downstream →</button>

Under the Hood: archify/assets/template.html

The base template at archify/assets/template.html contains a shared computeReachability implementation (line 6949) used by all rendered diagrams. This ensures consistent behavior across:

  • Static exports
  • Interactive web views
  • Embedded diagrams

The template defines the core traversal without UI dependencies, allowing custom interfaces to leverage the same algorithm.

Testing Reachability Logic

The test suite at archify/test/authored-reachability.test.mjs validates the algorithm against known graph fixtures. Key test patterns include:

  • Diamond graphs: Confirming upstream from a bottom node returns both top ancestors
  • Cycles: Verifying termination despite circular references
  • Isolated nodes: Ensuring empty results for disconnected elements

Running these tests guarantees that reachability behaves predictably across diagram types.

Summary

  • Archify.focus.reachability(direction) – Primary API for upstream/downstream queries; returns node IDs reachable from the focused node
  • computeReachability(originId, direction, relationships) – Pure DFS implementation in examples/web-app.html line 7016
  • reachabilitySnapshot() – Serializes state for deep-linking at line 7228
  • reachabilityRelationships() – Builds the directed adjacency map at line 7003
  • Toggle behavior – Calling the same direction twice collapses the highlight
  • Performance – Pure algorithm on cached relationship map enables instant browser-side computation

Frequently Asked Questions

How does Archify handle cycles in the graph when computing reachability?

Archify's computeReachability prevents infinite loops by tracking visited nodes. During DFS traversal, each node ID is added to an internal visited Set before recursion. Duplicate encounters skip processing. This is validated in archify/test/authored-reachability.test.mjs against cyclic graph fixtures.

Can I compute reachability without triggering the UI highlight?

Yes—call reachabilityFor(id, direction) directly instead of Archify.focus.reachability(). The wrapper at line 7113 returns raw results without invoking Archify.view.reveal(). Use this for background analysis or custom rendering pipelines.

What's the difference between reachability() and reachabilitySnapshot()?

reachability(direction) actively computes and visualizes reachability, returning the result and updating the diagram highlight. reachabilitySnapshot() returns the last computed state without recalculation—useful for URL generation or state persistence. The snapshot contains the same structure ({ direction, nodeIds }) but reads from cached focus state.

Where is the reachability algorithm defined if I need to customize it?

The canonical implementation lives in two places: archify/assets/template.html at line 6949 (used by all rendered diagrams) and examples/web-app.html at line 7016 (full UI example). Both use the same computeReachability signature. Override in template.html for global changes, or shadow in specific examples for localized behavior.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →