Skip to content

feat(upgrade): 3-column redesign + yearly-only matrix + retrospective-bug fix (#7) - #327

Merged
thomasluizon merged 2 commits into
mainfrom
feat/upgrade-redesign
Jun 27, 2026
Merged

feat(upgrade): 3-column redesign + yearly-only matrix + retrospective-bug fix (#7)#327
thomasluizon merged 2 commits into
mainfrom
feat/upgrade-redesign

Conversation

@thomasluizon

Copy link
Copy Markdown
Owner

Upgrade/paywall redesign (web + mobile) — research-backed

Fixes the retrospective-display bug and the "repeats itself too much" problem at once.

Structural fix (data model)

upgrade.ts matrix cell gains a 3rd Pro state pro: 'yearly' | true | false. AI Retrospective is now pro:'yearly' — renders ✓ + an inline Yearly pill instead of the old proEnabled:false bug (which showed Pro as not having it). The matrix is the single source of truth — proFeatures shrunk to 4 marquee keys; the trial-expired 3rd copy removed.

Layout (the repetition fix)

Chooser = 3 plan cards (Free · Pro-Yearly hero, center, raised+glow+badge+the only filled CTA · Pro-Monthly anchor). Feature list = ONE 2-column Free-vs-Pro matrix below — monthly & yearly are the same Pro minus retrospective, so the matrix never forks. Grouped categories, positive marking, value-text quantities, "Not included" text (a11y), sticky header.

Entitlements + trial

Matrix reflects the #186 free-tier rebalance (streaks, XP/level, streak-freeze = free; retrospective = yearly-only). Assumes gamification_free_tier is enabled — flip the flag when the rebalance launches so the entitlement matches the matrix. Trial CTA is convert-not-start (the trial is a backend signup grant): "Subscribe to keep Pro" (active) / "Upgrade to Pro" (expired), with the honest "no card was taken" line. No countdowns.

Mobile

Stacked selectable cards (yearly hero preselected), full matrix behind a "Compare all features" accordion, single sticky bottom CTA.

Validation

shared 1032 · web 1664 · mobile 642 · tsc + lint clean · i18n parity (1726 keys each).

🤖 Generated with Claude Code

… CTA (#7)

Chooser = 3 cards (Free / Pro-Yearly hero / Pro-Monthly); feature list = one 2-col Free-vs-Pro matrix (kills the 3x repetition). New cell state pro:'yearly'|true|false fixes the retrospective bug (now checkmark+Yearly pill, was proEnabled:false). Matrix reflects #186 free-tier (streaks/XP/freeze free). Trial-state CTA: Subscribe to keep Pro / Upgrade to Pro (trial is a backend grant). Mobile: stacked cards + accordion matrix + sticky CTA. web+mobile, both locales.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 27, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
orbit-ui-mobile-web Ignored Ignored Jun 27, 2026 2:50pm

Request Review

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review: PR #327 — feat(upgrade): 3-column redesign + yearly-only matrix + retrospective-bug fix

Recommendation: APPROVE

Summary

A well-executed redesign of the upgrade/paywall screens on both web and mobile. The 3-column Free/Pro-Yearly hero/Pro-Monthly chooser replaces the old radio-card selector cleanly. The AI Retrospective retrospective-bug fix is correct: pro: 'yearly' (the new UpgradeProState union) is the right model — the old proEnabled: false while still listing it in UPGRADE_YEARLY_EXTRA_FEATURES was genuinely inconsistent. Convert-state messaging (trial vs free) lands on both platforms in parity. i18n and backward-compat gates passed cleanly.


Findings

Critical — None

High — None

Medium

[MEDIUM] Scratchpad audit file committed to the branch

  • dimension: Dead / stale code (#2)
  • location: CUsersthomaAppDataLocalTempclaudeC--...scratchpadsoft_delete_audit.txt (tracked in this PR diff)
  • issue: A Windows temp-path scratchpad file (soft_delete_audit.txt, 70 lines of internal audit notes) was accidentally committed. No runtime effect but pollutes the commit history.
  • risk: Ships into main and stays in git history unless explicitly removed.
  • fix: git rm "CUsersthomaAppDataLocalTemp..." and add the Windows temp prefix to .gitignore. Can be done in a quick follow-up commit or the next squash.
  • reference: CLAUDE.md rule 2 — "Delete unused code immediately."

Low / Info

[LOW] any in the three new mobile test files

  • dimension: Type safety (#6)
  • location: apps/mobile/__tests__/components/upgrade/plan-comparison-cards.test.tsx:11-12,17,27, pricing-footer.test.tsx:12,32-33, plan-selection.test.tsx:29
  • issue: let tree: any and (node: any) in react-test-renderer test helpers. Pre-existing pattern in plan-card.test.tsx too, but these are new files.
  • fix: Type tree as import type { ReactTestRenderer } from 'react-test-renderer'; narrow node props as { children: unknown }.
  • reference: CLAUDE.md rule 3.

[INFO] freeHeading = "Your Pro trial ended" may mislead permanently-free users

  • dimension: Correctness (#1)
  • location: packages/shared/src/i18n/en.json upgrade.convert.freeHeading, both pricing-section.tsx files
  • issue: trialActive ? trialHeading : freeHeadingfreeHeading fires whenever isTrialActive === false, which includes users who never had a trial (trialEndsAt: null). The PR body states the trial is a backend signup grant (all users get one), so this is safe if that invariant holds server-side.
  • fix: No action if the backend always sets trialEndsAt at signup. If that invariant might not hold, add profile?.trialEndsAt ? freeHeading : genericHeading as a guard.
  • reference: CLAUDE.md rule 1 — documents the server-side assumption.

Subagents

Agent Verdict
parity-checker PAIRED — all substantive mirrors updated; three accepted platform-adapter differences (PricingFooter mobile sticky bar, page-level test web-only, lucide mock mobile-only)
i18n-syncer IN SYNC — 27+ new keys in both locales, all removed keys purged, zero orphaned callsites
contract-aligner N/A — no API types or Zod wire-format schemas changed; upgrade.ts changes are display-layer only
security-reviewer N/A — no orbit-api code touched

Backward-compat guard

No ⚠️ breaks old mobile clients findings.

  • TRIAL_EXPIRED_FEATURE_KEYS removal: was never consumed by either compiled app bundle (both trial-expired-modal.tsx had inline local arrays). Zero runtime impact.
  • freeEnabledfree, proEnabledpro, iconKey moved to category: TypeScript interface renames on a static in-memory data structure. No Zod schemas, no API wire format. Producer and consumer updated together in this PR. Old APK bundles carry their own compiled snapshot.

Validation

N/A — run commands require user approval in this CI session. PR body reports tsc + lint clean, 3338 tests passing (shared 1032 · web 1664 · mobile 642).


What's good

  • Retrospective bug fix is correct: UpgradeProState = 'yearly' | true | false with JSDoc is the right model. Both the data and every rendering path on web and mobile are updated together.
  • iconKey refactor: moving from per-row to per-category is the right DRY lift — icons belong to the category, not individual rows.
  • i18n discipline: 27+ keys added to both locales in the same commit. The shared test validates this.
  • Checkout extraction: checkoutLoading/checkoutError/CTA out of PricingSection into PricingFooter (mobile) / PlanCard (web) is a clean separation of concerns.
  • Test coverage: 5 new test files covering accordion expand/collapse, yearly-pill assertion, trial-vs-free CTA switching, and pricing footer states.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

PR Review — #327 feat(upgrade): 3-column redesign + yearly-only matrix + retrospective-bug fix

Decision: APPROVE

Severity Count
Critical (incl. ⚠️ old-client breaks) 0
High 0
Medium 0
Low / Info 1

Findings

[Low] Dead disabled prop on PricingFooterapps/mobile/app/upgrade.tsx:237-248

PricingFooter is only rendered when isOnline is true (outer gate: !showBilling && plans && isOnline), so disabled={!isOnline} can never be truthy while the component is mounted. Zero behavioral impact — offline state is handled by unmounting the footer and showing OfflineUnavailableState instead. Either drop isOnline from the render condition to make the disabled state reachable, or remove the disabled prop if hiding the footer offline is the intent.


Subagents

Agent Verdict
parity-checker PAIRED — all mirrors present; mobile-only PricingFooter vs web per-card CTAs is a valid platform adapter
i18n-syncer IN SYNC — all 23 new keys in both en.json and pt-BR.json; removed keys pruned from both
contract-aligner N/A — no orbit-api changes; packages/shared/src/types/* untouched

What's good

  • UpgradeProState = 'yearly' | true | false is the correct root-cause fix for the retrospective display bug — explicit discriminant rather than inferring from a missing proEnabled.
  • FreeCell/ProCell decomposition (both platforms) cleanly gates on row.type === 'text' before falling to boolean check/X rendering.
  • Tests target changed behaviors (accordion, yearly-only pill, trial vs. free CTA copy) — not implementation details.
  • i18n is spotless: 23 new keys, all removed keys cleaned from both locales.
  • useTrialExpired/useTrialUrgent correctly removed from the upgrade page; they remain available to profile/page.tsx and trial-banner.tsx.

@thomasluizon
thomasluizon merged commit 4d6865f into main Jun 27, 2026
10 checks passed
@thomasluizon
thomasluizon deleted the feat/upgrade-redesign branch June 27, 2026 17:07
thomasluizon added a commit that referenced this pull request Jun 27, 2026
…340)

#327 redesigned the upgrade paywall from a radiogroup plan-selector into the 3-column PricingSection (a PlanCard grid). The post-deploy prod smoke failed on the now-absent getByRole('radiogroup'). The 'paywall-checkout' testid still exists (the yearly hero card CTA), so drop only the obsolete radiogroup line. Prod is healthy — the other 6 smoke tests (auth, create/log habit, Astra) pass.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant