Skip to content

feat: add Lynx web platform support with sparkling-web-shell package - #3

Open
Huxpro wants to merge 18 commits into
mainfrom
claude/vue-router-sparkling-nav-7a7fy7
Open

feat: add Lynx web platform support with sparkling-web-shell package#3
Huxpro wants to merge 18 commits into
mainfrom
claude/vue-router-sparkling-nav-7a7fy7

Conversation

@Huxpro

@Huxpro Huxpro commented Jul 12, 2026

Copy link
Copy Markdown
Owner

Add multi-environment build config (web + lynx) to rspeedy so the
playground and template can produce browser-compatible bundles alongside
native ones. Web bundles output to dist/web/ and are served by a new
sparkling-web-shell package that renders them via .

  • Create packages/sparkling-web-shell with @lynx-js/web-core 0.19.8
    and @lynx-js/web-elements 0.11.3
  • Add environments config to playground lynx.config.ts with separate
    distPath for web output
  • Filter web/ dir from native asset copies
  • Add build:web and dev:web scripts to playground
  • Update template app.config.ts with matching environments config

@Huxpro
Huxpro force-pushed the claude/vue-router-sparkling-nav-7a7fy7 branch from 64204a2 to 192bc70 Compare July 12, 2026 20:18
@vercel

vercel Bot commented Jul 12, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
sparkling Ready Ready Preview, Comment Jul 13, 2026 3:49am

Huxpro and others added 16 commits July 12, 2026 23:08
Add multi-environment build config (web + lynx) to rspeedy so the
playground and template can produce browser-compatible bundles alongside
native ones. Web bundles output to dist/web/ and are served by a new
sparkling-web-shell package that renders them via <lynx-view>.

- Create packages/sparkling-web-shell with @lynx-js/web-core 0.19.8
  and @lynx-js/web-elements 0.11.3
- Add environments config to playground lynx.config.ts with separate
  distPath for web output
- Filter web/ dir from native asset copies
- Add build:web and dev:web scripts to playground
- Update template app.config.ts with matching environments config
…olink)

Add web platform support to sparkling-app-cli:
- New `run:web` command: autolink → build web → start web shell in browser
- `--platform` flag on `build` and `dev` commands (web/native/all)
- Web-aware `autolink`: generates `.sparkling/web-autolink.ts` with
  `import 'pkg/web'` for modules declaring web support
- `copy-assets` excludes `web/` subdirectory from native copies
- `doctor --platform web` checks for @lynx-js/web-core and web shell
- Updated types: `web` in PlatformConfig, MethodModuleConfig, AppConfig.paths
…entations

Add a web dispatch layer to sparkling-method so pipe calls route to
browser-native handlers when running in a web environment. Each method
package now exports a ./web subpath that registers handlers at import time.

- sparkling-method: web-registry.ts (registerWebMethod, isWebEnvironment),
  WebMethodHandler type, web intercept in LynxPipe.call() before NativeModules check
- sparkling-navigation/web: router.open (History API + CustomEvent), router.close
- sparkling-storage/web: getItem/setItem/removeItem via localStorage with namespacing
- sparkling-media/web: chooseMedia (file input), downloadFile (fetch+blob),
  uploadFile/uploadImage (FormData), saveDataURL (unsupported stub)
…b option

- Add web-registry to sparkling-method for dispatching pipe calls to
  browser-based handlers when NativeModules is unavailable
- Update sparkling-web-shell with onNativeModulesCall bridge and
  spkPipe Worker stub for web-core RPC
- Fix navigation web handler scheme parsing
- Add CLI run:web command and --platform web flag to dev/build/autolink
- Add web platform docs (guide, method implementations, limitations)
- Add "Enable web platform?" prompt to create-sparkling-app
- Add web sidebar entries to website rspress config
- Rebuild playground native bundles
The playground no longer tracks built .lynx.bundle files on main; the
cherry-picked web-support commits carried old prebuilt bundles from the
original PR base. Remove them again.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARyWgB3243gQx1exWK61Qb
…vigation

A framework-agnostic W3C-History-style shim that lets SPA routers
(vue-router first) drive Sparkling's MPA-style native navigation:

- NavigationHost contract: minimal platform API (initialUrl/open/close/
  visibility) any container stack can implement
- createHybridHistory: RouterHistory-compatible history per container —
  in-container navs behave like vue-router's memory history, boundary
  navs (pushExternal, go(-n) past the queue) walk the native stack
- Route manifest + sparkling scheme codec: build-time file-based route
  table embedded in every bundle so isolated JS heaps agree on
  route->scheme mapping; state/depth/route ride across heaps in the URL
- vue adapter: createSparklingRouter diverts cross-bundle push/replace
  (incl. named routes) to native opens, keeps same-bundle navs local
- codegen: scans src/pages/* convention into a manifest module +
  rsbuild/rspeedy plugin
- memory host: simulates the multi-container stack so vue-router's
  official memory-history suite is ported and cross-heap flows are
  tested with real vue-router instances (45 tests)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARyWgB3243gQx1exWK61Qb
…d globalProps

Web-support improvements split out for a stacked PR onto the web platform
work (tiktok#22). The previous shell destroyed and re-created a
single <lynx-view> on every navigation, which cannot represent Sparkling's
native container stack (no state preservation on back, no lifecycle, no
globalProps). Now:

- router.open STACKS a new <lynx-view> (own Worker heap); the previous
  view stays alive hidden underneath — router.close / browser back pops
  it and reveals the previous view with state intact
- each view is injected with globalProps (containerID + queryItems parsed
  from its scheme) mirroring the native SDK's GlobalPropsUtils — required
  by anything that reads lynx.__globalProps on web, incl. sparkling-history
- viewAppeared/viewDisappeared GlobalEvents are sent on stack transitions,
  mirroring the native ViewEventUtils contract
- browser history entries carry the scheme stack in state; popstate
  reconciles the view stack (back pops live views, forward re-creates
  fresh heaps, reload rebuilds — matching native process-restore behavior)
- sparkling-navigation web handlers slim down to validated event dispatch
  (sparkling:navigate / sparkling:close); the shell owns stack + history
  bookkeeping like the native SDK owns the activity stack, and the open
  handler now honors the replace option (previously ignored)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARyWgB3243gQx1exWK61Qb
Vue-Lynx demo proving vue-router can drive Sparkling multi-page native
navigation (MPA) via sparkling-history — validated end-to-end on the web
shell harness (26 Playwright checks: local SPA nav, cross-container
stacking, dynamic params, guards, named routes, back across the container
boundary, onRestore events, replace, state hand-off, deep-linking).

- file-based pages (src/pages/*) with per-page routes.json → route manifest
  generated at build time by pluginRouteManifest
- one createPageRouter() per bundle/heap; cross-bundle navs divert to
  sparkling router.open, same-bundle navs stay in-heap

Also fixes sparkling-history: HybridHistory.replace now merges the current
entry's state with the new data, matching vue-router's HTML5 history
(replaceState merges history.state). This preserves the initial cross-heap
state through the router's first-navigation replace(fullPath, { scroll }).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARyWgB3243gQx1exWK61Qb
…uite

- COMPATIBILITY.md: full traversal of Vue Router's guide features + e2e
  examples, each marked supported/reframed/limited for the Sparkling MPA
  model, with the reason and where it's validated
- README.md: layering (host / codec+manifest / history / adapter) and the
  navigation flow across heaps
- vue-features.spec.ts: proves createSparklingRouter leaves vue-router's
  in-heap features intact (dynamic match, nested, named views, redirect,
  alias, guard abort/redirect, afterEach, addRoute) while still diverting
  cross-bundle targets (12 tests)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARyWgB3243gQx1exWK61Qb
The first-session cherry-pick of the upstream web-support commits staged a
few files with unresolved conflict markers (create-app completion notes,
sparkling-media module.config.json, and the CLI / getting-started docs).
Resolve them: merge the web-aware completion notes without duplicating the
p.note output, align sparkling-media's module.config.json with the nav/
storage structure (adds web entry + platforms), and merge the docs so both
the native and web options are documented.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARyWgB3243gQx1exWK61Qb
The web-support cherry-pick added askWebPlatform written against inquirer,
but this fork's create-sparkling-app uses @clack/prompts. Rewrite it with
p.confirm + checkCancel to match the rest of user-prompts.ts (the previous
code referenced undefined inquirer/ui/handlePromptError and failed tsc).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARyWgB3243gQx1exWK61Qb
Move packages/playground-vue -> examples/vue-router-mpa so the go-web
prepare-examples pipeline discovers it and the <Go> component can embed it:

- lynx.config.ts: dual lynx+web environments with default filenames, so it
  emits main/users/settings .web.bundle (browser preview) + .lynx.bundle
  (QR/device), with an HTTP assetPrefix; keeps the route-manifest codegen
- package.json: @sparkling-example/vue-router-mpa, "build" script
- shared/router.ts: boot each bundle at its OWN default route when the
  container URL carries no owned route (the standalone go-web preview case),
  so the users/settings previews render their own pages instead of a route
  another bundle owns
- README: web-preview (live in-heap nav) vs device/harness (full MPA) scope

Also fix the web shell to load the web-target build: sparkling schemes name
the native <name>.lynx.bundle, but web-core needs <name>.web.bundle, so the
shell rewrites the extension when resolving the URL to render.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARyWgB3243gQx1exWK61Qb
New Examples page (en + zh) embedding the vue-router-mpa example via go-web:

- embeds the `main` bundle, where in-heap Home<->Features navigation runs
  LIVE in the web preview (pure Vue Router, no native bridge) — going beyond
  the other demos, whose native calls are inert on the web
- embeds the `users` bundle (dynamic /users/:id), booting at its own default
  route in the standalone preview
- scopes what the web preview shows vs. cross-page MPA navigation, which needs
  the QR/device tab or the local web-shell harness
- maps each Vue Router concept to its Sparkling equivalent and links the
  sparkling-history compatibility matrix

Wired into the Examples overview and the en/zh website sidebars. Verified the
page renders and the <Go> component mounts a live web-core preview of the
vue-lynx bundle.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARyWgB3243gQx1exWK61Qb
After rebasing onto PR#7 (which added the web-method bridge + a pluggable
RouterWebHost), drop the shell's old sparkling:navigate/close event contract
and install its stacked-container behavior via setRouterWebHost instead —
the sanctioned extension point. The host receives the full scheme (all a new
container's heap needs) and drives push/replace/close directly.

Also extend RouterWebHost.open with an optional { replace } (threaded from
router.open's params) so router.replace can swap the current container on web
instead of stacking; the default History-API host honors it via replaceState.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARyWgB3243gQx1exWK61Qb
Verified the example in the go-web <Go> web preview:
- in-heap (SPA) navigation WORKS live (e.g. Home <-> Features) — pure Vue
  Router, no native bridge
- cross-page (MPA) navigation does NOT run: go-web's preview provides no
  Sparkling `spkPipe` bridge and no hook to register a RouterWebHost on its
  <lynx-view>, so `router.open` fails ("Native module spkPipe is not
  registered"). The same example cross-navigates correctly in the web-shell
  harness, which owns the page and installs the host + bridge.

Home.vue now awaits cross-navigation and, when no native host is available,
shows an inline notice instead of failing silently — so the limitation is
visible in the <Go> preview. Docs (en/zh) updated to state precisely why
in-heap works but cross-page needs device/harness.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARyWgB3243gQx1exWK61Qb
…MpaPreview>)

go-web's <Go> previews one bundle with no native bridge, so cross-page
(MPA) navigation can't run in it. This adds a working, no-go-web-change
alternative: <MpaPreview> opens the Sparkling web shell in a NEW TAB, where
the full router.open -> new native container flow runs live in the browser.

A new tab (not an inline iframe) is deliberate: the shell owns the whole
page, so the browser's own back/forward buttons drive its MPA history stack,
instead of being trapped inside a frame.

- sparkling-web-shell: load example bundles from a runtime `?base=<url>` (so
  one static build serves any example), and build with a relative assetPrefix
  (WEBSHELL_ASSET_PREFIX=auto) so it can be dropped under any deploy base;
  publicDir is dev-only (copyOnBuild: false) so the static build carries no
  example bundles.
- website: scripts/build-mpa-preview.mjs builds the shell into
  public/mpa-preview/ (best-effort; wired into dev/build via prepare-previews).
  New global <MpaPreview> component renders a launch card + new-tab link
  (withBase). public/mpa-preview/ is gitignored (generated).
- docs (en/zh): the Vue Router example's cross-page section links to the live
  demo and explains the browser-back behavior.

Verified in a browser: from the launch link, tapping push('/users') stacks a
second <lynx-view> (1 -> 2), the top container is the users bundle at depth 1,
and the browser BACK button pops it back to main — real MPA on the web.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARyWgB3243gQx1exWK61Qb
The web shell swapped <lynx-view> containers instantly. Add native-style
page transitions: on router.open a new container slides in from the right
(iOS-like ease-out); on router.close / browser back the top container slides
back out, revealing the one beneath. Replace does a cross-fade. Honors
prefers-reduced-motion (and can be disabled per-navigation).

- sparkling-navigation web host: thread `animated` through RouterOpenOptions
  and a new RouterCloseOptions (read from the router.open/close payload,
  mirroring the native `animated` option). Undefined lets the host default.
- sparkling-web-shell: transform-based slide transitions in push/pop and in
  the browser back/forward reconciler; the shell animates by default
  (reduced-motion aware). No example changes needed — cross-page navigation
  animates out of the box.

Verified in a browser: tapping push('/users') slides the new container in
(sampled transform 100vw -> 0 with deceleration), it settles to none, and the
browser back button slides it out and pops the stack.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARyWgB3243gQx1exWK61Qb
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants