← All posts

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.

Vocion Teamv2.25.0

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

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:

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