Per-job pipeline machine, unified editor, per-language quarry
Per-job pipeline machine
ui/src/lib/pipeline.svelte.ts is the browser backend of the
process-request queue. It runs one xstate machine per job through five
sequential stages:
| Stage | Capability | Output |
|---|---|---|
| Radar | 'route' |
Candidate URLs (when discovery is on) |
| Extract | 'chat' |
Title, company, location, salary, description |
| Enrich | 'chat' + non-LLM service calls |
Industry, commute, salary fallback estimate |
| Evaluate | 'chat' |
One value per custom column |
| Tailor | 'chat' |
tailored_cv and tailored_cl snapshots |
Every stage executor lives in lib/domain/src/pipeline/stages/*.ts so the
CF Worker fallback can run the same code path. The browser driver is a thin
adapter: it claims the process_request row, calls the shared executor,
posts terminal status, and dispatches the next xstate event.
Stage state is broadcast through the realtime channel; the dashboard's per-row LED column reflects each job's current stage and any error.
Commute as an enrichment step
Commute is not a separate pipeline. It is one fact resolved during the
enrichment stage by commute-api.ts against the Google Maps Directions API,
using the workspace's home address and the job's location. The call is
deduped with a compute_lock keyed (jobId, 'commute') so concurrent
clients do not double-charge the Maps quota.
The result lands as a job.update op with actorClass: 'system' and
actorId: 'google-maps:directions', which tints the Commute cell in the
dashboard accordingly.
Unified CV / CL editor
CVLEditor.svelte is one editor surface, not two. The same component tree
edits the CV quarry, the CL quarry, the personal-info header, the format
settings, and (per-job) the tailored snapshots. The pane selector at
/[wsId]/cvl/[pane] switches which sub-doc the editor is bound to.
The editor walks the canonical Node tree directly — there is no flat
"sections" array — so every gesture (duplicate as variant, break out,
promote, demote) maps onto a cvl.* action op that expands into Node ops
inside a single Yjs transaction.
Per-language profile tree
cv_profile.<lang>.tree and cl_profile.<lang>.tree are independent peer
trees. EN is the base header layout fallback (see cv-ui-shell.md), but
content is not inherited across languages. The Profile Stack editor manages
which language tabs are available and which is the "live" target for
tailoring.
This shape keeps the quarry contract simple: a tailored snapshot for a German job comes from the German tree, full stop. There is no cross-language merge step that could leak FIXED-tier identity fields from one language into another.
Why a single pipeline matters
- One executor module per stage is the only thing that can keep the browser and CF-Worker backends from drifting.
- Per-job state means a stuck stage on one job never blocks every other job in the workspace.
- The editor and the pipeline share the same Yjs sub-doc layout, so the tailored snapshot a user inspects in the editor is the same bytes the pipeline wrote — no separate render pipeline, no presentation copy.