feat(notifications): resolve taps to native intents instead of web sessions - #22
Merged
Aalv3 merged 1 commit intoSep 9, 2026
Conversation
…ssions A notification tap now resolves to an intent computed from the notification payload, before any URL is built: native, badge, staff_external, or unavailable. The payload ordering is the whole point. endpointForSiteNotification is lossy - a granted_badge becomes /badges/:id/basic and badge_name is discarded - so every earlier attempt at badge routing started from a URL that had already thrown away what a native screen needs, and each one ended at a web page. granted_badge opens a small native BadgeEarned screen rendering badge_name from the payload. It makes no network request, so it cannot stall or fail. badge_title is a boolean in Discourse - whether the badge may be worn as a title - and is never rendered as descriptive text; the payload carries no description and V1 does not fetch one. Topic and MemberProfile routing are untouched. Type 21 without a topic resolves to /u/:me/activity/approval-given, which the profile pattern already matched; that is a valid native destination and is left exactly as it was. Staff /admin keeps its external handoff as an explicit documented exception - members get the bounded state, never the handoff. Everything else - group_message_summary, liked_consolidated, membership_request_accepted, chat, unknown types - reaches one explicit unavailable state. No silent no-op. Removes the abandoned authenticated-WebView experiment: js/webViewSession.js, the first_party_web disposition and its allowlist, the destination bootstrap wiring, _openFirstPartyWeb, and the web_session diagnostic stages. The strict WebView navigation guard, relaxed to admit the bootstrap, is restored. The WebView screen itself is untouched where it is legitimately used. The OTP contract was eventually correct - the request succeeded and the confirmation form rendered - but Finish Login failed with "Missing, invalid or expired token", and the architecture was wrong regardless: a member already authenticated in the app should never perform a second web authentication to read a notification. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fk48MTrNBBSZeLvcJmc8SR
This was referenced Sep 9, 2026
Aalv3
merged commit Sep 9, 2026
cf588cd
into
fix/auth-failure-classification-20260830
2 of 4 checks passed
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.
Implements the founder-approved native notification intent model and removes the abandoned authenticated-WebView experiment.
The architectural fix
DiscourseUtils.endpointForSiteNotificationis lossy: agranted_badgebecomes/badges/:id/basic?username=:uandbadge_nameis discarded. Every earlier attempt at badge routing started from that URL, so by the time classification ran, the information a native screen needs was already gone — which is why each attempt ended at a web page.notificationIntentreads the payload first.Four kinds:
native,badge,staff_external,unavailable.Type matrix
granted_badge/adminexternal, staff onlyNote on 21: it resolves to
/u/:me/activity/approval-given, which the pre-existing profile pattern already matched. A valid native destination exists, so it is class A and deliberately untouched — I did not reclassify existing behavior to fit the new model. A test pins this.BadgeEarned
Renders
badge_nameonly, from the payload, with Close. No network request — asserted by a test that fails the render iffetchis called.badge_titleis a boolean in Discourse (may the badge be worn as a title), not descriptive text — the repo's own existing fixture hadbadge_title: false, confirming it. It is never rendered. No badge description exists in the payload and none is fetched.Removed experiment surface
js/webViewSession.js(deleted) ·js/__tests__/webViewSession.test.js(deleted) ·first_party_webdisposition +FIRST_PARTY_WEB_PATHS+isFirstPartyWebPath·_openFirstPartyWeb· destination bootstrap prop/wiring ·web_sessiondiagnostic stages.Strict WebView guard restored —
WebViewComponent.jsandWebViewScreen.jsreverted to their pre-experiment state, so canonical pages without a native route no longer fall through to an unauthenticated Discourse session. The WebView screen itself is untouched where legitimately used.notificationRouting.test.jswas the experiment's own suite and is removed; its still-valid URL-classifier boundary tests are salvaged intomemberRouteBoundaries.test.js.Tests — 781/781 across 96 suites
notificationIntent.test.js(56 tests) proves: granted_badge → badge intent;badge_namesurvives from payload; the URL form would have lost it;badge_titlenever rendered as text; malformed badge payloads fall to bounded state; BadgeEarned renders the name, makes no network request, and closes via both header and action; all 25 topic types unchanged; MemberProfile unchanged; staff/adminstaff-only; members denied it; every class-C type bounded; unknown/absent bounded; unauthenticated resolves nothing; off-origin unavailable;destinationPresentationno longer emitsweb; the experiment files are gone; no intent path can reach a WebView; the strict guard is restored; read-marking still precedes resolution and the tap site no longer builds a URL.Gates
format/lint clean · Jest 781/781 ·
verify:ota,verify:ios-auth,verify:release-readiness,verify:backend,validate:otaall PASS.Per the new CI policy this is authenticated-surface JS work the logged-out Detox suite cannot exercise. It does touch
js/Discourse.js, so the classifier will select the full suite until PR #21 lands and its auth-shell path list is reconsidered.OTA
JS-only, runtime
an-ios-android-1.0.0-native-2unchanged. Not published — awaiting separate approval.