Skip to content
  • about
  • contact

A studio that ships software and shows its work.

hello@caparisonlab.com, Chittagong, Bangladesh

2026 Caparison Lab

  • work
  • capabilities
  • about
  • contact
  • RSS
work
web application

UpworkPilot

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.

Audit of an inherited buildSecurity fixesBackendSchemaHub authSync APICredit routingTest suitePackaging and deploy docsChrome extensionWeb API
year
2026
team
1

stack

next.jstypescriptpostgressupabasejavascriptchrome extensionopenaiopenainext.jstypescriptpostgressupabasejavascriptchrome extensionopenaiopenainext.jstypescriptpostgressupabasejavascriptchrome extensionopenaiopenainext.jstypescriptpostgressupabasejavascriptchrome extensionopenaiopenai

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.

What we built

One source tree produces two artifacts. The extension is the product: vanilla ES modules that Chrome loads with no build step, a six-tab side panel, a content script that reads Upwork's page, and three model calls. The hub half adds a Next.js API on Vercel with no pages and no login, which holds the service role key and answers only requests carrying an RS256 licence token the hub signed.

  • Radar tab that scans a whole Upwork feed page in one model call and shortlists the cards worth opening, with a one-line reason each.

  • Job score out of 100 with red and green flags, over a deterministic skip banner for jobs already hired, interviewing, or past 50 proposals.

  • Cover letter written first person from the member's saved profile, streamed into the panel, and filled back into Upwork's own proposal boxes. Never submitted.

  • Profiles holding skills, experience, rates, portfolio links, past winning proposals and per-profile prompt instructions, importable and exportable as JSON.

  • Sign-in against the hub over PKCE, a 24-hour RS256 licence token, and the member's OpenAI key released into service-worker memory only.

  • Credit mode that sends the call through the hub's gateway on the member's session, with no provider key, when the hub is the one paying.

  • Sync of profiles, settings, saved jobs, proposals and analyses across machines, last write wins on the update time, into seven tables in their own schema.

  • Standalone zip built by a staging script and audited by a nine-check preflight that will not emit a zip carrying a hub URL, a manifest key, or anyone's personal data.

Five days of commits, 24 to 28 August 2026, one author in the git history.

key decisions

  1. 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.

  2. 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.

  3. 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

  1. 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.

  2. 24 August 2026

    Backend skeleton and schema

    Moved the extension into its own workspace, added the Next.js API and the database migration.

  3. 25 to 26 August 2026

    Hub sign-in and key release

    PKCE sign-in, pinned extension id, licence check, per-session key fetch.

  4. 26 August 2026

    Sync API and distribution

    Five sync routes, the extension's merge policy, the standalone build pipeline and the deploy guide.

  5. 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

extension unit tests passing

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

Want something like this?

Taking over someone else's build starts with finding what it already gets wrong. Tell us what exists today and who depends on it.

Build something like this

next

Cinematic Workflow