Pulse

What we're working on, as we work on it.

Not a changelog and not a blog. The actual working log — the bug we chased for two hours, the approach we threw away, the thing that finally went green. Written up as they happen, published once a human has read them.

  1. experimentlatest Project: The Studio

    Teaching the system where a sound sits in a room

    Spent time on spatial control for audio — placing a voice at a position in space rather than flat in both ears, and being able to move it under control. It's fiddly in the way audio always is: small differences in timing and level are the whole effect, and the ear catches it the moment they're slightly off. It pairs with the voice work from last month — a voice that can be somewhere in the room is a different thing from one that just plays.

    Worked on by: Engineering 1 hour on this
    • audio
    • spatial
    • our-agent
  2. experiment Project: The Studio

    Starting real work on Ghostly, one of our own bets

    Began real work on Ghostly, one of the things we're building for ourselves rather than for a client. Early days is mostly deciding what it is and what it isn't, and standing up a rough end-to-end path so the idea can be argued with instead of just described on a whiteboard. Ours is the right place to be wrong cheaply before any of it goes near paid work.

    Worked on by: Engineering 1 hour on this
    • ghostly
    • r-and-d
  3. discussion Project: Growth Systems

    Deciding what the marketing is for before spending more on it

    Stepped back from the individual campaigns to the question underneath them — what the marketing is meant to bring in over the next couple of months, and which channels have actually earned more budget rather than just more attention. The plan that came out of it leans on the few things showing real signal and stops feeding the ones that only produce impressions. The point was to spend the next stretch's budget on purpose instead of by habit.

    Worked on by: Growth 40 minutes on this
    • marketing
    • planning
  4. build Project: Growth Systems

    Killing the weekly ritual of pulling ad numbers by hand

    Part of getting the ad spend under control was ending the Friday ritual of dragging numbers into a sheet that nobody trusted by the time it was finished. We wired the campaign data to report itself into one view, so is this working has an answer already sitting there instead of a chore standing in front of it. The automation isn't the interesting part; not having to remember to do it is.

    Worked on by: Growth 45 minutes on this
    • ads
    • automation
  5. build Project: Growth Systems

    The ad spend was running without anyone owning the brief

    The ad account had budget going out across a spread of campaigns that no single brief tied together, so it was hard to say what any given rupee was being asked to do. We pulled the active set into one place and gave each campaign one job it's accountable for, which by itself made two of them obviously redundant. Spend you can't attribute is just a subscription you forgot you had.

    Worked on by: Growth 1 hour on this
    • ads
    • campaigns
  6. discussion Project: Real-Estate CRM

    An hour across the table moved more than a week of tickets

    Sat down with the client to walk through where the CRM is and what the next stretch should carry. Most of the value was hearing which problems actually cost them time day to day versus the ones that only look big on a roadmap — the two lists overlapped less than we expected. We left with a shorter, sharper set of priorities than we walked in with.

    Worked on by: Founders 1 hour on this
    • client
    • scope
  7. build Project: Real-Estate CRM

    The assistant answered everything, including what it didn't know

    The CRM's assistant would answer in the same confident tone whether it had the ground truth in hand or was reaching from thin context — fine, until a guess about a unit's price or availability reaches a buyer as if it were fact. We put a confidence layer in front of the reply: below a threshold it says it isn't sure and routes to a person instead of inventing a clean-sounding answer. An assistant that knows the edge of what it knows is worth more than one that is always fluent.

    Worked on by: Engineering 1 hour on this
    • aria
    • confidence
  8. debug Project: Real-Estate CRM

    The spam filter started eating real enquiries too

    The new lead filter did its job on the bots and then, predictably, caught a few genuine enquiries that happened to read terse and templated. A filter that blocks a real buyer is worse than the spam it stops, so we leaned it the other way: a doubtful submission comes through as a flagged lead rather than being dropped. Better a person glances at ten borderline rows than one real enquiry never lands.

    Worked on by: Engineering 35 minutes on this
    • leads
    • spam
  9. win Project: How We Ship

    A client site that had lived on a branch finally shipped

    A client's site had been sitting finished-ish on a feature branch longer than it should have, held up by the kind of small merge conflicts and config differences that never feel urgent until they're the thing between done and live. We reconciled the branch with main, sorted out the differences between where it was built and where it actually runs, and put it out. The work was done weeks ago; today was the unglamorous last mile of getting it through the door.

    Worked on by: Engineering 50 minutes on this
    • deploy
    • merge
  10. debug Project: Real-Estate CRM

    Most of the new leads this week were never people

    The lead table had been filling faster than the sales side could work it, and it turned out most of the new rows were the public enquiry form being filled by bots — the same handful of shapes over and over, phone fields that were never dialable, messages assembled out of one template. We put detection at the point of capture rather than cleaning up after, so a submission that looks automated never becomes a lead a person has to triage. The part worth keeping is that a rising lead count had read as momentum for a while before anyone asked what was actually in it.

    Worked on by: Engineering 50 minutes on this
    • leads
    • spam
    • post-mortem
  11. win Project: The Studio

    Our own agent can hold its side of a voice channel now

    By the end of the day the agent joins a voice channel, answers out loud in something close to real time, and knows enough to wait its turn — a chain that a morning of silent connections and long dead pauses did not make look likely. It is our own agent, so it was the right place to get voice wrong a few times before it goes anywhere near a client.

    The pieces that had to line up — a real audio stream, first-chunk playback, a reply written for the ear, and the sense to stay quiet — are each small and none of them optional. Text was where it learned to think; this is where it learned to be in a room.

    Worked on by: Engineering 20 minutes on this
    • voice
    • our-agent
  12. discussion Project: The Studio

    The hard part of talking in a live room is knowing when not to

    Once it could speak, the question stopped being can it and became should it, right now. An agent that answers every pause the way it answers every message turns a voice channel into a room with one person who never lets anyone finish.

    So the same restraint we already ask of it in group chats has to carry into voice, where interrupting is louder and stopping cleanly matters more. Speaking was the easy half; deciding it's someone else's turn is the half that makes it bearable to have in the room.

    Worked on by: Product 30 minutes on this
    • voice
    • our-agent
  13. learning Project: The Studio

    A reply that reads fine sounds wrong the moment it's spoken

    The same reply that works on Discord came out stilted read aloud: links, code, the little asterisks around bold, and long hedging clauses all land as noise when there is no screen to carry them. A voice answer is not a text answer played through a speaker — it wants to be shorter, plainer, and finished in one breath.

    So the voice path gets its own shape now, not the chat one piped into a synth. What is worth saying out loud turned out to be a smaller set than what is worth typing.

    Worked on by: Product 30 minutes on this
    • voice
    • our-agent
  14. debug Project: The Studio

    The pause before it spoke was long enough to feel broken

    When it did start talking, the delay between someone finishing a sentence and the agent answering was long enough that a person would assume nothing was coming. Generating the whole reply, then turning all of it into audio, then playing it means you wait for the slowest of three stages before hearing a single word.

    Starting playback on the first chunk instead of the finished clip cut the dead air to something that reads as thinking rather than as frozen. In a text channel that whole gap is invisible; in a live room it is the difference between a conversation and a lag spike.

    Worked on by: Engineering 35 minutes on this
    • voice
    • latency
  15. debug Project: The Studio

    The agent joined the voice channel and sat there silent

    The first attempt connected cleanly — the agent showed up in the voice channel, the green ring and all — and then produced nothing. Joining a room and streaming audio into it turned out to be two separate problems, and passing the first one made it look like the second was already solved.

    Presence is a connection; sound is a stream you still have to open and feed. Once the audio pipe was wired up as its own step rather than assumed to ride along with the join, the silence went away.

    Worked on by: Engineering 40 minutes on this
    • voice
    • our-agent
  16. build Project: Growth Systems

    Getting LinkedIn automation to survive a headless container

    We built out the LinkedIn side of our outbound: connection requests and outreach both running unattended off a persistent browser context, so the logged-in session survives container restarts instead of dying with them. The login step now waits for either the PIN challenge or the phone-approval prompt rather than assuming one — assuming a single path is what had the automation failing silently for weeks while the logs looked healthy. Openers are generated per lead instead of dropped from a template, so each message reads like a person actually wrote it rather than a mail-merge.

    Worked on by: Engineering 2 hours on this
    • linkedin
    • automation
    • outreach
  17. build Project: How We Ship

    The agent that writes the log had no way to publish it

    Raman runs on a VPS and does real work there, but everything it wrote had to be relayed through a laptop before it could reach this page. It now has its own write-scoped deploy key for this repo and pushes to main directly, so an entry can go up while the laptop is asleep. The key and the clone live on the container's persistent volume rather than in the home directory, because the home directory does not survive the container being recreated and we would rather find that out now than the next time the image is rebuilt.

    Worked on by: Engineering 40 minutes on this
    • our-site
    • agents
    • deploys
  18. learning Project: How We Ship

    Our own repo said anyone was welcome to reuse it

    package.json still held whatever npm init guessed on day one. license: ISC is not a placeholder — it is an affirmative grant, so on a private repo for a site we run it was quietly telling anyone reading that the code was theirs to take. main pointed at a browser script nothing ever requires, and repository named a fork we don't deploy from.

    Set private: true, which is the field that actually prevents an accidental publish, and made the rest of it say what is true. Nobody had read any of it in a year, which is the point: the boilerplate makes claims on your behalf whether or not you meant them.

    Worked on by: Engineering
    • our-site
    • housekeeping
  19. learning Project: How We Ship

    The deploy footgun lived in one person's head

    The repo had four documents explaining individual subsystems and no README — nothing saying what the project is, how to run it, or which branch deploys. main is production, dev only produces previews, and local main tracks the client's fork rather than ours, so a bare git push on that branch goes somewhere that looks fine and isn't.

    We had already walked into that once and reported a preview deployment as live. The fix wasn't a script or a hook, it was writing the trap down at the top of a file the next person opens first.

    Worked on by: Engineering
    • our-site
    • deploys
  20. discussion Project: The Studio

    Opened the argument about selling products instead of hours

    Started talking seriously about launching our own product line rather than only building to order. Client work pays now and stops paying the moment we stop; a product is the opposite trade, and the studio has never made it.

    Nothing is decided. This is a record that the argument is open, not that it landed anywhere — and the honest counter is that the case for it gets weaker every time a new client project comes in.

    Worked on by: Founders
    • direction
  21. win Project: The Studio

    Several new development projects landed in the same stretch

    More new development work came in over a few days than the studio usually takes on at one time. Worth recording because the interesting part is not the win, it is what it forces: sequencing.

    Running several builds in parallel is where a small team quietly drops its standard — not by doing bad work, but by doing everyone's work slightly late. That is the thing to watch over the next month.

    Worked on by: Sales
    • new-work
  22. build Project: Community Games App

    A community app stopped adding features and started closing them out

    A client's community app went into its final phase of development, with a beta as the next milestone rather than another internal build. Scope was never what held it up — it carries most of a social platform, half a dozen modules deep, all sitting behind a single membership.

    Depth was. An audit earlier this year put every subfeature somewhere between a fifth and half finished, which is a specific kind of broken: everything demos and nothing survives a second user. So the final phase adds nothing new. It finishes what is already there, in the order a stranger would hit it, because beta is the first time the app is used by someone who wasn't in the room while it was built.

    Worked on by: Product
    • beta
  23. build Project: Our Website

    Answer engines had to guess what we do

    We shipped an llms.txt for a client back in July with no real evidence anything reads it. Did the same for our own site — but this time paired it with the parts that are actually load-bearing rather than leaning on the speculative one.

    FAQ and Service structured data went onto the pages that answer questions, and the robots rules stopped shutting out the crawlers that feed AI search in the first place. The llms.txt is still an unproven bet; the schema and the crawler access are not.

    Worked on by: Growth
    • aeo
    • structured-data
  24. build Project: Our Website

    Our client stories were a wall of embeds with nothing to read

    The testimonials section was entirely Instagram embeds. A crawler came away with the client's name and nothing else — the actual words, the part that says what we built and whether it worked, sat inside an iframe on someone else's domain.

    Gave each story a plain-HTML pull quote underneath the embed and wrapped it in Review markup attached to the Organization. The embed is still the nice-looking half; the text is now ours and points at something.

    Worked on by: Growth
    • seo
    • structured-data
  25. build Project: Our Website

    The homepage was competing with itself for its own name

    The nav and the logo pointed at /index.html on every page while the canonical tag claimed the bare domain, so we were telling search engines two different things about which URL the homepage actually is. Top-level pages sat at .html while blog and work were already directory-style, and cleanUrls meant Vercel 308d a visitor on every nav click.

    Settled on one shape everywhere — /path, no extension, no trailing slash — with 301s for the old forms. The part that was not obvious: Eleventy hands back /blog/slug/ for a permalink that writes slug/index.html, which is exactly the form Vercel redirects away from, so the sitemap had been nominating URLs that don't answer 200. One filter now strips it wherever a URL gets printed.

    Worked on by: Growth
    • seo
    • our-site
  26. debug Project: Our Website

    Every link inside our own posts took the scenic route

    The blog bodies still carried absolute links written for the old site — codevisionaryservices.com/blogs/..., no scheme and no www. Every one of them resolved through non-www, then www, then the new path, so a reader moving from one post to the next paid two redirects and whatever the link was worth got thinned out on the way.

    Replaced them with root-relative paths rather than freshly-correct absolute ones. An absolute link is only right until the next time a hostname changes, and this site has now changed hostname once.

    Worked on by: Growth
    • seo
    • redirects
  27. build Project: Our Website

    The first version of this feed read like marketing

    Version one of this page was a list of cards, which made it look like a blog of very short posts. Rebuilt it around a single timeline rail with the working day in the gutter and the hours running down it, so the shape of a day — the burst, the gap, the evening — is visible before you read a word of it.

    The other decision was to keep the approval gate. Entries here are drafted from real commits and stay invisible until a person has actually read them, which is slower, and is the only reason anything on this page is worth trusting.

    Worked on by: Design 2 hours on this
    • our-site
    • pulse
  28. win Project: Our Website

    The services page is now editable without touching HTML

    Our own services page was six hardcoded cards in a hand-written HTML file, which meant editing a service meant editing markup. It now loops over a JSON file exposed through the CMS, and the 01–06 numbers come from the loop rather than being typed in.

    The part worth keeping: instead of eyeballing it, we rebuilt and diffed the generated page against the old hand-written one line by line. Visible text is identical; the only differences are entity encoding. That's the check we should be running on every template we convert.

    Worked on by: Engineering 2 hours on this
    • our-site
    • cms
  29. learning Project: Our Website

    Two attempts at the team photos before we found the actual cause

    The team cards were cutting the tops off people's heads, so the first fix was object-position tuning. That was wrong — it only chose which quarter of each photo got thrown away. The real cause was that the sources are square and the card rendered them at 4:3 with object-fit: cover, so the browser was discarding 25% of every photo's height no matter where we pointed it.

    Re-cropped from the full-resolution originals with face boxes from the macOS Vision framework, and made the card 1:1 so cover has nothing left to cut. Then we actually looked: served the site locally and screenshotted every rendered image at 3x rather than reasoning about the CSS and calling it done.

    Worked on by: Design 1 hour on this
    • our-site
    • css
  30. discussion Project: Our Website

    Our own footer had invented registration numbers in it

    Going through the new site's footer, every piece of legal and contact data in it was placeholder text nobody had gone back and replaced — CIN, GSTIN, address, entity type. All of it now matches what's actually published and registered.

    One thing we couldn't resolve on our own: the live site labels the working hours PST, for an office in Howrah. Read as IST the window makes sense, so we've published it as GMT and flagged the question rather than quietly picking whichever reading suited us.

    Shashank and Pradip1h 30m on this
    • our-site
  31. discussion Project: Our Website

    The case studies on our own site were for work we'd never done

    The site we were days from launching carried case studies for projects that don't exist — placeholder work with invented outcomes that arrived with the template and was never taken back out. They read well. That is the problem with them.

    Replaced the lot with the real portfolio, which is shorter and less tidy. What we settled on is that anything on our own site has to be something a client could ring up and check, and where we don't have a number we say what we built rather than inventing one.

    Sayan, Vikash and Shashank1h 30m on this
    • our-site
    • trust
  32. build Project: AI Sales Agent

    Four writes had to finish before the lead saw a character

    The model routinely emits three or four tool calls in one block, and we ran them strictly one after another. Updating a lead stage, creating a task, scheduling a follow-up and sending the reply cost the sum of all four before anything reached the lead.

    Splitting them naively breaks things, though — the guards read state each other writes, and two messages fired together arrive in whatever order the provider accepts them. So there are two lanes now: sends and lead-document writes stay strictly ordered, everything created beside the lead runs in parallel. Audit rows moved off the reply path entirely. A block now costs the serial lane instead of the whole set.

    Worked on by: Engineering 2 hours on this
    • sales-agent
    • performance
  33. discussion Project: AI Sales Agent

    Our spam filter was a length threshold wearing a better name

    The check treated long inbound messages as pitches. It caught real spam, and it also caught anyone who had written a careful three-paragraph enquiry — which is close to the best lead you can get.

    Argued it out and moved the definition to intent: is this message pitching us something, rather than how long is it. That's a harder call and it will get some wrong. The failure we care about is turning away a serious buyer, not reading one more pitch.

    Worked on by: Product 45 minutes on this
    • sales-agent
    • classification
  34. debug Project: AI Sales Agent

    A near-miss brochure match is worse than sending nothing

    We lost a high-value lead after seven minutes of conversation, so we read the whole transcript rather than guessing. The brochure matcher scored projects on a majority of tokens, so a request for one development matched a different one from the same developer on one word out of two and sent it.

    Worse, the confirmation copy was written independently of the send result, so the agent twice told the lead a document had arrived when nothing had. Matching is strict now — every identifying token has to match, and if several projects are in play and none is named, it refuses and says why instead of guessing. Confirmations are generated from the delivery result and name the exact titles sent, so a mismatch is visible in the same message.

    Worked on by: Engineering 3 hours on this
    • sales-agent
    • post-mortem
  35. learning Project: Community Games App

    We built the table from the rules, which is not how anyone lays one out

    Rebuilt a card table in a client's app so it reads as a game rather than a form — seats around the felt, your chips where your hand is, the pot in the middle. We built it from the rules of the game, and shipped a table that is technically correct and that no player would recognise: the seating order and orientation were nothing like the real thing.

    Redid it against how the game is actually presented. Knowing the rules is not knowing the game, and there was no way to discover that except by putting it in front of somebody who plays.

    Worked on by: Design 2 hours on this
    • product
    • games
  36. debug Project: Community Games App

    People watching a game were told they'd won it

    Spectators in a client's community app were getting the end-of-game screen addressed to them — congratulated on winning a game they weren't playing, with no way to dismiss it.

    The handler checked that you were in the room, not that you were one of the players. Watching and playing are properly separate now, so a spectator sees the result reported rather than announced, and can close it.

    Worked on by: Engineering 1 hour on this
    • realtime
    • games
  37. learning Project: Our Website

    We sell mobile work and hadn't opened our own site on a phone

    Opened the new site on an actual handset rather than a narrowed browser window. The header overflowed below 420px, embedded client reels sat at three different heights, and sections ran off the side on most pages. All of it had been built and reviewed on a desktop.

    Fixed across the board, but the change that matters is procedural: a page isn't done until it's been looked at on a real phone. Shipping our own site untested on one is not a mistake we get to make twice.

    Sayan1 hour on this
    • our-site
    • css
  38. build Project: Our Website

    Our own site was hand-written HTML with no way to edit it

    Every copy change on our site was a developer's afternoon, and the blog didn't meaningfully exist. Put a static build and a CMS behind it, so the pages are templates and the content is files anyone on the team can edit through an admin screen.

    Wired the sitemap and metadata in while the structure was open. It took a few goes to get the admin route serving its config when the URL arrives without a trailing slash.

    Sayan2h 30m on this
    • our-site
    • cms
  39. learning Project: Community Games App

    The client kept re-adopting its own stale board

    A move can arrive without the full board attached, and we treated an absent board as "keep the one you have". If the local copy was already stale, it re-adopted its own stale copy on every move and could never recover — which looks precisely like a server sending the wrong state.

    We spent a while looking at the server. The thing we keep re-learning is that a missing field and an empty field want different handling, and collapsing one into the other is how a bug ends up appearing to come from somewhere else entirely.

    Worked on by: Engineering 1 hour on this
    • realtime
    • state
  40. debug Project: Community Games App

    Every re-render added another socket listener and none were removed

    Moves in a client's multiplayer feature were arriving two and three times and the board would jump backwards. Every time a screen re-rendered it attached a fresh socket listener without tearing down the old one, so a single event ran once for every mount that screen had ever had.

    Nothing crossed between users — it was one client shouting at itself. Registration and teardown live in one place now, and the same pass turned up events leaking between sessions and a stretch of interface still wired to state that no longer exists.

    Worked on by: Engineering 2 hours on this
    • realtime
    • sockets
  41. experiment Project: AI Sales Agent

    Upgraded the model, rolled it back six minutes later

    Pointed the agent at a newer model and shipped it. Every inbound message immediately threw on the API call, because that model isn't enabled on the key we run on — something we'd already hit once on a different service and hadn't written down anywhere.

    Reverted straight away. The more useful outcome was the error path: when the agent fails it used to reply "one of our agents will be in touch," which is a promise nobody had made. It says it's looking into it now, which is at least true.

    Worked on by: Engineering 1 hour on this
    • sales-agent
    • models
  42. experiment Project: Growth Systems

    Shipped an llms.txt without any evidence it gets read

    Added the structured-data, sitemap and metadata layer to a client's blog, and alongside it an llms.txt — an emerging convention for telling language models what a site is and which pages matter.

    Nobody has confirmed that any model actually fetches it. We shipped it because it costs one file and the worst case is a file that goes unread. If it turns out to do nothing we'll say so here rather than quietly leaving it in the win column.

    Worked on by: Growth 1 hour on this
    • seo
    • aeo
  43. debug Project: Learning Platform

    A session list wasn't scoped tightly enough to the person asking

    Found that the class list a learner receives wasn't scoped tightly enough to that learner, so a join link belonging to someone else's session could come back in the response. Nobody had reported it and we've no sign it was used, but it's the kind of thing you fix the hour you find it.

    Most of the time went on the part after the fix: checking every neighbouring query for the same mistake. It's scoped at the query now rather than filtered in the view, so a new caller can't reintroduce it by forgetting to filter.

    Worked on by: Engineering 1h 30m on this
    • security
    • access-control
  44. win Project: AI Sales Agent

    The gap before a reply is the part a person actually feels

    A lead sending a message waited on the whole pipeline — several database reads one after another, then a conversation lookup, then the model — before a single character came back.

    Acknowledged the message immediately, ran the pre-fetches together instead of in sequence, and cached the conversation id rather than fetching it every turn. None of that makes the answer better. It makes the wait short enough that it doesn't read as a machine thinking.

    Worked on by: Engineering 1 hour on this
    • sales-agent
    • performance
  45. win Project: Learning Platform

    Homework existed three times and worked zero times

    An admin could attach homework to a lesson. The teacher couldn't see it to grade it, and the learner couldn't submit against it. Each third of the feature had been built and each third had been checked on its own.

    Spent the evening walking the whole path in one sitting — attach, appear for the teacher, submit as the learner, come back graded — fixing whichever leg broke next. None of it was hard. The reason it stayed broken is that nobody had ever run the loop end to end.

    Worked on by: Product 3h 30m on this
    • uploads
    • grading
  46. discussion Project: Learning Platform

    Content was unlocking by the calendar, not by whether you'd done it

    Course material was appearing on a schedule — week three arrives, week three unlocks — regardless of whether the learner had finished week two. Someone who joined late saw material for lessons they hadn't reached; someone moving quickly was held back by a clock.

    The argument was about who gets to mark a lesson done. Letting the learner do it is the obvious answer and it makes the gate meaningless. It is the teacher's mark now, which is slower and occasionally irritating, and it is the only version where "completed" means anything.

    Worked on by: Product 2 hours on this
    • product
    • access-control
  47. debug Project: AI Sales Agent

    The fix was correct and in a file production never loads

    Added an instant greeting so a caller hears something the moment the line opens instead of a second of dead air. Tested it, watched it work, shipped it — and the live line was still silent.

    There are two session classes in the voice path and production runs the other one. Ported it across, then went looking for what else had only ever been fixed in the class nobody calls.

    Worked on by: Engineering 1 hour on this
    • sales-agent
    • voice
  48. debug Project: Real-Estate CRM

    A filter returning nothing looks exactly like having nothing

    A matching filter had been returning an empty set for every query, quietly enough that we had assumed there was simply nothing to match yet. It built a regex out of free-text input and ran it against fields that only ever hold one of a fixed handful of values, so it could only ever have matched by accident.

    Split the free-text search off from the constrained fields. The part worth carrying forward is how it hid: an empty result set is indistinguishable from an empty database unless you go and check.

    Worked on by: Engineering 40 minutes on this
    • search
    • post-mortem
  49. learning Project: How We Ship

    We found fixes on the live server that were in no commit anywhere

    Pulled the running code down to check something and found changes on it that exist in no commit in any branch — small fixes made directly on the box during an incident and then left there. The next deploy would have wiped them silently, and nobody would have been able to say which behaviour used to work.

    Committed them as their own change with a message saying exactly where they came from, so the history is honest rather than tidy. The rule now is that a fix made on the server isn't finished until it's in the repo. An ugly commit beats a server nobody can reproduce.

    Worked on by: Engineering 1 hour on this
    • process
    • deployment
  50. learning Project: Real-Estate CRM

    We were asking for the user's location one step too late

    Signup captured location after sending the email OTP. If someone then denied the permission we had already sent the mail, so a change of mind cost a wasted code and an inbox we couldn't take back. Moving the prompt in front of the OTP was the easy half.

    The hard half was that the location call quietly did nothing on web. The cross-platform wrapper's cached-position helper isn't implemented there, and we had been reading its empty result as "permission denied" for weeks. We ended up writing a plain HTML page that called the browser API directly, just to see what the browser actually said, and only then fixed the app. Most of that time was us trusting an abstraction instead of the platform under it.

    Worked on by: Engineering 1h 30m on this
    • signup
    • geolocation

Ready to stop losing leads?

Twenty minutes, one engineer, a straight answer on whether AI moves your numbers.

Book a call