Built in public · Changelog

How Supernova learned the job.

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

162

improvements shipped

14

days of building

12

shipped per day, avg

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

Product panorama

One deal, one continuous operating loop.

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

017 agents on the desk

Sell

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

02provenance quoted

Spec

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

03the ratchet

Model

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

04on real components

Design

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

05earned, not declared

Run

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

06propose-only

Audit

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

Latest20-Jul-202608:29 ISTProduct

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

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

20

Jul 2026

Monday

10 shipped

Product08:29 IST

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

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

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

Product20:18 IST

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

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

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

Product19:56 IST

The sales floor unifies: SDR and AE become one login

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

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

Product16:04 IST

The family goes electric: white + electric blue

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

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

Product10:30 IST

Supernova ships to supernova.nuvio.dev

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

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

Product10:20 IST

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

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

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

Agents10:11 IST

The agents get a front door, and the CEO gets a board that retires the timesheet

Two mirrors on the same truth landed together. First, the agent team got a public face: /agents (linked from the landing nav) walks the real 17-agent roster in its two squads - seven on the…

Two mirrors on the same truth landed together. First, the agent team got a public face: /agents (linked from the landing nav) walks the real 17-agent roster in its two squads - seven on the sales floor, ten on the delivery line - each card naming what the agent actually does rather than what a brochure wishes it did, the tools-and-ledger story told straight (an agent here is a tool loop whose every run opens a session you can read back call by call), and the promise made plainly: zero black boxes, and the operator is you - agents propose, the human disposes. Meeting Minutes and What's Next stand on the page too, marked as still in the shop, because a roster that hides its gaps is a brochure, not a page about a real product. Second, /admin/command grew two bands for the CEO, because Vinay's questions are not an SDR's: he asks where every engagement stands and what the company actually did this week. The Delivery portfolio band answers the first - every active project in one sweep: phase, go-live runway, the active sprint's progress, blocked count, open bugs, open gates. The work record · last 7 days answers the second, and it carries the bigger idea: it is the TIMESHEET REPLACEMENT. Every story move, QA run, bug hop, and plan change already lands in the audit spine the moment it happens, so a person's last seven days can be READ instead of asked for - the timesheet becomes a report, not a form, and nobody spends Friday reconstructing what the system watched them do all week. The concept generalizes hard (git commits and agent sessions join the record next, then a per-person drill-down), and it only works because the spine rule held from day one: every feature feeds the audit trail, so the audit trail can now feed the CEO.

Agents09:51 IST

The Spec Writer earns its tool belt, and quotes get checked by a machine

The glue thesis lands on its first delivery engine. The Spec Writer is no longer one stuffed prompt: src/lib/ai/spec-tools.ts gives it a working belt - read_document pages the FULL corpus…

The glue thesis lands on its first delivery engine. The Spec Writer is no longer one stuffed prompt: src/lib/ai/spec-tools.ts gives it a working belt - read_document pages the FULL corpus text in 6k-char pages (follow "MORE FOLLOWS" to END OF DOCUMENT; the old 15k truncation is gone), search_corpus keyword-hits across documents + timeline + promises, list_promises, list_existing_use_cases (which turns "Draft again" from blind appending into gap-filling), and save_use_case, the write-back that validates and inserts ONE use case WITH its stories and tests in a single transaction, so an appended use case can never again land story-less. The runner (agent-spec.ts) is a SONNET toolRunner, 30 iterations max, with two mandatory phases: READ the authoritative documents end to end and build a coverage list of every in-scope module, then SAVE one use case per module. And because "quote the corpus" used to be a polite request, a deterministic QUOTE VERIFIER now sits inside save_use_case: any source.quote not found verbatim in the corpus (normalized for whitespace and curly quotes) is rejected back to the agent with an error it must fix - grounding enforced by code, not by prompt. First live run, project 38 (SalarySe): 11 model calls, 22 tool calls, the agent read the signed agreement end to end, correctly treated the 8 existing use cases as ground to fill around rather than duplicate, and saved exactly one new use case, "Operate Talent-on-Demand Staffing, Onboarding and Commercial Governance", with 3 stories, 4 tests, and a verbatim Service Agreement quote. The whole run reads off /admin/ai sessions like a transcript, which is exactly what the ledger was built for.

Agents09:51 IST

Agent #17 answers "what do we build it with", and the landing stops guessing

The tech-stack question becomes an artifact instead of a hardcode. Tech Stack Advisor (src/lib/ai/agent-stack.ts) reads the corpus, ontology, and NFRs and proposes the stack layer by layer…

The tech-stack question becomes an artifact instead of a hardcode. Tech Stack Advisor (src/lib/ai/agent-stack.ts) reads the corpus, ontology, and NFRs and proposes the stack layer by layer into a new tech_stack_proposals table - versioned DRAFT -> APPROVED, layers jsonb where every choice carries a plain rationale a client could read, a corpus quote wherever the client's own documents NAME the technology, and a corpus_named flag (a named integration is FIXED and quoted verbatim; where the corpus is silent, the house default with the why). The TL approves on the Design page, and once a proposal is APPROVED the scaffold ships docs/TECH-STACK.md into the repo, retiring the assumption that every client gets Next+Tailwind+Drizzle because that is what the generator knew. The build also settled the agent-tools DOCTRINE in one sentence: an agent gets TOOLS wherever it reads the CLIENT's world (corpus, web) and stays single-shot where it transforms OUR structured artifacts; the conversion queue is Persona Researcher, Tech Stack Advisor (done), Impact Analyst. And the landing page was re-synced to the real product story - 17 agents, the delivery journey, the roles - so the marketing finally describes the app that exists rather than the one first imagined.

19

Jul 2026

Sunday

5 shipped

Product01:19 IST

The Page Architect writes its own FK

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

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

Design01:16 IST

The design language comes home: dealflow's founder ivory/white pair, back-ported

The user pointed at dealflow_nextjs ("steal the design language, do a 10000% job") - and its DESIGN.md carries a lineage note that made the job precise: dealflow ADOPTED antino4's Daylight…

The user pointed at dealflow_nextjs ("steal the design language, do a 10000% job") - and its DESIGN.md carries a lineage note that made the job precise: dealflow ADOPTED antino4's Daylight on 18-Jul, then refined it to the founder's ivory + white pair the same day. This ports the refinement back. Page stays ivory #faf6f0; cards flip from bone to TRUE WHITE lifting off it (superseding yesterday's inversion); the canvas drops a step under the page (#f5efe6) so the grain ladder reads canvas < page < washes < white. Three laws came with the values: solid warm neutrals never alpha-of-ink (borders are #e9dfcd solid - an espresso-alpha hairline composites to cold grey on ivory); the neutral hover wash #eee6d9 is never clay (chrome must not flare orange); and --primary is now SOLID CLAY #b4551c with a white label whose hover DEEPENS (#a34108) - on dark, muted clay #d97757 whose hover BRIGHTENS, the lightness-flipped twin. The radius ladder went from one scaled knob to fixed rungs (6 pills / 8 inputs / 10 menus / 12 cards / 16 dialogs), the Button took dealflow's full behavior set (borderless fills, hover lift + shadow deepen, press scale 0.97, icon micro-scales, disabled muted-wash instead of ghosting, link variant's 45-degree arrow), and globals gained their enforcement layers verbatim: hairline discipline on every card/overlay data-slot, the focus-collapses-to-1px-hairline rule, themed scrollbars, recharts re-inking. Dark got dealflow's ladder too (cards #282320 two steps over canvas, popovers #2e2823, brighter muted ink #c9c2b3). Landing verified intact; suite 24 green.

Product00:52 IST

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

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

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

Agents00:34 IST

The core strengthens: every agent run becomes a session you can open

The first brick of the glue horizon, foundation-first as the user called it (schema, then UI, then the core). The ledger was already half-true: llm_calls records every model call with a…

The first brick of the glue horizon, foundation-first as the user called it (schema, then UI, then the core). The ledger was already half-true: llm_calls records every model call with a trace_id. What was missing was the RUN as a first-class thing. Now withLlm() itself opens an agent_sessions row before the work and closes it after - COMPLETED with totals or FAILED with the reason - so no agent can forget to ledger itself, and agent_tool_calls records every tool invocation (name, input summary, output preview + length, sequence) through the same funnel the Writer's tool belt already used. All three tables share the trace_id, which means /admin/ai now carries an Agent sessions table (one row per run: outcome, calls, tools, tokens, time, cost) and each row opens /admin/ai/sessions/[trace]: the whole run reconstructed as a timeline - tool calls with their inputs and previews interleaved with model calls wearing purpose, model, tokens, latency, and stop reason. Proven live: fired Deal Check through the AE surface and read its 7 get_email calls and 3-phase loop (investigate → score → critic) off the drill-down. When the next "the regenerate failed" report lands, the answer is a click, not an absence. gatherCorpus also now returns its untruncated parts (documents, timeline, promises) - the doorway the Spec Writer's tool belt walks through next.

Fix00:12 IST

Tasks grow up: five states, and bugs become their own issue type

The user called the three-state workflow a toy ("very very very less states on a task, we have no way to raise a bug") and pointed at glue/boostr-web as the reference - whose ticket model…

The user called the three-state workflow a toy ("very very very less states on a task, we have no way to raise a bug") and pointed at glue/boostr-web as the reference - whose ticket model runs FOURTEEN lifecycle stages with agents driving the transitions. Landed the right-sized version: the delivery axis is now To do → In progress → In review → In QA → Done (Blocked stays a flag - work has a place AND a problem, two different facts) across the board's five columns, the dev's verbs (Start / Send to review / Send to QA, each with its way back), and QA's queue (In-QA stories first). The DONE column now belongs to QA: verify-and-close is hard-gated server-side - every test case carrying a latest-run PASS and zero open bugs, or the action refuses with the exact counts - and return-to-dev carries a mandatory note to the assignee. And bugs are first-class issues at last: raised from QA's bench (severity + what-broke detail mandatory), auto-assigned to the story's assignee, walked OPEN → In progress → FIXED by the dev, verified CLOSED (or reasoned WONT_FIX) by QA - every hop audited and notified, open bugs counted on the runway line. The deeper glue lessons (agents as tool-using loops with session ledgers, triage as a pipeline stage, dev plans as artifacts) are recorded as the next horizon in TASKS.

18

Jul 2026

Saturday

22 shipped

Product23:40 IST

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

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

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

Product23:24 IST

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

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

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

Product22:17 IST

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

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

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

Product22:04 IST

Ivory paper, bone panels: the tone step inverts

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

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

Product22:04 IST

The dashboard stops filing and starts reporting: the pulse strip

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

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

Product21:09 IST

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

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

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

Data21:09 IST

The Zoho walk learns to turn the page properly

The hourly cron started failing with DISCRETE_PAGINATION_LIMIT_EXCEEDED on /crm/v8/Tasks: Zoho v8 caps discrete page pagination at 2000 records (page 11 × 200 does not exist no matter how…

The hourly cron started failing with DISCRETE_PAGINATION_LIMIT_EXCEEDED on /crm/v8/Tasks: Zoho v8 caps discrete page pagination at 2000 records (page 11 × 200 does not exist no matter how many tasks do). The REST walk now switches to cursor flow the moment Zoho hands back info.next_page_token, and when a window somehow exhausts 2000 records without ever offering a token it stops that walk and says so in the sync log instead of asking for page 11 and dying. One TypeScript wrinkle: the params object fed a generic inferred from itself (TS7022 circular inference) - an explicit Record<string, string | number> settles it.

Product21:09 IST

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

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

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

Product19:34 IST

A lock grows a handle: PhaseLock offers the advance

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

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

Product18:40 IST

The changelog learns the minute: IST ship times from git

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

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

Product18:37 IST

The Documents page learns the sales universe

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

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

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

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

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

Fix10:52 IST

SalerySe's cast was 100% wrong, and the signed agreement knew it

User: "/tech-lead/personas?project=38 is 100% wrong, read please for me." Reading the corpus properly proved it three ways. (1) The uploaded "scope doc" was POISON: 80KB of one sentence…

User: "/tech-lead/personas?project=38 is 100% wrong, read please for me." Reading the corpus properly proved it three ways. (1) The uploaded "scope doc" was POISON: 80KB of one sentence repeated 2000x - the wizard byte-verification test file from the original live-fire, sitting in the corpus as the only readable scope ever since. Deleted. (2) The signed ToD agreement (readable now via the docx extractor) defines the REAL product in its recitals: SalarySe (Critical Path Technologies) is a salary-linked financial wellness platform - UPI-linked credit cards, InstaCash early wage access, tax-saving flexi-benefits, FD investments, integrated into employer payroll - NOT "payroll and compensation software" as the guessed-blind DESIGN.md claims. (3) The cast was engagement STAFF (Delivery Lead, UI/UX Designer, Account Lead), not the product's people.

  • Prompt rule sharpened: the cast is the CLIENT PRODUCT'S people; when the engagement is design/build work FOR a client's product, the cast is that product's users - delivery-side staff are stakeholders, never personas, even when use-case actors name them.
  • Re-ran on SalerySe (3.7 min): the cast pivoted to Salaried Employee (End User), Employer HR/Payroll Partner, Product Owner (approver), Cofounder (buyer), Technical Team (receives the Work Product). Antino's own staff left the cast. Pains now cite the agreement BY CLAUSE ("Clause 5(b): five business days to flag resource performance issues", "Work Product vests exclusively in the Client"). The two product personas carry researched-only pains, honestly - the corpus says what the product IS but not yet what its users suffer; that is what discovery is for.
  • Follow-up (user: "why is the Technical Team there at all?"): right again - receiving the handoff is an engagement relationship, not a product one, and quoting the contract about someone does not make them a product person. Rule sharpened: the CLIENT's engagement-side people (sponsor, deliverable-approver, receiving engineers) are stakeholders too; BUYER/APPROVER mean buyer/approver OF THE PRODUCT. Third run (v3): Salaried Employee [USER], Employer Benefits Administrator [BUYER], and Payroll/HRMS Integration as a [SYSTEM] actor - the engagement people now live only where they belong, the Overview's Team & stakeholders panel. v1/v2/v3 all preserved.
  • Named cascade, not yet fixed: use cases v3, the ontology (DesignEngagement et al), DESIGN.md's identity paragraph, pages, and mocks for 38 were all built on the engagement-as-product misreading and need their regeneration pass through the gate.
Product10:41 IST

Team and Documents become first-class pages

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

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

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

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

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

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

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

Product10:08 IST

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

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

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

Shipped now:

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

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

Product10:05 IST

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

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

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

The timeline stops hiding the promise record: email attachments surface

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

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

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

Zero sub-AA: the faded-text idiom retired

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

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

The vanilla theme inspected pixel by pixel, and three fixes

The user asked for a close inspection. Chrome extension was down, so a Playwright harness (tests/e2e/.state, the suite's own storage states) screenshotted seven tech-lead pages AND ran a…

  • The user asked for a close inspection. Chrome extension was down, so a Playwright harness (tests/e2e/.state, the suite's own storage states) screenshotted seven tech-lead pages AND ran a canvas-normalized computed-style audit: every visible text element's fg/bg composited through translucent layers (lab()/oklab() normalized via canvas), true WCAG ratio per pair. Findings and fixes, all measured:
  • text-clay on clay washes had regressed (base clay went hot, so bg-clay/10 text-clay chips - must/BUYER/COMPLETED/Planning - fell to 2.4:1): swept to text-clay-ink across 12 files, and clay-ink itself deepened #b94a0a -> #a34108 (5.2-5.5 on every wash; also fixes the 4.38 near-miss on bg-vanilla chips). Charts ramp + entity-colors follow.
  • muted-foreground was under body-text AA everywhere (3.2-3.6): #8c8475 -> #6f6757 in the light scopes (4.8-5.4 on every app surface). Pre-existing, not a shift regression, fixed anyway.
  • Semantic chips one step light: emerald-600 -> 700, sky-600 -> 700 on approved/EVIDENCED/RESEARCHED/phase chips (4.5-4.9).
  • After fixes: five of seven pages have ZERO sub-AA pairs. Leftovers live in the parallel session's open plan-board.tsx ("active" emerald chip, clay-hot-as-text label) and the deliberate text-muted-foreground/70 timestamp idiom (lifted from 2.3 to ~3.5 by the muted fix; kept as quiet meta).
Product09:12 IST

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

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

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

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

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

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

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

17

Jul 2026

Friday

4 shipped

Product23:19 IST

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

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

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

The tech-lead journey becomes the product: gate, memory, pipeline, designer, and the first commit from the platform

One sprint in three rounds (commit 717941b + the tests/docs follow-up), everything live-fired on SalerySe.

One sprint in three rounds (commit 717941b + the tests/docs follow-up), everything live-fired on SalerySe.

  • The feedback gate, everywhere: changesets is the spine - feedback in, the Impact Analyst's played-back understanding + structured blast radius out (every affected use case / story / test / entity / persona / role / page / mock named with MODIFY/ADD/REMOVE), and NOTHING mutates until the tech lead acknowledges. Approve applies across all affected artifacts in one synchronized pass (spines regenerate stories+tests together; the ontology revises as a set; docs catch up); Discard leaves the memory untouched. Superseding, FAILED-midway honesty, and full audit (CHANGESET_PROPOSED/APPLIED/REJECTED). The shared FeedbackGate component replaced both instant-apply fine-tune boxes and gave the ontology its first feedback affordance.
  • Everything versions, nothing overwrites: use_case_versions (whole spine snapshots), ontology_versions / personas_versions / product_roles_versions / page_defs_versions (coherent sets), page_mocks_versions, e2e_specs_versions, doc_versions - all with automatic BASELINES so the first gated change preserves the state it found. Live-verified: ontology v1 baseline vs v2 diff shows exactly the acknowledged FeedbackLog change (artifact_type enum + 3 relations) and nine untouched entities byte-identical.
  • The memory grew from 4 artifacts to 7: personas (Persona Researcher: corpus + live web_search, every pain point EVIDENCED with a corpus quote or RESEARCHED with a source, never attributed to the client - 6 personas, 8 evidenced + 9 researched pains on SalerySe), product roles (Role Definer: what the product GRANTS, personas map N:1, permissions name ontology entities), and page definitions (Page Architect: owner role, purpose, primary actions, entities shown, stories served - 10 pages across 3 roles). Plus "add a use case" through the gate (a brief becomes ADD items the apply executes via draftSingleUseCase) and gap-targeted test generation ("Cover 4 untested criteria" wrote exactly 4, gaps to zero).
  • The pipeline: the menu order IS the journey - Research → Use cases → Ontology → Roles → Design → Pages → Mocks → E2E tests → Planning - enforced as a RATCHET (projects.phase): locked surfaces say exactly what unlocks them, "Complete phase" checks prerequisites server-side (design cannot complete without the designer's approval), completed phases stay editable forever because mock feedback must reach back into use cases. PhaseStepper on the Overview; PHASE_COMPLETED audited.
  • The DESIGNER is a real actor (user's call, option A): a new login role with its own /designer workspace OWNING the design language + mocks (write); both surfaces are read-only for the tech lead, who keeps one governance control (assign the designer). The designer's approval records approver + version and opens the mocks phase. Routing rule: a designer proposal whose ripple reaches beyond design/mocks stays PENDING for the tech lead's acknowledgment - each side approves what it owns.
  • Mock Builder: one sanitized static HTML mock per page definition wearing the approved tokens (CSS variables re-scoped from the preview css), rendered only in sandbox="" iframes, defense-in-depth stripping of scripts/handlers/external loads. E2E Writer: one Playwright spec file per use case from stories + ACs + pages; every data-testid it invents is the implementation contract; specs ship into the scaffold as tests/e2e/ with config + devDep. Planning: stories → sprints → releases → milestones, pure CRUD, one-sprint-per-story. Team & stakeholders: project_members with side + escalation ladder on the Overview.
  • The git connector + the first commit (round 1, live-fired): GitHub org antinoai, org-scoped fine-grained PAT validated against the API before storing (card on /admin/integrations, expiry surfaced), and antinoai/saleryse created by the platform with ONE parentless commit b569a4c - author the tech lead, committer "Supernova Platform" - carrying the runnable scaffold: Next + Tailwind v4 + Drizzle, src/db/schema.ts COMPILED from the acknowledged ontology, the generated globals.css, docs/ (now eight files incl. PERSONAS/ROLES), brand assets. GitHub quirk learned: git/blobs 409s on empty repos → auto_init + force-move main onto our parentless commit.
  • Tests: tests/e2e/pipeline.spec.ts (12 tests, all green) covers nav-order-as-pipeline, the stepper, phase locks, read-only enforcement on design/mocks for the TL, docs listing, designer workspace + both role bounces; global-setup gained tech_lead + designer storage states (the designer seeded idempotently as designer.e2e@antino.io). tests/e2e/live-fire.spec.ts (LIVE_FIRE=1) walks the ENTIRE ratchet with real agent runs: Role Definer → designer assign + approve → Mock Builder → E2E Writer → sprint on the plan board.
Design01:32 IST

The design language gets a fine-tune loop, versioning, and the FULL component wall

Versioning, because the user called it super-important: every Designer run (first generation or a refine) writes a COMPLETE row to design_versions - the whole DESIGN.md, the whole token…

  • Versioning, because the user called it super-important: every Designer run (first generation or a refine) writes a COMPLETE row to design_versions - the whole DESIGN.md, the whole token CSS, the feedback that caused it, who, when. projects.design_md/css stay the current-truth cache; the history is never overwritten. The DESIGN.md card wears a v-badge; a Versions ladder on the page shows each version with its feedback quote and effect. Existing SalerySe artifacts backfilled as v1.
  • The fine-tune loop (same idiom as use cases): plain language into the panel beside DESIGN.md; Designer regenerates BOTH artifacts with the note woven through and reports an "effect" sentence. Audited DESIGN_REFINED with the quote; agent_run_summary verdict REFINED.
  • The preview wall now carries the whole kit, not 15 components: 50+ across Buttons/ButtonGroup/Toggle/Kbd/Spinner, Badge/Avatar/Item, all form controls (InputGroup, NativeSelect, InputOTP, Combobox, Field), Calendar, Tabs/Accordion/Collapsible, Alert/Progress/Skeleton/Empty, Dialog/AlertDialog/Sheet/Drawer, Tooltip/Popover/HoverCard/DropdownMenu, Menubar/ContextMenu, Command, NavigationMenu/Breadcrumb/Pagination, Separator/AspectRatio/ScrollArea, Resizable/Carousel, Chart (series colors bridged to client tokens), a contained Sidebar demo, Sonner triggers, and Supernova's conversation primitives (Bubble/Message/Attachment/Marker). The monolith was split into preview/sections/* (user's ask), one file per family.
  • Portaled content wears the client's skin too: menus, dialogs, tooltips, and pickers mount at <body> - outside the scope - so every *Content carries the scope class (with the dark flag). Verified live: the Actions dropdown and the Edit-employee dialog both open in SalerySe's dark teal-charcoal, not Supernova's cream.
  • Live-fired the refine as Naman: "swap marigold for warm amber #B45309, radius down to 6px, keep the teal" -> v2 in 138s / $0.22 on Sonnet. The tokens came back EXACTLY as asked (--accent #B45309, --radius 0.375rem, teal untouched), and the effect note volunteered that it moved the accent foreground to white to hold AA contrast. The wall renders v2: squarer buttons, amber accents, teal kept, both themes.
  • Follow-up (user: "we should have everything"): the last two holdouts joined the wall - MessageScroller as a live pinned-to-latest feed, DirectionProvider as the same controls rendered LTR and RTL side by side (a behavioral component demonstrated by its behavior). All 59 kit components now show. Sidebar tokens joined the bridge so the contained sidebar demo wears client colors too.
Agents00:06 IST

Designer (agent #10), the preview wall, and the delivery engines move to Claude Sonnet

Designer (src/lib/ai/agent-designer.ts): codifies the client product's design language from the tech lead's written rules (law where present), the approved use cases, the entities, and the…

  • Designer (src/lib/ai/agent-designer.ts): codifies the client product's design language from the tech lead's written rules (law where present), the approved use cases, the entities, and the assets on file. Two artifacts on projects: DESIGN.md (identity, palette table with hex + rationale, typography, spacing, voice, component rules, and an honest Sources section saying what was assumed) and scoped shadcn tokens (design_css). The CSS is variables-only by contract AND rebuilt by a sanitizer (only .design-preview / .design-preview.dark selectors survive, only --custom-properties, conservative value charset, no url()/expression/@) because injected <style> is a real surface even on an internal tool. Verdict PASS when written rules existed, ASSUMED_DEFAULTS when Designer had to derive. Audited AGENT_DESIGNED.
  • The preview wall (/tech-lead/design/preview): fifteen real shadcn components wearing the client's tokens, light/dark toggle local to the wall (both themes must survive). Works because globals.css maps Tailwind colors through @theme inline vars, so scoped overrides cascade; Supernova's own clay accents (Button's default variant is bg-clay) are bridged onto the client primary inside the scope so house styling can't leak orange into the client's skin.
  • The delivery engines now run Claude Sonnet (user: "we need to build claude sonnet agents for the same"). SONNET = 'claude-sonnet-5' beside HAIKU in anthropic.ts; Spec Writer, Data Modeler, and Designer generate on it (their output is what a tech lead defends to a client), JSON repair rounds stay on Haiku. max_tokens raised to 16k on generation calls: Sonnet 5 runs adaptive thinking by default and thinking shares the output budget. llm_pricing gained the claude-sonnet-5 row ($3/$15 sticker) so llm_calls.cost_usd computes; the live Designer run logged 11,978 output tokens / $0.18 / 118s, attributed to Naman, verdict PASS.
  • Markdown tables: the dependency-free Markdown component now renders pipe tables (header + |---| separator) as real tables - DESIGN.md's palette was pipe soup without it, and every agent-authored doc benefits.
  • Live-fired as Naman on SalerySe with real written rules (teal #0E7C7B on warm off-white, marigold accent "at most once per screen", tabular numerals as law, never exclamation marks). Sonnet honored every rule and showed its work: per-token contrast ratios, dark mode as "the same role meanings in a teal-charcoal register", voice rules with concrete before/after copy. Verified on the wall in both themes.

16

Jul 2026

Thursday

21 shipped

Agents23:11 IST

Design language, the approve gate, and Data Modeler (agent #9)

Design language page (/tech-lead/design, user's ask): the product-being-built's brand. Logo uploads render on BOTH light and dark swatches (a logo that survives one background is not a logo…

  • Design language page (/tech-lead/design, user's ask): the product-being-built's brand. Logo uploads render on BOTH light and dark swatches (a logo that survives one background is not a logo yet), a brand-imagery gallery, and freeform written rules (projects.design_notes) the UX engine will read as law. Assets live in project_assets (bytea, image-mime whitelist, 8MB cap), served INLINE by the authz-gated /tech-lead/assets/[id] (same status+role recheck as documents). Audited PROJECT_ASSET_ADDED.
  • The approve gate: use cases finally have their sign-off button. APPROVED is what downstream engines feed on; a refine drops the status back so the button honestly reappears. Audited USE_CASE_APPROVED, high-signal in the trail (a client-facing commitment).
  • Data Modeler (agent #9, agent-ontology.ts): derives the product's data model from the use cases, approved first (verdict says UNAPPROVED_BASIS when it had to model from drafts). A regeneration replaces the project's whole set in one transaction: the model is one artifact. The Ontology page went real; the payroll mock (and mock-data.ts) is deleted.
  • Live-fired the full chain as Naman on SalerySe: approve the refined use case -> derive -> 11 entities, 84 fields, 21 relationships, 4 PII flags, and DesignEngagement's status lifecycle (DISCOVERY -> ... -> COMPLETED) cites BR-5/BR-6 from the use case as its transition gates. Used-by chips trace to the real use case. Logo upload verified through the inline route.
Agents22:18 IST

Spec Writer: the first delivery engine, and the spine goes real

Housekeeping first (user's 1): the 36 seeded projects purged plus the FNBPC test row - the projects table now holds only wizard-created engagements (SalerySe). G. Vedant's test AI cap…

  • Housekeeping first (user's 1): the 36 seeded projects purged plus the FNBPC test row - the projects table now holds only wizard-created engagements (SalerySe). G. Vedant's test AI cap cleared.
  • The spine is real (user's 2): use_cases, user_stories, test_cases, use_case_refinements under projects (flows/rules/acceptance as structured jsonb inside their parent; refinements as rows because they are the spec's audit trail). The mock pages swapped to queries with the depth intact - coverage gaps and blocking questions now compute from real rows.
  • Spec Writer (user's 3, src/lib/ai/agent-spec.ts, agent #8): reads the project's own corpus - deal thread, call recaps, notes, promises, uploaded text documents - and drafts the tree with PROVENANCE on every use case. One repair round on malformed JSON (the Email Drafter idiom; the first live run needed it). Runs ride the full spine: withLlm attribution + budget gate, llm_calls, agent_run_summary, audit AGENT_SPECCED (PREVIEW).
  • The fine-tune loop is live: refine() takes plain language, regenerates the use case, bumps the version, drops status to REFINED, records the note + effect in history, audits USE_CASE_REFINED.
  • Live-fired on SalerySe as Naman, and the run was honest twice over: the corpus was mostly the sales thread (the uploaded "scope doc" was filler), so draft v1 specced the sales process it actually saw, with 3 blocking questions saying what was missing. ONE refinement note ("this reads like the sales process, not the product") pivoted the whole spec to "Deliver UI/UX Design Engagement for SalerySe Product": new actors, ₹390,000 budget as a precondition, WCAG 2.1 AA and React mobile-first in the NFRs, delivery-side blocking questions. v2, refined, history kept.
  • Queued next (user's 4): ontology generation from approved use cases, story/test regeneration, Roles and Pages, discovery capture, and the Design-language page (logo + brand assets).
Product21:49 IST

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

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

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

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

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

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

Project wizard, mock-first

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

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

The tech lead gets a world: project-scoped workspace

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

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

Role-home stubs stop greeting and promising

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

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

The cockpits stop greeting and start briefing

User calls, in order: "Good afternoon, Priyadharshini" told the rep nothing and stacked a second display-size headline over the briefing's own (the two even disagreed - live pulses 941…

  • User calls, in order: "Good afternoon, Priyadharshini" told the rep nothing and stacked a second display-size headline over the briefing's own (the two even disagreed - live pulses 941 overdue, the 09:40 snapshot 944); then "SDR · My Day · Thursday, 16 Jul 2026 - why do u need this" - the sidebar names the page, the breadcrumb names the role, the briefing's own eyebrow carries the date. Triple-redundant chrome.
  • Both cockpits (SDR My Day, AE Desk): greeting h1 AND page eyebrow removed (an sr-only h1 keeps each page named for assistive tech; Sync now stays, aligned with the stats line). The page now opens directly on the numbers: stats sentence -> pulses -> briefing.
  • The shared briefing headline (daily-briefing.tsx) dropped from clamp(1.45-2rem) display size to a text-lg lead sentence - it is the title of the DAY, not a second title of the page. The whole working day fits above the fold now.
  • NOTE for other pages: eyebrows stay app-wide (they carry real orientation on deep pages); the cockpits are special because nav + briefing already say everything the eyebrow said.
Product15:36 IST

New brand mark: three shapes that survive 16px

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

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

Apollo answers are bought once: the domain-keyed warehouse

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

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

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

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

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

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

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

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

docs: the standing docs catch up with the standing facts

README was 9 days stale and asserting two reversed decisions: the app/marketing theme SPLIT (they share one warm Daylight token language via .app-theme since the unification) and db:push as…

  • README was 9 days stale and asserting two reversed decisions: the app/marketing theme SPLIT (they share one warm Daylight token language via .app-theme since the unification) and db:push as the schema apply path (targeted ALTER TABLE scripts are the rule — push applies every accumulated diff at once and nearly dropped live tables once). Both corrected; the docs index now lists PROSHORT / DATA-REALITY / NEXT-STEPS, and the entity-colour convention points at both sources (entity-colors.ts for charts, entity-tone.ts for the tint map).
  • PRODUCT: the agent roster count read "7 of 8" while listing 7 live + 2 planned — it is 7 of 9; the SDR role row predated Prospecting and now names it (ICP -> agent -> dedupe -> enrich -> real Zoho push).
  • ENTITIES: the header no longer recommends db:push.
Fix10:37 IST

The app gets its first error boundary; the sign-in "unexpected response" explained

User hit "An unexpected response was received from the server" on /sign-in. Root cause: a STALE TAB, not a code bug - every dev recompile (and every production deploy) rotates Next's…

  • User hit "An unexpected response was received from the server" on /sign-in. Root cause: a STALE TAB, not a code bug - every dev recompile (and every production deploy) rotates Next's server-action IDs, so a tab left open across one submits an action the server no longer recognises. A hard refresh fixed it on the spot.
  • The durable fix: src/app/error.tsx - the first error boundary this app has had. It detects the stale-action message and says the true thing ("This tab is out of date... refresh once and carry on, nothing was lost") with a one-click refresh; every other error gets a calm Daylight card with retry (unstable_retry - this Next's API, not the old reset), full refresh, and the digest for the server log. No more white pages or dev-overlay hieroglyphics for users.
  • Also verified the MEDDPICC score live as an AE (the browser session is Anisha now): 44/100 developing, amber bar, all 8 evidence rows, receipt line - exactly as designed.
Product09:59 IST

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

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

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

MEDDPICC gets its number

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

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

Every LLM rupee has a name

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

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

Audit trail: the small things

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

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

AI cost per person + per-user budget gate; models on the ledger

Attribution - withLlm() now resolves the acting user itself (explicit meta.userId, else the session cookie; cron/scripts honestly log null), so llm_calls.user_id fills for every in-app…

  • Attribution - withLlm() now resolves the acting user itself (explicit meta.userId, else the session cookie; cron/scripts honestly log null), so llm_calls.user_id fills for every in-app agent run. Before: 5 of 8 agents logged null and 63 of 116 calls were unattributable.
  • /admin/ai "By person" - who incurred what: calls, tokens, this-month + all-time ₹ per user, one "(unattributed)" row for cron/scripts/legacy, and an inline monthly budget editor (₹ in the UI, USD in the ledger). Over-budget people surface as a pulse in the header sentence.
  • The gate - users.ai_monthly_budget_usd (targeted ALTER, null = no cap; ₹0 = block all) enforced in src/lib/ai/budget.ts at the same withLlm choke point, reading the same ledger the page shows, so the gate and the display cannot disagree. Blocked runs throw a human-readable AiBudgetError and audit as AGENT_BUDGET_BLOCKED (FAILURE, loud); budget edits audit as AI_BUDGET_SET with before/after. New index llm_calls_user_time_idx.
  • Models are visible (user ask) - the By-agent table gains a Models column and Recent calls gains Who + Model, date-suffix stripped (haiku-4-5, not the SKU).
  • Found-by-testing: a sub-₹1 budget rounded to $0.00 and read as "no cap" while blocking everything - the action now refuses caps under ₹1 (0 stays the explicit block-all), and the Used bar treats 0 as blocked, not capless.
Data09:10 IST

Prospecting reaches the finish line: real Zoho writes, Apollo contacts, refine loop

The push is real (src/lib/prospecting/push.ts): "Save to Zoho CRM" now creates Accounts, Leads, and Contacts via the same zohoPost/zohoPut client Enrich writes with. A new company becomes…

  • The push is real (src/lib/prospecting/push.ts): "Save to Zoho CRM" now creates Accounts, Leads, and Contacts via the same zohoPost/zohoPut client Enrich writes with. A new company becomes Account + Lead — the Lead owned by the pushing SDR (via zoho_user_mappings) so it lands in their book, its Description carrying the match reason + buying-signal verdict. Every create is mirrored into the staging tables immediately (best-effort, sync repairs gaps) so the lead book doesn't wait for the next sync. Audited PROSPECTS_PUSHED, high-signal, outcome SUCCESS/PARTIAL/FAILURE; per-company outcomes land in the result screen and the pushed jsonb.
  • Dupes flipped from wall to doorway. The dedupe verdict used to LOCK a row; now it routes the push: existing ACCOUNT -> new decision-makers attach under it; existing LEAD -> fill-only field update (industry/employees/website written only where Zoho is empty — research never overwrites a human). Re-pushing an already-PUSHED row is a no-op, so a double-click can't double-create.
  • Contact dedupe: each contact's email is checked against zoho_contacts AND zoho_leads before create; hits are skipped and counted ("3 already in CRM"). No email = create — name-matching a 170k-row mirror invents false merges, which are worse than dupes.
  • Apollo integration (src/lib/prospecting/apollo.ts): decision-maker search (CEO/CTO/COO/Founder/VP-Eng titles) behind a new encrypted APOLLO_API_KEY on /admin/settings. Verified emails + LinkedIn merge over the research agent's people by name; the agent roster remains the no-key fallback with emails honestly empty. Apollo's locked-email placeholder is treated as "no email", never stored. Apollo failure never kills an enrich run.
  • Refine loop: a "Find more" bar on the companies step — extra context ("only Bangalore", "500+ employees") rides WITH the original ICP, the agent is told what it already found, and a JS-side name/domain dedupe guarantees appends-only. Searches now audit as PROSPECT_SEARCH_RUN (was mislabelled LEAD_NOTE_ADDED).
  • Verified without side effects: tsc + lint clean; every SQL statement in push.ts (mirrors, fill-only update, contact dedupe) executed against the real Azure schema inside a rolled-back transaction. The Zoho API call itself is deliberately un-fired — the first live push should be a one-company test from an SDR session.
Product08:58 IST

One entity colour language; sparkles and violet are gone

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

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

15

Jul 2026

Wednesday

7 shipped

Data15:34 IST

Prospecting workspace (dynamic), buying-signal scoring, audit surface, manual sync

Prospecting (/sdr/prospecting) — a real, DB-backed workspace: an ICP in plain English -> a web-search agent finds real companies (src/lib/prospecting/search.ts, reuses the Anthropic…

  • Prospecting (`/sdr/prospecting`) — a real, DB-backed workspace: an ICP in plain English -> a web-search agent finds real companies (src/lib/prospecting/search.ts, reuses the Anthropic web_search harness) -> dedupe against the Zoho staging tables (dedupe.ts, website/name match; verified 8/9 already-in-CRM live) -> store the keepers -> enrich each via the AI research agent -> push to Zoho (dry-run for now, no CRM writes). Four-step wizard (prospecting-wizard.tsx) with per-step selection gates, sticky action bars, and the entity colour language. New tables: prospecting_searches, prospect_companies, prospect_contacts, prospecting_preferences; new server actions in src/app/sdr/prospecting/actions.ts. Tested end-to-end in Chrome.
  • Buying signals (per SDR) — a rep sets their own buying signals in /sdr/settings (prospecting_preferences.buying_signals, audited). On enrich, a second scoring pass (score.ts, SignalScorer) reasons over the research dossier + the rep's signals and returns an evidence-linked verdict (verdict/score/opinion/matched[{signal,evidence}]/missing), stored on prospect_companies.buying_signal_score and shown in the enrich card. Verified live: OpenObserve scored Strong 85/100 with per-signal evidence.
  • Audit trail read surface (/admin/audit) — stopped dumping raw JSON. Each event is now an English sentence (per-action templates in event-view.tsx, defensive over the varying payloads; leads with the stored summary where present, diffs before/after where not), with an icon + colour per category from the entity palette, day-grouped with signal hierarchy (high-signal = filled tile, sign-ins = quiet ledger). Expand shows a readable field diff + a labelled request-context grid (raw JSON one click away). ::1->localhost.
  • Manual "Sync now" on /sdr (force-sync-button.tsx + src/lib/sync/actions.ts): on-demand Zoho incremental sync then both Outlook (MS Graph) mailbox deltas, guarded against overlap, audited. The scheduled counterpart is the cron.
  • Toasts moved top-right (ui/sonner.tsx) so they never cover a bottom action bar.
  • Local `.env` parity — added the Anthropic / Proshort / MS Graph keys locally (were only on the VM), so dev has the full agent + sync surface.
Agents08:43 IST

The Rohit walk: the org chart stops decorating, the briefing stops wrapping names

Walking the admin suite as Rohit (Command / Users / Audit / AI usage / Tech leads / Org chart), two real design-language breaches:

Walking the admin suite as Rohit (Command / Users / Audit / AI usage / Tech leads / Org chart), two real design-language breaches:

  • The org chart was the last page decorating with colour. Its L1-L4 depth ramp was neon indigo / blue / emerald / purple on bordered boxes, the exact thing Daylight bans (hierarchy comes from scale and weight, never a rainbow; the entity hues are warm and muted). Depth is now a ladder of prominence: clay at the root, fading down the ink ramp, elevation carrying the box, borders gone. The legend reads from the same LEVEL_STYLES, so it re-tuned itself.
  • The briefing's lead column is a MEASURE column (times "12:30", amounts, ages) and aligns beautifully at w-16 with tabular-nums. The Command Board's rep names are not measures: "Nitish Kumar Rana" wrapped to three lines. Names and company labels now get w-28 + truncate, and drop tabular.

Everything else on the admin suite held up: the team briefing reads with real intelligence ("Himanshu carries the biggest load: Rs 19.9 Cr with 20 slipping deals and 58 overdue tasks"), the audit trail shows agent runs with actor glyphs, the AI ledger costs every call.

Product00:47 IST

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

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

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

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

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

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

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

Data00:34 IST

Enrich closes the loop: the FIRST live Zoho write-back, plus company channels

Two upgrades the same day, from the user's asks ("a way to update Zoho with it"; "email phone linkedin needs to be figured out"):

Two upgrades the same day, from the user's asks ("a way to update Zoho with it"; "email phone linkedin needs to be figured out"):

  • Zoho write-back, live: the dossier's proposed field updates now carry an Update Zoho button. applyZohoUpdates (src/lib/enrichment/actions.ts) PUTs the fields to Zoho v8 (the first real Zoho write in the product — zohoPut had never been used), then updates our mirror, clears the applied proposals from the stored dossier, and audits ZOHO_FIELDS_UPDATED with full before/after. Field map is deliberate: leads get Industry / No_of_Employees / Website / City / State / Country; accounts add Phone + billing address fields. A lead's Email/Phone/LinkedIn are the person's — company research never writes them. Verified live on the ANSCER lead: 6 empty fields (industry, employees, website, city, state, country) filled in Zoho + mirror, audit row before all null / after all researched values, proposals section cleared itself.
  • Company channels in the dossier: company gained website / linkedin (company page URL, validated) / email / phone, rendered as actionable chips (site ↗, LinkedIn ↗, mailto:, tel:) under the identity row. Panel redesigned: ICP read as a banded row (emerald/amber/zinc wash by score), news as a mono-dated timeline, key people with initial avatars, updates as field-shaped diff rows ("empty → Bengaluru"). The model prompt now demands field-shaped values (city = the city name alone) and channels it actually found, never guessed.
  • Fix en route: drizzle expands a JS array param into N placeholders, so = any(${arr}::text[]) broke — string_to_array on a joined param instead.
Product00:29 IST

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

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

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

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

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

Agents00:29 IST

Enrich (agent 7): live web research on any company, including ones not in the CRM yet

The seventh agent, Enrich, answers "who is this company, really?" with the live web instead of the CRM's stale fields. Built on Anthropic's server-side web_search tool (verified on the…

The seventh agent, Enrich, answers "who is this company, really?" with the live web instead of the CRM's stale fields. Built on Anthropic's server-side web_search tool (verified on the user's key; ≤5 searches per run, each metered at $0.01 into llm_calls.web_searches and the cost ledger):

  • `src/lib/ai/agent-enrich.ts`: one Haiku call with web search → structured dossier: what they do / size / HQ, recent news (every item must carry its source URL — uncited news is dropped deterministically), key people, an honest ICP-fit read (strong/medium/weak) against Antino's ICP, ≤3 talking angles, and proposed CRM updates (current → proposed, only where the web contradicts or fills a listed field — propose-not-act, they land in Zoho with write-back). Verdict PASS/THIN/NOT_FOUND; run summary + trace-true totals; upsert into new enrichments table (unique per entity_kind + entity_id).
  • Three entity kinds: lead (company or corporate-email domain), account (name + website), and `prospect` — research-from-nothing: the company name typed into accounts search, stored lowercase. The /ae/accounts?q=LifeWise empty state now offers "Research LifeWise on the web"; the dossier renders right there, with a Propose as account button that audits ACCOUNT_ADD_PROPOSED (PREVIEW) with the dossier fields in after.
  • Surfaces (src/components/enrich-panel.tsx): accounts-search empty state, account hub, AE lead hub, SDR lead hub. Toast on start + finish (principle 13), receipt line (verdict · N web searches · ₹). Server actions in src/lib/enrichment/actions.ts audit AGENT_ENRICHED.
  • Eval added (5th agent eval): /api/evals/run case + run-evals.mjs asserts company identified, all news cited, valid ICP score, totals == ledger, cost < $0.15 (search-metered ceiling).
  • Verified live: Bajaj Finserv account (PASS, 4 cited news, ICP strong, $0.088); LifeWise prospect (found LifeWise Academy, honestly scored ICP weak — the openers even warn the name is ambiguous across companies); ANSCER Robotics lead (ICP strong, the real $5.4M Series A + US expansion in news, key people matching the actual email thread, 4 proposed field updates: industry, 73 employees, anscer.com, Bengaluru).
Product00:10 IST

The email reader gets its universe

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

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

14

Jul 2026

Tuesday

37 shipped

Product21:45 IST

The deep test tier: correctness, not rendering

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

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

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

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

Product21:02 IST

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

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

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

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

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

Product20:53 IST

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

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

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

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

Test infrastructure: route smoke suite + agent evals + CI

The honest audit said it: agents were verified live but had no eval suite, and the early Playwright pass was a one-off, not maintained. Built the first two tiers:

The honest audit said it: agents were verified live but had no eval suite, and the early Playwright pass was a one-off, not maintained. Built the first two tiers:

  • Route smoke suite (tests/e2e/, npm run test:e2e) — Playwright signs in one seeded user per role (global-setup samples live entity ids from the DB), then hits 37 routes across admin/AE/SDR/public — every static page plus real dynamic instances (deal hub, lead hubs, account, contact, recording, meeting event, a peek URL). Each must return <500, render a landmark, show no Next error state (the dev overlay text is checked through the shadow DOM — <nextjs-portal> itself always exists in dev), and log no serious console errors. 37/37 green in ~39s — and the suite caught a real bug on its maiden run: /admin/ai 500'd because as w(window) used a reserved SQL word. Fixed.
  • Agent evals (scripts/evals/run-evals.mjs, npm run test:evals) — a token-guarded eval endpoint (/api/evals/run) runs each live agent against a real fixture entity; the runner asserts hard invariants: valid artifact structure (8 MEDDPICC dims, scores from the enum, every non-unknown score carries evidence; brief sections within bounds; extraction caps), sane verdict, run totals == the llm_calls ledger exactly, and a per-run cost ceiling ($0.10). All 4 evals passed — Meeting Prep even exercised the REPAIRED path live.
  • CI (.github/workflows/ci.yml) — typecheck + lint on every push; e2e/evals run locally (need the dev DB + key + server).

Next tiers (planned): golden-fixture grounding snapshots per agent (deterministic, no-API), the 8–10 money-flow tests (peek, link-recording, run-agent with a mocked client, close-on-activity, handoff accept), nightly evals with trend tracking off agent_run_summary.

Product20:16 IST

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

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

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

Product20:12 IST

LLM cost + telemetry infrastructure: the ledger

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

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

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

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

Data19:38 IST

Tech lead, from messy Zoho text to a resolved person on every deal

Zoho's Tech_Lead_Name_Involved is free text and a mess: first names, spelling variants (Radhakant / Radhakanth), multi-person strings ("Arka and Gorish"), and notes ("NA", "Through Prem,…

Zoho's Tech_Lead_Name_Involved is free text and a mess: first names, spelling variants (Radhakant / Radhakanth), multi-person strings ("Arka and Gorish"), and notes ("NA", "Through Prem, hence no tech lead"). 205 deals tagged, 71 distinct values, only 14 linking to an app user. Fixed it as a resolution layer, not a blind rewrite:

  • `tech_lead_map` (new table + Drizzle def): one row per distinct raw value, mapping it to a clean_name (the canonical spelling we write back to Zoho) and, where the person is in the system, a user_id. Seeded by scripts/tech-lead/resolve.mjs, which auto-resolved 36 names / 143 of 205 deals (70%) by first-name match (TECH_LEAD/DEV preferred, spelling variants folded).
  • `/admin/tech-leads` review surface: the residue (unmatched names, multi-person, notes) is a human's call, so the page lists every value by status with a one-click suggestion, a teammate picker, a "just a name" option, "no tech lead", and an inline add-a-teammate flow for people not yet in the system (Arka = 24 deals, Divyansh, Sudhanshu…). Every resolution is audited (TECH_LEAD_RESOLVED) and re-arms the Zoho write-back.
  • Shown everywhere a deal is (user: "super super important"): one TECH_LEAD_VALUE SQL expression resolves the display name (clean when mapped, raw until reviewed, null when cleared), threaded through the deal hub (clickable to the teammate), pipeline kanban cards, the Desk slipping list + deal hover-card, the Accounts table top-deal, the Inbox deal threads, the Follow-ups list, account + contact hubs, the team page, and the deal peek pane, all via the shared DEAL_SELECT / TechLeadChip / deal-row components.
  • Write-back of the cleaned names to the Zoho source is next (write scope now granted); the map is the reviewed outbox.
Design19:35 IST

Every page polished for espresso: two systemic sweeps + borderless chips

Getting every app page to the landing's bar, by fixing the SYSTEM rather than 30 screens:

Getting every app page to the landing's bar, by fixing the SYSTEM rather than 30 screens:

  • Shadow sweep: 111 hardcoded shadow literals (93× the same raised string) across 50+ files replaced with the theme-aware shadow-rest / raised / float tokens, so every card now has real depth on espresso and the warm tint on cream. Only deliberate one-offs remain (sidebar ring, the changelog glow, kbd inset).
  • Central dark tint layer (globals.css): the ~110 light-assuming status tints (bg-amber-100 text-amber-700 chips, icon tiles, banners) are remapped once, in one place, for .dark/.section-dark — pale washes become translucent hue washes, emphasis text brightens to shades tuned to the dark Daylight palette. The shared status maps (leadStatus/leadTemp/dealStage) ride it with zero call-site changes.
  • Chips lose their borders (Daylight rule): Badge outline → tone wash; Button outline → raised tone step + shadow (no hairline); Kbd and Alert borderless. Badge grew semantic tones (success/warning/danger/info/clay/neutral) so pages converge on ONE chip language instead of hand-rolled spans.

Verified on the espresso app: Pipeline (kanban chips as hue washes), the deal hub (stage gate, MEDDPICC, timeline), Accounts. Also fixed a banned em dash in the pipeline briefing sentence.

Product19:29 IST

Goldenflitch staff are app users (decision: yes)

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

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

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

Product19:26 IST

The app goes espresso

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

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

Fix19:22 IST

/design goes espresso: the dark-mode contrast pass, fixed in the reusables

The user's read on "the app feels light but not the landing's way" became a direction: the design page now runs the full espresso dark theme as the proving ground. /design wears .app-theme…

The user's read on "the app feels light but not the landing's way" became a direction: the design page now runs the full espresso dark theme as the proving ground. /design wears .app-theme dark, an espresso rail (.section-dark sidebar with the landing wordmark register), the landing's ruled eyebrow on the header, and a dark-aware canvas (faint cream dots, warm glow, never a white haze). Global radius knob turned down 14px→10px (crisper at app density).

The contrast pass then went component by component, every fix in the REUSABLE layer so the whole app inherits:

  • Root cause found: .dark only remaps variables, so any text without an explicit color class inherited body's light-resolved ink — dark-on-dark across the page. Fix: the dark scope carries text-foreground (documented in the page).
  • Table rewritten (ui/table.tsx): mono uppercase header BAND on a tone step with a stronger rule, real cell padding (px-4 py-2.5), theme-real hover (row-hover token, not invisible muted/50), selected rows in the warm clay wash, footer on the strong rule. Now reads as a data grid in both themes; every table in the app upgrades for free.
  • Elevation became theme-aware: --shadow-rest/raised/float now route through --elev-* vars (warm-tinted on cream, real black weight on espresso — a 5% warm shadow is invisible on dark).
  • Skeleton / Progress / Slider tracks: bg-muted (a near-zero tone step on espresso) → foreground-relative washes, visible in both themes.
  • Scrims (dialog/sheet/drawer/alert-dialog): bg-black/10 gets dark:bg-black/45.
  • Sonner on the dark page pins theme="dark" (it otherwise follows the OS, not the page scope).
  • Canvas glow fix shipped to the light app too: the near-white radial that washed every app page pale at the top is now warm cream.
Agents19:12 IST

The agent team gets plain-English names

"Witness" and "Qualifier" mean nothing to a salesperson — the name should say what the agent does. Renamed the whole team and propagated everywhere: Daily Brief (Briefer) · Email Drafter…

"Witness" and "Qualifier" mean nothing to a salesperson — the name should say what the agent does. Renamed the whole team and propagated everywhere: Daily Brief (Briefer) · Email Drafter (Writer) · Call Notes (Witness) · Deal Check (Qualifier) · Meeting Prep (Researcher) · Promise Tracker (Chaser); planned: Meeting Minutes (MoM), What's Next (Sorter). Single source of truth: src/lib/ai/agents.ts. Propagation: every agent writes the new name into agent_run_summary.agent_name + audit actorLabel; all UI copy (panel toasts, buttons, receipts, StubButton hints, "Copilot draft" → "Draft email"); agent system prompts; 115 telemetry rows migrated to the new names. One deliberate exception: historical audit_events rows keep the labels they were written with — the audit trail is append-only and history is never rewritten.

Product19:03 IST

Accounts table: from a number ledger to a state view

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

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

Product18:56 IST

Accounts sorted by recency

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

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

Agents18:33 IST

The Chaser: promises tracked, kept, chased (agent #6) + the lead thread gets the matcher

People commit to things on threads and calls — "we'll send the proposal by Friday", "Vikrant will share it with the US team" — and those promises evaporate. Two ships close #13 completely:

People commit to things on threads and calls — "we'll send the proposal by Friday", "Vikrant will share it with the US team" — and those promises evaporate. Two ships close #13 completely:

The Chaser (src/lib/ai/agent-chaser.ts): reads an entity's deterministic sources — the matched email thread (full bodies) + the recorded calls' action items — and extracts each concrete commitment: crisp text, who made it, which side (WE/THEY), the due date only when one was actually stated (resolved against that source's date, never invented), and the exact source it came from. A fresh-context critic drops anything the sources don't support (extraction filters, it never repairs). Rows land in `commitments` with full provenance (source email/recording + agent run id; a dedupe index makes re-scans idempotent). The Promises panel (promises-panel.tsx) sits on the AE deal hub and both lead hubs: WE/THEY typeset rows, due chips (amber → rose when overdue), source links into the recording page, and in-place kept / dismiss — REAL writes to our table, audited as COMMITMENT_FULFILLED/COMMITMENT_DISMISSED (not previews). Verified live on centaurihs: 7 promises from 3 emails + 2 calls (6 ours, 1 theirs — Vikrant taking Antino to his US counterparts), all due dates correctly resolved to the Jul-14 proposal deadline, critic verdict FILTERED (it dropped an unsupported one), ₹0.79. 6 of 8 agents live.

The matcher reaches the lead thread (#13's leftover): getLeadTimeline now uses the same 4-branch corporate-domain matcher as the deal thread — exact + company-domain mail in ONE indexed pass, with a per-row exact flag driving "with this lead" vs "team thread @domain" (the old code only fell back to domain when exact matched nothing, hiding team threads entirely — SwiffyLabs' finance-team invoices now show on Ashish's lead). And a hazard found before it shipped: a lead at microsoft.com would have dragged 400+ dmarcreport@/no-reply@ notifications into their timeline — new NOISE_LOCALPARTS filter on the from-domain branch (431→15 real rows), exact-address matches untouched, deal side inherits the fix.

Agents18:17 IST

The Researcher: the pre-call brief (agent #5)

Walk into every meeting prepared. On the meeting-event page, "Brief me" → the Researcher gathers deterministic grounding (who the external attendees are in the CRM — lead/contact → their…

Walk into every meeting prepared. On the meeting-event page, "Brief me" → the Researcher gathers deterministic grounding (who the external attendees are in the CRM — lead/contact → their deal + account; the recent email thread both directions; the last recorded calls' recap + action items/risks) and writes the brief: headline goal · who's in the room · where it stands · still open · lead with · ask them. Same guarantees as the rest of the team: a fresh-context critic flags any fact not in the grounding (one repair round), meeting_briefs stores one row per (mailbox, meeting), agent_run_summary + AGENT_RESEARCHED audit event, receipt on the panel.

Verified live on the Right Horizons negotiation meeting — the brief nailed the deal's actual shape from 5 recorded calls + the thread: "Bridge the 150% budget gap… reworked two-phase proposal that fits their 25-30L constraint"; who: "Sunil Kawariya — decision-maker; firm on 25-30L, rejected three-phase model"; still open: Anil's internal greenlight; ask: "Has Anil signed off on the Phase 1 scope?". Files: src/lib/ai/agent-researcher.ts, scripts/agents/create-researcher-table.mjs, event-page actions.ts + researcher-panel.tsx. 5 of 8 agents live.

Product18:13 IST

AE Follow-ups page (the missing backlog home)

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

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

Agents18:06 IST

The Qualifier: MEDDPICC scored from real evidence (agent #4)

The deal hub's empty MEDDPICC scorecard is now filled by a real agent. Same harness as the agentic Writer (tool loop → fresh-context critic → repair), new job: score the 8 MEDDPICC…

The deal hub's empty MEDDPICC scorecard is now filled by a real agent. Same harness as the agentic Writer (tool loop → fresh-context critic → repair), new job: score the 8 MEDDPICC dimensions with the evidence that earned each score.

  • `src/lib/ai/agent-qualifier.ts` — INVESTIGATE via the deal tool belt, then emit strict JSON (per-dimension score unknown|weak|partial|strong + evidence, headline, gap, suggestedFocus). Guardrails: a non-unknown score with empty evidence demotes to unknown; JSON extracted substring-wise; a re-emit round recovers truncated output (the first live run failed exactly here — too-small max_tokens + a strict parse; fixed). The critic audits that scores are EARNED ("strong needs explicit confirmation, not a passing mention"), one repair round.
  • Stored in deal_qualifications (one row per deal, latest wins; scripts/agents/create-qualifier-table.mjs) + agent_run_summary + an AGENT_QUALIFIED audit event; RACI-gated runQualifier action.
  • `qualifier-panel.tsx` — headline read, Biggest gap, 8 score rows (dot-colored, evidence expands per row), a clay Next move, Run/Re-qualify, and the auditable-AI receipt (verdict · tokens · ₹).

Verified live on the centaurihs deal (the very deal linked via the new in-place connect — full circle): 14 tool calls (bundle×1, email×3, call-intel×2, transcript-search×8), verdict PASS, ₹1.47. The read is blunt and grounded: "Early-stage prospect with implicit need but no urgency, incumbent entanglement, and US-based economic buyers — poor fit for short-cycle deal"; Competition scored strong quoting the Jul-1 call (two incumbent EOR vendors, 3 offers extended); next move: "Before sending the proposal due Jul-14, confirm who at Centauri HQ will evaluate it and secure a warm introduction — otherwise proposal dies in inbox."

Product17:55 IST

Title-case names (initcap at the data layer)

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

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

Product17:50 IST

Timeline email previews: strip the chrome, keep the message

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

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

Docs16:29 IST

/changelog: the journal becomes the public changelog

Built the build-in-public marketing page: /changelog is generated live from this very file. src/lib/changelog.ts parses the ## YYYY-MM-DD — Title entries (72 across 8 days at launch),…

Built the build-in-public marketing page: `/changelog` is generated live from this very file. src/lib/changelog.ts parses the ## YYYY-MM-DD — Title entries (72 across 8 days at launch), groups by day, and derives one tag per entry from its title (Agents / Fix / Performance / Design / Data / Docs / Product). The page (landing register, Daylight tokens, SiteNav/SiteFooter): an espresso hero — "Every day, shipped." — with the progress measured as big numerals (shipped / days / per-day average), a per-day velocity bar chart with the peak called out and today's bar glowing, and tag filters (?tag=agents, server-rendered). Below: the latest entry featured in full, then a date-grouped timeline — a monument day rail (sticky), a colored spine with a node per entry, tag mark + title + teaser collapsed, full markdown on expand. force-dynamic so every journal edit appears immediately. The journal IS the changelog — no second copy to maintain.

Design16:02 IST

App-wide em-dash sweep (design copy rule 17)

Replaced 402 prose em dashes ( — inside rendered strings) with commas across 148 files, enforcing the no-em-dash copy rule everywhere, not just the Briefer. The sweep protected code…

Replaced 402 prose em dashes ( inside rendered strings) with commas across 148 files, enforcing the no-em-dash copy rule everywhere, not just the Briefer. The sweep protected code comments (skipped lines starting //, *, /*, and em dashes sitting in trailing line comments) and left the empty-value '—' table glyphs alone (a legit convention, not prose). Builds clean (tsc 0). A few start-of-text-node cases ({' '}— …) the line sweep missed are being caught by hand as spotted.

Agents15:53 IST

#10: the Writer is AGENTIC — tool loop + critic, live on the AE inbox

The Copilot draft is no longer a pre-stuffed prompt: it's a real agent. Built on the official SDK's Tool Runner (client.beta.messages.toolRunner, betaTool) — deliberately NOT the "Claude…

The Copilot draft is no longer a pre-stuffed prompt: it's a real agent. Built on the official SDK's Tool Runner (client.beta.messages.toolRunner, betaTool) — deliberately NOT the "Claude Agent SDK" package, which is Claude-Code-as-a-library (subprocess + filesystem tools; wrong fit inside a Next.js server action). The Tool Runner is the SDK's model-driven tool loop over tools we define — genuinely agentic, in-process.

  • `src/lib/ai/tools.ts` — the Writer's tool belt, READ-ONLY and deal-scoped by construction (ids resolved at build time; get_email refuses ids off this thread): get_deal_bundle (facts + thread INDEX), get_email (full body + attachments), get_call_intelligence (recap/action-items/risks), search_transcripts (FTS over the deal's linked call transcripts — the GIN index earning its keep). Every call is logged for the receipt.
  • `src/lib/ai/agent-writer.ts` — INVESTIGATE→DRAFT (toolRunner, Haiku per the model dial, max 8 iterations) → CRITIC (fresh context, judges the draft against the evidence the tools actually returned: no invented facts/numbers/commitments, exactly one ask) → one repair round on failure. Telemetry row per run in agent_run_summary (model/tokens/cost/verdict).
  • Receipt in the UI — the toast now says what the agent did: "Writer investigated (deal bundle×1 · email×3) · critic flagged & repaired".
  • Verified live on the GasTech deal: agent chose to open 3 emails, critic flagged, repair fixed it; grounded draft with one clear ask. ~9.9k tokens in / 344 out ≈ ₹1 per draft on Haiku.

Also this stretch: grounding.ts (deep thread grounding — full email bodies, meetings, call intel, attachments + receipt) now powers the SDR lead Writer; the deal Writer's fetchers became the agent's tools, as planned.

Design15:51 IST

Lead-hub polish + the company→account reality

Fixes from a live QA pass on the AE lead hub:

Fixes from a live QA pass on the AE lead hub:

  • Company is clickable, and never dead-ends. A lead's company is free text; only 44% map to a real Account (measured: 8,528 of 19,476; 56% don't). So the company links to the account hub when one exists (getLead now returns companyAccountId), otherwise to that company's leads (/ae/leads?q=). The accounts search also got a real empty state: "No account named X, see leads at X →" instead of a blank.
  • Details shows only fields that have a value (the Field primitive returns null when empty, and renders internal hrefs via next/link same-tab).
  • Right-rail cards are equal widthLeadActions had lg:self-start, which shrank it to content inside the flex column; now w-full.
  • Design-system exemplar: the daily briefing elevated (hero headline, ladder body tones, tone-wash separation not border-b), a toast standard (src/lib/ui/toast.ts), and a deterministic em-dash sanitizer on the Briefer. See DESIGN.md 16-18.
Product15:35 IST

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

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

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

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

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

Agents15:21 IST

Markdown renderer for user/AI-authored text (+ transcript backfill complete)

"Markdown to html is not working" — traced to two real homes: (1) lead descriptions carry AI-enriched markdown ( bullets, bold — e.g. the Target/Bandhan leads), and (2) one transcript turn…

"Markdown to html is not working" — traced to two real homes: (1) lead descriptions carry AI-enriched markdown (* bullets, **bold** — e.g. the Target/Bandhan leads), and (2) one transcript turn where the prospect pasted formatted requirements into the meeting chat and Proshort merged it into the transcript. (The Proshort long fields/insights have zero literal markdown — checked all 548.) Built src/components/markdown.tsx: a tiny dependency-free renderer (bold/italic/code/links/headings/bullet+numbered lists) that outputs React nodes, never injected HTML (XSS-safe by construction), styled on the DESIGN text scale (clay-dot bullets, mono numerals, semibold ink emphasis). Wired into: transcript turns (recording page), AE + SDR lead descriptions, and the deal "What they want" panel. Verified on the Target lead + the InfraKeys transcript.

Also: the transcript backfill finished — 498/548 recordings now have speaker/timestamped transcripts (50 genuinely empty, 0 failed).

Data14:38 IST

Recording page v4: chat transcript, deadline chips, "not in your CRM" pulse + Add to CRM

Four upgrades on top of the tabbed rebuild, plus three new design principles:

Four upgrades on top of the tabbed rebuild, plus three new design principles:

  • Chat transcript — buyer left (white cards) / seller right (clay wash), full-width, with the buyer/seller labels anchoring each side. The join is proshort_recording_parties.speaker_name → affiliation. You can read the negotiation's rhythm at a glance.
  • Parser coverage — action items without an "(Org)" ("Mahak - Share the framework… - No deadline specified") now parse: owner matched to the call's external parties for the right avatar tint, and the trailing deadline becomes a chip (amber when real, muted for "no deadline"). Checked: there is NO literal markdown (**/##) anywhere in the 548 recordings — the "raw text" smell was unparsed structure, not un-rendered markdown.
  • "Not in your CRM" pulse + actiongetRecordingParties now carries crmKind (contact | lead | null by email). An external participant who exists nowhere in Zoho gets an attention Pulse + an Add to CRM button → proposeContact server action logs CONTACT_ADD_PROPOSED (PREVIEW) with the full proposed record to the audit spine; button flips to "✓ proposed" + toast. Captured externals show a Lead/Contact chip.
  • Tabs look like tabs — the line variant: bold active label + clay underline.

DESIGN.md principles 13–15 (from this session's gaps): 13 Every action is acknowledged (toast naming what happened + what happens next, or the control flips to its done-state — never silence); 14 The text color scale (ink → body /85 → meta muted → mono eyebrow; tabular-nums; one clay number per surface; never bold whole sentences); 15 Parse structure out of strings (owner/title/deadline embedded in a string is structure wearing a costume — split and typeset it, never dump the bullet).

Agents14:34 IST

The Witness: notes write themselves from the call recording

"Add note" was a hollow placeholder string. Replaced it with the Witness — on a deal, ✨ Draft from last call composes a real minute-of-meeting from the deal's most recent Proshort…

"Add note" was a hollow placeholder string. Replaced it with the Witness — on a deal, ✨ Draft from last call composes a real minute-of-meeting from the deal's most recent Proshort recording: recap + action items + risks + pain points + competition. Deterministic (src/lib/notes/recording-note.ts composes from the already-extracted proshort_recording_insights — no LLM, so it never invents; every line traces to the transcript). Matched to the deal via its buying-committee contacts or the account name. draftDealNote action + a proper note-compose panel in the deal rail (editable textarea, toast acknowledgement, Save). Preview until Zoho write-back. Verified live on centaurihs (Jul 10 call → recap + 4 action items + 3 risks + competitor). Next: same for leads + auto-fill on "Log call".

Design14:23 IST

Recording page v3: three jobs, three tabs (10/10 pass)

The single-scroll rebuild still read poorly (lopsided hero cards, a wall of 9 uniform risk bullets, a starved rail, no visual anchor). The insight that fixed it: a recorded call has three…

The single-scroll rebuild still read poorly (lopsided hero cards, a wall of 9 uniform risk bullets, a starved rail, no visual anchor). The insight that fixed it: a recorded call has three different jobs — act on it, mine it, read it — so the page is now Overview / Signals / Transcript tabs with a persistent context rail. What made it sing:

  • Parse the structure inside insight strings instead of dumping flat bullets. "Owner (Org) — task" → numbered checklist with initials avatars (clay = Antino, sky = prospect); "Title — detail" → semibold lead-in + muted detail. Same InsightText parser across Signals.
  • Talk time / share of voice — computed from the word-level timestamps (proshort_transcript_segments): a stacked bar + % per speaker (e.g. prospect 55% — a healthy discovery signal). Duration ("24-min call") now in the header sentence and on the video card.
  • Turns, not fragments — 272 utterances merged into 69 speaker turns, per-speaker stable colors, timestamps; transcript flows on the surface in its own tab (no boxed inner scroll).
  • The video is the anchor — the rail leads with a real thumbnail card (play overlay + duration badge → recording URL), then Linked in CRM, then participants with avatars + external count.

getRecordingTranscript gained end_sec. Register: product (earned familiarity — Gong-class patterns, Daylight language).

Product14:20 IST

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

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

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

Product13:44 IST

Close-on-activity everywhere + overlay-consistent counts

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

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

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

Close-on-activity: doing the work closes the follow-up (the durable fix)

The backlog rebuilds because "mark done" is a separate step reps skip. So make the work itself close the task. On the SDR lead hub, logging real activity — Send email / Log a call / Book a…

The backlog rebuilds because "mark done" is a separate step reps skip. So make the work itself close the task. On the SDR lead hub, logging real activity — Send email / Log a call / Book a meeting — now completes the lead's next open follow-up in the same action (completeNextForLead, subject-matched to the lead), toasts "Follow-up marked done ✓ — you just did it", and refreshes so it vanishes. The manual Done on the lead's task list now persists too (was toast-only). Both write the task_completions overlay + an audit event (reversible, PREVIEW).

  • Extracted a shared, role-flexible src/lib/tasks/actions.ts (completeTasks bulk + completeNextForLead); the cleanup panel re-exports from it. getLeadTasks now excludes cleared tasks (overlay), so the hub reflects it instantly. Verified end-to-end (lead "Sagar/Anaplan" → "Followup 2" auto-completes + hides).
  • Not yet wired: the inbox (parallel session's /ae/inbox) and the AE lead hub (inline, no rail) — same pattern applies when those are touched.
Agents13:25 IST

Follow-up cleanup: clear what's already done (+ briefing below the greeting)

The overdue-task mountain (19.5k team-wide) is mostly uncleared, not undone — reps did the work and never ticked the box. Built an evidence-based cleanup that recognises done/moot…

The overdue-task mountain (19.5k team-wide) is mostly uncleared, not undone — reps did the work and never ticked the box. Built an evidence-based cleanup that recognises done/moot follow-ups and clears them for the rep, reversibly.

  • `task_completions` overlay — Zoho write scope isn't granted, so we don't fake the mirror; a completion lives app-side and every open-task query LEFT-excludes it (cleared tasks vanish immediately). Becomes the write-back outbox when scope lands.
  • `getTaskCleanup(zid)` (deterministic — no LLM decides a close): matches each overdue task to its lead (subject-based, like getTasks) AND deal (what_id), then buckets: Moot (deal closed / lead lost/converted), Looks already done (lead or deal moved after the task was due, incl. an email off the CRM — the exact "did it, never logged it" case), Stale (>90d — suggest, don't auto). Each row carries its evidence.
  • `completeTasks` action — bulk clear → overlay + ONE audit event (reversible, PREVIEW).
  • `CleanupPanel` on /sdr/tasks: three bucket cards with evidence + "Clear N". Honest by design — it clears only what it can prove (~4-5% here; the CRM lacks evidence for the rest, which correctly stay). The durable fix remains close-on-activity (prevention), noted for next.
  • Layout: the daily briefing now sits below each cockpit's greeting/KPI header (AE Desk, SDR My Day, Command) — greeting first, then the brief.
Fix13:22 IST

Proshort: incremental 10-min sync + a false-match fix

The mirror had gone stale (525 recordings, last one 12-Jul) because the only tooling was a full backfill (~10 min of throttled calls — too heavy to run often). Re-ran it (525 → 548, now…

The mirror had gone stale (525 recordings, last one 12-Jul) because the only tooling was a full backfill (~10 min of throttled calls — too heavy to run often). Re-ran it (525 → 548, now current through today 14-Jul 11:30), then built the missing incremental path:

  • `src/lib/proshort/sync.ts`syncProshortIncremental(): walks the recordings list newest-first and stops as soon as a page holds nothing new, so a routine run is 1 list call + N details (usually N=0). Also refreshes the last 2 days of recordings, whose insights Proshort finishes generating after the call. Writes a DELTA batch row.
  • `POST/GET /api/proshort/sync` — token-guarded (shared token or Vercel Cron's Bearer $CRON_SECRET).
  • Every 10 minutes: vercel.json cron (*/10 * * * *) for deploy, + scripts/proshort/watch.mjs for local (a loop that hits the route; node scripts/proshort/watch.mjs).
  • Bug fix — false recording matches: getMeetingEvent's matcher joined on any shared party email, so a recording could link to a meeting merely because a colleague was in both (only the 30-min window prevented it). Now requires a genuine external party via the shared nonCorporateDomainsSql (which already excludes our own tenants + free providers).

Finding: the "Negotiation Discussion — Right Horizon x Antino" (13-Jul) has no recording because that call was never recorded — the mirror is current and 16 other calls exist from that day; Right Horizons' last recorded call is 03-Jul.

Fix12:45 IST

Two calendar-data bugs found via the briefing: recurring subjects + a 5:30 timezone error

The briefing surfaced two real mirror bugs (both now fixed in the shared DB + at the source):

The briefing surfaced two real mirror bugs (both now fixed in the shared DB + at the source):

  1. 01Recurring occurrences had no subject. 12,813 of 23,523 graph_events (every event_type='occurrence') had subject/organizer null — Graph puts those on the series master, and occurrences only reference it. Backfilled all 12,813 from their masters (subject, organizer, is_online_meeting, location, body_preview). "(no subject)" is gone.
  2. 02Every meeting was 5:30 early (UTC parsed as IST). graph/importer.ts's dt() did new Date(String(v)). Graph event start/end (dateTimeTimeZone) is a naive timestamp (zone in a separate field, always UTC here); JS parses a bare timestamp as local, so on the IST host every event stored 5:30 early — breaking times AND which-day bucketing (evening meetings fell into the wrong day, so "today's meetings" mis-timed and mis-counted). Fix: dt() now appends Z when there's no zone marker (message receivedDateTime/sentDateTime already carry Z, so they're untouched and were always correct). Backfilled graph_events.start_at/end_at +5:30 (one-time; verified against Anisha's real Outlook: 12:30 / 16:00 / 19:00 / 20:00 — exact match). Blast radius confirmed to be events only (the 4 dt() call sites: event start/end broken, message received/sent fine).

Regenerated all 17 briefings — times, day, and counts now correct.

Agents12:24 IST

The Briefer: the daily briefing agent (first of the agent team) is LIVE

The API key landed (valid — verified), so the agent program is unblocked. Built the Briefer end-to-end and put it as the first thing on the Desk / My Day / Command board.

The API key landed (valid — verified), so the agent program is unblocked. Built the Briefer end-to-end and put it as the first thing on the Desk / My Day / Command board.

  • Storage — two app-owned tables (src/db/schema.ts + scripts/briefings/create-tables.mjs): agent_run_summary (per-run telemetry every agent writes — model/tokens/cost/verdict; the audit agent_run_id points here) and briefings (one row per user per day; sections JSONB; unique(user_id, briefing_date)).
  • Section ownership done rightsrc/lib/briefing/facts.ts computes every NUMBER in SQL, role-scoped (AE/SDR = their book; HEAD_OF_SALES/ADMIN = team roll-up). briefer.ts (Haiku) writes prose only; the schedule (today's meetings, exact IST times), at-risk deals, and rep lists are rendered deterministically from facts — the model never touches a time or amount. This fixed the first-cut bug where the model narrated meetings (wrong/empty bullets).
  • SurfaceDailyBriefing component: headline + aligned mono lists (time/amount lead) + prose sections + a highlighted "Your one move" + an auditable-AI receipt (agent · model · "numbers computed from your live CRM"). Lazy-generates on first view; getAnthropic() now prefers process.env.ANTHROPIC_API_KEY.
  • BatchPOST /api/briefings/generate (token-guarded, the cron target). Generated 17/17 live briefings: Rohit (team: "₹42.3 Cr slipping… urgency starts with Himanshu and Harshad") + every mapped SDR/AE. Reads like a real chief-of-staff.

Format: bullets everywhere. Every section is now short bullet points (the model writes 2-4 one-sentence bullets per section, never paragraphs); data lists lead with a mono time/amount, narrative bullets with a clay dot. Regenerated all 17.

Enrichment + clickable links. Three data sources added to the facts layer (all numbers/names still code-computed): (1) meeting prep — each today's meeting is matched to a deal via an external attendee → shows "— FedX · Qualification · no next step"; (2) "Waiting on your reply" — the actual unanswered threads (lead + subject + days-waiting), not just a count; (3) "Follow up from your recent calls" — Proshort call intelligence (open action items + ⚠ risks from the last 10 days of recorded calls the user was on). Every list item now carries a ref and renders as a link (role-aware): meetings → the event page, deals → the deal hub, threads → the lead, calls → the recording. Verified: clicking a meeting opens its detail (correct 16:00 IST, real subject).

Still to do: the pre-dawn cron trigger + the rest of the agent team (MoM, Sorter, Qualifier, Researcher, Chaser) + the critic pass. #11 is now in progress (Briefer shipped).

Data10:47 IST

Corporate-domain email matcher (#13): the whole company thread, not one contact

A user question — "why am I not seeing the latest emails on this deal?" — turned out to be two things. First, a diagnosis that cleared the air: nothing was stale. Both mirrors are current…

A user question — "why am I not seeing the latest emails on this deal?" — turned out to be two things. First, a diagnosis that cleared the air: nothing was stale. Both mirrors are current (Zoho to 07-13/14, Graph email to today), and on the TruBoard Cleantech deal Jul 10 genuinely IS the newest email — confirmed by scanning the whole mirror for any TruBoard mail after it (zero). Anisha's mailbox is synced (anisha@antino.io.io not .com; her login/CRM identity vs her mailbox differ, bridged by the mapping).

But the diagnosis surfaced the real limitation: the deal's email thread matched only its mapped Zoho contacts — here a single contact, S. Kadhirvel — while the person actually driving the thread, Sohail Usmani (@truboardpartners.com), isn't a Zoho contact at all. His mail showed only incidentally (he cc'd Kadhirvel); drop that cc and it'd vanish. The whole TruBoard account has a team in Zoho (Srickant, Vipul, Rakesh…), none attached to this deal.

The fix (#13): match a deal's emails by the company domain, not just its contacts. src/lib/graph/match.ts — a reusable matcher: corporateDomains() (derive domains from the deal + account contacts, dropping a personal/internal exclusion list — gmail/yahoo/… and antino.com/antino.io/goldenflitch.com, so we never over-match free providers or internal mail) + matchedMessageIds(emails, domains) (a UNION of four branches: from-email, from-domain, recipient-email, recipient-domain). Wired into getDealTimeline (the thread — full 4-way match) and getDealInbox (the left-pane ranking — exact-email + from-domain only). TruBoard now surfaces 10 of Sohail's emails directly, plus the rest of the account's conversation.

The performance was the hard part (the whole reason it's worth logging): the natural shapes are traps. A top-level OR … EXISTS predicate → 8,305 ms (seq-scans all 170k messages). IN (select … from cte)1,078 ms (a CTE defeats the functional index — Postgres hash-semi-joins with a full seq scan; confirmed via EXPLAIN). The fix: pass emails/domains as array parameters and use = any($arr) in a UNION of index-backed branches29 ms. Added one index (idx_gmr_domain_lower on lower(split_part(email,'@',2)) for the recipient side) to scripts/perf/add-indexes.mjs. The inbox ranking stayed lean (exact-email + from-domain, recipient-domain dropped — it cost +480ms for barely any ranking change): 162 ms → 54 ms. Lesson banked: never match email sets against a CTE with `IN`/`EXISTS`; materialize to an array and `= any` the functional index. Second gotcha (caught in the browser after the first commit): ` sql${jsArray}::text[] renders as a **row constructor** ($1,$2,…)::text[] — a cast error — because drizzle expands a JS array into a paren-list. Build the literal explicitly: sqlarray[${sql.join(vals.map(v => sql${v}), sql, )}]::text[] array[$1,$2,…]::text[]. Verified via PgDialect().sqlToQuery + a live getDealTimeline run, not just a raw-pg` scratch (which had hidden the bug — raw pg accepts a single array param, drizzle doesn't render one).

13

Jul 2026

Monday

6 shipped

Product19:36 IST

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

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

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

Data19:34 IST

#17: Proshort tables in the Drizzle schema (db:push no longer drops them)

src/db/proshort-schema.ts already mirrored the 5 Proshort tables — but it was never registered in drizzle.config.ts, so drizzle-kit didn't read it and db:push still generated DROP…

src/db/proshort-schema.ts already mirrored the 5 Proshort tables — but it was never registered in `drizzle.config.ts`, so drizzle-kit didn't read it and db:push still generated DROP statements for proshort_* (525 recordings + 22k insights = data loss). Fix: added it to the config's schema array, plus the one missing index def (proshort_parties_email_idx on lower(email)) so the schema matches the live DB exactly (columns, PKs, indexes all verified against information_schema). Did not run push to "prove" it — that's the hazard; the fix is that drizzle-kit now knows the tables. Updated the memory (never-drizzle-push → "cautious": the drop hazard is resolved, but push still applies all diffs at once, so prefer targeted ALTER TABLE) + DATA-REALITY.md.

Fix07:25 IST

AE deal inbox (Outlook-style workspace) + timeline HTML sanitization

"the AE also needs to have an inbox similar to what we built for SDR — without inbox none of this has any value." Built /ae/inbox, the SDR inbox one level up the ontology (deals, not…

"the AE also needs to have an inbox similar to what we built for SDR — without inbox none of this has any value." Built `/ae/inbox`, the SDR inbox one level up the ontology (deals, not leads). Same two-pane, mail-client shape: left = the AE's deals split into "In conversation" (open deals whose buying committee — primary contact + contact-role members — has a live email thread in the Graph mirror, ranked by most-recent message across the whole pipeline) and "Open · quiet" (open deals with no live thread — the ones going dark, paginated); right = the deal header (stage chip, ₹, account, contact, Deal hub ↗) + the unified thread (emails + meetings + calls + notes + tasks via getDealTimeline) + a Copilot-draft compose box (preview — nothing sends). New data layer getDealInbox (verified 162ms / 12 real conversations for Anisha) and draftReplyForDeal (Haiku, grounded in the deal's real thread). Inbox added as the AE's #2 nav item. Note: the Copilot draft currently 401s — the ANTHROPIC_API_KEY in .env is expired (wiring is correct; SDR copilot fails identically) — a key rotation, not a code fix.

HTML sanitization — the thread was leaking raw markup (<div style="display:flex…">…) from Zoho notes pasted out of Proshort and Outlook body fragments, since we render detail as text. New htmlToText helper (src/lib/format/text.ts: strips tags, keeps block breaks as newlines, decodes the common entities incl. emoji &#…;) wired into `getDealTimeline`, `getLeadTimeline`, and the peek pane (email body + note content). Deal hub, both inboxes, and every peek now render clean prose. Also promoted the deal description to a clay-accented "What they want" brief at the top of the Deal hub Details card (was a muted grey footnote under the field grid) — narrative leads, facts follow.

Perf note (not a bug): /ae/pipeline?peek=… logged 3.5s in dev. Traced it — getPipeline 29ms + peekDeal 24ms = ~60ms of real work; the rest is Next.js dev-mode render overhead (Turbopack + React dev renderer, amplified by concurrent session load). Production build serves that route in ~100ms. No code change; the peek is a full server round-trip by design (URL-driven master-detail, not a client Sheet).

Data07:23 IST

Breadcrumbs show entity names, not zoho PKs

The breadcrumb rendered the raw id for [id] routes (e.g. AE / Deals / 4350976000054900540) because AppBreadcrumbs is a client component with no way to resolve a name. Fixed with a tiny…

The breadcrumb rendered the raw id for [id] routes (e.g. AE / Deals / 4350976000054900540) because AppBreadcrumbs is a client component with no way to resolve a name. Fixed with a tiny context (src/components/breadcrumb-title.tsx): BreadcrumbTitleProvider wraps the shell content, and each detail page renders an invisible <BreadcrumbTitle name={entity.name} /> that registers its name via effect; AppBreadcrumbs reads it and swaps the final crumb's label. Wired into all 9 detail pages (deal, account, contact, teammate, email, recording, meeting-event, AE + SDR lead). Now it reads AE / Deals / Defect Detection on Steel Sheets.

Product07:14 IST

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

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

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

Product07:11 IST

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

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

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

12

Jul 2026

Sunday

25 shipped

Design22:09 IST

The page philosophy: whole app re-skinned to the landing (ultracode)

The app didn't read like the marketing landing — garish chart colors, flat surfaces, KPI-tile scoreboards, no motion, espresso buttons. Codified a page philosophy and rolled it across every…

The app didn't read like the marketing landing — garish chart colors, flat surfaces, KPI-tile scoreboards, no motion, espresso buttons. Codified a page philosophy and rolled it across every app page via ultracode workflows.

The philosophy (now the app-wide rule): a page header is a mono eyebrow (clay dot) → a big font-light title → one full-width briefing sentence weaving the key numbers as prose (clay for the key metric) — not a grid of KPI stat-tiles (DESIGN principle 10, "briefing > scoreboard"). Attention is a Pulse only on what's wrong/slipping — never an alarm-number board. Cards lift, never border; sections carry mono eyebrows; numbers are font-mono tabular-nums; entrance/hover motion is reduced-motion-safe (animate-in, never the buggy Reveal wrapper).

Color: the 8-hue entity palette was bright/saturated/cool (#008300 green, #2a78d6 blue, #e34948 red…), clashing on warm cream. Retuned all three sources — the --color-entity-* CSS vars, the JS base/text hexes in entity-colors.ts, and the ENTITY_RAMPS pie ramps in charts.tsx — to muted, cohesive Daylight hues (deal=clay, lead=sage, account=slate-blue, task=amber + muted violet/teal/rose/rust). Buttons: the Button default variant is now the landing's clay pill, so auth + app primary CTAs match the site.

Rollout (workflows): the command page as the exemplar (briefing header + pulses + warm charts + a real horizontal-stacked-bar RepScoreboard) → batch 1 (10 home/admin pages) → batch 2/3 (17 SDR+AE pages) → settings (5 role pages). Every batch preserved functionality — verified in-browser as Anisha (AE): the AE Desk, Pipeline (deal peek still opens), the Deal hub (peek timeline + stage-gate + action rail all intact, "Advance stage" now clay), and settings (change-password form intact). ~40 pages, TSC clean throughout. Note: keep concurrent workflow count modest — running several at once alongside the sibling session starved the dev server once.

Performance21:57 IST

Performance: /ae/meetings was 24.8s → sub-second (query + indexes)

/ae/meetings took 24.8s in application code. Timed the DAL: getMeetingSignals's "unlinked" query was 8.6s — its not exists correlated-joined all 19k zoho_leads per attendee per meeting with…

/ae/meetings took 24.8s in application code. Timed the DAL: getMeetingSignals's "unlinked" query was 8.6s — its not exists correlated-joined all 19k `zoho_leads` per attendee per meeting with an OR domain-match (split_part(email,'@',2)), non-sargable, re-scanning the whole leads table repeatedly. Rewrote it to materialize the lead-email + lead-domain sets once as hashable CTEs, then check membership: 8,588ms → 240ms (~35×), identical 44 rows. Then the root cause app-wide: no functional indexes existed on the email/mailbox JOIN keys, but "email is the universal join key" and every join uses lower(...), so the PK/FK indexes were unusable — seq scans across recipients (753k), messages (148k), contacts (140k), attendees (44k). Added 7 functional indexes (lower(email) / lower(mailbox_email) / lower(from_email) on graph_events/attendees/messages/recipients + zoho_leads/contacts) via CREATE INDEX CONCURRENTLY + ANALYZE. getMeetings events query: 408ms → 172ms. Net: the meetings page drops from ~9s of DB time to <1s. Indexes are version-controlled in scripts/perf/add-indexes.mjs (idempotent; not via db:push).

Then the app-wide audit (task #19, a subagent that timed every DAL query against the live DB): one genuine outlier remained — `getTasks` (SDR follow-ups) at 2.6s, a per-task left join lateral scanning all 3,298 of the owner's leads filtering on unindexed lower(company). Added a composite functional index zoho_leads (owner, lower(company))2,655ms → 196ms warm (also speeds getLeadTasks + the lead-timeline task block). Added the cheap graph_messages (lower(split_part(from_email,'@',2))) for the rare lead-timeline domain fallback. The audit confirmed everything else (AE, metrics, peek, SLA, attachment DALs) is already FK/PK-indexed and sits at the ~150ms remote-DB network floor — no more indexes needed. 9 perf indexes total, all in scripts/perf/add-indexes.mjs.

Design20:27 IST

Landing rebuilt as the founding-story arc (Company Brain), 10/10 polish

The landing sold a narrow slice ("agentic sales OS"). Grounded in the two source docs the user supplied — antino-supernova-deck.pdf (the founding story: services→outcomes, the 11:47 PM…

The landing sold a narrow slice ("agentic sales OS"). Grounded in the two source docs the user supplied — antino-supernova-deck.pdf (the founding story: services→outcomes, the 11:47 PM resignation, four fault lines, the harness, the Company Brain, "priced like bread not oven-hours") and artifacts-library.html (the real product: a contract-to-production platform, Stages 0→6, maker→gate→checker, artifacts pinned to real standards) — rebuilt / as the full narrative arc: Hero → 11:47 email → four fault lines → the harness (translation chain) → the Company Brain reveal → the living pipeline (0→6 + HITL evidence gate) → the Rosetta-Stone moat → proof (85%+, ontology beats model) → new-world finale + CTA → Apple-style FAQ. Retired the sales-only sections; retitled "Sales OS" → "the Company Brain". Built + polished via two ultracode workflows (5-section build, then a 9-agent 10/10 polish pass against a shared design rubric). Hard lesson: the Reveal scroll-wrapper left sections invisible — all landing sections are now plain server components. Verified end-to-end: zero inter-section gaps (measured via DOM), TSC clean, every section renders. New ember starburst brand mark + favicon (clay→amber on espresso, the deck's collapse→burst motif) replaces the violet nova. Design language unified — app now shares the landing's warm Daylight tokens (.app-theme = :root).

Product20:24 IST

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

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

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

Product19:53 IST

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

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

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

Design18:53 IST

One design language: app adopts the landing's Daylight tokens

The logged-in app and the marketing site had diverged — landing warm Daylight (cream #edeae0, espresso ink, clay accent), app cool shadcn-zinc via the .app-theme scope. User: "why is the…

The logged-in app and the marketing site had diverged — landing warm Daylight (cream #edeae0, espresso ink, clay accent), app cool shadcn-zinc via the .app-theme scope. User: "why is the design language of app pages and landing page different — merge this; I want the landing's design language in all the pages." So .app-theme in globals.css now mirrors the :root Daylight layer: cream page, warm #fbfaf4 cards, espresso ink, clay `#c2613f` on primary/ring/links. One softening — --accent (dense shadcn hover surface) stays a warm neutral #efece2, not clay, so dropdown/ghost hovers don't flare orange. .app-canvas warmed (espresso dots + cream wash). Landing look unchanged (merge, not restyle). Verified on /design: raw tokens render the full warm Daylight palette + clay. Prior "app = shadcn zinc" decision reversed (memory + design-page copy updated); DESIGN.md "Daylight" now governs the app too.

Agents18:46 IST

Audit + notify spine v2: actor-kind (agents!), outcome, severity, the wrapper

Diagnosed the "fucked up" audit/notify: the code was well-built but the spine couldn't log agents (it required a User actor; null just meant "system"), had no outcome/severity/summary, and…

Diagnosed the "fucked up" audit/notify: the code was well-built but the spine couldn't log agents (it required a User actor; null just meant "system"), had no outcome/severity/summary, and covered only the identity/admin half — every sales + agent action wrote nothing. Rebuilt the foundation:

  • Schema (audit_events + notifications, via ALTER TABLE, never db:push): actor_kind (USER|AGENT|SYSTEM), actor_label (person or agent name), outcome (SUCCESS|FAILURE|PARTIAL|PREVIEW — for propose-not-act), summary (a rendered human sentence — what the trail shows), agent_run_id; notifications gain severity (info|warning|critical) + cta_label/cta_href.
  • `recordAudit` now takes actorKind/label/outcome/summary/agentRunId and is fire-and-forget (swallows errors, returns number | null) so a logging failure never breaks the action. notify gains severity + CTA and is fire-and-forget too.
  • `recordAuditAndNotify()` — THE spine wrapper (antino3's SyncAudit pattern): one call = one audit row + notifications with a shared vocabulary (outcome→severity). `recordAgentRun()` — logs an AGENT action attributed to the user it ran for.
  • Wired the Writer agent: every draft (success/empty/error) now writes an AGENT_DRAFTED_REPLY audit row (actorKind AGENT, label "Writer", outcome, char-count). Registry (audit-actions.ts) gained the sales + agent action codes + ActorKind/Outcome/Severity enums. Verified end-to-end (agent event + severity/CTA notification insert). Then made it visible + gated: /admin/audit now surfaces the new detail — an actor-kind glyph (agent = violet sparkle, system = cpu, user = person), a colored outcome chip (shown when not SUCCESS), and the summary sentence on its own line. Built the RACI access helper (src/lib/auth/access.ts): canActOnOwner/requireActOnOwner gate write-actions by admin-tier · ownership (Zoho mapping) · manager hierarchy (recursive report tree) — unauthorized → notFound(); reading stays open. Then wired the deal action rail to log every user action: src/app/ae/deals/[id]/actions.ts (logStageAdvance / logDealNote / logCall) — each requireRole('AE') → looks up the deal owner → requireActOnOwner (404 if not owner/manager/admin) → recordAuditAndNotify({ outcome: 'PREVIEW', … }) (audit row + a manager notification with a CTA). The deal-hub Stage-gate Advance button + the Advance / Log call / Add note rail buttons now call these server actions (client toast reflects the logged result) instead of a bare client toast. Typecheck-verified; the spine itself was DB-smoke-tested. Live click-through pending a fresh sign-in (I can't authenticate). Then wired the SDR lead action rail the same way (src/app/sdr/leads/[id]/actions.ts): Send email / Log call / Book meeting / Qualify / Add note now call RACI-gated server actions logging PREVIEW events + notifying the SDR's manager. Qualifying fires the SAL handoff (SAL_HANDOFF_SENT) — the seam antino3 calls the most important — and Unqualified logs LEAD_DISQUALIFIED with the reason code. #9 complete: every user action (deal + lead) and every agent run is now logged in detail, RACI-gated, with severity/CTA notifications. (SAL accept/reject on the AE side + the SLA engine belong to #8.)
Data18:18 IST

Zoho coverage: audit + field fill (Next_Step, Mobile, Reports_To…)

Audited whether the Zoho mirror pulls everything (a subagent, task #15). Finding: coverage is selective by construction — src/lib/zoho/manifest.json is a hand-picked field list, no raw…

Audited whether the Zoho mirror pulls everything (a subagent, task #15). Finding: coverage is selective by constructionsrc/lib/zoho/manifest.json is a hand-picked field list, no raw overflow (deliberate "fully-typed 1:1 mirror"). The forecasting spine is dense (probability/forecast/amount/close/stage all ~100%), but several workflow fields are structurally absent. Filled the real gaps (task #16): added zoho_deals.next_step + expected_revenue, zoho_contacts.reports_to + department, zoho_leads.mobile to the schema + manifest; the self-healing importer populates them on the next sync. Discovered a live hazard: drizzle-kit push wants to DROP the Proshort tables (525 recordings + 22k insights — they live outside the Drizzle schema, created by scripts/proshort/). The no-TTY guard blocked it; applied the columns via surgical ALTER TABLE ... ADD COLUMN IF NOT EXISTS instead. Recorded the never-push rule (memory + docs/DATA-REALITY.md, ported from antino3 with live 2026-07-12 fill rates — most account attributes are <2% filled, converted_s is 0%). Remaining #16: mirror CRM Attachments + deal line items (bigger builds); the raw jsonb idea is deferred — it contradicts the deliberate no-jsonb schema convention (flagged for a decision). Follow-up task #17: add Proshort tables to the Drizzle schema so push is safe.

Product17:59 IST

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

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

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

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

Product17:50 IST

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

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

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

Product17:42 IST

Email reader + attachments, and Inter as the app font

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

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

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

Product17:34 IST

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

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

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

Product17:31 IST

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

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

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

Product17:28 IST

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

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

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

Product17:24 IST

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

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

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

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

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

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

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

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

Product17:00 IST

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

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

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

Data16:56 IST

Lead timeline: Zoho tasks (who_id is a dead link for leads)

The lead timeline queried tasks by who_id=leadId — but a DB check shows Zoho task who_id points at contacts (539,625) and never leads (0), so that block returned nothing on every lead.…

The lead timeline queried tasks by who_id=leadId — but a DB check shows Zoho task who_id points at contacts (539,625) and never leads (0), so that block returned nothing on every lead. Leads are linked to their follow-ups by the subject convention instead ("Followup N with Lead {FirstName} from {Company}"), the same match getLeadTasks uses. Rewired getLeadTimeline's task block to match by the lead's owner + company + first name across all statuses (so the timeline shows the full done+pending chain, not just open). Added ownerId to LeadFull/getLead to scope it to the lead's actual owner (works for read-only cross-rep views too). Verified as Himanshu on Ishika's Collinson lead (read-only): "Followup 1/2 with Lead Saurabh from Collinson" now render in the timeline, while the owner-scoped "Open follow-ups" rail stays empty (correctly — they're not his to action). Both SDR and AE lead pages inherit this.

Data16:49 IST

Deal timeline: real-calendar meetings (the missing category)

The deal 360 was missing meetings. Grounded it in the data: for Himanshu's Tata Steel deal, zoho_events (CRM events, what_id) = 0, but the real Outlook calendar (graph_events) has meetings…

The deal 360 was missing meetings. Grounded it in the data: for Himanshu's Tata Steel deal, zoho_events (CRM events, what_id) = 0, but the real Outlook calendar (graph_events) has meetings with the buying committee — the deal timeline only queried zoho_events, so they never showed. (Org-wide only 197 CRM events link via what_id at all; the real meeting record lives in graph_events.) Added a graph_events block to getDealTimeline, matched to the same buying-committee email set the email block uses (primary contact + contact-roles), deduped across mailboxes. Now the deal timeline is genuinely unified — CRM tasks/notes/calls + committee emails + real-calendar meetings + Proshort calls. Verified as Himanshu on Tata Steel: "TATA Steel <> Antino" meetings render (teal calendar icon, "· with the buying committee") interleaved with the MOM email, follow-up tasks, and the Proshort discovery call. Subtitle updated to "CRM + email + meetings + calls, unified". (Lead timeline already carried all three — tasks via who_id, meetings via graph_events, emails exact/domain.)

Product16:40 IST

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

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

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

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

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

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

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

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

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

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

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

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

Follow-up chains, unified calls (Proshort), user-scoped timeline

Follow-ups grouped into chains. DB study confirmed the shape: 763 leads carry a "Followup 1 → Followup 2" chain (1636/1639 tasks resolve to a lead). So /sdr/tasks now groups by lead — one…

  • Follow-ups grouped into chains. DB study confirmed the shape: 763 leads carry a "Followup 1 → Followup 2" chain (1636/1639 tasks resolve to a lead). So /sdr/tasks now groups by lead — one card per lead with its chain and a Clear chain action — instead of a flat list. Ordered by lead recency (getTasks now returns leadRecency, cap lifted 300→2000 so the full 1,639 shows across 875 leads).
  • Proshort integration (conversation intelligence) — the call layer. New proshort_* staging tables (scripts/proshort/create-tables.mjs, typed in src/db/proshort-schema.ts) + backfill (scripts/proshort/backfill.mjs, v3 list→detail, throttled). Recorded calls (recap + overview: pain points, action items, risks, competitors, decision process) now surface as call items in the lead timeline. See docs/PROSHORT.md. Key in .env (rotate it).
  • User-scoped timeline (privacy). getLeadTimeline(lead, userEmail) now scopes meetings to the signed-in SDR's own mailbox and Proshort calls to recordings they were a party on — a user sees only their meetings/calls, never everyone's. Emails + meetings + calls interleave by date; the Inbox/lead labels count each.
  • Threaded user.email through the timeline callers (inbox, lead detail) and the Writer draft path.
Agents09:09 IST

First AI: the "Writer" agent + task accordion + hover cards

First real Claude call in the app. @anthropic-ai/sdk installed; src/lib/ai/anthropic.ts builds the client from the encrypted ANTHROPIC_API_KEY setting (cached until the key changes);…

  • First real Claude call in the app. @anthropic-ai/sdk installed; src/lib/ai/anthropic.ts builds the client from the encrypted ANTHROPIC_API_KEY setting (cached until the key changes); src/lib/ai/draft.ts = Writer, which drafts a reply grounded in the lead's real email thread + Zoho record (Haiku 4.5, read-only). Server action draftReply(leadId) in src/app/sdr/ai-actions.ts; the Inbox "Copilot draft" button now calls it (loading state, toast, graceful error) instead of a template.
  • Verified the path is real: clicking it fires a genuine API call and returns a proper Anthropic response. The stored key is currently an invalid 24-char placeholder (sk-ant-tes…, updated 2026-07-07) so it 401s — a valid key in Admin → Settings unblocks it, no code change.
  • Agent framing: the SDR gets a team of simple-named agents — Writer (live), Briefer / Sorter / Qualifier / Researcher / Chaser (planned) — all Haiku, all propose-not-act, user in the center. Write-back (send via Graph, CRM update via Zoho) is a separate, deliberate switch.
  • Follow-ups → accordion. getTasks now resolves each task to its lead via a lateral join (subject parse, since who_id is null), so tasks carry a hoverable/linkable lead. New src/lib/sdr/task-types.ts classifies each task (follow-up / status / future / notes / data / linkedin — grounded in a scan of the real subjects, ~8k are "Priority Action" bot tasks); the row expands to the right "how to finish it" actions per type. All preview + toast.
  • Universal `LeadHoverCard` (src/components/sdr/lead-hover-card.tsx) — hover any lead name to preview contact/status in the lead entity hue + jump to the record; the pattern extends to deal/contact on the AE surface.
Product08:40 IST

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

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

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

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

11

Jul 2026

Saturday

5 shipped

Product10:33 IST

Command board: donut breakdowns + SDR/AE rep scoreboard

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

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

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

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

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

HEAD_OF_SALES role (admin-tier) + fixed the KPI dashboards

New HEAD_OF_SALES role, admin-tier: it shares the /admin area and sees everything the CXO admins see (Command Board, users, org, audit, integrations). Rohit Naidu (id 163) promoted to it…

  • New `HEAD_OF_SALES` role, admin-tier: it shares the /admin area and sees everything the CXO admins see (Command Board, users, org, audit, integrations). Rohit Naidu (id 163) promoted to it (audited USER_ROLE_CHANGED; scripts/add-head-of-sales-role.ts, which also runs the ALTER TYPE user_role ADD VALUE 'HEAD_OF_SALES' AFTER 'ADMIN').
  • Access refactor so a role can share another's area: ADMIN_ROLES = [ADMIN, HEAD_OF_SALES], new requireAdmin() in dal.ts (replaces requireRole('ADMIN') across all 11 admin pages/actions), SEGMENT_ALLOWED_ROLES in roles.ts drives proxy gating (was 1:1 SEGMENT_ROLES), roleHome(HEAD_OF_SALES) = /admin, ROLE_NAV[HEAD_OF_SALES] = the admin nav. "Head of Sales" is now an assignable role in /admin/users.
  • Fixed two real crashes on the KPI dashboards (they were listed "working" but actually threw on render): (1) server components passed formatter functions (valueFormatter={formatInr}) to the client chart component — forbidden across the RSC boundary; replaced with a valueFormat="inr"|"count" string the client resolves. (2) weekStart from Postgres wasn't always a Date → Intl.DateTimeFormat threw "Invalid time value"; now coerced with a NaN guard. Both /admin/command and /ae/team render clean now.
  • Verified in Chromium: signed in as admin@glue.tools and as rohit.naidu@antino.com — both land on /admin, reach /admin/command, see ₹ figures + 16 chart SVGs, zero console/render errors.
Data09:10 IST

First metrics dashboards on live Zoho data (docs catch-up)

Shipped inside a7fe7fe alongside the pool-retry work, documented here: /admin/command (company-wide command board: ₹-pipeline/lead/deal stat tiles, pipeline by stage, lead-status breakdown,…

  • Shipped inside a7fe7fe alongside the pool-retry work, documented here: `/admin/command` (company-wide command board: ₹-pipeline/lead/deal stat tiles, pipeline by stage, lead-status breakdown, 12-week activity, top owners) and `/ae/team` (the same lens scoped to the signed-in AE's reporting subtree with per-member performance).
  • Foundations: src/lib/metrics/queries.ts (SQL over the zoho_* staging layer joined to our org tree) + format.ts (₹ INR + count formatting), and dashboard primitives StatTile / ChartCard / CategoryBarChart / WeeklyActivityChart — all charts on the entity color tokens, fixed order, per the color language.
Product08:46 IST

DB pool: retry-once on transient network errors

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

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

10

Jul 2026

Friday

2 shipped

Data07:59 IST

Microsoft Graph pipeline: multi-tenant email + calendar + attachment bytes

New graph_ staging layer (src/db/graph-schema.ts) — fully-typed, jsonb-free mirror of M365 email + calendar. Tenant-aware: every table keyed by an org discriminator (ANTINO | GOLDENFLITH),…

  • *New `graph_ staging layer** (src/db/graph-schema.ts) — fully-typed, jsonb-free mirror of M365 email + calendar. **Tenant-aware**: every table keyed by an org discriminator (ANTINO | GOLDENFLITH), because Antino and Goldenflith are separate Microsoft tenants with separate app registrations. Creds per-org env vars; graph_tenants` mirrors them (AES-encrypted secret) for a future admin UI.
  • Attachment bytes live in Postgres (graph_message_attachments.content bytea) — the explicit requirement. Inline path for ≤3 MB, /$value fetch for larger; verified byte-perfect (a 25 MB PDF stored intact, %PDF%%EOF). One heavy mailbox ≈ 900 MB of attachments.
  • Engine (src/lib/graph/importer.ts + client.ts, CLI scripts/graph/run.ts): client-credentials auth per tenant; full pull (delta-primed) + every-15-min delta via stored deltaLink; messages (body, recipients, threading), events (attendees), attachments. Hardened against transient Azure Postgres/network errors — per-mailbox retry-then-skip, pool keepAlive + timeouts, so one blip can't kill a multi-hour pull. Added a targeted mailbox <emails> command for re-syncing specific inboxes.
  • Access model: User.Read.All lets us enumerate the directory, but mail read is gated by an Exchange application access policy scoped to the sales team per tenant. Inaccessible + hard-excluded (CEO/leadership: vinay@, ceo@goldenflitch.com) mailboxes are marked and skipped, never fatal.
  • Antino: app access policy grants the sales team. 13 sales mailboxes pulling (Anisha, Nitish + the 10 SDR/closers, plus Harshad/Ishika/Priyadarshini once added to the policy mid-session). Vedant excluded (disabled account); Dhruv Raj + Abhishek Rana turned out to be Goldenflith, not Antino (their org-chart placeholder @antino.com emails were wrong).
  • Goldenflith: separate tenant; seeded from its own directory (53 goldenflitch.com mailboxes), access policy gates to the sales set. Pulling now.
  • 15-min delta cron added to src/instrumentation.ts alongside the hourly Zoho sync — per-tenant, skips an org until its FULL pull completes, guards overlaps.
  • Creds for both tenants in .env (gitignored).
Fix16:35 IST

Incremental sync fixed for activity modules; first real delta pulled

First real-world incremental sync surfaced two COQL blind spots: activity modules (Tasks/Calls/Events) reject What_Id — the polymorphic link we must have — and Deals.Stage_Modified_Time /…

  • First real-world incremental sync surfaced two COQL blind spots: activity modules (Tasks/Calls/Events) reject What_Id — the polymorphic link we must have — and Deals.Stage_Modified_Time / Calls.CTI_Entry / Events.Participants are unqueryable. Fix: Tasks/Calls/Events now sync via the same Modified_Time-desc REST walk as Notes (REST returns What_Id fine, deletions still tracked via the deleted API); the COQL path self-heals by dropping any column Zoho names in an INVALID_QUERY error (learned per process, mirroring the bulk-import behavior).
  • Sync then pulled two days of real CRM activity cleanly: Leads +650 upserts/2 deletes, Tasks +1,548/4, Notes +81, Calls/Accounts/Contacts small. Post-sync counts verified against live Zoho: Leads 19,034 / Tasks 588,206 / Contacts 140,500 all exact.
  • Known limitation confirmed in the wild: Zoho exposes no deleted-records API for Notes, so notes deleted in Zoho persist here until the next full import (currently 1 such row).

08

Jul 2026

Wednesday

1 shipped

Data09:10 IST

Zoho pipeline: full CRM in Postgres, Integrations page, hourly sync, webhook

All Zoho data is in our DB — 866k+ rows across a fully-typed, jsonb-free staging layer (zoho_): Tasks 587,297 · Contacts 140,499 · Accounts 50,904 · Notes 47,874 · Calls 19,384 · Leads…

  • All Zoho data is in our DB — 866k+ rows across a fully-typed, jsonb-free staging layer (zoho_*): Tasks 587,297 · Contacts 140,499 · Accounts 50,904 · Notes 47,874 · Calls 19,384 · Leads 18,610 · Deals 1,660 · Events 891 + campaigns/products/vendors, plus related lists (stage history 3,642 · contact roles 1,615 · campaign members 559) and zoho_users (70) with the zoho_user_mappings bridge to our users (email/local-part/name matching; catches anisha@antino.comanisha@antino.io).
  • Schema is generated from Zoho's own field metadata (scripts/zoho/generate-staging-schema.tssrc/db/zoho-schema.ts): one typed column per field, bigint lookup ids (indexed), multiselects as child tables. No hard FKs on staging by design (Zoho doesn't enforce integrity; truncate-reload + out-of-order sync) — orphan tallies instead (currently 0 dangling refs); the future domain layer gets hard FKs.
  • Engine (src/lib/zoho/importer.ts, ultracode-built + live-tested): Bulk Read v8 full imports (200k-row CSV pages, chunked inserts, self-healing drop-and-retry for bulk-unsupported fields), hourly incremental sync (per-module Modified_Time checkpoints via COQL, deleted-records tracking, Notes REST walk), targeted webhook sync, Zoho watch registration. Usable from server actions, instrumentation, and CLI (scripts/zoho/run.ts full|sync|users|related|module <Name>).
  • `/admin/integrations` — connection status, per-module batch table, Import everything / Sync now / Enable push (audited: ZOHO_IMPORT_TRIGGERED/ZOHO_SYNC_TRIGGERED/ZOHO_PUSH_ENABLED; admins notified), auto-refresh while running; in the admin sidebar.
  • Hourly cron in src/instrumentation.ts (activates on server boot; skips until a FULL import exists; renews the Zoho watch when APP_PUBLIC_URL is set). Webhook /api/zoho/webhook (token-checked; proven live — a pushed vendor id round-tripped through targeted sync).
  • Verified: all 11 module counts match live Zoho exactly; lead-status distribution reproduces the audit (77.3% Open); the "Anisha's active leads" question answers identically from pure SQL (Open 1,272 / Working 2: MILIND + Srishti, stale since March) — and SQL surfaced a status (Disqualified, 3 leads) the per-picklist API counting had missed.
  • Zoho credentials + webhook token live in .env (gitignored).

07

Jul 2026

Tuesday

8 shipped

Product23:49 IST

Entity color language

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

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

Sales leadership designations fixed from the org chart

scripts/fix-sales-designations.ts (idempotent, audited USER_UPDATED): the HR sheet's generic VP/AVP titles were overriding the chart's role labels. Rohit Naidu → Head of Sales (per user;…

  • scripts/fix-sales-designations.ts (idempotent, audited USER_UPDATED): the HR sheet's generic VP/AVP titles were overriding the chart's role labels. Rohit Naidu → Head of Sales (per user; chart label was "Director-Business & Strategy"), Anisha Ratnam → Head of Startup, Nitish Kumar → Head of Growth, Himanshu Gupta → Head of Government. Harshad/Vinay already correct.
  • Reporting tree needed no changes — all 17 chart edges already matched the image.
  • L4 "Key Accounts" people keep their specific HR designations (Business Consultant / Trainee / Intern / Tender Executive); the chart's "Key Accounts" reads as the team grouping, not individual titles.
Data23:23 IST

Zoho CRM current-state audit (docs/ZOHO-CRM.md)

Mapped the live Zoho CRM (read-only) and wrote it up in docs/ZOHO-CRM.md, linked from the docs index: module inventory + record counts, the full entity-relationship graph (lookups + related…

  • Mapped the live Zoho CRM (read-only) and wrote it up in docs/ZOHO-CRM.md, linked from the docs index: module inventory + record counts, the full entity-relationship graph (lookups + related lists), the Tasks linkage analysis, and the fault lines vs. SALES-PROCESS.md.
  • Entity relationships: hub-and-spoke around Accounts (company) + Contacts (people); Leads convert into Account/Contact/Deal; Deals hang off Account + Contact (+ Contact Roles M:M); Activities (Tasks/Calls/Meetings) attach polymorphically via Who_Id/What_Id. The live graph is only Leads → Accounts/Contacts → Deals + Activities — the quote-to-cash chain, Cases, Price Books, and the custom Legal Contract Management module are 0 records (empty scaffolding).
  • Volumes: Contacts 140.5k, Tasks 587k, Accounts 50.9k, Calls 19.4k, Leads 18.5k, Deals 1.66k, Meetings 891; Quotes/Sales Orders/Invoices/Purchase Orders/Price Books/Cases/Legal Contract Management all 0.
  • Tasks (587k) link mostly to leads/contacts, not deals; the convention shifted over time (older to Contacts 76%, newer to Leads 83%; Accounts steady ~13%; Deals 4-10%). Confirms activity lives at the top of the funnel, not on opportunities.
  • Docs-only change; no code touched.
Design12:48 IST

Sidebar app shell, shadcn light theme, org chart redesign, full company mapped, Zoho + sales-process absorbed

Sidebar app shell for every role (AppSidebar + AppShell rewrite): shadcn Sidebar (collapsible to an icon rail), role-aware nav with icons from ROLE_NAV (src/lib/nav.ts), an inline SVG logo…

  • Sidebar app shell for every role (AppSidebar + AppShell rewrite): shadcn Sidebar (collapsible to an icon rail), role-aware nav with icons from ROLE_NAV (src/lib/nav.ts), an inline SVG logo mark (components/logo.tsx) + src/app/icon.svg favicon, breadcrumbs in the header (AppBreadcrumbs), and a user dropdown with sign-out. Replaces the old top-nav; nav-links.tsx deleted. All five role layouts now just render <AppShell user={user}>.
  • Pure shadcn LIGHT theme for the app, scoped to .app-theme on the shell wrapper (globals.css): redefines the standard shadcn tokens and the legacy antino tokens (--ink*, --panel*, --clay*, --sage/rust/amber, sidebar tokens) to zinc/neutral light values. The marketing landing + public pages (:root, .dark, .section-dark) are untouched. Deep Daylight styling is deferred; DESIGN.md's warm-cream rules now apply to marketing only, not the app.
  • All logged-in pages are full width — removed every mx-auto max-w-* wrapper (admin home/users/org/audit/settings, role homes, settings panel).
  • Admin home rebuilt into a real overview (people counts briefing + recent-activity + pending-invites cards; no placeholder). Audit/settings full-width; audit maps ::1/127.0.0.1localhost.
  • Users table: sortable columns, shadcn Select for role, a searchable combobox (select2-style) for manager across all people.
  • Org chart redesigned as a top-down boxed hierarchy (org-tree.tsx): level-colored boxes + connector lines, leaf reports grouped into bulleted boxes, search with highlight, collapse/expand + expand-all/collapse-all, auto-collapse on large trees. page.tsx now roots founders/admins (fixes the bug that hid Radhakanth & Rajesh).
  • Notification bell golden rule: recentNotifications() returns the latest 5 unread only (badge still shows total unread).
  • Full company reporting tree loaded (scripts/apply-designation-tree.ts, idempotent + audited): reads the HR designation sheet's RM column (scripts/data/reporting-lines.json), sets manager_id for IT/Operations/Product while sales keeps the org-chart image tree (Sales/Marketing rows skipped). Aligned the two founder codes (Radhakanth ANTINO306, Rajesh ANTINO57) so their reports resolve. 210 manager changes applied; org chart went from 20 → 230 of 234 placed.
  • Sales domain absorbed into docs/SALES-PROCESS.md (statuses, temperature, opportunity stages, 8-phase flow, processes, tasks, SLAs, RACI) and linked in the docs index.
  • Zoho CRM explored (read-only) and stress-tested against the doc — fault lines found: missing Discovery/Solution-Validation deal stages, temperature ~8% populated, forecast discipline unused (Best Case=1 / Committed=0), 77% of leads stuck in Open, a parallel undisciplined Contacts-status funnel, and bulk-enriched data vs. curated ICP. Zoho OAuth creds stored in .env (gitignored).
  • Fixes found via live Chrome verification: root-layout inline <script><noscript> fallback (kills the React-19 script error + the original hydration warning); TooltipProvider added to the shell (sidebar tooltips were crashing); org-tree invalid <li>-in-<li> nesting → divs. tsc green; interactive checks (sorting, bell, dropdown) verified.
  • Monochrome cleanup pass: replaced every leftover warm token in the app (sage/rust/amber/clay) with shadcn-native patterns — status chips → Badge variants (secondary / destructive / outline), form errors + delete → destructive, success → foreground, notification badge/dot/link → primary/muted-foreground, audit action pill → secondary. Org chart stays intentionally color-coded by level. The scalable win: statuses now render through the shared Badge component, not hand-rolled colored spans.
  • Org chart made scalable for the full 234-person tree (org-tree.tsx), and turned horizontal (left-to-right): root on the left, each level a column to the right, siblings stacked vertically, so a large org grows down the page (natural scroll) instead of sprawling sideways. Auto-collapse to the top 3 tiers on large trees (opens as an overview, not a wall), Expand-all / Collapse-all controls, stray leadership split into their own row, and single leaf reports render as boxes instead of a "Reports (1)" list. Large report groups flow into multiple columns (2 at >10, 3 at >24) so a 40-person team is a compact card, not a tall strip, with no internal scrollbars. Fixed the .orgchart connector CSS hijacking the inner report lists (they were laid out sideways).
  • Calendar hydration fix (ui/calendar.tsx): data-day is now a deterministic YYYY-MM-DD id instead of locale toLocaleDateString(), which mismatched between Node SSR and the browser on every day cell (surfaced on /design).
  • `/design` showcase reskinned to the app's shadcn-light theme (scoped .app-theme on its root); intro copy updated. It was still rendering the old cream Daylight tokens.
Design08:56 IST

Micro-interactions on every component, tested live on /design

18-agent workflow: per-component micro-interaction pass across all 60 shadcn components (79 files: ui/ bases + /design section demos), on the product motion voice — 100–250ms,…

  • 18-agent workflow: per-component micro-interaction pass across all 60 shadcn components (79 files: ui/ bases + /design section demos), on the product motion voice — 100–250ms, ease-out-quart/quint, transform/opacity only, reduced-motion safe, purely presentational (no API changes; the polish reaches every app surface).
  • Highlights: checkbox check pops in + indeterminate now renders a minus; radio dot zooms in; switch thumb eases across and compresses on press; slider thumb grows on hover/grab; select/combobox chevrons rotate open and their check indicators pop; OTP digits pop in; input-group copy button flashes a sage confirmation; kbd chips got keycap depth; button trailing icons nudge on hover; toggles scale on press (suppressed in fused segments); avatar images fade in on load; badges-as-links sink on press; field errors rise in; and equivalent passes across feedback, navigation, overlay, and data components.
  • Every one of the 62 /design sections functionally tested in Chromium (Playwright): toggles flipped, overlays opened and Escape-closed, sliders dragged, tabs switched, toasts fired — state asserted via aria/data attributes, console errors collected, screenshots reviewed.
  • 3 real issues found, all fixed and re-verified: CommandDialog crashed the page when opened (missing cmdk <Command> root inside the dialog — real bug, now fixed in ui/command.tsx); split-dropdown menu rendered at min-width; line-chart x-axis dropped its first tick. Build green.
Design08:49 IST

Audit trail page, integrations keys, org chart, audit geo-context, UI polish

/admin/audit — the audit spine gets its read surface: filterable trail (action select from the registry, actor/entity search), expandable rows with before/after payloads and full request…

  • `/admin/audit` — the audit spine gets its read surface: filterable trail (action select from the registry, actor/entity search), expandable rows with before/after payloads and full request context.
  • Audit context enrichmentrecordAudit() now captures ip, raw user agent plus parsed browser/OS/device (ua-parser-js), geography (Vercel/Cloudflare headers when deployed; null locally), referer, and accept-language on every event, automatically for all call sites.
  • Integrations (/admin/settings, admin-only) — Resend + Anthropic API keys stored AES-256-GCM encrypted (key derived from SESSION_SECRET); format-validated (re_… / sk-ant-…); masked previews only (re_••••••cdef) in UI, audit (SETTING_UPDATED), and admin notifications. New app_settings table + SETTING_KEYS/getSetting/setSetting in src/lib/settings.ts.
  • `/admin/org` — org chart from users.manager_id: CEO → Director → Heads → Key Accounts tree with role badges, report counts, suspended dimming, and the unmapped-people count.
  • UI sweep: show/hide toggle on every password field (shared PasswordInput); every button now carries an icon; nav gained an active state (clay underline + aria-current); users table gained an empty state for no-match searches.
  • Tested in real Chromium (Playwright): 16-check flow suite (sign-in incl. show-password toggle, add person → invite accept → bell → change password → suspend/blocked-sign-in → reactivate → forgot-password) plus org/integrations checks — all green, screenshots delivered.
  • Known hydration warning on <html> (js class script from the landing work) noted for a future fix.
Fix08:19 IST

Sticky nav fix, login verified, founders added as ADMIN

Sticky nav bug fixed: .section-dark set position: relative outside any cascade layer, which beat the layered sticky utility on the landing nav (unlayered CSS wins). Dropped the position…

  • Sticky nav bug fixed: .section-dark set position: relative outside any cascade layer, which beat the layered sticky utility on the landing nav (unlayered CSS wins). Dropped the position (its isolation: isolate already provides the stacking context); verified in-browser — nav pinned at top: 0 with position: sticky at scrollY 2400.
  • Login flow verified in the browser (Playwright/Chromium against the prod build): admin@glue.tools + dev password → lands on /admin, shell shows Sign out; DB health confirmed.
  • Founders added as ADMIN (scripts/add-founders.ts, idempotent, audited): Rajesh Kumar (Co-Founder & COO, rajesh@antino.io, id 234) and Radhakanth Kodukula (Co-Founder, radhakanth@antino.io, id 235). Emails follow the @antino.io leadership pattern but are assumed (flagged in the audit context). Vinay was already ADMIN. Admins now: Glue Admin, Vinay, Rajesh, Radhakanth. Users total: 234.
Product08:19 IST

Credential flows, user-management suite, notifications spine live

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

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

06

Jul 2026

Monday

9 shipped

Design22:57 IST

Docs refresh + full-width /design

Root README.md rewritten from create-next-app boilerplate to a real project readme: stack, env setup (APP_DATABASE_URL, SESSION_SECRET), seeding scripts, dev sign-in, house rules, docs…

  • Root README.md rewritten from create-next-app boilerplate to a real project readme: stack, env setup (APP_DATABASE_URL, SESSION_SECRET), seeding scripts, dev sign-in, house rules, docs pointers.
  • ENTITIES.md brought current: two-pass seeding story (sheet import + org tree), 232-user total, full audit action list.
  • TASKS.md: committed batch (90edadf) moved to Done.
  • /design main column is now full width (dropped the max-w-4xl cap) — component sections fill the viewport beside the sidebar.
Design22:40 IST

Landing page, design system port, /design showcase, polish pass

Design system ported from antino.nuvio.dev (our own production site): full token set (warm cream/espresso/clay, light + dark + .section-dark island) into globals.css (Tailwind v4 @theme),…

  • Design system ported from antino.nuvio.dev (our own production site): full token set (warm cream/espresso/clay, light + dark + .section-dark island) into globals.css (Tailwind v4 @theme), IBM Plex Sans/Mono via next/font, radius 14px, warm elevation scale (shadow-rest/raised/float).
  • shadcn/ui installed — all 60 components at src/components/ui, restyled by the token layer.
  • Landing page (`/`) rebuilt from the live site, sections 00–09 verbatim: dark hero with typewriter briefing card, sample email with numbered receipts, eight-agents grid, animated counters, principles, how-it-works tabs, stop/start diff, built-by stats, FAQ accordion, pilot CTA. Built via a 10-agent ultracode workflow (theme → build → assemble → verify → 3 reviewers → fixer; 12/13 findings fixed).
  • `/design` showcase — 62 sections, every installed component with variants, foundations (tokens/type/radius), scrollspy sidebar, dark-preview strip. All 78 sidebar links verified against section ids.
  • Lift, not borders (rule locked): ~60 card containers across 35 files converted from hairline outlines to tone step + warm shadow + card-lift hover.
  • Polish pass (browser-audited on the production build): hero headline geometry fixed to match source (3 lines), text-balance on display headings, terminal-style caret blink, breathing LIVE dots, feed-row hovers, button press states + arrow nudges, nav underline slides, FAQ numbers turn clay when open, and annotation-cards ↔ email-superscript hover linking. All motion ease-out, reduced-motion safe. Verified live: agents grid, counters (1/15/8/0.59/0), accordion, scrollspy; zero console errors.
  • Stub /privacy + /terms pages; playwright added as devDep (used for full-page captures).
Product22:40 IST

Sales org tree seeded from org chart

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

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

Auth, user import, admin user management

Schema: users gains status (user_status: ACTIVE | SUSPENDED), employee_code (unique), designation, department, password_hash. New audit_events table — the audit spine is live (recordAudit()…

  • Schema: users gains status (user_status: ACTIVE | SUSPENDED), employee_code (unique), designation, department, password_hash. New audit_events table — the audit spine is live (recordAudit() in src/lib/audit.ts, secrets redacted).
  • Import: scripts/import-users.ts loads 226 employees from the designation sheet (scripts/data/designation-data.json) + seed admin (admin@glue.tools). All share the dev password dev-test-pw-1 (bcrypt). Roles mapped heuristically: IT leadership/leads → TECH_LEAD, IT rest → DEV, sales leadership/managers → AE, sales juniors → SDR, other departments → DEV (open question). Result: 1 ADMIN, 26 TECH_LEAD, 9 AE, 6 SDR, 185 DEV. Idempotent; every row audited.
  • Auth: email + password sign-in (/sign-in), jose-signed JWT session cookie (7d) with userId + role. DAL (src/lib/auth/dal.ts): verifySession / getCurrentUser / requireRole. Suspended users blocked at sign-in and DAL.
  • URL convention locked in: /<role-name>/<page-name>. src/proxy.ts (Next 16 renamed middleware → proxy) optimistically gates role areas and bounces wrong-role visitors to their own home; authoritative checks stay in layouts/actions.
  • User management: /admin/users — search/filter 227 people, inline role change, manager assignment (self/cycle-safe), suspend/reactivate. Self-role-change and self-suspend blocked. Every mutation audited + revalidatePath.
  • Role home stubs for all five roles so every sign-in has a landing surface.
  • Verified end-to-end against the dev server: sign-in (admin → /admin, tech lead → /tech-lead, bad password rejected), wrong-role redirect, 227 rows SSR'd on /admin/users, audit counts (227 USER_CREATED, USER_SIGNED_IN on each sign-in).
Docs21:08 IST

Docs system established

Added the living docs set: README, JOURNAL, PRODUCT, TASKS, ENTITIES, PAGES (this folder).

  • Added the living docs set: README, JOURNAL, PRODUCT, TASKS, ENTITIES, PAGES (this folder).
  • Rule locked in: all docs files are updated before every commit and push.
Product21:08 IST

Manager hierarchy (`503b23a`)

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

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

CAPITAL_SNAKE enums (`a330bf9`)

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

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

Database live

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

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

Initial scaffold (`bd35629`)

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

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