Why We Built Our Own Console to Run a Two-Person Dev Studio

For the first year of running Co Digit as a two-partner studio, our entire back office lived in a single Google Sheet. One tab for clients, one for who had paid what, a third for the running profit split between the two of us. It was free, it was instant, and it worked — right up until the afternoon we both opened it, looked at the same row, and read two different numbers out loud.

Nobody had done anything wrong. One of us had edited a cell while the other was mid-calculation, a formula had quietly stopped covering a client added two weeks earlier, and a payment had been marked "received" by the partner who sent it rather than the one who got it. None of these are spreadsheet bugs. They are what a spreadsheet does when you ask it to be the system of record for money shared between two people. That afternoon we decided to build the thing the sheet had been pretending to be. We called it Bridge.

The Three Things a Spreadsheet Could Not Do

Once we wrote down what we actually needed, three problems separated themselves from the rest, and none of them were really about formulas.

The first was money. We bill each client, take a development fee off the top, and split the remaining profit on a per-client ratio — sometimes 5:5, sometimes 4:6, occasionally something custom. A spreadsheet can compute that all day. What it cannot do is stop one partner from marking a payment settled before the other has actually watched the cash land in an account.

The second was time. Every client lives in a different calendar — one on Google Workspace, one on Outlook, two more on whatever their project tool exports. We were opening four calendars to answer the single question "what is due this week," and inevitably missing the one we forgot to check.

The third was the daily standup. With two developers and a rotating contractor, we wanted one place to post what we did, what was blocking us, and what was next — without it dissolving into a Slack thread that scrolls into the void by Thursday afternoon.

Why We Didn't Just Buy Something

The obvious objection is that all three problems are solved products. There are agency-management platforms, there are billing tools, there are standup bots. We tried several of each.

The issue was never capability. It was that every one of them is priced and designed for a team of fifteen, not a partnership of two. We would have paid three monthly subscriptions, learned three different data models, and then glued them together by hand anyway, because none of them knew the other two existed. For a studio our size, the integration tax was higher than the cost of building.

There was also a quieter reason. The numbers in question are our own income. Handing the single source of truth for how two people divide their money to a third-party service — with its own export limits, its own outages, and its own private definition of "deleted" — felt like exactly the wrong corner to cut to save a weekend.

The Decision That Shaped Everything: Trust the Database, Not the UI

The first real architectural choice set the tone for the rest. When you build an internal tool, the lazy path is to enforce the rules in the interface — hide the buttons a user should not press, and trust that nobody opens the network tab. For a public marketing page, fine. For a console holding revenue figures and per-developer updates, not fine.

So every access rule in Bridge lives in the database, as Postgres row-level security, rather than in the front end. A developer can only read and edit their own standup entries because the database itself refuses to return anyone else's rows — not because a script in the browser politely hides them.

-- A developer can only touch their own standup rows.
create policy "own_standups"
  on standups for all
  using ( auth.uid() = developer_id )
  with check ( auth.uid() = developer_id );

The difference is not theoretical. With this policy in place, it does not matter what the front end sends. A crafted request asking for another developer's updates comes back empty, because the filter lives one layer below anything the browser can reach. The interface became a convenience sitting on top of a database that was already safe, instead of the only thing standing between a user and data they should never see.

Money Needs Two Confirmations, Not One

The spreadsheet's original sin was that a single click could change a shared number. So in Bridge, settling a payment is deliberately not a single action.

When profit moves between partners, the sender marks it sent and the receiver marks it received. The ledger entry only turns green — only counts as settled — when both confirmations exist. Until then it sits in a visible in-between state that neither person can quietly resolve alone.

This sounds like a small UI detail. In practice it removed an entire category of disagreement. There is no longer a version of events where one partner believes a transfer happened and the other is still waiting, because the system will not call it done until both have said so. The number on the screen is now the one thing we never have to re-check against each other's memory.

The Calendar Problem Nobody Talks About

