Community notification choices now use `pushNotificationsEnabledV2`,
defaulting ON when absent. This gives every installed community a fresh
enabled preference, including communities previously disabled.
Subsequent explicit OFF choices persist per community through reloads
and reconnects. Previous values are deliberately discarded, not migrated
or preserved. There is no migration or compatibility read.
Enabled communities enter the existing APNs enrollment and active relay
lease publication path when connected to a push-capable relay. iOS still
controls notification display authorization. Notification eligibility
and lease addressing are unchanged.
Related work: [#8425](https://github.com/block/buzz/pull/8425).
### Validation
- Extended the settings integration test to load a saved community with
the previous preference OFF and no new preference and verify native
enrollment plus decrypted active lease publication, then OFF across
reconnect and ON again.
- The focused model test proves both previous values are ignored and
new-key OFF wins even when the previous key says ON. A temporary
compatibility-fallback mutation failed this test as expected; the
implementation was restored unchanged.
- Added storage coverage for independent community choices across
reloads; updated existing model round-trip coverage for the new key.
- Mobile dependency setup, formatting, analysis, and the full mobile
suite passed, including gateway-unconfigured coverage.
- Agent review found no blockers. Enrollment coverage uses the real
Flutter bootstrap, storage, signing, and lease construction with mocked
native and relay boundaries; no physical-device or live APNs delivery
test was run.
The fresh enabled preference is an intentional product exception to the
general zero-notification default in VISION.md. Subsequent user choices
and existing eligibility filters remain authoritative.
<!-- buzz-review-completed -->
---------
Signed-off-by: Tom Brow <tomb@block.xyz>
Main CI has been red since c09730e87 (#8223). The Mobile and Security
checks fail on main, and Docker image fails because it requires a green
CI run for the same commit. Locally, the pre-push Rust lane also failed
on load-sensitive `buzz-acp` timing tests. This PR fixes all three so
main passes locally and on GitHub.
- **Mobile:** #8223 was tested against a base from before #8206, which
removed `buzzDevPushAppProfile` and app-profile selection. Its new
`notification_toggle_test.dart` still put `appProfile` in the native
grant map, so the merged tree doesn't compile. This removes the stale
field.
- **Security:** cargo-deny now reports
[RUSTSEC-2024-0449](https://rustsec.org/advisories/RUSTSEC-2024-0449)
(`unsafe-libyaml` is unmaintained), which comes in through `serde_yaml`.
It's an unmaintained notice, not a vulnerability. The `serde_yaml_ng`
fork depends on the same crate, so removing it means migrating YAML
parsing in five crates. Like the existing unmaintained entries, it goes
in `deny.toml` with a reason.
- **buzz-acp test flakes:** the `spawn_script` test helper returned
before bash was running, and the timing tests started their idle clock
right away. Under full-suite load, process startup took longer than the
idle windows. `keepalive_resets_idle_past_deadline`,
`idle_resets_on_stdout_activity` and
`acp_steer_request_omits_expected_run_id_and_carries_session_and_prompt`
failed this way across three pre-push runs. The helper now has the
script print a ready line and consumes it before returning. The
keepalive loops also use brace expansion instead of `$(seq)`, which
avoids an extra process launch before the first line. The keepalive test
now uses a 250ms idle window with a 750ms floor. Both tests still fail
if keepalives don't reset the idle clock.
### Validation
- `cargo-deny check advisories` passes with this `deny.toml` and fails
on main's.
- The `notification_toggle_test.dart` tests and `flutter analyze` pass.
- `cargo test -p buzz-acp --lib` passes all 1055 tests.
- The `acp::tests` suite passed 20 of 20 runs with 4 concurrent copies
over 5 rounds. Under the same load, the version without the
brace-expansion change failed every run.
- All pre-push hooks pass on 5dbaa4d32: rust-tests, mobile-checks,
desktop-tauri-checks, file-size-check, branch-skew.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <32a2e2c9d428ee08902cab75d956da2c1d235a22d4766b0dd4138bf6e2e5db1d@buzz.block.builderlab.xyz>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Turning Notifications off invalidated the relay capability provider
because it watched the entire saved community. If the redundant NIP-11
request stalled or failed, the switch became disabled and reopening
Settings did not recover it. Discovery now follows community identity
and connection status, so preference and lease bookkeeping retain the
validated capability while a real reconnect still refreshes it. Each
opt-in also starts a fresh active lease immediately instead of waiting
for the old renewal timer. Failed attempts retain their gate until the
retry delay expires, preventing rebuilds from bypassing backoff.
Notifications now has the same single-line height as Set status, with
the full switch hit area and one combined accessibility label. This
follows [#8201](https://github.com/block/buzz/pull/8201); no duplicate
open PR was found.
### Validation
Added iOS widget regressions mount the production settings page and push
bootstrap with production storage logic, signing, and lease publication.
They verify a higher-generation active replacement after off/on with
accepted, failed, and pending cleanup, plus closing/reopening Settings,
switch and row taps, 2× text, accessibility, and rediscovery on
reconnect. A gate test verifies that rebuilds cannot bypass failure
backoff. Both UI failures and the missing active replacement were
reproduced before their fixes.
Mobile dependency installation, formatting/static checks, 3,234
configured tests, and three unconfigured-build tests passed. Captures
use the real Flutter settings widget at 375pt width, with synthetic data
and mocked native/network boundaries. Native navigation is outside the
capture. No device installation or deployment was performed.
After merge, validate the candidate on iOS: compare Notifications with
Set status, toggle off then immediately on, close/reopen Settings while
off and turn on, and repeat after reconnect. Confirm delivery resumes
after opt-in.
### Layout
Flutter widget captures with synthetic data; native navigation is
excluded.
| Before | After |
| --- | --- |
| 
| 
|
---------
Signed-off-by: Tom Brow <tomb@block.xyz>