Commit Graph

497 Commits

Author SHA1 Message Date
cesnimda 3d74baef78 feat(workspace): interview and follow-up workflow
CI and Deploy / test (push) Failing after 3m54s
CI and Deploy / deploy (push) Has been skipped
Phase 5.5. Completes the lifecycle after submission: prepare, communicate, chase.

Interview preparation gets a durable, user-owned store. There were already two
per-application AI stores, InterviewPrepNote and AiWorkspaceNote, but both are
caches that regenerate when their context signature changes — anything a user
typed into them would eventually be overwritten. InterviewPrepItem is the side
nothing regenerates, covering company research, technical notes, behavioural
answers, STAR examples and the user's own questions in one table, because those
categories differ only by label and adding one must not need a migration. Each
item records whether the user wrote it or accepted a suggestion, and an
IsPrepared flag makes the section double as the preparation checklist.

Generation stays in the existing AiWorkspaceService "interview" module, appended
to AiInteraction as before. A suggestion is history until the user adds it as a
prep item; opening the section generates nothing.

Follow-up reuses what exists rather than adding a tracker. The date is
JobApplication.FollowUpAt, the same field RulesEngine and the reminder hosted
service already act on, so reminders keep working with no new wiring. The task
stays an ApplicationChecklistItem in the follow-up category — the section counts
open tasks without owning them. The record is a FollowUpSet JobEvent, the same
type the rest of the app emits.

Communication is untouched: Correspondence already owns recruiter contacts,
history and notes, and the workspace already mounted it.

The timeline interpreter learned five more types — InterviewScheduled,
InterviewCompleted and OfferReceived as milestones, FollowUpCreated and
FollowUpCompleted as routine, deliberately outside the milestone spine so it
stays a summary of what actually happened. JobEvent remains the history source.

InterviewPrepItems is reconciler-owned with a no-op migration, guarded on
JobApplications, and verified on a fresh MariaDB 11: int AUTO_INCREMENT primary
key, varchar owner and title, tinyint flag, datetime(6), composite index inside
the key limit.

371 backend tests, 128 frontend tests, Release build and the production build all
pass locally.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 17:01:33 +02:00
cesnimda 4759f1f610 fix(cv): support application variant deep links
CI and Deploy / test (push) Failing after 1m23s
CI and Deploy / deploy (push) Has been skipped
The Application Workspace CV section linked to /cv-builder?variant={id}. That
route does not exist: the builder is mounted at /career/builder/:id and reads the
variant from the path, not a query string. The button dead-ended.

Corrected the href. No loading logic was added — the editor already loads the
variant by id and already has a safe error state, and ownership is already
enforced server-side, where CvVariantService scopes every read to the owner and
the controller returns 404.

Added tests for the deep-link entry point, which had none: the variant loads from
the route, a missing variant shows the error state rather than an empty editor,
and another user's variant is refused identically. The asset test now asserts the
exact href, so a route that the router does not serve fails the build instead of
shipping.

118 frontend tests and the production build pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 16:47:32 +02:00
cesnimda 02b38f7acb feat(workspace): application assets workflow
CI and Deploy / test (push) Failing after 1m11s
CI and Deploy / deploy (push) Has been skipped
Phase 5.4. Connects the career outputs a user already has to one job
application, without building a second copy of any of them.

The flow is strictly one-directional — CareerProfile -> CvVariant ->
application output — and nothing writes back up. No code path in this phase
touches CareerProfile or its children.

CV integration re-points rather than duplicates. GET/PUT /{id}/cv attaches one
variant to an application via CvVariant.JobApplicationId; replacing detaches the
previous variant instead of deleting it. Creating, duplicating, editing, theming,
previewing, exporting PDF and version history all stay in the existing CV
builder, which the section links into. There is no second CV system.

Tailoring composes the Phase 5.3 analysis and match into skills to highlight,
experience to prioritise, projects to emphasise, keywords to include and gaps to
address. Deterministic and advisory: it says what the user could emphasise and
the user edits the variant themselves. Nothing auto-applies.

Cover letters gain the history they were missing. JobApplication.CoverLetterText
stays the current text with its API contract unchanged; CoverLetterVersions
records what it used to be, so an AI rewrite is never destructive. Restore is
additive — the old text comes back as a new version, so what you restored from
still exists. Source and AiAction record whether the user wrote a version or
approved it from a suggestion, and an AI generation only becomes a version once
the user saves it.

Documents are untouched: the existing Attachment system already covers CV, cover
letter, certificates and portfolio files with a Purpose field, so the workspace
mounts that component rather than adding a second upload path.

CoverLetterVersions is the only new table — reconciler-owned, no-op migration,
guarded on JobApplications, and verified on a fresh MariaDB 11: int
AUTO_INCREMENT primary key, varchar(255) owner, datetime(6), composite index
inside the key limit.

360 backend tests, 115 frontend tests, type check, Release build and the
production build all pass locally.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 16:36:27 +02:00
cesnimda a7cecce13d feat(workspace): add application intelligence
CI and Deploy / test (push) Failing after 1m11s
CI and Deploy / deploy (push) Has been skipped
Phase 5.3. Three read-only reads that answer "how suitable is this job", "how
does my experience match", "what am I missing", "what happened previously".

Timeline (GET /{id}/timeline) is an interpretation layer over JobEvent, which
stays the source of historical truth. Each row gains a readable summary, a
category and a milestone flag; events group by day. Milestones are returned
unfiltered, because narrowing the detail must not hide what actually happened.

Job analysis (GET /{id}/analysis) extracts role, company, location, employment
type, seniority, salary, technologies, skills, responsibilities and keywords
from the advert, reusing the existing SkillTagger so the vocabulary matches the
job importer. It also reports what the advert does NOT say, which is usually the
more useful half.

