Skip to content

fix(desktop): ship a bundle that parses on WebKit without lookbehind (Intel/macOS 12) - #5547

Open
arielhauter wants to merge 4 commits into
block:mainfrom
arielhauter:macos12-webkit-lookbehind
Open

arielhauter wants to merge 4 commits into
block:mainfrom
arielhauter:macos12-webkit-lookbehind

Conversation

@arielhauter

Copy link
Copy Markdown

Summary

Buzz Desktop renders a blank window on Macs whose system WebKit predates Safari 16.4: the shipped bundle contains RegExp lookbehind patterns, and older WebKit fails at parse time, so the frontend never renders. This matches the symptom in #3295, and matters because the project ships and promotes Intel DMGs (#748 added the x86_64 release target; #2090 steers Safari users to the right DMG) while the app declares minimum macOS 10.15.

Four changes make the bundle lookbehind-free:

  1. desktop/vite.config.ts — build target ["es2020", "safari15"], so the toolchain lowers syntax for pre-16.4 WebKit.
  2. mentionHighlightExtension.ts — rewrite the two mention/channel patterns using a boundary capture group instead of lookbehind, with a prefix-length offset fix in findHighlightMatches.
  3. KeyboardShortcutsCard.tsx — replace the lookbehind/lookahead key-combo split with a small splitKeyCombo helper (same behavior).
  4. patches/[email protected] — the dependency ships pre-bundled code with a lookbehind in the email-autolink pattern; patched via the existing patches/ convention, since a build target cannot rewrite it.

Because the failure is at parse time, one pattern anywhere blanks the whole app — that is why both the build target and the source/dependency rewrites are needed.

Trade-offs, stated honestly: (a) the safari15 target affects all platforms, not only macOS 12; (b) the dependency patch slightly changes email-autolink edge behavior everywhere, relying on the package's boundary guard instead of the lookbehind — the ideal fix is upstream in micromark, this is the interim.

Heads-up: #4701 (open) introduces a new negative lookbehind in the bare-URL pattern; the same portability concern applies there.

Related issue

Relates to #3295 — same blank-render symptom; note the original report is arm64/Safari 17.6, which may be a separate cause. This PR fixes the lookbehind parse failure affecting pre-16.4 WebKit. Before opening I searched existing PRs/issues for "macOS 12", "Intel", "lookbehind" — closest existing PR: none found.

Testing

  • grep -rln -E '\(\?<[=!]' over the built Buzz.app Resources: clean.
  • just desktop-test: 4,604 tests pass. just desktop-tauri-test: pass.
  • Built with just desktop-release-build x86_64-apple-darwin on current main (post-0.5.9).
  • Smoke-tested the resulting DMG on macOS 12 Intel hardware (MacBook Air): window renders, login persists, channels and history load, mentions highlight, URLs linkify. Same patches previously ran in daily self-hosted use on a 0.5.8 base (Air + iMac).
  • Not tested: Apple Silicon regression, macOS 13+.

@aurelie-brebant

Copy link
Copy Markdown

Confirming this PR's diagnosis against the current release, on Intel hardware different from the ones already in the thread. I have not built the PR, so this confirms the analysis and its mapping onto the current tag, not the fix itself.

Environment

  • MacBook, Intel x86_64
  • macOS 12.7.6 (21H1320)
  • Buzz Desktop 0.5.20, installed from the official Buzz 0.5.20.dmg, no local build
  • Result: no window; initial render did not commit before reveal timeout, native process stays alive, com.apple.WebKit.WebContent spawns

The last confirmation against a release DMG in #3295 was v0.5.5 (7 Aug).

The four changes still map exactly onto desktop-v0.5.20

