6 Commits

Author SHA1 Message Date
Innei 1bd6c6c56c 🐛 fix(desktop): stop bundling web SPA artifacts into the renderer dist (#18644) 2026-08-24 15:34:45 +08:00
Innei 2df9829a5e feat(share): extract share pages into a standalone SSR micro app (#18496)
*  feat(share): extract share pages into a standalone SSR micro app

`/share/t/:id` and `/share/page/:id` were client-only routes inside the main
SPA, so a shared link opened on a blank shell and link previews carried the
generic app card. They now live in `apps/share`, built the same way as
`apps/workbench`: a React Router framework-mode app that server-renders on a
Cloudflare Worker for cloud, plus a plain SPA build that Next serves for
self-hosted deployments.

What the server renders is the shell, the hero and a complete meta set — title,
description and the OG/Twitter card — resolved from the route loader through a
per-request tRPC client. The conversation and page-viewer stacks stay on the
client: `clientOnly()` gates them and, in the SSR environment, a resolver stub
cuts every `*.client` module out of the worker graph entirely. Gating the render
alone does not help, because the dynamic import still pulls the graph into the
bundle.

Keeping that worker deployable took three more cuts, each found by tracing the
module graph rather than guessing:

- `@/store/user` is stubbed for SSR — `GroupAvatar` reads the viewer profile,
  and the store drags the model catalog and every provider behind it (39MB→19MB)
- `shiki` and `shiki/wasm` are stubbed for SSR — the `@lobehub/ui` barrel reads
  the bundled-language catalog at module scope, registering ~400 grammars for a
  highlighter the server never runs (19MB→7.6MB, 1.78MB gzip)
- SSR i18n resolves only the namespaces the shell renders; the generic loader
  would register every locale x namespace pair as a worker chunk

The conversation list keeps its `headerSlot` while it shows the skeleton. It
returned early before, so mounting the list to fetch blanked a title the server
had already rendered.

`/share` is removed from the desktop and mobile routers and rewritten by the
Next middleware to `/spa-share/<locale>/…`, mirroring how `/verify` reaches
workbench. The cloud worker is published from lobehub-cloud, which builds this
same app against its business overlay, so this repo ships no deploy workflow
for it.

* 🐛 fix(share): preload the error namespace so the boundary renders translated

The root ErrorBoundary reaches for the `error` namespace, which SSR did not
bundle, so a failed share page rendered `error.title error.desc error.retry`
verbatim. Fetching it on demand does not fix that case: a chunk-load failure is
exactly when the client can never load the dictionary to correct itself.

* 🐛 fix(share): drop the dev template fetch that served the main SPA

The handler asked the main Vite server for `/index.share.html`, which does not
exist at the repo root. Vite answers that with the HTML fallback — 200, main SPA
shell — so `/share/*` in `bun run dev` silently booted an app that no longer has
those routes. Share is developed against its own Vite server, so the branch goes
rather than the file being added.

`spaDevShells.test.ts` guards both directions: every remaining dev template must
exist at the root and point at its own app entry, and share must not reintroduce
one.

* 📝 docs(skill): record the cloud-overlay split in split-micro-app

Share is the second micro app and the first whose rendering code lives in the
lobehub-cloud business overlay, so the skill needed the ownership rules that
decides which repo builds and deploys, and the Vite 8 tsconfig-nearest-importer
landmine that makes an overlay build ship the open-source fallback with a green
build.

Also folds in what the extraction turned up: the full Docker chain, the dev shell
that answers 200 with the wrong app, shiki and @lobehub/ui bundle cuts, the error
namespace, hydration and virtualized-prefill rules, the asset prefix convention,
and the prod promote gate.
2026-08-20 17:53:41 +08:00
Innei a608bd3f1d 🐛 fix(python-interpreter): run the worker as classic, and publish workers where a page can load them (#18277)
* 🐛 fix(python-interpreter): construct the worker as classic, not a module

The worker was constructed with `type: 'module'` while the bundler emits it
as an IIFE, so the declared type never matched the output. It also loads
Pyodide through `importScripts`, and a module worker rejects that call:

    TypeError: Failed to execute 'importScripts' on 'WorkerGlobalScope':
    Module scripts don't support importScripts().

The failure lands inside `init()`, so nothing surfaces until the interpreter
is first used — construction succeeds and the tool then fails on its first
real call.

Verified end to end in a browser against the built worker: Pyodide loads,
`print("hello from", sys.platform)` writes `hello from emscripten`, and
`6*7` returns 42.

*  feat(build): publish web workers to the public root

`new Worker(url)` only accepts a same-origin script — no CORS header lifts
that, unlike a crossorigin script tag. So once the SPA's assets move to an
asset host, workers are the one thing that has to stay behind, served from
the page's own origin.

The build emits them into `app-workers/`, but `copyDirs` is an allowlist and
did not carry the directory across, so the files reached neither `public/`
nor the asset upload and the emitted URL resolved to nothing.

Copy them to `public/app-workers/` rather than into each SPA directory: the
files are byte-identical across variants, so one path keeps one copy and
gives the origin a single prefix to recognise.
2026-08-14 01:28:22 +08:00
Innei 5287fe849f feat: add standalone workbench SPA target (#17786)
*  feat: add standalone workbench SPA target

* ️ perf(verify): stabilize acceptance callbacks to keep CheckList memoized

* 🔧 chore(docker): copy workbench SPA assets into the runtime image

Mirror the existing public/_spa copy for public/_spa-workbench so the
workbench bundle is present in the runtime image regardless of Next.js
output file tracing.
2026-08-02 17:59:57 +08:00
Innei 71532e1ba7 🐛 fix: ship devtools chunks in production and stop DevDock load failure crashing the app (#17814)
* 🐛 fix: ship devtools chunks in production and stop DevDock load failure crashing the app

* 🐛 fix: stop paired chunk-error events from looping the auto-reload

A failed dynamic import surfaces as vite:preloadError, the rethrown unhandledrejection and the router ErrorBoundary render, all carrying the same Error. notifyChunkError now dedupes by error identity and keeps the sessionStorage reload guard monotonic, so a persistently missing chunk stops at the toast instead of reloading forever.
2026-08-02 15:18:20 +08:00
Innei 93213a51fb ️ perf: make SPA startup demand-aware (#17577)
* ️ perf(vite): make route chunk warmup demand-aware

* ️ perf(spa): preload routes through shared lazy loaders

* ⬆️ chore(deps): align UI editor and Shiki versions

* ️ perf(pwa): cache lazy code on demand

* ️ perf(spa): reduce initial JavaScript dependency closure

* ️ perf(desktop): simplify renderer startup

* ️ perf: refine imperative modal loading boundaries

* 🐛 fix(ci): restore production SPA startup

* ♻️ refactor: share deferred PWA cache rules

* 🐛 fix(ci): execute SPA copy entry with Node
2026-07-29 15:00:26 +08:00