RFC Review: Embeddable Create-Ticket Form in CDP Customer Detail (Web)
Companion review for
rfc-create-ticket-embeddable-web.md, produced by therfc-reviewerskill. Lives beside the RFC; valid only for the RFC revision inreviewed_rfc_last_updated(2026-06-30 /d656b29).
Executive Summary
- Overall Score:
8.0/10 - Rating:
Strong - RFC Type:
frontend - Sub-Type:
new-feature - Assessment Confidence:
High - Applied Caps/Gates:
none triggered(no category < 5.0; PRT/TDC/CNT/FMC all above their cap thresholds) - Implementation Readiness Verdict:
HOLD (partial-proceed)— chunks 1–3 PROCEED today; chunks 4–6 BLOCKED on CRM dependencies OQ-1 / OQ-2 / OQ-contract. - Report Path:
cdp/create-ticket-from-cdp/rfcs/rfc-create-ticket-embeddable-web-review.md - RFC Author: Jovi (CDP Frontend) | Reviewed: 2026-07-01
This is an unusually complete, evidence-grounded frontend RFC. Every in-repo code anchor it cites was verified against the live qontak-customer-fe checkout and is accurate (sandbox attrs, origin guard, the Deal embed's token/customer_id/source URL params, the Notes.vue permission gate, FeatureFlagStore, both config keys). For the CDP-owned surface an agent can implement chunks 1–3 (UI shell, permission/flag gate, "+" menu) directly from §2.A/2.D/4.C without asking a question. The biggest strength is the bidirectional PRD traceability + verified Repo Reading Guide; the biggest gap is that the highest-stakes contract — the cross-origin postMessage payload — is explicitly provisional (not frozen by CRM) and the embed it consumes is OUT of CRM Phase 1, so chunks 4–6 cannot be built correctly today. The one thing that must change before full agentic execution: CRM must (a) commit the CDP-reusable embed to a phase, (b) confirm in-iframe auth, and (c) freeze the typed contract — at which point §2.A stops being provisional. The RFC honestly sets §7 = no and this review agrees.
Quick Verdict
Why this RFC can be implemented agentically (chunks 1–3, today):
- Full TypeScript contracts for the CDP-owned side (props, emits, panel state machine) in Detail 2.A, plus a file-by-file scope map (Detail 2.D) and per-chunk verifiable acceptance criteria (Detail 4.C).
- Every pattern to mirror is named and verified against real source (Detail 2.0 Source Verification — all anchors confirmed in
qontak-customer-fe).
Why this RFC will cause agent guessing or rework (chunks 4–6, today):
- The
EMBED_INIT/TICKET_CREATED/ … payload field names in §2.A are taken from the PRD, not a frozen CRM contract (the RFC flags this itself) — an agent building chunk 4 now may build to field names that change. - The embed endpoint
/embed/ticket/createand its in-iframe auth are unbuilt / OUT of CRM Phase 1 (OQ-1/OQ-2) — there is nothing to integrate against.
Findings Ledger (carry-forward)
Stable, never-renumbered finding ids. R1 (first cycle) — all findings newly minted. REV-1…REV-4, REV-6, REV-8…REV-10 formalize the RFC's own §5 OQs with stable ids; REV-5 and REV-7 are net-new findings from this review (not in the RFC's OQ list).
| ID | Severity | Finding (one line) | RFC location | Status | First seen | Resolved in | Evidence / fix |
|---|---|---|---|---|---|---|---|
REV-1 | blocker | CRM CDP-reusable embed is OUT of Phase 1 — chunks 4–6 have nothing to integrate against | §1 Dependencies, §5 OQ-1 | open | R1 | — | External (PM + CRM). CDP can ship only chunks 1–3 behind an OFF flag until CRM commits the embed to a phase. |
REV-2 | blocker | Qontak One in-iframe auth unconfirmed (v2.8 JWT vs Mekari SSO) — create call may 401 | §3 Security, §5 OQ-2 | open | R1 | — | External (CRM + Platform). Headline risk; confirm SSO flows into the iframe before chunk 4. |
REV-3 | blocker | postMessage payload field names are provisional, not frozen by CRM | §2.A note, §5 OQ-contract | open | R1 | — | CRM must freeze EMBED_INIT/TICKET_CREATED/TICKET_CREATE_ERROR/EMBED_CLOSE/EMBED_RESIZE. Until then §2.A is provisional. |
REV-4 | major | CDP embed_source (embed-web-cdp) not yet in BE ALLOWED_EMBED_SOURCES — data_source coerced to embed-web-chat | §5 OQ-3, Detail 1.C S05 | open | R1 | — | External (CRM Backend). Gates TCKT-S05 attribution only; does not block create. |
REV-5 | minor | Inbound message handler validates event.origin + payload.version === 1 but not payload completeness — a malformed TICKET_CREATED (e.g. missing ticketId) is not guarded | §2.2, Detail 2.A, §3 Security | open | R1 | — | Net-new. Add a payload-shape guard (required-field check) before side effects; on malformed → treat as TICKET_CREATE_ERROR/no-op + log. Add a Vitest case. |
REV-6 | minor | Analytics naming unresolved: PRD snake_case vs repo '[Qontak One] [Customer] …' convention | §3 Monitoring note, §5 | open | R1 | — | Confirm with data team whether to wrap snake_case under a '[Qontak One] [Customer] Create Ticket Embed …' label. |
REV-7 | minor | NFS performance budget defers to "app defaults" — no concrete LCP/INP/CLS numbers or browser-version matrix | §3 Performance Requirement | open | R1 | — | Net-new. Acceptable for an additive lazy panel, but state the inherited numeric targets + browser matrix explicitly so test assertions have a number. |
REV-8 | minor | Auto-associate vs fallback POST undetermined — gates whether chunk 6 ships | §5 OQ-6, Detail 1.C S04 | open | R1 | — | Confirm with CRM whether the CDP path auto-associates the contact; if yes, chunk 6 is dead code. |
REV-9 | minor | Figma frames pending for both surfaces | §1 Design References, §5 OQ-8 | open | R1 | — | Mitigated — UI shell fully determined by the in-repo Deal pattern; pixel polish + menu copy/icon wait on Figma. |
REV-10 | minor | Exact ticket-create permission key for the CDP entry unnamed | §5 OQ-4 | open | R1 | — | Mirror Notes.vue canAddNotes once CRM/CDP name the CanCan key; put it in a sibling constants file. |
Ledger summary: 10 open (3 blocker / 1 major / 6 minor), 0 fixed (first cycle), 0 accepted-risk. REV-5 and REV-7 should be promoted into the RFC's §5 Open-Questions table — the other eight already live there and are now id-stamped.
PRD → RFC Traceability Matrix
Two PRDs linked (
prd-create-ticket-embeddable-web.mdSUPPORT +prd-create-ticket-from-cdp-anchor.md). Detail 1.A provides forward, reverse, surface, role, and §-by-§ coverage.
| PRD Element | RFC Section | Coverage |
|---|---|---|
TCKT-S01 (open embed) | §2.A, §2.C, Detail 1.C, 4.C #1–#3 | Full |
TCKT-S02 (auto-fill via EMBED_INIT) | §2.A, §2.2, 4.C #4 | Full (contract provisional — REV-3) |
TCKT-S03 (confirm + refresh) | §2.2, §3.A, 4.C #4–#5 | Full (payload-completeness guard missing — REV-5) |
TCKT-S04 (associate created ticket) | §2.4, 4.C #6 | Full, conditional on OQ-6 (REV-8) |
TCKT-S05 (data_source = CDP) | §2.4, §5 | Full (cross-squad — correctly n/a on CDP; depends REV-4) |
TCKT-S06 (pipeline/layout from CRM) | §1 Out of Scope | Full (cross-squad — CRM-owned) |
TCKT-S07-NEG (no create w/o perm/flag) | §2.D, §4.A, 4.C #2 | Full |
| PRD §§ HEADER, 2–15 | Detail 1.A PRD Section Coverage table | Full — all 15 sections mapped |
| RFC decisions D-1…D-6 | Detail 1.B reverse matrix | Full — each traces to a PRD AC/need |
Summary: 7 of 7 PRD stories fully covered (S05/S06 correctly mapped as cross-squad n/a — covered in CRM RFC), 0 partial, 0 missing. 0 RFC decisions without PRD justification (no scope creep).
Scorecard
Frontend Scorecard (11 categories)
| Category | Score | Evidence-Based Rationale |
|---|---|---|
| PRT — PRD Traceability | 9.0 | Detail 1.A: forward (PRD AC→§→file), reverse (decision→AC), UI/role/section coverage, per-story map (1.C) covering all 7 stories once. Bidirectional, no silent drops. |
| TDC — Technical Decisions | 7.5 | Detail 1.B: 6 decisions, each chosen option + alternatives + grounded rejection. CDP-side fully resolved; D-2/D-3 specify a contract that is provisional pending CRM freeze (REV-3) — a real gap for chunks 4–6, honestly owned cross-squad. |
| CNT — Contract Specificity | 7.5 | Detail 2.A: full TS interfaces (props, emits, EmbedInit/Ready/Created/Error/Close/Resize) + state shape + Detail 2.B data-fetching strategy (Pinia + $customFetch, imperative refetch, no SWR/optimistic). Richly specified — but the embed-facing half is flagged provisional (REV-3), the #1 weighted category's central risk. |
| SCB — Scope Boundaries | 9.0 | Detail 2.D: files to create / modify / explicitly NOT touched (AssociatedDeals.vue, associate-existing flow, TicketStore write paths) + shared-component impact. Agent can produce a file-by-file plan. |
| DEP — Dependencies | 9.0 | §1 Dependencies table + Detail 2.4: every dep has owner + availability + blocking flag; correctly flags the core embed as OUT of CRM Phase 1. Config keys (CRM_V3_EMBED_URL, CUSTOMER_360_CRM_URL) verified to exist. |
| FMC — Failure Mode Coverage | 8.5 | Detail 3.A failure catalog (401/403/404/429/500/timeout/offline/retry per call), 3.B error-message catalog (code→i18n→surface), narrative (double-click, dup TICKET_CREATED, nav-during-embed cleanup), origin+version spoof guard. Gap: no malformed-payload guard (REV-5). |
| NFS — Non-Functional Specificity | 7.5 | A11y (3.C) and security (threat model) strong and specific; performance defers to "app defaults" with no numbers and browser matrix only as "app matrix" (REV-7). Justified for a lazy additive panel but thin on quantification. |
| TPS — Test Plan Specificity | 8.5 | Detail 4.B real commands (verified in package.json), per-story Vitest ACs in 1.C ("postMessage once to new URL(CRM_V3_EMBED_URL).origin; not called for spoofed origin"), per-chunk ACs in 4.C, E2E correctly deferred to Lane-C. QA can write the suite. |
| ROL — Rollout & Rollback | 8.5 | §4: flag default OFF, named stages, stop conditions (>10%/>5%/origin-bypass), instant flag-OFF rollback, blast radius; 4.A config contract; 4.D verification + rollback recipe. Schedule/PICs correctly deferred to delivery/. |
| OBS — Observability | 8.0 | §3: 5 named Mixpanel events with properties + triggers, Datadog RUM for errors, alert thresholds + #cdp-ops channel. Naming-convention reconciliation open (REV-6). |
| CPA — Pattern Alignment | 9.0 | Detail 2.0 Repo Reading Guide + Patterns table + Source Verification — all anchors verified against live repo. Mirrors Deal embed, Notes gate, FeatureFlagStore, useMixpanel. The single deviation (typed version:1 vs legacy {msg}) is explicitly justified (D-2/D-3). |
Overall: 8.0 (judgment, not mean). Spec quality is Agentic-Ready-grade; the score sits just under 8.5 because the highest-weighted category (CNT) carries a documented provisional-contract risk and three blocking external OQs gate half the execution plan. No score caps applied.
Decision Closure Assessment
Decision Index
| # | Decision | Status | Critical Gaps |
|---|---|---|---|
| D-1 | Consume CRM embed, build nothing CDP-native | Resolved | none |
| D-2 | Use typed versioned postMessage contract | Partial | Interface provisional until CRM freezes contract (REV-3) |
| D-3 | Pass contact via EMBED_INIT keyed on qontakCustomerId (no token param) | Partial | Same provisional contract + auth assumption depends on OQ-2 (REV-2) |
| D-4 | data_source set server-side from allow-listed embed_source | Resolved | Depends on CRM allow-list (REV-4) but CDP decision itself is closed |
| D-5 | Degrade gracefully to associate-existing | Resolved | none |
| D-6 | New component TicketEmbedPanel.vue vs inline | Resolved | none (explicitly reversible) |
Aggregate: 4 Resolved, 2 Partial, 0 Dangling.
Decision: D-2 — Typed versioned postMessage contract
Status: Partial
What was decided: Adopt EMBED_INIT ⇄ TICKET_CREATED/TICKET_CREATE_ERROR/EMBED_CLOSE/EMBED_RESIZE, version: 1, origin-validated — superseding the Deal embed's legacy {msg} string contract.
Alternatives considered: Mirror the shipped Deal {msg} string contract — rejected because the CRM FE RFC defines a typed payload and {msg} "would not interoperate with the new Phase-1 component." Grounded and correct.
Grounding in existing code: Strong — mirrors AssociatedDeals.vue origin guard (verified L292) but tightens with a version check; the {msg} precedent ('Deal successfully created') verified at L301.
Interface specification: Fully shaped in Detail 2.A as TypeScript types — but the field names are sourced from the PRD, not a frozen CRM contract (the RFC says so explicitly). This is the gap.
Failure handling: Spoofed origin / wrong version → no-op + cdp_ticket_embed_init_dropped. Missing: malformed-but-valid-origin payload (REV-5).
Challenge results:
- Scale: fine — event-driven, single embed per panel.
- Reversibility: low cost to change field names if caught before chunk 4; high cost if built first and the frozen contract differs. This is why chunk 4 must wait on REV-3.
- Consistency: consistent with D-3.
- Agent implementability: an agent could generate the interfaces verbatim — and that is the risk: it would generate a provisional interface confidently.
Gaps and suggestions: Block chunk 4 until CRM freezes the contract; until then keep §2.A labelled provisional (already done). Add the payload-completeness guard (REV-5).
Decision: D-3 — Contact via EMBED_INIT, no token URL param
Status: Partial
What was decided: Pass contact context through EMBED_INIT keyed on qontakCustomerId; the embed authenticates itself from the host session (direct JWT Bearer), so CDP does not append a token query param the way the Deal embed does.
Alternatives considered: token + customer_id URL params as AssociatedDeals.vue does — verified real (L170–172: token, customer_id, source=cdp). Rejected per the CRM BE RFC's direct-JWT model. Well-grounded.
Interface specification: EMBED_INIT payload typed in §2.A (provisional).
Failure handling: OQ-2 — if Qontak One SSO does not flow into the iframe, the create call 401s. The RFC names this the "headline blocking risk" (REV-2) but cannot resolve it CDP-side.
Challenge results:
- Reversibility: if auth turns out to need a
tokenparam after all, this flips back to the Deal pattern —common/constants/auth.tsTOKEN_KEY = 'global_sso_token'(verified L1) is already cited as the fallback. Reasonable hedge. - Agent implementability: an agent cannot verify the auth model from this repo; it must trust the assumption. Blocked by REV-2.
Gaps and suggestions: Confirm OQ-2 with CRM + Platform before chunk 4. The fallback path is already identified, which de-risks reversal.
Decisions D-1, D-4, D-5, D-6 — Resolved (abbreviated)
- D-1 (consume vs build native): chosen + alternative rejected (duplicates CRM logic, perpetuates parity-lag); grounded in the anchor PRD. No interface to pin on CDP side. Resolved.
- D-4 (
data_sourceserver-side from allow-listedembed_source): correctly identifies CDP cannot influence the field (verified — Deal sendssource=cdpclient-side, overwritten server-side). CDP decision closed; CRM allow-list is the dependency (REV-4). Resolved. - D-5 (graceful degrade to associate-existing): additive, entry hidden when unavailable; the associate path "must never regress." Resolved — and the strongest safety property in the RFC.
- D-6 (extract
TicketEmbedPanel.vuevs inline): chosen for testability of the heavier typed handler; explicitly "reversible." Resolved.
UI State Audit
| Component | Loading | Empty | Error | Partial | Success | Assessment |
|---|---|---|---|---|---|---|
TicketEmbedPanel.vue (new) | defined (spinner until EMBED_READY/timeout) | n/a (CRM form always renders once ready) | defined (load-fail / TICKET_CREATE_ERROR → inline + retry + associate hint) | n/a (single embed) | defined (TICKET_CREATED → close + refresh + toast) | 5/5 (empty/partial justified n/a) |
AssociatedTickets.vue (existing list) | defined (existing skeleton) | defined (existing "No tickets yet") | defined (existing toast) | defined (existing infinite-scroll) | defined (refreshed list) | 5/5 |
Summary: 2 of 2 components have all applicable states defined (Detail 2.C). The two n/as are correctly justified, not omissions.
Performance Budget Check
| Metric | Target | Baseline | Source | Assessment |
|---|---|---|---|---|
| LCP / INP / CLS | "inherit app defaults (no new budget)" | not stated | — | vague — no numbers (REV-7); defensible for a lazy panel but un-assertable in tests |
| Bundle size delta | "negligible — one small SFC, no new dependency" | — | reasoning, not measured | adequate (reuses Pixel3/Pinia/useCustomConfig — no new dep, verified) |
| Code-splitting | component-level (panel mounts on open) | — | §3 | adequate |
| Browser support | "inherits the app matrix (Nuxt 4 / Vue 3)" | — | §3 | vague — no explicit version matrix (REV-7) |
Lazy-render + no-new-dependency claims are sound; the only gap is numeric targets for test assertions. Low-stakes for this feature.
Accessibility Review
| Aspect | Specified? | Details | Assessment |
|---|---|---|---|
| Keyboard navigation flow | yes | "+" popover navigable; panel reachable | adequate |
| Focus management (modal/route) | yes | focus moves into panel on open; Esc/back-arrow returns focus to "+" trigger | adequate — explicitly avoids the focus-return trap |
| ARIA labels | yes | iframe title="Create Ticket"; error role="alert" | adequate |
| Heading hierarchy | no | not addressed | minor — panel is shallow; low risk |
| Color contrast | yes | Pixel3 tokens (DS-verified) | adequate |
| Motion sensitivity | yes | no custom animation introduced | adequate |
| Screen reader behavior | partial | iframe title only; embed internals are CRM's | acceptable (cross-origin boundary) |
A11y is one of the better-specified sections; only heading hierarchy is unmentioned (low impact).
Pattern Alignment Check
| Pattern | RFC Approach | Assessment |
|---|---|---|
Pinia setup stores (storeToRefs) | follows | verified — matches TicketStore/UserStore |
| Feature-scoped component folder | follows | TicketEmbedPanel.vue beside AssociatedTickets.vue |
Pixel3 + panda css() styling | follows | matches AssociatedDeals.vue |
toastNotify error/toast | follows | verified L208/L140 in AssociatedTickets.vue |
Permission gate (canAddNotes) | extends | mirrors Notes.vue L83–96 (verified) for canCreateTicket |
Feature flag (featureFlags[code]) | extends | adds code to FeatureFlags + DEFAULT_FEATURE_FLAGS (verified L36/L49) |
| iframe embed + origin-validated listener | replaces contract | mirrors Deal shell but swaps legacy {msg} + URL-param contract for typed version:1 + EMBED_INIT — explicitly justified (D-2/D-3), not a silent parallel system |
Analytics (useMixpanel().track) | follows, naming TBD | event names need convention reconciliation (REV-6) |
No silent pattern drift. The one replacement is deliberate, documented, and the old pattern (Deal embed) is left untouched (Files explicitly NOT touched).
Mermaid Validity
4/4 blocks parse (mermaid v11 headless via jsdom; mmdc itself can't launch Chromium in this sandbox — same limitation the author recorded). Blocks: Repo Map (flowchart LR), Component diagram (flowchart TB), Embed-panel state machine (stateDiagram-v2), Sequence (happy + failure). No semicolons in sequence notes, all ()// in node labels are quoted, no </> in state transition labels. No findings.
Source Verification (independent re-check)
Every anchor in the RFC's Detail 2.0 Source Verification table was independently re-checked against the live qontak-customer-fe sibling checkout — all confirmed accurate (line numbers within ±1–2 where they drift):
| Claim | Verified |
|---|---|
AssociatedDeals.vue sandbox L15 / referrerpolicy L16 / createDealUrl L162 / origin guard L292 / 'Deal successfully created' L301 / listener add L368 remove L372 / https force L167 | ✅ |
Deal embed appends token + customer_id + source=cdp URL params (the D-3 deviation baseline) | ✅ L170–172 |
AssociatedTickets.vue associate POST L182 / fetchTicketsAssociated L241 / markContactUpdated L202 / toastNotify L208 | ✅ |
TicketStore.fetchTicketsAssociated L168 / qontak_customer_id L153 / /v1/tickets on CUSTOMER_360_CRM_URL | ✅ |
Notes.vue canAddNotes L83–96 / is_enabled === true L96 / ADD_NOTES_PERMISSION L27 | ✅ |
auth.ts TOKEN_KEY = 'global_sso_token' L1 | ✅ |
FeatureFlagStore enabled && enabled_for_company L49 / /v1/feature_flags on CUSTOMER_360_URL L77 | ✅ |
Config keys CRM_V3_EMBED_URL + CUSTOMER_360_CRM_URL declared | ✅ custom-config.d.ts L20–21 |
package.json scripts (test, test:coverage, lint, build) | ✅ |
The RFC's own honest caveat — the CRM embed contract/auth/reusability row is not verifiable in this repo — is correct and is the source of REV-1/REV-2/REV-3.
Strengths
- Verified grounding (Detail 2.0). Every CDP-owned anchor is real and accurately cited — independently re-confirmed against
qontak-customer-fe. An agent will not hallucinate the in-repo facts. - Bidirectional PRD traceability (Detail 1.A/1.C). All 7 stories + 15 PRD sections mapped; reverse matrix prevents scope creep; cross-squad ACs correctly marked
n/a — covered in CRM RFC. - Honest blocker disclosure (§5, §7). The RFC refuses to claim readiness it doesn't have: §7 = no, blocking OQs labelled, §2.A flagged provisional. This is exactly the discipline the gate exists to enforce.
- Safety-first design (D-5, §3, §4.D). Additive, flag-gated (default OFF), instant flag-OFF rollback, associate-existing flow explicitly untouched and regression-guarded.
Biggest Gaps
- Provisional core contract (REV-3, §2.A). The
postMessagepayload — the single most important contract for chunks 4–6 — is PRD-sourced, not CRM-frozen. Impact: an agent building chunk 4 today produces code against field names that may change. This is the gate that keeps §7 = no. - Unbuilt/blocked dependency (REV-1/REV-2, §1, §5). The embed endpoint is OUT of CRM Phase 1 and in-iframe auth is unconfirmed. Impact: chunks 4–6 have no integration target and may 401 on the create call regardless of CDP correctness.
- Missing payload-completeness guard (REV-5, §2.2/§3). Origin + version are validated, but a well-origin'd malformed
TICKET_CREATED(e.g. noticketId) flows intofetchWithReset/fallback-associate unchecked. Impact: a runtime error or a fallback POST withundefinedticketId on a partial message.
Priority Actions
- §5 OQ-1/OQ-2/OQ-contract (REV-1/2/3) — drive CRM to (a) commit the CDP-reusable embed to a phase, (b) confirm in-iframe auth, (c) freeze the
postMessagefield names. Nothing in chunks 4–6 is safely executable until these close; this is the §7 flip condition. - §2.2 / §3 Security (REV-5) — add a payload-shape guard (required-field validation per message type) before any side effect; on malformed → no-op + log (treat like
TICKET_CREATE_ERROR). Add a Vitest case alongside the spoofed-origin/wrong-version cases already specified. - §5 (REV-5, REV-7) — promote REV-5 and REV-7 into the RFC's §5 Open-Questions table so the RFC stays the canonical unresolved-work surface (the other eight OQs are already there and now id-stamped).
- §3 Performance (REV-7) — state the inherited numeric LCP/INP/CLS targets and the concrete browser-version matrix so test assertions have a number, even if "= app default."
Implementation Readiness Checklist
Unblocked (agent can proceed — chunks 1–3)
- PRD → RFC traceability complete (Detail 1.A/1.C)
- CDP-owned decisions resolved with alternatives rejected (D-1, D-4, D-5, D-6)
- Failure modes + error-message catalog (Detail 3.A/3.B) — except malformed-payload (REV-5)
- Configuration contract (Detail 4.A — flag + 2 config keys, verified)
- Rollout plan + rollback mechanism (§4, 4.D)
- Observability metrics + alerts (§3) — naming TBD (REV-6)
- Task decomposition with per-chunk acceptance criteria (Detail 4.C)
- CDP-owned interfaces / prop types specified (Detail 2.A)
- All UI states defined (Detail 2.C)
- Accessibility specified (Detail 3.C)
- Performance budget quantified (REV-7 — deferred to app defaults)
- Browser support matrix explicit (REV-7)
Blocked (must fix first — chunks 4–6)
- CRM contract frozen (REV-3) — §2.A payload provisional
- CDP-reusable embed committed to a phase (REV-1) — OUT of CRM Phase 1
- In-iframe auth confirmed (REV-2) — create call may 401
Verdict: Fix 3 blockers (REV-1/2/3, all CRM-owned) before chunks 4–6. Chunks 1–3 are ready to implement today behind an OFF flag.
Task Manifest
Matches the RFC's Detail 4.C; verified as well-formed and verifiable. Chunks 1–3 unblocked; 4–6 gated on REV-1/2/3.
| Order | Chunk | Files to Create/Modify | Acceptance Criteria | Dependencies |
|---|---|---|---|---|
| 1 | Scaffold TicketEmbedPanel.vue (iframe + state machine) | create features/customers/detail/components/TicketEmbedPanel.vue (+ .spec.ts) | renders sandboxed iframe src ending /embed/ticket/create; loading state default | None |
| 2 | Permission + flag gate | modify AssociatedTickets.vue, common/store/FeatureFlagStore.ts; create permission constant | entry hidden when perm disabled OR flag OFF; visible iff both (TCKT-S07-NEG) | None |
| 3 | "+" menu + open/close panel | modify AssociatedTickets.vue | "Create Ticket" opens panel; back-arrow closes; associate-existing unaffected (TCKT-S01) | #1, #2 |
| 4 | Typed postMessage handler + EMBED_INIT sender | modify TicketEmbedPanel.vue | EMBED_INIT posted once to new URL(CRM_V3_EMBED_URL).origin, qontakCustomerId === contactId; spoof/wrong-version → no-op; + malformed-payload guard (REV-5) | BLOCKED: REV-1/2/3 |
| 5 | Parent success handling (refresh + toast + analytics) | modify AssociatedTickets.vue | valid created → fetchTicketsAssociated re-invoked + toast + cdp_ticket_embed_created tracked | #4 |
| 6 | Optional fallback associate (OQ-6) | modify AssociatedTickets.vue | fallback path issues one POST with payload ticketId; failure logs cdp_ticket_embed_associate_failed | #5, gated REV-8 |
| 7 | Final verification | — | pnpm lint · pnpm test:coverage · pnpm build green | #1–#6 |
Dangling Decisions Log
| # | Decision | Location | Owner | Deadline |
|---|---|---|---|---|
| — | None. D-2/D-3 are Partial (provisional interface), not dangling — both have a chosen option; only the consumed contract is unfrozen. | Detail 1.B | — | — |
Open Questions
| # | Question | Category | Severity |
|---|---|---|---|
| 1 | Will CRM commit the CDP-reusable embed to a phase, and when? (REV-1) | DEP | Blocking |
| 2 | Does Qontak One SSO/JWT flow into the iframe so the create call authenticates? (REV-2) | TDC/DEP | Blocking |
| 3 | What are the frozen EMBED_INIT/TICKET_* field names? (REV-3) | CNT | Blocking |
| 4 | Will CRM add embed-web-cdp to ALLOWED_EMBED_SOURCES? (REV-4) | DEP | Important |
| 5 | How is a well-origin'd but malformed message handled? (REV-5) | FMC | Important |
| 6 | Mixpanel event naming — snake_case vs '[Qontak One] [Customer] …'? (REV-6) | OBS | Nice-to-have |
| 7 | Concrete perf targets + browser matrix, or explicit "= app default" numbers? (REV-7) | NFS | Nice-to-have |
| 8 | Does the CDP path auto-associate, or is the fallback POST required? (REV-8) | TDC | Important |
| 9 | Figma frames for entry + panel (REV-9) | NFS/Design | Nice-to-have |
| 10 | Exact ticket-create permission key (REV-10) | CNT | Important |
Evidence Notes
- Detail 2.0 Source Verification — independently re-checked every anchor against the live repo; all accurate. This is what lifts CPA/DEP/SCB to 9.0 and gives High confidence.
- Detail 2.A UI Contract — full TS interfaces present (CNT strength) but self-flagged provisional (CNT/TDC ceiling). The honesty is correct; the risk is real.
- §5 + §7 — the RFC's own blocking-OQ discipline drove the HOLD verdict; the review agrees rather than overriding.
- Mermaid — 4/4 parse headless; the author's
mmdc-can't-launch-Chromium note reproduced exactly.
Review History
| Cycle | Date | Reviewed RFC revision (last_updated / commit) | Score | Verdict | Findings open → fixed | Notes |
|---|---|---|---|---|---|---|
R1 | 2026-07-01 | 2026-06-30 / d656b29 | 8.0 | HOLD (partial-proceed) | 10 open (3 blocker / 1 major / 6 minor), 0 fixed | First cycle. Anchors verified against live qontak-customer-fe; 4/4 mermaid parse. REV-5 & REV-7 net-new; rest formalize §5 OQs. Chunks 1–3 executable now; 4–6 blocked on CRM REV-1/2/3. |