Repository navigation
feat!: rebuild as a hybrid shell around the web Console (v2.0) - #144
Conversation
Business UI moves to the Console served by the configured ObjectStack server; the app becomes a native shell that hosts it. The native metadata renderer, its 90+ data hooks, offline store and the @objectstack/client dependency are removed (preserved on the legacy/native-renderer branch). Shell skeleton: - server picker (host / URL / pasted Console link, https for public hosts) - long-lived Console WebView with cookie session, loading cover, offline + server-error pages, auto-retry on reconnect, process recovery - navigation policy: in-app browser for other sites, OS for mailto/tel, dangerous schemes blocked, trusted IdP hosts - Android back / iOS swipe-back through Console history - route ownership seam + native-screen registry (all routes web today) - bridge v1 (window.ObjectStackNative): getPushToken, haptic, openExternal, navigate, resetServer, ready/routeChanged events, safe-area CSS variables; origin-checked and argument-validated - native device push token (APNs/FCM) + tap routing to Console routes - app links: objectstack://open?route=… and objectstack://console/… - Sentry init, shell i18n (en/zh/ar), lint CI on pull requests Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q4EjjRHkJgnuLkU9vVgHHR
…bView by remount Review round on the hybrid shell: - One route gate, safeConsoleRoute (lib/shell/console-url.ts), now used by app links, push routes, bridge navigate, routeFromConsoleUrl and consoleUrl. It refuses ./.. segments (raw, percent-encoded in any case, double-encoded), encoded separators, backslashes, control characters and a leading //, so no entry point can build <server>/_console/../../api/... - parseUrl drops the scheme's default port, so normalizeServerUrl, originOf, routeFromConsoleUrl, the navigation policy and the bridge's origin check agree with the URLs WebViews report. The server picker adopts the origin a redirected probe ended on (apex -> www, http -> https) via resolveServerUrl / serverUrlAfterRedirect. - routeFromResponse reads content.data, then the iOS trigger.payload and its body: expo-notifications exposes only userInfo["body"] as content.data on iOS. PUSH-NOTIFICATIONS.md states the APNs and FCM payload contract. - ConsoleWebView: a failed load (onError, then onLoadEnd) no longer marks the page loaded or flushes the pending route; routes requested while the failure page is up are queued; retry re-creates the WebView at the pending route, else the last Console route. A dead render/content process re-creates the WebView (new key) instead of reload() on a dead instance. The idempotent bootstrap is also passed as injectedJavaScript. - The bridge injects nothing for a call from an untrusted origin; the forbidden_origin code is gone. - redirectIncomingPath adds a nonce so the same link opened twice navigates. - HYBRID-SHELL.md: correct the Android injection description, and record the Android isTopFrame and old-WebView iframe message-origin limits. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q4EjjRHkJgnuLkU9vVgHHR
…n trusted hosts
- buildNavigateScript(serverUrl, route) now dispatches a cancelable
`objectstack-native:navigate` CustomEvent ({ detail: { route } }) on
window. A Console that handles it with its own router calls
preventDefault() and the bundle is not reloaded; otherwise the script
falls back to window.location.assign(consoleUrl(route)). The route goes
through safeConsoleRoute (an escaping route yields no script) and is
spliced as escaped JSON. Documented under "Shell -> Console events" and
in the Console-side integration list.
- parseHostList drops a default port (:443 / :80), matching parseUrl, so a
trusted host written as idp.example.com:443 matches.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4EjjRHkJgnuLkU9vVgHHR
维护者速读
席位意见:ACCEPT。独立审核的 8 条应修问题已全部修复并带测试;补丁轮把壳发起的跳转改成先交给 Console 的前端路由,避免整页重载。唯一不能代你判断的是真机行为。 席位核对Head-sha:
Generated by Claude Code |
Why
The 1.x native metadata renderer could not keep pace with the platform:
@objectstack/*9.0.1 while the platform ships 18.2.0;client.notifications.registerDevice, which the framework client removed (the/notifications/devicesroutes were never built);kind:'react'(ADR-0081) run real React in the browser and cannot be rendered natively. ADR-0078 also requires every spec property to be live in each renderer, so a second renderer doubles that burden.2.0 turns the app into a native shell around the web Console. Business UI is the Console served by the configured server. The shell does what the web cannot do well. The 1.x renderer is preserved on
legacy/native-renderer.What's in this PR (Phase 1 — shell skeleton)
components/shell/ConsoleWebView.tsx):lib/shell/navigation-policy.ts):mailto:/tel:/ app schemes go to the OS;javascript:/file:/intent:/ top-framedata:are blocked;EXPO_PUBLIC_TRUSTED_HOSTS(e.g. the SSO identity provider) may load in the WebView.lib/shell/route-owner.ts): every route is web today. A route can later move to a native screen with one rule. A rule that names a screen the build doesn't have falls back to web.window.ObjectStackNative):getPushToken,haptic,openExternal,navigate,resetServer;ready,routeChanged;objectstack://open?route=…andobjectstack://console/…open the Console at that route.apps/docssite and the Maestro / E2E suites;@objectstack/client*, better-auth, TanStack Query and the other dependencies only they used.components/uiprimitives, design tokens, haptics, and the biometric / app-lock libraries.docs/HYBRID-SHELL.mdcovering the architecture, navigation policy, route-ownership rules and the bridge contract. README, ROADMAP, push, device testing, deployment and contributing docs are rewritten.lint-typecheck.ymlnow runs on pull requests; its docs-site step is gone.Verification
pnpm run lint(tsc + ESLint): clean.npx jest --ci: 26 suites, 233 tests pass.lib/shellhas about 90% line coverage. The bridge bootstrap script is executed against a fakewindow.npx expo export --platform ios: the Hermes bundle builds.docs/DEVICE-TESTING.md.Not in this PR (follow-ups)
@object-ui/mobileuseNativeHost()and shell mode, and a mobile-width audit of the Console.scan/pickImage/openFile/share, and external IdP sign-in viaASWebAuthenticationSession.The archive tag
native-renderer-finalcould not be pushed from this environment (the git proxy refused tag pushes), so thelegacy/native-rendererbranch serves as the archive.Generated by Claude Code