Dashboard and lists

The three screens you land on before opening anything.

RouteFile
/app/page.tsx
/processesapp/processes/page.tsx
/templatesapp/templates/page.tsx

/start/[templateKey] collects runInputs and evaluates startExpression before creating a process.


/ — Dashboard

Four stat cards, a recent-activity feed, and per-process avatars. Loads everything from a single GET /api/process and computes in the browser.

CardComputed from
Active Processesstatus === "running"
Awaiting My Sign-offhardcoded 0 — never computed
Completed This Weekstatus === "completed" and updatedAt within 7 days
Avg. Completion Timemean of updatedAt − startedAt over completed runs

The whole dashboard is replaced when a skin sets HomePage — see whitelabel. The Allocation Risk skin substitutes ConduitPipelineHome.


/processes — Active Processes

The real working list. Search, three filters, four sort orders, inline start, inline delete, and a progress bar per run.

ControlOptions
Searchfree text
Statusall · active · completed · rejected
Templateall, or one template
Sortactivity ↓↑ · started ↓↑

Loads GET /api/process and GET /api/process-templates together, so it can offer "start a run" inline and label each row with its template.

It invents a failure status

This list does what the platform does not — it derives failure:

function displayStatus(p: Process): DisplayStatus {
  if (p.status === "running") return "active";
  const r = p.result;
  if (r?.outcome === "rejected" || r?.status === "rejected") return "rejected";
  if (typeof p.error === "string" && p.error.length > 0) return "rejected";
  return "completed";
}

Three signals, none of them status, because status cannot express it.

So / and /processes disagree about the same run. The dashboard counts it as completed; the list shows it rejected. Both are reading the same document.

That is the third independent re-derivation of "did this fail?" in the codebase — this one, RecordOutcome in records, and the progress-bar tone below. Each would become unnecessary if ProcessStatus had a failure state.

Progress is positional, not real

den = template.steps.length
num = index of the current step in template.steps

It counts a step's position in the array against the array length. But array order is meaningless — the run follows nextStepKey. A template whose steps are declared out of order, or one that branches or loops, will show progress that jumps backwards or sits at 90% through a rework cycle.

It reads as a completion percentage and is not one.


/templates — Template list

Lists templates with a status badge (active / draft / archived), an active-run count per template, and a per-row menu.

The menu offers View (the read-only template viewer) and Start. There is no New Template or Clone: templates are code-owned and published on deploy (see manage templates).


What all three share

Everything is computed client-side. Each page fetches the full list and filters, sorts, counts, and derives in the browser. There is no query API — listProcesses() returns every process and always has.

Fine at hundreds of runs. At tens of thousands, every one of these screens ships the entire collection to the browser on load. See storage.

Permission-aware, not permission-enforced. Cards render — and nav items hide when permissions are missing, but the gating that matters happens in the API. Hiding a link does not block the route.


Known gaps

  • "Awaiting My Sign-off" is hardcoded to 0. Details
  • The dashboard counts failed runs as completed, while /processes shows them rejected. Details
  • Progress bars measure array position, which is meaningless for branching templates. Details
  • Every screen loads the full process list and filters in the browser.