What's new
Every improvement to Still Can Work, most recent first.
All notable changes to Still Can Work are documented here.
Format follows Keep a Changelog; versioning follows SemVer.
0.26.1 - 2026-10-04
Added
- **Verification scripts for three shipped stories whose Definition of Done was
open** (AB#2438, AB#2439, AB#2468). No app code changes.
- tests/e2e/ab2438-ab2439-ui.mjs is a Playwright run of "Correct for me",
`/rebuild?auto=1`, "Re-check my corrected resume", and editing the recruiter
resume inline (preview, copy, PDF text, print, hidden blank logistics). AI
routes are stubbed so it is deterministic; `PW_MODULE` points it at a local
Playwright install, which is not a dependency.
- tests/e2e/ab2468-gate-on.mjs runs with the access gate ON. A valid code
reaches the handler. No code, a wrong-secret code, an expired code or a
tampered code each get 401 `access_required`. `/api/unlock` stays open.
0.26.0 - 2026-10-04
Added
- Ignore Resume Review recommendations, re-score, and stop seeing them again
(AB#10522). Every ATS issue, age-bias flag, gap and line upgrade now has an
**Ignore** control with a reason - *It's inaccurate* or *I'm OK with this*.
Ignored items move to an **Ignored (n)** panel (Undo, Clear all), are left out
of "Apply these upgrades" and the recruiter resume, and are sent with every
later review so the model neither repeats them nor docks the score for them.
**Re-score without these** re-runs the review on the stored resume and shows
each score's before -> after. The list lives in this browser only
(`scw.ignored`), survives "Analyze a different resume" (a revised upload is
exactly when the same advice used to return), and is cleared by Clear all or
the workspace reset. `/api/analyze` takes an optional `ignored` field: a wrong
shape is a 400, over-long text and over-long lists are clamped, never rejected
(`lib/ignored.ts`, `docs/resume-review-ignore.md`).
0.25.7 - 2026-09-30
Security
- Next.js raised to 16.3.8 (AB#10101). Clears the critical GHSA-vcvr-r3jv-pc5j
(remote code execution in `next/og` `ImageResponse`; affected `>=16.2.0 <16.3.6`).
This app does not use `next/og` or `ImageResponse`, so exposure was lower, but the
dependency was in the vulnerable range. `eslint-config-next` moves to match.
- Transitive advisories cleared by npm audit fix (lockfile only): undici
(GHSA-3wwx-pv8p-q78v, GHSA-r53p-7pc4-xj5r, GHSA-rfgv-xxqx-mfg5 and others) and
`js-yaml` (GHSA-2883-xcg3-v3hh). `npm audit` now reports 0 vulnerabilities
(was 1 critical, 3 high).
0.25.6 - 2026-09-12
Fixed
- CI now actually runs the test suite (AB#4948). npm test has existed
all along and passes locally, but no workflow ever invoked it, so a
regression could land on `main` unnoticed. A new `quality` workflow builds
and runs the (fully mocked, credential-free) suite on every pull request
and push to `main`. Wiring it in surfaced two real bugs: the script itself
(`node --test "tests/**/*.test.mjs"`) only worked on newer Node versions,
fixed by switching to plain `node --test`; and every `tests/*.test.mjs`
file imports its subject straight from `lib/*.ts`, relying on Node's
native TypeScript type stripping, which the Node 20 this repo's other
workflows pin does not have — the `quality` workflow pins Node 24 instead.
- A weekly dependency scan now exists (AB#4943). Stillcanwork had no
dependency-scan workflow of any kind. A new `dependency-scan` workflow runs
`npm audit --omit=dev --audit-level=high` on a Monday schedule and on
demand (`workflow_dispatch`), so a newly-published advisory against an
unchanged dependency is caught even between PRs. It is not a required PR
check.
0.25.5 - 2026-09-12
Docs
- Repository references point at ChAImpNPO after the ChAImpOrg retirement
(AB#5186). The `ChAImpOrg` GitHub organization was deleted; the gitleaks
workflow comment and the README deploy instructions named it, and now point
at `ChAImpNPO/stillcanwork`.
0.25.4 - 2026-09-11
Fixed
- **A recruiter resume or rebuild no longer fails with a 500 when the coaching
feedback carries roles as objects** (AB#3674). `/api/analyze` returns
`suggestedRoles` as `{ title, why, ... }`; the pages map them to titles before
posting, but a payload that carried the objects through hit the route's schema
and was answered - and recorded by self-healing - as a server failure. Both
routes now accept either shape and keep the title (`lib/feedback.ts`), and a
form field that is still malformed is the caller's 400, not a recorded 500.
0.25.3 - 2026-09-09
Fixed
- The PR work-item-link gate was switched off by the Azure Boards bot (AB#4934).
`ab-link` carried `if: !endsWith(github.actor, '[bot]')` at job level, but
`github.actor` is whoever triggered the current event, not who authored the PR —
so the Azure Boards integration's own `pull_request: edited` run matched the
exemption and skipped. A skipped job reports `neutral`, which counts as passing
and supersedes the real run. The job no longer carries a job-level `if:` at all:
it always runs and always reports a real conclusion, and the bot exemption moved
into the step keyed on the PR **author**. The rules moved out of YAML into
`scripts/ab-link-check.mjs`.
0.25.2 - 2026-09-09
Security
- Next.js unauthenticated RCE — next 16.3.2 -> 16.3.3 (AB#4935). Two
critical advisories: GHSA-2xp9-vwfh-vxw4 (CVSS 9.5, unauthenticated RCE in the
Image Optimization API when AVIF files are used) and GHSA-p293-qw3h-jr36
(CVSS 9.0, unauthenticated RCE on Windows-hosted servers). The
image-optimization path is reachable by anyone who can request an image URL.
`eslint-config-next` moved with it.
- sharp 0.35.3 -> 0.35.4 (GHSA-rgj7-g3m4-5g8c, high — libheif), pinned
through `overrides` since it is transitive.
This repo runs no dependency scanner at all, so nothing here would have reported
either advisory. Found by scanning the estate's lockfiles directly; the coverage
gap is tracked on AB#4935.
0.25.1 - 2026-09-02
Fixed
- The footer cost could read $0.00 for up to ten minutes while real spend existed
(AB#3649). `/api/usage` was sent with `stale-while-revalidate=600`, so the CDN kept
serving the first (empty) response long after money had been spent - a figure that is
merely late is fine, one that reads zero looks broken. Now `max-age=30,
stale-while-revalidate=30`.
- Reading totals fetched every usage file on every cold request, so the read got
slower with each day recorded - the long CDN cache had been hiding it. Added a
60-second in-instance memo. The remaining limit is written down rather than
speculatively engineered: past days never change, so when history makes this slow the
fix is a rolled-up total for closed days with only today's files read live.
0.25.0 - 2026-09-01
Added
- AI running cost in the footer (AB#3649). Every model call now records its token
usage and cost, and the footer shows the **cumulative** spend across all visitors -
"what it takes to keep this running", not any individual's usage. Nothing per-visitor
is stored, so no one's spend is identifiable.
- Rates live in one place (lib/ai-pricing.ts), verified against the Anthropic pricing
table on 2026-09-01: Sonnet 5 $2/$10 per 1M in/out, Haiku 4.5 $1/$5.
- **A model with no published rate records its tokens and a null cost, never a
guessed one**, and the count of unpriced calls is reported alongside the total so it
is never read as complete when it isn't.
- GET /api/usage returns the public total; the per-model and per-route breakdown is
gated on `FEEDBACK_ADMIN_KEY`, the same key as the feedback listing.
- Writes are one blob per warm instance per UTC day, so instances never race each
other; within an instance writes are serialised. A shared counter would silently
lose increments, and a total that undercounts is worse than none.
- Recording is fire-and-forget and cannot fail a user's request. Because that is
exactly what made the incident pipeline silently dead (AB#3455), three consecutive
failures raise `USAGE_STORE_UNAVAILABLE` saying the displayed figure is now an
undercount.
Changed
- Per-IP rate limit raised from 15 to 40 AI calls per 10 minutes (AB#3648), sized for
a real working session rather than a round number. One application costs 3-4 calls, so
a 20-applications-a-day target is 60-80 calls a day - but the window is ten minutes, so
the burst matters, not the daily total. Each call takes 20-60s, so one person working
sequentially can only issue ~10-20 calls in that window; the old cap of 15 sat exactly
on that ceiling and throttled genuine use. The per-instance wallet guard (120/min) is
unchanged, so total spend exposure does not rise.
0.24.2 - 2026-09-01
Fixed
- Auto-filed incident Bugs would have been parented to another product (AB#3603).
`ADO_AUTOFILE_PARENT` defaulted to `768`, above a comment claiming that was "the
StillCanWork feature". It is not: 768 is `B1 - Automatic issue capture` in
`One\Platform\Portal`. The number came from the implementation this was modelled on and
was never updated. An existing test asserted `.../workItems/768`, so the defect was
locked in as expected behaviour.
There is now **no default**. An unset or invalid parent makes the filer refuse to file
and log `INCIDENT_FILING_UNCONFIGURED`, and `buildBugOps` omits the parent relation
rather than fabricating one - a Bug attached to another product's Feature reads as
tracked while being absent from every plan, which is harder to notice than nothing
being filed at all.
Configuration (done outside this repo)
- CRON_SECRET set on the Vercel project. isCronAuthorized() requires it, so with it
unset the daily `50 12 * * *` filing job had been rejected on every single run - the
reason no incident has ever been filed.
- ADO_AUTOFILE_PARENT set to 2420 (StillCanWork - AI reliability & operations).
Still blocked
- Filing needs an Azure DevOps credential - ADO_AUTOFILE_PAT (Work Items read/write), or
the federated `ADO_FILER_CLIENT_ID` + `AZURE_TENANT_ID` app registration. None is set,
and a credential cannot be created from here. Until one exists, incidents are captured
and stamped but never filed.
0.24.1 - 2026-09-01
Fixed
- The fit cover letter sometimes never named the employer (AB#3515). Found by driving
the real page in a browser: the fit result plainly carried `City of Tacoma`, the letter
named the role, opened "Dear Hiring Manager," and never said who it was to - which reads
as a mass mailing. Four earlier API runs happened to include the employer, so the
end-to-end check passed and the defect hid behind it. The prompt now requires the
organisation to be named in the body at least once, the E2E asserts the employer
separately from the placeholder check, and a unit test guards the instruction against
being lost in a reword.
0.24.0 - 2026-09-01
Added
- Cover letter written from the fit analysis (AB#3515). After a fit check on /fit,
a "Write my cover letter" action produces a ready-to-send letter built from what the
analysis actually found: matched requirements supply evidence-backed claims,
`atsKeywords` the posting's literal screening vocabulary, and `talkingPoints` the
opening hook. Previously the only cover letter lived on `/jobs`, was written from a
job-board snippet, and knew nothing about a fit check.
- Gaps are handled deliberately. Only gaps that are below high severity *and* carry
a credible mitigation are pre-empted, one sentence each. High-severity gaps are never
mentioned - a letter that volunteers a disqualifying weakness argues its writer out of
an interview, and this audience is already fighting an unfair screen.
- Only atsKeywords the candidate genuinely evidences are mirrored, and the prompt
forbids inventing an employer, title, date or credential.
- The letter renders in the page, is editable in place, and can be copied or printed.
Nothing is stored server-side.
- Implemented as an optional fit block on the existing POST /api/cover-letter.
Without it the route behaves exactly as before, so the `/jobs` flow is unchanged.
Inbound `fit` is clamped, never rejected (the AB#2946 lesson): over-long strings are
truncated, surplus and unusable entries dropped, and a non-boolean `have` coerces to
`false` so a keyword is never wrongly claimed.
0.23.2 - 2026-09-01
Fixed
- Every incident, feedback row and health check reported version: null (AB#2958).
`process.env.npm_package_version` is only set for a process started by an npm script;
Vercel's runtime does not start handlers that way, so it was undefined in every
deployment. Triage could not tell releases apart - precisely the information that was
needed while AB#2946, AB#3454 and AB#3494 were reaching users.
`next.config.ts` now injects `APP_VERSION` at build time from `package.json`, keeping
it the single place a version is written, and the new `lib/version.ts` exposes it with
an `npm_package_version` fallback for `npm run dev`. The four call sites
(`/api/health`, `/api/feedback`, `lib/incidents.ts`, `lib/incident-intake.ts`) use it.
A test fails if anything reads `npm_package_version` directly again, and another
asserts the changelog's top version matches `package.json`.
0.23.1 - 2026-09-01
Fixed
- "Build improved resume" returned 500, and the model chain never fell back
(AB#3494). `generateText()` resolves even when the model produced nothing usable;
the failure comes from the `output` **accessor**, which the AI SDK documents as
`@throws NoOutputGeneratedError`. Every `Output.object` route was written as
`const { output } = await withModelFallback(...)`, which reads that accessor
**after** the fallback has returned - so the chain recorded a success, returned, and
the getter threw straight past the `catch` that exists to try the next model.
New `withModelFallbackOutput()` reads `output` **inside** the callback, so the throw
lands where the fallback can see it. All 10 call sites converted (`analyze`, `fit`,
`interview`, `jobs`, `know-me`, `move`, `photo`, `rebuild`, `recruiter-resume`,
`linkedin`); `cover-letter` reads `text`, a plain property, and is unchanged.
A test walks `app/api/*/route.ts` and fails if any route destructures `{ output }`
again - the trap is silent when reintroduced, so it is guarded structurally.
**This is why 0.22.2 (AB#3454) did not fix the symptom.** Detecting the error by
class instead of by AI SDK v5 message text was necessary and correct, but the error
never reached the predicate. Production logs confirmed it: `/api/rebuild` 500s on the
deployment containing that fix, with no `skipping` line logged at all.
0.23.0 - 2026-09-01
Fixed
- Self-healing incident capture recorded nothing, and feedback could not be read
(AB#3455). Two independent faults, both dating from the project rebuild:
1. `BLOB_READ_WRITE_TOKEN` on the Vercel project held the **`FEEDBACK_ADMIN_KEY`
value** - a 48-character key with no `vercel_blob_rw_` prefix - so every Blob
call failed with `Invalid token: unable to extract store ID`. The store was also
connected to **zero** projects, so nothing was being injected. Fixed in Vercel:
the bogus variable was removed and the `stillcanwork-feedback` store is now
properly connected to the project.
2. Incidents read and write with `access: "private"`, but a Blob store's access is
**fixed when the store is created**, and `stillcanwork-feedback` is public - so
private operations answered `400 Bad Request` and could never have worked.
Incidents now use a dedicated **private** store (`stillcanwork-incidents`) via
`INCIDENTS_READ_WRITE_TOKEN`, falling back to `BLOB_READ_WRITE_TOKEN`. Public was
not an option: the path is `incidents/<signature>.json` with
`addRandomSuffix: false` and the signature derives from the error, so a public
URL would be guessable by anyone able to reproduce the error.
Added
- INCIDENT_STORE_UNAVAILABLE alarm - when the incident store rejects a write for
a configuration reason (bad token, wrong store access, denied access), a distinct
error is logged **once per instance**, in the same shape as AB#2422's
`AI_PROVIDER_MISCONFIGURED`. A self-healing pipeline that silently captures nothing
is worse than none, because it is trusted: AB#2946 and AB#3454 both reached users
while this pipeline was dead and mute. One-off blob errors are still warned about
per incident but are not escalated.
0.22.2 - 2026-09-01
Fixed
- **Resume analysis returned "We couldn't analyze that resume" on a transient model
answer instead of falling back** (AB#3454) - `isSkippable()` in `lib/ai.ts` detected an
unparseable model answer by message text, matching the **AI SDK v5** wording
(`"No object generated"`). The repo runs `ai@^7.0.77`, which throws
`NoOutputGeneratedError` with the message `"No output generated."`, so the predicate
never matched: one bad answer threw straight out of `withModelFallback` with 20 chain
entries left untried, and the route answered 500. Detection is now by **class and error
name** (`NoOutputGeneratedError.isInstance`, `NoObjectGeneratedError.isInstance`, plus a
name allow-list), never by message text. The predicate also walks the whole error chain
(`lastError`, `cause`, `errors[]`, cycle-safe) rather than following `lastError` alone,
so a provider failure buried three levels down still routes correctly. New
`unusableOutputReason()` reports `finishReason` (`length` = truncation) in the skip log.
- The AB#2422 test no longer scrapes lib/ai.ts as text and rebuilds it with
`new Function` - it imports `isBusyError` directly. The old form failed on any syntax
change to the file, which is a test that breaks on refactors rather than on regressions.
Known broken, not fixed here
- Vercel Blob is unusable on this project (AB#3455), so self-healing incident capture
(AB#2593) discards every incident and `GET /api/feedback` answers 500. Both fail with
`Invalid token: unable to extract store ID`. This needs the Blob store reconnected to
`prj_hcm7yoUTunY6JEwcIgB4rQ8UZEPH` in Vercel - a project-configuration change, not a
code change. It is why AB#2946 and AB#3454 both went unreported by the pipeline built
to catch them.
0.22.1 - 2026-08-31
Fixed
- **Job match and fit check failed for anyone whose AI-generated career profile
ran long** (AB#TBD) - `careerContextSchema` capped `workPreferences` at 160
characters and `watchOutFor` at 200, but `/api/know-me` generates those
strings with no cap at all. One long sentence from the model made the context
stored on the visitor's device permanently unparseable, so `/api/jobs`,
`/api/fit`, `/api/cover-letter`, `/api/interview`, `/api/move`, and
`/api/linkedin` answered 500 on every later request from that browser - the
only escape was clearing the workspace. Inbound career context is now
**clamped, never rejected**: over-long strings are truncated (limits raised to
2000/400), surplus and non-string list entries are dropped, and a malformed
context degrades to "no context shared" instead of failing the request.
New `parseCareerContext()` covers the multipart routes; `careerContextField`
covers the JSON ones.
- tests/ now holds the access-code suite as well - it lived in test/, which
`npm test` never globbed, so it had not been running in CI.
0.22.0 - 2026-08-28
Added
- UI-less self-healing capture + anonymized ADO auto-file (AB#2593) — always-on,
no-database incident pipeline. Uncaught server errors (`instrumentation.ts`
`onRequestError`), handled 5xx responses on every API route (`reportHandled`), browser
errors/unhandled rejections, React render errors (`app/error.tsx`), and 5xx answers to
same-origin `/api/*` calls (fetch hook, 5 reports per page load) are scrubbed at write
(emails, phones, SSNs, JWTs, hex, UUIDs, file paths), deduped by signature into
**private** Vercel Blobs at `incidents/<signature>.json`, and filed daily as one
anonymized ADO Bug per new signature (`/api/cron/incident-ado-filing`, `CRON_SECRET`-gated,
Entra-first via Vercel OIDC with PAT fallback, clean no-op when unconfigured). New
unauthenticated intake `POST /api/incident-report` (honeypot, per-IP cap, always 204,
`GET` → 405) is exempt from the access gate. 4xx/429 never record; nothing identifying
is ever stored. See `docs/self-healing.md`.
0.21.0 - 2026-08-28
Added
- Community access gate (AB#2468) — optional, anonymity-preserving gating so the
tool can be reserved for people navigating a tech layoff, reached through the
communities supporting them. Partners hand out **stateless HMAC access codes**
(`lib/access.ts`); a valid code unlocks the app and the middleware enforces it on
the AI routes. No account, no name, no database — the code carries no identity and
nothing about the person is stored. Ships **off**: it activates only when
`ACCESS_CODE_SECRET` (server) and `NEXT_PUBLIC_ACCESS_GATE=1` (client) are set. New
`POST /api/unlock`, `middleware.ts`, `components/AccessGate.tsx`, and
`scripts/gen-access-codes.mjs`. See `docs/access-codes.md`.
0.20.1 - 2026-08-28
Added
- ab-link CI check (AB#2495): every PR must carry a well-formed AB#nnn
work-item reference; near-misses fail with an explanation; auto-close
keywords rejected; bots exempt. Rollout of AB#823 from KR.
0.20.0 - 2026-08-28
Added
- **Misconfigured primary AI key now surfaces instead of masking as "busy"
(AB#2422).** A 401 on the configured direct Anthropic key records a distinct
`AI_PROVIDER_MISCONFIGURED` error (once per instance) while the fallback
chain keeps serving users; a successful direct call heals the state.
- /api/health: shallow liveness (version, key configured, observed key
state — no AI call); `?deep=1` with the admin bearer key runs a minimal
live ping so an invalid key is caught by monitoring, not by a user.
Capacity throttling still degrades to the friendly busy message; auth
failures are never classified as busy.
- npm test script (node --test) with the AB#2422 suite.
0.19.0 - 2026-08-27
Added
- Tailor my resume to this job (AB#2464): the "is this a fit?" check now has a
button that rebuilds your whole resume tuned to that specific role — leading with
what matches, mirroring the posting's wording, weaving in the tailored bullets and
screening keywords where your real experience supports them, and addressing the
gaps honestly. Nothing invented.
- Shore up a gap with your own details (AB#2464): each "Worth shoring up" item in
the resume review now takes a detail you can add ("actually, I did a QuickBooks
course last year") — and the resume builder folds it in as a real fact when you
rebuild.
0.18.0 - 2026-08-27
Added
- Target your regions (AB#2441): job matching now has a region picker (US, UK,
Canada, Ireland, Europe, Australia, NZ, remote-anywhere). Pick where you want to
work and on-site roles outside those regions are scored well down — remote roles
still count. Fixes results skewing heavily European for people elsewhere.
- Minimum match filter (AB#2441): a slider hides matches below a chosen % — no
more scrolling past 18%-fit roles.
- Hide roles you've already applied to (AB#2441): a toggle drops anything already
in your tracker (applied or rejected) from the matches.
- "Application not progressing" (AB#2441): on a job you've applied to, one button
records that the employer has gone quiet. It sets a "Not progressing" status in your
tracker (also a new status option there) and feeds an anonymous learning signal.
- All three search/filter preferences persist on this device.
0.17.0 - 2026-08-27
Added
- One-click "Correct these for me" (AB#2438): the robot-proofing (ATS) section
and the line-by-line upgrades section of the resume review each get a button that
rebuilds your resume with those fixes applied — plus the age-bias repairs and
stronger wording — in a single click, nothing invented.
- Re-check your corrected resume (AB#2438): after a rebuild, one button re-runs
the honest review on the corrected resume so your robot-proof and readiness scores
update with every fix factored in.
- Edit the recruiter-ready resume inline (AB#2439): an Edit toggle turns the
produced recruiter resume into a full editable form — every field, including each
role's bullets (with add/remove role) and the recruiter note — so you can adjust
the wording before downloading the PDF, copying, or printing.
0.16.1 - 2026-08-27
Added
- Download the recruiter-ready resume as a PDF (AB#2431): a "⬇ Download PDF"
button on the recruiter resume produces a clean, single-column PDF file you can
email or attach to a LinkedIn message — a real file, not a browser print. Built
server-side with `pdf-lib` (`POST /api/recruiter-pdf`). Copy-as-text and Print
remain.
0.16.0 - 2026-08-27
Added
- Recruiter-ready resume (AB#2424): a new resume variant written for a human
recruitment consultant — the person who decides who to put in front of their
clients. It leads with a pitch-ready positioning headline and summary a recruiter
can lift verbatim, states target roles and the level to market at, frames
experience as sellable outcomes, and lists selling points. An optional details
form captures the logistics a recruiter needs — availability, location, work
preference, work authorization, and (only if you enter it) salary expectation —
and nothing is invented: blanks stay blank. It also generates a short intro note
you can paste into an email to a recruiter. Reachable from the analysis results
("🤝 Recruiter-ready version") and the More menu. New `POST /api/recruiter-resume`.
0.15.1 - 2026-08-24
Changed
- AI cost cut ~60% with two-tier model routing: judgment-heavy work (resume analysis, resume builder, fit check, LinkedIn review) stays on Claude Sonnet 5; ranking, action kits, interview prep, cover letters, photo review, and the career compass now lead with Claude Haiku 4.5 (~3× cheaper, same fallback chain behind it). Every route also gained an output-token cap sized to its schema, bounding worst-case spend per request.
0.15.0 - 2026-08-24
Added
- LinkedIn review refine loop: an "Answer the fixes" panel under the results takes the information only you have (certifications, volunteer work, numbers, target roles) and regenerates the whole review — headline options, About rewrite, skills, and checklist rebuilt around your facts, treated as truth with no further invention. One page, start to finished profile.
0.14.2 - 2026-08-24
Fixed
- LinkedIn review skills advice now respects LinkedIn's 5-skill top-skills limit: the featured list recommends up to 5 (was a fixed "pin three"), additions are capped at 5 with guidance to swap out removals first when the list is full, and the limit is stated on the page.
0.14.1 - 2026-08-24
Changed
- Every AI wait now shows a real-time progress view: a live elapsed clock, a continuously advancing progress bar tuned to each feature's typical duration, rotating stage messages, and honest copy — including a "still working, complex ones take longer" note when a run exceeds double the typical time. Replaces the static (and sometimes misleading) "about 30–60 seconds" text across resume analysis, job matching, fit check, LinkedIn review, resume builder, photo check, action kits, and interview prep.
0.14.0 - 2026-08-24
Added
- Profile photo check (/photo + /api/photo): upload your profile picture for an honest AI review of the photograph — lighting, background, framing, expression, attire, and image quality, each rated with a fix — plus what to keep, ordered top fixes, a phone-only retake guide, and a two-second first impression. The review judges the photo, never the person, and never suggests hiding your age.
- Enhance studio on the same page, entirely in-browser (the photo never leaves the device for enhancement): drag-to-reposition crop with zoom, one-click auto-enhance (measured levels), brightness/contrast/color/warmth sliders, circle preview, and an 800×800 JPEG download ready for LinkedIn. The AI review pre-nudges the sliders for your specific photo.
0.13.0 - 2026-08-24
Added
- Native ESD weekly job-search log at /esd, fully part of the app: week editor with all three activity types (employer contact, WorkSource, other), search, CSV backup/import, and official EMS 10313 PDF export via pdf-lib — same browser storage as before, so existing logs and tracker-bridged applications carry over. Replaces the embedded standalone page.
Changed
- Header navigation redesigned: three primary destinations (Resume, Jobs, Fit check) plus an accessible "More" menu for Get to know me, LinkedIn review, My applications, My workspace, and What's new.
Fixed
- LinkedIn PDF reviews no longer die with a raw JSON parse error: heavy AI routes now allow up to 300 seconds (the review was hitting the 120-second function timeout), and every page reads API responses defensively, turning platform timeout/size errors into friendly messages.
0.12.0 - 2026-08-24
Added
- Application tracker (/applications): "I applied for this job" buttons on job matches and in the application kit log each application to a device-only tracker — grouped by week, with status (applied / heard back / interviewing / offer / closed), notes, links, and manual logging for jobs found elsewhere.
- ESD weekly job-search log integration (Washington State): the full ESD Job-Search Log app (official EMS 10313 PDF export, CSV backup, search) is embedded at /esd/ and gated behind a tracker setting — "I claim unemployment benefits in Washington State". Tracked applications can be added straight into the correct ESD week as employer contacts (weeks end Saturday, 3 activities per week), sharing this browser's storage with the embedded app.
0.11.0 - 2026-08-24
Added
- LinkedIn profile review (/linkedin + /api/linkedin): upload LinkedIn's own "Save to PDF" export or paste profile text for a recruiter's-eye audit — recruiter-ready score, the five-second first impression, ready-to-paste headline options and a full About rewrite, skills to pin/add/remove (the exact terms recruiters filter on), experience-section fixes, an age-signal scan, visibility settings and habits, photo/banner checklist, and an ordered do-this-today list. Tailored by age group, career compass, and resume analysis when present. Nothing stored.
0.10.0 - 2026-08-24
Added
- Feedback button on every page (floating, bottom-right): anonymous by default with an optional mood and reply email, saved to a private Vercel Blob store (stillcanwork-feedback) — nothing about the visitor is recorded beyond what they type. Includes a bot honeypot and rate limiting. Admins can read everything via GET /api/feedback with the FEEDBACK_ADMIN_KEY bearer token or browse the blob store in the Vercel dashboard.
0.9.0 - 2026-08-24
Added
- Device-only workspace persistence: resume review, original resume/text for the builder, age group, and career context now survive closing the tab and can be resumed on the same browser at /workspace. A clear-this-device control removes every saved item.
- "Get to know me" career compass at /me: an AI-guided profile of goals, preferred work shape, availability, priorities, constraints, and the visitor's own story. Its compact context now tailors job matching, Fit Check, application kits, interview prep, and strategy action kits beyond the resume alone.
- Anonymous campaign-link builder at /share. Use a non-personal scw_campaign label to see aggregated visits in Vercel Analytics, alongside anonymous completion events for key product flows. No resume, job-description, profile, account, or identifying data is sent as a custom analytics property.
0.8.0 - 2026-08-23
Added
- Vercel Web Analytics (@vercel/analytics) for privacy-friendly, cookie-free visitor metrics.
- Gitleaks secret scanning in CI (.github/workflows/gitleaks.yml) on every PR and push to main, matching the other ChAImpOrg repos.
- Platform-level WAF rate limit: 20 requests/minute per IP on /api/* (deny beyond), backing up the in-app limiter.
0.7.1 - 2026-08-23
Fixed
- Resume Builder no longer guesses years for relative dates in user notes ("this month", "recently") — the request now carries today's date and the prompt forbids attaching any date not explicitly stated or resolvable from it.
0.7.0 - 2026-08-23
Added
- Resume Builder (/rebuild + /api/rebuild + /api/resume-docx): one click rewrites your resume applying every fix from the review — age-bias repairs, robot-proofing, stronger bullets — plus your own notes (new certificates, corrections), with a strict no-invention rule. Outputs an on-screen preview, a plain-language change log, and a downloadable, editable Word (.docx) file in a clean ATS-safe layout. The original resume now stays in your browser session (never on our servers) so the builder can use it.
0.6.0 - 2026-08-23
Added
- "Beat the bots" ATS layer: Fit Check now extracts the posting's exact screening keywords (title variants, tools, certifications), marks which ones the resume already evidences, and says where to honestly add the rest — with explicit anti-deception guidance (no hidden text, no unearned claims). Resume analysis now displays its robot-proofing fixes (previously computed but not shown).
0.5.0 - 2026-08-23
Added
- Fit Check (/fit + /api/fit): paste or upload a job description (PDF/Word/text) and get an honest verdict against your analyzed resume — fit score, strong/good/stretch/poor verdict, matched requirements with evidence, gaps with severity and mitigations, talking points, resume bullets tailored to the posting's language, and red-flag scan of the posting itself (age-coded language, scam markers). Linked from the header.
- "Let's do this" action kits on every guide's Four Moves (/api/move): one click turns a strategy into a personal step-by-step plan — a next-15-minutes action, ordered steps, copy-paste message templates, and a measurable finish line. Personalized from the resume analysis when available.
0.4.0 - 2026-08-23
Added
- Direct Anthropic API as the primary AI path (@ai-sdk/anthropic, ANTHROPIC_API_KEY): Claude Sonnet 5 first, Claude Haiku 4.5 second, with the AI Gateway chain retained as fallback. Bypasses gateway free-tier restrictions entirely.
- Version number in the footer linking to a new "What's new" page at /changelog, rendered from this changelog.
- Per-IP rate limiting on all AI routes (lib/ratelimit.ts): sliding window of 15 AI requests per IP per 10 minutes plus a per-instance global cap of 120/minute, returning friendly 429s. Instance-local by design; platform WAF remains the escalation path.
0.3.0 - 2026-08-23
Added
- Logo: a sunrise-over-horizon mark ("a new chapter, still rising") in the header, plus a matching SVG favicon (app/icon.svg).
Changed
- Display font switched from Fraunces (serif) to Plus Jakarta Sans — a modern geometric sans; body remains Inter.
0.2.0 - 2026-08-23
Added
- New age group: In my 40s ("Prime years, full throttle") — full game plan with myths vs facts, four strategies (trajectory positioning, parallel conversations, network activation, pivot playbook), resume rules, curated boards, and red flags. Available at /guide/forties and in the landing-page picker.
0.1.1 - 2026-08-23
Fixed
- AI calls now run through a model fallback chain (lib/ai.ts): the Vercel AI Gateway free tier restricts premium models (403) and rate-limits others (429), and some free models ignore output schemas. The real request is attempted against each model in order (Claude Sonnet 5 first, then Haiku, Gemini Flash, DeepSeek, GLM, GPT-OSS, Llama), skipping restricted/throttled/schema-failing models, remembering the last one that worked. SCW_MODEL env var forces a specific model. When paid gateway credits are added, the app automatically uses Claude again.
- Cover letter generation switched from streaming to a single response so it can use the same fallback path.
0.1.0 - 2026-08-23
Added
- Initial release of Still Can Work — an AI career coach for semi-retired, older, and returning workers.
- Age-group selection (50s, 60s, 70+, returning after a break) with a tailored game plan per group: myth-vs-fact, four strategies, resume rules, curated age-friendly job boards, and scam/red-flag warnings.
- Resume analysis (/api/analyze): accepts PDF, Word (.docx), plain text, or pasted text; returns strongest skills with market demand, suggested roles with fit scores, a send-it-today readiness score, an ATS ("robot-proof") score, an age-bias exposure scan with fixes, line-by-line rewrite suggestions, and a 30-second elevator pitch. Powered by Claude via Vercel AI Gateway.
- Live job matching (/api/jobs): searches Remotive and Arbeitnow (free, keyless APIs), dedupes, and AI-ranks every listing against the candidate's actual skills with an honest match score, a one-sentence "why it fits", and flexibility signals. Plus one-click keyword deep links to Indeed, LinkedIn, AARP, FlexJobs, and USAJOBS.
- Application kit per job: a streaming tailored cover letter (/api/cover-letter) and an interview prep kit (/api/interview) including confident answers to age-adjacent questions, talking points, questions to ask, and negotiation tips.
- Privacy by design: no accounts, no database, nothing stored server-side; analysis state lives only in the visitor's browser session.
- Accessible, high-contrast, large-type design (Fraunces + Inter) with keyboard-visible focus and a print-friendly report view.