Skip to content
Tomer Naydnov

§01/Applytide

Product · Platform · Applied AI

Applytide

The browser had become the workaround for a process that needed its own operating system.

A job-application management platform: pipeline, documents, reminders, analytics and interview prep.

Role
Product · design · full-stack engineering
Span
Aug 2025 — Apr 2026
Status
Source archived
Team
Solo project · sole creator

The public repository is archived. No live deployment or external usage is claimed.

§02/The 60-second version

The 60-second version

01 / What was broken
Job discovery, tailored documents, follow-ups and interview context were spread across tabs, files, inboxes and memory.
02 / The move
Make capture almost invisible, then give every application one structured history from discovery to decision.
03 / My contribution
I independently defined, designed and built Applytide end to end: browser capture, the React interface, FastAPI services, data modelling, document tooling, infrastructure and the bounded AI layer.
04 / What exists as proof
An archived public repository with a substantial dated history and inspectable browser, frontend, backend and infrastructure code.

§03/The full story

Open what matters

The story

The summary above stands on its own. Open the decision trail below, one chapter at a time.

01/SignalThe tracking is the job

The tracking is the job

Anyone running a serious job search is operating a pipeline: sourcing, qualification, application, follow-up, interview, decision. It has stages, conversion rates, and a cycle time. It is a process, and people run it in a spreadsheet or in their head.

The friction is not finding jobs. It is the accounting around them — which version of the CV went where, what you said in the cover letter, when you promised to follow up, and which applications are actually still alive. That work is boring, high-frequency, and exactly what software is for.

What I got wrong initially: I assumed the painful step was writing applications. It was not. The painful step was capture — getting a posting out of a browser tab and into a structured record before the tab closed. Everything downstream depends on that one moment being frictionless.

02/FrameConstraints that actually bound the design

Constraints that actually bound the design

Two real constraints shaped almost every decision. The first was cost: any feature calling a language model has an unbounded bill attached to it, and a personal project cannot absorb that. The second was trust: this system holds a user's CV, their employment history, and a list of every company they are quietly talking to. That is a small dataset with a very high blast radius.

So security and cost control were not features to add later — they were framing constraints. That is why the build contains things a side project usually skips: HttpOnly cookie sessions rather than tokens in localStorage, TOTP two-factor, revocable session tracking, per-endpoint rate limits, and a hard daily spending cap on AI calls enforced in Redis.

Explicitly out of scope: automated applying. I excluded it because mass-applying optimises application volume while making the surrounding search process noisier.

03/PlanCapture first, because everything hangs off it

Capture first, because everything hangs off it

Since capture was the load-bearing step, the browser extension was built early rather than as an add-on. Getting a posting into the system in one click is the difference between a tool someone uses and a tool someone means to use.

The rest sequenced along the pipeline itself: capture, then the Kanban stages that model the process, then documents, then the reminder and analytics layers that only become meaningful once there is history to reason about. Analytics last is deliberate — a dashboard over three records is decoration.

The architecture was planned as separate services from the start where the boundaries were genuinely different: HTML email rendering is a Node concern, background scheduling is not an API concern, and the reverse proxy is where rate limiting belongs.

04/BuildEight Compose entries, counted by definition

Eight Compose entries, counted by definition

At the audited public commit, Docker Compose contains eight service entries, including development-only MailDev. The runtime topology also includes Nginx, a React SPA, a FastAPI backend, a Node email service, PostgreSQL, Redis and a scheduler process.

The backend is layered rather than flat — 15 include_router registrations at the API boundary, a domain layer holding business logic, 19 SQLAlchemy model class declarations under Alembic migrations, and infrastructure for email, LLM access, security and workers. Those are snapshot counts with recorded definitions, not product outcomes.

The hardest problem was extraction: turning an arbitrary job page into structured fields. The naive answer is to send the page to a model. That answer is wrong for reasons covered in the decision log below.

The second hardest was internationalisation. Full Hebrew support means real RTL — not a mirrored stylesheet, but a layout that is correct in both directions, including inputs, charts, and drag-and-drop. Retrofitting that is expensive, which is why it was designed in rather than bolted on.

05/ProveBudgets, health checks, and the things you cannot unit-test

Budgets, health checks, and the things you cannot unit-test

Some of this was conventional: Pydantic schemas validating every endpoint boundary, parameterised queries throughout, a health endpoint checking both Postgres and Redis.

The source logs LLM usage per call with token counts and account attribution against a configurable daily budget. That is a spend-bounding mechanism; without operating evidence, it is not a production-reliability claim.

Session management is testable in the same spirit: a user can list their active sessions and revoke them. The question that validates it is not “does the endpoint return 200”, it is “can a user who lost a laptop actually recover”.

06/FieldWhat the archive does — and does not — prove

What the archive does — and does not — prove

The public Applytide repository is archived. There is no live deployment or external-usage result presented here.

I stopped when continuing would have meant operating infrastructure, funding model usage and maintaining a security surface — a different commitment from proving the product and engineering decisions.

What the source independently demonstrates is an implemented multi-service architecture, session and budget controls, bilingual UI and a documented set of design decisions. It does not by itself prove production security, reliability or adoption.

§04/The evidence underneath

Optional depth
System & architecture
8
Compose entries

At audited commit; includes MailDev

19
Data models

SQLAlchemy class declarations

15
API routers

include_router registrations

2
Locales

EN / HE, full RTL

Technology

  • React 18
  • TypeScript
  • FastAPI
  • PostgreSQL
  • Redis
  • Docker
  • Nginx
  • OpenAI API
  • Chrome MV3
Decisions & trade-offs
  1. D-01

    2025

    Three-stage extraction cascade (JSON-LD → DOM → LLM) instead of LLM-only parsing.

    Why
    Some job boards publish JobPosting structured data that can be extracted without a model call. When it is present and well formed, it preserves source fields directly; this project did not benchmark its coverage or accuracy across boards. A model remains the least predictable path for fields such as salary, where a confident error is costly.
    Tradeoff
    Three code paths and three distinct failure modes to maintain instead of one, in exchange for avoiding model cost and extra transformation when structured source data is available. Coverage and precision were not benchmarked.
    Revisit if
    If model cost per extraction falls far enough that the accounting stops mattering, or if structured-data coverage across boards collapses.
  2. D-02

    2025

    Hard daily LLM budget enforced in Redis, with per-user attribution.

    Why
    Any AI feature exposed to users is an uncapped liability. Logging token usage after the fact tells you what went wrong; a budget stops it.
    Tradeoff
    Users can hit a ceiling and see a degraded experience on a bad day, which is a worse product moment than silently paying. I preferred a bounded bill.
    Revisit if
    If usage patterns become predictable enough to move from a hard cap to per-user quotas.
  3. D-03

    2025

    JWT in HttpOnly cookies, not tokens in localStorage.

    Why
    The dataset is a user's employment history and a list of companies they are privately talking to. Any XSS in an SPA turns localStorage tokens into a full account takeover.
    Tradeoff
    CSRF becomes a concern that has to be handled explicitly, and cross-origin setups get harder. That is a better class of problem to own.
    Revisit if
    Not without a very good reason.
  4. D-04

    2025

    Excluded automated mass-applying from the product scope.

    Why
    It optimises the metric the product displays while making the user's actual outcome worse, and it degrades the ecosystem it operates in. A tool for running a search well should not ship a feature that runs it badly at scale.
    Tradeoff
    Gave up the single most obviously marketable capability and the growth story attached to it.
    Revisit if
    No.
  5. D-05

    2026-04

    Archive rather than operate.

    Why
    Remaining work was operational — infrastructure, absorbing others' AI costs, and maintaining a security surface that deserves real attention. That is a commitment to operate a product, which is a different decision from building one.
    Tradeoff
    No live deployment to show, and no usage metrics. The code, architecture and reasoning remain inspectable.
    Revisit if
    If there is a reason to operate it that justifies the running cost and the duty of care.
Constraint study

Constraint dial

How should an arbitrary job page become structured data?

Move the constraints and see which answer they point at. The decision was never about the best technology — it was about which one survives these two numbers.

Real
TightRealAmple
Balanced
NarrowBalancedBroad

What I actually built

The three-stage cascade — JSON-LD, then DOM parsing, then LLM fallback. This is the implemented path: it avoids a model call when usable structured data exists. Coverage and extraction accuracy were not benchmarked.

Real conditions: one-person operating capacity, ongoing cost exposure, and a correctness bar high enough that a wrong salary field is worse than no salary field. That combination points at the cascade.

If I rebuilt it today
  1. 01

    Build the evaluation harness for extraction first. I made routing decisions between the three stages on judgement when a representative labelled corpus would have made them measurable.

  2. 02

    Start with one locale and add Hebrew at a defined checkpoint. Designing bidirectional from day one taxed every component before any external usage was documented.

  3. 03

    Cut the admin panel. It was built because it was interesting, and it served one administrator — me. That effort belonged in capture reliability.