A Chrome side panel that scores Upwork jobs and drafts proposals, for freelancers in a paid membership community.
One source tree, two shipped builds: a 118 KB standalone zip that needs no account, and a hub build with sign-in, hub-paid credit and cross-machine sync.
stack

The brief
Freelancers on Upwork pay connects to bid, and the real cost of a bad bid is the half hour spent writing a proposal for a client who had already hired someone. UpworkPilot v1.0.0 already did the reading and the writing, but it ran on one machine: 27 files, an OpenAI key pasted into a settings box, everything in local browser storage, no version control.
The history shows the brief in practice. Fix what was wrong in the inherited build, then give the same extension a second life inside a paid membership, where a member signs in, nobody pastes a key, and a profile written on one laptop is on the next.
key decisions
01
Two builds from one file, and a preflight that fails the zip
One config file is the only place allowed to hold deployment values, and flipping a single constant in it produces the other build. The danger is that a mis-built standalone zip looks completely normal: it installs, opens and works, while pointing at infrastructure its user has no account for. So the build refuses to produce a zip until nine checks pass. One of them caught a real exported profile, with rates and experience text, sitting in a folder that would have shipped to every member.
02
The member's OpenAI key lives in worker memory, nowhere else
The hub releases a decrypted key on the strength of four guards, and the fourth is one the hub cannot enforce: never write it to browser storage. The consent screen a member reads is written from that sentence, so persisting the key for convenience would make the product's own copy untrue. Manifest V3 evicting the worker every 30 idle seconds is the design, not a bug, and it is why that endpoint's rate limit is 240 an hour.
03
Last write wins, with the cost of it written into the file
The records being synced are single-author documents, and the realistic conflict is a second browser profile left open for a week. Conflict-free replicated types would be a large amount of machinery for a case whose correct answer is nearly always to keep what was just typed. The four things that policy silently costs, including that a browser with a fast clock wins every race, are documented in the module that implements it, with the fix named for when they stop being acceptable.
how it went
24 August 2026
Baseline and audit
Imported the working v1.0.0 as it stood, 27 files and 5,652 lines, then fixed four defects before adding anything.
24 August 2026
Backend skeleton and schema
Moved the extension into its own workspace, added the Next.js API and the database migration.
25 to 26 August 2026
Hub sign-in and key release
PKCE sign-in, pinned extension id, licence check, per-session key fetch.
26 August 2026
Sync API and distribution
Five sync routes, the extension's merge policy, the standalone build pipeline and the deploy guide.
27 to 28 August 2026
Credit mode
Gateway routing when the hub pays, plus the end-to-end test against a real local hub.
results
37/37
plus eight end-to-end scenarios that need a local stack and were not run for this write-up
at 15 September 2026, measured by the test suite, run for this case study
4
defects fixed in the inherited build before any new feature
pasted screening questions split on a literal backslash-n and reached the model as one blob; the model picker only affected cover letters; analysis re-billed on every tab visit; scraped job and model text went into the page unescaped
first two days, measured by commit history