Career matching (GET /{id}/match) feeds the master CareerProfile into the same
JobCvMatchService the CV builder uses, so one application scores identically
whichever surface asks. It returns the score, matched and missing skills, and
which experience and project entries are the evidence for each match.

All three are deterministic and own no data — no new table, no new column, and
nothing writes to the CareerProfile, a CvVariant, or the JobApplication. The AI
narrative stays where it already was, in AiWorkspaceService's job-analysis and
career-match modules, generated only when the user asks and versioned by the
append-only AiInteraction history. Opening a section costs nothing and changes
nothing.

Frontend adds Timeline, Analysis and Match sections to the workspace, sharing
one loader so loading, empty and error states are consistent. The deterministic
answer renders first, with the AI panel below it.

345 backend tests, 104 frontend tests, type check, production build all pass
locally.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 13:52:37 +02:00
cesnimda 7f426e255c fix(infrastructure): support clean MariaDB initialization
CI and Deploy / test (push) Failing after 1m1s
CI and Deploy / deploy (push) Has been skipped
A completely empty MariaDB database could not start: the reconciler assumed
migration-owned tables already existed, and migrations assumed reconciler-owned
tables already existed. Neither could go first. Existing databases worked, so
only fresh installs were affected.

Startup is now an explicit sequence: connect, reconcile, migrate, reconcile,
start. The reconciler runs twice because neither position alone works — pass 1
repairs legacy schemas and creates the reconciler-owned tables that migrations
reference, pass 2 picks up everything that could not exist yet on a fresh
database. Every statement is existence-guarded, so the second pass is a no-op
scan on a correct database.

Untangled the overlapping ownership:

- RuleSettings is migration-owned. The reconciler also created it, which made a
  clean install fail with "Table 'RuleSettings' already exists". It now only
  seeds the default row, and only once the table exists.
- The six CareerProfile child tables are reconciler-owned. Their migration was
  scaffolded against SQLite and indexed an unbounded longtext OwnerUserId, which
  exceeds MariaDB's 3072-byte key limit; it is now a no-op and the reconciler
  carries correct per-provider DDL. OwnerUserId and ItemKey are bounded to
  varchar(255) in the model so the index fits.
- Reconciler tables that reference another table are guarded on their parent, so
  pass 1 skips them on an empty database instead of failing on the foreign key.
- All index creation goes through one EnsureMySqlIndex helper, guarded on table
  existence as well as index existence. This removes ten copies of the raw block
  that crashed on a missing table.
- The DbContext-owned connection is no longer disposed by the reconciler, and
  Open() is guarded on connection state, so the second pass can reuse it.

Verified against MariaDB 11 and SQLite: empty MariaDB (40 tables, starts),
restart on the populated database (idempotent, rows preserved), empty MariaDB
via the Docker image, fresh SQLite (42 tables), and an existing partially
migrated SQLite dev database (34 tables upgraded to 44 with all 13 applications
and 8 companies intact). 329 backend tests pass in Release.

Ownership rules, startup order, fresh install and production upgrade are
documented in docs/infrastructure/database-ownership.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 11:51:55 +02:00
cesnimda 1f1cbb92f3 docs(infra): docs-only commit fails CI identically — proves failure is not in the repo
CI and Deploy / test (push) Failing after 1m2s
CI and Deploy / deploy (push) Has been skipped
Run 531 (8f73548) changed a single markdown file: no application code, no test,
no dependency, no workflow file. Its test job failed at 1m18s, in the same
duration band as every run before and after it, including the two Phase 5
milestone commits.

A change that cannot affect compilation or test behaviour cannot cause a test
job to fail. That settles the question the investigation was asked: the cause is
outside the repository. The specific runner mechanism is still unidentified and
still needs the access already listed (job logs via a read-scoped token,
journalctl -u act_runner, runner container config).

Also records a separately discovered, pre-existing bug: booting against a
completely empty MariaDB fails because the MySQL reconciler assumes the
migration-owned tables exist. Reproduced on clean HEAD, unrelated to CI, and
harmless to prod, whose database is populated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 11:21:56 +02:00
cesnimda 3a906b881e feat(workspace): unified application checklist (Phase 5 milestone 2)
CI and Deploy / test (push) Failing after 1m8s
CI and Deploy / deploy (push) Has been skipped
Evolve the existing readiness workflow into one persisted, user-controlled
checklist rather than adding a second tracker.

ApplicationChecklistItem records only completion state and user intent. Each
default system item carries a stable SystemKey and an AutoSignal — the same
signal /readiness already computed — and re-syncs on every read: a satisfied
signal auto-completes the item, a reverted signal reopens it, and a manual tick
always wins. Users can add, reorder, dismiss and delete.

Readiness is refactored into a projection of the checklist (score = completion
percentage, completed/missing = live items by status). Its DTO shape and the
workflowSignal/reminders health view are unchanged, so no API contract breaks.

The workspace's next recommended action now comes from the first pending
checklist item in category priority order (preparation, submission, follow-up,
interview, custom), replacing the parallel ruleset — so the overview can never
recommend something already ticked off, and a user's own task can be next.

The table follows the established MariaDB-safe path: the scaffolded migration is
a no-op and the idempotent reconciler owns the DDL for both providers. Verified
on MariaDB 11 — auto_increment PK, varchar/datetime(6)/tinyint(1) columns, both
indexes inside the key limit, cascade delete, unique system key per application,
and NULL system keys not colliding for custom items.

329 backend tests, 94 frontend tests, type check, production build and both
Docker builds pass locally.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 11:15:46 +02:00
cesnimda e55a6e86b7 feat(workspace): Application Workspace foundation (Phase 5 milestone 1)
CI and Deploy / test (push) Failing after 1m13s
CI and Deploy / deploy (push) Has been skipped
Every JobApplication gets a dedicated workspace at /applications/{id} — a
surface, not a new data store. It owns no data and duplicates none: CV comes
from the Phase 4 CvVariant lens, analysis/match/interview from the existing
AiWorkspacePanel, documents from Attachments, communication from
Correspondence, activity from JobEvent, stage semantics from JobPipeline. No
career data is copied and nothing here writes.

