Web Host (@lapis-notes/web)
The web package is the deployable browser/PWA host. It wraps the shared workspace renderer with host-level concerns that should not live inside @lapis-notes/workspace: the HTML document shell, PWA manifest and icon generation, service-worker registration, install affordances, and cross-origin-isolation headers needed by the browser-local SQLite runtime.
Source: packages/web/src/
Responsibilities
- Mounts the shared workspace renderer by importing
mountWorkspaceApp()from@lapis-notes/workspace/components/app. - Shows browser-only host status items in the shared workspace status bar, including the shared app version item, a dismissible install affordance when the browser exposes
beforeinstallprompt, a pending-update affordance that can reopen the host update prompt, and an icon-only offline indicator when the browser network state drops offline. - Owns the browser host document (
index.html), including title, light/dark bootstrap theme-color metadata, installable icon links, and the early telemetry seed object. - Generates the browser-host icon set from the shared workspace
logo.svg, keeping the favicon plus Apple touch and PWA PNG assets in sync. - Supports repo-root Nixpacks builds through
nixpacks.toml, which runspnpm --filter @lapis-notes/web buildso deploy-time builds keep the full pnpm workspace available. - Provides a Docker Hub image path for static Web/PWA hosting as
lapisnotes/web, built from the same repo-root package build and served by a nonroot distroless Caddy runtime on port8080with/healthzfor deployment health probes. The published image version follows the web package CalVer frompackages/web/package.json. - Mirrors the browser-host headers required for SharedArrayBuffer-backed SQLite (
Cross-Origin-Embedder-Policy: require-corp,Cross-Origin-Opener-Policy: same-origin). - Configures
vite-plugin-pwato emit the web app manifest plus a generated service worker for offline startup. - Registers a web-app protocol handler for
web+lapis://where the browser supports installed-PWA protocol handling, routing launches through/open?url=...before the shared renderer dispatches the embedded app URL. - Provides the Web/PWA plugin asset server for verified installed plugin files, mirroring supported JavaScript, CSS, JSON, WASM, and image assets into Cache Storage under version/hash
/__lapis/plugins/[vault-id]/[plugin-id]/[version]/[sha256]/[path]URLs. - Provides a dismissible host-level install affordance in the shared status bar when
beforeinstallpromptfires and hides it after installation, explicit dismissal, or prompt consumption. - Surfaces pending service-worker updates through a host-owned bottom-right prompt with
LaterandInstall Nowactions instead of auto-reloading as soon as the browser detects a waiting worker.
Boot Surface
Entry Point (main.ts)
Reads the shared bootstrap appearance mode from IndexedDB-backed vault state, applies light/dark/system classes to the document root, initializes web-host document metadata, registers Window Controls Overlay handling, starts host theme-color synchronization, mounts the shared workspace app into #app, then calls registerWebPwa().
Web Host Document (pwa-host-document.ts)
- Marks the host document with
data-pwa-host="true"anddata-runtime="web-pwa"before the workspace renderer mounts so PWA title-bar CSS can apply immediately. - Detects installed-PWA display modes and sets platform metadata (
data-os,data-engine) used by the shared shell layout.
Host Theme Color (host-theme-color.ts)
- Resolves the shared
--workspace-chrome-backgroundshell token from the same CSS variable chain used by sidebar and top-tab headers. - Replaces bootstrap light/dark
theme-colormetas with one dynamic meta tag once the host can read computed theme values. - Re-syncs when the document root appearance classes change or when
prefers-color-schemechanges.
Window Controls Overlay (pwa-window-controls.ts)
- Tracks installed-PWA display mode (
standaloneorwindow-controls-overlay) on the document root. - When the browser activates
window-controls-overlay, setsdata-pwa-titlebar-hidden="true"and mirrorsnavigator.windowControlsOverlaygeometry into CSS custom properties so the shared workspace tab header can replace the native title bar on Chromium desktop PWAs. The web host document initializesdata-pwa-titlebar-hidden="false"on boot andpwa-window-controls.tskeeps the attribute explicit ("true"in WCO,"false"in standalone) for everydata-pwa-host="true"document. PWA chrome header borders and the optional:root::beforedivider paint only whendata-pwa-titlebar-hidden="false". - Derives platform-specific safe-area insets from the overlay rectangle: macOS reserves leading space for traffic lights, Windows and Linux reserve trailing space for caption buttons, and all platforms reserve top space for the hidden title bar height. Collapsed title-bar geometry reports height
0and clears the top inset so tabs stay visible instead of being negative-margin clipped. - The shared workspace shell CSS consumes those properties under
html[data-pwa-host="true"]to draw the tab header edge-to-edge while keeping macOS traffic-light clearance. Top tabs and both sidebar dock tab rows remain visibly painted in collapsed and expanded Window Controls Overlay geometry because the sidebar tab buttons use explicit nav-item foreground colors instead of inherited text color; macOS left-dock clearance is split-scoped after subtracting the ribbon width, Windows/Linux right-dock clearance applies to the right sidebar tab row, and WCO chrome rows keep the shared sidebar header bottom border. - Existing installs must be removed and reinstalled after manifest changes for Window Controls Overlay to take effect; standalone fallback keeps the native title bar.
PWA Registration (pwa.ts)
- Uses
virtual:pwa-registerso the generated service worker can report waiting updates without auto-applying them. - Skips service-worker registration during local development and clears stale dev registrations/caches so the shared workspace shell starts without PWA-induced reload races.
- Marks the document as offline-ready when the service worker finishes preparing caches.
- Tracks browser network state separately from offline-ready shell caching, mirroring the current network mode onto the document root and the shared status bar.
- Waits for the mounted workspace app’s command registry and status bar services before registering browser-only install commands or status items, because
globalThis.appcan exist before all runtime services are initialized. - Registers browser-only status-bar items for
InstallandDismisswhen the browser exposes the deferred install prompt event. - Registers browser-only status-bar items for
Updateand offline network state, and mounts a host-owned prompt component above the shared status bar so update UI stays in the web host boundary instead of the notifications plugin. - Persists explicit dismissals in browser storage so the custom install affordance stays hidden across reloads while leaving browser-native install entry points available.
- Hides the install affordance after installation completes or after the deferred prompt is consumed.
- Polls for new service-worker versions when the PWA returns to the foreground or regains connectivity, then exposes
Latersemantics by hiding the prompt while keeping the waiting update available through the status bar until the user installs it or reloads. - Leaves native
lapis://handling to desktop shells. Browser/PWA launches use the web-supportedweb+lapis://protocol handler and the/open?url=...route because browsers do not generally allow arbitrary bare custom schemes for installed web apps. - Installs a host-owned
PluginAssetServerinto the shared workspace mount. The server reads verified installed-plugin metadata from.obsidian/installed-plugins.json, checks installed file paths, sizes, and SHA-256 hashes through the shared API helper, writes supported assets into thelapis-plugin-assets-v1Cache Storage bucket, and returns same-origin/__lapis/plugins/<vault-id>/<plugin-id>/<version>/<sha256>/<path>module URLs for future renderer ESM plugin imports.
Vite Host Config (vite.config.ts)
- Reuses the same dev-time source aliases as the workspace dev shell for bundled first-party plugins, including explicit first-party UI subpaths such as
@lapis-notes/ui/table-dnd/sensorsand@lapis-notes/ui/table-dnd/utilsthat do not map through the wildcard component export shape. - Installs the shared plugin-host import-map Vite plugin so web/PWA HTML receives the generated app-owned host import map before renderer modules load.
- Resolves
$libagainst the owning first-party source package during local development so workspace renderer modules and directly sourced bundled plugins can keep package-local imports. - Enables
vite-plugin-pwaingenerateSWmode with a manifest that preferswindow-controls-overlay, falls back tostandalone, and uses shell-alignedtheme_color/background_colorvalues instead of static brand blue. - Leaves Workbox in prompt-mode update flow by avoiding eager
skipWaiting, so a newly downloaded service worker stays waiting until the host update prompt asks the user to install it. - Disables
vite-plugin-pwa’s dev service worker so local web-host startup matches the workspace dev shell and avoids stale cached layout state during HMR. - Raises
workbox.maximumFileSizeToCacheInBytesto 50 MiB so large browser-only runtime assets remain eligible for precache. - Adds a Workbox
CacheOnlyruntime route for/__lapis/plugins/that serves only plugin assets already mirrored into Cache Storage by the active workspace runtime. This keeps dynamic module loading same-origin while avoiding a broad vault-file service worker route.
Build Context
The web host is built from the repository root when a deployment system performs a fresh install, because the package resolves workspace dependencies and source aliases against sibling packages across the pnpm monorepo. The repo-root nixpacks.toml captures that supported path by overriding the default Node build command to pnpm --filter @lapis-notes/web build while leaving the existing package-local build script authoritative for icon generation and the Vite bundle.
The repo also provides docker/web/Dockerfile for publishing the Web/PWA host as lapisnotes/web. The Docker build installs the @lapis-notes/web workspace dependency closure from the repository root, passes LAPIS_BUILD_COMMIT through to the Vite build so the shared About dialog can expose the source commit, labels the image with OCI title/description/source/revision/version/license metadata, and copies packages/web/dist into a Docker Hardened Images Caddy runtime (dhi.io/caddy:2). The runtime listens on port 8080, returns 200 ok from /healthz, disables Caddy admin/config persistence and automatic HTTPS, expects TLS termination at the ingress layer, serves hashed Vite assets with immutable caching, serves HTML/service-worker/manifest files with no-cache semantics, preserves 404 responses for missing file-like asset requests, and falls back to index.html for app routes such as /open?url=....
Icon Generation (scripts/generate-pwa-icons.mjs)
- Copies the shared workspace
logo.svgintopublic/favicon.svg. - Rasterizes the shared
lapis.pngapp tile into padded PNGs for install surfaces:apple-touch-icon.png(180x180) andpwa-192x192.png/pwa-512x512.png/pwa-1024x1024.pngcenter the logo at 82% scale (~9% inset per side), and dedicatedpwa-*-maskable.pngoutputs center the logo at 80% scale for the maskable safe zone.
Offline Scope
The current web host precaches the built application shell and browser-local runtime assets, including large WASM and worker bundles that power SQLite, DuckDB, PDF rendering, and notebook execution. After a successful online load, the browser host can reopen the core app shell offline and continue working with browser-local vault and generated-state storage.
That offline guarantee currently applies to the shipped host shell and browser-local features. Optional features that intentionally fetch remote resources on first use, such as notebook dependency URLs resolved through esm.sh or transformer model assets pulled from Hugging Face, remain outside the precached shell and degrade when the network is unavailable.