Merging calendars sounds trivial until you try it. Every client platform can export an iCal feed, but each of us was subscribing to those feeds individually, on our own devices, in our own apps. Add a client and you had to remember to send the new link to your partner, who had to remember to add it before it would show up.

Bridge collapses that into one feed. It takes every client's Google or Outlook iCal URL, merges the events on the server, and serves a single subscribe link. You subscribe once, on every device you own, and a client added in the console simply appears — no second step, no forwarded link, no "did you add it yet."

The lesson here was about where to put the work. We could have asked each person to manage their own subscriptions and saved ourselves the merge logic entirely. Doing the merge once, centrally, meant the humans had to remember nothing. Any time you can move a recurring chore from a person to a server, you should — the server does not forget on a busy Friday.

Built on a Free Tier on Purpose

Bridge runs on a free Supabase project and a free Netlify site. That was not a cost-cutting accident — it was a constraint we set on day one, for two reasons.

The first is honesty about scale. A two-partner studio does not generate the kind of load that costs money on these platforms, and pretending otherwise would have pushed us to over-engineer. Staying inside the free tier forced every decision to stay proportional to the actual problem in front of us.

The second is that it made the tool portable. Because there is nothing exotic in the stack — Postgres, row-level security, a static front end, a handful of iCal feeds — anyone running a similar small studio can stand up their own copy in an afternoon. We built Bridge for ourselves, but the white-label version costs nothing to run, which means the design was never quietly locked to our particular setup.

What We Would Tell Anyone Building an Internal Tool

We are not in the business of selling agency software, and Bridge is not a product we are pitching. It is the tool we needed, built once, in public. A few of the choices held up well enough to pass on.

Build for the size you are, not the size you imagine. Every feature in Bridge is scaled to two partners and a contractor, and that constraint is exactly what made it shippable in a weekend instead of a quarter.

Put the rules where they cannot be bypassed. Enforcement in the database rather than the interface is the single decision we are most glad we made, and it cost almost nothing up front.

When something involves shared money, make agreement explicit. The dual-confirmation ledger is the least clever part of the whole system and the part that has saved us the most grief.

The Quiet Test

The Google Sheet still exists, by the way. We kept it around for a few weeks after Bridge went live, out of pure habit, glancing at it to make sure the new system agreed with the old one. It always did — and then one day we noticed we had not opened it in a month.

That is the quiet test for any internal tool. Not whether it launches, not whether the demo looks sharp, but whether it makes the thing it replaced boring enough to forget. The sheet that once gave two partners two different numbers is now just a tab we never click. That, more than any feature, is how we know Bridge worked.

Prvih godinu dana vođenja Co Digita kao studija s dva partnera, cijeli naš back office živio je u jednom Google Sheetu. Jedan tab za klijente, jedan za to tko je što platio, treći za tekuću podjelu profita između nas dvojice. Bilo je besplatno, bilo je trenutno, i radilo je — sve do poslijepodneva kad smo ga obojica otvorili, pogledali isti redak i naglas pročitali dva različita broja.

Nitko nije pogriješio. Jedan od nas uređivao je ćeliju dok je drugi bio usred računanja, jedna formula tiho je prestala pokrivati klijenta dodanog dva tjedna ranije, a uplatu je kao "primljeno" označio partner koji ju je poslao, a ne onaj koji ju je dobio. Ništa od toga nisu bugovi u tablici. To je ono što tablica radi kad od nje tražiš da bude evidencija za novac koji dijele dvije osobe. To poslijepodne odlučili smo izgraditi ono što se tablica pretvarala da jest. Nazvali smo to Bridge.

Tri stvari koje tablica nije mogla

Kad smo zapisali što nam zapravo treba, tri su se problema izdvojila od ostalih, i nijedan zapravo nije bio o formulama.

Prvi je bio novac. Svakom klijentu naplaćujemo, uzmemo dev fee s vrha, a ostatak profita dijelimo po omjeru za tog klijenta — nekad 5:5, nekad 4:6, povremeno nešto custom. Tablica to može računati cijeli dan. Ono što ne može je spriječiti jednog partnera da uplatu označi naplaćenom prije nego što je drugi stvarno vidio da je novac sjeo na račun.

Drugi je bio vrijeme. Svaki klijent živi u drugom kalendaru — jedan na Google Workspaceu, jedan na Outlooku, još dva na onome što njihov projektni alat izvozi. Otvarali smo četiri kalendara da bismo odgovorili na jedno pitanje "što dolazi ovaj tjedan", i neizbježno propuštali onaj koji smo zaboravili provjeriti.

Treći je bio daily standup. S dva developera i contractorom koji se rotira, htjeli smo jedno mjesto za objaviti što smo radili, što nas blokira i što je sljedeće — bez da se to rasprši u Slack thread koji do četvrtka popodne odscrolla u prazno.

Zašto nismo jednostavno nešto kupili

Očit prigovor je da su sva tri problema već riješeni proizvodi. Postoje platforme za vođenje agencija, postoje alati za naplatu, postoje standup botovi. Probali smo nekoliko od svakog.

Problem nikad nisu bile mogućnosti. Problem je što su svi oni cijenjeni i dizajnirani za tim od petnaest ljudi, ne za partnerstvo dvojice. Platili bismo tri mjesečne pretplate, naučili tri različita podatkovna modela, a onda ih svejedno ručno spajali, jer nijedan ne zna da druga dva postoje. Za studio naše veličine, porez na integraciju bio je veći od troška izgradnje.

Postojao je i tiši razlog. Brojevi o kojima je riječ su naš vlastiti prihod. Predati jedini izvor istine o tome kako dvije osobe dijele svoj novac vanjskom servisu — s njegovim limitima na izvoz, njegovim ispadima i njegovom privatnom definicijom riječi "obrisano" — činilo se kao točno pogrešan kut koji bi se rezao da se uštedi vikend.

Odluka koja je sve oblikovala: vjeruj bazi, ne UI-ju

Prvi pravi arhitektonski izbor postavio je ton za ostalo. Kad gradiš interni alat, lijeni put je provesti pravila u sučelju — sakriti gumbe koje korisnik ne bi smio pritisnuti i vjerovati da nitko neće otvoriti network tab. Za javnu marketinšku stranicu, u redu. Za konzol koji drži brojke o prihodu i unose po developeru, nije u redu.

Zato svako pravilo pristupa u Bridgeu živi u bazi, kao Postgres row-level security, a ne u frontendu. Developer može čitati i uređivati samo svoje standup unose jer sama baza odbija vratiti tuđe retke — ne zato što ih skripta u pregledniku uljudno skriva.

-- Developer može dirati samo svoje standup retke.
create policy "own_standups"
  on standups for all
  using ( auth.uid() = developer_id )
  with check ( auth.uid() = developer_id );

Razlika nije teorijska. S ovom politikom na mjestu, nije važno što frontend pošalje. Sastavljen zahtjev koji traži tuđe unose vraća se prazan, jer filter živi jedan sloj ispod svega do čega preglednik može doprijeti. Sučelje je postalo pogodnost na vrhu baze koja je već sigurna, umjesto jedine stvari koja stoji između korisnika i podataka koje nikad ne bi smio vidjeti.

Novac treba dvije potvrde, ne jednu

Iskonski grijeh tablice bio je da jedan klik može promijeniti zajednički broj. Zato u Bridgeu naplata uplate namjerno nije jedna radnja.

Kad profit ide između partnera, pošiljatelj označi poslano, a primatelj označi primljeno. Stavka u ledgeru zazeleni se — broji se kao naplaćena — tek kad postoje obje potvrde. Do tada stoji u vidljivom međustanju koje nijedna osoba ne može tiho riješiti sama.

Zvuči kao mali UI detalj. U praksi je uklonio cijelu kategoriju neslaganja. Više ne postoji verzija događaja u kojoj jedan partner vjeruje da se prijenos dogodio, a drugi još čeka, jer sustav neće reći da je gotovo dok obojica nismo potvrdili. Broj na ekranu sad je jedina stvar koju nikad ne moramo provjeravati naspram tuđeg pamćenja.

