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.
Source: wiki/content/canon/cv-pipeline-and-editor.md