First-run nudges derived from live workspace state
The app can now ask the server which onboarding action is relevant for the current counsel. Completion is never copied into onboarding storage: every read derives it from the real matter, document, WhatsApp, and Vani state.
App API contract
GET /app/onboarding/nudges
200 {
"nudges": [
{"key": "create_first_matter", "order": 1},
{"key": "connect_whatsapp", "order": 3},
{"key": "personalise_vani", "order": 4}
]
}
POST /app/onboarding/nudges/{key}/dismiss
POST body (optional): {"permanent": true}
204 No Content
Responses retain stable registry positions even when an earlier nudge is ineligible. The consumer can therefore show the first returned item while preserving a deterministic sequence.
One-query decision
create_first_matter = no firm matter exists
add_first_document = a firm matter exists and no firm document exists
connect_whatsapp = no active caller Meta binding exists
personalise_vani = no agents.counsel_preference row exists
for firm + caller + agent_key='mira'
All four facts and the caller's current suppression state are selected in
one PostgreSQL round trip. The code registry maps those facts to ordered
nudges; adding a future fifth ask is one registry entry plus one
EXISTS projection.
Dismissal lifecycle
dismissal 1 → suppressed for 1 day
dismissal 2 → suppressed for 3 days
dismissal 3 → suppressed for 7 days
dismissal 4 → suppressed until PostgreSQL 'infinity'
{"permanent": true} → 'infinity' immediately
The only durable onboarding state is the dismissal row in the
identity schema. Its primary key includes firm, caller, and
nudge key; row-level security enforces the firm boundary. Firm and user
identifiers come only from the authenticated database session.
Scope boundary
Drive is intentionally absent from v1 because neither a persistent Drive connection nor document import origin exists today. This path performs deterministic database work only: no generative model call, manual trace span, UI, or analytics event was added.