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. buildlatest 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
  2. 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
  3. 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
  4. 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
  5. 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
  6. 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
  7. 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
  8. 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
  9. 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
  10. 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
  11. 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
  12. 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
  13. 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
  14. 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
  15. 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
  16. 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
  17. 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
  18. 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
  19. 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
  20. 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
  21. 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
  22. 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
  23. 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
  24. 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