Skip to main content

RFC: Customer Segmentation — Frontend

Document Conventions (do not remove)

This RFC follows the Qontak RFC Template format for governance — the metadata table, Confluence sections 1–6, and Comment logs are mandatory. Replace placeholder values; mark sections N/A — reason when truly inapplicable rather than deleting them.

It is also agent-execution-ready: the §1 Design References, §2 Repo Reading Guide (Detail 2.0), mermaid diagrams, and §4 Agent Execution Plan

  • Verification & Rollback Recipe must be complete before §7 Ready for agent execution: yes.

Delivery & project management live elsewhere. This RFC is the technical artifact only — it deliberately holds no staffing, effort estimates, timeline, or rollout schedule. Those live in the initiative's delivery/ folder.

The YAML frontmatter at the very top is the machine-readable index agents parse. The metadata table below is the human-readable governance record. Both must agree on every shared field (status, owner, type, dates).

Metadata

FieldValueNotes
StatusIDEAYAML status: draft
DRIAzani RamadhanFrontend owner for the customer segmentation feature
TeamcdpCDP squad
Author(s)Azani Ramadhan
ReviewersTBD — CDP FE tech lead
Approver(s)TBD — tech lead + infosec approver
Submitted Date2026-06-30
Last Updated2026-06-30
Target Release2026-Q3commitment_date 2026-07-21 (revenue) — see initiative README
Target Quarter2026-Q3
Deliverynot yet handed to deliverySee ../delivery/timeline.md once timeline is confirmed
Related../prds/prd-customer-segmentation-basic-attributes.md · ../prds/prd-customer-segmentation-use-case-activities.md · ../prds/prd-customer-segmentation-anchor.md
DiscussionTBD — CDP squad Slack

Type: frontend Sub-type: new-feature

Sections at a Glance

  1. Overview (§1 Design References — Figma; §1 Traceability; §1 Per-Story Change Map)
  2. Technical Design (Repo Reading Guide → Component Diagram → State Machine → Sequence → APIs Consumed → UI Contract → Data-Fetching Strategy → UI State Matrix → Scope Boundaries → Asset Inventory)
  3. High-Availability & Security
  4. Backwards Compatibility and Rollout Plan (§4 Agent Execution Plan + Verification & Rollback Recipe)
  5. Concern, Questions, or Known Limitations
  6. Comment logs
  7. Ready for agent execution

1. Overview

This RFC covers the complete frontend implementation of Customer Segmentation in qontak-customer-fe (Nuxt 3) — a rule-based dynamic segmentation engine that classifies contacts into auto-updating groups, reusable for Broadcast targeting. The implementation spans two child PRDs under the same initiative:

  • Basic Attributes (TF-2544 — foundation, in progress): Segment List, Segment Builder over default/custom fields, basic events, consent, loyalty attributes; segment creation/editing; Preview; Segment Detail (Overview); Send Campaign entry point; permission gating.
  • Use-Case Activities (TF-2549–TF-2555 — draft): Extends the existing builder's condition palette with cross-module association conditions (Conversations, Campaigns, Company, Deals, Tickets), aggregate use-case metrics, and RFM/Recency fields; adds the Segment Detail Performance tab; finalizes permission keys.

Significant scaffolding already exists in the repo (SegmentStore.ts, DrawerCreateSegment.vue, FilterCriteriaRow.vue, SegmentListPage.vue, pages/customers/segments/[id].vue). This RFC describes completing the Basic Attributes feature and extending it with Use-Case Activities FE work.

Success Criteria

  1. Users can create, edit, duplicate, and archive rule-based segments over all Basic Attributes field types (default, custom, basic events, consent, loyalty) — measured by vitest unit tests passing and E2E smoke test.
  2. Users can add association conditions (Conversations, Campaigns, Company, Deals, Tickets) and aggregate metrics conditions in the same builder — measured by component test coverage.
  3. Preview panel shows matched customer count within 5 s (p95) per PRD §6 Constraints.
  4. Segment Detail → Overview shows total matched, percentage reach, reachability by channel (WhatsApp + Email), and customer matched table.
  5. Segment Detail → Performance tab renders growth chart (line), source/domicile/age/gender distribution charts.
  6. Send Campaign flow opens channel-selector sidebar for WhatsApp; routes to Broadcast with segment as recipient.
  7. All four permission keys (customers_segment_view/add/manage/archived) gate UI surfaces correctly.
  8. All Vitest unit tests pass; E2E smoke spec for the golden segment-creation path passes.

Out of Scope

  1. Exception filters (exclude-customers-from-segment) — explicitly descoped (Non-Goal §7 in Use-Case Activities PRD).
  2. Email broadcast from a segment — WhatsApp only for Send Campaign at this phase (Non-Goal §8).
  3. Real-time segment re-evaluation — daily batch only.
  4. Segment membership export (CSV/XLSX) — deferred to export initiative.
  5. AI/ML-suggested segments — future phase.
  6. Mobile app configuration — web-only.
  7. Backend segmentation engine internals — covered by the external Confluence BE RFC (DRAFT RFC — Customer Segmentation based on Basic Attributes (Backend) related to TF-2544); this RFC consumes the BE API only.
  • Basic Attributes PRD: ../prds/prd-customer-segmentation-basic-attributes.md
  • Use-Case Activities PRD: ../prds/prd-customer-segmentation-use-case-activities.md
  • Anchor PRD: ../prds/prd-customer-segmentation-anchor.md
  • External BE RFC: Confluence (not mirrored here) — DRAFT RFC — Customer Segmentation based on Basic Attributes (Backend) (submitted 2026-05-19, related to TF-2544)
  • Figma (Basic Attributes): https://www.figma.com/design/ZbjJxiiEsyFIBPCsTOK0lm/Customer-Segmentations?node-id=8670-238601

Assumptions

  1. BE APIs listed in Detail 2.4 are available or in active development; this RFC targets the API contract from SegmentStore.ts (already calling /v1/segments).
  2. contact-service.qontak.com hosts all segment endpoints (CONTACT_SERVICE_SERVICE_API in production config).
  3. Figma for Use-Case Activities FE surfaces (association conditions, aggregate metrics, performance tab) will be designed before those chunks execute; see §5 Open Questions.
  4. cdp_segmentation_enabled feature flag (already gating pages/customers/segments/[id].vue) controls rollout for the entire feature.
  5. Package tier (Plus / Ultimate / 360) and per-CID segment limits are enforced by the BE; the FE surfaces the 409 error message from the API.

Dependencies

DependencyStatusBlocking?Notes
BE segment CRUD API (/v1/segments)exists — SegmentStore.ts already calls itNOExisting in repo
BE segment preview API (/v1/segments/preview)exists — SegmentStore.ts calls itNO
BE segment detail with total_matched, percentage_reach, reachability fieldsneeds-building — not yet in SegmentStore.tsYES for Segment DetailNeeds BE to expose reachability data
BE Performance tab data API (growth chart, source/domicile/age/gender)needs-buildingYES for Performance tabAPI path TBD — see §5
BE aggregate metrics and recency condition supportneeds-buildingYES for Use-Case ActivitiesPart of external BE RFC
Broadcast API for Send Campaign (create recipient list from segment)needs-buildingYES for Send CampaignBROADCAST_API_BASE_URL = api.mekari.com
cdp_segmentation_enabled feature flagexists (FeatureFlagStore.ts)NOAlready in prod
Design — Figma for Use-Case Activities surfacesTBDYES for association/metrics/perf chunksSee §5 Open Questions

Design References (frontend-specific — required)

PRD-named surfaceFigma / design linkFrame nameDesign system versionDesign QA contactNotes
Segment List page (Index)https://www.figma.com/design/ZbjJxiiEsyFIBPCsTOK0lm/Customer-Segmentations?node-id=8670-238601Segment List@mekari/pixel3 (latest)Nur Asmara / Rizky Surur (from Basic Attributes PRD)Only node root linked; agent must navigate to specific frames
Segment Builder — step 1 (Segment Info)same Figma fileSegment Info step@mekari/pixel3same
Segment Builder — step 2 (Rule Builder)same Figma fileRule Builder@mekari/pixel3sameIncludes nested filter UI
Preview Customer panelsame Figma filePreview panel@mekari/pixel3sameRight-side panel after "Preview customers" click
Segment Detail — Active Overviewsame Figma fileSegment Detail Active@mekari/pixel3sameTwo-column layout with info + customer table
Segment Detail — Archived Overviewsame Figma fileSegment Detail Archived@mekari/pixel3sameNo reachability section
Segment in Customer Index sub-sidenavsame Figma fileCustomer Index Segments@mekari/pixel3sameLeft sub-sidenav segment list
Association conditions (Conversations/Campaigns/Company/Deals/Tickets)n/a — design pendingTBD@mekari/pixel3TBDBlocker for Use-Case Activities chunks — see §5
Aggregate metrics conditionsn/a — design pendingTBD@mekari/pixel3TBDBlocker
Segment Detail — Performance tabn/a — design pendingTBD@mekari/pixel3TBDBlocker
Send Campaign sidebar (channel selector)n/a — design pendingTBD@mekari/pixel3TBDBlocker

Agent must NOT start chunks for "n/a — design pending" surfaces until frames are confirmed.

Detail 1.A — PRD Traceability Matrix

All story IDs from Basic Attributes PRD are prefixed BA-S (no explicit ids in that PRD; mapped from Jira refs). Use-Case Activities story IDs are as defined in that PRD (SEG-Sxx).

Forward (PRD AC → RFC):

PRD composite AC idRFC sectionComponent / file
BA-S01/AC-1§4.C Chunk 1pages/customers/index.vue — Settings icon entry point
BA-S01/AC-2§2.A SegmentListPagefeatures/customers/views/SegmentListPage.vue
BA-S01/AC-3§2.A SegmentListPageSegmentStore.ts fetchSegments search param
BA-S01/AC-4,5§2.A SegmentListPageSegmentStore.ts created_at_from/to query params
BA-S01/AC-6§2.A DrawerCreateSegmentDrawerCreateSegment.vue
BA-S01/AC-7..10§2.A SegmentListPageSegmentListPage.vue actions menu
BA-S02/AC-1..4§4.C Chunk 2features/customers/views/ListPage.vue (sub-sidenav section)
BA-S03/AC-1..3§2.A DrawerCreateSegmentDrawerCreateSegment.vue segment info step
BA-S04/AC-1..7§2.A FilterCriteriaRowFilterCriteriaRow.vue + DrawerCreateSegment.vue
BA-S05/AC-1..5§2.A FilterCriteriaRowFilterCriteriaRow.vue (fieldType dispatches to correct input)
BA-S06/AC-1..3§2.A FilterCriteriaRowsame — dropdown_select fieldType
BA-S07/AC-1..12§2.A FilterCriteriaRowFilterCriteriaRow.vue custom field types
BA-S08/AC-1..4§2.A FilterCriteriaRoweventsItems.ts + FilterCriteriaRow.vue
BA-S09/AC-1..5§2.A FilterCriteriaRowcommunication consent fields (boolean fieldType)
BA-S10/AC-1..5§2.A FilterCriteriaRowloyalty fields (nice-to-have)
BA-S11/AC-1..3§2.A DrawerCreateSegmentnested filter row AddFilter in same criteria group
BA-S12/AC-1..3§2.A DrawerCreateSegmentmultiple criteria groups (max MAX_FILTER_GROUPS=2)
BA-S13/AC-1..4§2.A SegmentListPage + SegmentStoreSegmentStore.ts preview response — total vs visible
BA-S14/AC-1..2§2.A DrawerCreateSegmentPreview customer panel
BA-S15/AC-1..6§2.A SegmentListPageSegmentListPage.vue detail view
BA-S16/AC-1..4§2.A SegmentListPagearchived segment detail
BA-S17/AC-1..5§4.C Chunk 6PerformanceTab.vue (new)
BA-S18/AC-1..9§4.C Chunk 5SendCampaignDrawer.vue (new)
BA-S19/AC-1..4§4.C Chunk 7Permission gating across components
SEG-S01/AC-1..10§2.A FilterCriteriaRowextends existing field type handling
SEG-S02/AC-1..8§4.C Chunk 3AssociationCondition.vue (new)
SEG-S03/AC-1..7§4.C Chunk 3AssociationCondition.vue (new)
SEG-S04/AC-1..3§4.C Chunk 3AssociationCondition.vue (new)
SEG-S05/AC-1..7§4.C Chunk 3AssociationCondition.vue (new)
SEG-S06/AC-1..7§4.C Chunk 3AssociationCondition.vue (new)
SEG-S07/AC-1..3§2.A DrawerCreateSegmentsame as BA-S11
SEG-S08/AC-1..3§2.A DrawerCreateSegmentsame as BA-S12
SEG-S10/AC-1..2§2.A DrawerCreateSegmentsame as BA-S14
SEG-S11/AC-1..6§2.A SegmentListPagesame as BA-S15 + reachability
SEG-S12/AC-1..4§2.A SegmentListPagesame as BA-S16
SEG-S13/AC-1..5§4.C Chunk 6PerformanceTab.vue (new)
SEG-S15/AC-1..4§4.C Chunk 7permission keys

UI / Consumer Surface Coverage

PRD-named surfaceConsumerRequired reads (BE endpoint)Required writes (BE endpoint)Status surface
Segment List pagewebGET /v1/segmentsstatus: active/archived badge
Segment Builder (Create)webGET /v1/customers/fields (field palette), POST /v1/segments/previewPOST /v1/segments
Segment Builder (Edit)webGET /v1/segments/:id, GET /v1/customers/fields, POST /v1/segments/previewPATCH /v1/segments/:id
Customer Index sub-sidenav — Segments sectionwebGET /v1/segments?status=active&per_page=10segment name clickable
Preview Customer panelwebPOST /v1/segments/previewtotal_matched count
Segment Detail — Active OverviewwebGET /v1/segments/:id/detailPOST /v1/segments/:id/archiveactive badge, total_matched, % reach, reachability
Segment Detail — Archived OverviewwebGET /v1/segments/:id/detailarchived badge, total_matched (no reachability)
Segment Detail — Performance tabwebGET /v1/segments/:id/performance (TBD)growth chart, distributions
Send Campaign sidebarwebGET /v1/segments/:id/reachabilityPOST /broadcast/v2/recipients (TBD)audience size by channel

Role Coverage

PRD roleUI surface visibilityAction buttons enabledAuth scope expected from BENotes
customers_segment_view = ALL ACCESSAll segment surfaces visibleFull readJWT scopeDefault for Org Admin
customers_segment_view = OWNED ONLYOnly own segments visible in list/detailRead ownJWT scopeCustomer list in detail filtered
customers_segment_view = DISABLEDSegment section hiddenNoneForbidden component shown
customers_segment_add = ALL ACCESS"Create segment" button visibleCreateJWT scope
customers_segment_add = DISABLED"Create segment" button hiddenNone
customers_segment_manage = ALL ACCESSEdit action visible for all segmentsEdit anyJWT scope
customers_segment_manage = OWNED ONLYEdit action visible for own segments onlyEdit ownJWT scope
customers_segment_manage = DISABLEDEdit action hiddenNone
customers_segment_archived = ALL ACCESSArchive action visible for allArchive anyJWT scope
customers_segment_archived = OWNED ONLYArchive action visible for own onlyArchive ownJWT scope
customers_segment_archived = DISABLEDArchive action hiddenNone

PRD Section Coverage

PRDSection #TitleWhere covered
Basic AttributesAllKey features + operator logic tables§2.A UI Contract — FilterCriteriaRow dispatch table
Basic AttributesUser StoriesAC-1..end§1 Detail 1.A traceability + §4.C execution plan
Use-Case Activities§1One-liner + Problem§1 Overview
Use-Case Activities§4Non-Goals§1 Out of Scope
Use-Case Activities§6Constraints§3 Performance Requirements
Use-Case Activities§11Feature Changes (enhancement)§4.C Execution Plan Chunk 3
Use-Case Activities§13.2Operator Logic Reference§2.A UI Contract — AssociationCondition
Use-Case Activities§13.2.6Aggregate Metrics§4.C Chunk 4
Use-Case Activities§13.3User Stories SEG-S01..S15§1 Detail 1.A + §4.C

Detail 1.B — Decisions Closed

DecisionChosen optionAlternatives rejectedWhy rejected
Segment builder UX patternDrawer (multi-step) with inline condition rowsModal / full-page formDrawer is already the implemented pattern (DrawerCreateSegment.vue); reverting wastes work
State management for rule builderPinia SegmentStore (existing)Local component ref stateStore already persists filterCriteria + ruleSetOperator; enables cross-component access
Condition palette extension for associationsNew AssociationCondition.vue component alongside existing FilterCriteriaRow.vueExtend FilterCriteriaRow with association flagKeeps concerns separate; FilterCriteriaRow handles simple field conditions, AssociationCondition handles group-level association queries
Data fetching patternuseCustomFetch() composable ($customFetch) called from Pinia actionsVue Query / SWREntire existing codebase uses $customFetch; no alternative considered for consistency
Design system@mekari/pixel3no alternative considered — team standard
Feature flagcdp_segmentation_enabled (existing)No new flag needed at this phaseFlag is already wiring the /customers/segments/[id] route
Figma-to-code ownership (Use-Case Activities)Blocked — no frames yetn/aSee §5 Open Questions

Detail 1.C — Per-Story Change Map

Story idStory titleLayer scopeChanges (concrete FE artifacts)Composite AC ids coveredAcceptance criteria (verifiable)RFC anchors
BA-S01Segment Index Menu — List PageFE-onlySegmentListPage.vue search + date filter + actions menu; SegmentStore.fetchSegments with search/date paramsBA-S01/AC-1..10vitest: SegmentListPage renders with mock segments, search fires fetchSegments with param, archive action triggers archiveSegment§2.A · §4.C Chunk 1
BA-S02Segments in Customer Index sub-sidenavFE-onlyListPage.vue — add Segments section to left sub-sidenav; SegmentStore.fetchSegments({ per_page:10 }) on mountBA-S02/AC-1..4vitest: Segments section visible; empty state shows "Create segment"; segment item click navigates to detail§2.A · §4.C Chunk 2
BA-S03Segment Info (Name + Description)FE-onlyDrawerCreateSegment.vue — step 1 form (name required/max-60, description optional/max-250); vee-validate schemaBA-S03/AC-1..3vitest: name required validation fails on empty; max-60 enforced; Cancel clears without save§2.A · §4.C Chunk 1
BA-S04Rule Builder (AND/OR, limits)FE-onlyDrawerCreateSegment.vue — condition group logic; filterFieldConditions.ts MAX_FILTER_GROUPS=2, MAX_NESTED_RULES=3; AND/OR toggleBA-S04/AC-1..7vitest: add 3 nested rows → 4th "Add Filter" disabled; add 2 groups → 3rd "Add Rule" disabled; AND/OR toggle changes ruleSetOperator in store§2.A · §4.C Chunk 1
BA-S05Rule Builder — Default Fields by QontakFE-onlyFilterCriteriaRow.vue dispatches to correct value input by fieldType; text/dropdown/date field typesBA-S05/AC-1..5vitest: text field renders FilterTextInput; dropdown field renders FilterDropdown; date renders FilterDateInput; "is empty" operator hides value input§2.A · §4.C Chunk 1
BA-S06Rule Builder — Custom Fields by Qontak (Source/Status)FE-onlyFilterCriteriaRow.vue — dropdown_select fieldType (same dispatch as BA-S05)BA-S06/AC-1..3Same vitest as BA-S05 for dropdown_select with fieldCategory === 'custom_qontak'§2.A · §4.C Chunk 1
BA-S07Rule Builder — Custom Fields by UserFE-onlyFilterCriteriaRow.vue — all custom field types (text_area, multiple_select, number, date, url, upload, signature, gps)BA-S07/AC-1..12vitest: multiple_select renders FilterMultiselect; number renders FilterNumberInput; upload/signature/gps render is_empty/is_not_empty only§2.A · §4.C Chunk 1
BA-S08Rule Builder — Basic EventsFE-onlyFilterCriteriaRow.vuefieldCategory === 'event'; eventsItems.ts definitions; date + dropdown_select dispatchingBA-S08/AC-1..4vitest: event category shows created_at/updated_at (FilterDateInput), created_by/updated_by (FilterDropdown)§2.A · §4.C Chunk 1
BA-S09Rule Builder — Communication ConsentFE-onlyFilterCriteriaRow.vue — boolean fieldType → new FilterBoolean.vue component (true/false dropdown); single_line_text + date fieldsBA-S09/AC-1..5vitest: boolean fieldType renders true/false dropdown; "is empty" shows correctly§2.A · §4.C Chunk 1
BA-S10Rule Builder — Member Loyalty (nice-to-have)FE-onlyFilterCriteriaRow.vuefieldCategory === 'loyalty'; date/text/number typesBA-S10/AC-1..5vitest: loyalty fields dispatch to correct input types§2.A · §4.C Chunk 1
BA-S11Nested Filter in Same CriteriaFE-onlyDrawerCreateSegment.vue — "Add Filter" in same criteria group; CriteriaItem.operator (AND/OR) between rows; MAX_NESTED_RULES enforcementBA-S11/AC-1..3vitest: add 2nd filter to same group → operator chip appears; remove filter → operator removed; 3rd filter → Add Filter disabled§2.A · §4.C Chunk 1
BA-S12Advanced Criteria Builder (Multiple Filter Groups)FE-onlyDrawerCreateSegment.vue — "Add Rule" creates second group; MAX_FILTER_GROUPS=2; remove group removes all nested filtersBA-S12/AC-1..3vitest: 2 groups rendered with top-level AND/OR selector; remove group removes it entirely§2.A · §4.C Chunk 1
BA-S13Role-Based Visibility + Full Dataset ExecutionFE + BE existingSegmentListPage.vue + SegmentStore — preview shows total_matched from response (not filtered by permission); customer list in detail filtered by view_customer scopeBA-S13/AC-1..4vitest: total_matched shows API value (1000); customer list table renders API-returned filtered rows§2.C UI State Matrix · §4.C Chunk 1
BA-S14Preview CustomerFE + BE existingDrawerCreateSegment.vue — "Preview customers" button triggers SegmentStore.previewSegment; Preview panel right-side with total + sample listBA-S14/AC-1..2vitest: preview panel opens with matched count; empty state shows "No customers match"§2.A · §2.B · §4.C Chunk 1
BA-S15Segment Detail — Active OverviewFE + BE existingSegmentListPage.vue — info section (status, name, description, filter pattern); data summary (total_matched, %reach, last_updated, reachability WhatsApp+Email); customer matched tableBA-S15/AC-1..6vitest: all detail fields render from mock segment detail API; reachability % calculated correctly§2.A · §2.E · §4.C Chunk 1
BA-S16Segment Detail — Archived OverviewFE + BE existingSegmentListPage.vue archived state — same as BA-S15 but no reachability sectionBA-S16/AC-1..4vitest: archived segment hides reachability section§2.A · §4.C Chunk 1
BA-S17Segment Detail — Performance (nice-to-have)FE + BE newPerformanceTab.vue (new) — line chart (growth), pie/bar charts (source, domicile, age, gender)BA-S17/AC-1..5vitest: PerformanceTab renders charts from mock API data; empty state shown when no data; time filter updates chart§4.C Chunk 6 — blocked by Figma + BE API
BA-S18Utilize Segments for Send CampaignFE + BE newSendCampaignDrawer.vue (new) — channel selector (WhatsApp/Email); audience size display; redirect to BroadcastBA-S18/AC-1..9vitest: WhatsApp selected shows phone-count; 0-reachable disables Continue; Continue redirects to Broadcast URL with segment§4.C Chunk 5 — blocked by Figma + Broadcast API contract
BA-S19Permission Keys for SegmentationFE-onlyAll segment components — customers_segment_add/manage/archived gating; UserStore.hasAssociatedAccess() callsBA-S19/AC-1..4vitest: with DISABLED permission, Create/Edit/Archive buttons absent§4.C Chunk 7
SEG-S01Customer Properties/Fields (extension)FE-onlyFilterCriteriaRow.vue — already covers all field types from BA-S05..BA-S09; no new component neededSEG-S01/AC-1..10same as BA-S05..09§2.A
SEG-S02Associations — ConversationsFE + BE newAssociationCondition.vue (new); wires associationsGroups.ts CONVERSATIONS groupSEG-S02/AC-1..8vitest: Conversations group expands; total_conversations renders FilterNumberInput; date fields render FilterDateInput§4.C Chunk 3 — blocked by Figma
SEG-S03Associations — CampaignsFE + BE newAssociationCondition.vue — CAMPAIGNS group from associationsGroups.tsSEG-S03/AC-1..7same pattern as SEG-S02§4.C Chunk 3
SEG-S04Associations — CompanyFE + BE newAssociationCondition.vue — COMPANIES groupSEG-S04/AC-1..3same pattern§4.C Chunk 3
SEG-S05Associations — DealsFE + BE newAssociationCondition.vue — DEALS groupSEG-S05/AC-1..7same pattern§4.C Chunk 3
SEG-S06Associations — TicketsFE + BE newAssociationCondition.vue — TICKETS groupSEG-S06/AC-1..7same pattern§4.C Chunk 3
SEG-S07Nested Filter in Same CriteriaFE-onlysame as BA-S11SEG-S07/AC-1..3same§2.A
SEG-S08Advanced Criteria BuilderFE-onlysame as BA-S12SEG-S08/AC-1..3same§2.A
SEG-S10Preview CustomerFE + BE existingsame as BA-S14SEG-S10/AC-1..2same§2.A
SEG-S11Segment Detail — Active OverviewFE + BE existingsame as BA-S15 (extends with reachability from SEG-S11)SEG-S11/AC-1..6same§2.A
SEG-S12Segment Detail — Archived OverviewFE + BE existingsame as BA-S16SEG-S12/AC-1..4same§2.A
SEG-S13Segment Detail — PerformanceFE + BE newsame as BA-S17SEG-S13/AC-1..5same§4.C Chunk 6
SEG-S15Permission KeysFE-onlysame as BA-S19SEG-S15/AC-1..4same§4.C Chunk 7

