Files
halobestie-clone/requirement/phase3.3-plan.md
ramadhan sjamsani 780cade3db Phase 3.3: topic sensitivity + Phase 3.4: auth foundation
Phase 3.3 — Session Topic Sensitivity (complete):
- Backend: topic_sensitivity column + session_sensitivity_log, sensitivity service
  (flip with one-way-latch + audit), PATCH /api/shared/chat/sessions/:id/topic,
  topic carried in pairing + extension WS payloads, CC filter + sensitive stats
  + per-mitra sensitive columns on activity page
- client_app: TopicSelectionBottomSheet before pricing, topic flows through
  pairing request, silent WS handler for session_topic_updated
- mitra_app: SensitivityBadge + SensitivityTheme + sensitivityConfigProvider,
  overlay badge + yellow accent, chat screen app-bar toggle with configurable
  confirmation + latch, extension card shows current flag, history + transcript
  yellow theme
- control_center: Sensitivitas Topik settings section, topic filter + column
  with inline audit log, sensitive stats dashboard card, mitra activity
  sensitive columns with QC flag

Phase 3.4 — Self-Managed Auth (foundation only):
- Migration: auth_sessions + otp_requests tables, social identity columns on
  customers, password_hash + lockout on control_center_users, OTP + CC lockout
  app_config keys
- New services: password (bcrypt + complexity), token (JWT HS256 + refresh
  rotation, session_id claim pre-wires future Valkey revocation),
  social-identity (Google + Apple JWKS), OTP (Fazpass stub — real API TBD)
- Constants: AuthProvider + OtpChannel
- Middleware, auth route rewrites, WS auth update, Firebase → FCM isolation
  still pending (next chunk); Fazpass docs + Apple Developer setup still
  required before E2E testing

Docs:
- requirement/phase3.3.md, phase3.3-plan.md, phase3.3-testing.md
- requirement/phase3.4.md, phase3.4-plan.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 10:15:12 +08:00

