feat(frontend,email): Archera Insurance CTA in modals + education pages + email mentions (closes #314) by cristim · Pull Request #336 · LeanerCloud/cloud-commitments-cli · GitHub
Skip to content

feat(frontend,email): Archera Insurance CTA in modals + education pages + email mentions (closes #314) - #336

Merged
cristim merged 8 commits into
feat/multicloud-web-frontendfrom
feat/issue-314-archera-modal-cta
May 13, 2026
Merged

cristim merged 8 commits into
feat/multicloud-web-frontendfrom
feat/issue-314-archera-modal-cta

Conversation

@cristim

@cristim cristim commented May 11, 2026 •

Copy link
Copy Markdown
Member

Summary

Fourth and final iteration of issue #314. Frontend + email-template scope, no IaC, no API/handler changes.

What this PR ships

Modal CTAs (soft, non-blocking, both link to the Archera education overlay):

  • Inside openPurchaseModal (frontend/src/recommendations.ts):

    Insure this commitment with Archera →

  • Inside openCreatePlanModal + openNewPlanModal (frontend/src/plans.ts):

    Insure this plan with Archera →

Both rendered as small secondary text-links — not buttons competing with the primary action. TODO(@cristim): final copy markers next to each.

Education pages (full-viewport overlay, hash-friendly):

  • Page A — "What is Archera Insurance?" opens with a "Why is CUDly telling me about this?" lead section that surfaces two transparency disclosures BEFORE getting into product detail:

    1. CUDly is fully functional regardless of whether you enroll in Archera Insurance. Nothing in CUDly is gated.
    2. Archera sponsors CUDly's development with a fraction of their insurance revenue. The honest reason CUDly surfaces this option.

    Then: plain-language explainer of commitment-overuse insurance, when it makes sense, and the 7-day post-purchase enrollment window.

  • Page B — "How the CUDly ↔ Archera integration works" carries a shorter "Disclosure" subheading near the top mirroring both facts in one sentence each, then the step-by-step integration flow (signup → Archera's own pre-filled IaC for AWS/GCP / OAuth consent for Azure → cost-data ingestion).

Both pages cross-link and carry https://archera.ai/signup?mode=cudly (target=_blank, rel="noopener noreferrer").

Email surfaces (new in this iteration):

  • internal/email/templates.go → purchaseApprovalRequestTemplate (plain text) + purchaseApprovalRequestHTMLTemplate (HTML): brief Archera Insurance mention near the bottom noting the buyer will have 7 days from each purchase to enroll. Links to the education page.
  • internal/email/templates.go → purchaseConfirmationTemplate: more prominent placement (7-day clock now active) with direct signup link.
  • template_renderers.go extended to populate ArcheraEducationURL on the relevant data structs.

Skipped (per #314's body): purchaseFailedTemplate, scheduledPurchaseTemplate, RI exchange templates.

Regression-guard tests:

  • Both education pages assert presence of both disclosure facts (no-gating + sponsorship-revenue) — so future copy edits can't silently remove them.
  • Email render tests assert "Archera" + "7 days" + signup-link string in both approval and confirmation templates.

Why the previous directions were abandoned

PR Direction Why closed
#310 Bundle Archera IAM trust policy into CUDly's federation IaC (a) External ID is derived from customer's Archera org ID which only exists post-signup; (b) Azure integration is OAuth consent, not RBAC role; (c) Archera ships pre-filled IaC themselves — duplicating creates drift surface
#315 Shared Terraform module + Settings → Federation Setup checkbox Same three problems + Settings is the wrong conversion moment
(cancelled) Settings checkbox + signup link Settings is the wrong UX moment — high-intent surfaces are at purchase time
#336 (this) Modal CTA + education pages + email mentions Right surfaces, no IAM duplication, transparency disclosures land where users will see them

Acceptance criteria

  • Purchase modal renders the simplified CTA; click opens Page A.
  • Plan-creation modal (both openCreatePlanModal and openNewPlanModal paths) renders the simplified CTA; same target.
  • Page A leads with the transparency disclosures (no-gating + sponsorship) before product detail.
  • Page B includes a "Disclosure" subheading mirroring both facts.
  • Both pages cross-link + carry signup link.
  • purchaseApprovalRequestTemplate + HTML variant mention Archera + 7-day window + education-page link.
  • purchaseConfirmationTemplate mentions Archera + 7-day window + direct signup link.
  • Frontend tests for CTAs + page navigation + signup-link attributes.
  • Regression-guard tests asserting both disclosure facts appear on both pages.
  • Email render tests asserting Archera mention + 7-day window + signup link.
  • Accessibility: CTAs are <button> elements with discernible names; overlay has role=region + aria-label; back button keyboard-reachable; heading hierarchy (h1 → h2) within overlay.
  • TODO(@cristim): final copy / TODO(@cristim): final disclosure copy markers left wherever wording is provisional.

Not in scope

  • Settings → Federation Setup checkbox (dropped from earlier iterations).
  • API / handler changes — none.
  • Bundle template changes — none (internal/iacfiles/templates/, iac/federation/, iac/modules/, terraform/environments/<cloud>/archera.tf, scripts/generate-federation-iac.go, scripts/check-archera-parity.sh — untouched).
  • Authentication / payment integration — none (signup happens on Archera's side).

TODO(@cristim) markers left for review

Location What needs review
Page A "Why is CUDly telling me about this?" Final disclosure copy (no-gating + sponsorship wording)
Page A intro paragraph Plain-language explainer wording
Page A "How it works" section Revenue model description
Page B "Disclosure" subheading Shorter disclosure wording
Page B step 1 Confirm exact onboarding URL/flow with Archera
Page B step 2 Confirm "Terraform/CloudFormation" for AWS and GCP
Page B "Disclaimers" Disclaimer language with legal/Archera
Both modal CTAs "Insure this commitment with Archera →" / "Insure this plan with Archera →" wording
Both email templates Final Archera section copy + 7-day window wording

Closes / Supersedes

Summary by CodeRabbit

  • New Features
    • Archera education overlay and post-purchase/plan offer modal with deeplink support, signup CTA, and improved keyboard/focus behavior
  • Style
    • Added styling for Archera overlay, modal, CTA, disclosure and responsive layout
  • Email
    • Purchase-related emails now conditionally include Archera enrollment info and signup links (7-day enrollment wording)
  • Tests
    • New UI tests for overlay/modal flows and email template tests verifying Archera content inclusion

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 11, 2026 •

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6bb33ef3-4056-42c3-8406-f0260155058f

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR consolidates Archera Insurance education into a single merged overlay page, introduces a post-purchase/plan offer modal, refactors integration to open modals on successful transactions, and extends backend email templates to conditionally include Archera Insurance enrollment messaging with optional education links.

Changes

Frontend: Archera Modal UI

Layer / File(s) Summary
Archera module types and exports
frontend/src/archera.ts
ArcheraContext type, signup and deeplink constants, and module-scoped focus/cleanup state exported for overlay/modal flows.
Offer modal lifecycle
frontend/src/archera.ts
openArcheraOfferModal(context) renders the offer dialog with backdrop, disclosure, inline <details> "Learn more" embedding education body, ESC/backdrop/button close handlers, idempotent reopen teardown, and Tab focus trap; closeArcheraOfferModal() clears modal and restores focus.
Education overlay and deeplink entry
frontend/src/archera.ts
openArcheraPage() renders a unified education overlay (When it makes sense, How it works steps, premium note, full disclosure) into #archera-page-container and focuses back control; closeArcheraPage() hides/clears and restores focus; handleArcheraDeeplink() matches configured Archera paths.
App and plan modal integration
frontend/src/app.ts, frontend/src/plans.ts
App init calls deeplink handler; purchase success paths open the offer modal; plans module opens offer modal after new-plan creation and guards modal openers with explicit checks.
HTML overlay container
frontend/src/index.html
Adds hidden #archera-page-container for the education overlay.
CSS for overlay and offer modal
frontend/src/styles/modals.css
Adds styles for inline Archera CTA, full-viewport education overlay, signup block, and the offer modal including the expandable learn-more section.
Frontend tests for modal and overlay flows
frontend/src/__tests__/archera.test.ts
Jest tests add DOM setup/teardown, validate overlay and disclosure content, exercise modal open/close behaviors, details expansion, reopen idempotency, Escape listener cleanup, and keyboard focus wrapping.

Backend: Email Payload and Templates

Layer / File(s) Summary
Email payload structure
internal/email/sender.go, internal/api/handler_purchases.go, internal/purchase/execution.go
Adds NotificationData.ArcheraEducationURL and computes/populates it from a normalized dashboard base using a new helper in purchase and approval email flows.
Email templates with conditional Archera content
internal/email/templates.go
Purchase confirmation and purchase-approval (plain-text and HTML) templates now conditionally render an "Archera Insurance" enrollment block with fixed signup link and a permalink to .ArcheraEducationURL.
Backend template tests
internal/email/template_renderers_test.go
New Go tests verify inclusion and omission of Archera blocks and correct enrollment-window phrasing and URLs when ArcheraEducationURL is set or empty.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

  • LeanerCloud/CUDly#314: Implements Archera Insurance education and offer flows including UI overlays, modal integration, deeplink handling, and email template enhancements as specified in the issue.

Possibly related PRs

  • LeanerCloud/CUDly#294: Modifies post-approval UX in frontend/src/app.ts; code-adjacent to the modal-opening changes in this PR.

Poem

🐰 A rabbit's tale of modals so neat,
With education pages merged complete!
Offers bloom when purchases succeed,
Deep-links guide to what users need.
From frontend clicks to backend mail,
Archera's story sings its trail.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title clearly summarizes the main changes: adding Archera Insurance CTAs in modals, education pages, and email mentions across the frontend and email systems.
Docstring Coverage ✅ Passed Docstring coverage is 80.77% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/issue-314-archera-modal-cta

Comment @coderabbitai help to get the list of available commands and usage tips.

@cristim cristim added priority/p2 Backlog-worthy severity/medium Moderate harm urgency/this-quarter Within the quarter impact/many Affects most users effort/m Days type/feat New capability triaged Item has been triaged labels May 11, 2026
@cristim

cristim commented May 11, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 11, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 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 `@frontend/src/__tests__/archera.test.ts`:
- Around line 87-166: The tests append fixed-ID DOM nodes via
buildArcheraContainer, buildPurchaseModalDOM, and buildPlanModalDOM but never
remove them, causing stale-node collisions between tests; add a teardown to
clear those nodes between tests (e.g. an afterEach that removes elements with
IDs 'archera-page-container', 'purchase-modal', 'plan-modal' or simply resets
document.body.innerHTML = ''), ensuring each test starts with a clean DOM and
preventing selector bleed from previous runs.

In `@frontend/src/archera.ts`:
- Around line 70-100: When opening the overlay in openArcheraPage, capture and
save the currently focused element (e.g., const previouslyFocused =
document.activeElement as HTMLElement | null) onto the container (or a
module-scoped variable) before moving focus to the back button; then in
closeArcheraPage, after hiding/clearing the container, restore focus to that
saved element if it still exists in the document (and is focusable) and then
clear the saved reference. Update functions openArcheraPage and closeArcheraPage
to use the same storage location for the saved element and guard restoration
with a type check and document.contains to avoid errors.

In `@frontend/src/styles/modals.css`:
- Around line 238-244: The Archera overlay (`#archera-page-container`) currently
has z-index: 900 which is lower than modal layers (.modal / .modal-overlay at
1000); update `#archera-page-container` to use a z-index higher than the modal
layers (e.g., 1100) so the education page renders on top of open modals, keeping
position: fixed and overflow-y: auto intact.
🪄 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: efb411d6-fb2d-48a2-8297-1f385bdccb54

📥 Commits

Reviewing files that changed from the base of the PR and between c95a6ac and 5d3b205.

📒 Files selected for processing (6)
  • frontend/src/__tests__/archera.test.ts
  • frontend/src/archera.ts
  • frontend/src/index.html
  • frontend/src/plans.ts
  • frontend/src/recommendations.ts
  • frontend/src/styles/modals.css

Comment thread frontend/src/__tests__/archera.test.ts
Comment thread frontend/src/archera.ts Outdated
Comment thread frontend/src/styles/modals.css
@cristim cristim changed the title feat(frontend): Archera Insurance CTA in Purchase + Plan-creation modals + education pages (closes #314) feat(frontend,email): Archera Insurance CTA in modals + education pages + email mentions (closes #314) May 11, 2026
@cristim

cristim commented May 11, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 11, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/api/handler_purchases.go (1)

1266-1277: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Guard ArcheraEducationURL assignment when dashboard base is empty.

At Line 1277, dashboardBase + "/archera-insurance" is set unconditionally. If dashboardBase == "", emails still render the Archera block with a relative URL, which is usually invalid in email clients. Only set this field when dashboardBase is non-empty so templates can omit the block as intended.

💡 Suggested patch
   dashboardBase := h.resolveDashboardURL(req)
   data := email.NotificationData{
     DashboardURL:        dashboardBase,
     ApprovalToken:       execution.ApprovalToken,
     ExecutionID:         execution.ExecutionID,
     TotalSavings:        totalSavings,
     TotalUpfrontCost:    totalUpfront,
     Recommendations:     summaries,
     RecipientEmail:      to,
     CCEmails:            cc,
     AuthorizedApprovers: approvers,
-    ArcheraEducationURL: dashboardBase + "/archera-insurance",
   }
+  if dashboardBase != "" {
+    data.ArcheraEducationURL = dashboardBase + "/archera-insurance"
+  }
🤖 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 `@internal/api/handler_purchases.go` around lines 1266 - 1277, The
ArcheraEducationURL field on the email.NotificationData is unconditionally set
to dashboardBase + "/archera-insurance", which yields a relative URL when
dashboardBase is empty; update the construction of email.NotificationData so
ArcheraEducationURL is only populated when dashboardBase is non-empty (e.g.,
check the resolved dashboardBase from h.resolveDashboardURL(req) and set
ArcheraEducationURL to the concatenated string only if dashboardBase != ""
otherwise leave it empty or omit), locating this change around the
NotificationData literal that includes DashboardURL, ApprovalToken, ExecutionID,
etc., and ensure any template logic relies on an empty value to skip rendering
the Archera block.
🤖 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 `@frontend/src/archera.ts`:
- Around line 93-96: The code currently overwrites _previouslyFocused on every
openArcheraPage(...) call; change openArcheraPage so it only captures
document.activeElement into _previouslyFocused when the overlay is transitioning
from hidden to visible (i.e., when there is no current _previouslyFocused or
when the overlay is not currently visible), rather than on every call. In
practice update openArcheraPage to check the overlay visibility/state (or
whether _previouslyFocused is null) before assigning _previouslyFocused =
document.activeElement instanceof HTMLElement ? document.activeElement : null,
and leave closeArcheraPage unchanged so it can restore the original opener
focus.

---

Outside diff comments:
In `@internal/api/handler_purchases.go`:
- Around line 1266-1277: The ArcheraEducationURL field on the
email.NotificationData is unconditionally set to dashboardBase +
"/archera-insurance", which yields a relative URL when dashboardBase is empty;
update the construction of email.NotificationData so ArcheraEducationURL is only
populated when dashboardBase is non-empty (e.g., check the resolved
dashboardBase from h.resolveDashboardURL(req) and set ArcheraEducationURL to the
concatenated string only if dashboardBase != "" otherwise leave it empty or
omit), locating this change around the NotificationData literal that includes
DashboardURL, ApprovalToken, ExecutionID, etc., and ensure any template logic
relies on an empty value to skip rendering the Archera block.
🪄 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: 7bd02675-c0a6-4aee-9d44-016f2cab97f0

📥 Commits

Reviewing files that changed from the base of the PR and between 5d3b205 and 0de11a5.

📒 Files selected for processing (10)
  • frontend/src/__tests__/archera.test.ts
  • frontend/src/app.ts
  • frontend/src/archera.ts
  • frontend/src/plans.ts
  • frontend/src/styles/modals.css
  • internal/api/handler_purchases.go
  • internal/email/sender.go
  • internal/email/template_renderers_test.go
  • internal/email/templates.go
  • internal/purchase/execution.go
✅ Files skipped from review due to trivial changes (1)
  • internal/purchase/execution.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • frontend/src/plans.ts
  • frontend/src/styles/modals.css

Comment thread frontend/src/archera.ts Outdated
cristim added a commit that referenced this pull request May 11, 2026
PR #333 (closes #332) landed a botched merge of `unset_falls_through`
in TestNewSenderFromEnvironment_EmailEnabled — two overlapping copies
of the same sub-test ended up concatenated (one missing its closing
braces, one preceded by stray fragments), plus duplicate
`ctx :=` / `sender, err :=` lines. The result didn't parse:

  gofmt -l internal/email/factory_test.go
  internal/email/factory_test.go:309:2: missing ',' before newline ...
  internal/email/factory_test.go:312:6: expected '(', found TestNewSenderWithConfig_AWS

This trips `gofmt` and `go vet` in the pre-commit workflow on every
open PR against `feat/multicloud-web-frontend` (e.g. #326, #335, #336).

Keep the `prev/hadPrev` version of the sub-test (the one that actually
does `os.Unsetenv` first, which is the case the test name describes),
drop the orphaned `orig/hadOrig` fragment, and remove the duplicate
ctx/sender declarations.

Verified locally: gofmt clean, `go vet ./internal/email/...` clean,
`go test ./internal/email/...` 306 tests pass.
cristim added 3 commits May 11, 2026 23:07
…cation pages (closes #314)

Introduces the Archera Insurance CTA at the two high-intent moments
where a user is about to commit money, plus two education pages that
explain what Archera Insurance is and how the integration works.

**New module — frontend/src/archera.ts**
- `renderArcheraCTA()`: returns a small, muted <p> with an inline
  <button> that reads "Worried about committing? Archera Insurance
  underwrites commitment-overuse — learn how it works →". Placed
  below the approval note in the Purchase modal, and before the
  Save/Cancel row in Plan-creation modals.
- `openArcheraPage('what-is-archera' | 'how-it-works')`: renders a
  full-viewport overlay panel using DOM methods only (no innerHTML).
  Doesn't touch SPA routing state or the URL.
- `closeArcheraPage()`: hides + clears the overlay.
- `ARCHERA_SIGNUP_URL = 'https://archera.ai/signup?mode=cudly'` for
  attribution-tagged referral links.

**Page A — "What is Archera Insurance?"**
Plain-language explainer: commitment-overuse insurance, how Archera
earns (sharing-of-savings), when it makes sense, what CUDly's role is.
Cross-links to Page B. Signup link at bottom (target=_blank,
rel="noopener noreferrer").

**Page B — "How the CUDly ↔ Archera integration works"**
Step-by-step: sign up → Archera generates pre-filled IaC for AWS/GCP
(or OAuth consent for Azure) → Archera ingests cost data → purchase
commitments normally through CUDly. Disclaimers: Archera is
third-party, no credentials stored in CUDly. Cross-links to Page A.
Signup link at bottom.

TODO(@cristim) markers flag all copy that needs final review.

**Modal integration**
- `recommendations.ts / openPurchaseModal`: appends `renderArcheraCTA()`
  inside `#purchase-details`, below the approval note.
- `plans.ts / openCreatePlanModal + openNewPlanModal`: calls
  `injectPlanModalCTA()` (guarded against duplication with an id
  check) to insert the CTA before `.modal-buttons` in `#plan-form`.

**CSS (styles/modals.css)**: `.archera-cta`, `.archera-cta-link`,
`#archera-page-container`, `.archera-page-inner`, `.archera-page-back`,
`.archera-signup-block`, `.archera-signup-btn`, `.archera-signup-note`.

**HTML (index.html)**: adds `<div id="archera-page-container"
role="region" aria-label="Archera Insurance information">` (hidden
by default) before the modal stack.

**Tests (archera.test.ts)**: 26 assertions covering renderArcheraCTA
structure, page A/B content + crosslinks + signup link attributes,
closeArcheraPage, and CTA injection in both Purchase and Plan modals.

Nothing in backend/, cmd/, internal/, iac/, terraform/, or scripts/ is
touched. This is a frontend-only change. Supersedes PR #310 and PR #315
(closed without merging; earlier iterations bundled IAM/IaC which was
rejected by Archera-side review — see issue #314 for history).
Frontend (CR fixes):
- archera.ts: save/restore focus around overlay open/close (CR: focus
  restoration on closeArcheraPage)
- modals.css: raise #archera-page-container z-index from 900 to 1100
  so overlay sits above .modal layers (CR: z-index below modals)
- archera.test.ts: add global afterEach DOM teardown to prevent stale-
  node collisions between test suites (CR: missing teardown)

Frontend (scope additions from coordinator):
- archera.ts: update renderArcheraCTA copy to "Insure this commitment/
  plan with Archera →"; add transparency disclosure section (Page A:
  "Why is CUDly telling me about this?", Page B: "Disclosure") carrying
  two non-negotiable facts: CUDly fully functional without Archera +
  Archera sponsors CUDly; add handleArcheraDeeplink() for deep-link
  routing from email; export ARCHERA_PAGE_A/B_PATH constants
- app.ts: wire handleArcheraDeeplink() into init() after
  handlePurchaseDeeplink()
- plans.ts: pass 'plan' context to renderArcheraCTA in injectPlanModalCTA
- archera.test.ts: update CTA copy assertions; add disclosure tests (6);
  add handleArcheraDeeplink tests (3); total: 41 tests, all passing

Backend (email templates — scope addition):
- sender.go: add ArcheraEducationURL field to NotificationData; empty
  value silently omits the Archera block for backward compat
- templates.go: add conditional Archera 7-day enrollment block to
  purchaseApprovalRequestTemplate (plain-text), purchaseApprovalRequest-
  HTMLTemplate (HTML), and purchaseConfirmationTemplate
- handler_purchases.go, execution.go: populate ArcheraEducationURL as
  DashboardURL + "/archera-insurance" matching ARCHERA_PAGE_A_PATH
- template_renderers_test.go: add 4 Go tests asserting Archera content
  in all three modified templates + absence test when URL is empty
…aseApprovalEmail cyclomatic complexity

The if dashboardBase != "" guard added in the previous commit pushed
sendPurchaseApprovalEmail from complexity 10 to 11, exceeding the
gocyclo pre-commit threshold. Extracted the guard into a package-private
archeraEducationURL helper; sendPurchaseApprovalEmail calls it inline
without a branch, restoring complexity to 10.
@cristim
cristim force-pushed the feat/issue-314-archera-modal-cta branch from d6e5130 to 6c2b5c7 Compare May 11, 2026 21:07
@cristim

cristim commented May 11, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

PR was rebased onto base branch to pick up #337 (factory_test.go fix). Archera changes in this branch:

  • frontend modal CTAs (Insure this commitment with Archera → / Insure this plan with Archera →)
  • education pages with transparency disclosures (CUDly works without Archera + sponsorship fact)
  • regression-guard tests for both disclosures on both pages
  • email template Archera mentions with 7-day window (purchaseApprovalRequestTemplate + HTML + purchaseConfirmationTemplate)
  • archeraEducationURL helper in internal/api/handler_purchases.go to keep sendPurchaseApprovalEmail cyclomatic complexity ≤ 10
  • Archera overlay z-index raised to 1100 to render above modals

Previous round's findings should all be resolved.

@coderabbitai

coderabbitai Bot commented May 11, 2026

Copy link
Copy Markdown
Contributor

@cristim: Understood! Triggering a fresh review of PR #336 now, taking into account the rebase onto the base branch (picking up the #337 factory_test.go fix) and all the Archera changes you've listed:

  • Frontend modal CTAs for purchase and plan flows
  • Education overlay pages with transparency disclosures
  • Regression-guard tests for both disclosures on both pages
  • Email template Archera mentions with the 7-day enrollment window
  • archeraEducationURL helper to keep cyclomatic complexity ≤ 10
  • Archera overlay z-index raised to 1100

I'll check whether the previous round's findings are fully addressed.

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@cristim

cristim commented May 11, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 11, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

cristim added 2 commits May 12, 2026 17:49
Restructures the Archera Insurance flow into a post-action offer modal,
collapses the previously split education pages into one, and tightens
the copy.

Frontend changes:
- Replace the inline "Insure this {commitment,plan} with Archera →" CTA
  inside the Purchase and Plan-creation modals with a new post-action
  offer modal (openArcheraOfferModal). The modal pops after a successful
  Send-for-Approval submission (app.ts handleExecutePurchase +
  handleFanOutExecute) or plan creation (plans.ts savePlan). The user
  sees title + lead + brief disclosure, then a collapsed "Learn more"
  drop-down, then two equal-weight actions: "No thanks" and "Sign up at
  Archera →" (opens archera.ai in a new tab).
- Merge the previously separate "What is Archera Insurance?" (Page A)
  and "How the integration works" (Page B) overlays into a single
  education page (buildArcheraPage). Both ARCHERA_PAGE_A_PATH and
  ARCHERA_PAGE_B_PATH still deep-link to the merged page (PAGE_B kept
  for backward compatibility with already-delivered email links).
- "Learn more" is a native <details>/<summary> that expands the full
  education content inline within the offer modal. Summary text on the
  left, chevron on the right (rotates on open). Body inherits .archera-
  page-inner typography for visual parity with the standalone page.
- Education content tightened: dedupe the lead-paragraph + "Why is CUDly
  telling me about this?" overlap, drop the four-bullet Disclaimers
  section in favour of a single closing "Full disclosure:" paragraph
  that absorbs the legal/scope points. Drop the "All Upfront" qualifier
  from "When it makes sense" and reframe the second use case from
  "finance team requires a safety net" to user-perspective "you want to
  be covered in case your usage drops".
- "How it works" intro now explicitly states the 7-day enrollment
  window from each purchase.
- Premium note clarifies the insurance fee is paid to Archera and the
  cloud commitment bought through CUDly is unaffected (same price,
  same billing) — replaces the earlier sharing-of-savings framing.
- Add aria-haspopup="dialog" + aria-expanded toggling on the (now-
  removed) Columns popover trigger to expose dialog state to assistive
  tech. (No-op for the removed inline CTAs; this change is the modal's
  a11y surface.)
- Drop the inline scripts' em-dashes from JSDoc + user-facing strings.

Tests:
- archera.test.ts rewritten: drops renderArcheraCTA + page A/B + plan-
  modal-CTA + purchase-modal-CTA describe blocks (those surfaces are
  gone). Adds openArcheraOfferModal describes covering visibility,
  context-dependent headlines, signup link attributes, No-thanks +
  backdrop + ESC close, Learn-more collapsible expand, re-open replace
  semantics. handleArcheraDeeplink test now verifies both legacy and
  current URL paths open the same merged page. 1605 frontend tests
  pass.
Replaces the 4-line "Learn more: <URL>" Archera block in the purchase-
confirmation, purchase-approval-request (plain text), and approval-
request (HTML) emails with the full education content inlined into
the message body — so recipients don't need to click through to the
dashboard to read what the option is, when it makes sense, and how
it works.

Structure mirrors the in-app offer modal's expanded "Learn more"
content:
  - Header + 7-day enrollment window note
  - One-paragraph lead (covers the gap + non-gating)
  - "When it makes sense" (2 use cases)
  - "How it works" with the 3-step onboarding flow
  - Premium clarification (insurance fee is separate from the cloud
    commitment; same price, same billing)
  - Full-disclosure paragraph (sponsorship + scope + Archera-owns-terms)
  - Sign up CTA + permalink

Always-expanded (no <details>/<summary>): Outlook desktop, Outlook.com,
and mobile Outlook strip those, so the safest is a flat inline body.
Apple Mail, iOS Mail, and Gmail web render the styled HTML naturally;
all other clients fall back to the plain-text version.

The original {{.ArcheraEducationURL}} is kept as a "Permalink:" at the
bottom of each block — preserves the existing tests' URL assertions
and gives a fallback for forwarded emails, bookmarks, or clients that
mangle the inlined HTML. 310 Go tests still pass.
@cristim

cristim commented May 12, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 12, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 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 `@frontend/src/archera.ts`:
- Around line 75-89: openArcheraOfferModal currently adds a document keydown
listener each time and overwrites _offerModalCleanup, leaking old handlers when
reopened; fix by ensuring any existing listener is removed before adding a new
one: if _offerModalCleanup exists call it (or remove the previously attached
named handler) before attaching the new document.addEventListener in
openArcheraOfferModal, and update _offerModalCleanup to remove that specific
listener and null itself when the modal is closed; reference the
openArcheraOfferModal function and the shared _offerModalCleanup
variable/handler so reopening always cleans up the previous listener.
- Around line 96-103: The dialog created in archera.ts (the panel element)
currently sets initial focus but doesn't trap Tab/Shift+Tab, letting keyboard
users tab into background controls; implement a focus trap on the panel by
collecting its focusable descendants (querySelectorAll for a11y focusables), add
a keydown listener on panel that intercepts Tab: if Tab without Shift and
activeElement is last focusable, move focus to the first; if Shift+Tab and
activeElement is first, move focus to the last; ensure the listener is attached
where panel is created (the variable panel) and removed when the dialog is
closed, and keep the existing initial-focus code (line ~193) so keyboard focus
is contained while aria-modal="true".
🪄 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: 15012eb3-43c2-4799-b2e2-ada31fd6c364

📥 Commits

Reviewing files that changed from the base of the PR and between 6c2b5c7 and 96c422d.

📒 Files selected for processing (7)
  • frontend/src/__tests__/archera.test.ts
  • frontend/src/app.ts
  • frontend/src/archera.ts
  • frontend/src/index.html
  • frontend/src/plans.ts
  • frontend/src/styles/modals.css
  • internal/email/templates.go

Comment thread frontend/src/archera.ts
Comment thread frontend/src/archera.ts
…alog

Address two CR findings on PR #336:

1. **Stale keydown listener leak**: openArcheraOfferModal added a
   document-level keydown handler (ESC closes) and stored its cleanup in
   `_offerModalCleanup` on every call, but didn't first invoke the prior
   cleanup. A re-open without an explicit close in between leaked the
   previous handler. Fix: call `_offerModalCleanup()` (if set) at the top
   of openArcheraOfferModal before attaching the new handler. Idempotent
   — no-op when no prior open is in flight.

2. **No focus trap on the dialog panel**: the panel declares
   `role="dialog" aria-modal="true"` but Tab / Shift+Tab let keyboard
   users escape into the background app. That's an a11y regression for
   an "aria-modal=true" element. Fix: attach a panel-level keydown
   handler that intercepts Tab — Tab on last focusable wraps to first,
   Shift+Tab on first wraps to last. Focusables are re-queried on each
   Tab press (not cached at modal-build time) so the Tab traversal
   picks up the additional focusables that the <details> drop-down
   expands when opened. Trap handler is torn down alongside the ESC
   handler by the merged `_offerModalCleanup` closure.

Tests in archera.test.ts:
- `re-opening tears down the previous open's ESC listener (no leak)`
  spies on document.addEventListener / removeEventListener and asserts
  the second open both adds AND removes one keydown handler.
- `traps Tab focus inside the panel (Tab from last → first)`
  focuses the last focusable, dispatches Tab, asserts focus wraps to
  the first and the event was preventDefaulted.
- `traps Shift+Tab focus inside the panel (Shift+Tab from first → last)`
  the mirror case.

1608 frontend tests pass (3 new).
@cristim

cristim commented May 13, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 13, 2026

Copy link
Copy Markdown
Contributor

…to feat/issue-314-archera-modal-cta

# Conflicts:
#	frontend/src/plans.ts
cristim added a commit that referenced this pull request May 13, 2026
…358)

Four users.test.ts cases were stale on `feat/multicloud-web-frontend`
and are failing on every PR-merge ref against it (including #336, #352,
others). The tests assert the pre-#348 / pre-#347 behaviour:

1. `loadUsers › should load users and groups`
2. `loadUsers › should render groups after loading`
   `mockGroups` is missing the `allowed_accounts: []` field that the
   backend now ships on every group (multi-cloud account-scoping work).
   The actual groups from `api.listGroups` carry the field, so the
   deep-equal assertion against the fixture diverges.

3. `openCreateUserModal › should make password required`
4. `saveUser › should validate empty password for new user`
   Issue #348 ("invite users without a password and let them set it on
   first login") made password optional on create. userModals.ts now
   explicitly sets `passwordInput.required = false` (line 36) so the
   form-level HTML validation doesn't block the invite flow, and
   saveUser intentionally calls `api.createUser` with an empty password
   to trigger the backend invite-email path. Both old tests asserted
   the opposite behaviour and now fail.

Fix:
- Add `allowed_accounts: []` to the two `mockGroups` entries.
- Invert "should make password required" → "should not mark password
  as required (blank invites the user)" asserting `required === false`.
- Replace "should validate empty password" → "should allow empty
  password for new user (invite flow, issue #348)" asserting
  createUser was called with an empty password and no error toast was
  rendered.

Unblocks the frontend-build-sentinel workflow on PRs against
`feat/multicloud-web-frontend`. 1633 frontend tests pass.
@cristim
cristim merged commit 44365bb into feat/multicloud-web-frontend May 13, 2026
6 checks passed
@cristim
cristim deleted the feat/issue-314-archera-modal-cta branch June 3, 2026 21:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/m Days impact/many Affects most users priority/p2 Backlog-worthy severity/medium Moderate harm triaged Item has been triaged type/feat New capability urgency/this-quarter Within the quarter

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant