One card, every surface
v2.25.0 lands review card contract v2 — subject, provenance, recommendation, typed content and per-object verbs — so any object type presents through one template and decides through one path.
Every new reviewable object type used to mean a new card. The review queue rendered one shape,
the personalization console rendered another, and a reviewer deciding the same underlying run
from two places could not be sure they were doing the same thing. v2.25.0 replaces that with a
single contract: card contract v2, plus one shell — ReviewActionCard — that renders it
wherever a decision is made.
What's new
- The contract grew zones.
ReviewCardgainssubject,provenance,recommendation, typedcontent,linksand per-object verbs. All of them are optional, so every v1 card renders unchanged. - Content has kinds, and kinds have renderers.
emailanddocumentregister into a renderer registry, the way actions register presenters — a new object type lands by registering a kind, not by editing the card. A kind nothing registered still shows its payload; a drill, never a blank. - Confidence and lane status come from the run. Never from the presenter. A presenter cannot claim a confidence the run does not have.
- Snooze, wired end to end. oRPC route, queue-feed filter, card button.
- A note for the agent rides every verb — into the assignment note, the triage signal and the learning capture.
Why we built it
The presenter-per-surface shape had a correctness problem, not just a duplication problem. When
the personalization console and the review queue each built their own card for the same
action_run, "Decline" on one surface and "Decline" on the other were two code paths that
merely looked alike. Divergence was a matter of time.
Now the console renders the same ReviewActionCard, fetched from the same pending-run feed and
keyed by the lead's review_action_run_id back-link. Decline, Snooze and Enroll on either
surface are observably one operation on one run.
Edits work the same way. A content edit maps back through the action's own applyContentEdits
and re-validates like any other edited input — the card does not get its own private notion of
what a valid edit is.
How to use it
The release registers personalization.enroll as the first object type built on the contract
from scratch. Its card carries a subject, its provenance, a recommendation, and the personalized
sends as editable email content. The verbs:
- Decline → the lead moves to
held. - Enroll → the lead is pushed into the recommended existing HubSpot sequence, with the
approved personalized sends staged on the contact as a note (the sequences API carries no
per-enrollment copy), the lane moves to
handed_off, and the decision is stamped.
personalization.enroll is on the never-auto list: no trust rule can release an enrollment
without a human. That is deliberate — an autonomous sequence enrollment is an irreversible
outbound act against a real person.
The drafting tools land beside the briefing ones:
next_brief_to_draft claims a briefed lead; the try is counted by the claim,
and failed briefs are never drafted
save_draft_sequence validates the sends, verifies the recommendation against
the live sequence library, and proposes the review item
server-side so the agent cannot forget to
hubspot_list_sequences reads the sender's existing sequence library
Proposing server-side is the important one. An agent that drafts and then forgets to queue the
result has produced nothing; moving the propose into save_draft_sequence makes the queueing a
property of saving.
Migration 0065 adds mql_at (the stage-entry date, pinned on the connector in both spellings),
recommended_sequence, and the draft-attempt columns. Queue rows now lead with the true MQL
stage-entry date when the mirror carries one, and say "arrived" otherwise.
What's next
- More content kinds —
imageis next, for visual-QC objects. - The three pre-existing actions now present through the template; the remaining bespoke review surfaces should follow.
Links
- GitHub release: https://github.com/vocion/vocion-core/releases/tag/v2.25.0
- Dashboard surface:
/dashboard/review,/gtm/personalization - Code:
packages/core/src/features/review/ReviewActionCard.tsx,contentKinds.tsx - Action:
packages/core/src/libs/actions/personalization-enroll.ts