-
Notifications
You must be signed in to change notification settings - Fork 13
Architecture
The five-minute tour. The canonical source is docs/architecture/ in the repo (OVERVIEW.md, SERVER.md, FRONTEND.md, API.md, DATA-FLOWS.md).
Browser (SPA) Express server (Node ≥18) Parent career-ops project
──────────── ───────────────────────── ─────────────────────────
public/index.html server/index.mjs (~130 LOC cv.md config/profile.yml
└► js/app.js orchestrator) portals.yml data/*.md
└► js/router.js ──HTTP+SSE──► ├─ server/lib/routes/*.mjs (31) ──► reports/*.md modes/*.md
└► js/views/*.js ├─ server/lib/*.mjs (helpers) jds/*.txt output/*.pdf
└► js/api.js └─ server/lib/sources/*.mjs (59) scan.mjs doctor.mjs …
127.0.0.1:4317
-
index.mjs— acreateApp()factory, ~130 LOC after the P-2 refactor split (was 1230 LOC at v1.7.x). It is a pure orchestrator: wires middleware, callsregister<Topic>Routes(app)for each of the 31 route modules, mounts static serving frompublic/and the SPA catch-all. No inline route handlers remain.- Middleware: JSON/text body parsing, unconditional security headers (CSP is always on since v1.58.4), the activity-logging middleware, static file serving.
-
server/lib/*.mjs— shared helpers. Highlights:-
paths.mjs— the single source of truth for parent-project paths (resolveProjectRoot(),PATHS.*). Resolves once per process at import time. -
security.mjs— the one-and-only sanitizers:isValidJobUrl,stripDangerousMarkdown,sanitizeJobDescription,sanitizePathName,isPubliclyExposed. -
safe-fetch.mjs— DNS-rebind-safesafeGet(one lookup, pinned TCP connect, per-hop redirect revalidation, byte cap). -
rate-limit.mjs—llmRateLimit(no-op on loopback; 10 req/min/IP on public bind). -
file-lock.mjs—withFileLock(path, fn)per-path async mutex for read-modify-write onapplications.md/pipeline.md. -
llm-dispatch.mjs— the shared provider cascade (runActiveProvider/providerAvailable) overanthropic.mjs,openai.mjs(OpenAI/Qwen/OpenRouter),gemini.mjs. -
prompts.mjs—bundleProjectContextand the prompt builders that inline parent files into LLM requests. -
store.mjs— defensive readers (safeReadApps/Pipeline/Reports) +ensureRussianPortalsDefaults. -
parent-relay.mjs— the fail-soft shell-out relay contract (used by followup/patterns/lifetime/salary-gap). -
llm-usage.mjs/llm-pricing.mjs— usage rollup + editable price table for#/usageand the sidebar HUD. -
ru-scanner.mjs/en-scanner.mjs— the two in-process scanners. -
runner.mjs—runNodeScript(buffered) +streamNodeScript(SSE) for spawning parent.mjsscripts.
-
Each exports register<Topic>Routes(app). New routes go into server/lib/routes/<topic>.mjs, never back into index.mjs.
activity auto-pipeline batch career-plan cli-detect config content
cv-studio docs-assistant export followup health help interview jds
llm logos market memory networking openrouter orientation pipeline
portals reports runners scan stats tracker two-pager usage
See API Reference for the endpoint inventory each one owns, and Features for the views they back.
-
index.html— one page, ~150 LOC. Loads CSS + scripts via<script src>(no bundler), mounts#content, renders the sidebar nav + footer. -
js/app.js— boot: loads/api/health, renders the language switcher, starts the router, wires global shortcuts (Ctrl+K,Esc), the mobile drawer, and the notifications drawer that re-surfaces the per-tab toast journal. -
js/router.js— hash-router.Router.register('name', renderer)per view; renderers return a DOM node or HTML string. Aliases keep URLs stable across renames; dedicated 404 view. -
js/api.js—API.get/post/put/delete+API.stream(SSE). Wrapsfetch, normalizes errors, manages the connection-error banner. -
js/views/*.js— one file per route. Pure render-and-wire functions; no client-side state library. -
js/lib/*.js— self-contained widgets and helpers:i18n.js(locale loader +data-i18nwalker),usage-hud.js(sidebar usage meter),docs-fab.js(floating Ask-the-docs launcher),bug-report.js+logbuf.js(in-app bug reporter),cv-diagnostics.js,cv-privacy.js,company-logo.js,report-export.js(Markdown/PDF/DOCX export).
Styling is hand-written CSS with docs-style tokens in public/css/app.css, theme-aware (light/dark) and RTL-mirrored ([dir="rtl"]) for Arabic. CSP forbids inline scripts — every handler is addEventListener, never inline onclick=.
Adding an EN job board touches two registries — this is deliberate:
-
server/lib/sources/<slug>.mjs— the source meta registry. Exportsexport const meta = { value, label, region, configKey? }.server/lib/sources/registry.mjsreaddirSync-scans the folder at boot and dynamicallyimport()s every*.mjs, collecting eachmetablock (P-14, v1.69.0). This drivesGET /api/scan/sourcesand the#/scansource dropdown. 70 files = 65 EN + 5 RU. -
server/lib/portals/adapters/<slug>.mjs+server/lib/portals/registry.mjs(ALL_ADAPTERS) — the fetch-walk registry. 65 EN adapters that actually perform the HTTP fetch/parse walk.ALL_ADAPTERS.length === 65is asserted bytests/adapter-registry.test.mjswith the exact sorted id list.
RU sources additionally need a RU_DISPATCH row in ru-scanner.mjs. See Scanner Providers for the full walkthrough.
-
Reads are unrestricted — the UI reads
cv.md,config/profile.yml,portals.yml,data/*,reports/*,modes/*,jds/*,output/*,.env. -
Writes are explicit-user-action only — every write corresponds to a documented HTTP action initiated by a UI control. The complete list lives in
docs/architecture/DATA-FLOWS.md:-
PUT /api/cv→cv.md(sanitized, 1 MB cap) -
POST /api/tracker→data/applications.md(dedup, file-locked) -
POST/DELETE /api/pipeline→data/pipeline.md(URL-gated, file-locked) - scan runs →
data/scan-history.tsv+data/last-scan.json(append/atomic replace) -
POST /api/jds//api/evaluate→jds/*.txt -
POST /api/deep {run:true}→interview-prep/<company>-<role>.md -
POST /api/config→ parent.env(onlyKNOWN_KEYS) - user-layer config writes:
config/career-plan.md,config/memory.md,config/two-pager.yml,networking/net-*.md,data/role-stats.jsonl,data/follow-ups.md - the single auto-write:
ensureRussianPortalsDefaults()appendsrussian_portals:toportals.ymlon first boot (idempotent).
-
-
Boundaries never crossed: no write outside
PROJECT_ROOT, no symlink writes, no execution of arbitrary user-supplied scripts (runners invoke only a hardcoded list of.mjsfilenames).
- Parent layout discovery is dynamic — always
PATHS.<thing>, never literal... - CSP excludes
'unsafe-inline'/'unsafe-eval'fromscript-src. - Writes to the parent are explicit user actions only.
- Sanitizers are never duplicated — one
isValidJobUrl, onestripDangerousMarkdown, onesanitizeJobDescription.
career-ops-ui v1.209.0 · Repository · Releases · Issues
Guides
Reference
Development
Languages (Home)