Built in public · Changelog
How Supernova learned the job.
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.
Brief
The Daily Brief opens each cockpit: meetings with exact times, deals needing a touch, the one move.
Draft
The Email Drafter investigates the thread through scoped tools, then a fresh-context critic verifies every claim.
Qualify
Deal Check scores MEDDPICC with the evidence that earned each score, never vibes.
Prep
Meeting Prep briefs every call: who is in the room, where it stands, what to ask.
Track
The Promise Tracker extracts what each side committed to, source attached, resolved in place.
Audit
Every run costed and receipted: model, tokens, verdict, the audit trail behind every artifact.
15
Jul 2026
Wednesday
3 shipped
ProductBulk 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…
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).
ProductThe 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 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 asaside. 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
Linkedinfrom lucide-react (which dropped brand glyphs) and broke tsc tree-wide, swapped toLink2.
Verified: all 13 AE + public routes return 200 with no runtime errors; pipeline/lead-hub pulses confirmed linking in the browser.
ProductThe 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,…
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
ProductThe 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:
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-strongare 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/raisedgain an inset cream hairline (4-5%), the "physically lit" card top that espresso surfaces were missing.
ProductChangelog 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…
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.
ProductThe 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 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.
ProductAccounts 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…
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.
ProductThe 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:
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.
ProductMutation 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)…
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):
- 01Log a call on a deal →
CALL_LOGGEDPREVIEW audit + close-on-activity chain completion → completions + audit restored. - 02SAL handoff → synthetic
SAL_HANDOFF_SENTseeded → appears in/ae/handoffs→ Accept →SAL_ACCEPTED→ both events + notifications removed. - 03Mark a promise kept → REAL
commitmentsstatus 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.) - 04Link an unlinked recording to its suggested deal → real
proshort_recording_linksrow → deleted. - 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).
ProductLLM 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…
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 ingetAnthropic()so everymessages.create(incl. the ones toolRunner makes internally) self-logs;withLlm()carries agent/entity context viaAsyncLocalStorage,t.mark()tags phases,t.totals()is the real sum. Fixed a race: ctx updates now complete beforecreate()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.
ProductGoldenflitch 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…
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:
"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 connect —
suggestLinks(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, today —
proshort_recording_linksis our own table (source='manual',method='user'), no Zoho scope needed; every link logsRECORDING_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.
ProductAccounts 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…
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.
ProductAE 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…
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.
ProductTitle-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…
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.
ProductTimeline 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…
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).
ProductClose-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…
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.
ProductClose-on-activity everywhere + overlay-consistent counts
Extended close-on-activity across every work surface + made the completion overlay consistent app-wide:
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)
LeadActionsrail (actions usegetCurrentUser+ role-aware links, still RACI-gated), so AEs get the same close-on-activity + manual close their leads' follow-ups. - Deal hub —
logCall+ newlogDealEmail/logDealMeetingare 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), plusgetLeadTasks/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…
#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.
ProductMeeting 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…
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).
ProductProcess 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…
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
ProductProcess 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…
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_SENT → SAL_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.
ProductDeal 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…
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.)
ProductProcess 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…
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).
ProductDetail 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,…
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.
ProductDetail 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…
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.
ProductUniversal 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…
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
getDealTimelineitem now carriesref("recording:…", "email:…", "meeting:…", "task:…", "note:…", "call:…"). Rows with a ref become peek links with a ↗ affordance and a selected-row highlight. - Deal page wired:
?peekrenders 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 (
getLeadTimelinerefs + 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.
ProductEmail 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 + 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.
ProductTeammate 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,…
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.)
ProductContact 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…
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.
ProductRebrand 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 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).
ProductAE 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: 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).
ProductAE 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…
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).
ProductAE 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…
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 viawhat_id).src/lib/ae/stage.tsfor 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.
ProductRecording 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,…
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 insrc/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 awhy-unmatchedinspection. - 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.
ProductSDR 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…
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()joinsgraph_events+graph_event_attendees. - Foundations:
src/lib/format/datetime.ts— canonical12-Mar-2029 16:30 ISTeverywhere (+istDayKey/formatTimeISTfor the calendar); global Toaster (sonner) mounted in the shell; sticky header. - Fixes along the way: pg SSL deprecation warning (explicit
sslconfig indb/index.ts, stripsslmode); command-board crash (formatCount(undefined)— realigned/admin/commandto the redesignedgetRepScoreboardschema + hardenedformatCount); 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
ProductCommand 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…
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
BreakdownPieChartindashboard/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. getWeeklyActivityweek bucket now returns a deterministicto_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.
ProductRole 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…
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(allrootUserId-scoped) and the committed dashboard primitives (StatTile,ChartCard,CategoryBarChart,WeeklyActivityChart). Applied the sameweekStartDate-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.
ProductDB 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.
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.tsnow wrapspool.querywith a single retry (300ms backoff) onENOTFOUND | EAI_AGAIN | ECONNREFUSED | ETIMEDOUT | ECONNRESET. Pre-connect errors are always safe to retry;ECONNRESETcould 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 onceand fails only after the second attempt (~360ms) — so a blip that heals within the window succeeds silently.
07
Jul 2026
Tuesday
2 shipped
ProductEntity 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 ·…
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 +-textink) → utilities likebg-entity-lead/10,text-entity-deal-text. Registry + helper insrc/lib/entity-colors.ts(labels, lucide icons, literal class strings,chartVar);<EntityBadge kind variant={chip|dot|icon}>insrc/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).chartVarin 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.
ProductCredential 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 =…
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) andnotifications(recipient, type = audit action, title/body, FK to the sourceaudit_eventsrow,read_at). The notifications spine is live. - Audit registry: all action strings now come from the
AUDIT_ACTIONSconst insrc/lib/audit-actions.ts; addedUSER_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
hasPasswordas 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>/settingsfor all five roles: read-only profile card + change-password form (verify current, auditedPASSWORD_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
ProductSales 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) →…
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.comemail + 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 staySDR. Antino site (antino.com) confirms Vinay as Co-founder & CEO. - Users total: 232.
ProductManager hierarchy (`503b23a`)
users.manager_id: nullable self-referencing FK to users.id; null = top of tree.
Manager hierarchy (`503b23a`)
users.manager_id: nullable self-referencing FK to users.id; null = top of tree.
users.manager_id: nullable self-referencing FK tousers.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.
ProductCAPITAL_SNAKE enums (`a330bf9`)
Project-wide convention set: enum values CAPITAL_SNAKE_CASE, plural table names, semantic column names.
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_rolevalues changed toADMIN | SDR | AE | TECH_LEAD | DEV, defaultDEV.
ProductDatabase 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.
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.
supernovadatabase created on Azure by admin; granteddealflow_appUSAGE/CREATE on schemapublic, all privileges on the database, and default privileges on future tables/sequences.- First
drizzle-kit pushapplied:user_roleenum +userstable. Verified end-to-end as app user.
ProductInitial scaffold (`bd35629`)
Next.js 16 app (App Router, TS, Tailwind v4, ESLint) scaffolded into empty repo.
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;
.envwithAPP_DATABASE_URL(git-ignored). /api/healthroute for DB connectivity checks;db:generate/migrate/push/studioscripts.docs/DESIGN.mdcopied 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.