Three months, 275 commits, one database. A personal operating system that swallows links, tasks and money into a single pipeline — and captures from wherever I already am: the browser, a share sheet, a photo of a receipt, my bank's email, a Telegram message.
Money — Overview. Every figure on this screen is derived, not typed. Net worth reads from account balances plus valued crypto holdings; "on this pace" projects the month from elapsed days; "safe to spend" is expected income minus commitments minus what's already gone, divided by the days that remain. Mockup data.
Counted on 9 Aug 2026 from git rev-list, vitest run (69 files, 0 failures) and the InstantDB schema. 186 of those commits are features, 46 are fixes. The rest is plumbing.
I run a voice-AI and staffing company out of Costa Rica, plus a personal brand on the side. By the start of this year I had three dead systems.
A link graveyard: 1,400-odd saves scattered across Instagram bookmarks, browser tabs, a Notion database I'd stopped feeding, and a Telegram "saved messages" chat. Nothing was tagged. Nothing was findable. I was re-searching for the same competitor ad three times a month.
A task app I never opened, because opening it meant a context switch away from whatever produced the task in the first place.
And money I couldn't see. Two currencies, five accounts, a crypto wallet, business and personal bleeding into each other. Every budgeting app assumes you live in a country Plaid covers. Costa Rica isn't one. So the honest options were: type every transaction by hand, or fly blind.
Different symptoms, same disease. Capture was expensive and nothing was connected. Saving a thing cost more attention than the thing was worth, so I stopped saving. And once the pieces lived in five products, no single one of them could ever answer a question that spanned two.
The design brief fit in a sentence: make saving free, and put everything in the same place.
Free meaning zero friction — I should never have to open the app to feed it. Same place meaning literally one database, not three products with an integration bolted between them. Links, tasks and transactions are separate pillars in the UI, but they share a schema, a tag vocabulary, a project taxonomy, and one AI worker that classifies all three.
The app switches pillars from a single control in the sidebar. Everything else — search, tags, projects, capture, settings — carries across.
Save a URL from anywhere. It gets scraped, enriched, summarised, tagged and filed without a form.
Type a sentence, get a dated task. Today, Week, Upcoming, Projects. Swipe to complete.
Statements, receipt photos, bank emails and manual entries — all through one pipeline.
Links — Library. Covers, durations, engagement counts, creator handles and content type all arrive from the enrichment pass. Amber chips are what the AI proposed but I haven't accepted yet — suggestions stay visibly separate from tags I chose. Mockup data.
A save is a URL and nothing else. Everything after that is the machine's job.
The pipeline resolves the source (Instagram, X, YouTube, LinkedIn, TikTok, generic web), then tries the cheapest extraction that could work and escalates only if it fails: oEmbed, then the platform's own API, then a rendered-HTML pass, then a paid scraper. Each field remembers which source produced it, so when a title comes back wrong I can see whether it came from OpenGraph or from a scraper, and fix the right layer.
What lands: title, creator and handle, original post date, cover image, full caption or article body, duration, engagement count, and a two-line summary. Then a classifier assigns project, category, subcategory and tags — scoped to the project, so the same tag carries a different meaning under Interstaff than under my personal brand.
Chrome extension overlay (⌘⇧S), iOS share sheet, Telegram message, or paste into the app. Offline saves queue and drain on reconnect.
The extension can pull every outbound link on the page you're reading and save the set — useful for a roundup post or a competitor's resources page.
Follow a creator and new posts arrive in the library on their own, skipping the inbox so they never bury the triage queue.
Highlights, a rich-text note editor, and file uploads — photos, video, PDF, markdown — attached to the save itself.
Left: the browser extension. A centred overlay on the page you're already reading, with the project pre-picked and tags pre-proposed. Right: the Telegram bot — the same database, reachable from a chat window. Photograph a receipt and it comes back parsed into line items. Two scheduled runs a day surface anything worth interrupting me for. Mockup data.
Task capture is a single text field. "call the dentist tomorrow 3pm" becomes a task titled Call the dentist, dated tomorrow at 15:00. "review the Q3 ad spend friday #interstaff" picks up the project and the tag. If parsing is ambiguous the raw text survives as the title, which is always better than losing it.
Then the ordinary things, done properly: Today, Upcoming, Week, Projects, Completed. Sub-task checklists. Recurrence. Swipe right on a phone to complete. Bulk edit from a selection bar. Reminders through native notifications on iOS and push on the web.
The part that only works because of the shared database: a task can point at a link. The competitor ad sits attached to the task about responding to it, and the task shows up in the project's view next to the money spent on that project.
The phone build. Same code as the web app, installed as a PWA or shipped through Xcode with Capacitor — including a share extension and a home-screen scan widget. Transfers render grey and sign-neutral so an internal move between my own accounts never reads as spending. Mockup data.
This pillar took more work than the other two combined, for one reason: there is no bank API. Every consumer finance product in the US starts by connecting an account and receiving a clean transaction feed. I had to manufacture that feed from whatever my banks would give me.
So there are five doors in, and all five open onto the same corridor:
The corridor is the important part.
Every source produces the same draft shape and hands off to one pipeline. That pipeline dedupes bidirectionally and sign-aware, so re-importing a statement I already photographed doesn't create twins — and so a deduper never mistakes a transfer's opposite leg for a duplicate of itself. It pairs internal transfers by matching amount, direction and a tight time window, then marks both legs so they drop out of spending entirely. It matches charges against known subscriptions and debts by merchant alias, name or schedule. And it queues everything for the AI tagger.
I learned this the expensive way. Email import shipped first as its own parallel path — its own writer, its own dedupe, its own pairing logic. It produced duplicate transactions and transfers that matched across different days. I tore it out and rerouted it through the existing machine, then wrote the rule into CLAUDE.md so neither I nor the agent would do it again: any money feature routes through the existing transaction pipeline; never build a parallel path.
Money — Review. The queue is the whole trick. Automation that silently guesses wrong is worse than no automation, so anything below confidence lands here with both candidates side by side and a one-click resolution. The badge in the sidebar is the only nag. Mockup data.
The first version, in April, was a Tauri desktop app: Rust core, SQLite with full-text search, macOS Keychain for secrets, Notion as the source of truth. Fourteen commits, 107 Rust tests, and a working binary.
I killed it in June.
Not because it was bad — it was genuinely nice — but because it was wrong about my life. I save links from my phone more than my desk. A local SQLite file on a MacBook can't be the source of truth for someone whose capture surface is a phone in a supermarket queue. So the whole thing moved to a React PWA on InstantDB, deployed on Vercel, syncing everywhere in real time. Costa Rican internet made the offline-first sync model a requirement rather than a nicety.
That pivot cost about a week and bought back the entire product.
The stack, and why each piece is there| Layer | Choice | Reason |
|---|---|---|
| Data | InstantDB | Real-time sync, offline writes, one schema for all three pillars. Cheaper than Supabase at single-user scale. |
| App | React 19 + TS strict + Vite | One codebase renders the web app, the PWA and the iOS build. |
| Style | Tailwind v4 + CSS tokens | Every surface reads from variables, so light and dark are one palette swap. |
| Server | 12 Vercel functions | Enrichment, AI tagging, vision, email import, Telegram webhook, two daily crons. Twelve is the Hobby-plan ceiling, so it's a real constraint. |
| AI | OpenRouter | One key, swappable models. Tagging, summarising and receipt vision all route through it. |
| Capture | WXT (MV3) + Capacitor | Chrome extension and native iOS from the same TypeScript. |
| Chat | Telegram Bot API | 34 commands. The assistant surface I already had open all day. |
I built it alongside an AI coding agent working from a written brief in the repo. The brief holds the schema, the taxonomy, the coding standards, the division of labour, and the hard rules — including the pipeline rule above. Every task ends with a typecheck, a test run and a conventional commit. That discipline is why 619 tests exist for a personal app: the agent writes them by default, and they're what let me refactor the money pipeline twice without losing a weekend.
₡1,000 rendered as $1,000. The currency converter had a fallthrough where an unknown pair returned the amount untouched. On a screen mixing colones and dollars, that quietly displayed a ₡1,000 charge — about two dollars — as a thousand-dollar one, and my net worth was nonsense for a day. The fix was a rule the code now enforces: a cross-currency pair may never render 1:1. If there's no rate, fall back to a table; never pass the number through.
Transfers pairing across days. Bank emails carry a send time and a posting time, and I was matching on the wrong one. A colón transfer sent Friday night and posted Monday matched against an unrelated Monday deposit. Pairing now uses real send-time inside a tight window.
A build that passed locally and broke in production. Vercel type-checks the serverless functions with a stricter config than the app's, and the failure was silent. Every function now carries a directive on line one, and I verify production after every merge rather than trusting the deploy hook.
None of these are interesting engineering. All three are the kind of thing that only surfaces when you're the user, which is the actual argument for building your own tools.
| Before | Now |
|---|---|
| Saving a link meant a browser tab, a Notion form and four fields I'd skip. | ⌘⇧S. Title, creator, summary, cover, category and tags arrive on their own. |
| 1,400 saves, untagged and unsearchable. | One search box over all three pillars, plus a tag hub that spans links, tasks and transactions. |
| Money visible once a month, if I sat down for an hour with a spreadsheet. | Net worth, burn rate and a 90-day outlook, current to the last charge — reachable from a Telegram message. |
| Subscriptions I'd forgotten I was paying for. | The pipeline flags any charge that repeats three months without being registered. |
| Reconciling two currencies by hand. | Every account holds its native currency; the app converts for display and refuses to fake a rate it doesn't have. |
| A task app I didn't open. | Tasks captured from the same box that captures everything else, and attached to the links and projects they came from. |
The benefit I didn't design for, and now care about most: questions that span two pillars are answerable. How much did I spend on the tools I saved from that thread? What's still open on the project that's burning the most? Those were unanswerable when the data lived in five products, not because any product was weak, but because none of them could see the others.
Nothing here is technically novel. Bookmark managers, task apps and budgeting tools have existed for twenty years, and the companies that make them employ people better at this than I am.
What changed is the cost of the bundle.
| What it replaces | Typical cost | Why it couldn't serve me |
|---|---|---|
| Bookmark manager | ~$3/mo | No AI enrichment for social posts; no idea what a project is. |
| Task manager | ~$5/mo | Can't attach a task to the artefact that produced it. |
| Budgeting app | ~$15/mo | Requires a bank connection that doesn't exist in my country. |
| Read-later + highlights | ~$8/mo | Yet another silo with its own tags. |
| Automation glue | ~$20/mo | Held the integrations together and broke whenever an API moved. |
| Link-o-Mator | ~$5/mo | Hosting, plus metered AI and scraping. One schema, no glue. |
Roughly $50 a month of subscriptions, none of which talked to each other, replaced by infrastructure I control — in three months of evenings, by one operator with an AI agent.
That's the disruption, and it isn't about me. Vertical SaaS has spent a decade selling generalised solutions to specialised problems, and charging for the generalisation. The Costa Rica gap is the clearest example: no budgeting company will ever build a BAC-email parser, because the market is too small. It took me an afternoon, because my market is one.
When bespoke becomes cheap, the products most exposed are the ones whose value is configuration — a form, a taxonomy, a set of rules a competent operator could specify in a paragraph. What survives is what's genuinely hard to reproduce: real network effects, real regulatory moats, real data nobody else has. A monthly fee for a nicely-shaped CRUD app around a schema is not that.
The second-order effect is stranger. Because I own the schema, every new idea is additive. Adding subscription detection didn't require a vendor to prioritise it, and it inherited every tag, project and account already in the system. Compounding like that is what a stack of SaaS subscriptions structurally cannot do, no matter how good each one is.
It isn't a product for sale, and it may never be. It's the thing I use every day, which is a higher bar than most software I've paid for clears.
All screens above are faithful rebuilds of the shipped interface, populated with mockup data — no real financial figures appear anywhere on this page. Figures in the ledger are counted from the repository on 9 August 2026.
Link-o-Mator · 275 commits · 619 tests · Built in San José, Costa Rica