- GET /api/jobapplications/{id}/workspace: one aggregate read (role, company,
  stage, dates, attached CV variant, cover letter, documents, AI history,
  recent activity) replacing the page fanning out across endpoints
- Next recommended action: ordered rules answering "what do I do next?", the
  core product principle for this phase
- ApplicationWorkspacePage: left nav + linkable ?section=, reusing the existing
  component for each domain; later-milestone sections say so rather than faking
- Entry point from the job dialog via an optional onOpenWorkspace callback —
  the dialog must not depend on router context (it is mounted without a
  <Router> in several suites), so the caller owns navigation
- 8 backend tests (aggregate, CV variant surfacing, counts, activity ordering,
  next-step rules, tenant scoping)

Local: 314 backend, 88/88 frontend (31 suites), tsc clean, production build ok.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 23:57:24 +02:00
cesnimda 3b59152782 docs(infra): runner fails at different stages across runs — nondeterministic
CI and Deploy / test (push) Failing after 1m0s
CI and Deploy / deploy (push) Has been skipped
Two post-reboot runs: the first reproduced the failure identically (smoke 1s
pass, suite 3s fail), the second failed earlier at `dotnet restore` in 0s — a
step that succeeded in 3-4s on every previous run, same commit, same runner.

That rules out stuck state (reboot changed nothing) and rules out a
deterministic sandbox policy such as seccomp/W^X blocking runtime IL emission,
which was the leading remaining hypothesis. Combined with host telemetry
showing no disk/memory/PID pressure, confidence in any specific mechanism drops
to ~25%; confidence that application code is not the cause stays high.

Removes the pure-vs-Moq diagnostic scaffolding (it never executed). No test
skipped or weakened.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 23:20:19 +02:00
cesnimda c4c0cd4e42 ci: test whether EF-InMemory/Moq (runtime IL emission) is the runner trigger
CI and Deploy / test (push) Failing after 52s
CI and Deploy / deploy (push) Has been skipped
2026-07-18 23:17:12 +02:00
cesnimda 8f73548e33 docs(infra): host telemetry rules out disk/memory/PID exhaustion and fail2ban
CI and Deploy / test (push) Failing after 1m18s
CI and Deploy / deploy (push) Has been skipped
Server shows 62G free (71% used), inodes 14%, 16G /dev/shm (~32G RAM), ulimit
-u 127749, no cgroup pids.max, and no fail2ban installed. That falsifies both
resource-exhaustion hypotheses at the host level and the fail2ban explanation
for the deploy failure.

Notes the caveat that Gitea act_runner usually runs jobs inside a Docker
container, so host figures do not describe the environment the tests ran in
(separate cgroup limits, and a 64MB /dev/shm by Docker default).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 23:14:28 +02:00
cesnimda 55962bd29b docs(infra): conclude runner + deploy investigation — both outside the repo
CI and Deploy / test (push) Failing after 1m1s
CI and Deploy / deploy (push) Has been skipped
Moves the report to docs/infrastructure/runner-investigation.md with the
requested structure (evidence, experiments, hypothesis, confidence, required
infrastructure changes, why application code is no longer suspected).

Decisive new experiment: the suite was run from a clean `git archive HEAD`
tree — byte-identical to CI's checkout, without the gitignored runtime dirs
(jobtracker.db, keys/, CvArtifacts/, backups/) that earlier local runs had
silently included. 10/10 pass in 1s. That removes the last difference between
the local tree and the runner, eliminating application code (~95% confidence).

Also establishes, by route probe, that production is healthy but stale:
/api/public-cv/{unknown} returns 404 locally (route exists, AllowAnonymous) but
401 on prod, same as a nonsense path — PublicCvController is absent, so Phase 4
and Phase 5 have never deployed. Production therefore never ran the faulty
migration: no half-built tables exist there and no data cleanup is needed.

Deploy is a second, separate infrastructure failure: the first attempt reached
deploy.sh (37s, consistent with the MariaDB crash since fixed), every attempt
since dies at 3s at SSH connection time while the host serves traffic normally.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 23:00:27 +02:00
cesnimda b7dc07b045 docs(ci): report runner-only backend test failure investigation
CI and Deploy / test (push) Failing after 1m8s
CI and Deploy / deploy (push) Has been skipped
Bisected the failure across four CI runs (job logs are not readable via the
Gitea API) down to the AiWorkspace test classes — 10 tests that pass on
Windows, in a clean Linux container, under a 1GB memory cap, in CI's exact
step order, with a custom-dir SDK and no DOTNET_ROOT, serially, and under a
hostile locale/timezone.

Ruled out: Linux behaviour, case sensitivity, path separators, locale/culture,
time zone, environment variables, parallel execution, test ordering, shared
state, memory. Not testable remotely: host permissions/limits.

Assessment is environmental: the workflow already documents three failure
modes on this same runner with an identical signature (processes dying with no
error output — SDK cache corruption, npm ci SIGSEGV, CRA build OOM/SIGSEGV).
Report includes evidence table and recommended infrastructure fix.

