From 5057c5f0ce0f521a57c40f0a92d43c04d47b321b Mon Sep 17 00:00:00 2001 From: zlshames Date: Fri, 18 Sep 2026 17:00:15 -0400 Subject: [PATCH 1/8] Add a "Is BlueBubbles a good fit for me?" questionnaire Six questions that answer the one thing a newcomer actually wants to know before spending an evening on setup. Light in tone, honest in substance, and willing to say no. Every question maps to a real constraint already documented in the FAQ or the install guide: a Mac is required and a VM counts, the server only relays while that Mac is awake, Full Disk Access is mandatory, and SMS is not supported. Nothing here is invented. Three severities rather than two, because two was not enough: - Disqualifying ends the quiz on the spot. Only "no Mac, and no plans to get one" qualifies, since that is the single requirement with no workaround. - Blocking carries on but steers the result to "probably not". A managed school or work Mac, and wanting to read messages only on an iPhone, are both of this kind. The first version treated them as caveats, which produced a result headed "Yes, with a couple of caveats" sitting directly above a note reading "Probably not for you". Caught by running the paths rather than by reading the code. - Caveats produce a yes with the relevant notes attached: a Mac that sleeps, needing SMS, wanting zero configuration. The result sends people somewhere useful and matched to the verdict: the install guide and downloads for a yes, the FAQ and Discord for a no. All six questions are in the HTML and the script shows one at a time, so without JavaScript the page is still a readable list rather than an empty box; a noscript note gives the short version. Answers stay in the browser and nothing is sent anywhere, which the page says up front. Linked from the footer and from the home hero, where the question is actually being asked. Co-Authored-By: Claude Opus 5 --- scripts/verify-build.mjs | 1 + src/data/fit.ts | 167 ++++++++++++++++++++ src/data/site.ts | 1 + src/pages/index.astro | 7 + src/pages/is-it-for-me/index.astro | 239 +++++++++++++++++++++++++++++ 5 files changed, 415 insertions(+) create mode 100644 src/data/fit.ts create mode 100644 src/pages/is-it-for-me/index.astro diff --git a/scripts/verify-build.mjs b/scripts/verify-build.mjs index 402eb71..979d588 100644 --- a/scripts/verify-build.mjs +++ b/scripts/verify-build.mjs @@ -29,6 +29,7 @@ const REQUIRED_PAGES = [ 'downloads/server/index.html', 'install/index.html', 'faq/index.html', + 'is-it-for-me/index.html', 'donate/index.html', // Not linked from anywhere, but Creem validates this URL -- so a build // that stops emitting it should fail rather than pass quietly. diff --git a/src/data/fit.ts b/src/data/fit.ts new file mode 100644 index 0000000..6f104ae --- /dev/null +++ b/src/data/fit.ts @@ -0,0 +1,167 @@ +/** Questions for "Is BlueBubbles a good fit for me?". + * + * Every constraint here is real and traceable to the FAQ or the install guide: + * a Mac is required (a VM counts), the server only relays while that Mac is + * awake, Full Disk Access is mandatory, and SMS is not supported. The tone is + * light; the answers are not. + */ + +export type Tone = 'good' | 'info' | 'warn'; + +export interface Choice { + id: string; + label: string; + /** Ends the quiz immediately. Used only where the answer is genuinely fatal. */ + disqualifies?: boolean; + /** + * Steers the result to "probably not" without ending the quiz. For answers + * that are a serious problem but not a hard impossibility -- a managed Mac, + * say, where the block is policy and permissions rather than physics. + */ + blocks?: boolean; + /** Carried through to the result. */ + note?: string; + tone?: Tone; +} + +export interface Question { + id: string; + question: string; + /** A line of context under the question. */ + help?: string; + choices: Choice[]; +} + +export const QUESTIONS: readonly Question[] = [ + { + id: 'mac', + question: 'Do you have a Mac, or a way to get one?', + help: "This is the big one. BlueBubbles needs a Mac signed in to iMessage to do the actual talking — there's no way around it.", + choices: [ + { id: 'have', label: 'Yes, I have a Mac', tone: 'good' }, + { + id: 'buy', + label: 'I could pick up a cheap old one', + tone: 'good', + note: 'A second-hand Mac mini is the usual route. It does not need to be fast — it only relays messages.', + }, + { + id: 'vm', + label: "I'd run macOS in a virtual machine", + tone: 'info', + note: 'That works, and there are guides for it in the docs and on the subreddit. Expect a fiddlier setup than real Apple hardware, and be aware it is a grey area under macOS licensing.', + }, + { + id: 'none', + label: 'No, and I have no plans to', + disqualifies: true, + note: 'Without a Mac there is nothing to connect to. This is the one requirement with no workaround.', + }, + ], + }, + { + id: 'uptime', + question: 'Can that Mac stay awake and online?', + help: 'Your Mac is the bridge. When it sleeps, the bridge is out.', + choices: [ + { id: 'always', label: 'It can run 24/7', tone: 'good' }, + { + id: 'mostly', + label: 'Most of the time', + tone: 'info', + note: 'Fine. Messages sent while it is asleep will arrive once it wakes, rather than being lost.', + }, + { + id: 'sometimes', + label: 'Only when I happen to be using it', + tone: 'warn', + note: 'BlueBubbles will still work, but only while that Mac is awake. If it spends most of the day shut, you will miss notifications until you open it. Worth setting the Mac to stay awake and to restart after a power cut.', + }, + ], + }, + { + id: 'managed', + question: 'Is the Mac managed by a school, an employer, or an MDM?', + help: 'Managed Macs tend to lock down exactly the things BlueBubbles needs.', + choices: [ + { id: 'personal', label: "No, it's mine", tone: 'good' }, + { id: 'unsure', label: "I'm not sure", tone: 'info', note: 'If it was handed to you by an IT department, assume it is managed and check before installing anything.' }, + { + id: 'managed', + label: 'Yes, it belongs to a school or employer', + tone: 'warn', + blocks: true, + note: 'Probably not for you. BlueBubbles needs Full Disk Access and, for the extra features, a helper installed into Messages. Managed Macs usually block both — and doing it anyway may well breach the device policy you agreed to. Use a personal Mac instead.', + }, + ], + }, + { + id: 'client', + question: "Where do you want to read your messages?", + help: 'This is the whole point, so it is worth being clear about it.', + choices: [ + { id: 'android', label: 'Android', tone: 'good' }, + { id: 'desktop', label: 'Windows or Linux', tone: 'good' }, + { id: 'web', label: 'In a browser', tone: 'good' }, + { + id: 'apple', + label: 'Only on an iPhone or iPad', + tone: 'warn', + blocks: true, + note: 'You already have iMessage on those. BlueBubbles exists to get iMessage onto things Apple does not cover, so there is not much here for you.', + }, + ], + }, + { + id: 'sms', + question: 'Do you need green-bubble SMS and RCS as well?', + choices: [ + { id: 'no', label: 'No, iMessage is what I am after', tone: 'good' }, + { + id: 'yes', + label: 'Yes, I want all my texts in one place', + tone: 'warn', + note: 'BlueBubbles does not support SMS at this time. It carries iMessage only, so your SMS will stay wherever they are now.', + }, + ], + }, + { + id: 'setup', + question: 'How do you feel about a bit of setup?', + help: 'Installing a server app, granting permissions, connecting a Google account for notifications. Roughly twenty minutes.', + choices: [ + { id: 'fine', label: 'Happy to tinker', tone: 'good' }, + { id: 'guided', label: "I'll manage if there are instructions", tone: 'good', note: 'There are. The install guide walks through every step with screenshots.' }, + { + id: 'none', + label: 'I want it to just work with no configuration', + tone: 'warn', + note: 'Then set expectations accordingly. This is self-hosted software: you run the server, so there is some assembly. Nothing hard, but it is not nothing.', + }, + ], + }, +] as const; + +export interface Verdict { + id: 'yes' | 'maybe' | 'no'; + title: string; + blurb: string; +} + +export const VERDICTS: Record = { + yes: { + id: 'yes', + title: 'Yes — this is built for you', + blurb: 'You have what BlueBubbles needs and none of the usual blockers. Grab the server for your Mac and a client for whatever you actually carry around.', + }, + maybe: { + id: 'maybe', + title: 'Yes, with a couple of caveats', + blurb: 'BlueBubbles will work for you, but a few things are worth knowing before you start. None of them are dealbreakers on their own.', + }, + no: { + id: 'no', + title: 'Honestly? Probably not', + blurb: 'Something in your answers is a genuine blocker rather than an inconvenience. We would rather tell you now than after an hour of setup.', + }, +}; diff --git a/src/data/site.ts b/src/data/site.ts index dce73ea..7fff6cb 100644 --- a/src/data/site.ts +++ b/src/data/site.ts @@ -54,6 +54,7 @@ export const FOOTER_LINKS: readonly NavItem[] = [ { href: '/', label: 'Home' }, { href: '/downloads/', label: 'Downloads' }, { href: '/install/', label: 'Install' }, + { href: '/is-it-for-me/', label: 'Is it for me?' }, { href: '/faq/', label: 'FAQ' }, { href: LINKS.webApp, label: 'Web App', external: true }, { href: '/donate/', label: 'Donate' }, diff --git a/src/pages/index.astro b/src/pages/index.astro index 824a55c..426801a 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -64,6 +64,13 @@ import devices from '@assets/devices.png'; +

+ Not sure it suits your setup? Answer six questions and we'll tell you straight. +

+
    {STORES.map((store) =>
  • )}
diff --git a/src/pages/is-it-for-me/index.astro b/src/pages/is-it-for-me/index.astro new file mode 100644 index 0000000..f3e5f49 --- /dev/null +++ b/src/pages/is-it-for-me/index.astro @@ -0,0 +1,239 @@ +--- +import BaseLayout from '@layouts/BaseLayout.astro'; +import PageHeader from '@components/PageHeader.astro'; +import { QUESTIONS } from '@data/fit'; +import { LINKS, SITE } from '@data/site'; +--- + + + + +
+ +
+ { + QUESTIONS.map((question, index) => ( +
+

+ Question {index + 1} of {QUESTIONS.length} +

+ {question.question} +

{question.question}

+ {question.help &&

{question.help}

} + +
+ {question.choices.map((choice) => ( + + ))} +
+ + +
+ )) + } +
+ + + + +
+
+ + From 2471cdbdeb1b3a385b69876908e4cc262edeff8f Mon Sep 17 00:00:00 2001 From: zlshames Date: Fri, 18 Sep 2026 17:07:49 -0400 Subject: [PATCH 2/8] Add a step indicator to the questionnaire Numbered nodes joined by connectors, in the shape of React Bits' Stepper: answered questions collapse to a tick on a filled node, the current one is ringed, the rest stay outlined, and each connector fills as you pass it. Built in CSS rather than with the `motion` package that component depends on. The transitions here are colour and background changes on three states, which CSS does without a runtime. The indicator tells the truth about partial runs: it marks only the questions actually answered, so a disqualifying answer on question one leaves one node ticked and five outstanding rather than implying a completed run. Verified across the paths -- forward, Back, early exit, full completion and restart. It is aria-hidden. Each question card already announces "Question N of 6", and repeating that as six list items would only add noise for a screen reader without adding information. Co-Authored-By: Claude Opus 5 --- src/pages/is-it-for-me/index.astro | 81 ++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/src/pages/is-it-for-me/index.astro b/src/pages/is-it-for-me/index.astro index f3e5f49..5fce23d 100644 --- a/src/pages/is-it-for-me/index.astro +++ b/src/pages/is-it-for-me/index.astro @@ -19,6 +19,36 @@ import { LINKS, SITE } from '@data/site';
+ {/* Progress indicator. Decorative: each question card already announces + "Question N of M" for assistive tech, so this would only repeat it. */} + +
{ QUESTIONS.map((question, index) => ( @@ -108,7 +138,22 @@ import { LINKS, SITE } from '@data/site'; let current = 0; const answers: Answer[] = []; + const nodes = [...document.querySelectorAll('[data-node]')]; + const connectors = [...document.querySelectorAll('[data-connector]')]; + + /** answered = how many questions are behind us; active = the one on screen, + * or -1 once the result is up and nothing is current. */ + const paintIndicator = (answered: number, active: number) => { + nodes.forEach((node, i) => { + node.dataset.state = i < answered ? 'done' : i === active ? 'current' : 'todo'; + }); + connectors.forEach((connector, i) => { + connector.dataset.state = i < answered ? 'done' : 'todo'; + }); + }; + const show = (index: number) => { + paintIndicator(index, index); steps.forEach((step, i) => (step.hidden = i !== index)); const back = steps[index]?.querySelector('[data-back]'); if (back) back.hidden = index === 0; @@ -125,6 +170,9 @@ import { LINKS, SITE } from '@data/site'; const warned = answers.some((a) => a.tone === 'warn'); const verdict = VERDICTS[negative ? 'no' : warned ? 'maybe' : 'yes']; + // Only the questions actually answered are ticked -- a disqualifying + // answer ends things early, and pretending otherwise would be a lie. + paintIndicator(answers.filter(Boolean).length, -1); steps.forEach((step) => (step.hidden = true)); result.hidden = false; @@ -237,3 +285,36 @@ import { LINKS, SITE } from '@data/site'; show(0); } + + From 31b4c7cbc464a0786564c1f4a283bb10e809c5f1 Mon Sep 17 00:00:00 2001 From: zlshames Date: Fri, 18 Sep 2026 17:10:51 -0400 Subject: [PATCH 3/8] Compose the questionnaire's verdict from the answers that caused it The result said "something in your answers is a genuine blocker", which told the reader nothing they could act on -- the detail was in the notes below, but the sentence carrying the verdict was vague. Each deciding answer now supplies a short clause naming why it counts, and the verdict sentence is built from the ones that actually applied: The sticking point is that you do not have a Mac. Two things get in the way: the Mac belongs to a school or employer and you only read messages on Apple devices. BlueBubbles will work for you. One thing worth knowing first: your Mac will not be awake much. The clauses are deliberately short. A first attempt carried the full explanation in each, which read fine alone and became an unparseable run-on once two were joined by "and". The detail stays in the notes, which is where there is room for it. Counting is handled in both directions: one, two, or a few, for blockers and caveats alike. The first version hardcoded "Two things" and would have said that for three. Co-Authored-By: Claude Opus 5 --- src/data/fit.ts | 16 +++++++++++++-- src/pages/is-it-for-me/index.astro | 31 ++++++++++++++++++++++++++++-- 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/src/data/fit.ts b/src/data/fit.ts index 6f104ae..9786461 100644 --- a/src/data/fit.ts +++ b/src/data/fit.ts @@ -21,6 +21,12 @@ export interface Choice { blocks?: boolean; /** Carried through to the result. */ note?: string; + /** + * A clause naming why this answer counts, used to compose the result + * sentence. Written to read after "the sticking point:" or in a list, so it + * starts lowercase and carries no full stop. + */ + reason?: string; tone?: Tone; } @@ -54,6 +60,7 @@ export const QUESTIONS: readonly Question[] = [ { id: 'none', label: 'No, and I have no plans to', + reason: "you do not have a Mac", disqualifies: true, note: 'Without a Mac there is nothing to connect to. This is the one requirement with no workaround.', }, @@ -74,6 +81,7 @@ export const QUESTIONS: readonly Question[] = [ { id: 'sometimes', label: 'Only when I happen to be using it', + reason: 'your Mac will not be awake much', tone: 'warn', note: 'BlueBubbles will still work, but only while that Mac is awake. If it spends most of the day shut, you will miss notifications until you open it. Worth setting the Mac to stay awake and to restart after a power cut.', }, @@ -89,6 +97,7 @@ export const QUESTIONS: readonly Question[] = [ { id: 'managed', label: 'Yes, it belongs to a school or employer', + reason: 'the Mac belongs to a school or employer', tone: 'warn', blocks: true, note: 'Probably not for you. BlueBubbles needs Full Disk Access and, for the extra features, a helper installed into Messages. Managed Macs usually block both — and doing it anyway may well breach the device policy you agreed to. Use a personal Mac instead.', @@ -106,6 +115,7 @@ export const QUESTIONS: readonly Question[] = [ { id: 'apple', label: 'Only on an iPhone or iPad', + reason: 'you only read messages on Apple devices', tone: 'warn', blocks: true, note: 'You already have iMessage on those. BlueBubbles exists to get iMessage onto things Apple does not cover, so there is not much here for you.', @@ -120,6 +130,7 @@ export const QUESTIONS: readonly Question[] = [ { id: 'yes', label: 'Yes, I want all my texts in one place', + reason: 'you want SMS as well as iMessage', tone: 'warn', note: 'BlueBubbles does not support SMS at this time. It carries iMessage only, so your SMS will stay wherever they are now.', }, @@ -135,6 +146,7 @@ export const QUESTIONS: readonly Question[] = [ { id: 'none', label: 'I want it to just work with no configuration', + reason: 'you would rather not configure anything', tone: 'warn', note: 'Then set expectations accordingly. This is self-hosted software: you run the server, so there is some assembly. Nothing hard, but it is not nothing.', }, @@ -157,11 +169,11 @@ export const VERDICTS: Record = { maybe: { id: 'maybe', title: 'Yes, with a couple of caveats', - blurb: 'BlueBubbles will work for you, but a few things are worth knowing before you start. None of them are dealbreakers on their own.', + blurb: 'BlueBubbles will work for you.', }, no: { id: 'no', title: 'Honestly? Probably not', - blurb: 'Something in your answers is a genuine blocker rather than an inconvenience. We would rather tell you now than after an hour of setup.', + blurb: 'We would rather tell you now than after an hour of setup.', }, }; diff --git a/src/pages/is-it-for-me/index.astro b/src/pages/is-it-for-me/index.astro index 5fce23d..f587fc0 100644 --- a/src/pages/is-it-for-me/index.astro +++ b/src/pages/is-it-for-me/index.astro @@ -132,9 +132,15 @@ import { LINKS, SITE } from '@data/site'; if (form && result && steps.length) { type Answer = { question: string; choice: string; note?: string; tone?: Tone; - fatal?: boolean; blocks?: boolean; + fatal?: boolean; blocks?: boolean; reason?: string; }; + /** "a", "a and b", "a, b and c" */ + const list = (parts: string[]) => + parts.length <= 1 + ? (parts[0] ?? '') + : `${parts.slice(0, -1).join(', ')} and ${parts[parts.length - 1]}`; + let current = 0; const answers: Answer[] = []; @@ -183,7 +189,27 @@ import { LINKS, SITE } from '@data/site'; (negative ? 'text-rose-500' : warned ? 'text-amber-500' : 'text-accent'); document.getElementById('fit-verdict-title')!.textContent = verdict.title; - document.getElementById('fit-verdict-blurb')!.textContent = verdict.blurb; + + // Say which answers decided it, rather than "something in your answers". + const deciding = answers + .filter((a) => (negative ? a.fatal || a.blocks : a.tone === 'warn')) + .map((a) => a.reason) + .filter((r): r is string => Boolean(r)); + + let blurb = verdict.blurb; + if (deciding.length === 1) { + blurb = negative + ? `The sticking point is that ${deciding[0]}. ${verdict.blurb}` + : `${verdict.blurb} One thing worth knowing first: ${deciding[0]}.`; + } else if (deciding.length > 1) { + // Count-aware in both directions: there can be more than two, and the + // singular case above must not say "a few things". + const many = deciding.length === 2 ? 'Two things' : 'A few things'; + blurb = negative + ? `${many} get in the way: ${list(deciding)}. ${verdict.blurb}` + : `${verdict.blurb} ${many} worth knowing first: ${list(deciding)}.`; + } + document.getElementById('fit-verdict-blurb')!.textContent = blurb; const notes = document.getElementById('fit-notes')!; notes.innerHTML = ''; @@ -260,6 +286,7 @@ import { LINKS, SITE } from '@data/site'; tone: choice.tone, fatal: choice.disqualifies, blocks: choice.blocks, + reason: choice.reason, }; // A disqualifying answer ends it there; no point asking the rest. From 8879bf9b2de64fb815a0673d7396df3d0d2b30ce Mon Sep 17 00:00:00 2001 From: zlshames Date: Fri, 18 Sep 2026 17:16:27 -0400 Subject: [PATCH 4/8] Correct the questionnaire on SMS and RCS BlueBubbles does carry SMS and RCS. They reach the Mac by Text Message Forwarding from an iPhone, so what is actually required is a phone number, not a missing feature. The question now asks whether you want them and splits on whether you have an iPhone with a SIM: - Have one: a clean yes, with a note to enable Text Message Forwarding and point it at the server Mac. - Do not: a caveat rather than a blocker, noting that a cheap prepaid SIM in an old iPhone on the same Apple ID is the usual workaround, and that iMessage works regardless. This was wrong because it was taken from the FAQ, which says "Not at this time, though we hope to add this feature in the near future!" -- and that answer is live on the site today. The quiz is fixed here; the FAQ entry still needs correcting, and is the more visible of the two. Co-Authored-By: Claude Opus 5 --- src/data/fit.ts | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/data/fit.ts b/src/data/fit.ts index 9786461..49b9a01 100644 --- a/src/data/fit.ts +++ b/src/data/fit.ts @@ -124,15 +124,22 @@ export const QUESTIONS: readonly Question[] = [ }, { id: 'sms', - question: 'Do you need green-bubble SMS and RCS as well?', + question: 'Do you want green-bubble SMS and RCS as well?', + help: 'BlueBubbles carries these too, but they reach your Mac by way of an iPhone, so a phone number is involved.', choices: [ - { id: 'no', label: 'No, iMessage is what I am after', tone: 'good' }, + { id: 'no', label: 'No, iMessage is all I need', tone: 'good' }, { - id: 'yes', - label: 'Yes, I want all my texts in one place', - reason: 'you want SMS as well as iMessage', + id: 'iphone', + label: 'Yes, and I have an iPhone with a SIM in it', + tone: 'good', + note: 'Turn on Text Message Forwarding on that iPhone and point it at your server Mac. SMS and RCS then land in Messages alongside iMessage, and BlueBubbles picks them up with everything else.', + }, + { + id: 'no-iphone', + label: 'Yes, but I have no iPhone with a number', + reason: 'SMS and RCS need an iPhone with a phone number', tone: 'warn', - note: 'BlueBubbles does not support SMS at this time. It carries iMessage only, so your SMS will stay wherever they are now.', + note: 'SMS and RCS arrive by Text Message Forwarding from an iPhone, so a phone number is required. The usual workaround is a cheap prepaid SIM in an old iPhone, kept on the same Apple ID purely to relay texts. Without one you still get iMessage -- just not the green bubbles.', }, ], }, From e6f8a004bf24ea5197dbadc09a9ba167c0744cf1 Mon Sep 17 00:00:00 2001 From: zlshames Date: Fri, 18 Sep 2026 17:20:51 -0400 Subject: [PATCH 5/8] Make the questionnaire's result notes stand on their own, and fix forwarding Several notes were written as replies to the question that prompted them, which reads fine while answering and badly on the result screen, where the question is no longer on the page. "There are. The install guide walks through every step" is the worst of them: on its own, "There are" refers to nothing. Also fixed: "Fine.", "Then set expectations accordingly", and "That works". Corrects the SMS note as well. It said to turn on Text Message Forwarding and point it at the server Mac, which overstates the work: with the iPhone and the Mac on the same Apple ID, texts forward on their own. The manual setting is now described as the fallback for when they do not appear, which is what it actually is. Co-Authored-By: Claude Opus 5 --- src/data/fit.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/data/fit.ts b/src/data/fit.ts index 49b9a01..b557b77 100644 --- a/src/data/fit.ts +++ b/src/data/fit.ts @@ -55,7 +55,7 @@ export const QUESTIONS: readonly Question[] = [ id: 'vm', label: "I'd run macOS in a virtual machine", tone: 'info', - note: 'That works, and there are guides for it in the docs and on the subreddit. Expect a fiddlier setup than real Apple hardware, and be aware it is a grey area under macOS licensing.', + note: 'Running macOS in a VM works, and there are guides for it in the docs and on the subreddit. Expect a fiddlier setup than real Apple hardware, and be aware it is a grey area under macOS licensing.', }, { id: 'none', @@ -76,7 +76,7 @@ export const QUESTIONS: readonly Question[] = [ id: 'mostly', label: 'Most of the time', tone: 'info', - note: 'Fine. Messages sent while it is asleep will arrive once it wakes, rather than being lost.', + note: 'Messages sent while the Mac is asleep arrive once it wakes, rather than being lost.', }, { id: 'sometimes', @@ -132,7 +132,7 @@ export const QUESTIONS: readonly Question[] = [ id: 'iphone', label: 'Yes, and I have an iPhone with a SIM in it', tone: 'good', - note: 'Turn on Text Message Forwarding on that iPhone and point it at your server Mac. SMS and RCS then land in Messages alongside iMessage, and BlueBubbles picks them up with everything else.', + note: 'With the iPhone and the Mac signed in to the same Apple ID, texts forward to the Mac on their own. If they do not show up, check Text Message Forwarding under Settings then Messages on the iPhone. Once they land in Messages, BlueBubbles picks them up alongside iMessage.', }, { id: 'no-iphone', @@ -149,13 +149,13 @@ export const QUESTIONS: readonly Question[] = [ help: 'Installing a server app, granting permissions, connecting a Google account for notifications. Roughly twenty minutes.', choices: [ { id: 'fine', label: 'Happy to tinker', tone: 'good' }, - { id: 'guided', label: "I'll manage if there are instructions", tone: 'good', note: 'There are. The install guide walks through every step with screenshots.' }, + { id: 'guided', label: "I'll manage if there are instructions", tone: 'good', note: 'The install guide walks through every step, with screenshots.' }, { id: 'none', label: 'I want it to just work with no configuration', reason: 'you would rather not configure anything', tone: 'warn', - note: 'Then set expectations accordingly. This is self-hosted software: you run the server, so there is some assembly. Nothing hard, but it is not nothing.', + note: 'This is self-hosted software: you run the server yourself, so there is some assembly. Nothing hard, but it is not nothing.', }, ], }, From 0d41864209e02bcabccecb2055d1522cd13343d6 Mon Sep 17 00:00:00 2001 From: zlshames Date: Fri, 18 Sep 2026 17:22:58 -0400 Subject: [PATCH 6/8] Reword the questionnaire's affirmative result "Grab the server for your Mac and a client for whatever you actually carry around" was doing too much: "grab" and "carry around" are colloquial in a way the rest of the page is not, and "a client for whatever you actually carry around" makes the reader work out what a client is and which of their devices counts. Now: "Install the server on your Mac, then the app wherever you want to read your messages." Same instruction, in the order it happens. Co-Authored-By: Claude Opus 5 --- src/data/fit.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/fit.ts b/src/data/fit.ts index b557b77..6c99456 100644 --- a/src/data/fit.ts +++ b/src/data/fit.ts @@ -171,7 +171,7 @@ export const VERDICTS: Record = { yes: { id: 'yes', title: 'Yes — this is built for you', - blurb: 'You have what BlueBubbles needs and none of the usual blockers. Grab the server for your Mac and a client for whatever you actually carry around.', + blurb: 'You have what BlueBubbles needs, and none of the usual blockers. Install the server on your Mac, then the app wherever you want to read your messages.', }, maybe: { id: 'maybe', From a46989faee13f54e6c5a1d88b248e9c20bd7c384 Mon Sep 17 00:00:00 2001 From: zlshames Date: Fri, 18 Sep 2026 17:27:41 -0400 Subject: [PATCH 7/8] Ask which macOS the server Mac runs The current 2.x server needs Sonoma or newer, so the macOS version decides which server someone ends up on -- and that is worth knowing before they start rather than after. Tahoe and Sequoia pass without comment. Sonoma passes with a note that a few of the newer Private API features want Sequoia or Tahoe. Ventura and older is a caveat rather than a blocker: BlueBubbles still works, on the 1.x server, which is in maintenance rather than active development. The note gives the three ways out -- upgrade macOS if the Mac supports it, use OpenCore Legacy Patcher if Apple has dropped it, or move to a newer Mac. The floor is Sonoma, not Sequoia: Package.swift declares .macOS(.v14) with "macOS 14 (Sonoma)" beside it, the compatibility matrix starts at 14, and the lowest capability floor is 14. Confirmed before building, because telling a Sonoma user to patch a Mac that already works would have been worse than saying nothing. The question count is no longer written into the copy. The lead derives it from the data and the home page link no longer names a number, so the next question added cannot leave "Six questions" stranded above seven of them -- which is exactly what this change would have done. The result title is now count-aware too: "Yes, with one caveat" above "One thing worth knowing first", rather than the static "a couple of caveats" contradicting the sentence beneath it. Co-Authored-By: Claude Opus 5 --- src/data/fit.ts | 28 ++++++++++++++++++++++++++++ src/pages/index.astro | 2 +- src/pages/is-it-for-me/index.astro | 9 +++++++-- 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/src/data/fit.ts b/src/data/fit.ts index 6c99456..5314556 100644 --- a/src/data/fit.ts +++ b/src/data/fit.ts @@ -66,6 +66,34 @@ export const QUESTIONS: readonly Question[] = [ }, ], }, + { + id: 'macos', + question: 'What macOS version is that Mac on?', + help: 'The current server needs Sonoma or newer. Older Macs are not shut out, but they are on an older server.', + choices: [ + { id: 'tahoe', label: 'Tahoe (26)', tone: 'good' }, + { id: 'sequoia', label: 'Sequoia (15)', tone: 'good' }, + { + id: 'sonoma', + label: 'Sonoma (14)', + tone: 'good', + note: 'Sonoma runs the current server. A handful of the newer Private API features need Sequoia or Tahoe, but the core of BlueBubbles is all there.', + }, + { + id: 'older', + label: 'Ventura (13) or older', + reason: 'the Mac is on a macOS too old for the current server', + tone: 'warn', + note: 'BlueBubbles still works, but on the 1.x server rather than the current 2.x one, and 1.x is in maintenance rather than active development. If that Mac can take a newer macOS, upgrading is the simplest fix. If Apple has dropped it, OpenCore Legacy Patcher will often get an older Mac onto Sonoma or later. Failing both, a newer second-hand Mac is the other way out.', + }, + { + id: 'unsure', + label: 'I am not sure', + tone: 'info', + note: 'Check the Apple menu, then About This Mac. Sonoma or newer runs the current server; Ventura or older runs the 1.x server, which is no longer actively developed.', + }, + ], + }, { id: 'uptime', question: 'Can that Mac stay awake and online?', diff --git a/src/pages/index.astro b/src/pages/index.astro index 426801a..2ffc674 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -67,7 +67,7 @@ import devices from '@assets/devices.png';

Not sure it suits your setup? Answer six questionsAnswer a few questions and we'll tell you straight.

diff --git a/src/pages/is-it-for-me/index.astro b/src/pages/is-it-for-me/index.astro index f587fc0..7f52ce0 100644 --- a/src/pages/is-it-for-me/index.astro +++ b/src/pages/is-it-for-me/index.astro @@ -13,7 +13,7 @@ import { LINKS, SITE } from '@data/site'; >
@@ -188,7 +188,6 @@ import { LINKS, SITE } from '@data/site'; 'text-xs font-semibold uppercase tracking-wider ' + (negative ? 'text-rose-500' : warned ? 'text-amber-500' : 'text-accent'); - document.getElementById('fit-verdict-title')!.textContent = verdict.title; // Say which answers decided it, rather than "something in your answers". const deciding = answers @@ -196,6 +195,12 @@ import { LINKS, SITE } from '@data/site'; .map((a) => a.reason) .filter((r): r is string => Boolean(r)); + // The title has to agree with the sentence under it: "a couple of + // caveats" above "One thing worth knowing first" reads as a bug. + const titleEl = document.getElementById('fit-verdict-title')!; + titleEl.textContent = + verdict.id === 'maybe' && deciding.length === 1 ? 'Yes, with one caveat' : verdict.title; + let blurb = verdict.blurb; if (deciding.length === 1) { blurb = negative From d1caa059b444081d5b91f87d8e4633f27fceb100 Mon Sep 17 00:00:00 2001 From: zlshames Date: Fri, 18 Sep 2026 17:33:27 -0400 Subject: [PATCH 8/8] Put the macOS question behind a V2_RELEASED flag The question shipped talking about "the current 2.x server" and 1.x being in maintenance. Neither is true yet: 2.x has not been released, so a reader cannot be on it, and telling someone their Mac is too old for a release that does not exist is worse than not asking. There are now two variants of that one question and a flag choosing between them. The rest of the quiz is identical either way. While 1.x is the only server, macOS version does not decide whether BlueBubbles runs -- it decides how much of the Private API is available. So the question asks in those terms, with buckets matching the FAQ's own compatibility tables (Ventura and newer, Big Sur or Monterey, Catalina and older), and says nothing about versions at all. Flipping V2_RELEASED to true on release day swaps in the other variant: Sonoma floor, the 1.x-versus-2.x explanation, and OpenCore Legacy Patcher for Macs Apple has dropped. Verified by building both ways. With the flag false the page offers the Ventura-and-newer buckets and contains no mention of 2.x, 1.x, OpenCore, Sonoma, Sequoia or Tahoe; with it true the Sonoma-floor choices appear. It is now back to false. Co-Authored-By: Claude Opus 5 --- src/data/fit.ts | 103 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 75 insertions(+), 28 deletions(-) diff --git a/src/data/fit.ts b/src/data/fit.ts index 5314556..ba551e7 100644 --- a/src/data/fit.ts +++ b/src/data/fit.ts @@ -38,6 +38,80 @@ export interface Question { choices: Choice[]; } +/** + * Whether the 2.x Swift server has shipped. + * + * Until it has, the questionnaire must not talk about it: there is no "current + * 2.x server" for a reader to be on, and telling someone their Mac is too old + * for a release that does not exist is worse than not asking at all. Flip this + * to true on release day and the macOS question swaps to the 2.x framing -- + * Sonoma floor, OpenCore Legacy Patcher, the lot. + * + * The rest of the quiz is unaffected; this is the only question that differs. + */ +export const V2_RELEASED = false; + +/** Asked while 1.x is the only server. macOS version changes which Private API + * features are available, but does not decide whether BlueBubbles runs. */ +const MACOS_QUESTION_TODAY: Question = { + id: 'macos', + question: 'What macOS version is that Mac on?', + help: 'BlueBubbles runs on a wide range of macOS versions. Newer ones simply expose more of what iMessage can do.', + choices: [ + { id: 'ventura-plus', label: 'Ventura (13) or newer', tone: 'good' }, + { + id: 'bigsur-monterey', + label: 'Big Sur (11) or Monterey (12)', + tone: 'info', + note: 'Everything central works. A few of the newer Private API features -- editing and unsending among them -- want Ventura or later.', + }, + { + id: 'catalina-older', + label: 'Catalina (10.15) or older', + tone: 'info', + note: 'BlueBubbles works here, but noticeably less of it: several Private API features need Big Sur or later. The compatibility tables in the FAQ spell out which.', + }, + { + id: 'unsure', + label: 'I am not sure', + tone: 'info', + note: 'Check the Apple menu, then About This Mac. Any reasonably recent macOS is fine; newer ones just unlock more features.', + }, + ], +}; + +/** Asked once 2.x has shipped, when the version decides which server you run. */ +const MACOS_QUESTION_V2: Question = { + id: 'macos', + question: 'What macOS version is that Mac on?', + help: 'The current server needs Sonoma or newer. Older Macs are not shut out, but they are on an older server.', + choices: [ + { id: 'tahoe', label: 'Tahoe (26)', tone: 'good' }, + { id: 'sequoia', label: 'Sequoia (15)', tone: 'good' }, + { + id: 'sonoma', + label: 'Sonoma (14)', + tone: 'good', + note: 'Sonoma runs the current server. A handful of the newer Private API features need Sequoia or Tahoe, but the core of BlueBubbles is all there.', + }, + { + id: 'older', + label: 'Ventura (13) or older', + reason: 'the Mac is on a macOS too old for the current server', + tone: 'warn', + note: 'BlueBubbles still works, but on the 1.x server rather than the current 2.x one, and 1.x is in maintenance rather than active development. If that Mac can take a newer macOS, upgrading is the simplest fix. If Apple has dropped it, OpenCore Legacy Patcher will often get an older Mac onto Sonoma or later. Failing both, a newer second-hand Mac is the other way out.', + }, + { + id: 'unsure', + label: 'I am not sure', + tone: 'info', + note: 'Check the Apple menu, then About This Mac. Sonoma or newer runs the current server; Ventura or older runs the 1.x server, which is no longer actively developed.', + }, + ], +}; + +const MACOS_QUESTION: Question = V2_RELEASED ? MACOS_QUESTION_V2 : MACOS_QUESTION_TODAY; + export const QUESTIONS: readonly Question[] = [ { id: 'mac', @@ -66,34 +140,7 @@ export const QUESTIONS: readonly Question[] = [ }, ], }, - { - id: 'macos', - question: 'What macOS version is that Mac on?', - help: 'The current server needs Sonoma or newer. Older Macs are not shut out, but they are on an older server.', - choices: [ - { id: 'tahoe', label: 'Tahoe (26)', tone: 'good' }, - { id: 'sequoia', label: 'Sequoia (15)', tone: 'good' }, - { - id: 'sonoma', - label: 'Sonoma (14)', - tone: 'good', - note: 'Sonoma runs the current server. A handful of the newer Private API features need Sequoia or Tahoe, but the core of BlueBubbles is all there.', - }, - { - id: 'older', - label: 'Ventura (13) or older', - reason: 'the Mac is on a macOS too old for the current server', - tone: 'warn', - note: 'BlueBubbles still works, but on the 1.x server rather than the current 2.x one, and 1.x is in maintenance rather than active development. If that Mac can take a newer macOS, upgrading is the simplest fix. If Apple has dropped it, OpenCore Legacy Patcher will often get an older Mac onto Sonoma or later. Failing both, a newer second-hand Mac is the other way out.', - }, - { - id: 'unsure', - label: 'I am not sure', - tone: 'info', - note: 'Check the Apple menu, then About This Mac. Sonoma or newer runs the current server; Ventura or older runs the 1.x server, which is no longer actively developed.', - }, - ], - }, + MACOS_QUESTION, { id: 'uptime', question: 'Can that Mac stay awake and online?',