514 lines
26 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Phase 3.3 Implementation Plan: Session Topic Sensitivity
## Summary of Clarified Requirements
| Topic | Decision |
|---|---|
| Customer selection UX | Required bottom sheet after "Mulai Curhat", before pricing; two buttons (not toggle) |
| Customer flag mutability | Locked for session duration — customer cannot change |
| Mitra flag mutability | Can flip mid-session via app-bar toggle |
| Flip confirmation dialog | Default `true`, disableable via `app_config` key `sensitive_flip_confirmation_enabled` |
| One-way latch | Default `false`, enableable via `app_config` key `sensitive_flag_one_way_latch` |
| Visual cue for mitra | Label "Topik sensitif" + warning-yellow doodle background |
| Customer visual cue | None — customer UI stays pink always |
| Storage | `chat_sessions.topic_sensitivity` enum + mirrored on `chat_requests`-like flow (actual rows live in `chat_sessions` before acceptance; see Section 1.1) |
| Audit trail | New `session_sensitivity_log` table — logs every mitra flip |
| WS event to customer | Sent but silently ignored client-side (future-proofing) |
| Extension | Shows current flag; flag carries over unchanged on acceptance |
| Control center | Sensitive stats panel + filter on sessions page + 2 new config keys |
| Pricing | Unchanged — schema leaves room for future differentiation |
| Push notification content | Stays generic — label only surfaces in overlay |
| Multi-device mitra sync | Out of scope |
| Auto-moderation | Out of scope |
---
## Data Model Note
After reviewing the codebase, `chat_requests` is NOT a separate table — pairing requests live as rows in `chat_sessions` with `status` transitions (`SEARCHING``PENDING_ACCEPTANCE``ACTIVE` → ...). Mitra notifications are tracked in `chat_request_notifications`. Therefore, **one column on `chat_sessions` is sufficient** — no mirroring needed.
---
## Work Stream 1: Backend — Schema, Services, Routes
### 1.1 DB Migration
**File:** [backend/src/db/migrate.js](backend/src/db/migrate.js)
```sql
-- New column on chat_sessions
ALTER TABLE chat_sessions
ADD COLUMN IF NOT EXISTS topic_sensitivity VARCHAR(16) NOT NULL DEFAULT 'regular';
-- Optional index if CC filter needs it
CREATE INDEX IF NOT EXISTS idx_chat_sessions_topic_sensitivity
ON chat_sessions (topic_sensitivity);
-- New audit table
CREATE TABLE IF NOT EXISTS session_sensitivity_log (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
session_id UUID NOT NULL REFERENCES chat_sessions(id) ON DELETE CASCADE,
changed_by_mitra_id UUID NOT NULL REFERENCES mitras(id),
from_value VARCHAR(16) NOT NULL,
to_value VARCHAR(16) NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_session_sensitivity_log_session
ON session_sensitivity_log (session_id);
```
**Seed new `app_config` keys** (in the same migration or the seed script):
```sql
INSERT INTO app_config (key, value) VALUES
('sensitive_flip_confirmation_enabled', 'true'::jsonb),
('sensitive_flag_one_way_latch', 'false'::jsonb)
ON CONFLICT (key) DO NOTHING;
```
### 1.2 Constants
**File:** [backend/src/constants.js](backend/src/constants.js)
```js
const TopicSensitivity = Object.freeze({
REGULAR: 'regular',
SENSITIVE: 'sensitive',
});
const WsMessage = Object.freeze({
// ... existing
SESSION_TOPIC_UPDATED: 'session_topic_updated',
});
```
### 1.3 Config Service
**File:** [backend/src/services/config.service.js](backend/src/services/config.service.js)
Add:
- `getSensitiveFlipConfirmationEnabled()` → bool (default `true`)
- `getSensitiveFlagOneWayLatch()` → bool (default `false`)
- `setSensitiveConfig({ flipConfirmationEnabled, oneWayLatch })` — used by control center
### 1.4 Pairing Service — Accept Topic Flag From Customer
**File:** [backend/src/services/pairing.service.js](backend/src/services/pairing.service.js)
- `createPairingRequest({ customerId, durationMinutes, isFreeTrial, topicSensitivity })` — new param, validated against `TopicSensitivity` enum; defaults to `regular` if omitted.
- Insert `topic_sensitivity` column when creating the `chat_sessions` row.
- When broadcasting the `chat_request` WS message to candidate mitras, include `topic_sensitivity` in the payload (alongside `duration_minutes`, `is_free_trial`).
### 1.5 New Service: Sensitivity Service
**New file:** `backend/src/services/sensitivity.service.js`
Exports:
- `flipSessionSensitivity({ sessionId, mitraId, toValue })`
- Load session; verify ownership (mitra_id matches).
- Verify session status is `ACTIVE` or `EXTENDING` (not flippable if `CLOSING`, `COMPLETED`, etc.).
- Read `sensitive_flag_one_way_latch` from config. If `true` and current value is `sensitive`, reject with 409.
- No-op if `fromValue === toValue`.
- Transaction: UPDATE `chat_sessions.topic_sensitivity`, INSERT `session_sensitivity_log` row.
- Broadcast WS message `session_topic_updated` to both participants: `{ session_id, topic_sensitivity, changed_at }`.
- Return updated session.
- `getSessionSensitivityLog(sessionId)` — used by control center session detail (future).
### 1.6 Extension Service — Expose Current Flag
**File:** [backend/src/services/extension.service.js](backend/src/services/extension.service.js)
- `requestExtension` does not change the flag; flag carries over unchanged.
- When broadcasting the extension request WS message to the mitra, include `topic_sensitivity` from the session row.
### 1.7 New Routes
**File:** [backend/src/routes/public/shared.chat.routes.js](backend/src/routes/public/shared.chat.routes.js) (or the existing shared chat routes file — confirm name at implementation)
| Method | Path | Purpose | Auth |
|---|---|---|---|
| `PATCH` | `/api/shared/chat/sessions/:sessionId/topic` | Mitra flips flag | Firebase (mitra) |
**Body:** `{ topic_sensitivity: 'regular' \| 'sensitive' }`
**Response:** `{ session_id, topic_sensitivity, changed_at }`
### 1.8 Client Chat Request Route — Accept Topic Param
**File:** [backend/src/routes/public/client.chat.routes.js](backend/src/routes/public/client.chat.routes.js) — `POST /api/client/chat/request`
Extend request body schema:
```js
{
duration_minutes: Joi/ajv,
is_free_trial: bool,
topic_sensitivity: enum('regular', 'sensitive') // required
}
```
Pass through to `createPairingRequest`.
### 1.9 Internal Routes — Config + Stats
**File:** [backend/src/routes/internal/config.routes.js](backend/src/routes/internal/config.routes.js)
Add:
- `GET /internal/config/sensitivity``{ flip_confirmation_enabled, one_way_latch }`
- `PATCH /internal/config/sensitivity` → accept same shape
**File:** [backend/src/routes/internal/session.routes.js](backend/src/routes/internal/session.routes.js)
- Extend session list query to accept `topic_sensitivity` filter param (`all` / `regular` / `sensitive`).
- Extend session detail response to include `topic_sensitivity` and recent `session_sensitivity_log` entries.
**File:** [backend/src/routes/internal/dashboard.routes.js](backend/src/routes/internal/dashboard.routes.js) (or wherever dashboard stats live)
- Add `sensitive_session_stats` to the dashboard payload: `{ total_sessions, sensitive_count, sensitive_percent }` over the last N days (match existing stat windows).
### 1.10 WebSocket — New Message Type
**File:** [backend/src/plugins/websocket.js](backend/src/plugins/websocket.js)
No plugin change needed if broadcast is done via existing `sendToSessionParticipant` helper — just a new `WsMessage.SESSION_TOPIC_UPDATED` constant.
---
## Work Stream 2: client_app — Topic Selection
### 2.1 New Widget: Topic Selection Bottom Sheet
**New file:** `client_app/lib/features/chat/widgets/topic_selection_bottom_sheet.dart`
A non-dismissible `DraggableScrollableSheet` (or `showModalBottomSheet` with `isDismissible: false` and `enableDrag: false`) containing:
- Title, body, sub-question (copy as in PRD Section 1)
- Two equal-width buttons: "Topik umum" (primary) + "Topik sensitif" (secondary style, equal height)
- Helper line below buttons
- System back / back button cancels and returns to home
**Return:** `Future<TopicSensitivity?>` — null if cancelled, otherwise the selected enum.
### 2.2 Topic Sensitivity Enum
**New file:** `client_app/lib/core/constants/topic_sensitivity.dart`
```dart
enum TopicSensitivity {
regular('regular'),
sensitive('sensitive');
final String value;
const TopicSensitivity(this.value);
static TopicSensitivity fromString(String v) =>
values.firstWhere((e) => e.value == v, orElse: () => TopicSensitivity.regular);
}
```
### 2.3 Home Screen — Wire Bottom Sheet Into Flow
**File:** [client_app/lib/features/home/home_screen.dart](client_app/lib/features/home/home_screen.dart)
Current "Mulai Curhat" → opens pricing sheet directly.
New flow:
1. Tap "Mulai Curhat"
2. Show `TopicSelectionBottomSheet` → await result
3. If result is null → no-op
4. If result is non-null → open existing `PricingBottomSheet`, passing selected topic as param
### 2.4 Pricing Bottom Sheet — Carry Topic
**File:** [client_app/lib/features/chat/widgets/pricing_bottom_sheet.dart](client_app/lib/features/chat/widgets/pricing_bottom_sheet.dart)
- Accept `TopicSensitivity topicSensitivity` constructor param.
- When firing the pairing request (via `PairingNotifier`), pass through `topic_sensitivity` to the request body.
### 2.5 Pairing Notifier — Pass Topic to Backend
**File:** [client_app/lib/core/pairing/pairing_notifier.dart](client_app/lib/core/pairing/pairing_notifier.dart)
- Extend `startPairing({ int durationMinutes, bool isFreeTrial, required TopicSensitivity topicSensitivity })` to include `topic_sensitivity` in the API call body.
### 2.6 No Customer UI Changes Beyond Selection
- Chat screen remains pink doodle — **no conditional coloring**.
- WS listener for `session_topic_updated` is wired but **silently no-ops** (so the server-side event doesn't cause errors).
**File:** [client_app/lib/core/chat/chat_notifier.dart](client_app/lib/core/chat/chat_notifier.dart) (or wherever WS messages are dispatched)
Add a switch case for `session_topic_updated` that logs and returns — no state update.
---
## Work Stream 3: mitra_app — Flag Display + Mid-Session Flip
### 3.1 Shared Enum + Widget: Sensitivity Badge
**New file:** `mitra_app/lib/core/constants/topic_sensitivity.dart` — same shape as client_app version.
**New file:** `mitra_app/lib/core/chat/widgets/sensitivity_badge.dart`
A small pill widget rendering "Topik sensitif" on yellow background. Configurable size. Reused across overlay, chat header, extension card, history list.
### 3.2 Chat Request Overlay — Label + Color
**File:** [mitra_app/lib/core/chat/widgets/chat_request_overlay.dart](mitra_app/lib/core/chat/widgets/chat_request_overlay.dart)
- Extend `ChatRequestIncomingData` (in [mitra_app/lib/core/chat/chat_request_notifier.dart](mitra_app/lib/core/chat/chat_request_notifier.dart)) with `TopicSensitivity topicSensitivity` field.
- Parse from WS `chat_request` message payload (`topic_sensitivity`).
- When sensitive: show yellow accent (e.g., left-border or top-strip) + `SensitivityBadge` near session metadata.
### 3.3 Chat Request Notifier — Parse Topic
**File:** [mitra_app/lib/core/chat/chat_request_notifier.dart](mitra_app/lib/core/chat/chat_request_notifier.dart)
Extend `ChatRequestIncomingData` constructor + WS handler to populate `topicSensitivity`.
### 3.4 Mitra Chat Notifier — Track + Flip Topic
**File:** [mitra_app/lib/core/chat/mitra_chat_notifier.dart](mitra_app/lib/core/chat/mitra_chat_notifier.dart)
- Add `TopicSensitivity topicSensitivity` to the chat state (populated from session fetch on connect).
- Handle incoming WS `session_topic_updated` → update state (authoritative from backend — handles the case where mitra flipped on another device in the future).
- New method `flipTopic()`:
- Read `sensitive_flip_confirmation_enabled` from app_config provider (fetched on app start or via dedicated provider)
- Show confirmation dialog if enabled
- Call `PATCH /api/shared/chat/sessions/:sessionId/topic` via existing `ApiClient`
- Show toast on success; show error dialog on failure (e.g., one-way latch violation → "Sesi sudah ditandai sensitif dan tidak bisa diubah kembali.")
### 3.5 Mitra Chat Screen — Yellow Doodle + Toggle
**File:** [mitra_app/lib/features/chat/screens/mitra_chat_screen.dart](mitra_app/lib/features/chat/screens/mitra_chat_screen.dart)
- Conditional background: watch `topicSensitivity` from `mitraChatNotifier`; swap doodle asset (pink ↔ yellow).
- Header banner: persistent small strip showing "Topik sensitif" label when sensitive.
- App-bar toggle icon (e.g., `Icons.flag` / `Icons.flag_outlined`) wired to `flipTopic()`.
- If `one_way_latch` is enabled and current is `sensitive`, disable the icon and show tooltip "Sesi sudah terkunci sensitif."
### 3.6 Doodle Assets
**New files:** `mitra_app/assets/doodle_yellow.png` (or SVG) — warning-yellow version of existing pink doodle pattern.
- Exact yellow hex: proposed `#FFC107` (Material amber) or `#F7B500` — confirm with designer at implementation. Choose whichever contrasts best with white text/dark badges.
- Update `mitra_app/pubspec.yaml` assets section.
### 3.7 Extension UI — Current Flag
**File:** [mitra_app/lib/features/chat/widgets/extension_request_card.dart](mitra_app/lib/features/chat/widgets/extension_request_card.dart) (or wherever the extension request UI lives)
- Read `topicSensitivity` from the extension WS payload / session state.
- If sensitive: yellow accent + `SensitivityBadge` on the extension card.
### 3.8 Chat History List + Transcript
**File:** [mitra_app/lib/features/chat/screens/chat_history_screen.dart](mitra_app/lib/features/chat/screens/chat_history_screen.dart) (confirm name)
- List row: add `SensitivityBadge` when session is sensitive.
- Transcript detail: yellow doodle background for sensitive sessions (pink otherwise).
### 3.9 Mitra API Client
**File:** [mitra_app/lib/core/network/api_client.dart](mitra_app/lib/core/network/api_client.dart) (confirm name)
Add:
```dart
Future<Session> flipSessionTopic(String sessionId, TopicSensitivity to);
```
---
## Work Stream 4: control_center — Config + Stats + Filter
### 4.1 Settings Page — New Sensitivity Section
**File:** [control_center/src/pages/settings/SettingsPage.jsx](control_center/src/pages/settings/SettingsPage.jsx)
Add new section "Sensitivitas Topik":
- Checkbox: "Aktifkan dialog konfirmasi saat Mitra menandai topik sensitif" (`sensitive_flip_confirmation_enabled`)
- Checkbox: "Kunci searah — Mitra tidak bisa membatalkan tanda topik sensitif" (`sensitive_flag_one_way_latch`)
Wire to new endpoints: `GET/PATCH /internal/config/sensitivity`.
### 4.2 Sessions Page — Filter
**File:** [control_center/src/pages/sessions/SessionsPage.jsx](control_center/src/pages/sessions/SessionsPage.jsx)
- Add dropdown filter: "Semua / Umum / Sensitif".
- Pass `topic_sensitivity` query param to session list endpoint.
- Add column "Topik" with badge (green "Umum" / yellow "Sensitif").
### 4.3 Session Detail — Show Audit Trail
**File:** `control_center/src/pages/sessions/SessionDetailPage.jsx` (confirm name)
- Show current `topic_sensitivity`.
- Show compact timeline of `session_sensitivity_log` entries: "Mitra X menandai topik sebagai sensitif pada HH:MM".
### 4.4 Dashboard — Sensitive Stats Panel
**File:** [control_center/src/pages/dashboard/DashboardPage.jsx](control_center/src/pages/dashboard/DashboardPage.jsx)
- New card: "Sesi Sensitif" with count + percentage over selected date range.
### 4.5 Mitra Activity — Per-Mitra Sensitive Breakdown
**Backend file:** [backend/src/services/mitra-activity.service.js](backend/src/services/mitra-activity.service.js)
Extend `getMitraActivitySummary` SQL to join `chat_request_notifications``chat_sessions` on `session_id` and group per-mitra, returning additional columns:
- `sensitive_total` — count of notifications where joined session had `topic_sensitivity = 'sensitive'`
- `sensitive_accepted` — count of those with `response = 'accepted'`
- `sensitive_rejected` — count with `response = 'rejected'`
- `sensitive_acceptance_rate``sensitive_accepted / sensitive_total` (null if `sensitive_total = 0`)
This is a pure join — no new column needed on `chat_request_notifications` because the flag is derivable from the parent session row.
**Backend file:** [backend/src/routes/internal/mitra-activity.routes.js](backend/src/routes/internal/mitra-activity.routes.js)
`GET /internal/mitra-activity/summary` response includes the new fields above.
**Control center file:** [control_center/src/pages/mitra-activity/MitraActivityPage.jsx](control_center/src/pages/mitra-activity/MitraActivityPage.jsx)
Extend summary table with additional columns (may need a sub-grouping to keep it readable):
| Mitra | Total | Accepted | Rejected | Missed | Ignored | Rate (%) | Avg Response (s) | **Sensitive Total** | **Sensitive Accepted** | **Sensitive Rate (%)** |
Optionally: color the "Sensitive Rate" cell red when significantly lower than the overall rate — useful for spotting mitras who accept regular requests but consistently skip sensitive ones (potential QC signal).
Optional detail view: on the detail log table, add a "Topik" column with the session's sensitivity badge.
---
## 5. Implementation Order
| Step | What | Apps Affected | Dependencies |
|---|---|---|---|
| **Work Stream 1: Backend** | | | |
| 1 | DB migration: `topic_sensitivity` column, `session_sensitivity_log` table, 2 new `app_config` keys | Backend | None |
| 2 | Constants: `TopicSensitivity`, `WsMessage.SESSION_TOPIC_UPDATED` | Backend | None |
| 3 | Config service: getters + setter for sensitivity flags | Backend | Step 1 |
| 4 | Pairing service: accept + persist + broadcast `topic_sensitivity` | Backend | Steps 12 |
| 5 | New sensitivity service: `flipSessionSensitivity`, `getSessionSensitivityLog` | Backend | Steps 14 |
| 6 | Extension service: include `topic_sensitivity` in WS payload | Backend | Step 4 |
| 7 | New route: `PATCH /api/shared/chat/sessions/:sessionId/topic` | Backend | Step 5 |
| 8 | Extend `POST /api/client/chat/request` schema | Backend | Step 4 |
| 9 | Internal routes: `/internal/config/sensitivity`, filter on sessions, dashboard sensitive stats | Backend | Step 3, 5 |
| **Work Stream 2: client_app** | | | |
| 10 | `TopicSensitivity` enum | client_app | None |
| 11 | `TopicSelectionBottomSheet` widget | client_app | Step 10 |
| 12 | Home screen: new flow (topic sheet → pricing) | client_app | Step 11 |
| 13 | Pricing sheet + PairingNotifier: accept + forward flag | client_app | Steps 10, 12 |
| 14 | ChatNotifier: silently handle `session_topic_updated` WS | client_app | None |
| **Work Stream 3: mitra_app** | | | |
| 15 | `TopicSensitivity` enum + `SensitivityBadge` widget + yellow doodle asset | mitra_app | None |
| 16 | ChatRequestNotifier + overlay: parse topic, show badge + yellow accent | mitra_app | Step 15 |
| 17 | MitraChatNotifier: track topic, handle `session_topic_updated`, `flipTopic()` | mitra_app | Step 15, backend step 7 |
| 18 | Mitra chat screen: yellow doodle + header + app-bar toggle + config-driven dialog/latch | mitra_app | Step 17 |
| 19 | Extension card: show current flag | mitra_app | Step 15, backend step 6 |
| 20 | History list + transcript: badge + yellow doodle | mitra_app | Step 15 |
| **Work Stream 4: control_center** | | | |
| 21 | Settings page: sensitivity config section | control_center | Backend step 9 |
| 22 | Sessions list: filter + column | control_center | Backend step 9 |
| 23 | Session detail: audit trail timeline | control_center | Backend step 9 |
| 24 | Dashboard: sensitive stats card | control_center | Backend step 9 |
| 25 | Mitra Activity: extend summary SQL + API + table with sensitive columns | Backend + control_center | Backend step 1 |
| **Testing** | | | |
| 26 | E2E: customer selects sensitive → mitra sees label + yellow → mitra flips → audit + stats reflect | All | All above |
---
## 6. New Files
| File | Purpose |
|---|---|
| `backend/src/services/sensitivity.service.js` | Flip + audit + one-way-latch enforcement |
| `client_app/lib/core/constants/topic_sensitivity.dart` | Enum |
| `client_app/lib/features/chat/widgets/topic_selection_bottom_sheet.dart` | Customer topic-selection UI |
| `mitra_app/lib/core/constants/topic_sensitivity.dart` | Enum |
| `mitra_app/lib/core/chat/widgets/sensitivity_badge.dart` | Reusable badge |
| `mitra_app/assets/doodle_yellow.png` | Yellow doodle for sensitive sessions |
## 7. Modified Files (Primary)
| File | Change |
|---|---|
| `backend/src/db/migrate.js` | Migration + seed |
| `backend/src/constants.js` | `TopicSensitivity` + `WsMessage.SESSION_TOPIC_UPDATED` |
| `backend/src/services/config.service.js` | Getters/setter |
| `backend/src/services/pairing.service.js` | Accept + persist + broadcast topic |
| `backend/src/services/extension.service.js` | Include topic in WS payload |
| `backend/src/routes/public/client.chat.routes.js` | Request body schema |
| `backend/src/routes/public/shared.chat.routes.js` | New PATCH topic route |
| `backend/src/routes/internal/config.routes.js` | GET/PATCH sensitivity config |
| `backend/src/routes/internal/session.routes.js` | Filter + audit in detail |
| `backend/src/routes/internal/dashboard.routes.js` | Sensitive stats |
| `client_app/lib/features/home/home_screen.dart` | New pre-pricing step |
| `client_app/lib/features/chat/widgets/pricing_bottom_sheet.dart` | Accept + forward topic |
| `client_app/lib/core/pairing/pairing_notifier.dart` | API body |
| `client_app/lib/core/chat/chat_notifier.dart` | No-op WS handler |
| `mitra_app/lib/core/chat/chat_request_notifier.dart` | Parse topic |
| `mitra_app/lib/core/chat/widgets/chat_request_overlay.dart` | Badge + yellow accent |
| `mitra_app/lib/core/chat/mitra_chat_notifier.dart` | State + flip method + WS handler |
| `mitra_app/lib/features/chat/screens/mitra_chat_screen.dart` | Yellow doodle + app-bar toggle |
| `mitra_app/lib/features/chat/widgets/extension_request_card.dart` | Badge + accent |
| `mitra_app/lib/features/chat/screens/chat_history_screen.dart` | Badge on row + yellow transcript |
| `mitra_app/pubspec.yaml` | Register new asset |
| `control_center/src/pages/settings/SettingsPage.jsx` | Sensitivity config section |
| `control_center/src/pages/sessions/SessionsPage.jsx` | Filter + column |
| `control_center/src/pages/dashboard/DashboardPage.jsx` | Sensitive stats card |
---
## 8. Risks & Mitigations
| Risk | Mitigation |
|---|---|
| Existing sessions have no `topic_sensitivity` value after migration | Column has `DEFAULT 'regular'` + NOT NULL — backfill automatic |
| Mitra flips topic but WS delivery fails to customer | Customer silently ignores anyway; backend state is source of truth, no user-facing consequence |
| Race: mitra flips to `sensitive`, then to `regular` while latch config toggles mid-session | `flipSessionSensitivity` reads latch config at flip-time; flip is atomic in a DB transaction |
| Customer cancels topic-selection sheet → home screen flashes | Confirm sheet result is `null` and return cleanly to home — no pairing request created |
| Yellow color clashes with existing pink UI elements | Restrict yellow to chat screen background + extension card; test contrast with message bubbles and badges |
| Mitra flips flag after session is `CLOSING` | Backend rejects with 409; mitra UI gracefully shows toast "Sesi sudah berakhir" |
| Old sessions in history have no audit log entries | No migration needed — history just shows current value with no log entries |
---
## 9. Testing Checklist
**Customer flow (client_app):**
- [ ] Tap "Mulai Curhat" → topic sheet appears, cannot dismiss by tap-outside
- [ ] System back cancels entire flow
- [ ] Topik umum → pricing sheet shows, pairing request sent with `topic_sensitivity: regular`
- [ ] Topik sensitif → pricing sheet shows, pairing request sent with `topic_sensitivity: sensitive`
- [ ] Chat screen stays pink regardless of flag
- [ ] Customer receives `session_topic_updated` WS message after mitra flip → no UI change, no error
**Mitra flow (mitra_app):**
- [ ] Sensitive incoming request → yellow accent + "Topik sensitif" badge on overlay
- [ ] Regular incoming request → no badge, no color change
- [ ] Sensitive active session → yellow doodle background + header label
- [ ] Flip toggle (confirmation enabled): dialog shows, Batal cancels, Tandai flips
- [ ] Flip toggle (confirmation disabled via CC): flips instantly with toast
- [ ] One-way latch enabled + session already sensitive: toggle disabled with tooltip
- [ ] One-way latch enabled + session regular: can flip to sensitive, then toggle locks
- [ ] Mitra requests extension on sensitive session → extension card shows badge + yellow
- [ ] Mitra requests extension after mid-session flip to sensitive → extension card reflects current flag
- [ ] History list: sensitive sessions show badge
- [ ] Transcript: sensitive sessions show yellow doodle
**Control center:**
- [ ] Settings page: toggle `sensitive_flip_confirmation_enabled` → mitra app behavior changes on next flip
- [ ] Settings page: toggle `sensitive_flag_one_way_latch` → mitra app behavior changes
- [ ] Sessions page: filter "Sensitif" only shows sensitive sessions
- [ ] Session detail: audit trail shows all flips with mitra name + timestamp
- [ ] Dashboard: sensitive session count + % matches manual query
- [ ] Mitra Activity: per-mitra sensitive total / accepted / rate columns populate correctly
- [ ] Mitra with 0 sensitive requests shows `—` (not 0%) for sensitive rate
**Backend:**
- [ ] `flipSessionSensitivity` logs every call in `session_sensitivity_log`
- [ ] Ownership check: mitra A cannot flip mitra B's session
- [ ] Status check: flip on `CLOSING`/`COMPLETED` session → 409
- [ ] Latch violation returns 409 with clear error code
- [ ] No-op flip (same value) does not create log entry