Files
halobestie-clone/TECH_DEBT.md
Ramadhan Sjamsani 495eb98787 fix(db): widen customer_transactions.type to VARCHAR(128)
TransactionType.FIRST_SESSION_DISCOUNT ('first_session_discount', 22 chars) overflowed the VARCHAR(20) column, throwing in acceptPairingRequest AFTER the session was flipped to ACTIVE but before startSessionTimer/startSessionListener/PAIRED-notify ran. Every first-session-discount pairing thus half-completed: lost transaction row, no server-side timer, and a 500 to the mitra so its app never opened the chat. Widen the column (CREATE TABLE + idempotent ALTER). Deferred hardening (bookkeeping INSERT in the critical path) logged in TECH_DEBT.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 22:27:07 +08:00

6.0 KiB

Tech Debt

Running list of known shortcuts, deferred hardening, and "good enough for now" decisions that need follow-up before they bite us in production.

Format: [date] short title, then enough context for someone (or future-you) to act on it without re-deriving the discussion.


Backend

[2026-06-01] Bookkeeping INSERT sits in the pairing critical path

File: backend/src/services/pairing.service.js (acceptPairingRequest, ~line 506)

What happened: the INSERT INTO customer_transactions runs after the session is flipped to ACTIVE but before startSessionTimer, startSessionListener, the customer PAIRED WS notify, and the other-mitra dismiss fan-out. A varchar(20) overflow on type = 'first_session_discount' (22 chars) threw there, so every first-session-discount pairing half-completed: no transaction row, no server-side timer, no PAIRED push (customer recovered via polling), and a 500 returned to the mitra so its app never opened the chat.

Fixed now: column widened to VARCHAR(128) (migrate.js), so the INSERT no longer throws.

Why it's still debt: a bookkeeping write can still abort critical pairing steps if it ever fails again (constraint change, DB hiccup, future longer enum). Hardening: either move the customer_transactions INSERT to the end of acceptPairingRequest, or wrap it in a try/catch that logs-but-doesn't-throw, so transaction recording can never again half-complete a pairing. Same applies to the equivalent INSERT in extension.service.js.


[2026-05-11] Public GET /api/public/bestie/available needs rate limiting before prod

File: backend/src/routes/public/public.bestie-availability.routes.js

Decision: The endpoint was made unauthenticated by business requirement — SHome1st renders before any JWT exists, and the CTA must reflect global mitra availability so users see whether bestie is online before committing to onboarding. Response is intentionally a single boolean (no count, no IDs).

Why it's debt: No auth + no rate limit. The 10s in-memory cache bounds DB load, but a single attacker can still hammer the endpoint to:

  • run sustained traffic against the public listener (DoS surface)
  • scrape available over time to infer mitra online/offline patterns (weak information leak — only "is anyone online", but still a signal)

Mitigation before prod:

  • Per-IP rate limit (suggested: ~30 req/min/IP, headroom over the legitimate 5s client poll cadence = 12 req/min/IP).
  • Implement via @fastify/rate-limit plugin so other public endpoints can share the policy as we add them under /api/public/*.
  • Verify Cloud Run / NLB preserves real client IP and that request.ip reflects it (Fastify already has trustProxy: true).

Not required: auth, captcha, or removing the count from /api/client/mitra-availability (that route stays authed for CC/debug).


Client app

[2026-05-11] Social-login (Google / Apple) has no entry point after S3a rewrite

Files: client_app/lib/features/auth/screens/register_screen.dart (no longer renders them); client_app/lib/core/auth/auth_providers_provider.dart (still wired).

Decision: RegisterScreen was rewritten to match Figma S3Phone 1:1 (step-dots + name greeting + privacy card + tanpa-verif ghost link). Figma S3a shows no Google/Apple buttons, so they were removed from this screen.

Why it's debt: Google/Apple buttons used to render here when the authProvidersProvider flags were enabled. Today both flags are false (creds pending — see Phase 3.4 Status memory), so nothing visible is missing. But the moment /api/shared/auth-providers flips either flag, the buttons have nowhere to live.

Fix-when-creds-arrive:

  • Decide where Google/Apple buttons belong (likely a dedicated login screen reachable from the SHome1st "masuk →" banner), or whether to bring them back to S3a as Figma-friendly tiles above the phone input.
  • loginGoogle / loginApple on authProvider are still intact, so the wiring is one button widget away.

[2026-05-12] Stage 10 — Bestie Offline Popup variant not wired on BestieHistoryList

File: client_app/lib/features/home/screens/bestie_history_list_screen.dart

Decision: Stage 10 follow-up restored BestieHistoryList as a separate picker screen (per mermaid §4) and made offline rows un-tappable (dimmed). Mermaid §4 actually calls for a Bestie Offline Popup (returning variant) to surface when the user picks an offline bestie — with options "cari bestie lain" and "tanya admin".

Why it's debt: Today the offline row is just disabled. The user gets no explicit prompt to redirect them into the blast flow or to contact admin.

Fix: wire BestieOfflinePopup with variant='returning' on offline-row tap. The popup widget already exists from Stage 8 (Tanya Admin sheet ships with the wiring); just needs to be triggered here.

[2026-05-12] S5 ESP screen retired from spec — code still ships it

Files: client_app/lib/features/onboarding/ (S5ESP screen + nav wiring); screens/onboarding.jsx::S5ESP (Figma reference still in handoff); any espSelectionProvider / espSkippedProvider Riverpod state.

Decision: Business removed the ESP multi-select step from the customer flow on 2026-05-12. Both verified and anonymous branches now go from VerifChoiceSheet straight to the usp_seen? gate. See requirement/flow_customer.mermaid.md §2.

Why it's debt: Stage 2 (commit 2645bcd) shipped the ESP screen and its state providers. The screen is still reachable in the current build. The mermaid spec is the source of truth — the code has drifted behind by one business decision.

Fix:

  • Delete the ESP screen widget and its route registration.
  • Remove espSelectionProvider / espSkippedProvider and any nav step that routes through ESP.
  • Wire VerifChoiceSheet → USPGate → (USP screen | skip → next) directly.
  • Drop the "ESP is decorative only" memory (it's now superseded by removal).
  • Keep screens/onboarding.jsx::S5ESP in the Figma handoff folder — it's history, not active design.