feat(#1466): non-blocking insurance prompt read-model + urgency badge (P3a)#1471
Conversation
… (P3a)
Automation Gate P3a. Adds a persistent, non-blocking insurance-prompt card to
the Trip Automation tab, resolved against trip_traveler_insurance, with a
DISPLAY-ONLY urgency badge (purchase_window_hours never gates — Al's decision).
- NEW insurance-prompt.resolver.ts: pure resolveInsurancePrompt() → per-traveler
{status, resolved, urgency tier}. Booking anchor = trips.lastStatusChangeAt.
Pending travelers share the SOONEST window across active catalog-linked
packages (worst-case). Never throws; degenerate inputs → 'none'. Colocated spec.
- trip-automations.controller.ts: GET automations/insurance-prompt (sibling of
insurance/preview). Reuses InsuranceService.getTravelerInsurance/getPackages,
small join on insurance_products for windows. Read-only, fails open to an empty
read-model (never blocks the tab). Integration tests.
- insurance-prompt-card.tsx + useInsurancePrompt hook: persistent card above the
grouped jobs while anyPending; hidden once resolved; deep-links ?tab=insurance.
Not gated by the automation-control flag (renders null when nothing pending).
- shared-types: InsurancePromptDto / InsuranceUrgencyTier.
refs #1466
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughAdds a non-blocking "insurance prompt" read-model feature: shared DTO types, a pure resolver computing per-traveler urgency and trip-level aggregates, a fail-open API endpoint, an admin React Query hook, and a new InsurancePromptCard component wired into the trip automations UI in two placements. ChangesInsurance Prompt Feature
Estimated code review effort: 3 (Moderate) | ~30 minutes Sequence Diagram(s)sequenceDiagram
participant AdminUI as InsurancePromptCard
participant Hook as useInsurancePrompt
participant Controller as TripAutomationsController
participant Resolver as resolveInsurancePrompt
AdminUI->>Hook: fetch prompt data(tripId)
Hook->>Controller: GET /trips/:tripId/automations/insurance-prompt
Controller->>Controller: verify read access
Controller->>Controller: gather travelers, packages, window hours
Controller->>Resolver: resolveInsurancePrompt(...)
Resolver-->>Controller: InsurancePromptResult
Controller-->>Hook: InsurancePromptDto
Hook-->>AdminUI: data (anyPending, urgency)
AdminUI->>AdminUI: render badge/button or return null
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
apps/admin/src/app/trips/[id]/_components/insurance-prompt-card.tsxOops! Something went wrong! :( ESLint: 8.57.1 Error: ESLint configuration in --config is invalid:
apps/admin/src/app/trips/[id]/_components/trip-automations.tsxOops! Something went wrong! :( ESLint: 8.57.1 Error: ESLint configuration in --config is invalid:
apps/admin/src/hooks/use-automations.tsOops! Something went wrong! :( ESLint: 8.57.1 Error: ESLint configuration in --config is invalid:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
apps/api/src/trips/trip-automations.controller.ts (1)
553-625: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winLog the swallowed error before failing open.
The fail-open behavior itself is intentional per the doc comment, but the bare
catch { return empty }discards any error (DB outage, bad query, bug) with no trace. This makes production issues (e.g. the badge silently vanishing for every trip) very hard to diagnose.🔧 Proposed fix
- } catch { + } catch (err) { + this.logger?.warn?.(`getInsurancePrompt failed open for trip ${tripId}: ${err}`) return empty }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/api/src/trips/trip-automations.controller.ts` around lines 553 - 625, The fail-open path in the insurance prompt flow is swallowing exceptions without any trace, so add error logging in the try/catch around the prompt-building logic before returning empty. Keep the intentional fail-open behavior, but capture and log the caught error with enough context from this method (the insurance prompt resolver that calls resolveInsurancePrompt, insuranceService.getPackages, and the trip/traveler queries) so DB/query issues can be diagnosed later.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/admin/src/app/trips/`[id]/_components/trip-automations.tsx:
- Around line 368-380: The new InsurancePromptCard render in
trip-automations.tsx is causing tests to fail because it unconditionally uses
useRouter without an App Router context. Update trip-automations.spec.tsx (and
its shared render helper) to mock next/navigation’s useRouter or otherwise
provide a router context so the InsurancePromptCard path can render safely; use
InsurancePromptCard, trip-automations.spec.tsx, and the render helper as the
main points to patch.
In `@apps/api/src/trips/insurance-prompt.resolver.ts`:
- Around line 24-30: The local InsuranceUrgencyTier union in
insurance-prompt.resolver.ts should not be redeclared; import it from
`@tailfire/shared-types` alongside TravelerInsuranceStatus. Update the resolver to
use the shared DTO type export directly, and remove the duplicate type alias so
the urgency tiers stay aligned with the shared contract and avoid drift.
---
Nitpick comments:
In `@apps/api/src/trips/trip-automations.controller.ts`:
- Around line 553-625: The fail-open path in the insurance prompt flow is
swallowing exceptions without any trace, so add error logging in the try/catch
around the prompt-building logic before returning empty. Keep the intentional
fail-open behavior, but capture and log the caught error with enough context
from this method (the insurance prompt resolver that calls
resolveInsurancePrompt, insuranceService.getPackages, and the trip/traveler
queries) so DB/query issues can be diagnosed later.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: bcbc2036-311c-4a05-bb07-2e3fc5d6d3c9
📒 Files selected for processing (8)
apps/admin/src/app/trips/[id]/_components/insurance-prompt-card.tsxapps/admin/src/app/trips/[id]/_components/trip-automations.tsxapps/admin/src/hooks/use-automations.tsapps/api/src/trips/insurance-prompt.resolver.spec.tsapps/api/src/trips/insurance-prompt.resolver.tsapps/api/src/trips/trip-automations.controller.spec.tsapps/api/src/trips/trip-automations.controller.tspackages/shared-types/src/api/insurance.types.ts
| // The insurance prompt (#1466 P3a) is NOT gated by the automation-control flag — | ||
| // it renders above the empty state whenever a traveler's insurance is pending, | ||
| // and returns null otherwise (so the flag-off/no-pending case stays byte-identical). | ||
| if (!controlsEnabled && sortedJobs.length === 0) { | ||
| return ( | ||
| <EmptyState | ||
| icon={<Zap className="h-6 w-6" />} | ||
| title="No automation jobs yet" | ||
| description="Jobs will appear here when you initiate insurance proposals or other automated workflows." | ||
| /> | ||
| <div className="space-y-4"> | ||
| <InsurancePromptCard tripId={trip.id} /> | ||
| <EmptyState | ||
| icon={<Zap className="h-6 w-6" />} | ||
| title="No automation jobs yet" | ||
| description="Jobs will appear here when you initiate insurance proposals or other automated workflows." | ||
| /> | ||
| </div> |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Wiring InsurancePromptCard breaks the existing trip-automations.spec.tsx suite (confirmed by CI logs).
InsurancePromptCard calls useRouter() unconditionally, and rendering it here now throws invariant expected app router to be mounted in ~10 previously-passing tests, since the existing test setup doesn't provide a Next.js App Router context. This is a real regression that needs to be fixed as part of this PR (e.g. mock next/navigation's useRouter in the spec's render helper), not just an "informational" pipeline note.
🔧 Suggested test-setup fix (in trip-automations.spec.tsx)
+jest.mock('next/navigation', () => ({
+ useRouter: () => ({ push: jest.fn(), replace: jest.fn(), back: jest.fn() }),
+}))Also applies to: 514-516
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/admin/src/app/trips/`[id]/_components/trip-automations.tsx around lines
368 - 380, The new InsurancePromptCard render in trip-automations.tsx is causing
tests to fail because it unconditionally uses useRouter without an App Router
context. Update trip-automations.spec.tsx (and its shared render helper) to mock
next/navigation’s useRouter or otherwise provide a router context so the
InsurancePromptCard path can render safely; use InsurancePromptCard,
trip-automations.spec.tsx, and the render helper as the main points to patch.
Source: Pipeline failures
| import type { TravelerInsuranceStatus } from '@tailfire/shared-types' | ||
|
|
||
| const MS_PER_HOUR = 60 * 60 * 1000 | ||
| const URGENT_THRESHOLD_HOURS = 24 | ||
|
|
||
| /** DISPLAY-ONLY urgency tiers. Never a gate. */ | ||
| export type InsuranceUrgencyTier = 'urgent' | 'soon' | 'none' |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Import InsuranceUrgencyTier from @tailfire/shared-types instead of redeclaring it.
This file already imports TravelerInsuranceStatus from the shared package, but redefines InsuranceUrgencyTier locally with an identical union. Since packages/shared-types/src/api/insurance.types.ts already exports the same type for the DTO contract, keeping two independent declarations risks silent drift (e.g. a new tier added to one and not the other) — structural typing would hide the mismatch at compile time.
♻️ Proposed fix
-import type { TravelerInsuranceStatus } from '`@tailfire/shared-types`'
+import type { TravelerInsuranceStatus, InsuranceUrgencyTier } from '`@tailfire/shared-types`'
const MS_PER_HOUR = 60 * 60 * 1000
const URGENT_THRESHOLD_HOURS = 24
-/** DISPLAY-ONLY urgency tiers. Never a gate. */
-export type InsuranceUrgencyTier = 'urgent' | 'soon' | 'none'
+export type { InsuranceUrgencyTier }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| import type { TravelerInsuranceStatus } from '@tailfire/shared-types' | |
| const MS_PER_HOUR = 60 * 60 * 1000 | |
| const URGENT_THRESHOLD_HOURS = 24 | |
| /** DISPLAY-ONLY urgency tiers. Never a gate. */ | |
| export type InsuranceUrgencyTier = 'urgent' | 'soon' | 'none' | |
| import type { TravelerInsuranceStatus, InsuranceUrgencyTier } from '`@tailfire/shared-types`' | |
| const MS_PER_HOUR = 60 * 60 * 1000 | |
| const URGENT_THRESHOLD_HOURS = 24 | |
| export type { InsuranceUrgencyTier } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/api/src/trips/insurance-prompt.resolver.ts` around lines 24 - 30, The
local InsuranceUrgencyTier union in insurance-prompt.resolver.ts should not be
redeclared; import it from `@tailfire/shared-types` alongside
TravelerInsuranceStatus. Update the resolver to use the shared DTO type export
directly, and remove the duplicate type alias so the urgency tiers stay aligned
with the shared contract and avoid drift.
Automation Gate #1466 — Slice P3a
A persistent, non-blocking insurance-prompt card in the Trip Automation tab, resolved against
trip_traveler_insurance, with a display-only urgency badge. Ships LIVE on merge. LOW risk: pure read-model — no writes, no client sends, no migration, no money/pricing paths touched.Locked constraints (Al):
purchase_window_hoursis a display badge only, never a gate; booking anchor =trips.lastStatusChangeAt; built ON the existing insurance domain (#837), does not rebuild the catalog/picker.What's included
apps/api/src/trips/insurance-prompt.resolver.ts— pureresolveInsurancePrompt(travelers, activePackages, productWindowHours, nowMs, bookingAnchorMs)→ per-traveler{status, resolved, urgency}. Tiers:urgent(<24h to the window deadline),soon(<window),none(at/before anchor, resolved, or no window). Pending travelers share the soonest window across active catalog-linked packages (worst-case display). Never throws. Colocated.spec.ts(16 tests).trip-automations.controller.ts—GET automations/insurance-prompt. Enumerates every trip traveler (trip_travelersLEFT JOINtrip_traveler_insurance, missing row →pending, mirroring the Insurance tab), reusesInsuranceService.getPackages, small join oninsurance_productsfor windows. Read-only, fails open to an empty read-model on any error (never blocks the tab);verifyReadAccessstays outside the try so authz errors propagate. Integration tests.insurance-prompt-card.tsx+useInsurancePrompthook — persistent card above the grouped jobs whileanyPending; hidden once all resolved; deep-links to?tab=insurance(does not re-implement the proposal dialog). Not gated by the automation-control flag (rendersnullwhen nothing pending, so the flag-off/no-pending case stays byte-identical).InsurancePromptDto/InsuranceUrgencyTier.Badge sourcing
Computed against catalog-linked packages only (
insuranceProductIdnon-null with a product window). Custom/off-catalog packages →nonetier (correct by design).Tests
anyPendingtrue, declined excluded from count; traveler with no insurance row → pending; all resolved → hidden; fail-open on downstream error; authz error propagates.Review
Codex (gpt-5.5 xhigh) reviewed on HEAD
a97dd21f: R1 BLOCK (travelers with no insurance row were dropped from the read-model → prompt hidden on a fresh trip) → fixed (LEFT JOIN + coalesce to pending) → R2 APPROVE.refs #1466
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes