Repository navigation
Add annual Pro pricing #9234
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Add annual Pro pricing #9234
Changes from all commits
Commits
Show all changes
57 commits
Select commit
Hold shift + click to select a range
4079ac3
feat(web): add annual Pro pricing
lawrencecchen a61a0ab
fix(web): toggle pricing without navigation
lawrencecchen 32dfdac
feat(web): refine annual pricing presentation
lawrencecchen 5a7d0aa
fix(web): use cmux product blue for annual savings
lawrencecchen d1efb9c
fix: include working cmux theme picker
lawrencecchen 3b8ee71
chore: update Ghostty theme picker base
lawrencecchen 2b337b6
Merge remote-tracking branch 'origin/main' into feat-annual-pricing
lawrencecchen 9a511d9
fix(web): adapt product blue for theme contrast
lawrencecchen d78a78c
chore: update Ghostty theme picker base
lawrencecchen 23ff3aa
Merge remote-tracking branch 'origin/main' into feat-annual-pricing
lawrencecchen 419df25
test(web): reproduce stale Pagefind trace
lawrencecchen 28cc907
fix(web): build Pagefind before Next tracing
lawrencecchen 88e5c16
test(web): cover annual Team pricing
lawrencecchen bdc02d2
feat(web): add annual Team pricing
lawrencecchen 8f09503
test: reject external browser intent from untrusted sites
lawrencecchen 4ca3781
fix: trust-gate external browser handoffs
lawrencecchen d8cf605
test(web): reject invalid billing intervals
lawrencecchen 83c96b4
fix(web): reject invalid billing intervals
lawrencecchen 17edc2d
Merge remote-tracking branch 'origin/main' into feat-annual-pricing
lawrencecchen f0f88f7
Pin GhosttyKit for pricing theme picker
lawrencecchen 681e5de
Harden annual pricing integration
lawrencecchen 37b02bb
Pin app theme injection to trusted origin
lawrencecchen bd2f958
Merge remote-tracking branch 'origin/main' into feat-annual-pricing
lawrencecchen 7badff4
test: cover final annual pricing regressions
lawrencecchen 95a977f
fix: close annual pricing review gaps
lawrencecchen 689162d
test(web): exercise Stripe catalog behavior
lawrencecchen 58187a7
test: cover cross-origin annual checkout handoff
lawrencecchen 01b3a2e
fix: trust cross-origin checkout from app pricing
lawrencecchen 2de43cc
test: reject arbitrary external pricing destinations
lawrencecchen 6fff7b5
fix: pin external pricing handoff destination
lawrencecchen 94fb05d
test: require same-origin app checkout relay
lawrencecchen bd3347e
fix: relay app checkout through trusted origin
lawrencecchen 6313f5d
test: cover app origin and Stripe pagination guards
lawrencecchen 86171a8
fix: close final pricing safety gaps
lawrencecchen 16288c4
test: reject invalid pricing relay parameters
lawrencecchen b80286e
fix: validate app pricing relay parameters
lawrencecchen eb33602
test: cover app web loopback aliases
lawrencecchen 65515f8
fix: share browser loopback host validation
lawrencecchen 7eb3e31
refactor: keep app origin helpers scoped
lawrencecchen ac99420
test: keep unrelated Pagefind coverage unchanged
lawrencecchen d060ae5
test: preserve tagged callbacks through pricing relay
lawrencecchen 27f2fd4
fix: preserve trusted tagged pricing callbacks
lawrencecchen 7365941
fix: clean up embedded pricing layout
lawrencecchen 29f60ce
test: cover annual Team labels without lookup keys
lawrencecchen 880a08d
fix: label annual Team subscriptions from metadata
lawrencecchen 465ba6c
test: cover annual pricing review regressions
lawrencecchen 16e8936
fix: close annual pricing review gaps
lawrencecchen 9c20eb6
Merge remote-tracking branch 'origin/main' into feat-annual-pricing
lawrencecchen b22f63e
Merge remote-tracking branch 'origin/main' into feat-annual-pricing
lawrencecchen 9d263fb
fix: return validated restore arguments
lawrencecchen cff68a5
fix: align annual pricing interaction contracts
lawrencecchen 535d274
fix: authenticate pricing callbacks and catalog retries
lawrencecchen c88e6dc
test: cover final annual pricing review regressions
lawrencecchen 971518b
fix: close final annual pricing review gaps
lawrencecchen 7cb2960
test: keep app theme policy in package suite
lawrencecchen ffcb8bc
Merge remote-tracking branch 'origin/main' into feat-annual-pricing
lawrencecchen 3afa9eb
Record combined GhosttyKit artifact
lawrencecchen File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
103 changes: 103 additions & 0 deletions
103
Packages/macOS/CmuxBrowser/Sources/CmuxBrowser/AppSession/BrowserAppTheme.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,103 @@ | ||
| public import Foundation | ||
|
|
||
| /// Serializable app-web theme values shared by pricing and Pro welcome browser surfaces. | ||
| public struct BrowserAppTheme: Equatable, Sendable { | ||
| public let appearance: String | ||
| public let background: String | ||
| public let foreground: String | ||
| public let accent: String | ||
| public let accentOnBackground: String | ||
| public let accentOnForeground: String | ||
|
|
||
| /// Creates a complete theme after the app has resolved readable accent variants. | ||
| public init( | ||
| appearance: String, | ||
| background: String, | ||
| foreground: String, | ||
| accent: String, | ||
| accentOnBackground: String, | ||
| accentOnForeground: String | ||
| ) { | ||
| self.appearance = appearance | ||
| self.background = background | ||
| self.foreground = foreground | ||
| self.accent = accent | ||
| self.accentOnBackground = accentOnBackground | ||
| self.accentOnForeground = accentOnForeground | ||
| } | ||
|
|
||
| /// Query items used to seed a server-rendered app-web page with the native theme. | ||
| public var queryItems: [URLQueryItem] { | ||
| [ | ||
| URLQueryItem(name: "appearance", value: appearance), | ||
| URLQueryItem(name: "background", value: background), | ||
| URLQueryItem(name: "foreground", value: foreground), | ||
| URLQueryItem(name: "accent", value: accent), | ||
| URLQueryItem(name: "accent_on_background", value: accentOnBackground), | ||
| URLQueryItem(name: "accent_on_foreground", value: accentOnForeground), | ||
| ] | ||
| } | ||
|
|
||
| /// JavaScript that refreshes an already-rendered supported page without navigation. | ||
| public func applyingJavaScript() -> String? { | ||
| let payload = JavaScriptPayload( | ||
| appearance: appearance, | ||
| background: background, | ||
| foreground: foreground, | ||
| accent: accent, | ||
| accentOnBackground: accentOnBackground, | ||
| accentOnForeground: accentOnForeground | ||
| ) | ||
| guard let data = try? JSONEncoder().encode(payload), | ||
| let json = String(data: data, encoding: .utf8) else { | ||
| return nil | ||
| } | ||
| return """ | ||
| (() => { | ||
| const root = document.querySelector('[data-cmux-app-theme]'); | ||
| if (!root) return false; | ||
| const theme = \(json); | ||
| root.style.setProperty('--ghostty-background', theme.background); | ||
| root.style.setProperty('--ghostty-foreground', theme.foreground); | ||
| root.style.setProperty('--cmux-product-blue', theme.accent); | ||
| root.style.setProperty('--cmux-product-blue-on-background', theme.accentOnBackground); | ||
| root.style.setProperty('--cmux-product-blue-on-foreground', theme.accentOnForeground); | ||
| root.style.backgroundColor = theme.background; | ||
| root.style.colorScheme = theme.appearance; | ||
| root.dataset.cmuxAppThemeAppearance = theme.appearance; | ||
| if (root.hasAttribute('data-app-pricing-appearance')) { | ||
| root.setAttribute('data-app-pricing-appearance', theme.appearance); | ||
| } | ||
| if (root.hasAttribute('data-app-pro-welcome-appearance')) { | ||
| root.setAttribute('data-app-pro-welcome-appearance', theme.appearance); | ||
| } | ||
| for (const element of [document.documentElement, document.body]) { | ||
| element?.style.setProperty('background', theme.background, 'important'); | ||
| } | ||
| document.querySelector('meta[name="theme-color"]') | ||
| ?.setAttribute('content', theme.background); | ||
| return true; | ||
| })() | ||
| """ | ||
| } | ||
|
|
||
| /// Checks the app-web URL contract without constructing theme colors. | ||
| public static func supportsAppSurface(url: URL?, trustedOrigin: URL) -> Bool { | ||
| guard let url, | ||
| let scheme = url.scheme?.lowercased(), | ||
| scheme == "http" || scheme == "https", | ||
| BrowserAppWebOrigin(trustedOrigin).containsAppSurface(url) else { | ||
| return false | ||
| } | ||
| return true | ||
| } | ||
|
|
||
| private struct JavaScriptPayload: Encodable { | ||
| let appearance: String | ||
| let background: String | ||
| let foreground: String | ||
| let accent: String | ||
| let accentOnBackground: String | ||
| let accentOnForeground: String | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
33 changes: 33 additions & 0 deletions
33
...es/macOS/CmuxBrowser/Sources/CmuxBrowser/AppSession/BrowserExternalNavigationPolicy.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| public import Foundation | ||
|
|
||
| /// Decides whether a trusted app-web surface explicitly requests the system browser. | ||
| public struct BrowserExternalNavigationPolicy: Equatable, Sendable { | ||
| public let trustedOrigin: URL | ||
|
|
||
| /// Creates a policy pinned to the exact app-web origin. | ||
| public init(trustedOrigin: URL) { | ||
| self.trustedOrigin = trustedOrigin | ||
| } | ||
|
|
||
| /// Returns true only when a trusted app-web source activates a same-origin | ||
| /// web URL with an explicit intent marker. | ||
| public func shouldOpenInSystemBrowser( | ||
| _ url: URL, | ||
| sourceURL: URL? | ||
| ) -> Bool { | ||
| guard let sourceURL, | ||
| BrowserAppWebOrigin(trustedOrigin).contains(sourceURL), | ||
| isSafeWebDestination(url), | ||
| let components = URLComponents(url: url, resolvingAgainstBaseURL: false), | ||
| components.queryItems?.contains(where: { | ||
| $0.name == "cmux_external_browser" && $0.value == "1" | ||
| }) == true else { | ||
| return false | ||
| } | ||
| return true | ||
| } | ||
|
|
||
| private func isSafeWebDestination(_ url: URL) -> Bool { | ||
| BrowserAppWebOrigin(trustedOrigin).contains(url) | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For package consumers, this six-argument public initializer leaves the meanings and invariants of the three accent variants undocumented. The package API rules require
- Parameterdocumentation for public initializers and documentation for public property invariants, so add DocC entries for these arguments rather than only a summary sentence.AGENTS.md reference: AGENTS.md:L99-L102
Useful? React with 👍 / 👎.