Built in public · Changelog

How Supernova learned the job.

The product story, newest first: what actually became possible in each stretch, not a list of tweaks. Generated live from the engineering journal; the log is the changelog.

162

improvements shipped

14

days of building

12

shipped per day, avg

9
6 Jul
8
7 Jul
1
8 Jul
2
10 Jul
5
11 Jul
25
12 Jul
6
13 Jul
peak37
14 Jul
7
15 Jul
21
16 Jul
4
17 Jul
22
18 Jul
5
19 Jul
10
20 Jul

Product panorama

One deal, one continuous operating loop.

Seventeen agents live in the product today, two squads (7 sales-side, 10 delivery-side), propose-only, a named human at every gate. Everything below ships; nothing is a mock.

017 agents on the desk

Sell

Daily Brief, Email Drafter, Call Notes, Deal Check, Meeting Prep, Promise Tracker and Enrich run the deal desk, evidence attached.

02provenance quoted

Spec

A won deal births the project; Persona Researcher and Spec Writer read the real corpus and draft the use-case tree, every requirement quoting its source.

03the ratchet

Model

Data Modeler, Role Definer and Page Architect ratchet ontology, roles and the page atlas forward, one approved artifact feeding the next.

04on real components

Design

Designer codifies the design language; Mock Builder renders one mock per page wearing the approved tokens.

05earned, not declared

Run

E2E Writer specs the tests; Tech Stack Advisor and Impact Analyst guard the plan; delivery runs five-state stories, bugs, QA gates and release exit criteria.

06propose-only

Audit

Every run is a session you can open: model calls, tool calls, cost, the audit trail behind every artifact.

20

Jul 2026

Monday

7 shipped

Product08:29 IST

One Home for the sales floor, and the Desk's hidden 500 dies

The user looked at Desk and My Day side by side and asked the obvious question: what is the difference? There wasn't one worth two pages - both opened on the same Daily Brief, and since the…

The user looked at Desk and My Day side by side and asked the obvious question: what is the difference? There wasn't one worth two pages - both opened on the same Daily Brief, and since the floor unified, both roles saw both. antino3 has ONE Home, and its porting spec had already named the merge as decision #9; the user resolved it: unify. The cockpit at /ae is now THE sales Home: one briefing sentence weaving both motions (pipeline money, won FY, follow-ups due, meetings, untouched leads, active conversations - and zero-value money clauses drop out of the sentence entirely, so a deal-less SDR reads a prospecting day, not "₹0 across 0 deals"), one Needs-you queue holding deal fires and lead fires together, the four DayCard doorways, and the slipping-deals + live-conversations panels under one Daily Brief. /sdr redirects, roleHome() sends both sales roles straight to /ae with no hop, the rail carries a single Home item, and BOTH segments now crumb as "Sales" - the /sdr-vs-/ae split is plumbing, not product. The merge also killed a bug that predated it: /ae had been returning 500 on every load while still painting a full page (streaming hid it). The digest named the culprit - DealHoverCard slotted a server-serialized <Link> into Radix's asChild trigger, which arrives across the client boundary as a lazy node Slot rejects during SSR ("Primitive.a failed to slot onto its children"). The trigger now renders its own Link inside the client component, and the /ae route smoke went red to green. The two stale deep-spec assertions ("SDR is bounced out of the AE area") were rewritten to the unified-floor truth: an SDR walks into /ae/pipeline freely.

Product08:29 IST

The inbox becomes a mailbox again: the Mail lens, marked with the CRM

"Why is inbox not having all latest emails" - because it never showed the mailbox. The sync was innocent (delta pulls every 15 minutes, every sales mailbox fresh to the minute); the query…

"Why is inbox not having all latest emails" - because it never showed the mailbox. The sync was innocent (delta pulls every 15 minutes, every sales mailbox fresh to the minute); the query was the gate: the SDR inbox listed only leads whose exact address had mail, the AE inbox only deals whose committee had a thread, and a measured 85-97% of a rep's last-3-day mail matched neither - newsletters, yes, but also every real human not yet in the book. antino3 shows the mailbox; now Supernova does too. A new mail layer (src/lib/mail/queries.ts) reads the signed-in rep's OWN mirrored mailboxes - mapped via graph_user_mappings, spanning both tenants for the Harshads of the world - one row per conversation, newest first, last 90 days, grouped Today / Yesterday / Earlier. Both inboxes open on this Mail lens by default, with the old lead/deal lenses intact behind a toggle (any ?lead=/?deal= link still lands there, compose included). Clicking a row reads the conversation in place, with "Full email" into the existing reader. And per the user's follow-up, every row is MARKED with what it belongs to: entity-toned chips (colour names the entity, DESIGN 22) for the lead, or the contact's account and live deal, resolved by exact sender address - in the thread header they become doorways into the hubs. Two guards keep the marks honest: senders on our own domains never wear a customer mark (colleagues sit in the Zoho mirror as stray "leads" - Mahak was one), and the matching is annotation, never filter. One index (idx_gm_mailbox_received, targeted CONCURRENTLY script) took the list query from 961ms to 93ms. Verified live as Anisha (her 3,152 conversations, this morning's 02:45 mail on top) and Nitish (the LKS thread wearing its deal + account marks).

Product20:18 IST

The scrum is not a deal: internal calls stop being nagged

The user opened the Sales Daily Scrum recording and found the meeting page pulsing "not part of a deal, connect it" at the team's own ritual. Right diagnosis, wrong patient: the nag exists…

The user opened the Sales Daily Scrum recording and found the meeting page pulsing "not part of a deal, connect it" at the team's own ritual. Right diagnosis, wrong patient: the nag exists so client calls land on the pipeline, but an internal call is not deal material and should never be asked to become some. The page now detects internal rituals (a ritual-shaped title - scrum, standup, townhall, all hands, retro, team sync, one-on-one - AND no prospect company on the recording; client calls titled "X <> Antino" never match, and unnamed client calls keep the nag, which is correct) and wears a calm "Internal call · team ritual, not deal material" chip instead of the amber pulse. Verified on the scrum itself. This also lays the marker for the scrum-feeds-the-briefing idea already specced in PAGE-SPECS: the system now knows which calls are the team's voice.

Product19:56 IST

The sales floor unifies: SDR and AE become one login

The user's call from their latest discussion: SDR and AE stop being separate worlds. The implementation honors the distinction that matters and erases the one that does not - DATA stays…

The user's call from their latest discussion: SDR and AE stop being separate worlds. The implementation honors the distinction that matters and erases the one that does not - DATA stays book-scoped per person inside every query, but the DOOR is now shared: both roles enter both /sdr and /ae areas (SEGMENT_ALLOWED_ROLES opens both segments both ways; a new requireSales() gate replaces thirty-four exact-role checks across both areas), and both roles carry ONE unified rail that reads as the actual sales motion: Desk, My Day, Inbox, Prospecting, Leads, Pipeline, Handoffs, Accounts, Follow-ups, Meetings, Team. Twin pages that existed per role (two inboxes, two follow-up lists, two meeting calendars) keep one canonical door on the rail and stay reachable by URL. Also this hour: the force-sync button moved from the SDR home into the shell header for EVERY role and grew its third mirror - one click now sweeps Zoho CRM, both Outlook tenants, and Proshort calls, sequential, guarded, audited. Proven live both ways: an AE walked into /sdr/leads and an SDR into /ae/pipeline, each wearing the unified rail; the full routes + pipeline suite came back 48 green.

Product16:04 IST

The family goes electric: white + electric blue

The user called the move ("move the color family to white and electric blue") and the token architecture made it an afternoon, not a rebrand project: because every surface, chip, chart, and…

The user called the move ("move the color family to white and electric blue") and the token architecture made it an afternoon, not a rebrand project: because every surface, chip, chart, and shadow drinks from the CSS variable layers, the whole shift was one mapping pass over globals.css - sixty-odd hex substitutions, applied to both light blocks and both dark twins at once. Page #fbfcfe on a cool canvas, cards stay true white, every warm neutral stepped to its cool twin, and the clay slots became ELECTRIC: #2f6bff as the identity accent, #1d4ed8 as the solid button fill with its deepen-on-hover, #1e40af as the ink register, #5b8cff brightening on dark. Warm shadows re-tinted toward slate. The brand mark followed (identity runs hotter than UI: #1A56FF), the deal entity and its chart ramp moved to the blue family, and the semantic greens, ambers, and reds stayed exactly where they were - a status system should not change meaning because the brand changed clothes. One honest stumble: the first screenshots still showed clay because the dev server had served a stale CSS chunk; a recompile nudge and the electric theme was everywhere. Suite green after one scoped fix: the design page's read-only assertion now excludes the tech-stack panel's Propose button, which is the tech lead's own and always was going to match /generate/i.

Product10:30 IST

Supernova ships to supernova.nuvio.dev

The deployment question ("can you ensure you can deploy there") turned out to be three discoveries and one fix. Discovery one: the box already existed - a dedicated Azure VM named supernova…

The deployment question ("can you ensure you can deploy there") turned out to be three discoveries and one fix. Discovery one: the box already existed - a dedicated Azure VM named supernova in the GLUE resource group, DNS pointed, nginx + TLS terminating, pm2 running the app from a repo clone - but serving a commit three days stale, from before the entire delivery-side era. Discovery two: my machine had no SSH entry on it; Azure CLI admin did, so az vm user update granted the key. Discovery three, the sneaky one: the VM's GitHub deploy key had died, and the first deploy chain hid the failing git pull behind a pipe - pm2 restarted the OLD build and reported success, the exact silent-failure shape the glue journal warns about. Fixed properly: a fresh ed25519 deploy key minted on the VM and registered read-only on the repo, the pull fast-forwarded forty-plus commits, and the unmasked install-build-restart chain came back INSTALL-OK, BUILD-OK, RESTARTED with every smoke route (/, /agents, /changelog, /sign-in) at 200 over HTTPS. Production talks to the same Azure Postgres the session has been applying DDL to all along, so the schema was already waiting for the code. The ritual is now written into TASKS' resume notes, dead-key lesson included.

Product10:20 IST

The work record opens: every name on the CEO's board is now a door

The timesheet-as-a-report idea takes its next stride. When the work record landed on /admin/command it answered "what did the company do this week" as ten rows of counted, audited actions -…

The timesheet-as-a-report idea takes its next stride. When the work record landed on /admin/command it answered "what did the company do this week" as ten rows of counted, audited actions - but the moment Vinay reads a row, the next question is already forming: what is THIS person actually working on? That question is now one click. Every name in the work record that resolves to a real user links through to /admin/people/[id], the per-person work view: the identity header up top (role, designation, department), then a six-tile band of the numbers a COO reaches for first - stories delivered and the points behind them, QA runs recorded in the last 30 days, bugs fixed, audited moves this week (amber when the week went quiet, because silence is a signal too), and their AI spend in rupees, because an agent-assisted developer's cost line belongs beside their output line. Under the numbers sits the now: Working on now lists every story assigned to them across projects, blocked work surfaced first with the block reason worn loud in amber, each row carrying its project, points, and delivery-status chip; Bugs on them does the same for open bugs, worst severity first. And under the now sits the record: their last five standup notes with sprint and day, and The week's trail - the last twenty audited actions under their name, action chip plus the human sentence plus the IST minute, the literal work record nobody had to fill in. The page invents nothing: assignments come from the sprint boards, the KPIs from the same ledgers the rest of the product already writes (test_runs, bugs, llm_calls, the audit spine), the trail from the spine itself. The timesheet stopped being a form last week; this week it grew a face per person, and the COO's "what are the devs working on" became a page instead of a meeting.

19

Jul 2026

Sunday

2 shipped

Product01:19 IST

The Page Architect writes its own FK

Small debt, twice paid by hand, now closed at the source: the surface-map agent gets the project's PRODUCT ROLES in its context ("owner_role MUST be EXACTLY one of these, or empty for a…

Small debt, twice paid by hand, now closed at the source: the surface-map agent gets the project's PRODUCT ROLES in its context ("owner_role MUST be EXACTLY one of these, or empty for a public page" - no more inventing "Customer" when the role is "Diner"), and the insert resolves owner_role to owner_role_id case-insensitively in the same statement. The atlas groups by the FK; the free-text label is only a display fallback for sets an old regeneration has not re-linked.

Product00:52 IST

Paygen becomes the showcase: a project that reads like a real week

The user's call: enrich ONE project with super-nuanced contextual data so the system can show what it is capable of. Paygen (39) already carried the full pipeline output;…

The user's call: enrich ONE project with super-nuanced contextual data so the system can show what it is capable of. Paygen (39) already carried the full pipeline output; scripts/seed-paygen-demo.mjs (idempotent, re-runnable after e2e suites) now layers a delivery week over it that is TRUE to the domain. A seven-person roster mixing real Antino staff (Smita, Jagriti, Neha, QA) with the client's people (GIVIT's founder as sponsor L1, their product owner accepting releases L2, Shashank as the Antino escalation). All eleven stories assigned and spread across a live plan: Sprint 1 mid-flight (14-25 Jul, ships in R1 Pilot) with one story in EVERY column - S-101 done, S-104 in QA, S-102 in review, S-201 in progress, and S-103 blocked on a reason that cites the actual business rule ("Meta Business verification stuck in review; Tenant cannot reach ACTIVE until VERIFIED (BR-2)"). Five standups that tell one continuous story - the block raised and escalated, the pilot restaurant confirmed, T-122 failing on the resume flow, the fix landing, Priyank asking for the demo on the 24th. QA runs green where earned plus that one honest FAIL with a 3/3 repro note; two bugs born from it (the serialization mismatch FIXED awaiting verify, the 24-char WhatsApp list truncation OPEN with options argued in the detail). Gates: R1 Pilot 12-Aug wearing sign-off + AC coverage + QA-runs-green (0/3, honestly), R2 Ops and Comms 26-Aug, Phase 1 Live 30-Aug against go-live 31-Aug - so the runway line reads "43 days out · 32p open · 1 blocked · 2 open bugs". A week of audit entries and two unread bell notifications complete the picture. Every seat now demos: the TL's desk, the board, the sprint room, the dev contract, QA's bench.

18

Jul 2026

Saturday

19 shipped

Product23:40 IST

Board joins the rail, and the Plan leads with the ladder

Two moves toward "as Jira as possible - people understand this well" (the user's standing call). Board is now a first-class rail item, the exact split Jira ships: Plan is the backlog view,…

Two moves toward "as Jira as possible - people understand this well" (the user's standing call). Board is now a first-class rail item, the exact split Jira ships: Plan is the backlog view, Board is the active sprint - /tech-lead/board resolves the selected project's ACTIVE sprint and renders its three-column room (cards with assignees, blocks, the standup log); no active sprint gets an honest empty state listing the planned sprints with doorways, never a blank page. And the Plan now leads with the drill-down the user described: milestone CARDS at the top (target date, releases count, done/total stories with a progress bar, gates rollup), a click opens the milestone's releases, a release opens its sprints, a sprint opens the board - every ring of the ladder is one click deeper, the way tools people already know navigate containment. The chain is a permanent test now (rooms.spec walks card -> milestone -> release -> sprint -> standup log). Suite at 24 green.

Product23:24 IST

The delivery side fills its seats: a Dev workspace, a QA role, and a watchdog that chases

The role-coverage gap closed in one move ("QA dev tech lead many things missing - fill the gaps"). Dev: /dev stops being a stub - the Team roster's user_id link makes assignment real, so My…

The role-coverage gap closed in one move ("QA dev tech lead many things missing - fill the gaps"). Dev: /dev stops being a stub - the Team roster's user_id link makes assignment real, so My Work lists exactly the stories assigned to this login (grouped by project, live-sprint first), and each opens as the dev's CONTRACT: acceptance criteria with proven/covered/uncovered marks, edge cases, the test cases that prove the ACs wearing QA's latest verdicts, and the e2e specs whose covers name the story. Two verbs, hard-scoped to their own stories: move (DONE wakes the tech lead) and block-with-reason (always wakes the tech lead). QA: a real role (enum value, /qa area, seeded e2e user) whose queue is DONE-but-unverified stories; every test case renders with steps + expected, PASS is one click, FAIL demands the note and notifies the tech lead AND the assignee. test_runs is the ledger - a test case is a plan until someone runs it; the latest run is its verdict - and releases can now gate on QA runs green (computed from runs, never human-settable, joining SPEC_PASSES in the honesty club). The watchdog: an hourly delivery cron with four checks, each nudging the tech lead at most once a day - active sprint with no standup after noon IST, stories blocked past two days ("an escalation, not a status"), feedback proposals waiting past 24 hours, releases landing within a week with an open (or absent) gate. Proven end to end by the new three-seat delivery.spec: the TL assigns a face in the sprint room, the dev works the contract, QA records a PASS - 22 green across the suite.

Product22:17 IST

A page you can actually audit, and a sprint that knows who and what stands in the way

Two gaps the user named, closed together. First (their example): the atlas cards are too small to judge a surface, so every page grew a detail room - /tech-lead/pages/[id] renders the mock…

Two gaps the user named, closed together. First (their example): the atlas cards are too small to judge a surface, so every page grew a detail room - /tech-lead/pages/[id] renders the mock at 75vh in real device widths (Desktop fill / Tablet 834 / Mobile 390, the toggle every design review trains people on), with the definition beside it as the checklist you audit AGAINST (primary actions, entities it speaks for, stories served, states to check) and a contextual verdict box that stamps "About the page X:" into the PAGES gate - the audit and its consequence stay on one screen. Atlas titles link in. Second: the sprint room's cards now carry the two facts a standup runs on - an assignee (from the project's Team roster, user_stories.assignee_member_id; the empty roster honestly says "no team yet") and a blocked state with a mandatory reason (blocked_reason), amber on the card, counted in the room header, both audited. And the operations desk opens with the runway line: go-live date, days out, open points, blocked count - amber inside three weeks, rose past due. Paygen reads "Go-live 31-Aug-2026, 44 days out, 35p open", which is the sentence the whole desk existed to say. New permanent spec (rooms.spec.ts): audit-size viewer + device toggle + block/unblock roundtrip.

Product22:04 IST

Ivory paper, bone panels: the tone step inverts

The user named the palette exactly: page #FAF6F0 ivory, cards #F0E5D7 bone - a tone step DEEPER than the page, so surfaces read as soft panels laid on paper rather than white boxes…

The user named the palette exactly: page #FAF6F0 ivory, cards #F0E5D7 bone - a tone step DEEPER than the page, so surfaces read as soft panels laid on paper rather than white boxes floating; borderless throughout, the tone step plus the warm shadow is the whole edge. That inverts the old relationship (cards used to sit LIGHTER on the cream), so the entire yellow-cream neutral family moved together - background, sidebar, muted insets, hovers, rules, grid lines, inputs - each remapped one step into the ivory/bone family in both light token blocks (dark untouched). The canvas doctrine flipped with it: the grain canvas used to sit a step darker than the page so lighter cards would pop; now the canvas IS the ivory page and the cards sink a tone deeper into it. Three borders that had crept into the new surfaces went out the same door: the ERD boxes dropped their ring for tone-plus-shadow, the sprint-room empty slot went from dashed outline to a muted inset, and the plan board's story rows swapped hairline separators for odd-row tone striping. Muted text on bone still meets AA at 4.5:1.

Product22:04 IST

The dashboard stops filing and starts reporting: the pulse strip

User: docs can be removed from the dashboard; it needs more metrics, something that gives a sense of what is going on. Both halves landed. The Documents panel left the Overview (files have…

User: docs can be removed from the dashboard; it needs more metrics, something that gives a sense of what is going on. Both halves landed. The Documents panel left the Overview (files have one home now, /tech-lead/documents, which already carried the thread files and uploads - the spec moved with them), and in the freed attention budget the page opens with a pulse strip: six tiles that answer "what exists, what waits on me, is this week alive" before any panel is read - use cases approved/total, stories with the live AC-tested percentage (amber under 60), pages mocked, e2e spec count, an amber Needs your hand (pending gate reviews + open questions summed and itemized), and Moves this week (the 7-day audited-action count, amber when zero because a quiet project is a warning). Every tile is a doorway to its surface. On Paygen the strip reads 5/5 approved, 11 stories at 58% AC, 7/7 mocked, 10 specs, 11 open questions waiting, 38 moves this week - which is exactly the sense-of-what-is-going-on the page owed its owner.

Product21:09 IST

The plan becomes a place you RUN: Jira-shaped board, sprint rooms, and the operations desk

The user's unifier ask, landed as four surfaces. The Plan redrew itself in the shape every Jira user already knows (the user's call: "people are used to seeing things a certain way - do not…

The user's unifier ask, landed as four surfaces. The Plan redrew itself in the shape every Jira user already knows (the user's call: "people are used to seeing things a certain way - do not move away from that"): collapsible sprint bands over a Backlog of full-width story rows, releases + milestones in a right rail, the exit-criteria gate still on the release card because that is the part Jira does not have. The sprint room (/tech-lead/plan/sprint/[id]) runs the sprint: a TODO/DOING/DONE board over the new user_stories.delivery_status (delivery is a separate axis from spec status - APPROVED-as-spec and TODO-as-work at once is the whole point) plus the scrum ritual as data: sprint_standups, one note per day, the sprint's running log. Release and milestone rooms give every ring of the ladder a page of its own (gate read-only with live AC math; editing stays on the Plan - one place to change criteria, many to see them). The ontology grew an ERD - deterministic grid SVG, entity boxes with PII badges and states chips, orthogonal cardinality-labeled edges, no diagram library. And the Overview, once a project reaches PLANNING, opens with the Delivery operations desk: active sprint progress, a standup-today nudge, gates rollup, next milestone - the whole operation of a project at one glance, every line a doorway. Driven end to end by Playwright on the freshly-walked Paygen: sprint created, stories picked, sprint started, story moved to DOING, standup logged, desk live. One Playwright lesson en route: hasText: 'Backlog' also matched the sprint band's "move stories in from the backlog below" (case-insensitive substring), so the drive silently grabbed the wrong select - anchor on headings.

Product21:09 IST

Paygen walks the whole ratchet, and the proposal grades the output

The user's ask: simulate the ENTIRE pipeline on project 39 and judge quality, quantity, and fidelity against what was sold. Playwright walked every rung as the real users (tech lead +…

The user's ask: simulate the ENTIRE pipeline on project 39 and judge quality, quantity, and fidelity against what was sold. Playwright walked every rung as the real users (tech lead + designer storage states, real server actions, real SONNET agents): 5 use cases approved → 12-entity ontology → 4 roles → designer assigned → DESIGN.md generated + approved → 7 pages → 7 mocks → 10 e2e specs → PLANNING. The locks held at every rung (mocks would not complete until every page was mocked) and the PhaseLock handles moved the phase without ever visiting the Overview. Fidelity, graded against the extracted proposal: the negative scope HELD (delivery is out of scope in the doc and appears nowhere in the artifacts; the one Order entity speaks pickup/reservation only) and the product identity stayed right end to end - but the pipeline never modeled payments, the two-way Petpooja POS sync, or the Flow Builder (the proposal's self-declared most engineering-intensive module), and it INVENTED a Coupon entity plus discount fields that appear nowhere in the document. Quantity: 11 stories, 14 tests, 3-of-7 pages are one onboarding wizard sliced thin, and the "Draft again" append left UC13 approved with zero stories. Mechanics caught live: the Page Architect writes owner_role text but never owner_role_id (all 7 NULL, backfilled; it also invented a "Customer" role where the product role is "Diner"). The honest verdict: the ratchet and gates work; the Spec Writer needs a scope-coverage pass (model what IS in the proposal, invent nothing that is not) more than it needs more volume.

Product19:34 IST

A lock grows a handle: PhaseLock offers the advance

User on project 39: "where is the button to generate use cases?" Behind the ratchet - the project was still in RESEARCH, the surface phase-locked, and the only way forward (Complete phase)…

User on project 39: "where is the button to generate use cases?" Behind the ratchet - the project was still in RESEARCH, the surface phase-locked, and the only way forward (Complete phase) lived on the Overview stepper: a dead end exactly where the user hit the wall. PhaseLock now checks the current phase's prerequisites server-side and acts on the answer: prereq met -> "Complete Research and continue" right on the lock (same server action as the stepper, same server-side checks); prereq unmet -> the SPECIFIC gap in amber ("Research at least one persona first") with a doorway to the Overview. All 8 call sites pass projectId. Probed on 39: the lock offers the advance.

Product18:40 IST

The changelog learns the minute: IST ship times from git

The journal carries dates; git knows the minute. scripts/changelog-times.mjs mines the first commit that introduced each ## heading (git log -S, idempotent, append-only) into the…

The journal carries dates; git knows the minute. scripts/changelog-times.mjs mines the first commit that introduced each ## heading (git log -S, idempotent, append-only) into the checked-in docs/JOURNAL-times.json; the changelog merges them, orders same-day entries by ship time (they are sometimes inserted mid-file), and wears the time on the featured card and every spine entry - a 02:10 entry and a 14:37 entry tell different stories, which is half the flavour of a build-in-public log. 137 of 137 headings resolved; the miner joins the docs-before-commit ritual.

Product18:37 IST

The Documents page learns the sales universe

(user's ask) /tech-lead/documents now shows what the DEAL's email thread carried, not just what someone uploaded: the thread-files derivation moved from the Overview into…

  • (user's ask) /tech-lead/documents now shows what the DEAL's email thread carried, not just what someone uploaded: the thread-files derivation moved from the Overview into lib/tech-lead/thread-files.ts (shared, deduped by name keeping the latest copy, .ics filtered) and gained email context - sender, subject, date. Sealed-key downloads through the existing /tech-lead/attachments route; read-only by design, what was SENT cannot be deleted here. On SalerySe it surfaces exactly the record that matters: the signed service agreement, the eSign audit trail, and the v2 agreement draft.
Product14:37 IST

Tests joins its parent: E2E specs render on the use case they verify

The last child artifact left the rail (user: the use-case detail shows stories while a separate Tests page holds the specs - "not all in sync"). A spec verifies a use case, so it now…

The last child artifact left the rail (user: the use-case detail shows stories while a separate Tests page holds the specs - "not all in sync"). A spec verifies a use case, so it now renders THERE: the detail page matches e2e_specs.covers against the use case's story codes and shows each spec as a collapsible card (file name, version, covered stories, full Playwright source with its data-testid contract) right under the story tree. /tech-lead/tests redirects to use-cases; the write-all-specs affordance moved to the use-cases list header; the rail is down to 8 project items + the parallel session's Team and Documents. One repair en route: drizzle's sql template mangles JS-array params (the ::text[] ANY pattern silently returned nothing), so the covers intersection computes in TS. Suite 15 green with a new redirect + inline-specs test.

Product10:41 IST

Team and Documents become first-class pages

/tech-lead/team (user's ask): the roster managed properly - two columns (Antino side, client stakeholders), escalation ladder chips, add on either side, inline row editing (role, side,…

  • /tech-lead/team (user's ask): the roster managed properly - two columns (Antino side, client stakeholders), escalation ladder chips, add on either side, inline row editing (role, side, level, email), remove. Member audit events got their own names (TEAM_MEMBER_ADDED/UPDATED/REMOVED - they were borrowing PLAN_CHANGED) with icons in the trail.
  • /tech-lead/documents (user's ask): the project's files as a real management surface - metadata list (size, IST time, uploader via a new join on getProjectDocuments), download through the existing authz route, drag-drop upload reusing the wizard's action and caps, and deletion behind an AlertDialog with a stale-list guard: the action takes the id AND the file name the user believes they are deleting, and a mismatch touches nothing (the bytes live only in that row; PROJECT_DOCUMENT_REMOVED audits with the before payload).
  • Follow-up (user: Antino actors ARE employees, dropdown needed): the Antino-side add is now an employee PICKER over the 234 active users (searchable, name + designation), project_members.user_id finally used - the server resolves name/email from the users row and rejects duplicates, so an Antino member can never be a typo. Live-fired: picked Namrata Harsh, added, removed; both audited.
  • Both in the tech-lead nav after Plan (governance surfaces, not pipeline phases - no PhaseLock), both screenshot-verified in the vanilla theme, both zero sub-AA on the contrast audit.
Product10:33 IST

The research gaps close: the engines READ the documents, and bots leave the cast

The follow-through on the user's audit ("no - are all problems related to research sorted?"). Three fixes, proven by a live re-run:

The follow-through on the user's audit ("no - are all problems related to research sorted?"). Three fixes, proven by a live re-run:

  • Text extraction into the corpus (src/lib/docs/extract.ts): DOCX via adm-zip (exact), PDF via a bounded naive stream extractor - bounded because the first cut hit catastrophic regex backtracking measured in MINUTES on the real signed agreement, and honest because CID-encoded e-sign PDFs produce mojibake that LOOKS like text: an English-function-word gate returns null rather than feed a model noise wearing the agreement's name. gatherCorpus now builds ONE reading list from wizard uploads AND the files the thread carried (latest per name, keys verified server-side, up to 8), extracts each, and labels rough extractions. The checklist strip upgraded: renders even when SUFFICIENT (emerald), because "agreement: missing" matters regardless.
  • SYSTEM actors: personas are humans by prompt contract now; bots/POS/APIs get kind SYSTEM, no pains, no goals (systems have requirements, not feelings), and render in their own "System actors" section as integration contracts.
  • Docs-first research: the corpus is primary, web fills only silence, evidenced-vs-researched parity demanded where the corpus supports it, thin corpus means a smaller honest cast.
  • Live-fired on Paygen (the Research again button through Playwright, 2.3 min on Sonnet): the cast went from 1-2 evidenced / 3-4 researched per persona to evidenced-first with document citations - "proposal.docx, section 2.2: the WhatsApp Cloud API does not expose programmatic flow deployment", "Payment_Options_Briefing, Option 1: Friction HIGH, customer leaves WhatsApp" - and "WhatsApp Ordering Bot" moved to [SYSTEM] with zero pain points. Page badges: 8 SAID vs 2 WEB. v1 preserved in personas_versions; v2 live.

Still open from the audit: the tech-stack suggestion artifact (nothing proposes a stack; the scaffold hardcodes Next+Tailwind+Drizzle).

Product10:08 IST

Personas: horizontal cards, contextual feedback, and the corpus confesses its gaps

The user audited /tech-lead/personas on Paygen and called four things. (1) The research had NOT used the documents: project 39 has zero uploads, gatherCorpus never reads thread attachments,…

The user audited /tech-lead/personas on Paygen and called four things. (1) The research had NOT used the documents: project 39 has zero uploads, gatherCorpus never reads thread attachments, and docx/pdf are unreadable anyway - so the cast ran 1-2 evidenced vs 3-4 researched pains per persona, web-heavy exactly as they smelled. (2) "The user and the agent have to be treated differently": the cast contains a "WhatsApp Bot" PERSONA - a system actor shoehorned into the human cast (fix pending: SYSTEM actor category). (3) Layout was a 3-col wall of text. (4) Feedback was only free-form.

Shipped now:

  • Full-width horizontal persona cards: identity | pains | outcomes as columns with eyebrows, provenance badges renamed to the honest SAID / WEB, per-column icons - typography does the structuring, storage stays structured rows (personas + personas_versions; PERSONAS.md is a derived rendering, never the store).
  • Contextual per-persona feedback (persona-feedback.tsx): every card takes a note about THAT persona, stamped 'About the persona "X":' before entering the same changeset gate - contextual input, one review queue up top.
  • The corpus-sufficiency flag (user: "should we flag if the document set is not complete enough to even run?"): gatherCorpus now returns deterministic health - SUFFICIENT | THIN | INSUFFICIENT with reasons AND a named checklist (proposal/scope · signed agreement/MSA · requirements), each located across wizard uploads AND thread attachments as in-corpus / on-file-unreadable / missing. Paygen reads honestly: proposal on file but unreadable, agreement missing (open deal), research will lean on the web.
  • gatherCorpus also now passes the deal NAME into the timeline (the committee-union recall fix), so the engines see the same emails the Overview shows.
  • Probed via Playwright: flag + checklist + 5 feedback buttons render on project 39.

Named but not yet built: docx/pdf text extraction into the corpus (the biggest unlock: the Paygen proposal names Petpooja POS, Meta Cloud API, Phase-1 reservation+pickup-only), SYSTEM actor category, docs-first research prompt, and a TECH-STACK suggestion artifact (nothing in the pipeline proposes a stack today; the scaffold hardcodes Next+Tailwind+Drizzle).

Product10:05 IST

Apple buttons, ONE Button, and the colored-ink deepening

Buttons are Apple-style now (user's call): every size of the one true Button is a pill; default is a solid deep-burnt fill #b4551c with a WHITE label (4.9:1) darkening on press; destructive…

  • Buttons are Apple-style now (user's call): every size of the one true Button is a pill; default is a solid deep-burnt fill #b4551c with a WHITE label (4.9:1) darkening on press; destructive went from tinted to solid rust + white (5.0:1). The hot #fc6c26 is barred from button fills and from carrying white text - it stays the identity accent. 36 call sites swept from bg-clay text-ink to bg-clay-solid text-white; wizard step dots and badges deliberately keep ink.
  • "How do we ensure the reusable Button is the only button": an eslint no-restricted-syntax rule now ERRORS on raw <button> outside src/components/ui; the 34 pre-existing offender files are an enumerated ignore list in the config - a visible burn-down that new code cannot join.
  • The colored-text layer went one step deeper, driven by the canvas audit against the /design showcase (all 59 components): semantic inks (success #446a2c, warning #78530c, danger #a83c26, slate #38607e), three entity inks (lead/task/employee), raw --ink-3 both themes, badge/alert destructive text via text-(--ink-danger), tabs inactive to foreground/70, and the showcase's hot-clay data-text demos moved to clay-ink/clay-hot. Final state: /design carries exactly TWO sub-AA rows, both intentional (a disabled-state demo, an --ink-4 watermark numeral); all seven tech-lead pages remain at zero.
  • Gotcha for the record: repeated sed -i rename-writes detached Turbopack's file watcher from globals.css - token edits silently stopped reaching the browser until a plain in-place write re-attached it. Verify served CSS, not just source, when a token change "does nothing".
Product09:52 IST

The timeline stops hiding the promise record: email attachments surface

User: the Overview "shows what we have from communication etc" but not the attachments - "without attachment we will never know what we have promised to build". They were righter than they…

User: the Overview "shows what we have from communication etc" but not the attachments - "without attachment we will never know what we have promised to build". They were righter than they knew: 77k email attachments sit in graph_message_attachments (bytea, synced all along), including SalerySe's SIGNED service agreement (SalarySe x Goldenflitch_ToD_Service_Agreement_signed.pdf, circulated 4x) and Paygen's proposal docx - and none of it was surfaced anywhere. Worse, the committee-based email matcher found ZERO emails for SalerySe (Zoho contact hygiene) and missed Paygen's proposal-bearing thread (4 of 14 emails).

  • Emails now carry their files: getDealTimeline email rows aggregate non-inline attachments (lateral jsonb_agg); the Overview renders paperclip chips under each email, and a "From the thread" section tops the Documents panel - every file the thread ever carried, deduped by name keeping the LATEST copy (agreements recirculate; the last one is the signed one), .ics noise excluded. What was SENT renders above what was uploaded.
  • Matching is committee ∪ subject: the committee match (precision) is unioned with a fuzzy subject match on the account AND deal names (recall), vowels wildcarded because humans spell SalerySe three ways, deduped by message id. SalerySe went 0 -> full thread; Paygen 4 -> 14 emails with the proposal aboard.
  • Sealed capability links: graph_message_attachments has no FK path to a project, so instead of re-deriving committee->deal->project in a byte route, the page that already proved the user may see the message MINTS a signed, expiring key per attachment (jose HMAC, aud graph-attachment). /tech-lead/attachments/[key] verifies the seal + the live users row (JWT cookies outlive suspensions), streams bytea; PDFs and images open inline (reading the agreement is the point), everything else downloads.
  • Probed live via Playwright on both projects: SalerySe shows the signed agreement streaming 200 at full size; Paygen shows proposal + payment briefing. Suite now 16 green.
Product09:52 IST

Zero sub-AA: the faded-text idiom retired

The last soft spot fell: text-muted-foreground/60|70|80 (timestamps, hints, meta lines - 58 usages) swept to full-strength muted, which after the #6f6757 deepening IS the accessible quiet…

  • The last soft spot fell: text-muted-foreground/60|70|80 (timestamps, hints, meta lines - 58 usages) swept to full-strength muted, which after the #6f6757 deepening IS the accessible quiet register (4.8-5.4). Two /50s that were real text (the audit day-count, panel "not set") joined them. /50-and-below survives ONLY on decorative and disabled elements (chevrons, hover-reveal arrows, disabled pagination, absent-state icons) - WCAG-exempt, deliberately faint.
  • Plan board's ACTIVE chip took one more step (emerald-800 on its greener wash, 4.36 was a near-miss).
  • Final canvas-normalized audit across all seven tech-lead pages: zero sub-AA text pairs. The vanilla + burnt orange combination is now fully proven, not just pretty.
Product09:12 IST

Vanilla, literally: the exact hexes, ink-on-orange, and the app goes light

The user's verdict on the tuned values: "this is not the same colors I gave you." Correct. The palette now uses the EXACT hexes - page paper #FFF4D6, accent #FC6C26 - made accessible by…

  • The user's verdict on the tuned values: "this is not the same colors I gave you." Correct. The palette now uses the EXACT hexes - page paper #FFF4D6, accent #FC6C26 - made accessible by flipping button text from white to espresso ink: ink on #FC6C26 is 5.79:1 (AA body) where white was 2.87 (fail). The sweep converts bg-clay text-white to bg-clay text-ink app-wide; because --ink is theme-resolved, landing dark bands keep their light-on-orange look automatically.
  • The paper ladder derives from #FFF4D6 (panel #fff9e6, card #fffcf0, row #fbf0d0, hover #f5e7bc, grids); every semantic ink measures at or above its old level. clay-hot #e85d15, clay-ink #b94a0a, ring #e85d15; o-ramp/entity-deal/chart ramp/changelog glow/auth wash follow.
  • The app is LIGHT now: the parallel session had hardcoded app-theme dark in the shell (the espresso direction) - which is why the user kept seeing a dark page after the palette shift. Removed dark from app-shell and /design; DarkBody became AppThemeBody (portal scope mirror only); Toasters to light. Dark tokens untouched, awaiting a real toggle.
  • Straggler note: repo-panel.tsx still has one bg-clay text-white - it is dirty in the parallel session's working tree, left alone deliberately; convert after they commit.
Product09:12 IST

Navigation learns the ontology: Role → Page → Mock, and releases get gates

The user's critique, in order: mocks are children of pages so a Mocks rail item makes no sense; pages belong to roles so even the Pages list was flat-wrong; Docs is an output, not a…

The user's critique, in order: mocks are children of pages so a Mocks rail item makes no sense; pages belong to roles so even the Pages list was flat-wrong; Docs is an output, not a workspace; Settings is user-level, not project-level; and the Plan page drew the milestone → release → sprint hierarchy as three flat sibling lists with no dates. Plus the deeper one: tests are a document nothing waits on. All fixed.

  • The Role → Page edge became real. page_defs.owner_role was free text, and the live data proved the failure: pages said UIUXDesigner/DeliveryLead while product_roles said Designer/EngagementAdmin - ZERO matched. Added owner_role_id (FK, nullable = a public pre-sign-in page like login), backfilled all 10 via the roles' persona lists. Grouping trusts the FK; unlinked labels render honestly flagged.
  • The page atlas replaced the flat Pages grid AND the Mocks page: pages grouped by the product role that owns them (persona chips on the group header, "Public · before sign-in" bucket for role-less pages), each page a card with Definition | Mock tabs - a mock has no identity apart from its page. Amber dot = not built; sandboxed iframe when built; the MOCKS-ripple feedback gate moved here. /tech-lead/mocks is now a redirect; the designer's build workspace is untouched. MOCKS phase prereq sharpened: every page has a mock, not "at least one".
  • The rail slimmed 12 → 9: Mocks gone (child), Docs gone (output - a doorway on the Overview's repository panel now), Settings gone from EVERY role's rail (user-level; the sidebar footer's account menu already owned it).
  • Releases got their gates (release_criteria): SPEC_PASSES (honestly PENDING until repo CI exists - never a fake green), AC_COVERAGE (computed live from the actual test_cases coverage of the release's stories), MANUAL_SIGNOFF (the only kind a human may mark met). WAIVED requires a written reason. A release with no criteria wears "none: this release ships on trust".
  • The plan board now draws the ladder as it nests: Milestone (target date) → Releases (target date + gate) → Sprints (start/end dates, PLANNED/ACTIVE/DONE status cycling with one-ACTIVE-at-a-time) → stories; ungrouped releases/sprints in amber buckets with assign controls. The schema's dates and statuses existed all along; now they render and edit.
  • Suite updated to assert the new IA and grew to 15 (mocks-redirects, atlas-tabs-no-Build, rail-has-no-Settings/Mocks/Docs, plan-gate probe): all green.

17

Jul 2026

Friday

1 shipped

Product23:19 IST

The Vanilla shift: burnt orange + vanilla, contrast-corrected

The user brought a burnt-orange #FC6C26 + vanilla #FFF4D6 combo (an Instagram UI-palette reel) and asked whether it fits. Verdict: the mood IS Daylight (hot orange + warm paper is the…

  • The user brought a burnt-orange #FC6C26 + vanilla #FFF4D6 combo (an Instagram UI-palette reel) and asked whether it fits. Verdict: the mood IS Daylight (hot orange + warm paper is the identity the app already runs), but the literal pair fails WCAG (white on #FC6C26 is 2.87:1; orange text on vanilla 2.62:1). Adopted the disciplined version into the LIGHT theme.
  • Paper goes vanilla: page #f0e8d2, panel #f6f0dd, card #fcf8ea, row #f4edd8, hover #ebe1c4, grid #e0d5b4 - the same tone-step ladder one notch yellower. Espresso ink holds 13.6:1; sage/amber/rust keep their compliance levels (deltas under 0.05).
  • Clay becomes burnt orange, and gets MORE accessible doing it: base #b4551c (white text 4.93:1 vs old clay's 4.14), hover #cf5f13 (3.96 vs 3.34), text-orange #96430e (5.53). The o-ramp, deal entity tone, deal chart ramp, auth panel, and entity-colors follow. --vanilla highlight surface is now the true #fff4d6.
  • Laws that made it usable: orange is never body text (ink reads, orange acts); white-on-orange only at button scale; #F97316 stays the mark's hue alone. Dark espresso untouched. All ratios measured with a WCAG script, not eyeballed; recorded in DESIGN.md's Vanilla-shift entry.

16

Jul 2026

Thursday

14 shipped

Product21:49 IST

Use cases, stories, tests, ontology: mock pages with real depth

User specced the project ontology (use cases -> user stories -> test cases; project has an ontology = data model) and, on the first thin cut, called it: "depth is missing a lot lot lot".…

  • User specced the project ontology (use cases -> user stories -> test cases; project has an ontology = data model) and, on the first thin cut, called it: "depth is missing a lot lot lot". Second cut treats a use case as a WORKING SPEC, not a summary card.
  • /tech-lead/use-cases (mock): cards carry version, MoSCoW priority, phase, story/test counts, and confess their gaps (AC untested, blocking questions). /tech-lead/use-cases/[id]: actors, preconditions, numbered main flow, alternate flows, exception flows (rose), business rules (BR-n), NFRs, open questions (blocking ones pulse in the header), provenance with the client's actual quote, traceability chips into entities and product pages, the fine-tune loop (plain language -> regenerate -> history of what each note changed), and the story tree: points, priority, dependencies, edge cases, UX notes, test cases with steps -> expected and AC-coverage mapping - an acceptance criterion without a covering test is flagged amber, per story and rolled up per use case.
  • /tech-lead/ontology (mock): entities with constraints (required/unique/PII badges), STATE MACHINES rendered as the clay ladder with cross-references to the business rules that gate them, cardinality on every relationship, and used-by chips back into the use cases. PII counted in the header sentence.
  • Nav grew Use cases + Ontology (project param carried on tech-lead links); everything badged "mock · not wired yet". The payroll-SaaS example runs deep on uc-1 deliberately so every level has a real exemplar.
Product21:31 IST

The wizard goes real (ultracode): projects + documents in the DB

Built by a 12-agent ultracode workflow (foundations -> parallel server/client build against a pinned contract -> integrate -> adversarial review -> fix). Review confirmed 5 real defects,…

  • Built by a 12-agent ultracode workflow (foundations -> parallel server/client build against a pinned contract -> integrate -> adversarial review -> fix). Review confirmed 5 real defects, all fixed before landing: the 50mb body limit was arithmetically below the legal 10x15MB submit (raised to 160mb); the download route trusted the 7-day JWT so suspended/demoted users could still fetch bytes (now re-checks users.status+role); createProject was not transactional (partial doc failure left a ghost project while toasting "nothing was written" - now one tx); addProjectDocuments had no UI (new add-documents.tsx in the world page's Documents panel); ?project=12.5 500d.
  • The wizard (/tech-lead/new) now runs on real data: listProjectableDeals (60 deals, Closed Won first, Lost excluded, already-bound disabled), real "attaches itself" bundle counts per selected deal, files to project_documents (bytea, bytes IN the row - Azure Postgres is the one durable store), per-file 15MB / 10 files, audit PROJECT_CREATED / PROJECT_DOCUMENT_ADDED (deal-family icons in the trail), download via authz-gated /tech-lead/documents/[id]. Width fixed: deal grid runs 3 columns on xl.
  • Live-fired as Naman: created SalerySe with a real 80,000-byte upload - stored bytes equal declared size, uploaded_by recorded, audit sentence reads "Naman Srivastava created project \"SalerySe\" from deal SalerySe with 1 document", Documents panel lists it with a working download link.
  • The user's mid-build "Could not create the project" toast was the half-wired state during the workflow (config restart mid-request); both no-file and with-file creates verified after landing.
Product20:40 IST

Project wizard, mock-first

User call: mock pages before plumbing, starting with project creation. /tech-lead/new is a fully clickable 4-step wizard (Deal -> Project -> Documents -> Create) with hardcoded deals and a…

  • User call: mock pages before plumbing, starting with project creation. /tech-lead/new is a fully clickable 4-step wizard (Deal -> Project -> Documents -> Create) with hardcoded deals and a pretend create - amber "mock · not wired yet" badge and honest success copy ("nothing was written").
  • The flow encodes the journey's step 1: a project is born from a signed deal (deals already project-bound show disabled); name prefills from the deal; go-live date with a HARD-DEADLINE toggle (the Christmas/New Year wall from the journey doc); the documents step splits "what you upload" (scope docs, diagrams, mockups) from "what attaches itself" - the deal's emails, recorded calls, meetings, briefs, and promises, which Supernova already holds. That split is the platform's unfair advantage made visible.
  • Entry point: "New project" in the sidebar project switcher. Wizard chrome borrowed from the prospecting wizard (stepper, cards, action bars) - one wizard language app-wide.
  • User also specced the project ontology (use cases -> user stories -> test cases + dev tasks; project data model/ontology; product roles -> pages, some role-less like login) - captured for the next mock, not built yet.
Product17:26 IST

The tech lead gets a world: project-scoped workspace

User's design, verbatim: "a tech lead will have many projects, each project is associated with a deal... a dropdown on top left below logo on sidebar and I should be able to see the world…

  • User's design, verbatim: "a tech lead will have many projects, each project is associated with a deal... a dropdown on top left below logo on sidebar and I should be able to see the world around that project only." Exactly that shipped.
  • `projects` is a first-class table (deal_zoho_id unique, tech_lead_id, status lifecycle), not a live query over deals - the data proved why: Naman's mapped deals are mostly Closed Lost; his WORLD is the 3 real engagements. Seeded 36 projects from tech_lead_map x won/open deals.
  • Sidebar project switcher (project-switcher.tsx) under the logo, TECH_LEAD only; selection rides ?project= (linkable, no hidden state). /tech-lead renders the selected project's world: the unified deal timeline (reusing getDealTimeline - the tech lead is not the Zoho owner, so a new unscoped-but-entitled getProjectWorld reads the deal), Born-from rail, MEDDPICC score summary, Promises (read-only, canRun=false - agents run from the AE side).
  • The "Welcome, Naman / delivery oversight" stub is gone (user call, same rule as the cockpits): the project name IS the title.
  • Verified live as Naman in Chrome: Maintenance Plan (AMC)/OneStopAfrica with 17 emails, switch to ?project=14 -> Comopack ₹6.1L, 7 emails + 1 meeting, different AE, whole world swaps.
Product17:16 IST

Role-home stubs stop greeting and promising

The Tech Lead and Dev homes opened with "Welcome, <name>" under a "delivery oversight" eyebrow: a greeting plus a promise, neither of which is information (the sidebar footer already says…

  • The Tech Lead and Dev homes opened with "Welcome, <name>" under a "delivery oversight" eyebrow: a greeting plus a promise, neither of which is information (the sidebar footer already says who you are; the oversight surface does not exist). Both stubs are now quiet: role eyebrow, "Home", one honest sentence about what will land there and that nothing needs attention. The role-blurb card and the coming-soon card are gone with them.
Product15:36 IST

New brand mark: three shapes that survive 16px

User-designed mark adopted: brand-orange tile (#F97316, deliberately hotter than the app's clay - identity may run hotter than UI, and this hue is the mark's alone), cream burst wedge,…

  • User-designed mark adopted: brand-orange tile (#F97316, deliberately hotter than the app's clay - identity may run hotter than UI, and this hue is the mark's alone), cream burst wedge, espresso collapsed-star circle. Same collapse-and-burst story as the old starburst, told in three flat shapes.
  • Why it wins: the old mark was eight gradient spikes that turned to mush at favicon size; this still reads at 16px (verified side-by-side at 96/40/16 on light + dark). Two tunings from the original draft: pure #000/#fff became the espresso/cream the product already owns, and the translucent confetti square was dropped - it failed the same test that killed Sparkles (invisible small, decoration large).
  • One geometry everywhere: SupernovaMark in src/components/logo.tsx feeds the sidebar, auth split, and landing nav; src/app/icon.svg is the same shapes.
Product12:36 IST

Apollo answers are bought once: the domain-keyed warehouse

Every enrich was re-buying the same Apollo people search - same domain, second SDR, re-run after a partial failure, all metered API calls. Re-enrich even deleted the stored contacts and…

  • Every enrich was re-buying the same Apollo people search - same domain, second SDR, re-run after a partial failure, all metered API calls. Re-enrich even deleted the stored contacts and fetched again.
  • Two tables, not one, because Apollo returns two kinds of data with two lifecycles: apollo_companies is the QUERY (per-domain gate, 180-day TTL - user-set; a zero-people result is a remembered miss, also an answer we paid for) and apollo_people is the PEOPLE (keyed by Apollo's own stable id, upserted in place; email_status VERIFIED/LOCKED records the credit state instead of throwing the locked placeholder away).
  • apolloFindPeople() keeps its exact signature so the wizard code is untouched: warehouse first (works even keyless), API on miss, upsert back, and STALE cache served when Apollo itself errors - an enrich never dies on a vendor hiccup that we already have an old answer for.
  • Verified at the SQL level with seeded rows: fresh gate serves, 181-day row reads stale, cached miss remembered, same-id re-upsert updates 1 row in place. Live fire comes free with the next real enrich.
Product12:34 IST

Hover to glance, click to navigate: click-peek leaves the lists

The lists made a CLICK do a glance's job: clicking a lead/deal/account name cost a server round-trip to open a side pane, when the user wanted either a quick look (should be free) or the…

  • The lists made a CLICK do a glance's job: clicking a lead/deal/account name cost a server round-trip to open a side pane, when the user wanted either a quick look (should be free) or the actual page (should be one click). New contract on all five list surfaces (AE leads, SDR leads, pipeline, accounts, SDR follow-ups): hover shows the instant preview card (LeadHoverCard/DealHoverCard, zero fetches — the row already holds name/status/email/phone, now selected into getLeadBook so the card costs nothing) and click opens the real page (lead hub / deal hub / account hub). Pipeline and accounts skip the hover card — their cards/rows already display everything it would say — and go straight to direct navigation.
  • Peek survives only where it is irreplaceable: the three hub timelines (deal + both lead hubs), because an email, note, or call has no page of its own — the pinned pane is the only reader for its full body. Everywhere else the ?peek= plumbing (params, peekHref, closeHref, grid split) is deleted.
  • LeadHoverCard gained an href prop so the AE side opens /ae/leads/<id> instead of the hardcoded SDR route.
Product12:25 IST

AE leads: the company column links the account hub, not a name search

Clicking a company on /ae/leads always went to /ae/accounts?q=<name> — a search page — even when the CRM has the actual account. getLeadBook now resolves companyAccountId with the same…

  • Clicking a company on /ae/leads always went to /ae/accounts?q=<name> — a search page — even when the CRM has the actual account. getLeadBook now resolves companyAccountId with the same matcher the lead hub already used (exact name, then prefix both ways, shortest match wins), and the column links /ae/accounts/<id> when it resolves (~44% do), falling back to the search only when there is genuinely no account (e.g. QubeHealth-Pay, which has none — the user's example account URL turned out to be Uni Seoul's).
Product09:59 IST

Live-fire verified; workspace resume; the companies-view click stops shape-shifting

Both push paths proven against the real Zoho (driven in Chrome as Priyadarshini): the dupe path put 15 decision-maker contacts under the 4 existing accounts (Zeta, Cashfree, M2P, IDfy) with…

  • Both push paths proven against the real Zoho (driven in Chrome as Priyadarshini): the dupe path put 15 decision-maker contacts under the 4 existing accounts (Zeta, Cashfree, M2P, IDfy) with zero duplicate accounts or leads; the create path made Truffle AI a new Account + Lead + 2 Contacts, owner-mapped into her book and visible there INSTANTLY via the staging mirror. Refine verified live too: "also include AI infra from Pune/Hyderabad" appended exactly 1 non-duplicate company. Audit shows PROSPECTS_PUSHED SUCCESS with honest counts.
  • Workspace resume (getLatestWorkspace, no-args on purpose — WHOSE workspace comes from the session, never a client argument): /sdr/prospecting reloads the rep's latest live search and reopens at the right step. Found the hard way — navigating to Leads mid-wizard stranded 4 enriched companies in the DB with no way back.
  • The companies-view click (/sdr/leads?view=companies) used to dump the company name into the fuzzy, org-wide q search — the view silently shape-shifted and could surface other reps' leads (user: "it does something else"). Company cards now link ?co=<name>: an EXACT, book-scoped company filter with a labelled chip and a × back to the grid. q still searches org-wide; co answers the different question the card was asking.
Product09:51 IST

MEDDPICC gets its number

User ask: "MEDDPICC, need to calculate score - where is it". It was Deal Check (agent-qualifier.ts) grading 8 dimensions with evidence but never producing the score the sales process doc…

  • User ask: "MEDDPICC, need to calculate score - where is it". It was Deal Check (agent-qualifier.ts) grading 8 dimensions with evidence but never producing the score the sales process doc calls for ("score against MEDDPICC", the SQL bar) - the panel only showed a coverage count ("5/8 read").
  • New pure module src/lib/ai/meddpicc.ts (keys/types moved out of the server-only agent so the client panel can share them): meddpiccScore() = strong 1 / partial ½ / weak ¼ / unknown 0, averaged to 0-100; meddpiccBand() = ≥70 well qualified / ≥40 developing / <40 thin, one vocabulary for every surface. Derived, never stored - the number cannot drift from the evidence that earned it.
  • The deal hub's qualifier panel now leads with the score: big number, band-toned progress bar, band word. Validated against the one stored read: grades pwpw·upps -> 44/100 "developing", agreeing with the agent's own headline ("early-stage exploration, strong incumbent entrenchment").
Product09:44 IST

Every LLM rupee has a name

User asked why "(unattributed)" exists on /admin/ai and how to make sure it never grows. Answer shipped in three parts:

  • User asked why "(unattributed)" exists on /admin/ai and how to make sure it never grows. Answer shipped in three parts:
  • Backfilled the provable - 7 legacy null calls had a chain (llm_calls.trace_id -> agent_run_summary -> audit_events.actor_id); their ₹20 went back to the people who ran them. The remaining 60 calls (₹129, 13-15 Jul) have no provable actor and are labelled "(unattributed · pre-16-Jul history)" rather than guessed at - that row can never grow.
  • Named the system lane - llm_calls.system_source (targeted ALTER) + asSystemRun(label, fn) in telemetry: sessionless-but-legitimate runs (the evals route) ledger as "System · eval" instead of null.
  • Made the gap impossible - withLlm now THROWS in development when a run has neither a user nor a system label (loud console.error in prod; telemetry never takes the product down). A future agent that forgets attribution fails at the desk it was written at.
Product09:21 IST

Audit trail: the small things

The "from Chrome … · localhost" clause left every row - it repeated the same dev fingerprint a hundred times. It now appears only where it IS the story (sign-ins, password events,…

  • The "from Chrome … · localhost" clause left every row - it repeated the same dev fingerprint a hundred times. It now appears only where it IS the story (sign-ins, password events, failures); the expanded Request-context grid keeps the rest. Referer shows the path, not the origin.
  • Consecutive repeats collapse - six identical "generated a follow-up draft for Abhi Gawri" rows are one fact: one row, a ×N chip, and "N times, 19:48-19:52" in the subline. Failures never collapse; day counts still count real events.
  • The header sentence stopped being a riddle - "from 12 actors · 12 by agents" -> "9 people, 12 performed by agents".
  • The action filter speaks English - "Agent drafted reply", not AGENT_DRAFTED_REPLY (values stay enums).
  • AI_BUDGET_SET reads high-signal (governance), agentRunId renders as a #reference in diffs.
Product08:58 IST

One entity colour language; sparkles and violet are gone

src/lib/entity-tone.ts (new) - the entity colour map, extracted from the audit trail and made THE source for every surface (DESIGN 22). Six page-local KIND_TINT copies (both inboxes, both…

  • `src/lib/entity-tone.ts` (new) - the entity colour map, extracted from the audit trail and made THE source for every surface (DESIGN 22). Six page-local KIND_TINT copies (both inboxes, both lead hubs, the deal hub, the SDR day card, the peek pane, connect-records) disagreed with the tokens about the same entity - task was amber in the ledger and violet on the timelines, email rust vs indigo, and the peek pane gave four entities two colours (lead=contact=blue, account=task=violet). All deleted; kindTint(kind) everywhere.
  • The audit trail lost its "agent" colour - an agent is an actor (actorKind/actorLabel), not an entity, so agent events are coloured by what they TOUCHED (a drafted reply is email-rust) and the raw violet-500 family is gone. Signal hierarchy fixed too: every AGENT_* was blanket-high, so six identical follow-up-draft rows outshouted a role change; drafts/briefs/research are now quiet ledger lines and only AGENT_ENRICHED (writes to Zoho) stays loud. Per-action icons for lead/deal events (RouteIcon stage moves, BadgeCheck/BadgeX qualify, StickyNote notes, Flag next-step).
  • Sparkles is dead on app surfaces (user: "sparkles as an icon and purple as a color are both bad ideas") - 22 sites now name the work: PenLine drafts (composers, deal-actions, Copilot draft), BookOpen research (researcher-panel, enrich-panel, prospecting Enrich step), Newspaper for Daily Brief, Handshake for Promise Tracker, ClipboardCheck for Deal Check, Quote for call recaps, SearchCheck for evidence-based cleanup, Lightbulb for suggestions. Only the /design badge demo keeps one.
  • Violet/indigo purged app-wide with in-palette meanings: Nurturing teal, QFL joins the qualified emerald family, Negotiation + accounts-Won wear clay (closest to money), changelog Data sky, speaker-3 emerald, funnel gates are the clay-on-ink ladder (was emerald/violet), and the dead violet/indigo dark-mode shims left globals.css. Prospecting wizard also lost its max-w-3xl/max-w-2xl caps (DESIGN 20).

15

Jul 2026

Wednesday

3 shipped

Product00:47 IST

Bulk blasts are not conversations (the SDR inbox was inventing threads)

Walking the SDR inbox as Priyadarshini, the top "conversation" was Anand Singh at Probus: 15 emails, newest a tender notice from nprocure@ncode.in, labelled "with this lead". The label was…

Walking the SDR inbox as Priyadarshini, the top "conversation" was Anand Singh at Probus: 15 emails, newest a tender notice from nprocure@ncode.in, labelled "with this lead". The label was technically true and semantically garbage: anand@probus.io is on that tender blast, along with 387 other addresses. He is on the same mass mailing list as our mailbox; he has never corresponded with us at all. The mirror holds 1,260 such blasts from that one sender.

The damage was real: fake threads at the top of the inbox (ranked by "last email"), inflated conversation counts and thread lengths, a "with this lead" claim that never happened, and the same pollution feeding the Briefer's waiting on your reply.

Fix, central so every surface inherits it (BULK_RECIPIENT_LIMIT = 12 in lib/graph/match.ts): the RECIPIENT-side match branches now exclude messages with more than 12 recipients. A real thread has a handful of people on it; a 400-address blast is a broadcast. The FROM side is untouched: if the lead personally sent it, it is real no matter how many were copied. Same guard in getInbox's ranking CTE.

Verified: Anand's thread 31 matched -> 0 (the entire "conversation" was fake); the inbox now opens on a genuine thread (Deepak Mishra at Chakr, 9 emails + 2 meetings + a recorded call). The filter is also faster than no filter (49ms vs 250ms) because it prunes rows before the join, so the 29ms deal-timeline budget is safe.

Also this pass: SDR nav order put My Day 5th, below the lists; the cockpit comes first for every role now (the AE's Desk sets the shape).

Product00:29 IST

The uniformity pass: one component per job, every pulse a doorway, no width caps

The all-pages walk turned up the same disease three times: a shared idea reimplemented per page until the design language drifts. Fixed at the root, then codified as DESIGN principles 19-21.

The all-pages walk turned up the same disease three times: a shared idea reimplemented per page until the design language drifts. Fixed at the root, then codified as DESIGN principles 19-21.

  • AttnItem existed 8 times with drifting shapes (some with counts, one with children, three prop signatures). Now one src/components/attn-item.tsx, and it takes an `href`: every attention item is a link to the surface where the problem gets fixed (past-due -> pipeline, no-next-step -> tasks, overdue -> follow-ups, unlinked call -> the recording), with the arrow affordance on hover. Plain text only where there is genuinely nowhere to go (read-only banners). A count you cannot act on is noise.
  • Settings was five near-identical pages that had drifted (two eyebrow treatments, two card classes, a stray em dash, full-width password fields). Now one AccountSettings; admin composes it and passes Integrations as aside. admin 108->61 lines, the other four ~78 -> 10 each.
  • Width caps killed: max-w-3xl/2xl/xl/[64ch] on cards, headlines, and page copy. The app is a cockpit, not an article: a cap strands the viewport and breaks alignment (one card stops at 48rem, the next runs full, nothing shares an edge). Control widths (dialogs, the password form) stay.
  • Em-dash sweep across every rendered string (page copy, toasts, pulse descriptions, metadata).
  • Also: the meeting-event page strips Teams-invite boilerplate from Notes; the email reader got its universe rail; enrich-panel imported Linkedin from lucide-react (which dropped brand glyphs) and broke tsc tree-wide, swapped to Link2.

Verified: all 13 AE + public routes return 200 with no runtime errors; pipeline/lead-hub pulses confirmed linking in the browser.

Product00:10 IST

The email reader gets its universe

A message is never just a message. /ae/emails/[id] now carries a universe rail resolved from the CRM graph (src/lib/graph/email-universe.ts): Part of (the deals the participants belong to,…

A message is never just a message. /ae/emails/[id] now carries a universe rail resolved from the CRM graph (src/lib/graph/email-universe.ts): Part of (the deals the participants belong to, with stage dot + amount, the account, and each sender/recipient resolved to their contact or lead, all linked), Conversation (the rest of the Graph conversation_id thread, each message opening in the reader), and With these people (recorded calls + calendar meetings matched by participant email). Honest empty state when nobody on the message is in the book (newsletters). Verified on the TruBoard thread: deal, account, contact, 2 prior messages, and 5 meetings/calls, every node a link. Also from the page-by-page loop: the meeting-event page strips Teams-invite boilerplate from Notes.

14

Jul 2026

Tuesday

15 shipped

Product21:45 IST

The deep test tier: correctness, not rendering

"Very detailed test cases, not surface-level smoke" — the new tests/e2e/deep.spec.ts (17 tests) asserts three things smoke tests can't:

"Very detailed test cases, not surface-level smoke" — the new tests/e2e/deep.spec.ts (17 tests) asserts three things smoke tests can't:

  • UI == DB, exactly. Every checked value is computed from the database through the app's own pure formatters and asserted verbatim in the UI: the deal hub's ₹ amount/account/contact; the pipeline board's card count AND header total vs the owner's SQL aggregate; a lead search returning the exact DB row; the MEDDPICC panel mirroring `deal_qualifications` dimension-by-dimension (all 8 scores + the "N/8 read" chip); the Promises "N open" chip; the recording Signals tab badge vs the insights count; meeting attendee counts; the admin AI ledger's per-agent cost vs the SQL sum; the audit trail showing the actual newest event's summary; the Deal Check receipt ₹ vs agent_run_summary.
  • Permission boundaries probed. SDR bounced out of /admin and /ae (URL asserted); an AE opening ANOTHER rep's deal sees the read-only banner and no action buttons; a nonexistent deal 404s; a garbage ?peek= token can't break the page; unauthenticated hits redirect to sign-in.
  • Business rules. Briefing storage idempotency (two desk visits → still exactly one row per user/day).

One real locator lesson: "Competition"/"Champion" also appear inside timeline email previews (also lis), so panel assertions must scope to the section — the kind of false-positive smoke tests never notice. Suite total: 72 tests (37 smoke + 13 journeys + 5 mutations + 17 deep), 71 passed + 1 transient-flaky-passed-on-retry, ~52s.

Product21:02 IST

Mutation journeys: the flows that change state, tested + restored

The missing half of journey coverage. tests/e2e/mutations.spec.ts (serial) drives the five state-changing flows end-to-end and asserts both the UI acknowledgement (the toast — principle 13)…

The missing half of journey coverage. tests/e2e/mutations.spec.ts (serial) drives the five state-changing flows end-to-end and asserts both the UI acknowledgement (the toast — principle 13) and the actual write, then deletes exactly what it created (surgical predicates: action + entity + after-timestamp — tests/e2e/db.ts with waitForRow/cleanAudit helpers):

  1. 01Log a call on a dealCALL_LOGGED PREVIEW audit + close-on-activity chain completion → completions + audit restored.
  2. 02SAL handoff → synthetic SAL_HANDOFF_SENT seeded → appears in /ae/handoffs → Accept → SAL_ACCEPTED → both events + notifications removed.
  3. 03Mark a promise kept → REAL commitments status flip + audit → status restored. (First version leaked: it pre-picked a commitment by id but the panel's display order differs, so a different row flipped — caught by the timeout, leak repaired, test now tracks whichever row actually changed.)
  4. 04Link an unlinked recording to its suggested deal → real proshort_recording_links row → deleted.
  5. 05SDR logs activity on a lead → PREVIEW audit + chain completion → restored.

Full suite: 37 route smokes + 13 read journeys + 5 mutation journeys = 55 tests, ~45s, zero pollution (verified: no e2e audit rows, flips, completions, or links left behind). One retry configured — the live dev server can 500 transiently while recompiling under 4-worker load (2 flaky-passed-on-retry, 0 hard failures). Still open for full story coverage: admin user-management mutations (invite/role/suspend — need a disposable user fixture), settings/change-password (would rotate the shared dev password — deliberately excluded), and the write-back journeys (blocked on grants).

Product20:53 IST

The highlight pair + material upgrades: vanilla × clay-hot, alpha hairlines, lit card edges

Three token-level upgrades from the day's design studies, all propagating system-wide:

Three token-level upgrades from the day's design studies, all propagating system-wide:

  • The highlight pair (burnt-orange × vanilla study): new --vanilla (warm surface for the FEW hot moments; espresso-warm equivalent in dark) and --clay-hot (the interaction step above clay). Applied: the briefing's "Your one move" band sits on vanilla with a rest shadow; the primary button goes clay → clay-hot on hover/active. Never ambient — the pair works because it is scarce.
  • Alpha hairlines (dealflow mechanic): --border/--border-strong are now translucent ink (rgb(33 30 24 / .12) light, cream-alpha dark), so the same divider token reads correctly on any surface it crosses.
  • Lit top edge: dark --elev-rest/raised gain an inset cream hairline (4-5%), the "physically lit" card top that espresso surfaces were missing.
Product20:16 IST

Changelog v3: the story claim + the Product Panorama (revq.io study)

Studied revq.io's marketing pages in detail (home, changelog, how-it-works) and applied the two strongest devices to /changelog. The headline became a story claim, "How Supernova learned…

Studied revq.io's marketing pages in detail (home, changelog, how-it-works) and applied the two strongest devices to /changelog. The headline became a story claim, "How Supernova learned the job", with the editorial bar stated in the subcopy: what became POSSIBLE in each stretch, not a list of tweaks. And before the timeline, a Product Panorama: six cells, one operating loop (Brief / Draft / Qualify / Prep / Track / Audit), each with a mono number, a real status tag (every-desk-daily, critic-checked, 8-dimensions, pre-call, with-provenance, propose-only) and a one-liner. The reader sees what the product IS today before reading how it got here. Earlier from the same study: the landing hero's tightened display register (leading 1.02, tracking -0.03em) and the honesty tag on the walkthrough card.

Product20:12 IST

LLM cost + telemetry infrastructure: the ledger

Cost was estimated with hardcoded constants duplicated in five files, and tool-loop runs undercounted tokens (toolRunner's final.usage is the LAST call's usage, not the loop's sum — proven…

Cost was estimated with hardcoded constants duplicated in five files, and tool-loop runs undercounted tokens (toolRunner's final.usage is the LAST call's usage, not the loop's sum — proven live: a Deal Check run reported 10.9k tokens; the ledger counted 15.7k). Built it properly:

  • `llm_pricing` — rates per model (input/output/cache-write/cache-read per Mtok); cost computed in ONE place, re-computable.
  • `llm_calls` — the ground-truth ledger: one row per API call (trace_id, agent, purpose loop|critic|repair|generate|extract|reemit, model, all four token kinds incl. prompt-cache, latency, stop reason, status, cost). Every rollup — per run/agent/user/entity/day/total — is a query over this.
  • Auto-instrumentation (src/lib/ai/telemetry.ts) — instrument() patches the Anthropic client once in getAnthropic() so every messages.create (incl. the ones toolRunner makes internally) self-logs; withLlm() carries agent/entity context via AsyncLocalStorage, t.mark() tags phases, t.totals() is the real sum. Fixed a race: ctx updates now complete before create() returns (the critic call was missing from totals).
  • All 5 agents rewired (Deal Check, Meeting Prep, Email Drafter, Promise Tracker, Daily Brief): hardcoded COST consts deleted; run summaries now write ledger-true tokens/cost + trace_id (runs join to their calls).
  • `/admin/ai` — the spend surface: today/7d/all-time in the briefing sentence, per-agent table (runs, verdicts, calls, tokens, avg latency, cost), the raw recent-calls ledger. Nav item "AI usage".

Verified: one Deal Check run = 5 ledger rows (4 loop turns + critic, 1.9s→13.3s each, per-call cost); regenerated all 17 briefings — 17/17 runs' totals equal the ledger exactly. Ledger stands at 28 calls ≈ ₹10.74.

Product19:29 IST

Goldenflitch staff are app users (decision: yes)

Executed the standing decision. scripts/add-goldenflitch-users.mjs (idempotent, every mutation audited as SYSTEM): sourced the GF tenant's own directory (53 mailboxes), skipped 9 shared…

Executed the standing decision. scripts/add-goldenflitch-users.mjs (idempotent, every mutation audited as SYSTEM): sourced the GF tenant's own directory (53 mailboxes), skipped 9 shared service inboxes + 2 already-existing people (Vinay = the CEO; Shraddha Sharma name-match flagged, not duplicated), created 39 users (role DEV, department "Goldenflitch", no password — invite-link to activate), and repaired the 3 placeholder identities: Harshad → harshad@goldenflitch.com (Harshad Bhardwaj), Dhruv Raj → dhruv@, Abhishek Rana → abhishek@. Then re-matched mappings: graph 50 unmatched → 10 (all service inboxes), zoho +2.

The repair is also a data-quality win: mailbox-scoped surfaces (briefing, meetings, inbox) key off user.email, and Harshad's real mailbox holds 7,444 messages vs the 585 his guessed @antino.com address saw. Nuance parked in TASKS: he genuinely has both mailboxes — multi-mailbox users would need a user_email_aliases concept someday.

Product19:26 IST

The app goes espresso

The whole signed-in app now runs the dark espresso theme, the landing's register: AppShell carries .app-theme dark text-foreground, the sonner toaster pins dark, and a tiny DarkBody client…

The whole signed-in app now runs the dark espresso theme, the landing's register: AppShell carries .app-theme dark text-foreground, the sonner toaster pins dark, and a tiny DarkBody client helper mirrors the scope onto <body> so Radix PORTALS (dialogs, dropdowns, tooltips, which mount outside the shell) resolve dark tokens too, portals only open after interaction so there is never a flash. The token system carried the surfaces: Desk, briefing, tables, sidebar all read correctly on first flip thanks to the /design contrast pass landing in the reusables first.

Product19:03 IST

Accounts table: from a number ledger to a state view

The accounts list was a flat ledger (Deals / Open / Pipeline / Won / Last activity), numbers you had to decode, not a signal you could scan. Reframed it around the one question that…

The accounts list was a flat ledger (Deals / Open / Pipeline / Won / Last activity), numbers you had to decode, not a signal you could scan. Reframed it around the one question that matters: "which accounts should I act on, and why?" Every account now carries a State derived from its deals + activity, computed in code: Live (open deal moving, recent), At risk (deals past close or no next step), Cooling (no touch 60d+), Won (closed, expansion target), Dormant (nothing open). Columns are now Account · State · In flight · Open pipeline · Last touch · Won, where "In flight" shows the actual biggest open deal + its stage ("centaurihs · Qualification") instead of a meaningless "Open: 1", the State chip has a hover reason, and a stale Last-touch goes rose. getAccounts gained the top-open-deal (lateral join) + slipping / no-next-step signals. Still ordered by recency. Next candidate: a State filter so an AE can jump straight to At-risk.

Product18:56 IST

Accounts sorted by recency

/ae/accounts now orders by last activity — the account's own last_activity_time or the freshest of its deals', whichever is later — instead of open pipeline, with a visible Last activity…

/ae/accounts now orders by last activity — the account's own last_activity_time or the freshest of its deals', whichever is later — instead of open pipeline, with a visible Last activity column so the ordering is legible (a sort you can't see is a sort you don't trust). Header sentence updated to say so; pipeline stays as the tiebreak. Also gave the list page its missing PAGES.md row.

Product18:13 IST

AE Follow-ups page (the missing backlog home)

AEs had no follow-ups surface, so Anisha's 174 overdue had nowhere to live. Honest finding first: her tasks are recent (12d), contact-linked lead follow-ups with NO done-evidence (1 of 174…

AEs had no follow-ups surface, so Anisha's 174 overdue had nowhere to live. Honest finding first: her tasks are recent (12d), contact-linked lead follow-ups with NO done-evidence (1 of 174 detectably clearable, 0 stale), so an evidence-based cleanup panel would be empty and useless for AEs. The right surface is a place to SEE and WORK them, not a magic cleaner. Built /ae/tasks (getAeOverdueTasks resolves each to its linked deal/contact; AeTaskList client): the overdue list with per-task Done (writes the completion overlay), a Clear all bulk, and each row links to its contact/deal to go work it. The cleanup panel still shows on top when it does find safe-to-clear buckets (AEs with closed-deal tasks). Nav item added. Close-on-activity remains the real fix, working the lead clears the chain.

Product17:55 IST

Title-case names (initcap at the data layer)

Zoho names are entered any which way ("bhargav krishna", "NAGA KUMAR"). Wrapped the 12 lead/contact display-name expressions across sdr/ae/peek queries in Postgres initcap(), so every…

Zoho names are entered any which way ("bhargav krishna", "NAGA KUMAR"). Wrapped the 12 lead/contact display-name expressions across sdr/ae/peek queries in Postgres initcap(), so every render (lists, hub header, breadcrumb, hover cards, timeline senders, briefing) shows title case at once. Idempotent for already-correct names; only intentional mid-word caps (McDonald) flatten, which is rare and the source is usually lowercase there anyway. Verified: "bhargav krishna" → "Bhargav Krishna" in header + breadcrumb + timeline.

Product17:50 IST

Timeline email previews: strip the chrome, keep the message

The lead/deal/inbox timelines dumped raw body_preview: Outlook's "You don't often get email from…" safety banner, "Get Outlook for iOS" footers, long ____ separator runs, and forwarded…

The lead/deal/inbox timelines dumped raw body_preview: Outlook's "You don't often get email from…" safety banner, "Get Outlook for iOS" footers, long ____ separator runs, and forwarded "From: … Sent: …" / "On <date> … wrote:" tails. Added cleanEmailPreview (src/lib/format/text.ts) that runs after htmlToText and removes all of that, leaving just the human sentence, and wired it into getLeadTimeline + getDealTimeline. E.g. a preview that read "You don't often get email from bhargav… Learn why this is important Running 5 minutes late… Get Outlook for iOS ___ From: Naga…" now reads "Running 5 minutes late. Joining soon. Regards, Bhargav." The full email reader/peek keeps the whole body (a deliberate read, not a preview).

Product15:35 IST

"Part of": explicit call→CRM linkage + in-place connect (a real write)

The recording page never said whether a call was part of a deal — mkccuiq0 was auto-linked to its contact while the matching centaurihs deal sat unlinked, silently. Now:

The recording page never said whether a call was part of a deal — mkccuiq0 was auto-linked to its contact while the matching centaurihs deal sat unlinked, silently. Now:

  • "Part of" states linkage per kind — Deal / Lead·Contact / Account each show "✓ name" (clay tile, click-through, "· Linked by you" when manual) or an honest "no deal" with an attention Pulse (the deal is the one that matters for pipeline). Header pulse: "not part of a deal — connect it →".
  • In-place connectsuggestLinks (consolidated with the parallel session's version into one superset query) proposes candidates from the call's external attendees (email → lead/contact → their deals/account, company domain) and the prospect company name, each with the reason shown ("attendee is the deal contact"). One click links it; a debounced search over deals/leads/accounts covers everything else.
  • A REAL write, todayproshort_recording_links is our own table (source='manual', method='user'), no Zoho scope needed; every link logs RECORDING_LINKED (SUCCESS, not preview) to the audit spine; toast + instant refresh (principle 13).

Verified end-to-end on mkccuiq0: suggestions surfaced the centaurihs deal + account → one click → "Part of: centaurihs · Deal · Qualification · Linked by you", pulse cleared, suggestion consumed.

Product14:20 IST

Close-on-activity closes the CHAIN, not one task

Follow-ups come as chains ("Followup 1 → 2 → …"); one touch should clear the whole pending chain, not a single reminder. Reworked completeChainForLead/completeChainForDeal (was…

Follow-ups come as chains ("Followup 1 → 2 → …"); one touch should clear the whole pending chain, not a single reminder. Reworked completeChainForLead/completeChainForDeal (was completeNext*) to complete every open matched task due on/before today in one action — one audit event, closedCount reported to the UI ("N follow-ups marked done ✓"). Strictly-future follow-ups (a genuine planned next step) are deliberately left open. Tested on a real 4-task chain (Siddharth / Godfrey Phillips): 2 due + 2 future → an activity closes the 2 due, leaves the 2 future. All four callers (SDR/AE inbox, lead hub, deal hub) show the count.

Product13:44 IST

Close-on-activity everywhere + overlay-consistent counts

Extended close-on-activity across every work surface + made the completion overlay consistent app-wide:

Extended close-on-activity across every work surface + made the completion overlay consistent app-wide:

  • Inbox — SDR inbox (lead) and AE inbox (deal) Send / Log call / Book now log the touch AND complete the record's next follow-up (completeNextForLead / completeNextForDeal), toasting the closed step + refreshing.
  • AE lead hub — added the (now role-flexible) LeadActions rail (actions use getCurrentUser + role-aware links, still RACI-gated), so AEs get the same close-on-activity + manual close their leads' follow-ups.
  • Deal hublogCall + new logDealEmail/logDealMeeting are close-on-activity; the rail's manual close now persists.
  • Overlay consistency — every overdue-task COUNT now excludes task_completions: SDR summary + nav pulse, AE desk, the SLA board, and the briefing (ic overdue + topOverdue + team per-rep), plus getLeadTasks/getDealTasks. Clear a task once and it drops out of every count, not just the list. Verified lead + deal close paths against the DB.

13

Jul 2026

Monday

3 shipped

Product19:36 IST

#14 (start): StubButton — honest "SOON" states, no fake buttons

antino3's rule: no button that toasts a lie. Built src/components/stub-button.tsx — a visibly-disabled control with a "SOON" pill + a tooltip saying what it WILL do and why it's off (an…

antino3's rule: no button that toasts a lie. Built src/components/stub-button.tsx — a visibly-disabled control with a "SOON" pill + a tooltip saying what it WILL do and why it's off (an unbuilt agent or an ungranted write scope). First applied to the deal-rail Draft follow-up button (that's the blocked Writer agent — it was faking a "Follow-up drafted" toast); now it reads honestly with the reason (needs a valid ANTHROPIC_API_KEY). More fake buttons to convert as the audit surfaces them.

Product07:14 IST

Meeting detail page (every meeting is clickable; the "unlinked" pulse now leads somewhere)

/ae/meetings flagged "unlinked" meetings (external attendee not matched to a lead) with a pulse that asked for an action — but only recorded meetings were clickable (to the recording…

/ae/meetings flagged "unlinked" meetings (external attendee not matched to a lead) with a pulse that asked for an action — but only recorded meetings were clickable (to the recording detail); the rest were dead ends with no detail page. Built /ae/meetings/event/[id] for calendar meetings (graph events): getMeetingEvent(graphId, mailbox) → title, time, online/location, organizer, attendees (response dots), notes, and a link to the recording detail if one lines up. Crucially, an External guests panel lists the non-Antino attendees each with a Find link (/ae/leads?q=<name>) — the actionable path the unlinked pulse promised. Wired the AE meetings chips so every meeting links: recorded → recording detail, else → this event page. Verified getMeetingEvent on "Expat Orbit<>Antino" (3 attendees).

Product07:11 IST

Process spine #8 complete: the SAL handoff seam (receive side)

Closed the loop on "the most important seam." An SDR qualifying a lead already logs SAL_HANDOFF_SENT (#9); built the receive side — /ae/handoffs, a team-wide SAL inbox. getPendingHandoffs…

Closed the loop on "the most important seam." An SDR qualifying a lead already logs SAL_HANDOFF_SENT (#9); built the receive side/ae/handoffs, a team-wide SAL inbox. getPendingHandoffs derives the queue straight from the audit spine: SAL_HANDOFF_SENT events with no later SAL_ACCEPTED/SAL_REJECTED for that lead, joined to the lead + the SDR who sent it. Each row → Accept (logs SAL_ACCEPTED, notifies the SDR "picked up") or Reject with a reason (logs SAL_REJECTED, warning-severity notify back). Both are RACI-safe AE server actions through recordAuditAndNotify (PREVIEW), and accepting/rejecting drops the lead out of the queue via the NOT EXISTS. Added a Handoffs nav item (inbox icon) + breadcrumb. Verified the full flow against the DB with a temp handoff event (queue populated → cleaned up). With the SLA engine + Command Board hygiene row (below), #8 is done — the process spine now has stage-gates, the lead status machine, the SLA engine, and the SDR→AE handoff seam end to end. (Optional Command Board polish remains: per-rep SLA columns, forecast categories.)

12

Jul 2026

Sunday

15 shipped

Product20:24 IST

Process spine #8: the SLA engine + Command Board hygiene row

Started #8 (finish the process spine). Built the SLA / hygiene engine (src/lib/sla/queries.ts, getSlaBoard) — the manager's early-warning signals computed live from the mirror: untouched…

Started #8 (finish the process spine). Built the SLA / hygiene engine (src/lib/sla/queries.ts, getSlaBoard) — the manager's early-warning signals computed live from the mirror: untouched leads (open/new with no activity ever — a speed-to-lead breach; the data has 1,606, oldest 123 days), no-next-step deals (40), stale deals (30d+ inactive, 19), overdue tasks (19,193), and the SAL handoff funnel from the audit spine (SAL_HANDOFF_SENTSAL_ACCEPTED/REJECTED, with an acceptance rate). Surfaced as an "SLA & hygiene" row at the top of the manager Command Board (/admin/command), tone-colored (rose = breach, amber = attention). Verified the counts directly against the DB. Remaining #8: the AE-side SAL accept/reject queue (an SDR qualifying already logs the handoff; the receive side is next) and the per-rep SLA scoreboard columns.

Product19:53 IST

Deal hub: surface all the data we hold (#18)

The deal hub showed ~8 fields; the mirror holds far more. Enriched getDeal + DealFull to fetch every meaningful column and surfaced them on the hub: a prominent Next step callout (the field…

The deal hub showed ~8 fields; the mirror holds far more. Enriched getDeal + DealFull to fetch every meaningful column and surfaced them on the hub: a prominent Next step callout (the field we just added to the mirror), Expected revenue, Pipeline, Lead source, Investment type, Domain, Tech lead, Company location, Sales cycle (shown only when populated — honest empty states, since per DATA-REALITY.md many custom fields are sparse), and the Estimation sheet as a real link. Also added contactId so the primary contact can link to its hub. Contact hub enriched next: getContact/ContactFull now fetch + surface a Details card — department, reports-to (linked to that contact's hub), source, sourced-by, company type, company industry, projected 1-year revenue, mobile — all shown only when populated. Deal + contact done; account/lead/teammate follow in the #18 sweep. (Next_Step/Expected_Revenue are empty until the next admin-gated Zoho sync populates them from the new manifest fields.)

Product17:59 IST

Process spine v1: deal stage-gates (from the antino3 study)

Studied the predecessor project C:\Users\vaibh\projects\antino3 (same product, Java stack, further along on process/agents/ontology) with four parallel researchers and turned it into a gap…

Studied the predecessor project C:\Users\vaibh\projects\antino3 (same product, Java stack, further along on process/agents/ontology) with four parallel researchers and turned it into a gap program (session tasks #7-14). First fill: the deal process spine. antino3's lesson — "we built artifacts, not process." Zoho has a flat 3-stage picklist; src/lib/process/deal-stages.ts overlays the enterprise funnel S1 Qualify → S2 Solution & Validation → S3 Proposal → S4 Negotiate and inserts the stage the data is missing — S2, the Tech Lead / pre-sales home. Each stage has exit criteria evaluated against the deal's real signals (has contact, next step, meetings, emails, amount, close date, probability); a deal can't credibly advance until its gate is met. StageGate (src/components/ae/stage-gate.tsx) renders the funnel (done/current/future, S2 marked app-native with a sparkle) + the criteria checklist + an Advance button that's enabled only when the gate passes (preview toast until write-back). Wired into the deal hub above MEDDPICC. Verified on "Defect Detection on Steel Sheets" (Proposal): 3/3 met, Qualify+S2 passed, "Advance to Negotiate" live.

Lead status machine (src/lib/process/lead-stages.ts + LeadStatusGate): the symmetric piece for leads. Normalizes Zoho's free-text lead_status mess (Open/New/Working/Qualified-Hot·Warm·Cool/Nurturing/Recycled/Lost·Junk·Not-Qualified/Disqualified) into clean phases — a forward funnel New → Working → Qualified plus off-ramps (Nurturing/Recycled/Lost). Exit criteria per forward step (first touch, discovery, temperature, next step) computed from the lead's timeline signals; advance gated + preview. Wired into the AE lead hub. Verified on Pramod Sao (Working, 2/3 — discovery + interest met, next step missing → locked). Hygiene (open deal, no next step = red) already lives on the pipeline/desk. Process spine core done (task #7).

Product17:50 IST

Detail pane (peek) — v3: connect it across the list surfaces

"Connect it everywhere." Turned the main list pages into master-detail: clicking an entity opens its peek pane beside the list (?peek=<kind>:<id>, scroll-preserving) instead of navigating,…

"Connect it everywhere." Turned the main list pages into master-detail: clicking an entity opens its peek pane beside the list (?peek=<kind>:<id>, scroll-preserving) instead of navigating, with the ↗ affordance on every openable name/card and a selected-row/card highlight; the pane's own ↗ still opens the full page, and sort/filter/paginate drop ?peek so they close the pane. Done on: AE leads (lead pane), pipeline board (deal pane — cards reflow to 1fr + 360px, emerald ring on the selected card), accounts (account pane, portfolio stats), SDR leads People view (lead pane). Each reuses the shared PeekPane + getPeek data layer, so a page is ~15 lines: parse ?peek, a peekHref/closeHref, wrap the list in a grid, and point row links at the peek. Verified as Himanshu: AE leads → Anurag Rudra pane; pipeline → AI Use Cases deal pane; accounts → Ministry of Cooperation portfolio pane — all in-place, no navigation. Remaining surfaces (SDR tasks chains, inbox, meetings calendar, Desk lists, hover-card "Open") are follow-ups — the framework makes each a quick add. Tracked in the session task list.

Product17:42 IST

Email reader + attachments, and Inter as the app font

Email reader (/ae/emails/[id]) — the last ontological object. The peek pane already previews an email's stripped-plain body; this is the full read: src/lib/graph/attachments.ts…

Email reader (/ae/emails/[id]) — the last ontological object. The peek pane already previews an email's stripped-plain body; this is the full read: src/lib/graph/attachments.ts (getEmailFull → subject, from, to/cc, the real body_content + type, and attachments) renders the HTML body in a sandbox="" iframe (no scripts/forms/same-origin) or plain text, plus an attachments grid. Attachments download through GET /api/graph/attachment?k=<token> — auth-gated (getCurrentUser), streaming the graph_message_attachments.content bytea, addressed by an opaque base64url token (encodeAttKey) so no mailbox address or raw id sits readable in the URL. peekFullHref now maps email → the reader (AE). Verified on the "GITEX 2024" email: full HTML body + 39 real attachments (names, sizes, download icons). Inline attachments filtered out; cloud/reference attachments shown non-downloadable.

Font: switched the app sans from IBM Plex Sans to Inter (next/font/google, variable font) — --font-sans + --font-heading now point at --font-inter. IBM Plex Mono stays (the "supernova" wordmark + tabular numbers). Verified across the account hub.

Product17:34 IST

Teammate hub (the user as a first-class object)

Built /ae/team/[id] — a rep 360, completing the ontological page set (Lead, Deal, Meeting, Account, Contact, User all now have full pages). getTeammate (identity from users: role,…

Built /ae/team/[id] — a rep 360, completing the ontological page set (Lead, Deal, Meeting, Account, Contact, User all now have full pages). getTeammate (identity from users: role, designation, department, reports-to) + the rep's sales snapshot by reusing getAeDesk/getPipeline on their Zoho id (getAeZohoId(userId)), so it works for any mapped rep — SDR or AE. Shows open pipeline / won FY / open leads / overdue + their open deals (shared OpenDealRow). Graceful empty state when a user has no CRM mapping. Linked from the Team scoreboard (rep names). Verified on Shambhavi Srivastava (SDR): identity + ₹10.7 Cr pipeline, 8 open deals, 99 overdue. (Loosened the shared deal-row prop types to structural subsets so both DealRow and AccountDeal render through them.)

Product17:31 IST

Detail pane (peek) — v2: lead timelines (SDR + AE)

Extended the peek pane to the lead 360s. Every getLeadTimeline item now carries a ref (note/task/call/email/meeting → their stable ids; Proshort → recording:…). Tasks use the…

Extended the peek pane to the lead 360s. Every getLeadTimeline item now carries a ref (note/task/call/email/meeting → their stable ids; Proshort → recording:…). Tasks use the subject-matched follow-up rows (since task who_id never points at leads); meetings/emails use the graph id; recordings the document id. Both /sdr/leads/[id] and /ae/leads/[id] render the PeekPane in the right column when ?peek is present (SDR: replaces the action rail; a read-only lead now gets a 2-col layout so the pane has a home; AE: replaces the follow-ups rail), with the ↗ affordance + selected-row highlight on clickable rows. Verified as Himanshu on Ishika's Collinson lead (read-only): clicking "Followup 1 with Lead Saurabh from Collinson" opened the task pane (Not Started · Normal · Due 13-Jul · Owner Ishika) in-place, no navigation.

Product17:28 IST

Contact hub (the person as a first-class object)

Built /ae/contacts/[id] — the contact 360 the app was missing (contacts existed only as timeline names and peek previews). getContact + getContactDeals (deals where zoho_deals.contact_name…

Built /ae/contacts/[id] — the contact 360 the app was missing (contacts existed only as timeline names and peek previews). getContact + getContactDeals (deals where zoho_deals.contact_name = the contact, plus zoho_deal_contact_roles). Shows identity (title, account link, city), the deals the person is on (open + full Won/Lost history), a reach panel (email/phone + open/won stats), and notes. Extracted the deal-row rendering into a shared src/components/ae/deal-rows.tsx used by both the account and contact hubs. Linked in: account-hub contacts now click through, and peekFullHref maps contact → the page (and account → the new hub, fixing its old link to the list). Verified on Swapnil Randive (Hitachi India): 6-deal history (1 Won ₹6.5L, 5 Lost) + reach + notes.

Product17:24 IST

Universal detail pane (peek) — v1 on the deal timeline

The goal: never navigate away to see an element's detail. Built a reusable, URL-driven detail pane (a real master-detail split — not a Sheet/overlay): ?peek=<kind>:<id> on a detail page…

The goal: never navigate away to see an element's detail. Built a reusable, URL-driven detail pane (a real master-detail split — not a Sheet/overlay): ?peek=<kind>:<id> on a detail page swaps the right column from the rail (Actions/MEDDPICC) to a pane rendering that element, deep-linkable and server-rendered.

  • Data layer src/lib/peek/queries.ts — one fetcher per ontological element: recording (reuses the Proshort intelligence: recap, signals, insights, participants, CRM links, watch URL), lead, deal, contact (+ its deals), account (+ portfolio), email (full body + recipients), meeting (attendees, external/Antino split), task, note, call. getPeek("<kind>:<id>") parses + dispatches.
  • Component src/components/peek/peek-pane.tsx — an async server component; header with entity icon + kind + "open full page" ↗ + close X, then a per-kind body. Sticky, scrolls internally.
  • Timeline refs — every getDealTimeline item now carries ref ("recording:…", "email:…", "meeting:…", "task:…", "note:…", "call:…"). Rows with a ref become peek links with a ↗ affordance and a selected-row highlight.
  • Deal page wired: ?peek renders the pane in place of the rail; close returns. Verified as Himanshu on the Hitachi deal: clicked the "Hitachi <> Antino : CV Solution" recording → pane shows recap + signals + Watch; clicked the calendar meeting → pane swaps in-place to attendees (external/Antino) + Open-in-calendar. No navigation, content swaps live.
  • Next: lead timelines (getLeadTimeline refs + SDR/AE lead pages), then connect peek everywhere entities appear (hover cards' "Open" → peek, list rows) with the ↗ affordance. Tracked in the session task list.
Product17:23 IST

Rebrand to Supernova + Account hub (the company as a first-class object)

Rebrand: the product is now Supernova (Antino is the company that runs it). New brand mark — an 8-ray violet nova burst (#7C5CFF core, gradient rays, glow) in src/components/logo.tsx +…

Rebrand: the product is now Supernova (Antino is the company that runs it). New brand mark — an 8-ray violet nova burst (#7C5CFF core, gradient rays, glow) in src/components/logo.tsx + src/app/icon.svg; wordmark "supernova". Renamed all product branding (28 page title: strings across SDR/AE/Admin/auth, root layout "Supernova — Sales OS", marketing wordmarks, the design-system page). Deliberately kept the company references — meeting-participant affiliation ("External vs Antino"), the pilot copy ("Antino's own 15 reps"), @antino.com domains — since those name the company, not the product (renaming them would corrupt what the data means).

Account hub (/ae/accounts/[id]) — the missing ontological object. Two user questions ("why isn't the company clickable", "why can't I see past deals") were the same gap: no account 360. Built it: getAccount/getAccountDeals/getAccountContacts (deals keyed by zoho_deals.account_name = the account FK, despite the column name). Shows header + stats, open deals and full deal history (Closed Won/Lost — 458 won + 1,052 lost org-wide were previously invisible everywhere), and the account's contacts. Wired the link in across the app: accounts list rows, deal-hub account name (via accountId), recording linked-account, and the /ae/leads company column (→ ?q= search, since a lead's company is free text). Verified on MOD-MISO (2 open + 2 past Won deals render).

Product17:00 IST

AE Meetings: Day/Week/Month switcher + click-through to recording detail

/ae/meetings was month-only, and its meeting chips were hover-only (no way in). Brought it up to the SDR calendar's level: a Day / Week / Month view switcher driven by ?view=&date= (IST,…

/ae/meetings was month-only, and its meeting chips were hover-only (no way in). Brought it up to the SDR calendar's level: a Day / Week / Month view switcher driven by ?view=&date= (IST, Monday-start weeks, prev/next + Today), the same getMeetings + getMeetingSignals data, rendered through the shared MeetingItem (hover card + recording/unlinked pulses). MeetingItem already grew an href prop (parallel session), so recorded meetings now link straight through to their call intelligence instead of nesting an anchor in a button. Built /ae/meetings/[id] as the AE-side recording detail — read-only, reusing getRecording/getRecordingParties/getRecordingInsights/getRecordingLinks: recap, budget/timing/decision scalars, call intelligence grouped by kind, a Linked in CRM panel (deal → /ae/deals, lead → /ae/leads, read-only) and participants split external vs Antino. No connect/write flow — that stays on the SDR side. Verified as Himanshu across all three views (June 2026) and clicked through the "Discovery Call: Goldenflitch & FabHotels" recording (recap + Sahil Malhan linked lead + participants).

Product16:40 IST

AE lead surface (their book, read-only 360)

AEs are full-cycle — they own leads too — but the AE nav gave them no way in. Added the lead surface, reusing the SDR lead data layer (getLeadBook / getLead / getLeadTimeline / getLeadTasks…

AEs are full-cycle — they own leads too — but the AE nav gave them no way in. Added the lead surface, reusing the SDR lead data layer (getLeadBook / getLead / getLeadTimeline / getLeadTasks are keyed by a Zoho owner id, so they're role-agnostic):

  • `/ae/leads` — the AE's own lead book: data table with search + status filter + sortable columns + server-side pagination (25/page), mirroring the SDR People view. The company lens already lives on /ae/accounts, so this stays focused on people.
  • `/ae/leads/[id]` — a read-only lead 360: Details, the lead's open follow-ups (read-only), and the unified CRM + email + meetings timeline. This completes the user's "email of a lead/deal in the detail page" ask on the AE side (the deal 360 already interleaves the buying committee's Graph emails via getDealTimeline).
  • Nav: Leads added to the AE role (after Pipeline). Verified as Himanshu: leads table renders his book with the status tabs + sortable columns; the Atul Sharma hub shows Details + a 7-email unified timeline (real HBS India thread, IST timestamps).
Product13:04 IST

AE universe v1: the deal layer (Pipeline board + Deal hub)

Started the AE role, grounded in a deep DB read first. Key findings: AEs are full-cycle — they own 13,440 open leads (more than SDRs' 3,896) and the deal pipeline. The active pipeline is…

Started the AE role, grounded in a deep DB read first. Key findings: AEs are full-cycle — they own 13,440 open leads (more than SDRs' 3,896) and the deal pipeline. The active pipeline is small (~151 open deals org-wide) in 3 real stages (Qualification → Proposal/Price Quote → Negotiation/Review) — the process-doc's Discovery/Solution stages don't exist. 115/151 open deals are past-due (₹42 Cr slipping) and 40 have no next step — the AE's "overdue chains." Deals are single-threaded (1 contact, roles null → MEDDPICC empty), but their timelines are rich (7,367 tasks + 878 notes via what_id). 246/525 Proshort recordings link to the CRM — the AE's call-intelligence unlock. Himanshu is the closer (38% win, ₹19.7 Cr); deals never come from leads (0 conversions); all INR.

So the AE universe = the SDR workspace (reusable — AEs own leads) + a deal layer. Built the deal layer read-only:

  • `src/lib/ae/queries.ts` — deal DAL scoped to the AE owner id: getPipeline (grouped by open stage), getAeDesk (blended counts: open pipeline, slipping, no-next-step, won FY, open leads, overdue tasks), getDeal, getDealTimeline (tasks/notes/calls/meetings via what_id). src/lib/ae/stage.ts for stage styling.
  • `/ae/pipeline` — Kanban board: 3 stage columns, deal cards with ₹/account/close, past-due + no-next-step flags.
  • `/ae/deals/[id]` — deal hub: Details + rich timeline + an app-native MEDDPICC scorecard (the data is empty; the Qualifier agent fills it from calls) + a buying-committee multi-threading prompt; read-only "owned by X" when not yours.
  • Nav: added Pipeline to the AE role. Verified as Himanshu (AE): board shows 20 open deals / ₹19.7 Cr incl. the ₹10 Cr Ministry of Cooperation deal, all past-due; deal hub renders real Details + timeline + MEDDPICC.
Product11:35 IST

Recording detail + connect records, link resolver, the Pulse system

Meeting/recording detail page (/sdr/meetings/[id]) — full Proshort intelligence for a call (recap, budget/timing/contract/decision-process, and the call-intelligence lists: pain points,…

  • Meeting/recording detail page (/sdr/meetings/[id]) — full Proshort intelligence for a call (recap, budget/timing/contract/decision-process, and the call-intelligence lists: pain points, action items, risks, competitors, questions…), participants split internal/external, Watch-recording link. Query layer in src/lib/proshort/queries.ts.
  • Connect records — the meeting↔lead/deal linking UI, all cases: auto-linked (with method + confidence), suggested candidates, manual search of leads/deals, and orphan → create-lead. Link/unlink are real writes to proshort_recording_links (src/lib/proshort/actions.ts); create-lead is preview (Zoho write-back). Link table migrated to a composite PK (multi-link) + source (auto|manual).
  • Layered link resolver (scripts/proshort/resolve-links.mjs) — best-confidence-wins matcher (exact email→lead/contact, name+company, company domain, account website, fuzzy company name): 152/191 external calls auto-linked. The residual 40 are honest gaps — 28 personal-email individuals/candidates (no company) and ~15 real companies genuinely absent from Zoho; matching can't place them, only create-lead can. Verified with a why-unmatched inspection.
  • The Pulse system (src/components/pulse.tsx) — the app's universal "needs attention" dot (animated ping, tone-colored new/attention/warn/ok), each with a hover tooltip that explains its meaning (self-teaching). First wired on the calendar: a blue recording-available + amber not-linked-to-a-lead pulse per meeting, scoped to the user's own meetings (getMeetingSignals). To spread across nav counts, going-cold leads, no-next-action, orphan calls.
Product08:40 IST

SDR workspace: a day-in-the-life command center

Built the SDR role from a couple of dashboard stubs into a real workspace, all on live Zoho + Graph data (only write-back actions are preview). New DAL: src/lib/sdr/queries.ts (everything…

Built the SDR role from a couple of dashboard stubs into a real workspace, all on live Zoho + Graph data (only write-back actions are preview). New DAL: src/lib/sdr/queries.ts (everything scoped to the signed-in SDR's Zoho owner id) + src/lib/sdr/status.ts (lead status/temperature styling + SALES-PROCESS dispositions/reason codes).

  • My Day (`/sdr`) — replaced the stats dashboard with a command center: follow-ups due today (+ overdue), meetings today, leads never worked (+ new this week), active conversations. Each card links into the work. getMyDay().
  • Inbox (`/sdr/inbox`) — Outlook two-pane. Left: leads split into Conversations (whole-book, ranked by most-recent email via an exact/recipient match against graph_messages) + Not yet contacted. Right: the selected lead's unified communication thread + a reply/log/book compose box. getInbox(), getLeadTimeline() (stitches email mirror + Zoho notes/calls/events; exact email then company-domain fallback).
  • Leads (`/sdr/leads`) — merged the old Leads + Companies into one page with a People ⇄ By-company toggle (SDRs don't own zoho_accounts, so "company" is the lead book grouped by the company field — 831 companies). Dropped the separate Companies route/nav.
  • Lead hub (`/sdr/leads/[id]`) — the cross-entity view the model needed: a Next action strip + the lead's own open follow-up tasks, closeable inline with toasts. Tasks have who_id = null, so the task↔lead link is reconstructed from the subject ("…with Lead X from Company") — 1636/1639 matched. Right rail actions qualify via the process dispositions + reason codes.
  • Follow-ups (`/sdr/tasks`) — the task backlog as a triage list, bucketed Overdue/Today/Upcoming, each parsed into lead · company.
  • Meetings (`/sdr/meetings`) — real calendar with Day / Week / Month views (IST), color-coded by online/in-person/cancelled; a Radix hover card shows full details incl. attendees + external company (from attendee email domain). getMeetings() joins graph_events + graph_event_attendees.
  • Foundations: src/lib/format/datetime.ts — canonical 12-Mar-2029 16:30 IST everywhere (+ istDayKey/formatTimeIST for the calendar); global Toaster (sonner) mounted in the shell; sticky header.
  • Fixes along the way: pg SSL deprecation warning (explicit ssl config in db/index.ts, strip sslmode); command-board crash (formatCount(undefined) — realigned /admin/command to the redesigned getRepScoreboard schema + hardened formatCount); audit trail & integrations now show IST (were raw UTC).
  • Verified in Chromium signed in as an SDR (Ishika): My Day (87 due today / 1,372 overdue / 805 unworked), Inbox (Bhaskara's real Exide thread), Leads (878 / 831 companies), Follow-ups (300 shown), Meetings (Bechtel discovery call, week grid), Lead hub (Neha's next action + 2 tasks + 6 matched emails). Zero render errors.

11

Jul 2026

Saturday

3 shipped

Product10:33 IST

Command board: donut breakdowns + SDR/AE rep scoreboard

"Open pipeline by stage" and "Lead book by status" are now donuts (user request): new BreakdownPieChart in dashboard/charts.tsx — one entity hue per chart (deal-greens / lead-teals, darkest…

  • "Open pipeline by stage" and "Lead book by status" are now donuts (user request): new BreakdownPieChart in dashboard/charts.tsx — one entity hue per chart (deal-greens / lead-teals, darkest = largest slice, dataviz one-hue rule), slices sorted desc with tail folded into "Other", and the legend doubles as a data table (name + share % + formatted value) so identity never rides on color alone.
  • Rep scoreboard on /admin/command: every active SDR/AE with a Zoho mapping — open leads (unconverted, not Lost/Junk/Not Qualified), new leads 30d, tasks/calls/meetings 30d (getRepScoreboard() in metrics/queries.ts). Entity dots in the column headers.
  • getWeeklyActivity week bucket now returns a deterministic to_char(..., 'YYYY-MM-DD') string (belt-and-braces with the earlier Date coercion for the Intl RangeError).
  • Verified in Chromium signed in as admin: 4 chart SVGs render, zero page/asset errors; scoreboard shows the real book (e.g. Mahak Rai: 3,163 open leads / 4,451 tasks-30d). Data note: calls/meetings are near-zero for most reps — real Zoho behavior (activity logged as tasks), not a query bug.
Product10:29 IST

Role home dashboards (SDR + AE), live per-person data

SDR home (/sdr) and AE Desk (/ae) upgraded from welcome-blurb stubs to real dashboards, each scoped to the signed-in person via the metrics lib's rootUserId (a user with no reports resolves…

  • SDR home (`/sdr`) and AE Desk (`/ae`) upgraded from welcome-blurb stubs to real dashboards, each scoped to the signed-in person via the metrics lib's rootUserId (a user with no reports resolves to just their own owned records). SDR home leads with the lead book (leads owned, new-leads/30d, activity/7d tiles + lead-status breakdown + 12-week activity); AE Desk leads with pipeline (open pipeline ₹, won FY, new leads, activity tiles + pipeline-by-stage + 12-week activity), plus a link to the Team view. Honest empty state when no CRM records map to the user.
  • Reuses the existing getSalesOverview/getLeadStatusBreakdown/getPipelineByStage/getWeeklyActivity (all rootUserId-scoped) and the committed dashboard primitives (StatTile, ChartCard, CategoryBarChart, WeeklyActivityChart). Applied the same weekStart Date-coercion guard as the other dashboards.
  • Verified in Chromium: signed in as an SDR (Zubair) and an AE (Anisha) — both homes render real numbers and charts (Anisha's Desk: ₹6.8 Cr open pipeline / 24 deals), zero console errors.
Product08:46 IST

DB pool: retry-once on transient network errors

A momentary DNS blip (getaddrinfo ENOTFOUND nuvio.postgres.database.azure.com) surfaced as a user-facing 500 on /ae. The host resolved fine seconds later — purely transient.

  • A momentary DNS blip (getaddrinfo ENOTFOUND nuvio.postgres.database.azure.com) surfaced as a user-facing 500 on /ae. The host resolved fine seconds later — purely transient.
  • src/db/index.ts now wraps pool.query with a single retry (300ms backoff) on ENOTFOUND | EAI_AGAIN | ECONNREFUSED | ETIMEDOUT | ECONNRESET. Pre-connect errors are always safe to retry; ECONNRESET could in theory retry an in-flight write, accepted trade-off (low write volume, rare duplicate beats guaranteed 500). Callback-style queries bypass the wrapper (drizzle is promise-only); double-wrap guarded via a symbol on the hot-reload-shared pool.
  • Verified live: normal drizzle query works through the wrapper (234 users); against a bogus host the wrapper logs one [pg pool] transient network error, retrying once and fails only after the second attempt (~360ms) — so a blip that heals within the window succeeds silently.

07

Jul 2026

Tuesday

2 shipped

Product23:49 IST

Entity color language

One permanent hue per domain entity, built on a dataviz-validated 8-slot categorical palette (all-pairs CVD on white surface; chip ink ≥ 4.5:1): Account blue · Lead aqua · Task yellow ·…

  • One permanent hue per domain entity, built on a dataviz-validated 8-slot categorical palette (all-pairs CVD on white surface; chip ink ≥ 4.5:1): Account blue · Lead aqua · Task yellow · Deal green · Contact violet · Meeting red · Employee magenta · Email orange.
  • Tokens in globals.css @theme (--color-entity-<kind> base + -text ink) → utilities like bg-entity-lead/10, text-entity-deal-text. Registry + helper in src/lib/entity-colors.ts (labels, lucide icons, literal class strings, chartVar); <EntityBadge kind variant={chip|dot|icon}> in src/components/entity-badge.tsx.
  • Rules: color follows the entity everywhere (chips, dots, icon tiles, chart marks), fixed, never cycled, never reassigned by rank; never color-alone (icon/label always rides along); charts about entities use entityColor(kind).chartVar in the fixed slot order (the CVD-safety mechanism).
  • Showcased on /design#entity-colors (chips/dots/tiles, token table, chart-order strip), linked in the sidebar under Foundations.
Product08:19 IST

Credential flows, user-management suite, notifications spine live

Schema: new credential_tokens (single-use invite / password-reset links; only the SHA-256 hash stored, prior unused same-type tokens voided on reissue) and notifications (recipient, type =…

  • Schema: new credential_tokens (single-use invite / password-reset links; only the SHA-256 hash stored, prior unused same-type tokens voided on reissue) and notifications (recipient, type = audit action, title/body, FK to the source audit_events row, read_at). The notifications spine is live.
  • Audit registry: all action strings now come from the AUDIT_ACTIONS const in src/lib/audit-actions.ts; added USER_DELETED, USER_INVITED, USER_INVITE_ACCEPTED, PASSWORD_RESET_REQUESTED, PASSWORD_RESET_LINK_ISSUED, PASSWORD_RESET_COMPLETED, PASSWORD_CHANGED. Existing role/status/manager actions refactored to the registry and now also notify (affected user + active admins excluding the actor; old/new manager on manager changes). Every notification carries its audit event id.
  • `/admin/users` suite: "Add person" dialog creates a passwordless user and swaps to a copyable invite-link panel (7-day expiry); rows get a dropdown menu — Edit details (prefilled dialog), Copy invite link (reissues for never-activated users, amber "Invited" chip), Issue reset link (2-hour expiry, for password users), Suspend/Reactivate, and Delete (hard delete, never-activated only, AlertDialog confirm). The page query exposes hasPassword as a SQL boolean so the hash never reaches the client.
  • Public credential pages, styled to match the sign-in card: /forgot-password (byte-identical generic response for known/unknown emails — anti-enumeration — while active users generate an admin-notifying audit event), /invite/[token] (set first password → session → role home; notifies issuer + manager), /reset-password/[token] (set new password → /sign-in; security receipt to the user + admins). Passwords bcrypt cost 10, min 8, confirm-match validated client and server; tokens re-validated server-side on submit and consumed once.
  • Bell + settings: app shell (server component) now renders a notifications bell for every role — clay unread badge (99+ cap), popover list, click-to-mark-read, mark all read — plus a Settings nav item. /<role>/settings for all five roles: read-only profile card + change-password form (verify current, audited PASSWORD_CHANGED, receipt notification).
  • Verified end-to-end against the dev server: tsc clean; full invite lifecycle (create → invite link → set password → signed in as DEV, token consumed); forgot-password enumeration check; reset + change-password round trips with fresh sign-ins; bell showing correct unread count; delete guards (self / activated / has-reports rejected) confirmed in code. Test user and tokens cleaned up.
  • Known limitation (queued): JWT sessions are not invalidated on password change/reset — they remain valid until expiry.

06

Jul 2026

Monday

5 shipped

Product22:40 IST

Sales org tree seeded from org chart

Applied the sales hierarchy from the org chart image (scripts/apply-org-tree.ts, idempotent, fully audited): Vinay Krishna Gupta (CEO, ADMIN) → Rohit Naidu (Director-Business & Strategy) →…

  • Applied the sales hierarchy from the org chart image (scripts/apply-org-tree.ts, idempotent, fully audited): Vinay Krishna Gupta (CEO, ADMIN) → Rohit Naidu (Director-Business & Strategy) → four Heads (Anisha Ratnam/Startup, Nitish Kumar/Growth, Himanshu Gupta/Government, Harshad/Growth & Partnerships) → 11 Key Accounts people. 16 reporting edges.
  • Created 5 users missing from the designation sheet: Vinay (vinay@antino.io, ANTINO86) and Paridhi Srivastava (paridhi.srivastava@antino.com, ANTINO875) with real emails from the sheet's second tab; Harshad, Dhruv Raj, Abhishek Rana with placeholder emails (harshad@, dhruv.raj@, abhishek.rana@antino.com) — no source lists their real ones. All get the shared dev password.
  • Renamed "Rohit S" → "Rohit Naidu" (his rohit.naidu@antino.com email + the chart confirm the sheet name was truncated).
  • Role calls: CEO → ADMIN; Heads and Key Accounts (deal-owning) → AE; sheet-designated trainees/interns/tender exec stay SDR. Antino site (antino.com) confirms Vinay as Co-founder & CEO.
  • Users total: 232.
Product21:08 IST

Manager hierarchy (`503b23a`)

users.manager_id: nullable self-referencing FK to users.id; null = top of tree.

  • users.manager_id: nullable self-referencing FK to users.id; null = top of tree.
  • Drizzle relations: manager (one) / directReports (many).
  • Open question noted: does role imply hierarchy position, or is the tree free-form? Currently free-form.
Product21:08 IST

CAPITAL_SNAKE enums (`a330bf9`)

Project-wide convention set: enum values CAPITAL_SNAKE_CASE, plural table names, semantic column names.

  • Project-wide convention set: enum values CAPITAL_SNAKE_CASE, plural table names, semantic column names.
  • user_role values changed to ADMIN | SDR | AE | TECH_LEAD | DEV, default DEV.
Product21:08 IST

Database live

supernova database created on Azure by admin; granted dealflow_app USAGE/CREATE on schema public, all privileges on the database, and default privileges on future tables/sequences.

  • supernova database created on Azure by admin; granted dealflow_app USAGE/CREATE on schema public, all privileges on the database, and default privileges on future tables/sequences.
  • First drizzle-kit push applied: user_role enum + users table. Verified end-to-end as app user.
Product21:08 IST

Initial scaffold (`bd35629`)

Next.js 16 app (App Router, TS, Tailwind v4, ESLint) scaffolded into empty repo.

  • Next.js 16 app (App Router, TS, Tailwind v4, ESLint) scaffolded into empty repo.
  • Drizzle ORM + pg wired to Azure Postgres; .env with APP_DATABASE_URL (git-ignored).
  • /api/health route for DB connectivity checks; db:generate/migrate/push/studio scripts.
  • docs/DESIGN.md copied from antino3 — current direction is Daylight (top section); everything below the HISTORICAL marker is reference only.
  • Git repo initialized, pushed to private GitHub repo verma6uc/antino4.