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.

99

improvements shipped

9

days of building

11

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
6
15 Jul

Product panorama

One deal, one continuous operating loop.

Six agents live in the product today, propose-only, a named human at every gate. Everything below ships; nothing is a mock.

01every desk, daily

Brief

The Daily Brief opens each cockpit: meetings with exact times, deals needing a touch, the one move.

02critic-checked

Draft

The Email Drafter investigates the thread through scoped tools, then a fresh-context critic verifies every claim.

038 dimensions

Qualify

Deal Check scores MEDDPICC with the evidence that earned each score, never vibes.

04pre-call

Prep

Meeting Prep briefs every call: who is in the room, where it stands, what to ask.

05with provenance

Track

The Promise Tracker extracts what each side committed to, source attached, resolved in place.

06propose-only

Audit

Every run costed and receipted: model, tokens, verdict, the audit trail behind every artifact.

15

Jul 2026

Wednesday

3 shipped

Product

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).

Product

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.

Product

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

Product

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.
Product

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.

Product

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.

Product

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.

Product

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.

Product

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).

Product

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.

Product

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.

Product

"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.

Product

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.

Product

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.

Product

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.

Product

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).

Product

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.

Product

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

Product

#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.

Product

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).

Product

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

Product

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.

Product

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.)

Product

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).

Product

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.

Product

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.

Product

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.
Product

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.

Product

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.)

Product

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.

Product

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).

Product

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).

Product

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).
Product

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.
Product

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.
Product

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

Product

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.
Product

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.
Product

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

Product

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.
Product

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

Product

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.
Product

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.
Product

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.
Product

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.
Product

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.