Checked against the tag, not main:

  • A full scan of desktop/src for (?<[=!] returns exactly three hits, and they are precisely the two files this PR rewrites:
    • desktop/src/features/settings/ui/KeyboardShortcutsCard.tsx:17
    • desktop/src/features/messages/lib/mentionHighlightExtension.ts:488 and :503 (the patterns @claytonwinterbotham flagged at 110/125, since moved)
  • No other lookbehind has crept into first-party source.
  • pnpm-lock.yaml still pins [email protected] (via [email protected]) and @radix-ui/[email protected].
  • patches/ still contains only isomorphic-git.patch and [email protected], so the new patch file lands cleanly.
  • desktop/vite.config.ts still declares no build.target, and there is still no minimumSystemVersion in desktop/src-tauri/tauri.conf.json.

The boot-graph edge that makes KeyboardShortcutsCard a startup failure rather than a settings-only one is also unchanged: AppShell.tsx:70-74 imports DEFAULT_SETTINGS_SECTION and isSettingsSection as values (used at line 175) from SettingsPanels.tsx, which statically imports KeyboardShortcutsCard at line 66.

One correction to the reasoning, which strengthens the case for the build target

Earlier in #3295 the missing target was described as leaving the dependency graph at esnext. That is not what Vite 8 does, and the real default is a better argument for this PR.

[email protected], the pinned version, defaults build.target to baseline-widely-available, documented in its own type declarations as:

transpile targeting browsers that are included in the Baseline Widely Available on 2026-01-01. (Chrome 111+, Edge 111+, Firefox 114+, Safari 16.4+).

Safari 16.4 is exactly the release that added both RegExp lookbehind and class static blocks. The default is therefore off by precisely one WebKit generation from macOS 12's system WebKit: both constructs are treated as supported and passed through untouched. A default named "baseline widely available" reads as conservative, which is likely why this went unnoticed.

Measured in a real WKWebView on this machine

A small Swift harness loading the constructs as separate <script> blocks, so each block's parse result is independent:

navigator.userAgent                 AppleWebKit/605.1.15
control script                      runs
/(?<!\+)\+(?!\s*$)/                 block never runs
/(?<=^|\s|\p{P}|\p{S}).../gu        block never runs
class C extends Map { static {} }   block never runs
new RegExp("(?<!x)y")               block never runs

The WebContent binary actually executed is 17613.3.9.1.16, while Safari.app on the same machine reports 17.6, matching the red herring already noted in #3295.

The last line is the one worth keeping in mind for review: new RegExp with a lookbehind fails too, so no build target can rescue mentionHighlightExtension.ts. The source rewrite in this PR is required, not cosmetic.

Happy to run any specific check on macOS 12 Intel if it helps move the review along.

@XIII-EOG

XIII-EOG commented Sep 7, 2026

Copy link
Copy Markdown

Hi! I have a MacBook Pro 13" Early 2015, Intel i5, 8 GB RAM, running macOS 12.7.6. The official Buzz 0.5.23 x64 DMG opens to a blank window, and older releases have the same issue.

I saw that you successfully tested the WebKit fix on Intel Monterey. Would you be able to share the patched x64 DMG or a downloadable build artifact? I’m not a developer, and building from source on this old Mac requires a large toolchain that I’m struggling to install.

Thank you!

@aurelie-brebant

Copy link
Copy Markdown

@arielhauter a practical question, as one of the people waiting on this. GitHub now reports the branch as conflicting with main (mergeable: false). mentionHighlightExtension.ts has moved a fair bit since 11 Aug: the two lookbehind patterns were at :488 and :503 at desktop-v0.5.20 and are at :569 and :584 on main today.

Since maintainers generally skip conflicted PRs during triage, the rebase looks like the real blocker right now rather than reviewer attention. Are you still planning to pick this back up? If not, would you mind saying so, so someone else can carry the same four commits forward rather than leaving it stalled?

State of things as of today, for whoever looks next: main still has no build.target, all three lookbehinds are still present, patches/ is unchanged, and 0.5.22 and 0.5.23 both shipped without the fix.

@arielhauter

Copy link
Copy Markdown
Author

@arielhauter a practical question, as one of the people waiting on this. GitHub now reports the branch as conflicting with main (mergeable: false). mentionHighlightExtension.ts has moved a fair bit since 11 Aug: the two lookbehind patterns were at :488 and :503 at desktop-v0.5.20 and are at :569 and :584 on main today.

Since maintainers generally skip conflicted PRs during triage, the rebase looks like the real blocker right now rather than reviewer attention. Are you still planning to pick this back up? If not, would you mind saying so, so someone else can carry the same four commits forward rather than leaving it stalled?

State of things as of today, for whoever looks next: main still has no build.target, all three lookbehinds are still present, patches/ is unchanged, and 0.5.22 and 0.5.23 both shipped without the fix.

Hi @aurelie-brebant yes I'll revisit this tomorrow and get it up-to-date! Appreciate your shared focus on it...

@XIII-EOG I'll see if I can get you a downloadable build tomorrow...

@arielhauter
arielhauter force-pushed the macos12-webkit-lookbehind branch from 8d26fac to 189e502 Compare September 16, 2026 04:31
@github-actions

Copy link
Copy Markdown

🔐 Codex Security Review

Status: review required for the current range.

The current range is c507a4d488ca27796e78d876b9c24ee38442cc1b...189e5024549749f8734db9b44e5a9af34e5dd7ea.
A new review must complete for this exact range. When manual authorization
is required, a Block organization member must comment exactly
@buzz-security-review 189e5024549749f8734db9b44e5a9af34e5dd7ea to authorize a new review.
Any previous review applies only to its recorded range.

@XIII-EOG

Copy link
Copy Markdown

"@XIII-EOG I'll see if I can get you a downloadable build tomorrow..." Hello, sir! Sounds perfect, waiting forward. Huge thank you!!!

@arielhauter

arielhauter commented Sep 16, 2026 •

Copy link
Copy Markdown
Author

@XIII-EOG CI on this PR needs maintainer approval before it runs, so no CI artifact yet. I've published my rebased build as a release on my fork (so you can download the DMG from there, if have any issues, just let me know and I'll send it another way):

https://github.com/arielhauter/buzz/releases/tag/v0.5.23-macos12-webkit-fix

SHA-256 and install steps are in the release notes. It's built from main @ c507a4d plus this PR's four commits, smoke-tested on two macOS 12 Intel machines. Unofficial and unsigned — please verify the checksum, and report back here whether it renders on your 2015 MBP. That's a third hardware confirmation for the maintainers.

@arielhauter

arielhauter commented Sep 16, 2026 •

Copy link
Copy Markdown
Author

@tlongwell-block — tagging you since #748 added the Intel x64 DMG release target and this PR fixes the blank window on the macOS 12 machines that target ships to. It's rebased and conflict-free, with three users waiting on this thread (#3295). Could someone on the team approve the workflow runs so CI can verify it? Happy to make any changes review turns up.

@aurelie-brebant

Copy link
Copy Markdown

@XIII-EOG CI on this PR needs maintainer approval before it runs, so no CI artifact yet. I've published my rebased build as a release on my fork (so you can download the DMG from there, if have any issues, just let me know and I'll send it another way):

https://github.com/arielhauter/buzz/releases/tag/v0.5.23-macos12-webkit-fix

SHA-256 and install steps are in the release notes. It's built from main @ c507a4d plus this PR's four commits, smoke-tested on two macOS 12 Intel machines. Unofficial and unsigned — please verify the checksum, and report back here whether it renders on your 2015 MBP. That's a third hardware confirmation for the maintainers.

Confirmed working. @arielhauter I installed your fork build and the window renders.

Environment

  • MacBook, Intel x86_64, macOS 12.7.6 (21H1320)
  • Buzz_0.5.23_x64.dmg from the fork release, SHA-256 4adb13ff1c54f9df48f5d36f50a06bcae8d38ddfba193942b10f2b5495eb429e
  • Previously: official 0.5.20 DMG, blank window, no UI at all

Before and after, same machine

Official 0.5.20:

keyring unreachable but migration marker present; booting keyring-locked recovery...
repos dir resolved at boot...
media proxy listening on 127.0.0.1:61870
initial render did not commit before reveal timeout

Your build:

buzz-mesh: installed tokio runtime with 8 MiB worker stacks
persisted identity pubkey <redacted>
repos dir resolved at boot...
media proxy listening on 127.0.0.1:56893
buzz-mesh: status report after startup failed: relay unreachable

The reveal-timeout line is gone and the window paints. relay unreachable is unrelated, I have no relay running yet. So this is a third hardware confirmation, independent of your two machines.

Two notes for anyone else on macOS 12

The release notes still read SHA-256: <paste hash>. The placeholder was never filled in, so the verification step the notes prescribe cannot be done. The hash above is what I measured; worth pasting into the release so the next person can check. Four people have downloaded it meanwhile. For what it's worth, a self-published hash next to a self-published binary is weak assurance anyway, but an empty field is worse than an honest one.

Expect a keychain prompt on first launch, "Buzz wants to use your confidential information stored in buzz-desktop". That is expected rather than alarming: keychain ACLs are bound to code-signing identity, and this build is unsigned, so macOS sees a different application asking for the item the signed app created. Choosing Allow (not Always Allow) is enough, and the app then writes persisted identity pubkey instead of falling back to an ephemeral key. So on macOS 12 this build also clears the keyring warning that the official build shows.

For the maintainers

Structural diff of the fork bundle against the official 0.5.20 bundle is two files, Contents/CodeResources and Contents/_CodeSignature/CodeResources, both signing artifacts. Same seven executables, no added binaries, no launch agents. The branch is rebased and mergeable: true; what it still needs is workflow authorization so CI can run, and a review.

@XIII-EOG

Copy link
Copy Markdown

@XIII-EOG CI on this PR needs maintainer approval before it runs, so no CI artifact yet. I've published my rebased build as a release on my fork (so you can download the DMG from there, if have any issues, just let me know and I'll send it another way):

https://github.com/arielhauter/buzz/releases/tag/v0.5.23-macos12-webkit-fix

SHA-256 and install steps are in the release notes. It's built from main @ c507a4d plus this PR's four commits, smoke-tested on two macOS 12 Intel machines. Unofficial and unsigned — please verify the checksum, and report back here whether it renders on your 2015 MBP. That's a third hardware confirmation for the maintainers.

Hi! First of all, thank you so much for your help. You solved a problem that seemed impossible for me to get past.

This was my first time using GitHub, and somehow I found the right place to ask for help straight away—and then someone actually made a patch specifically to help me. That feels like such an unlikely and amazing chain of events. Honestly, it’s incredible. Thank you!

The patched build installed successfully, and the app now opens. However, when joining eo-team, I got this error on the “Joining eo-team” screen:

AbortSignal.timeout is not a function

I’m using an Intel Mac running macOS Monterey 12.7.6.

@arielhauter

arielhauter commented Oct 18, 2026 via email •

Copy link
Copy Markdown
Author

matt2e added a commit that referenced this pull request Nov 5, 2026
…9094)

## Summary

Re-entering a channel trims older pages from the head of the cached
timeline, and our Virtua patch compensates for each trim from the live
DOM `scrollTop`. When the reader was near the floor, the browser had
already clamped `scrollTop` to the shorter list, so the removal was
counted twice and the reader could be thrown to the top of history. That
is the `message-target-highlight.spec.ts` flake in Desktop Smoke E2E.
The patched scroller now remembers the last offset it observed or wrote,
and compensates from that offset when the live one was clamped to the
floor.

### fix(desktop): compensate head trims from the unclamped scroll offset

#### Problem

`message-target-highlight.spec.ts:79` ("message target flashes: ...")
flaked in Desktop Smoke E2E, always at :179: the linked row had left the
viewport 2.2 s after it flashed.
- main:
-
https://github.com/block/buzz/actions/runs/41568206043/job/125695139845
-
https://github.com/block/buzz/actions/runs/41669669478/job/126035103096
-
https://github.com/block/buzz/actions/runs/41669740241/job/126035480466
-
https://github.com/block/buzz/actions/runs/41694874145/job/126123865714
-
https://github.com/block/buzz/actions/runs/41728489172/job/126240193479
-
https://github.com/block/buzz/actions/runs/41737019256/job/126269365379
- branches:
-
https://github.com/block/buzz/actions/runs/41575774131/job/125719148353
-
https://github.com/block/buzz/actions/runs/41636043464/job/125918005733
-
https://github.com/block/buzz/actions/runs/41751914119/job/126319694104
-
https://github.com/block/buzz/actions/runs/41544910958/job/125618626645
-
https://github.com/block/buzz/actions/runs/41714110046/job/126190515793

All eleven runs predate #8742, and each failed in a case that picks
`mounted[2]`: the bottom-settle mode #8742 fixed by holding the target.
Run 41694874145 also failed once in "other channel, row near the
bottom", and that mode survives #8742: on current main it fails 40 of 60
local runs with tracing on, as CI's retries run.

#### Cause

Re-entering a channel refreshes its newest window, which drops the older
pages from the head of the cached timeline. #8660 passes that head trim
to Virtua as a `shift`, and Virtua compensates by the removed height in
a layout effect. Our Virtua patch (#2855) applies shift compensation to
the live DOM `scrollTop` so that corrections landing before the next
scroll event accumulate. But by then the browser has laid out the
shorter list and clamped `scrollTop` to the new floor whenever the
reader was within the removed height of it, so the removal is counted
twice. In a failing run the view sat 6px above the floor: trimming the
412px channel intro moved it 406px too far, and trimming 3054px of older
rows then wrote `scrollTop` -907, pinning it to the top.
`onStartReached` loaded older history and the target never came back. A
reader scrolled slightly above the bottom loses their place the same
way.

#### Fix

The patched Virtua scroller remembers the last offset it observed in a
scroll event or wrote itself. When a shift compensation finds the live
offset below that and pinned to the floor, the browser clamped it, so
the compensation starts from the remembered offset. Otherwise it keeps
#2855's live-offset base. The ESM and CJS builds change identically, and
the lockfile carries the new patch hash.

#### Validation

- New `virtuaShiftClampPatch.jsdom-test.mjs` drives the real patched
`VList` over a scroller that clamps like a browser. Head trims far from,
near and at the floor keep the reader's rows in place, a trim keeps a
scroll whose event has not fired yet, and a prepend then a trim before
any scroll event accumulate. Each part of the new condition fails a case
when removed: the previous patch line fails the near-floor, at-floor and
accumulation cases (3500 for 3900 and 4000, 3000 for 4000); upstream's
store-offset base fails the unreported-scroll and accumulation cases;
always using the remembered offset, or dropping the floor check, fails
the unreported-scroll case (2500 for 1900).
- "other channel, row near the bottom" with `--repeat-each=30
--workers=4 --trace=retain-on-failure`: 40/60 failed at :179 on current
main, 0/60 failed (60/60 passed) with the fix, and 25/30 failed again
with the previous patch reinstalled.
- All five cases, 20 traced runs each: 100/100 passed.
- scroll-history, virtualization, timeline-no-shift,
overscroll-boundary, dm-history-live-regression and
live-broadcast-reply-timeline, 3 runs each: 117/117 passed.
- Full smoke project: 1485 passed, 3 skipped, 2 failed.
`mentions.spec.ts:807` passed 5/5 on rerun. `messaging.spec.ts:2785`
(own-message avatar) fails the same way on a current-main build here
(3/3).
- `pnpm test`: 6933 unit and 176 jsdom tests passed.

**Rebased onto origin/main `64d12e297` (this branch):** Linux
workstation.

- `pnpm install --frozen-lockfile` installs virtua with the new patch
hash (`f4df71c39f6c...`).
- `pnpm typecheck` and `biome check --error-on-warnings` on the new
jsdom test are clean.
- Unit tests under `src/features/messages` and `src/features/channels`:
1990/1990. jsdom tests there: 12/12. Full desktop `pnpm test`: 6933 unit
and 176 jsdom tests passed.
- Playwright smoke after `pnpm build:e2e`, `--workers=4 --retries=0`:
messaging, send-channel-binding, scroll-history, virtualization,
timeline-no-shift, overscroll-boundary, live-broadcast-reply-timeline
and message-target-highlight, 137 passed and 1 failed. The failure is
`messaging.spec.ts:2785` (own-message avatar), which fails the same way
on main here, as noted above. `dm-history-live-regression.spec.ts` isn't
in the smoke project, so this run left it out.
- `message-target-highlight.spec.ts`, `--repeat-each=10`: 50/50 passed.
"other channel, row near the bottom", `--repeat-each=30`: 30/30 passed.

## Coordination

No open PR edits `patches/[email protected]`. The overlaps are all in
`pnpm-lock.yaml`:

- #5547 ("fix(desktop): ship a bundle that parses on WebKit without
lookbehind (Intel/macOS 12)") and #7074 ("chore(deps): update react
monorepo (patch)") merge cleanly with main but conflict with this branch
on the `[email protected]` patch-hash lines. #5547 adds a patched dependency
next to the virtua entry, and #7074 changes the React peer versions in
the same lines. Whichever lands second resolves it by taking both sides'
package changes and re-running `pnpm install` so the lockfile records
`patch_hash=f4df71c39f6c...` for virtua.
- #7073, #7080, #7633, #8218 and #8643 also touch `pnpm-lock.yaml` and
merge cleanly with this branch. Older open PRs that touch it already
conflict with main, and none gains a new conflict from this branch.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Signed-off-by: mtoohey <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>

This branch has not been deployed

No deployments
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.

3 participants