Removes the temporary bisection scaffolding; keeps the restore/build/test split
and the host smoke. No test was weakened, skipped, or filtered.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 22:12:00 +02:00
cesnimda 0f62dc42c2 ci: split failing slice per test class to name the offender
CI and Deploy / test (push) Failing after 1m0s
CI and Deploy / deploy (push) Has been skipped
2026-07-18 21:57:29 +02:00
cesnimda 7fa3080a28 ci: bisect backend suite across steps to localise runner crash
CI and Deploy / test (push) Failing after 1m0s
CI and Deploy / deploy (push) Has been skipped
The host smoke passes, so the test host starts; the full suite still dies ~3s
in with parallelism disabled, so one specific test takes the process down on
this runner only. Job logs are unreadable via the API, so the suite is sliced
across four steps — the first failing step identifies the class. Temporary
diagnostic scaffolding; every test still runs, nothing is skipped.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 21:55:35 +02:00
cesnimda 2bdc4a9748 ci: isolate backend test-host failure and serialise the suite
CI and Deploy / test (push) Failing after 1m0s
CI and Deploy / deploy (push) Has been skipped
Restore and build pass on the self-hosted runner but the test run dies after
~3s — too fast to have executed 306 tests. The suite passes on Windows, in a
clean Linux container, under a 1GB memory cap, in CI's exact step order, and
with the SDK installed to a custom dir without DOTNET_ROOT, so the trigger is
specific to this runner rather than the code.

Adds a one-test host smoke step (separates "host cannot start" from "the suite
takes the host down" using step boundaries, since job logs are not readable via
the API) and disables xUnit collection parallelism for the full run — the same
remedy the frontend already needs (--runInBand) on this resource-flaky runner.
All 306 tests still run; only concurrency changes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 21:52:26 +02:00
cesnimda 45725acc7c ci: split backend restore/build/test into separate steps
CI and Deploy / test (push) Failing after 1m0s
CI and Deploy / deploy (push) Has been skipped
The backend test step fails on the self-hosted runner after 8s while passing
on Windows, in a clean Linux container, and in CI's exact build-then-test
order. The job log is not readable via the Gitea API (401), so step boundaries
are the only available telemetry: splitting restore / build / test makes the
failing phase identifiable from step timings alone.

Restore retries once, mirroring the npm ci and dotnet SDK retries already in
this workflow for the same runner's known flakiness. The suite itself is
unchanged — still the whole suite, nothing filtered or skipped.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 21:35:16 +02:00
cesnimda cfba7fbbc4 fix(ci): actually run the backend test suite
CI and Deploy / test (push) Failing after 1m3s
CI and Deploy / deploy (push) Has been skipped
The build step only builds JobTrackerApi, so the test project was never
compiled — and `dotnet test --no-build` then made the step a ~1s no-op
(locally it errors "test source file not found"; on the persistent
self-hosted runner it can silently run a stale binary). The 306 backend
tests have not been gating CI.

Drop --no-build so the test project is compiled and the suite runs.
Verified locally: 306 passed in 14s instead of "succeeding" in 1s.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 20:47:44 +02:00
cesnimda 1430313a20 fix(db): provision CV builder + AI workspace tables via the MySQL-safe reconciler
CI and Deploy / test (push) Successful in 2m19s
CI and Deploy / deploy (push) Failing after 7s
Deploy failed on prod (MariaDB) while the test job was green: backend startup
threw during Database.Migrate(), so deploy.sh's post-deploy health check exited.

Root cause: AddCvVariants/AddAiInteractions were scaffolded against SQLite, so
they bake SQLite type names into their DDL — DateTimeOffset emits `TEXT`, bool/int
emit `INTEGER`, and the PK gets no AUTO_INCREMENT. Run against MariaDB that yields
a structurally wrong table, and the composite index over a TEXT column then trips
"ERROR 1071: Specified key was too long; max key length is 3072 bytes". SQLite
accepts all of it, which is why local/container verification passed.

Fix, following the pattern already used for CareerProfiles/AiWorkspaceNotes:
- both migrations become no-ops; the three tables are reconciler-owned
- reconciler provisions them idempotently per dialect (MySQL: varchar/int
  AUTO_INCREMENT/datetime(6); SQLite: CREATE TABLE IF NOT EXISTS)
- DropMalformedMySqlTable rebuilds a half-built table left by the failed
  migration, guarded on row count so a table with ANY rows is never dropped
- bound the indexed string columns with HasMaxLength so the model matches

Verified against a real MariaDB 11 container: reproduced error 1071, then
confirmed the corrected DDL yields auto_increment PKs, varchar/datetime columns
and all previously-failing indexes. 306 backend tests green; SQLite container
starts clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 20:41:29 +02:00
cesnimda 7a74311a71 test(settings): unbreak stale settings-view assertion blocking CI
CI and Deploy / test (push) Successful in 2m15s
CI and Deploy / deploy (push) Failing after 37s
The Notifications tab dropped the old "check system status" link; SMTP status
now lives under Admin → System → Settings (settingsNotificationsDelivery). The
test still asserted the removed text, failing the frontend CI job and blocking
deploy. Point the assertion at the current delivery caption. Full suite 88/88.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 15:30:36 +02:00
cesnimda fd9bd3f47a docs(architecture): document the AI Career Assistant (Phase 5)
CI and Deploy / test (push) Failing after 1m56s
CI and Deploy / deploy (push) Has been skipped
ai-career-assistant.md (modules, prompt flow, provider abstraction, append-only
history model, extension points, security). Master guide + roadmap Phase 5
updated with the shipped workspace and the open provider-selection extension.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 15:26:04 +02:00
cesnimda bb0c0feb4c feat(ai): AI Workspace panel on every job application
CI and Deploy / test (push) Failing after 1m54s
CI and Deploy / deploy (push) Has been skipped
Phase 5 frontend. A new "AI Workspace" tab in the job details dialog hosts the
five suggestion modules (Job Analysis, Career Match, Cover Letter with tone,
Interview Prep, Application Review) with a generate flow, a dependency-free
markdown renderer for results, and a history sidebar (reuse / compare / copy /
delete). Everything is suggestion-only — copy to keep; nothing auto-applies.

- aiWorkspace.ts (types + API), components/AiWorkspacePanel.tsx,
  components/Markdown.tsx (no HTML injection surface — renders React nodes)
- mounted as the last tab in JobDetailsDialog (index-safe, no reindexing)
- 3 tests (generate flow, cover-letter mode, markdown); tsc + build clean

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 15:23:23 +02:00
cesnimda f299d7be7c feat(ai): AI Workspace per job application — modules + append-only history
CI and Deploy / test (push) Failing after 1m52s
CI and Deploy / deploy (push) Has been skipped
Phase 5 backend. A unified AI Workspace for each application, orchestrating the
five suggestion modules through the existing ISummarizerService provider
abstraction and storing every generation as append-only history (AiInteraction)
so outputs can be reused, compared, and deleted — distinct from the existing
AiWorkspaceNote cache (one row, overwritten).

Modules (all suggestion-only, "never invent facts" guardrail, never mutate the
profile/variant/application): job-analysis, career-match, cover-letter (6 modes),
interview, application-review. Each builds a prompt from the job + master profile
text and returns markdown.

- Models/AiInteraction.cs + migration AddAiInteractions (verified on container)
- Services/AiWorkspaceService.cs (prompts, history, delete)
- Controllers/AiWorkspaceController.cs (/api/jobapplications/{id}/ai:
  generate, history, delete, modules+provider)
- 7 tests (store, history filter/order, delete, mode normalization, unknown
  module, empty output, tenant scoping); 306 backend green

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 15:17:13 +02:00
cesnimda 074c78a7ef docs(architecture): record Phase 4.5 builder polish
CI and Deploy / test (push) Failing after 2m4s
CI and Deploy / deploy (push) Has been skipped
cv-builder.md (outline-driven Content tab, DnD, rich text, preview page nav,
public-CV routing fix + deployment note), cv-theme-engine.md (rich text/ATS/
print), roadmap Phase 4.5 shipped.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 14:48:33 +02:00
cesnimda 582c4e07e9 feat(career): builder UX overhaul — DnD, rich text, per-item editing, richer preview
CI and Deploy / test (push) Failing after 1m56s
CI and Deploy / deploy (push) Has been skipped
Phase 4.5 (priorities 1, 2, 5, 6).
- Content tab now reads GET /api/cv/outline: each entry-based section expands
  to its entries with per-item hide, title/subtitle override, and rich-text
  bullet editing (RichTextField markdown toolbar).
- Native HTML5 drag-and-drop reorder for sections AND entries (useDragReorder,
  zero deps), with drop-line + dim animations; arrow buttons remain for keyboard.
- Preview: zoom presets (+/- , slider, Fit), measured page count with page
  navigation and dashed page-break indicators, an "updating…" chip, 300ms debounce.
- Save indicator now distinguishes Unsaved / Saving / Saved / failed (aria-live).
- Loading skeletons, better empty states, ATS-friendly theme badge, relative
  times in History; a11y: ARIA labels, keyboard-selectable theme cards, focus rings.
- Pure helpers moveItem/wrapSelection extracted + unit-tested; tsc + build clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 14:46:37 +02:00
cesnimda e3b255f226 feat(career): theme polish, rich-text bullets, entry ordering, outline API
CI and Deploy / test (push) Failing after 1m55s
CI and Deploy / deploy (push) Has been skipped
Phase 4.5 backend enablers.
- Themes (priority 3): AtsFriendly flag on single-column themes (surfaced in
  GET /api/cv/themes), print-quality page-break rules (entries never split
  across a page; headings stay with content; widow/orphan control), darkened
  the creative sidebar for AA contrast.
- Rich text (priority 1): bullets/summary support **bold**, *italic*,
  __underline__, [text](url) via a safe inline pass — everything is HTML-escaped
  first, so no user tag can survive; only the whitelist emits markup.
- Entry ordering (priority 1): CvSectionSetting.ItemOrder reorders entries
  within a section by ItemKey, never touching the master profile.
- Outline API: GET /api/cv/outline returns the master profile as sections+entries
  with ItemKeys, so the Content tab can render editable per-item rows.
- 3 new tests (22 total in the builder suite).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 14:38:35 +02:00
cesnimda 585047da9e fix(career): public CV deep links work on direct load, refresh, and shared links
CI and Deploy / test (push) Failing after 1m55s
CI and Deploy / deploy (push) Has been skipped
Phase 4.5 (priority 4). The app is a React Router SPA behind Next static
export, which only generated `/` — so a hard load of any deep path (/cv/{slug},
/login, /career/builder/…) hit Next's client not-found before React Router
could route it. Replace the single app/page.tsx with an optional catch-all
app/[[...slug]] (server page + client shell so generateStaticParams stays
server-only) that matches every path; nginx already serves index.html for
unknown paths (try_files), so React Router now owns routing on direct load.

Also: PublicCvPage shows a friendly 404 empty state and sets the document
title. Verified live — /login and /cv/{slug} both resolve on direct navigation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 10:34:56 +02:00
cesnimda 17edf19f89 docs(architecture): document CV builder, theme engine, Phase 4 status
CI and Deploy / test (push) Failing after 1m53s
CI and Deploy / deploy (push) Has been skipped
cv-builder.md (variant model, rendering pipeline, API, builder workflow,
extension points, known deep-link limitation) + cv-theme-engine.md (how a
theme is data and how to add one). Roadmap Phase 4 marked foundation-shipped
with the remaining polish itemised.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 10:07:43 +02:00
cesnimda 158dd02b00 feat(career): CV Builder UI — 3-tab builder, live preview, variants, public CV
CI and Deploy / test (push) Failing after 1m57s
CI and Deploy / deploy (push) Has been skipped
Frontend for Phase 4. /career/builder lists CV variants; the editor has the
three spec tabs (Content / Customize / AI Tools, plus History) beside an
always-on live preview that re-renders through the server theme engine on a
debounce. Content: reorder/hide/rename sections, headline override, custom
sections. Customize: 8-theme picker, accent colour, fonts, density, page size,
photo/icons/page-number toggles. AI Tools: suggestion-only assistance (never
auto-applied). Autosave with version history + restore, public on/off with a
copyable /cv/{slug} link, PDF export. Public read-only page at /cv/:slug.

- cvBuilder.ts (types + API), CvBuilderPage, CvBuilderEditor, PublicCvPage
- routes + nav wired in App.tsx; "Open CV Builder" entry on Career Workspace
- 2 component tests; tsc + production build clean

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 10:05:21 +02:00
cesnimda a3e18e4b44 feat(career): CV builder backend — data-driven theme engine + variant model
CI and Deploy / test (push) Failing after 1m51s
CI and Deploy / deploy (push) Has been skipped
Phase 4 foundation. A CvVariant is a lens over the master CareerProfile
(section order/visibility, per-item overrides keyed by ItemKey, theme +
builder settings) — it references career data, never duplicates it. One
renderer (ThemedCvRenderer) draws every theme; a theme is pure data
(CvThemeCatalog, 8 professional themes), so adding a theme needs no renderer
change. Autosave version history + non-destructive restore, public CV via
/api/public-cv/{slug} (anonymous, noindex, filter-bypassing owner load), and
an AI-assist endpoint reusing the existing provider abstraction (suggestions
only, never auto-applied).

- Models: CvVariant/CvVariantVersion, CvVariantSettings, CvTheme + catalog
- Services: CvVariantResolver (profile+lens -> render model), ThemedCvRenderer,
  CvVariantService, CareerProfileService.LoadStructuredForOwnerAsync (public)
- API: CvVariantController (/api/cv), PublicCvController (/api/public-cv)
- Migration AddCvVariants (2 self-contained tables; verified applied on the
  running container), 16 tests (resolver/renderer/service), 296 backend green

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 09:52:58 +02:00
cesnimda 707d8c59d2 docs(architecture): mark Phase 3 career foundation shipped
CI and Deploy / test (push) Failing after 1m51s
CI and Deploy / deploy (push) Has been skipped
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 01:03:40 +02:00
cesnimda a1dd447091 feat(career): version history restore on /career
CI and Deploy / test (push) Failing after 1m54s
CI and Deploy / deploy (push) Has been skipped
Phase 3, versioning UI. The /career overview now shows a collapsible version
history with a Restore action per past version, backed by the versioning API.
Restore reapplies the chosen snapshot as a new version (non-destructive) and
refreshes the profile + completeness in place.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 01:01:54 +02:00
cesnimda f1bf92a4e0 feat(career): add career profile versioning
CI and Deploy / test (push) Failing after 1m54s
CI and Deploy / deploy (push) Has been skipped
Phase 3, version history (list + restore). CareerProfileVersions was already
populated on every save; this makes it usable.

- ICareerProfileService.ListVersionsAsync — the append-only history, newest first,
  with the current version flagged.
- RestoreVersionAsync — reapplies a past snapshot NON-DESTRUCTIVELY: it is re-saved
  as a new version, so the current state stays in history and the restore is itself
  reversible. Syncs the relational children + blob projection like any save.
- Endpoints: GET /career/profile/versions, POST /career/profile/versions/{v}/restore.

Tests (+4): versions listed newest-first with current flagged; restore reapplies
an old snapshot as a new version (history preserved, reversible); restore of a
missing version returns null.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 00:58:18 +02:00
cesnimda 2b57d65715 feat(career): wire /career to the relational profile API + completeness overview
CI and Deploy / test (push) Failing after 1m52s
CI and Deploy / deploy (push) Has been skipped
Phase 3, frontend. /career now reads and writes the master profile through the
relational source of truth instead of the legacy blob path.

- CareerProfilePage loads GET /career/profile (structured profile from the
  relational children + cvText + completeness) and saves PUT /career/profile
  ({ profile, cvText }). This keeps the relational store authoritative — the
  previous PUT /auth/profile blob write left it stale after first load.
- Added a "Profile completeness" overview (percent bar + missing sections) at the
  top of /career, from the server scorecard.
- PUT /career/profile now accepts { profile, cvText } so the single /career save
  covers both the structured profile and the raw imported text; GET returns cvText.

Tests: career-save asserts the /career/profile payload; new completeness-overview
test; controller tests updated for the request wrapper. 75/76 frontend pass (the
1 failure is the unrelated pre-existing settings-view suite); prod build clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 00:53:35 +02:00
cesnimda b203120ab4 feat(career): master career profile API
CI and Deploy / test (push) Failing after 1m56s
CI and Deploy / deploy (push) Has been skipped
Phase 3, API layer. GET/PUT /api/career/profile — the endpoint the /career editor
uses to read and write the master profile.

- GET: returns the structured profile (assembled from the relational children,
  backfilled from the blob if needed) plus a completeness scorecard.
- PUT: validates limits, persists via CareerProfileService (relational children +
  append-only version), then serializes the result into
  ApplicationUser.ProfileCvStructureJson so the legacy read paths stay in sync.
  Identity fields are untouched (they belong to /profile).
- GET /completeness: just the scorecard, for the overview.
- CareerCompleteness: weighted percent + missing sections.
- CareerProfileValidator: item-count/length limits (abuse guard, NOT completeness
  — a work-in-progress profile always saves).

Tests (+4): put/get round-trip + projection sync, completeness, over-limit
rejection, empty WIP profile accepted.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 00:43:26 +02:00
cesnimda 46ff9454a8 feat(career): relational projection and backfill for the master profile
CI and Deploy / test (push) Failing after 1m53s
CI and Deploy / deploy (push) Has been skipped
Phase 3, service layer. CareerProfileService now maintains the relational children
as the source of truth for structured career data, with the StructuredCvProfile
blob kept as a derived projection.

- SaveVersionAsync additionally syncs the relational children (replace-all,
  preserving ItemKeys from the blob item ids; SortOrder = array position) and the
  LongTailJson (contact, summary, interests, other sections, metadata).
- New LoadStructuredAsync reads the master profile from the relational children,
  lazily backfilling from the ProfileJson blob for profiles that predate Phase 3.
- CareerProfileMapper: the two-way projection between relational rows and
  StructuredCvProfile.

Tests (+5): round-trip through relational, item-key preservation, wholesale child
replacement (no orphans), backfill from a pre-Phase-3 blob, empty profile.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 00:39:53 +02:00
cesnimda 3a4c8fbc10 feat(career): structured career profile foundation
CI and Deploy / test (push) Failing after 1m55s
CI and Deploy / deploy (push) Has been skipped
Phase 3, schema layer. Relational children of CareerProfile — the editable master
career profile. See docs/architecture/career-profile-model.md.

- New entities (Models/CareerEntities.cs): CareerExperience, CareerEducation,
  CareerSkill, CareerProject, CareerCertification, CareerLanguage. Each carries
  OwnerUserId (tenant filter), a stable ItemKey (carried from the blob so future
  CV variants can reference items), and SortOrder. List fields persist as JSON
  string columns via [NotMapped] accessors — plain TEXT, reconciler-friendly.
- CareerProfile gains typed child collections + a LongTailJson column (contact,
  summary, interests, achievements, orgs, pubs, courses, custom sections,
  metadata). ProfileJson becomes a derived projection for legacy read paths.
- DbContext: DbSets + tenant query filters + ordered indexes; FK/cascade by
  convention via the typed collections.
- Migration hand-edited to add only the 6 new tables + LongTailJson; the
  scaffolder re-emitted four reconciler-owned tables (AiWorkspaceNotes,
  CareerProfiles, InterviewPrepNotes, CareerProfileVersions) which were stripped.
  The regenerated snapshot now includes them, closing the drift. Verified against
  a copy of the real dev DB: applies cleanly, no data loss.

Long tail (achievements/orgs/pubs/courses) starts as JSON; promotable to
relational later without a source-of-truth change. Source-of-truth flip stays
deferred; the blob is kept as a derived projection.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 00:34:33 +02:00
cesnimda 9c8644e9f9 docs(architecture): document career profile model
CI and Deploy / test (push) Failing after 1m54s
CI and Deploy / deploy (push) Has been skipped
Phase 3 foundation: entities, relationships, ownership, source-of-truth, and
snapshot rules for the structured career profile. Relational children
(Experience/Education/Skill/Project/Certification/Language) under CareerProfile;
long tail as JSON; blob (ProfileCvStructureJson) becomes a derived projection for
legacy read paths; lazy non-destructive backfill.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 00:25:02 +02:00
cesnimda cf8b2fa014 docs(architecture): document profile and career ownership
CI and Deploy / test (push) Failing after 1m50s
CI and Deploy / deploy (push) Has been skipped
Add section 4a to docs/architecture/current.md: request flow, data ownership,
API responsibilities, and future extension points for the /profile vs /career
separation completed in Phase 2/2.2.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 00:13:06 +02:00
cesnimda 21c9b1ea63 refactor(profile): slim ProfilePage and CareerProfilePage to their own concerns
CI and Deploy / test (push) Failing after 1m53s
CI and Deploy / deploy (push) Has been skipped
Complete the Phase 2.2 split. Each dedicated component now carries only its own
state, effects, and JSX; the shared-copy duplication from the split checkpoint
is removed.

- ProfilePage (/profile): 1372 -> ~495 lines. Dropped the 700-line master-CV
  block, all CV/rewrite/PDF state + helpers + the extraction-run polling effects.
  loadProfile now fetches only /auth/me (no runs/jobs). Saves identity only.
- CareerProfilePage (/career): dropped identity fields, password, 2FA/sessions
  and their state; loadProfile no longer sets identity fields. Saves the master
  profile only. Owns the master-CV editing surface.

Both save through the partial-update PUT /auth/profile, so neither can overwrite
the other's data. The master career profile stays the only editable source of
truth on /career.

Tests: the CV-editing tests in profile-page.test.tsx now render CareerProfilePage
(where that surface lives) — all 5 pass, fixing 4 pre-existing failures that were
caused by the display:none shared block.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 00:11:42 +02:00
cesnimda e428274e39 refactor(profile): split account and career into dedicated components
CI and Deploy / test (push) Failing after 2m43s
CI and Deploy / deploy (push) Has been skipped
Phase 2.2 — stop backing /profile and /career from one component behind a
boolean. /career now renders a dedicated CareerProfilePage; /profile keeps
ProfilePage. Each hardcodes its mode and saves only its own concern (identity
vs master profile) via the partial-update endpoint.

This commit is the behaviour-preserving checkpoint: the two components still
share the full implementation (each carries all state, only its own JSX renders).
The per-component pruning that removes the other concern's state/JSX follows in
subsequent commits, verified by tsc at each step.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 23:01:53 +02:00
cesnimda 8b5ad03808 docs: mark Phase 2 profile/career separation done (2.1)
CI and Deploy / test (push) Failing after 2m59s
CI and Deploy / deploy (push) Has been skipped
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 20:01:22 +02:00
cesnimda 66cc6a7db4 feat(phase-2): separate /profile (identity) from /career (master profile)
Phase 2 — Career/Profile separation. The master career profile is the source of
truth; identity and career data are now saved independently so neither wipes the
other. CV Builder deliberately not built yet.

Backend — PUT /auth/profile is now a partial update:
- null/omitted field -> unchanged; "" -> cleared; value -> set (trimmed).
- Email/UserName never cleared to empty (login identifiers).
This lets /profile save identity fields and /career save the master-profile
fields through the same endpoint without one nulling the other. 4 new tests
cover the data-integrity guarantees (identity save keeps the CV, career save
keeps identity, empty clears, null leaves).

Frontend:
- ProfilePage save payload is now scoped by careerOnly: /career sends only
  { profileCvText, profileCvStructureJson }, /profile sends only identity.
- CareerWorkspacePage: removed the inert "CV Builder" tab (careerView) — Phase 2
  establishes the master profile only; the builder is Phase 4.
- Dropped the dead careerView prop.
- Updated the CV-save test to render career mode and assert identity is excluded.

Source-of-truth flip (CareerProfileService authoritative) stays deferred to F5
per the branch design; CareerProfileService keeps mirroring via its dual-write.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 19:59:38 +02:00
cesnimda a28c47f515 docs: mark Career Workspace foundation integrated; record what remains on the branch
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 19:17:37 +02:00
cesnimda 992f89e619 feat: integrate Career Workspace foundation from feature/career-workspace
Recover the F1 Career Profile foundation + AI-workspace persistence from the
unmerged feature/career-workspace branch, so Phase 2 builds on the documented,
tested target state instead of re-deriving it. Foundation only — CV Builder
commits (variants, ATS badge, rewrite diff) stay deferred per "do not build CV
Builder yet". See docs/career-workspace-branch-assessment.md.

Squashed from 3 branch commits (235e291, 5916f09, 00a035e), resolved against
main + Phase 0:

- CareerProfile + CareerProfileVersion (append-only history), dual-written from
  every profile save path via CareerProfileService. ApplicationUser.
  ProfileCvStructureJson stays authoritative; the tables mirror it. Stable item
  IDs assigned to jobs/education/certifications/projects (the prerequisite for
  future variant lineage). CvDateNormalizer for free-text -> YYYY-MM.
- InterviewPrepNote + AiWorkspaceNote: cache AI interview prep / candidate fit /
  focus plan keyed by an attachment-context signature, so they stop regenerating
  (and re-spending the provider) on every open.

Conflict resolutions (union, favouring current code + Phase 0):
- JobTrackerContext / StartupInitializationExtensions: kept Phase 0's tables and
  reconciler blocks, added the career/interview/ai-note tables (both SQLite and
  MySQL dialects).
- ProfileCvController: dropped the branch's in-file DTO records (main defines them
  in ProfileCvDtos.cs) and the LayoutFamily/AtsRating template fields (deferred
  ATS-badge work), keeping main's 7-arg CvTemplateDescriptor.
- JobApplicationsController: kept the branch's cache-check, restored main's
  AsNoTracking on the read-only user load.

Tables ship empty (verified dev); nothing to migrate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 19:11:42 +02:00
cesnimda aedd6e32ad docs: recover Career Workspace research + strategy from feature/career-workspace
Bring the four Career Workspace documents onto main as the target architecture
for Phases 2-4, and point MASTER_IMPLEMENTATION_GUIDE.md at them. Taken from the
branch tip (later commits refined them). Pure additions — none previously existed
on main.

- cv-builder-competitor-deep-research.md (Novoresume, Reactive Resume, FlowCV,
  Teal, Enhancv, Canva, Resume.io, Kickresume; matrix; pricing intelligence).
- cv-builder-product-teardown.md
- career-workspace-product-strategy.md
- career-workspace-implementation-roadmap.md (F0-F5)

MASTER_IMPLEMENTATION_GUIDE.md v1.1: adds a Source-Of-Truth Documents section and
restates the "profile is the source of truth; documents reference snapshots" rule.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 17:06:46 +02:00
cesnimda eac34705e3 feat: Phase 0 foundation — Job entity, expanded pipeline, AI service lockdown, DateApplied history
Unblocks the documented core workflow and closes the AI-service exposure,
without changing existing behaviour.

Job/JobApplication split (additive; see ADR-002):
- New Job entity (the opportunity) with owner-scoped query filter; nullable
  JobApplication.JobId FK. Nothing reads Job yet.
- Migration AddJobEntityAndProspectStages, hand-edited to drop reconciler-owned
  tables the scaffolder re-emitted; verified against the real dev DB.

Pipeline: 10 internal stages across three concerns kept separate —
PipelineStage (workflow) / PipelineGroup (UI: NotApplied/Active/Closed) /
PipelineCategory (analytics). Adds Saved/Interested/Preparing/Withdrawn;
keeps Waiting and Ghosted. Kanban shows 3 grouped columns; cards keep a stage
chip and full transitions; drag applies only safe transitions (never infers
Ghosted/Withdrawn).

DateApplied nullable + SavedAt. Cleared when leaving Applied so analytics stay
accurate; the discarded date is preserved as an AppliedDateCleared JobEvent.

AI service lockdown: no host port; private ai_internal network (backend is the
only other member); X-Ai-Service-Token required on all non-/health endpoints;
AI_SERVICE_TOKEN mandatory via compose. Verified backend-only against the live
stack.

Also carries two pre-existing working-tree files (views/ProfilePage.tsx,
views/CareerWorkspacePage.tsx) so the tree is clean for the branch integration.

Tests: +40 backend (247 total), +5 sidecar (16), +15 frontend.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 17:05:25 +02:00
cesnimda b176a44627 docs: reorganize tree, restore architecture + research from archive, add Phase 0 reports
Active docs/ was stub scaffolding while the real docs sat in docs/_archive/.
Restore and correct them, and record the Phase 0 work.

- docs/architecture/current.md: verified system map (from archived SYSTEM_OVERVIEW,
  9 corrections against code).
- docs/research/competitors.md: sourced competitor analysis (from archived
  PRODUCT_RESEARCH, feature matrix corrected).
- docs/decisions/ADR-002-job-application-model.md: the Job/JobApplication split.
- docs/application-discovery-report.md, docs/implementation-roadmap.md,
  docs/phase-0-foundation-report.md, docs/career-workspace-branch-assessment.md.
- Remove 10 zero-byte placeholder files that advertised content that never existed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 17:04:32 +02:00
cesnimda aa3567d8a8 feat(applications): guide job creation in steps 2026-07-15 10:39:13 +02:00
cesnimda d7d7e70d08 feat(ui): separate career and connected accounts 2026-07-15 10:37:02 +02:00