Skip to content

feat(omni): approval UX — correlated identity, reactions, ⏳→✅ acks - #2509

Merged
namastex888 merged 5 commits into
devfrom
wish/omni-approval-ux
Jul 3, 2026
Merged

namastex888 merged 5 commits into
devfrom
wish/omni-approval-ux

Conversation

@namastex888

Copy link
Copy Markdown
Contributor

Summary

The v5 omni WhatsApp approval bridge is live-proven, but its live QA exposed three UX/correctness gaps. This wish (omni-approval-ux) closes them: approvals are now correlated (a reaction resolves the exact one it targets, via the real WhatsApp stanza id), reaction-driven (👍/👎), and self-updating via a two-state ⏳→✅ acknowledgement — genie sets ⏳ on its approval message the moment it's sent, swapping in place to ✅/❌ once you answer.

Spike-first (0 live messages)

G1 grounded four unknowns against the running Omni build with zero live traffic (all source-proven): reactions arrive on omni.message.{instance}.{chatId} (the assumed omni.event.> has zero publishers — the QA disproof); the send returns the stanza id; outbound set-reaction is GO (with a documented fallback); text replies carry no quoted id (so bare text stays an honest oldest-fallback).

What shipped

  • G2 — correlated identity + reactions: announce() now sends via an injectable seam and stores the real stanza id (retiring a self-referential genId() ref that matched nothing); reactions parse off omni.message.*, correlate 👍/👎 to the exact approval, dedupe the dual-emit echo; dead omni.event.> retired.
  • G3 — ⏳→✅ lifecycle: ⏳ on send → ✅/❌ on resolve/expire via an injectable set-reaction seam (fallback = one-seam swap). A reconciliation pass makes the runner the authoritative acker regardless of which process expired the row — curing a stuck-⏳ hook-fork race and transport-dropped swaps. Plus genie omni test-approval (fake default, --live) and a genie doctor hook-timeout guardrail. New last_status_glyph column (additive, no user_version bump — a bump would brick existing DBs; one-time migration backfill + 24h recency cap so upgrade never sweeps history).

Review rigor

Every group was independently reviewed; G3's reviews found and closed two real HIGHs (a stuck ⏳ on the timeout path, and a reconcile sweep of historical rows on upgrade) before ship — both would have silently violated the wish's own promises (live-state accuracy, anti-spam). One LOW residual noted (a pending-with-bogus-id row caught at the exact upgrade instant — 0–few rows, self-limiting, no real traffic).

Anti-spam guarantee

The automated suite emits zero live WhatsApp traffic (injectable fakes; --live is a documented manual escape hatch never called by tests). The G1 spike sent 0 messages.

Needs your eyes — one live confirmation

Everything is source-proven, but the ⏳→✅ in-place render (does WhatsApp swap the emoji visually?) is the sole empirical unknown. I'll run one labelled round-trip between your own numbers to confirm — the fallback (message-edit / status-reply) covers it if the swap doesn't render.

Gates

Full bun run check 628 pass / 0; build green; omni test-approval prints the ⏳→✅ round-trip; complexity budget + biome clean.

Merge decision: Felipe.

Rebased onto current dev (was based on a pre-omni-hardening commit). Wish:
correlated identity, reaction approve/deny, two-state ⏳→✅ acks; sequential
G1->G2->G3. SPIKE.md (0 live msgs, source-proven): reactions arrive on
omni.message.{instance}.{chatId} (omni.event.> has zero publishers), send
returns the stanza id, outbound set-reaction GO, text quoted-id unavailable
(bare text stays oldest fallback).
Group 2 of omni-approval-ux. announce() now sends via an injectable OmniSend
seam (default: signed POST to omni /api/v2/messages) and stores the REAL
WhatsApp stanza id via attachOmniMessageId — retiring the self-referential
genId() ref that matched nothing inbound. Inbound reactions parse off
omni.message.* as [Reaction: <emoji> on message <id>], correlate 👍/👎 to the
exact approval by omni_message_id (oldest only when no id), and the dual-emit
bare-emoji echo drops structurally (reaction-vs-text split). Retired the dead
omni.event.> path/handleEvent. Bare text stays oldest fallback; PR#2507
instance-scope guard kept. Reviewed: SHIP. 28 runner/queue tests + typecheck.
Group 3 (final) of omni-approval-ux. genie sets ⏳ on the approval message the
moment it's sent (the G2 stanza id), swapping in place to ✅ (approved) / ❌
(denied/expired) — via an injectable OmniSetReaction seam (default: signed omni
--reaction POST; fallback message-edit/status-reply is a one-seam swap). A tick
reconciliation pass (reconcileStatusAcks) makes the runner the authoritative
acker regardless of which process expired the row, curing a stuck-⏳ hook-fork
race AND transport-dropped swaps. Reactions targeting an unknown id no-op
(not resolveOldest). Adds genie omni test-approval (fake default, --live) + a
doctor hook-timeout guardrail. New last_status_glyph column (additive, no
user_version bump; one-time migration backfill + 24h recency cap so upgrade
never sweeps history). Reviewed: SHIP (2 HIGHs found+fixed). 77 omni tests.
@coderabbitai

coderabbitai Bot commented Jul 3, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 9d9f4bbb-4701-4b57-977d-994b7da0c048

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch wish/omni-approval-ux

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements the Omni Approval UX wish, introducing correlated approval identity, reaction-based approvals, and a two-state status-reaction lifecycle (⏳→✅/❌). Key changes include capturing and storing the real Omni message ID (stanza ID) on send, correlating inbound reactions and quoted replies to specific approvals, and updating the approval message's status reaction. Additionally, a genie doctor guardrail check was added to warn if the Claude Code hook timeout is below the poll budget, and a genie omni test-approval command was introduced to drive a single round-trip (fake or live). Database schema updates add a last_status_glyph column to the approvals table with a one-time backfill sentinel for historical rows to prevent redundant status updates. There are no review comments to assess, so no feedback is provided.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 483df2af34

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/lib/omni-runner.ts
if (!res.ok) return { success: false, error: `HTTP ${res.status}` };
// SendResult ({ success, messageId, ... }) may be top-level or `{ data }`-wrapped.
const json = (await res.json()) as { messageId?: string; data?: { messageId?: string } };
const messageId = json.messageId ?? json.data?.messageId;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Store the external stanza id, not the REST UUID

When the live runner uses this default send path, the repo's own spike notes that the persisted POST /messages REST route returns messageId: message.id (the Omni UUID), while inbound WhatsApp reactions target the stanza/external id (.genie/wishes/omni-approval-ux/SPIKE.md lines 65-68). Storing this response field therefore makes resolveReaction() compare an external id from [Reaction: … on message <id>] against a UUID and miss; because explicit reaction targets intentionally do not fall back to oldest, 👍/👎 reactions on real approvals will be ignored and the approval will time out instead of resolving.

Useful? React with 👍 / 👎.

@namastex888
namastex888 merged commit 0b039da into dev Jul 3, 2026
11 checks passed
@automagik-genie
automagik-genie deleted the wish/omni-approval-ux branch September 25, 2026 04:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant