feat(voice): offer Windows downloads - #155
Merged
Merged
Conversation
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P1W6SyAGqXSQUbDN4fHfUi
wavekat.com has never offered a Windows visitor the installer we build on every release. It hands them a .dmg. wavekat-voice docs/54 names that the one item on its list that is pure loss rather than an investment call, and this is its Phase 1a. Windows is two files — x64 and arm64 — and which chip a visitor has is not in a user-agent string. `navigator.userAgentData` knows, but only on Chromium and only asynchronously, so it would buy an informed guess for some machines and a blind default for the rest. So the Mac and Linux buttons still download in one click, and the Windows primary opens a two-row menu instead. That asymmetry is the design: the alternative is handing an x64 desktop a file Windows refuses to run. The rows carry an Early tag and the panel says plainly that the Windows builds aren't code-signed yet, linking to installation.md's SmartScreen walkthrough. Signing, PR CI on Windows, and the clean-VM smoke test are docs/54's later phases; this ships distribution, not a claim that Windows is as proven as macOS. Two details worth keeping: A null from /releases/latest is a real answer, not a gap to fill from the fallback table. No release has published an arm64 installer yet, so that row ships hidden and the browser-side refresh reveals it the day one does — without a rebuild. Falling back to a constant there would render a choice that 404s. Each toggleable element names its display in `data-dl-show`, and the alt menu wraps each platform GROUP, so promoting a platform toggles the wrapper while a row's own hidden state stays about whether that file exists. Two independent reasons to hide, on one element, was the bug waiting to happen. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P1W6SyAGqXSQUbDN4fHfUi
The site told a Windows visitor, in nine languages, that the product does not run on their computer — while the release pipeline built them an installer. #251 stripped Windows from the platform claims deliberately; this puts it back now that the download page offers the build. Scope is a named list, not a sweep. The site holds ~325 "Mac & Linux" strings and most are SEO titles tuned to "… for Mac" demand queries, where a third platform name spends characters against a 50-char budget and dilutes an exact-match query. So only the statements that DENY Windows change: the platforms FAQ and meta description on /voice/, the comparison table's platform row, the alternatives intro's "with Windows on the way", About, Privacy, and the five posts that close with "Windows is coming when there's demand". The last of those matter most — a "which platforms?" answer is the passage an AI answer lifts verbatim. "Will you support Windows?" becomes "Does WaveKat Voice run on Windows?", which is what someone actually types, and it feeds FAQPage schema from the same array the page renders. It answers yes, names both architectures, and says the build is unsigned — a fair answer beats a flattering one on a page whose job is to be quoted. The two Chinese descriptions were rewritten rather than extended: a third platform pushed them to 90 and 92 against the 85-char CJK budget that check:meta enforces. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P1W6SyAGqXSQUbDN4fHfUi
The SEO/GEO review CLAUDE.md requires for edited pages found three surfaces the copy pass missed, all of them the ones a searcher or an answer engine actually reads: /voice/download/'s description read "Download WaveKat Voice for Mac or Linux" in nine languages — a SERP snippet naming the platforms and omitting the one the page now offers. It also had room: at 107 characters it was under the 110 floor. /llms.txt is the site's own file in public/, not the automation contract synced from wavekat-voice, and it still described a softphone "for Mac and Linux" with a download line listing only .dmg and .deb. That is the file ChatGPT and Perplexity read. The alternatives descriptions and the docs index carried the same claim. Every description was re-measured rather than extended: the Korean one landed at 87 against the 85-char CJK budget and was shortened instead. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P1W6SyAGqXSQUbDN4fHfUi
Three surfaces the copy pass missed and the review caught (the download and alternatives descriptions, public/llms.txt, the blog posts' platform closer), and the no_arch/no_installer split, which was a decision made while implementing rather than one the spec anticipated. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P1W6SyAGqXSQUbDN4fHfUi
Contributor
The disclosure panel is taller than the gap below the button, so it overflows whatever contains it. Two containers stopped it: the homepage hero and the /voice/ download card both used `overflow-hidden` to keep a blurred glow in bounds, cutting the open menu in half. The clipping moves onto the decoration itself, leaving the section free to overflow. The homepage needed a second half: the band below it reveals on scroll, and that transform makes it a stacking context painting after the hero, so it covered the menu even unclipped. The hero takes `z-10` — the header is `z-40`, so nothing changes there. Neither is new. Linux was clipped the same way; Windows just made the menu tall enough to notice.
The unsigned-build note under the Windows rows pointed at /docs/voice/installation/, which is synced from the private repo and English-only — so a Chinese reader clicking "如何继续安装" landed in English prose. /voice/download/ is already translated into all nine locales, so it gains an #windows section instead: the SmartScreen box explained in one self-contained paragraph, three steps past it, and which of the two installers to pick. Step 1 of "Getting set up" now names the .exe next to the .dmg and the .deb. The href is built with localizedPath(), so each locale's menu points at its own page. Also two menu details the taller Windows panel exposed: the panel forces `text-left` (the CTA card centres its text) and the Early tag no longer wraps to two lines beside a wrapped label.
Asking "Intel or AMD?" treated the two installers as symmetric. They aren't: an x64 build runs on every Windows PC we support — natively on Intel and AMD, emulated on Windows 11 for ARM — while an arm64 build runs on almost none of them. Only one of the two mistakes is fatal, and it isn't the one x64 makes. So Windows gets the same plain pill as Mac and Linux, wired to windows-x64, and the words "Intel or AMD" leave the page. The ARM build moves into "other platforms" as the one row that stays listed when Windows is the active platform — it's the only choice the primary can't make for a visitor. `dlWindowsX64` is gone; the row uses `dlWindows`. The unsigned-build note now also sits under the button, because the visitor being handed a Windows download is the one about to meet SmartScreen. /voice/download/#windows says the same thing about arch that the menu now implies, in all nine locales.
0.0.46 is the first release to publish a native Windows-on-ARM installer, so the menu row that shipped hidden is now live. Verified against the real feed rather than a mock: all four targets 302 to distinct files that HEAD 200 at the advertised size, and every control on the page — primary and each menu row, in three locales — resolves to the target it names. The build-time fallback can hold windows-arm64 now that one exists; it was held out because a constant for a never-published target renders a button that 404s.
Three things in the "other platforms" menu contradicted each other, and each was visible in the same screenshot. The unsigned-build note covers BOTH Windows installers but hung off the x64 row, so it rendered between the two Windows entries — reading as a caveat about x64 alone and cutting the ARM row loose from the group above it. It caps the group at the foot of the panel now, on the same show/hide rule as before: hidden exactly when a Windows visitor is already reading the same line under their button. The ARM row said "Windows on ARM" while the two rows above it said "Download for …". Every row in the menu is a download, so every row is worded like one; the requirement line under each label is what says which machine it is for. Nine locales take their own download verb, and each /voice/download/ page quotes the label verbatim in the Windows walkthrough, so those nine sentences move with it. Those requirement lines named Surface as the ARM cue, but most Surface models are Intel or AMD — the one mistake that hands someone an installer their PC refuses to run. Both Windows rows now name the chip family instead, in parallel: Intel & AMD (x64), Snapdragon (ARM64). The panel needed `w-max` to hold any of it. It is absolutely positioned inside the `relative` <details>, so its containing block is only as wide as the summary; shrink-to-fit capped it there and long rows wrapped, which is what the fixed `min-w` was really propping open. A fixed minimum can't hold nine locales. The note carries its own `max-w` in return — a paragraph's max-content width is the whole sentence on one line, and it would otherwise set the width of the entire menu. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P1W6SyAGqXSQUbDN4fHfUi
The unsigned-build note lives inside the download control and only appears once the promotion script picks Windows, so the control is one pill tall for a Mac or Linux visitor and about 46px taller for a Windows one. Every row holding it centred its items, which meant that on Windows the buttons beside it dropped by half the note's height — the homepage's "View on GitHub" sat visibly below the download button it is supposed to sit next to. The two shapes need different alignment. In the homepage hero the sibling is a matching pill, so `items-start` is equivalent to centring when the note is hidden and correct when it is not. On the alternatives and provider pages the sibling is a small text link, which `items-start` would slam to the top of the pill; `items-baseline` puts it on the button's text line instead, landing within half a pixel of where centring puts it today and staying there whether or not the note shows. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P1W6SyAGqXSQUbDN4fHfUi
A `git add -A` swept `.claude/worktrees/` into the previous commit as a gitlink. It is a local session worktree — its own git repository, empty to anyone who clones this one — so it is untracked and ignored now. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P1W6SyAGqXSQUbDN4fHfUi
wavekat-eason
marked this pull request as ready for review
August 16, 2026 00:35
Windows on ARM64 reports "Windows NT 10.0; Win64; x64" in its
user-agent string on purpose, so x64 software keeps working. The
promotion script read only the UA, which meant an ARM machine could
never be told apart from an Intel one and always landed on the
emulated x64 installer, with the native build two clicks away under
"other platforms".
The chip is only exposed through
navigator.userAgentData.getHighEntropyValues(), which is Chromium-only
and async — the two reasons docs/05 originally ruled it out. So it runs
as a second pass rather than in place of the first: the platform swap
stays synchronous and x64 still goes on the button for everything that
cannot answer, and a machine that answers arm/64 is upgraded a few
milliseconds later. Being wrong in that direction costs nothing, since
Windows 11 for ARM emulates x64.
Supporting changes:
* altEntries collapses into one `entries` list keyed on (platform,
archKey). The primary and the menu are exact opposites by
construction, so they cannot drift into offering a file twice or
not at all — an ARM visitor now sees x64 in the menu for the same
reason an Intel one sees ARM.
* Primaries carry data-dl-avail, and resolve() reverts to the arch
every machine can run when the one it wants is unpublished. The
release refresh updates that flag and re-runs the promotion, so a
target appearing or vanishing between deploys moves the button and
not just the menu row.
* resolve() is now idempotent and re-entrant, which is what makes the
second pass a small addition instead of a second code path.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P1W6SyAGqXSQUbDN4fHfUi
Two statements survived the Windows promotion sweep and now contradict the ones next to them. `click-to-call-phone-links` had its platforms FAQ rewritten to "Mac, Windows and Linux, the three platforms" but kept the following clause — "and it works on both" — in all nine locales. One sentence, two counts. The other four posts in that sweep say "all three"; this one was missed. `place-calls-from-the-command-line` opens with "It's built into the app today on Mac and Linux" while its own FAQ, three screens down, answers "Mac, Windows and Linux". The lead is the passage an answer engine lifts, so it was the wrong half to leave stale. Upstream `docs/voice/installation.md` calls Windows a supported platform and the automation doc limits the CLI to nothing, so "all three" is the true claim. Also drops a JSDoc line describing a design that didn't ship: `dlWindows` labels a button that downloads x64, not one that opens a choice. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P1W6SyAGqXSQUbDN4fHfUi
Three gaps the review found, all of them the same shape: a rule this branch established and then applied incompletely. `678d7b2` re-aligned every row holding <VoiceDownload /> because the control grows ~46px taller once the unsigned-build note appears on Windows — but it missed `VoiceOverview.astro`, which is the `/voice/` hero in all nine locales. The sibling there is a small text link, the same shape as the alternatives and provider pages, so it takes `items-baseline` for the same reason those did. The promotion script only consulted `data-dl-avail` for a non-default arch, so the rule the file's own comment states — "a row for a file the platform says is missing would 404, so the same rule runs in reverse" — held for menu rows and not for primary buttons. A release that published `windows-arm64` but not `windows-x64` left an Intel visitor a visible "Download for Windows" button resolving to a 404, and `mac` was worse still: it is the SSR default, so it rendered shown regardless. The arch is now the first published candidate of `[activeArch, 'default']`, or none — no button beats a broken one, and the menu is still there. Exercised by running the shipped script over the built markup: Mac, Linux, Windows, Windows-on-ARM, Android and iPhone are unchanged in the live all-four-published state, and each degraded combination now hides the primary instead of pointing it at a missing file. CLAUDE.md still told the next author that Voice is "Mac and Linux today; Windows when there's demand", which would reintroduce the copy this branch just removed, and `.gitignore` swallowed all of `.claude/` when only the session worktrees and settings.local.json are local state. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P1W6SyAGqXSQUbDN4fHfUi
wavekat-eason
pushed a commit
that referenced
this pull request
Aug 16, 2026
🤖 I have created a release *beep* *boop* --- ## [0.0.56](wavekat-com-v0.0.55...wavekat-com-v0.0.56) (2026-08-16) ### Features * **voice:** offer Windows downloads ([#155](#155)) ([c0ff5a7](c0ff5a7)) ### Bug Fixes * **deps:** clear Dependabot alerts, Astro 7 ([#156](#156)) ([fb51a03](fb51a03)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
wavekat-eason
added a commit
that referenced
this pull request
Aug 19, 2026
## What was wrong Two page families claimed WaveKat Voice runs on two platforms while the comparison table on the same page said three. Windows shipped in #155; `/voice/alternatives/linphone/` and `/voice/providers/2talk/` were never updated, in any of the nine locales. The 2talk page was worse than stale — it told the reader Windows was hypothetical, then sent Windows visitors elsewhere: ``` 'Mac and Linux today (Windows when there is demand).' 'Mac 和 Linux(有需求时支持 Windows)。' "You're on Windows and want a free, lightweight client today — that's MicroSIP" ``` Separately, **call flows** and the **HubSpot integration** — two of the clearest differences against a plain SIP client — appeared on neither page. Five of the nine alternatives titles also led with the brand (`WaveKat Voice — a Linphone alternative`, `WaveKat Voice — alternativa a Linphone (Mac/Linux)`), spending budget on a term that already ranks #1. ## What changed Both pages, all nine locales: | | Before | After | |---|---|---| | Platform claim | title, description, H1, intro all "Mac & Linux" | all three, everywhere, with the honest caveat that the Windows build is newer and unsigned | | Titles | brand-first, or 2 platforms | demand-first, 3 platforms (`Linphone alternative for Mac, Windows & Linux`, `2talk 软电话(Mac/Win/Linux)`) | | Comparison rows | 8 / 7 | 10 / 9 — added answering & voicemail, and CRM logging | | FAQs | 3 / 6 | 7 / 9 | | Breadcrumb node 1 | `Voice` | `WaveKat Voice` + `inLanguage` | - **Call flows / voicemail** are stated with the mechanism, not as a bullet: greeting, opening hours, menu, voicemail or transfer; the message is recorded *and transcribed*; you can pick up mid-message. The FAQ names the real limits — menus, hours and transfers are Pro (free for a year in early access), and the flow runs on your own computer, so if the app isn't running the provider's voicemail takes the call. - **HubSpot**: connect once, every call files itself on the number-matched contact with time, direction, outcome, duration, transcript, and a recording playable inside HubSpot. Pro, free during early access, webhooks for other CRMs. - **Fair to the competition**: Linphone, Bria and MicroSIP each get "no answering of its own — voicemail is your provider's mailbox, which it can dial", and MicroSIP keeps its genuine strength ("the smallest possible Windows client") without implying we don't run there. - **Every localized title and description re-measured** against its own budget (CJK ≤ 28/85, Latin ≤ 50/160) instead of translated from English. `check:meta` caught four CJK descriptions over budget mid-review — they're now 80–85. - Localized "where to go next" blocks (the pages previously linked out only to the hub) and each language's conventional FAQ heading. ## Verification ``` npm run build && npm run check:links && npm run check:meta ✓ links OK — 241 pages scanned, no broken or leaking internal links. ✓ meta OK — titles and descriptions within budget ``` Checked against `dist/`: all 18 pages carry 7/9 FAQs, 10/9 comparison rows, the corrected breadcrumb entity, and both new topics. The only surviving `Mac and Linux` strings on these pages are inside the new Windows FAQ answers, where they're correct. ## Deliberately not in this PR - **The Bria price** — `NZ$5.95 + GST / month (as of July 2026)` appears in the 2talk table and two FAQs in all nine locales. The dataset comment says re-check before quoting; I couldn't verify it, so I left the figure and its "as of" stamp rather than restamping it and manufacturing false freshness. Worth a two-minute check. - **Seven blog posts** still open with "WaveKat Voice — the SIP softphone **for Mac and Linux** …" in all nine locales, two of them in the meta description. Same defect class, ~60 strings; these are dated announcements, so how to phrase it is a separate call. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01P1W6SyAGqXSQUbDN4fHfUi Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
wavekat.com has never offered a Windows visitor the installer we build on
every release — it hands them a
.dmg. wavekat-voicedocs/54calls thatthe one item on its list that is pure loss rather than an investment call.
This is its Phase 1a and Phase 4 together: offer the build, and say we
offer it.
Spec:
docs/05-windows-downloads.md.Merge last. The buttons point at the platform's resolver and the build
reads
/releases/latest, so this needs wavekat-platform#256 deployedfirst — otherwise the build falls back to constants and renders Windows
buttons that 404. wavekat-voice#318 (the synced docs) goes in between.
One button per platform, x64 for everyone
Windows is two files, but they are not symmetric: an x64 installer runs on
every Windows PC we support — natively on Intel and AMD, emulated on
Windows 11 for ARM — while an arm64 installer runs on almost none of them.
Only one of the two mistakes is fatal, and it isn't the one x64 makes. So
Windows gets the same plain pill as Mac and Linux, wired to
windows-x64,and the words "Intel or AMD" appear nowhere on the page.
The ARM build lives in "other platforms", as the one row that stays listed
when Windows is the active platform — a Windows visitor has already been
handed x64, so it's the only choice their primary can't make for them.
Detection promotes the platform, never the chip:
navigator.userAgentDatais the only thing that knows the chip, and it's Chromium-only and
asynchronous.
Windows rows carry an
Earlytag, and the line saying the builds aren'tcode-signed appears twice on purpose — in the menu for someone choosing
Windows from a Mac, and under the button for the visitor the promotion
script just handed a Windows download.
The install walkthrough is ours now
That line used to point at
/docs/voice/installation/, which is syncedfrom the private repo and English-only — a Chinese reader clicking
"如何继续安装" landed in English prose.
/voice/download/is alreadytranslated into nine locales, so it gains an
#windowssection instead:the SmartScreen box in one self-contained paragraph, three steps past it,
and which build to pick if your PC is ARM. Step 1 of "Getting set up" now
names the
.exenext to the.dmgand the.deb.A clipping bug the taller menu exposed
The disclosure panel overflows whatever contains it, and two containers
stopped it: the homepage hero and the
/voice/download card both usedoverflow-hiddento keep a blurred glow in bounds, cutting the open menuin half. The clipping moves onto the decoration itself. On the homepage
there was a second half — the band below reveals on scroll, and that
transform makes it a stacking context painting after the hero, so it
covered the menu even unclipped; the hero takes
z-10. Linux was clippedthe same way. Windows just made the menu tall enough to notice.
Two details worth review
A
nullfrom/releases/latestis a real answer, not a gap to fill fromthe fallback table: no release has published an arm64 installer yet, so
that row ships hidden and the browser-side refresh reveals it the day one
does, with no rebuild. Falling back to a constant there would render a
choice that 404s.
Each toggleable element names its display in
data-dl-show, and every"other platforms" row is wrapped — so promoting a platform toggles the
wrapper while a row's own hidden state stays about whether that file
exists. Two independent reasons to hide on one element was the bug waiting
to happen.
Promotion is a named list, not a sweep
The site holds ~325 "Mac & Linux" strings, most of them SEO titles tuned
to "… for Mac" demand queries where a third platform name spends
characters against a 50-char budget and dilutes an exact-match query. Only
the statements that deny Windows change: the platforms FAQ and
description on
/voice/, the comparison table's platform row, thealternatives intro's "with Windows on the way", About, Privacy, and the
five posts closing with "Windows is coming when there's demand".
"Will you support Windows?" becomes "Does WaveKat Voice run on Windows?" —
what someone actually types — and feeds
FAQPageschema from the samearray the page renders.
What the SEO review caught
public/llms.txtis the site's own AI-answer file, separate from theautomation contract synced from wavekat-voice, and still described a
softphone "for Mac and Linux" with a download line listing only
.dmgand.deb./voice/download/'s description read "for Mac or Linux" in ninelanguages — a snippet telling a Windows searcher to leave the page that
now offers them the build.
Not done here
/voice/'s title still spends its budget on the brand and carries nodemand term, and "for Windows" is a modifier we rank for nowhere. Retitling
the flagship page is a deliberate decision, not a side effect of this PR.
Testing
npm run build && npm run check:links && npm run check:meta, builtagainst the upstream docs branch via
SYNC_DOCS_REF_VOICE.check:metatook two rounds: a third platform name pushed the two Chinese
/voice/descriptions to 90 and 92 against the 85-char CJK budget, and the Korean
alternatives one to 87 — all rewritten rather than extended. The whole
control was also built against a mock of the new API shape to confirm both
Windows rows render with distinct sizes before the platform ships.
🤖 Generated with Claude Code
https://claude.ai/code/session_01P1W6SyAGqXSQUbDN4fHfUi