Problem s kalendarom o kojem nitko ne govori

Spajanje kalendara zvuči trivijalno dok ne probaš. Svaka klijentska platforma može izvesti iCal feed, ali svatko od nas pretplaćivao se na te feedove pojedinačno, na vlastitim uređajima, u vlastitim aplikacijama. Dodaj klijenta i moraš se sjetiti poslati novi link partneru, koji se mora sjetiti dodati ga prije nego što se išta pokaže.

Bridge to sažima u jedan feed. Uzme iCal URL svakog klijenta s Googlea ili Outlooka, spoji događaje na serveru i posluži jedan link za pretplatu. Pretplatiš se jednom, na svakom uređaju koji imaš, i klijent dodan u konzolu jednostavno se pojavi — bez drugog koraka, bez proslijeđenog linka, bez "jesi li ga već dodao".

Pouka je bila o tome gdje staviti posao. Mogli smo zamoliti svakoga da sam upravlja svojim pretplatama i posve si uštedjeti logiku spajanja. Spojiti jednom, centralno, značilo je da se ljudi ne moraju sjećati ničega. Kad god posao koji se ponavlja možeš premjestiti s osobe na server, trebaš — server ne zaboravlja u prometni petak.

Na besplatnom tieru, namjerno

Bridge radi na besplatnom Supabase projektu i besplatnoj Netlify stranici. To nije bila slučajnost iz štednje — bilo je ograničenje koje smo postavili prvog dana, iz dva razloga.

Prvi je iskrenost o skali. Studio s dva partnera ne generira opterećenje koje na tim platformama košta novac, a pretvarati se suprotno gurnulo bi nas u over-engineering. Ostati unutar besplatnog tiera prisililo je svaku odluku da ostane proporcionalna stvarnom problemu pred nama.

Drugi je da je to alat učinilo prenosivim. Budući da u stacku nema ničeg egzotičnog — Postgres, row-level security, statički frontend, pregršt iCal feedova — bilo tko tko vodi sličan mali studio može dignuti vlastitu kopiju u jedno popodne. Bridge smo izgradili za sebe, ali white-label verzija ne košta ništa za održavati, što znači da dizajn nikad nije bio tiho zaključan za naš konkretni setup.

Što bismo rekli svakome tko gradi interni alat

Nismo u poslu prodavanja softvera za agencije, i Bridge nije proizvod koji nudimo. To je alat koji nam je trebao, izgrađen jednom, javno. Nekoliko se izbora pokazalo dovoljno dobrima da ih prenesemo dalje.

Gradi za veličinu koja jesi, ne za onu koju zamišljaš. Svaka značajka u Bridgeu skalirana je na dva partnera i contractora, i upravo je to ograničenje učinilo da se isporuči u vikend, a ne u kvartal.

Stavi pravila tamo gdje se ne mogu zaobići. Provedba u bazi umjesto u sučelju jedina je odluka kojoj se najviše veselimo, a unaprijed nas je koštala gotovo ništa.

Kad nešto uključuje zajednički novac, neka slaganje bude eksplicitno. Ledger s dvostrukom potvrdom najmanje je pametan dio cijelog sustava i dio koji nam je uštedio najviše živaca.

Tihi test

Google Sheet, usput, još postoji. Zadržali smo ga nekoliko tjedana nakon što je Bridge zaživio, iz čiste navike, povremeno bacajući pogled da provjerimo slaže li se novi sustav sa starim. Uvijek se slagao — a onda smo jednog dana primijetili da ga nismo otvorili mjesec dana.

To je tihi test za svaki interni alat. Ne hoće li se pokrenuti, ne izgleda li demo oštro, nego čini li stvar koju je zamijenio dovoljno dosadnom da je zaboraviš. Tablica koja je nekad dvojici partnera davala dva različita broja sad je samo tab koji nikad ne kliknemo. To je, više od bilo koje značajke, način na koji znamo da je Bridge upalio.

← Back to blog