# How to Trace Upstream and Downstream Reach in Archify Viewer: A Step-by-Step Guide

> Learn to trace upstream and downstream reach in Archify viewer. Explore system graphs and understand node ancestors and descendants with this step-by-step guide.

- Repository: [tt-a1i/archify](https://github.com/tt-a1i/archify)
- Tags: how-to-guide
- Published: 2026-08-17

---

**Archify's interactive viewer lets you explore the authored graph of a system by tracing upstream (all ancestors) or downstream (all descendants) of any selected node.**

Tracing **upstream and downstream reachability** is essential for understanding dependency flow in complex software systems. The Archify viewer provides built-in controls to instantly highlight all reachable nodes from any selected component. This article explains how the feature works according to the `tt-a1i/archify` source code, with practical examples you can use immediately.

## Understanding Upstream vs. Downstream Reachability

- **Upstream reachability** — All ancestors of a node: components that influence or are required by your selected node.
- **Downstream reachability** — All descendants of a node: components that depend on or are influenced by your selected node.

These directions map directly to the `direction` parameter used throughout the reachability system in [`archify/assets/template.html`](https://github.com/tt-a1i/archify/blob/main/archify/assets/template.html).

## Using the UI Controls to Trace Reachability

When you select any node in the Archify viewer, two toolbar buttons appear automatically:

| Button ID | Label | Function |
|-----------|-------|----------|
| `#btn-reach-upstream` | Upstream | Shows count of reachable ancestors; toggles upstream tracing |
| `#btn-reach-downstream` | Downstream | Shows count of reachable descendants; toggles downstream tracing |

Clicking either button activates the corresponding reachability mode. Clicking the same button again clears the view and returns to normal display.

The button state is synchronized with:
- **ARIA attributes** (`aria-pressed`) for accessibility
- **URL hash** (`#focus=…&reach=upstream`) for bookmarkable views
- **Status line** showing the count of reachable nodes

## The Core Algorithm: `computeReachability` and `reachabilityFor`

The reachability calculation is implemented in [`archify/assets/template.html`](https://github.com/tt-a1i/archify/blob/main/archify/assets/template.html) through two key functions.

### `reachabilityFor(id, direction)`

This entry point forwards requests to the core algorithm:

```javascript
// From archify/assets/template.html (line 6999-7001)
function reachabilityFor(id, direction) {
  return computeReachability(id, direction);
}

```

Valid `direction` values are `'upstream'` or `'downstream'`.

### `computeReachability(startId, direction)`

This function performs a **breadth-first search** over the JSON IR relationships:

```javascript
// Simplified structure based on archify/assets/template.html (lines 6950-6979)
function computeReachability(startId, direction) {
  const queue = [{ id: startId, depth: 0 }];
  const visited = new Map(); // id → depth
  const edgeKeys = new Set();
  
  while (queue.length > 0) {
    const { id, depth } = queue.shift();
    
    if (visited.has(id)) continue;
    visited.set(id, depth);
    
    // Get neighbors based on direction
    const neighbors = direction === 'upstream' 
      ? getPredecessors(id)   // who points TO this node
      : getSuccessors(id);    // who this node points TO
    
    for (const neighbor of neighbors) {
      if (!visited.has(neighbor.id)) {
        queue.push({ id: neighbor.id, depth: depth + 1 });
        edgeKeys.add(neighbor.edgeKey);
      }
    }
  }
  
  return {
    direction,
    originId: startId,
    nodeIds: Array.from(visited.keys()),
    edgeKeys: Array.from(edgeKeys),
    depths: Object.fromEntries(visited),
    maxDepth: Math.max(...visited.values())
  };
}

```

The result object contains everything needed to visualize the reachable subgraph.

## Applying Reachability: The `applyReachability` Function

When a toolbar button is pressed, `applyReachability(direction, options)` executes the following steps (lines 7050-7072):

1. **Validates** that a node is currently selected
2. **Obtains** the reachability snapshot via `reachabilityFor`
3. **Clears** any prior reachability highlights
4. **Marks** matching nodes and edges with data attributes:
   - `data-reach-match` — indicates this element is in the reachable set
   - `data-reach-depth` — the distance from the origin node
   - `data-reach-origin` — the starting node ID

### URL Synchronization and Deep Linking

The viewer automatically updates the URL hash (lines 7089-7110):

```javascript
// Example generated URL
https://example.com/viewer#focus=service-api&reach=upstream

```

This enables sharing specific reachability views via copy-paste or bookmark.

### Auto-Revealing the Subgraph

Finally, if `Archify.view.reveal` is available, the diagram pans and zooms to show only the reachable nodes (lines 7110-7111):

```javascript
if (Archify.view && Archify.view.reveal) {
  Archify.view.reveal(result.nodeIds);
}

```

## Clearing the Reachability View

To reset the display, the viewer calls `clearReachability` (lines 7026-7032):

```javascript
function clearReachability() {
  // Remove all reachability data attributes
  document.querySelectorAll('[data-reach-match]')
    .forEach(el => {
      delete el.dataset.reachMatch;
      delete el.dataset.reachDepth;
      delete el.dataset.reachOrigin;
    });
  
  // Reset toolbar state and URL hash
  updateToolbarState(null);
  updateUrlHash({ focus: currentFocusId }); // removes 'reach' param
}

```

Clicking an active upstream/downstream button triggers this automatically.

## Manual Invocation from Browser Console

For debugging or custom integrations, you can invoke reachability directly:

```javascript
// Trace upstream from a specific node
const upstream = reachabilityFor('service-api', 'upstream');
console.log('Upstream nodes:', upstream.nodeIds);
console.log('Max depth:', upstream.maxDepth);

// Trace downstream
const downstream = reachabilityFor('service-api', 'downstream');

// Apply manually to update the visual state
applyReachability('upstream', { reveal: true });

```

## Relevant Source Files in tt-a1i/archify

| File | Purpose |
|------|---------|
| [`archify/assets/template.html`](https://github.com/tt-a1i/archify/blob/main/archify/assets/template.html) | Core implementation: `computeReachability`, `reachabilityFor`, `applyReachability`, UI wiring, URL handling |
| [`examples/web-app.html`](https://github.com/tt-a1i/archify/blob/main/examples/web-app.html) | Demonstration page with upstream/downstream buttons in a live diagram |
| `archify/test/authored-reachability.test.mjs` | Automated test suite validating reachability calculations against known graph structures |

## Summary

- **Trace upstream reachability** in Archify by clicking the `#btn-reach-upstream` toolbar button or calling `applyReachability('upstream')`
- **Trace downstream reachability** by clicking `#btn-reach-downstream` or calling `applyReachability('downstream')`
- The **BFS-based algorithm** in `computeReachability` traverses the JSON IR, recording depths and edge keys
- **Data attributes** (`data-reach-match`, `data-reach-depth`, `data-reach-origin`) drive the visual highlighting
- **URL hash synchronization** makes reachability views bookmarkable and shareable
- Click the active button again — or call `clearReachability()` — to reset the view

## Frequently Asked Questions

### How do I programmatically check if a node has upstream dependencies without changing the view?

Call `reachabilityFor(nodeId, 'upstream')` directly. This returns the full result object including `nodeIds`, `depths`, and `maxDepth` without triggering any visual updates.

### Can I trace reachability for multiple nodes simultaneously?

The current implementation in [`archify/assets/template.html`](https://github.com/tt-a1i/archify/blob/main/archify/assets/template.html) focuses on a single `originId`. For multi-node reachability, you would need to call `reachabilityFor` for each node and merge the results manually, or extend the `computeReachability` function to accept an array of start IDs.

### What happens if the graph contains cycles?

The `computeReachability` function uses a `visited` Map to track seen nodes, so cycles are automatically handled — the BFS will not revisit nodes, preventing infinite loops while still correctly recording the shortest path depth to each reachable node.

### Is there a way to filter reachability by relationship type?

The current implementation traverses all relationships in the JSON IR. To filter by type, you would modify the neighbor iteration logic inside `computeReachability` (lines 6950-6979) to check edge properties before adding to the queue.