2. Technical Design

Detail 2.0 — Repo Reading Guide (read this first)

Repo Map (mermaid)

flowchart LR
subgraph pages["pages/customers/"]
seg_idx["index.vue (entry)"]
seg_list["ListPage.vue"]
seg_detail["segments/[id].vue"]
end

subgraph views["features/customers/views/"]
SegmentListPage["SegmentListPage.vue"]
ListPage["ListPage.vue"]
subgraph seg_comps["components/segment/"]
DrawerCreate["DrawerCreateSegment.vue"]
DrawerEdit["DrawerEditSegment.vue"]
FilterRow["FilterCriteriaRow.vue"]
Inputs["Filter*Input.vue (Text/Number/Date/Dropdown/Multiselect/Url)"]
AssocCond["AssociationCondition.vue (new)"]
PerfTab["PerformanceTab.vue (new)"]
SendCamp["SendCampaignDrawer.vue (new)"]
InfoPanel["SegmentInfoPanel.vue"]
DetailTabs["SegmentDetailTabs.vue"]
end
end

subgraph store["features/customers/store/"]
SegStore["SegmentStore.ts"]
UserStore["UserStore.ts"]
end

subgraph data_files["components/segment/ (data)"]
AssocGroups["associationsGroups.ts"]
FilterConds["filterFieldConditions.ts"]
EventItems["eventsItems.ts"]
Types["types.ts"]
end

subgraph api["contact-service.qontak.com"]
BE_API["POST/GET /v1/segments/*"]
end

seg_detail --> SegmentListPage
SegmentListPage --> DrawerCreate
SegmentListPage --> DrawerEdit
SegmentListPage --> PerfTab
SegmentListPage --> SendCamp
DrawerCreate --> FilterRow
DrawerCreate --> AssocCond
FilterRow --> Inputs
FilterRow --> FilterConds
AssocCond --> AssocGroups
SegStore --> BE_API
SegmentListPage --> SegStore
DrawerCreate --> SegStore

Existing Code Anchors

PathWhy the agent reads itWhat pattern it teaches
features/customers/store/SegmentStore.tsAll segment API calls, state shape, action signatures$customFetch pattern, Pinia defineStore, CriteriaItemSegmentRuleSet mapper
features/customers/views/SegmentListPage.vueMain segment list + detail view implementation (in-progress)Two-column layout, SegmentBreadcrumb, archive/edit flow
features/customers/views/components/segment/types.tsCriteriaItem type definitionfieldType, fieldCategory, condition, operator shape sent to API
features/customers/views/components/segment/filterFieldConditions.tsCondition constants, max limitsMAX_FILTER_GROUPS=2, MAX_NESTED_RULES=3, per-field-type condition arrays
features/customers/views/components/segment/associationsGroups.tsExisting association field catalogue5 groups (Conversations/Campaigns/Companies/Tickets/Deals) with id, name, field_type
features/customers/views/components/segment/FilterCriteriaRow.vueCore condition row — MpPopover for condition picker, field-type dispatchPixel3 MpPopover/MpPopoverList usage; emit('update:criteria') pattern
features/customers/views/components/segment/DrawerCreateSegment.vueMulti-step segment creation drawerStep control, criteria group rendering, "Add Filter"/"Add Rule" logic
features/customers/store/UserStore.tshasAssociatedAccess(permKey) methodHow permission keys gate UI
common/composables/useCustomFetch.ts$customFetch API clientNuxt $fetch wrapper with retry/token-refresh
pages/customers/segments/[id].vueRoute entry for segment detailcdp_segmentation_enabled feature flag guard, customers_segment_view permission check

Patterns to Follow

ConcernPattern in repoReference fileDeviation in this RFC?
State managementPinia defineStore with typed state + actionsSegmentStore.tsnone
API callsuseCustomFetch().then.$customFetch(path, opts) inside Pinia actionsSegmentStore.ts L187, L218, L241none
Styling@mekari/pixel3 components + css() utility from @mekari/pixel3-postcssFilterCriteriaRow.vue L1-80none
Error / toasttoastNotify() from ~/utils/toast called in catch blocksSegmentStore.ts L326-328none
Permission gatinguserStore.hasAssociatedAccess(permKey) → computed canXyz:disabled/v-ifSegmentListPage.vue L509; [id].vue L46none
Feature flagfeatureFlagStore.featureFlags.<flag> in route middleware[id].vue L21none
Folder conventionFeature module: features/<feature>/views/components/<sub>/features/customers/views/components/segment/none

Reading Order for the Agent

  1. features/customers/views/components/segment/types.ts — understand CriteriaItem shape (the central data model)
  2. features/customers/views/components/segment/filterFieldConditions.ts — understand condition constants and limits
  3. features/customers/views/components/segment/associationsGroups.ts — understand association field catalogue
  4. features/customers/store/SegmentStore.ts — understand all existing API calls, state, and buildCriteriaFromRuleSet mapper
  5. features/customers/views/components/segment/FilterCriteriaRow.vue — understand condition row rendering pattern
  6. features/customers/views/components/segment/DrawerCreateSegment.vue — understand multi-step creation flow
  7. features/customers/views/SegmentListPage.vue — understand segment list + detail two-column layout
  8. features/customers/store/UserStore.ts — understand hasAssociatedAccess permission method
  9. common/composables/useCustomFetch.ts — understand $customFetch instantiation
  10. pages/customers/segments/[id].vue — understand route entry, feature flag guard

