FlowView — Version Selection & Filtering Logic

Implementation plan

Step 1 — Delete existing logic

All current filtering and propagation code is flawed and must be removed before reimplementing. Delete the following blocks from frontend/src/widgets/FlowView/index.js:

Keep selectedVersions and selectedFilterTagIds state declarations (lines 21–22) — they are the right shape; only the logic that drives them is being replaced.

In frontend/src/widgets/FlowView/ProcessNode.js, remove the unfiltered version list from the <select> (lines 138–150); it will be replaced in step 5.

Step 2 — Implement visibility computation

Add a pure function (no side-effects, no hooks) computeVisibleVersions(processes, selectedFilterTagIds):

  1. If selectedFilterTagIds is empty, every version of every process is visible — return early with a full set.
  2. Otherwise, seed the visible set with every version that has all of the filter tags.
  3. Expand transitively: for each visible version, walk its dependencies array and mark each { source_process_id, source_process_version } as visible. Repeat until no new entries are added (BFS or iterative DFS, order does not matter here).
  4. Return a Map<processId, Set<versionNumber>> of visible versions, and derive a Set<processId> of visible processes (those with at least one visible version).

Step 3 — Implement the two-sweep propagation

Add a pure function propagate(processes, startProcessId, startVersion, visibleVersions) that returns a new selectedVersions map:

Upward DFS from (startProcessId, startVersion):

Downward DFS from (startProcessId, startVersion):

Both sweeps track a visited set of processId strings to prevent revisiting.

Step 4 — Implement initialisation

Add a function initialise(processes, visibleVersions, activeProcess) that produces a fresh selectedVersions map:

  1. Find all sinks (processes no other process depends on). Sort by process id.
  2. For each sink in order, pick its latest visible version and call propagate(), merging results (later sinks overwrite earlier ones for shared upstreams).
  3. If activeProcess exists and activeProcess.version is visible, call propagate() from (activeProcess.processId, activeProcess.version) and merge, overwriting the baseline.

Step 5 — Wire up with hooks and callbacks

In index.js:

In ProcessNode.js:

Step 6 — Verify

Data model recap

Filtering logic

Filtering hides/shows nodes and edges based on the active tag filter (selectedFilterTagIds). A filter change triggers the full initialization sequence, which re-selects versions based on what is visible under the new filter (updating selectedVersions). Nothing is persisted to the server as a result — selectedVersions is ephemeral widget state.

Logic for what processes and versions to show when a filter is active:

Invisible versions are NOT shown in the version dropdown on a process. If the active process's version (from global app state) is invisible under the current filter, no process gets the "active process" blue border.

Propagation logic

When a version is selected by the user on a process, the versions selected on other processes need to also be updated to make the graph consistent. This happens on the filtered graph, and after filtering, when a user changes the value in a version dropdown.

The graph is a DAG, so this can be done with two DFS passes, each starting from the touched process. Think of the touched process as the root of a tree, with two separate one-directional sweeps that never backtrack:

Rules applied at each visited node: * For dependencies of the current process and version (upward sweep), change them to exactly the version the dependency specifies, if that version is visible under the active filter. If not, do nothing and stop recursion. * For processes dependent on the current process (downward sweep), select the latest visible version (under the active filter) that depends on the current version of the current process, if there is one. If not, do nothing and stop recursion. The downstream process will then appear "free floating." Note: the newly selected downstream version may have other dependencies (not to the current process) whose selected versions don't match — those connections are also free-floating. Only the dependency that triggered this sweep is guaranteed consistent; no upward sweep is triggered from the newly selected downstream node.

On initial load

Once the graph is loaded, or when the filter changes, the following steps run in order:

  1. Select the latest visible version (under the active filter) on all leaves (sinks: processes with no downstream dependants) and recurse as if they had been selected by the user. This sets a baseline selection for the whole graph. Sinks are processed in a deterministic order (sorted by process id) so that when two sinks require different versions of a shared upstream dependency, the last sink in that order wins consistently.
  2. If there's an activeProcess and its version is visible under the active filter, operate as if the user had selected that version from the dropdown on the process. This partially overwrites the baseline for processes in the activeProcess chain; processes outside that chain keep their baseline values. If the activeProcess version is invisible, skip this step (and no process gets the blue border).

Active-process sync

activeProcess is global app state: the process currently open in the process editor and selected in the top toolbar. activeProcess.version is the version currently being viewed/edited there. FlowView receives this as external state.

Known issues in the current logic

Resolved by this redesign