3 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
| 3a3db4efa6 |
feat(robius-notification): iOS via UNUserNotificationCenter, and the Apple/Windows logic comes out of the cfg blocks
Some checks failed
repo hygiene / hygiene (push) Has been cancelled
doc-engine / engine (push) Has been cancelled
doc-engine / coverage (push) Has been cancelled
doc-engine / consumer (push) Has been cancelled
email / gates (push) Has been cancelled
email / email-domain (push) Has been cancelled
email / nigig-email (push) Has been cancelled
email / supply-chain (push) Has been cancelled
nigig-map / test (push) Has been cancelled
p2p-intel / engine (push) Has been cancelled
p2p-intel / notifications (push) Has been cancelled
p2p-intel / coverage (push) Has been cancelled
p2p-intel / makepad-app (push) Has been cancelled
p2p-intel / exchange-tab (push) Has been cancelled
sms / gates (push) Has been cancelled
sms / robius-sms (push) Has been cancelled
sms / android (push) Has been cancelled
sms / nigig-sms (push) Has been cancelled
sms / supply-chain (push) Has been cancelled
spreadsheet / engine-coverage (push) Has been cancelled
spreadsheet / ui-controller-coverage (push) Has been cancelled
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
nigig-build (CAD) / cad-engine-coverage (push) Has been cancelled
nigig-build (CAD) / doc-workspace-coverage (push) Has been cancelled
nigig-build (CAD) / cad-widget-coverage (push) Has been cancelled
Two gaps remained on the Apple and Windows side after the last commit, and
they are different in kind.
The first was a missing backend. iOS returned PermanentlyUnavailable with a
note saying it was not implemented. It now uses UNUserNotificationCenter --
the only option there, since every iOS process is bundled, so the
Objective-C exception that rules that API out on macOS cannot occur. The
objc2-user-notifications bindings are real and fetchable, so this
type-checks for aarch64-apple-ios against the actual framework, and the
module was confirmed genuinely reachable by injecting a type error and
watching the target build fail.
Its is_available() returns a constant false, and that is a limit worth
naming rather than burying. The real answer comes from
getNotificationSettingsWithCompletionHandler:, which is asynchronous -- it
hands the settings to a block on an arbitrary queue. A synchronous
is_available() could only produce that by blocking on a completion handler,
which on the main thread is a deadlock rather than a delay. Returning false
errs toward telling the user delivery is unverified; the alternative is
claiming an availability the platform never confirmed, which is the whole
failure this crate was written to remove. A correct answer needs an async
entry point, which is a change to the public API rather than a bug fix, so
it is recorded in the ADR instead of being quietly wrong.
The second gap is subtler and, I think, the more valuable fix. The Apple and
Windows backends contain pure logic -- XML escaping, tag clamping -- that
was sitting inside #[cfg(target_os = "windows")], where cargo test on the
only available machine could never reach it. Those functions had zero tests
and no prospect of any.
They now live in src/payload.rs, which is cfg-free and runs on every target.
Eleven tests cover them, and they cover exactly the rules a compiler cannot:
an unescaped `&` in a merchant name makes the toast XML malformed and
Windows discards the whole notification rather than showing a mangled
character, and the Binance P2P book is full of `&`. A byte-wise truncation
of the 64-character tag limit panics outright on the full-width names that
book also contains -- so the clamp cuts on character boundaries, pinned by a
test that would panic if anyone changed it back.
One test pins something deliberately counter-intuitive: escaping is not
idempotent, and escape_xml("&") is "&". That is correct, and the
test exists so nobody "fixes" double-escaping by teaching the function to
detect already-escaped input, which is precisely how escaping filters grow
holes.
The distinction this commit is really about: the last one made the Apple and
Windows backends *compile*, which catches wrong selectors -- it found
CreateToastNotifier(&HSTRING), which does not exist. It could not catch
wrong behaviour. Extracting the logic is what makes the behaviour testable
on a machine that will never run either platform.
62 tests, up from 51. Clippy clean with -D warnings on all five targets:
linux-gnu, linux-android, apple-darwin, apple-ios, windows-msvc. p2p-intel
unchanged at 225.
|
|||
| 3ef182414e |
feat(robius-notification): real macOS and Windows backends, type-checked against the actual frameworks
Some checks failed
repo hygiene / hygiene (push) Has been cancelled
p2p-intel / engine (push) Has been cancelled
p2p-intel / notifications (push) Has been cancelled
p2p-intel / coverage (push) Has been cancelled
p2p-intel / makepad-app (push) Has been cancelled
p2p-intel / exchange-tab (push) Has been cancelled
The previous commit refused to write these two, on the grounds that code no compiler has ever seen is not an implementation -- it is plausible-looking text that would sit in the same crate as tested code and be read as equally finished. That reasoning holds. The premise behind it did not. `cargo check` needs the *target's standard library*, not a linker or a platform SDK. `rustup target add x86_64-pc-windows-msvc aarch64-apple-darwin` puts the real `windows` and `objc2` crates -- genuine WinRT metadata, genuine Objective-C class definitions -- in front of the type checker on a Linux host. So both backends are now written and both compile against the frameworks they call. Compile-checking earned its place immediately. The Windows backend was written against `ToastNotificationManager::CreateToastNotifier(&HSTRING)`, which does not exist: the AUMID overload is `CreateToastNotifierWithId`. Nothing short of a compiler holding the real metadata would have caught that, and it would have shipped looking entirely correct. Because a cfg-gated module can silently compile to nothing -- leaving a green check that proves only that the module was skipped -- each backend was verified to be genuinely reachable by injecting a type error and confirming the target build failed. Both macOS and Windows were checked this way, then restored. Two platform decisions worth recording. macOS uses NSUserNotification, not UNUserNotificationCenter, and that is a deliberate downgrade to a deprecated API. `UNUserNotificationCenter.current()` raises an Objective-C exception when the process has no bundle identifier; that unwinds through Rust frames and aborts. A plain `cargo run` host has no bundle, so the modern API would crash the caller instead of reporting unavailable -- which is worse than deprecated. The nil check on `defaultUserNotificationCenter` is there for the same reason: msg_send on nil returns zero rather than crashing, so every later call would silently do nothing, which is precisely the failure this crate exists to remove. Windows requires the host to supply an AppUserModelID, because a library cannot invent one. It comes from an MSIX manifest or a Start Menu shortcut, and a fabricated id produces a notifier that constructs happily and then fails at Show. `set_app_user_model_id` is therefore public, a no-op off Windows so portable hosts call it unconditionally, and `is_available()` is false with a reason naming exactly what is missing until it is called. Toast payloads are XML, so text is escaped -- the same bug class as the Telegram MarkdownV2 escaping p2p-intel needed before it dropped Telegram, and a merchant nickname containing `&` is not hypothetical. The support table now has two columns, "compiles" and "executed", because they are different claims. All four backends compile; only Linux has posted a notification. iOS remains unavailable by design: it needs UN* with a bundle and an entitlement, which is an app-packaging concern rather than something this crate can satisfy. Off Windows the module defines no entry points at all rather than stubs. Clippy was right to call them dead: sys/mod.rs dispatches elsewhere, so they existed only to satisfy a symmetry nothing needs. CI gains a step that clippies all four cross-targets with -D warnings, so a cfg-gated backend cannot rot unnoticed -- which is exactly how the CreateToastNotifier mistake would have survived. 51 tests, clippy clean on five targets, p2p-intel still at 225. |
|||
| e16d1d1d44 |
feat(robius-notification): a zero-dependency D-Bus notifier, and p2p-intel's last limit closes on Linux
Some checks failed
p2p-intel / engine (push) Has been cancelled
p2p-intel / notifications (push) Has been cancelled
p2p-intel / coverage (push) Has been cancelled
p2p-intel / makepad-app (push) Has been cancelled
p2p-intel / exchange-tab (push) Has been cancelled
repo hygiene / hygiene (push) Has been cancelled
sms / gates (push) Has been cancelled
sms / robius-sms (push) Has been cancelled
sms / android (push) Has been cancelled
sms / nigig-sms (push) Has been cancelled
sms / supply-chain (push) Has been cancelled
doc-engine / engine (push) Has been cancelled
doc-engine / coverage (push) Has been cancelled
doc-engine / consumer (push) Has been cancelled
spreadsheet / engine-coverage (push) Has been cancelled
spreadsheet / ui-controller-coverage (push) Has been cancelled
email / gates (push) Has been cancelled
email / email-domain (push) Has been cancelled
email / nigig-email (push) Has been cancelled
email / supply-chain (push) Has been cancelled
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
nigig-build (CAD) / cad-engine-coverage (push) Has been cancelled
nigig-build (CAD) / doc-workspace-coverage (push) Has been cancelled
nigig-build (CAD) / cad-widget-coverage (push) Has been cancelled
nigig-map / test (push) Has been cancelled
ADR 0035 left one of p2p-intel's four limits open: no OS notification backend, so alerts stopped when the window closed. The seam existed with one implementation that truthfully did nothing. This is the crate that fills it. Posting a notification on Linux is one D-Bus method call. Three ways to make it were measured rather than assumed: notify-rust with libdbus is twelve crates but a C library, which needs pkg-config and breaks the Android and iOS cross-compile this crate family keeps clean; notify-rust with zbus is pure Rust and 169 crates including an async executor; writing the wire format out is about 250 lines and nothing at all. robius-sms deleted polkit and gio for exactly this reason -- its E9 note records they were the sole source of two RUSTSEC advisories and an LGPL question for every consumer -- so pulling a 169-crate tree back into the same family for one method call would reverse that decision for a worse reason. The cost of hand-rolling is that the protocol has to be exactly right, and a mistake makes the daemon disconnect with no diagnostic. That cost was paid in tests: the suite starts a private dbus-daemon per test and talks to it. This matters more than it sounds. The marshaller and the parser were written from the same reading of the specification, so them agreeing with each other proves only that I was consistently wrong or consistently right; only a third party can say which. It found three bugs no unit test would have. The first is the one worth dwelling on. Every error reply parsed as success. The header-field walk assumed all fields were strings, but REPLY_SERIAL is a u32, and reading its four bytes as a string length desynchronised the cursor so ERROR_NAME was never reached. `post` returned Ok against a bus with no notification service running. That is precisely the bug this crate was written to eliminate -- a notifier that reports success and delivers nothing -- reintroduced by accident inside its own parser. I cannot think of a stronger argument for testing against something you did not write. Second, is_available() was true on a bare bus, because NameHasOwner *succeeds* and answers false in its body; checking only for an error reported a working notifier on a machine with no notification daemon. Third, replies were not correlated. The bus sends NameAcquired unprompted right after Hello, so "read the next message" consumed a signal and treated it as the answer. Replies are now matched on REPLY_SERIAL, and a single read carrying several messages is walked rather than truncated. The suite also serialises every test that mutates DBUS_SESSION_BUS_ADDRESS behind a mutex. The variable is process-wide and cargo runs tests in parallel threads; three consecutive parallel runs are now green. --test-threads=1 would have made the failures go away too, and would have hidden a real hazard from whoever reads the file next. On the four platforms, honestly. Linux is implemented and tested. Android is implemented and *compiles* -- cargo check and clippy both pass for aarch64-linux-android -- but has never run on a device, and the module says so in its first paragraph. It handles the two things Android drops silently, missing POST_NOTIFICATIONS on API 33+ and a missing channel on API 26+, because both are the same accepted-and-discarded failure this crate exists to remove. Apple and Windows are deliberately not written. Neither could be compiled here -- no macOS or Windows toolchain and no way to add one -- and objc2 message sends or WinRT calls that no compiler has ever seen are not an implementation. They are plausible-looking text that would sit in the same crate as tested code and be read as equally finished. Both return PermanentlyUnavailable with a reason naming ADR 0036, and their module docs record the call sequence so the next person starts from a design rather than a blank file. The support table says "written" and "verified" in separate columns for the same reason. p2p-intel's dashboard now uses SystemNotifications instead of UnavailableNotifications. The latter stays: on a platform with no backend it is still the truthful answer, and a test needs something that reliably cannot deliver. Alerts are tagged per fiat so a market replaces its own previous notification rather than stacking -- a 30-second poll would otherwise fill the shade, and a full shade is what makes someone turn notifications off for the app entirely, which costs more than the feature is worth. Two new tests pin the invariant that a sink must never report delivery it did not achieve. CI gains a notifications job that installs dbus and sets ROBIUS_NOTIFICATION_REQUIRE_DBUS=1. The bus-backed tests skip when dbus-daemon is absent so the suite stays green on a bare machine, but a silent skip in CI would mean the integration tests quietly stopped running while the build stayed green. I verified the guard fails by hiding dbus-daemon behind a stub that exits 127. 50 tests in the new crate, 225 in p2p-intel, clippy clean on host and Android, and the app still starts under Xvfb. |