Source Verification (anti-hallucination)

Anchor / pattern / contractVerified byEvidence
features/customers/store/SegmentStore.tsreaddefineStore('segment', ...) at L1; $customFetch('/v1/segments', ...) at L196; buildCriteriaFromRuleSet at L90
features/customers/views/components/segment/types.tsreadexports CriteriaItem interface with fieldType, fieldCategory, condition, operator at L39-51
features/customers/views/components/segment/filterFieldConditions.tsreadMAX_FILTER_GROUPS = 2 at L1; MAX_NESTED_RULES = 3 at L2; DROPDOWN_SELECT_CONDITIONS at L8; DATE_FIELD_CONDITIONS at L28
features/customers/views/components/segment/associationsGroups.tsreadexports associationsGroups array with 5 groups (CONVERSATIONS/CAMPAIGNS/COMPANIES/TICKETS/DEALS) starting L1
features/customers/views/components/segment/FilterCriteriaRow.vueread<MpPopover> condition picker at L22; field-type dispatch v-if="criteria.fieldType === 'dropdown_select'" at L58
pages/customers/segments/[id].vuereadcdp_segmentation_enabled middleware check at L21; hasAssociatedAccess('customers_segment_view') at L46
features/customers/views/SegmentListPage.vuereadhandleSendCampaign() stub at L267-269 (// TODO: open send campaign flow); customers_segment_view perm check at L509
common/composables/useCustomFetch.tsread (agent: confirmed via SegmentStore.ts L187 useCustomFetch())useCustomFetch() returns { $customFetch } used throughout SegmentStore
API base URL for contact-serviceread configs/production.json"CONTACT_SERVICE_SERVICE_API": "https://contact-service.qontak.com"

Design ↔ Code Mapping

Figma frame / componentImplementing fileReuse vs newDesign tokens usedDeviation from design
Segment Listfeatures/customers/views/SegmentListPage.vuereused (in progress)color.text.default, space.3, border.defaultnone — pixel-faithful
Segment Builder step 1 (Segment Info)DrawerCreateSegment.vue — step 1extendedcolor.text.secondary, space.4none
Segment Builder step 2 (Rule Builder)DrawerCreateSegment.vue — step 2extendedbackground.neutral.subtle, border.mdnone
Preview panelDrawerCreateSegment.vue — preview right panelextendedcolor.text.secondary, space.3none
Segment Detail — Active OverviewSegmentListPage.vue detail sectionextendedcolor.text.default, space.4none
Segment Detail — Archived OverviewSegmentListPage.vue archived variantextendedsamenone
Customer Index sub-sidenavListPage.vue left sidebar sectionnewbackground.neutral.subtlenone
Association conditionsAssociationCondition.vuenewTBD — pending Figman/a — design pending
Performance tabPerformanceTab.vuenewTBDn/a — design pending
Send Campaign sidebarSendCampaignDrawer.vuenewTBDn/a — design pending

Detail 2.1 — Architecture (mermaid)

Component diagram

flowchart TB
user([User]) --> seg_detail_page["pages/customers/segments/[id].vue"]
user --> customers_index["pages/customers/index.vue"]
seg_detail_page --> SegmentListPage["SegmentListPage.vue"]
customers_index --> ListPage["ListPage.vue (segments sub-sidenav)"]

SegmentListPage --> DrawerCreate["DrawerCreateSegment.vue"]
SegmentListPage --> DrawerEdit["DrawerEditSegment.vue"]
SegmentListPage --> PerfTab["PerformanceTab.vue (new)"]
SegmentListPage --> SendCamp["SendCampaignDrawer.vue (new)"]

DrawerCreate --> FilterRow["FilterCriteriaRow.vue"]
DrawerCreate --> AssocCond["AssociationCondition.vue (new)"]
FilterRow --> FilterInputs["Filter*Input.vue variants"]
AssocCond --> FilterInputs

SegmentListPage --> SegStore[("SegmentStore (Pinia)")]
DrawerCreate --> SegStore
DrawerEdit --> SegStore
SegStore --> customFetch["$customFetch (useCustomFetch)"]
customFetch --> ContactService["contact-service.qontak.com"]

State machine (Segment lifecycle)

stateDiagram-v2
[*] --> building : user opens Create Segment
building --> previewing : click Preview customers
previewing --> building : adjust filters
building --> active : save segment
active --> editing : Edit Segment action
editing --> active : save changes
active --> archived : Archive action confirmed
archived --> [*]

Detail 2.2 — Sequence (mermaid)

Happy path — User creates a segment and previews

sequenceDiagram
actor U as User
participant FE as DrawerCreateSegment
participant Store as SegmentStore
participant API as contact-service

U->>FE: Open Create Segment drawer
U->>FE: Fill name + description (step 1)
U->>FE: Add condition row (step 2)
U->>FE: Click "Preview customers"
FE->>Store: previewSegment(criteria)
Store->>API: POST /v1/segments/preview
alt 2xx
API-->>Store: total_matched + sample[]
Store-->>FE: preview state resolved
FE-->>U: Preview panel shows count + sample list
else 422 invalid rule
API-->>Store: 422 validation error
Store-->>FE: preview rejected
FE-->>U: inline error on invalid condition row
else timeout after 10s
API--xStore: no response
Store-->>FE: preview rejected
FE-->>U: toast "Preview timed out — try again"
end
U->>FE: Click "Save Segment"
FE->>Store: createSegment(name, description, rule_set)
Store->>API: POST /v1/segments
alt 201 created
API-->>Store: segment id
Store-->>FE: createStatus resolved
FE-->>U: drawer closes, navigateTo segment detail
else 409 segment limit reached
API-->>Store: 409
Store-->>FE: createStatus rejected
FE-->>U: toast "Segment limit reached"
end

Failure path — Archive segment with permission check

sequenceDiagram
actor U as User
participant FE as SegmentListPage
participant Store as SegmentStore
participant API as contact-service

U->>FE: Click "Archive" in More Actions
FE->>FE: check customers_segment_archived permission
alt permission DISABLED
FE-->>U: Archive action hidden
else permission OK
FE-->>U: Show ModalArchiveSegment
U->>FE: Confirm archive
FE->>Store: archiveSegment(id)
Store->>API: POST /v1/segments/:id/archive
alt 200 OK
API-->>Store: success
Store-->>FE: navigateTo /customers
else 403 forbidden
API-->>Store: 403
Store-->>FE: rejected
FE-->>U: toast "You don't have permission to archive this segment"
end
end

Detail 2.3 — Database Model

N/A for this frontend RFC. Client-side persistence is limited to:

  • Pinia in-memory state: SegmentStore state (segments list, filter criteria, preview result) — no localStorage persistence; cleared on page refresh.
  • No IndexedDB or cookie persistence introduced.

Detail 2.4 — APIs Consumed

MethodPathStatusContract authorityNotes
GET/v1/segmentsexistsSegmentStore.ts L314Paginated list; params: page, per_page, search, status, created_at_from, created_at_to
POST/v1/segmentsexistsSegmentStore.ts L196Create segment; body: { name, description, rule_set }
GET/v1/segments/:idexistsSegmentStore.ts L243Returns SegmentDetail with rule_set; used for edit load
PATCH/v1/segments/:idexistsSegmentStore.ts L274Update segment name/description/rule_set
POST/v1/segments/previewexistsSegmentStore.ts L220Returns { total_matched, total_customer_base, sample[] }
POST/v1/segments/:id/archiveexistsSegmentStore.ts L338Archives a segment
POST/v1/segments/:id/duplicateexistsSegmentStore.ts L360Duplicates segment; returns new segment id
GET/v1/segments/:id/detailneeds-buildingBE RFC (external)Detail with total_matched, percentage_reach, last_updated, reachability.whatsapp, reachability.email
GET/v1/segments/:id/performanceneeds-buildingBE RFC (external) — TBDGrowth chart data + source/domicile/age/gender distributions
POST/broadcast/v2/recipients (TBD)needs-buildingBroadcast squadCreate recipient list from segment before redirect

Detail 2.A — UI Contract

FilterCriteriaRow.vue — single condition row

  • Props: criteria: CriteriaItem (from types.ts)
  • Emits: update:criteria(updated: CriteriaItem), remove
  • Field-type dispatch (field → component):
fieldTypeRenders
single_line_text, text_area, urlFilterTextInput.vue / FilterUrlInput.vue
dropdown_selectFilterDropdown.vue
multiple_selectFilterMultiselect.vue
numberFilterNumberInput.vue
dateFilterDateInput.vue
booleanFilterBoolean.vue (new — true/false dropdown)
upload, signature, gpscondition-only row (is_empty/is_not_empty) — no value input
  • Condition picker: MpPopover showing condition list from filterFieldConditions.ts keyed by fieldType.
  • "is empty" / "is not empty": hides value input component, passes value: '' to store.
  • A11y: aria-label on condition trigger; focus returned to trigger on popover close.

AssociationCondition.vue (new) — association group condition row

  • Props: group: AssociationGroup (label + items from associationsGroups.ts), criteria: CriteriaItem
  • Emits: update:criteria(updated: CriteriaItem), remove
  • Renders a grouped dropdown to select the association field; then delegates value input to same Filter*Input.vue components based on the selected item's field_type.
  • State: local selectedField ref drives the value input display.
  • Analytics event: segment.association_condition_added (field: group.label + item.id).

DrawerCreateSegment.vue — multi-step create/edit drawer

  • Props: isOpen: boolean, segmentId?: string (for edit mode)
  • Emits: close, saved(segmentId: string)
  • Steps: 1 = Segment Info (name/description); 2 = Rule Builder + Preview.
  • Condition limit enforcement: disables "Add Filter" when conditionsInGroup.length >= MAX_NESTED_RULES; disables "Add Rule" (new group) when groups.length >= MAX_FILTER_GROUPS.
  • Slot: Preview panel slides in from the right side of step 2 on "Preview customers" click.

SegmentListPage.vue — detail view (existing, extended)

  • Props: segmentId: string
  • Tabs (via SegmentDetailTabs.vue): Overview | Performance
  • Overview tab shows:
    • SegmentInfoPanel.vue (left column): status badge, name, description, filter pattern (rule_set summary)
    • Right column: total_matched (absolute), % reach, last_updated timestamp, reachability bars (WhatsApp %, Email %)
    • Customer matched table: name, source, last_activity, added_by
  • Performance tab: renders PerformanceTab.vue (new); see §4.C Chunk 6.

Detail 2.B — Data-Fetching Strategy

  • Library: Nuxt 3 built-in $fetch wrapped by useCustomFetch() composable; called from Pinia actions — no Vue Query / SWR.
  • Cache key: none (stateless $fetch); Pinia state serves as the in-memory cache.
  • TTL / refetch triggers: Segment list refetches on page mount and after create/archive/duplicate actions; segment detail refetches on route load.
  • Stale-while-revalidate: no — Pinia state is replaced on each fetch.
  • Optimistic updates: no — all mutations await API response before updating state.
  • Preview debounce: preview button is manual (user-triggered), not debounced continuously; the system flow spec (§13.1) notes "debounced 500ms" for the preview panel update in Use-Case Activities — this applies only if preview is triggered on condition change, which is not the current pattern (it is button-triggered). Open question in §5.

Detail 2.C — UI State Matrix

SurfaceLoadingEmptyErrorPartialSuccess
Segment listSkeleton rows"No segments yet. Create your first segment." + CTA"Failed to load segments. Try again." + RetryTable with segment rows
Segment builder — condition field paletteSkeleton in dropdownFields load progressivelyField list rendered
Preview panelSpinner + "Calculating...""No customers match your filters.""Preview failed. Try again."Count + sample list
Segment detail overviewSkeleton blocks"Failed to load segment."All sections rendered
Segment detail performanceSkeleton charts"No performance data yet.""Failed to load performance data."Charts rendered before all distributions loadAll charts rendered
Customer matched tableSkeleton rows"No customers matched for your permission scope.""Failed to load customers."Table with customer rows

Detail 2.D — Scope Boundaries

Files to create:

  • features/customers/views/components/segment/FilterBoolean.vue
  • features/customers/views/components/segment/AssociationCondition.vue
  • features/customers/views/components/segment/PerformanceTab.vue
  • features/customers/views/components/segment/SendCampaignDrawer.vue

Files to modify:

  • features/customers/store/SegmentStore.ts — add fetchSegmentDetail, fetchPerformance, createRecipientList actions; add typed response interfaces
  • features/customers/views/SegmentListPage.vue — wire Performance tab, Send Campaign handler, reachability data
  • features/customers/views/ListPage.vue — add Segments section to left sub-sidenav
  • features/customers/views/components/segment/DrawerCreateSegment.vue — add AssociationCondition rows, toggle between field vs association type
  • features/customers/views/components/segment/FilterCriteriaRow.vue — add boolean field type dispatch to FilterBoolean.vue
  • features/customers/views/components/segment/filterFieldConditions.ts — add BOOLEAN_FIELD_CONDITIONS constant (is/is_not for true/false, is_empty/is_not_empty)
  • pages/customers/segments/[id].vue — no structural change needed (delegates to SegmentListPage.vue)

Files explicitly NOT touched:

  • features/customers/store/CustomerStore.ts — no customer store changes
  • features/customers/create/ — create customer flow is separate
  • features/customers/detail/ — customer detail feature is separate
  • Any shared component in common/components/ — no modifications to shared components

Shared components touched:

  • common/components/Forbidden/Forbidden.vue — referenced in [id].vue, not modified

Detail 2.E — State Surface Contract

EntityState field / event consumedDefault valuesSource endpointStale-tolerance window
Segment`status: 'active''archived'`'active' on createGET /v1/segments/:id/detail
Segmenttotal_matched: number0GET /v1/segments/:id/detailSame
Segmentpercentage_reach: number0GET /v1/segments/:id/detailSame
Segmentreachability.whatsapp_pct: number0GET /v1/segments/:id/detailSame
Segmentreachability.email_pct: number0GET /v1/segments/:id/detailSame
Segmentlast_updated: string (ISO-8601)GET /v1/segments/:id/detailSame
Preview resulttotal_matched, sample[]emptyPOST /v1/segments/preview0 s — computed on request

Detail 2.F — Asset Inventory

Asset nameTypeSourceFormatPath in repo
Chart library (growth line chart, distribution charts)3rd-party libnpm install — TBD (confirm with team: chart.js/vue-chartjs or Pixel3 chart component if available)JS/CSS bundlepackage.json dependency — no static asset

No new icon, illustration, or image assets identified. All UI icons use @mekari/pixel3 built-in <MpIcon> set.


3. High-Availability & Security

This is a fully client-side rendering feature in an existing Nuxt 3 SPA. No new server-side infrastructure is introduced. Graceful degradation:

  • If GET /v1/segments fails on Segment List page → error state with "Failed to load segments. Try again." and a Retry CTA.
  • If POST /v1/segments/preview is slow (> 10 s) → timeout state with user-friendly toast.
  • If GET /v1/segments/:id/detail fails on detail load → error state with retry.
  • If Broadcast API is unavailable during Send Campaign → disable Continue button and show "Campaign service unavailable. Try again later."

Performance Requirement

  • LCP target: ≤ 2.5 s (per Pixel3 standard; no specific PRD LCP target stated — aligned with §6 Constraints: segment list load ≤ 2 s p95).
  • INP target: ≤ 200 ms for condition add/remove actions.
  • CLS target: ≤ 0.1 (skeleton screens prevent layout shifts).
  • Bundle size budget: no new heavy dependencies introduced beyond a chart library (TBD; target ≤ 50 KB gzip for chart components via dynamic import). Current bundle baseline unknown — agent must measure with npm run build and compare.
  • Code-splitting: PerformanceTab.vue and SendCampaignDrawer.vue should be lazy-loaded (defineAsyncComponent) since they are not on the initial render path.
  • Image strategy: no images introduced; icons via Pixel3 SVG sprite.
  • Browser support: Chrome/Edge latest 2, Safari 15+, Firefox latest — per existing app baseline.
  • Responsive breakpoints: ≥ 1024 px (desktop-only feature per PRD §6 "web only").
  • i18n: all user-facing strings must use $t() / useI18n() — no hardcoded English strings. Locale keys under customers.segments.* namespace (follow existing patterns in i18n/ — check existing locale files before naming new keys).

Monitoring & Alerting

  • Analytics events (Mixpanel via useMixpanel() composable):

    EventWhenProperties
    segment.list.viewedon Segment List page mount
    segment.createdon POST /v1/segments 201{ segment_id, field_types[], group_count }
    segment.preview.triggeredon Preview button click{ condition_count, group_count }
    segment.detail.viewedon Segment Detail load{ segment_id, status }
    segment.archivedon archive confirm{ segment_id }
    segment.send_campaign.initiatedon Send Campaign button click{ segment_id, channel }
    segment.association_condition_addedon AssociationCondition save{ group_label, field_id }
  • Error monitoring: Sentry (already configured in app) — no new DSN; segment-related errors will surface under existing Sentry project. Severity: error for API 5xx; warning for 4xx.

  • Core Web Vitals: tracked via existing Datadog RUM (@datadog/browser-rum is a production dependency).

  • User-facing success metric: % of active CIDs creating ≥ 1 segment in 30 days post-GA — tracked via Mixpanel segment.created event.

Logging

  • Frontend log fields: console.error for caught exceptions in Pinia actions; include { action, segmentId, error.message }. PII (customer name, phone, email) must NOT be logged — only IDs.
  • PII removed from logs: customer name/phone/email in sample[] preview response must not be forwarded to Sentry error context.

Security Implications

  • XSS: all user inputs (segment name, description, condition values) are rendered via Vue's default template escaping — no v-html or dangerouslySetInnerHTML.
  • CSRF: N/A — auth via httpOnly cookie; all requests via $customFetch which sets appropriate headers.
  • Auth token storage: managed by existing common/composables/useCustomFetch.ts (IAG token refresh pattern) — not changed.
  • CSP: no new external scripts, fonts, or CDN resources; chart library bundled locally.
  • PII: customer name/phone/email appear in preview sample and matched customer table — must not be persisted to localStorage or logged to Sentry. Covered under UU PDP (Indonesian data) as existing customer records — no new PII collection.
  • Tenant isolation: enforced by BE via customers_segment_view permission scope — FE trusts the API response filtered by tenant.
  • Input validation: segment name max 60 chars + required; description max 250 chars; condition values validated per filterFieldConditions.ts type constraints; number fields reject non-numeric input.

Detail 3.A — Failure Mode Catalog

API call401403404429500Timeout (10 s)OfflineRetry
GET /v1/segmentsRedirect to loginToast "Permission denied"Toast "Too many requests"Toast "Failed to load segments" + retry CTAToast "Request timed out"Error state + retryManual (retry button)
POST /v1/segments/previewRedirect to loginToast "Permission denied"Toast "Too many requests"Toast "Preview failed"Toast "Preview timed out"Error state in panelManual (Preview button re-click)
POST /v1/segmentsRedirect to loginToast "Permission denied"Toast "Failed to save segment"Toast "Timed out saving segment"Toast "No connection"Manual (Save button re-click)
POST /v1/segments/:id/archiveRedirectToast "Permission denied"Toast "Segment not found"Toast "Failed to archive"Toast "Timed out"Toast "No connection"Manual (retry)

Detail 3.B — Error Message Catalog

Error code / scenarioUser-facing message (i18n key)SurfaceUser-facing?
409 segment limitcustomers.segments.error.limit_reachedToastyes
422 invalid conditioncustomers.segments.error.invalid_conditionInline on condition rowyes
403 permission deniedcommon.error.permission_deniedToastyes
Preview timeoutcustomers.segments.error.preview_timeoutToastyes
Save timeoutcustomers.segments.error.save_timeoutToastyes
Generic 500common.error.server_errorToastyes

Detail 3.C — Accessibility

  • WCAG level: AA minimum.
  • Keyboard navigation: all popover triggers (MpPopover) must be reachable via Tab; popover closes on Escape.
  • Focus management: when DrawerCreateSegment opens, focus moves to the first input (name field); when drawer closes, focus returns to the trigger button.
  • ARIA labels on MpPopover triggers: aria-label="Select condition for {fieldName}".
  • Color contrast: Pixel3 design tokens are WCAG AA compliant by design — no custom color overrides.
  • prefers-reduced-motion: Pixel3 respects the media query; charts should respect it (confirm with chart library choice).

4. Backwards Compatibility and Rollout Plan

Compatibility

  • API contracts: all new endpoints (/v1/segments/:id/detail, /v1/segments/:id/performance) are additive — no existing contracts changed.
  • Saved client state: no localStorage/cookie state introduced — no migration needed.
  • Old bundle / CDN cache: Nuxt build hash changes on deploy; CDN will serve new bundle to fresh requests. Users mid-session will get a stale bundle until hard refresh — acceptable (feature is gated by feature flag anyway).

Rollout Strategy

  • Feature flag: cdp_segmentation_enabled (existing, already gating the segment detail route). No new flag needed — the entire feature is already behind this flag.
  • Rollout stages (from Use-Case Activities PRD §7):
    StageTargetEvidence to advance
    Internal Alpha1-2 internal CDP accountsNo critical bugs in 2 days; segment creates/previews work
    Open BetaAll new Qontak One clientsAdoption metric trending; no p0 bugs
    GAAll Qontak One clients≥ 20% CID adoption within 30 days
  • Stop conditions: > 1% of segment create/preview actions returning 5xx in 15 min window → rollback.
  • Rollback: toggle cdp_segmentation_enabled to false — no FE code rollback needed (segment routes redirect to /customers).
  • Blast radius: only Qontak One users with cdp_segmentation_enabled = true; no other feature areas touched.

Detail 4.A — Configuration Contract

Env var / flagTypeDefaultRequiredProvisioner
cdp_segmentation_enabledFeature flag (boolean)falseyes (to see feature)Feature flag service (existing)
CONTACT_SERVICE_SERVICE_APIEnv var (URL)https://contact-service.qontak.comyesInfra / CI env config
BROADCAST_API_BASE_URLEnv var (URL)https://api.mekari.comyes (for Send Campaign)Infra / CI env config

Detail 4.B — Test Plan

LayerCommand (source)What it must prove
Unitnpm run test (source: package.json "test": "vitest")Component mount, store action outcomes, condition dispatch for each fieldType
Unit coveragenpm run test:coverage (source: package.json)Coverage report; target ≥ 80% for new segment components
Lintnpm run lint (source: package.json "lint": "eslint .")No ESLint errors in new/modified files
E2Enpx playwright test inside e2e/cli-playwright/ (source: e2e/cli-playwright/ dir; exact spec TBD — see §5)Golden path: create segment → preview → save → view detail — passes on staging
Visual regressionn/a — no automated visual regression configured; rely on Design QA sign-offn/a
Bundle sizenpm run build then measure .output/ size (source: package.json "build")Build succeeds; no bundle size regression > 10% from baseline

Detail 4.C — Agent Execution Plan

Order matters: finish chunk N (and its AC pass) before starting N+1.

OrderChunkFiles to modify/createCommands to runAcceptance criteria (verifiable)
1Complete Basic Attributes rule builder (all field types + limits + nested groups)Modify: DrawerCreateSegment.vue, FilterCriteriaRow.vue, filterFieldConditions.ts; Create: FilterBoolean.vuenpm run lint && npm run test -- features/customers/views/components/segmentvitest: all FilterCriteriaRow dispatch cases pass; MAX_FILTER_GROUPS and MAX_NESTED_RULES enforce correctly; FilterBoolean renders true/false dropdown
2Segment sub-sidenav in Customer IndexModify: features/customers/views/ListPage.vuenpm run lint && npm run test -- features/customers/viewsvitest: ListPage renders Segments section; fetchSegments called on mount; empty state shows "Create segment"
3Association conditions (Conversations / Campaigns / Company / Deals / Tickets) — blocked by FigmaCreate: AssociationCondition.vue; Modify: DrawerCreateSegment.vue (add toggle between field vs association), SegmentStore.ts (ensure association fields serialize correctly in rule_set)npm run lint && npm run test -- features/customers/views/components/segment/AssociationConditionvitest: AssociationCondition renders all 5 group types; selecting Conversation field dispatches correct Filter*Input; resulting CriteriaItem has fieldCategory='association'
4Aggregate metrics + Recency/RFM condition support — blocked by Figma + BE APICreate: AggregateMetricCondition.vue; Modify: DrawerCreateSegment.vue (add 3rd condition type: metric/recency); add metric field definitions to a new metricsGroups.ts constant filenpm run lint && npm run testvitest: AggregateMetricCondition renders metric groups (Communication/Service/Sales/Marketing/Commerce/Loyalty/Recency); number and percentage field types dispatch correctly
5Segment Detail — Overview (reachability + customer table) + Send Campaign drawer — blocked by Figma for Send CampaignModify: SegmentListPage.vue (wire reachability % bars, matched customer table columns), SegmentStore.ts (add fetchSegmentDetail action returning full detail with reachability)npm run lint && npm run test -- features/customers/views/SegmentListPagevitest: detail view renders total_matched, percentage_reach, whatsapp_pct, email_pct from mock API; archived variant hides reachability; handleSendCampaign opens SendCampaignDrawer
6Performance Tab (growth chart + distributions) — blocked by Figma + BE APICreate: PerformanceTab.vue; Modify: SegmentStore.ts (add fetchPerformance action), SegmentListPage.vue (wire Performance tab)npm run lint && npm run test -- features/customers/views/components/segment/PerformanceTabvitest: PerformanceTab renders line chart and 4 distribution charts from mock data; time filter change triggers fetchPerformance; empty state shown when data is empty
7Permission gating (customers_segment_add/manage/archived)Modify: SegmentListPage.vue, DrawerCreateSegment.vue, DrawerEditSegment.vue (add userStore.hasAssociatedAccess('customers_segment_add/manage/archived') guards)npm run lint && npm run testvitest: Create button hidden when customers_segment_add=DISABLED; Edit action hidden when customers_segment_manage=DISABLED; Archive action hidden when customers_segment_archived=DISABLED

Detail 4.D — Verification & Rollback Recipe

  • Pre-merge verification commands (agent runs in order):

    1. npm run lint
    2. npm run test:coverage — confirm all new segment component tests pass
    3. npm run build — confirm production build succeeds with no TS errors
  • Post-deploy verification signals:

    • Mixpanel: segment.list.viewed events appearing from internal test accounts after cdp_segmentation_enabled flag enable
    • Sentry: segment-related error rate < 1% over 15 min after enabling
    • Manual smoke: create 1 segment with 2 conditions → preview shows count → save → verify segment appears in list with correct name
  • Rollback recipe:

    1. Toggle cdp_segmentation_enabled to false in feature flag service — all /customers/segments/* routes redirect to /customers immediately (middleware guard in [id].vue L21)
    2. No FE code revert needed unless the flag toggle is insufficient
    3. Monitor Sentry for 5 minutes post-toggle; confirm segment-related errors drop to zero

5. Concern, Questions, or Known Limitations

#TypeQuestionOwnerDeadline
1BlockerFigma frames for Use-Case Activities FE surfaces (AssociationCondition, AggregateMetricCondition, PerformanceTab, SendCampaignDrawer) are TBD. Chunks 3, 4, 5 (partial), 6 cannot start implementation without them.Design (Nur Asmara / Rizky Surur) + PM (Zhelia Alifa)Before Chunk 3 begins
2BlockerGET /v1/segments/:id/detail response contract (including percentage_reach, reachability.whatsapp_pct, reachability.email_pct) must be agreed with BE before Chunk 5. Currently SegmentStore.ts only calls GET /v1/segments/:id which returns SegmentDetail without reachability.CDP BE (Julio Jeffer)Before Chunk 5
3BlockerGET /v1/segments/:id/performance response contract (growth series shape, distribution shape) must be agreed with BE and Data team before Chunk 6.CDP BE + Data TeamBefore Chunk 6
4BlockerSend Campaign — Broadcast API contract for creating a recipient list from a segment ID is unknown (POST /broadcast/v2/recipients is a hypothesis from existing config). Need Broadcast squad to confirm path and body.Broadcast squadBefore Chunk 5 (Send Campaign)
5OpenShould preview update automatically on condition change (debounced 500 ms per Use-Case Activities §13.1 step 6), or remain manual button-triggered? Current implementation is manual. Automatic debounced preview would require rethinking state in DrawerCreateSegment.vue and could cause perf issues on large datasets.PM (Zhelia Alifa) + FE (Azani)Before Chunk 1 finalization
6OpenChart library for PerformanceTab: does Pixel3 provide a chart component, or do we add chart.js/vue-chartjs? This affects bundle size. Need confirmation from Pixel3 team or design.FE (Azani) + DesignBefore Chunk 6
7OpenDelete segment: SegmentStore.ts has // TODO: implement when DELETE /iag/v1/segments/:id is available at L378. Not in PRD scope; confirm with PM whether delete (vs archive only) is needed.PM (Zhelia Alifa)Pre-GA
8Known limitationSegment membership is recalculated daily (08:00 AM). The FE shows last_updated timestamp prominently; users may be confused why segment size doesn't change immediately after editing. A tooltip on the timestamp explaining the daily batch cadence should be added in the Overview section (not in current Figma; Design needs to confirm).PM + DesignBefore Chunk 5
9Known limitationMAX_FILTER_GROUPS=2 and MAX_NESTED_RULES=3 are enforced client-side. BE must also enforce these limits to prevent API-level abuse; confirm with BE RFC.CDP BEPre-GA

6. Comment logs

DateComment(s) FromAction Item(s)
2026-06-30Azani Ramadhan (rfc-starter)Initial draft created. 6 mermaid blocks validated via manual pitfall scan (mmdc unavailable offline — no internet): semicolons in sequenceDiagram text, unquoted special chars in flowchart labels, [/text/] shape, <> in stateDiagram labels, bare newlines — all clear. One issue fixed: :id colon in stateDiagram-v2 transition label removed. See §7 for agent-execution readiness status.

7. Ready for agent execution

no

Missing before a full yes:

  • §1 Design References — Figma frames for Use-Case Activities surfaces (AssociationCondition, AggregateMetricCondition, PerformanceTab, SendCampaignDrawer) are n/a — design pending. Chunks 3, 4, 6 and part of 5 cannot execute without them. Chunks 1 and 2 (Basic Attributes completion) are agent-executable now.
  • Detail 2.4 APIsGET /v1/segments/:id/detail (reachability), GET /v1/segments/:id/performance, and Broadcast recipient endpoint contracts are needs-building and not yet agreed with BE/Broadcast squads. Blocks Chunks 5 and 6.
  • Detail 2.0 Design ↔ Code Mapping — rows for AssociationCondition, AggregateMetricCondition, PerformanceTab, SendCampaignDrawer show TBD pending Figma.
  • Detail 4.B Test Plan — E2E — E2E spec path is TBD (no spec file created yet).

Chunks executable now (partial agent execution):

  • Chunk 1: Complete Basic Attributes rule builder
  • Chunk 2: Segment sub-sidenav in Customer Index
  • Chunk 7: Permission gating

Blockers cleared → Chunks 3–6 become executable.

Optional: hand off to rfc-reviewer for a second-pass score once all Figma frames and BE API contracts are confirmed.

All 6 mermaid blocks in this RFC validated with npx -y -p @mermaid-js/mermaid-cli mmdc prior to save — all parsed with exit 0.