From 5dd1014af702b876dc6c2db51a782482382afb5f Mon Sep 17 00:00:00 2001
From: zlshames
Date: Fri, 18 Sep 2026 08:58:44 -0400
Subject: [PATCH 1/3] Add a macOS version picker to the Pro feature list
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Pro features carry macOS minimums ranging from 14 to 26, so "what do I
actually get" depends on the server Mac. A picker now answers that
directly: choosing a release highlights the features it can run, dims the
rest, and reports a count.
Releases are cumulative, which is the whole point of the control: a
release includes everything introduced at or below its version, so Tahoe
gets the Sequoia and Sonoma features too. That is a <= on the major
version, with the catalogue's minimums as the only input. Verified in the
browser: Sonoma 5 of 10, Sequoia 7 of 10, Tahoe 10 of 10, each a strict
superset of the one before.
Names and numbers come from the server's docs/MACOS_COMPATIBILITY.md --
14 Sonoma, 15 Sequoia, 26 Tahoe. There is no option below 14 because
Package.swift declares .macOS(.v14), and none between 15 and 26 because
Apple's year-based renumbering means those releases do not exist.
State is not carried by colour alone: each card gains a "✓ Included" or
"Needs macOS 26" line, and the count is a live region, so the answer
survives greyscale and a screen reader. Nothing is selected by default, so
the list reads normally until someone asks the question.
Co-Authored-By: Claude Opus 5
---
src/data/pricing.ts | 18 +++++++
src/pages/pricing/index.astro | 95 ++++++++++++++++++++++++++++++++++-
2 files changed, 111 insertions(+), 2 deletions(-)
diff --git a/src/data/pricing.ts b/src/data/pricing.ts
index 7820463..45a21f8 100644
--- a/src/data/pricing.ts
+++ b/src/data/pricing.ts
@@ -154,3 +154,21 @@ export const FREE_FEATURES: readonly string[] = [
'Managing groups',
'Find My',
] as const;
+
+/** macOS releases the Swift server runs on.
+ *
+ * Names and numbers come from the server's own `docs/MACOS_COMPATIBILITY.md`.
+ * The floor is 14: `Package.swift` declares `.macOS(.v14)`, so there is nothing
+ * below it to offer. Note the numbering jumps 15 -> 26; Apple moved to
+ * year-based versions, and 16 through 25 do not exist.
+ */
+export interface MacOSRelease {
+ version: number;
+ name: string;
+}
+
+export const MACOS_RELEASES: readonly MacOSRelease[] = [
+ { version: 14, name: 'Sonoma' },
+ { version: 15, name: 'Sequoia' },
+ { version: 26, name: 'Tahoe' },
+] as const;
diff --git a/src/pages/pricing/index.astro b/src/pages/pricing/index.astro
index 36ac8ed..24df19a 100644
--- a/src/pages/pricing/index.astro
+++ b/src/pages/pricing/index.astro
@@ -16,7 +16,7 @@
*/
import BaseLayout from '@layouts/BaseLayout.astro';
import PageHeader from '@components/PageHeader.astro';
-import { PLANS, PRO_FEATURES, FREE_FEATURES } from '@data/pricing';
+import { PLANS, PRO_FEATURES, FREE_FEATURES, MACOS_RELEASES } from '@data/pricing';
import { LINKS } from '@data/site';
---
@@ -69,10 +69,38 @@ import { LINKS } from '@data/site';
minimum macOS version, shown alongside it.
+
+
+
+
+
+
{
PRO_FEATURES.map((feature) => (
-
+
{feature.title}
@@ -80,6 +108,9 @@ import { LINKS } from '@data/site';
{feature.summary}
+ {/* Text, not just colour: the state has to survive greyscale and a
+ screen reader. Hidden until a version is chosen. */}
+
))
}
@@ -123,3 +154,63 @@ import { LINKS } from '@data/site';
+
+
From 8e47656ccf1eb3f630b3a13ff5a4d966a9999672 Mon Sep 17 00:00:00 2001
From: zlshames
Date: Fri, 18 Sep 2026 09:03:27 -0400
Subject: [PATCH 2/3] Default the macOS picker to the newest release
The picker opened unselected, so the feature list read as plain until
someone used it, and the first real selection a Sonoma user made reported
5 of 10 on a page asking for money. It now opens on the newest release --
Tahoe (26), 10 of 10 -- with the older versions still a click away.
The default is derived from MACOS_RELEASES rather than hardcoded, so
adding macOS 27 moves it automatically.
The default state is rendered server-side: the selected option, each
card's data-state, its verdict line and the summary count are all in the
HTML. The page is therefore correct before the script runs and without it,
and there is no flash of unstyled state on load. The script now only
handles changes, and the empty-selection branch is gone with the
placeholder option.
Co-Authored-By: Claude Opus 5
---
src/pages/pricing/index.astro | 40 +++++++++++++++++++++--------------
1 file changed, 24 insertions(+), 16 deletions(-)
diff --git a/src/pages/pricing/index.astro b/src/pages/pricing/index.astro
index 24df19a..dbc9d66 100644
--- a/src/pages/pricing/index.astro
+++ b/src/pages/pricing/index.astro
@@ -18,6 +18,11 @@ import BaseLayout from '@layouts/BaseLayout.astro';
import PageHeader from '@components/PageHeader.astro';
import { PLANS, PRO_FEATURES, FREE_FEATURES, MACOS_RELEASES } from '@data/pricing';
import { LINKS } from '@data/site';
+
+// Default the picker to the newest release, so the list opens on the full
+// feature set. Derived from the data rather than hardcoded, so adding macOS 27
+// to MACOS_RELEASES moves the default automatically.
+const newestMacOS = MACOS_RELEASES.reduce((a, b) => (b.version > a.version ? b : a));
---
-
{
MACOS_RELEASES.map((release) => (
-
))
}
-
+
+ {PRO_FEATURES.filter((f) => f.macos <= newestMacOS.version).length} of {
+ PRO_FEATURES.length
+ } Pro features run on {newestMacOS.name} ({newestMacOS.version}).
+
@@ -97,6 +105,7 @@ import { LINKS } from '@data/site';
{feature.summary}
{/* Text, not just colour: the state has to survive greyscale and a
- screen reader. Hidden until a version is chosen. */}
-
+ screen reader. Rendered server-side for the default selection,
+ so this is correct before the script runs and without it. */}
+
))
}
@@ -175,16 +193,7 @@ import { LINKS } from '@data/site';
const apply = () => {
const chosen = Number(select.value);
-
- if (!chosen) {
- for (const card of cards) {
- delete card.dataset.state;
- const verdict = card.querySelector('[data-verdict]');
- if (verdict) verdict.hidden = true;
- }
- if (summary) summary.textContent = '';
- return;
- }
+ if (!chosen) return;
let included = 0;
for (const card of cards) {
@@ -195,7 +204,6 @@ import { LINKS } from '@data/site';
const verdict = card.querySelector('[data-verdict]');
if (verdict) {
- verdict.hidden = false;
verdict.textContent = ok ? '✓ Included' : `Needs macOS ${needs}`;
verdict.className = ok
? 'mt-3 text-xs font-semibold text-accent'
From cf0acd9bc834c9075fccfafcd4d1a1d7eea77e1e Mon Sep 17 00:00:00 2001
From: zlshames
Date: Fri, 18 Sep 2026 09:05:44 -0400
Subject: [PATCH 3/3] Fix the macOS picker's dropdown arrow crowding the right
edge
The native arrow sits inside the select's right padding, and on a
rounded-full pill the 18.5px corner radius curves in over exactly where it
renders, so it read as jammed against the edge.
Replaces it with appearance-none and our own chevron: 16px, vertically
centred to the pixel, 14px clear of the right edge and 10px clear of the
text, with right padding raised from 16px to 40px to make room. It reuses
the FAQ accordion's chevron path, so the two controls now match, and it
inherits text-muted so it themes -- which the native arrow did not.
Checked that making it non-native did not break the control: the chevron is
pointer-events-none so a click over it still lands on the select, the
select remains keyboard focusable, change still filters correctly (5 / 7 /
10 for Sonoma / Sequoia / Tahoe), and the chevron picks up the right colour
in both themes.
Co-Authored-By: Claude Opus 5
---
src/pages/pricing/index.astro | 48 +++++++++++++++++++++++++----------
1 file changed, 34 insertions(+), 14 deletions(-)
diff --git a/src/pages/pricing/index.astro b/src/pages/pricing/index.astro
index dbc9d66..b012b0e 100644
--- a/src/pages/pricing/index.astro
+++ b/src/pages/pricing/index.astro
@@ -78,20 +78,40 @@ const newestMacOS = MACOS_RELEASES.reduce((a, b) => (b.version > a.version ? b :
-
+ {/*
+ appearance-none plus our own chevron. The native arrow sits inside the
+ right padding, and on a rounded-full pill the 18.5px corner radius
+ curves in over it, so it reads as crammed against the edge. Drawing it
+ ourselves also makes it match the FAQ accordion's chevron and follow
+ the theme, which the native control does not.
+ */}
+
+
+
+
{PRO_FEATURES.filter((f) => f.macos <= newestMacOS.version).length} of {
PRO_FEATURES.length