Conversation
…ead WebView PR #16 stopped notification taps silently doing nothing, but routed first-party destinations into the in-app WebView, and on device that produced a blank screen stuck on "Still loading...". The WebView never issued a request. Its own navigation policy in WebViewComponent.onShouldStartLoadWithRequest returns false for any canonical-origin URL whose route is not 'native', with the comment that such pages "must not fall through to an unauthenticated Discourse/PWA session". That guard is correct: the WebView has no authenticated Discourse session. The OTP that establishes one is issued only as part of an ASWebAuthenticationSession authorization callback, and Discourse core exposes no on-demand OTP route - config/routes.rb has no /user-api-key/otp, and the one_time_password scope maps to no routes at all. So loading these destinations would show a login wall, which is exactly what the guard prevents. Rather than navigate into that dead end or loosen the guard, a recognised first-party destination with no native screen now tells the member plainly: "Not available in the app - This notification links to a page the app cannot open yet. It has been marked as read, and nothing else is affected." with a single Close. No loading state, no login wall, no external hand-off, no off-origin navigation. The presentation decision moved into a pure notificationDestination module so every branch is directly testable rather than asserted by reading source, and openUrl branches on the resulting kind. Destinations stay classified as first_party_web, so a proper destination can be added later without re-deriving the routing. Native Topic and MemberProfile routing is unchanged, staff /admin still hands off externally, and malformed, unknown, off-origin, unauthenticated and non-staff admin destinations remain denied with nothing loaded. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fk48MTrNBBSZeLvcJmc8SR
Owner
Author
|
Superseded by #22. This PR's bounded unavailable state is absorbed into the unified intent model as the class-C destination, alongside native Topic/MemberProfile routing and the new native BadgeEarned screen. Closing in favour of the single implementation. |
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.
Backlog #9 follow-up. Fixes the destination-render FAIL found on device in PR #16. JS-only.
What was wrong
PR #16 correctly stopped notification taps from silently doing nothing, but routed first-party destinations into the in-app WebView. On device that produced a blank screen stuck on "Still loading...".
The WebView never issued a request. Its own policy blocks the load:
That guard is correct. The WebView has no authenticated Discourse session, and there is no way to establish one on demand:
config/routes.rbexposes no/user-api-key/otp— onlynew,create,device,device/poll,activate,device/authorize,device/deny,revoke,undo-revoke.UserApiKeyScope::SCOPESmapsone_time_password: []— no routes at all; it is checked inside the authorization flow, so the OTP exists only as a side effect of anASWebAuthenticationSessioncallback.So loading these destinations would show a login wall — precisely what the guard prevents. No server OTP bridge is being built here.
Fix
A recognised first-party destination with no native screen now shows an explicit state instead of navigating:
No loading state, no login wall, no external hand-off, no off-origin navigation. Destinations remain classified
first_party_web, so a real destination can be added later without re-deriving the routing.The presentation decision moved into a pure
js/notificationDestination.jsso every branch is directly tested rather than asserted by grepping source — that gap is what let the PR #16 defect through.Covered classes
granted_badge, group_message_summary, liked_consolidated, membership_request_accepted, chat mention and chat message — all present the identical state, and
opensWebView()is asserted false for each.Preserved
Native Topic and MemberProfile routing unchanged (12 topic types tested). Staff
/adminstill hands off externally; non-staff denied. Malformed badge payloads, unknown types, off-origin, look-alike hosts, plain HTTP and unauthenticated callers all denied with nothing loaded. Notification read-marking still precedes destination resolution.Validation
validate:system: format/lint clean, 96 suites / 786 tests (+31),verify:ota17/17,verify:ios-auth12/12, native Release BUILD SUCCEEDED.js/notificationDestination.js(new),js/Discourse.js(+35/−17), tests. Noios/,android/,vendor/, config, dependency or lockfile change. Runtimean-ios-android-1.0.0-native-2unchanged.One pre-existing PR #16 test asserted the old
route.dispositionshape insideopenUrl; it now asserts the refactoredpresentation.kindform plus thatopenUrlnever navigates to the WebView.