@kazzle/app 0.1.800 → 0.1.802

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -17,7 +17,8 @@ the AI stale types. This package replaces the copies with real imports:
17
17
  ```ts
18
18
  import { defineConfig } from '@kazzle/app';
19
19
  import { defineTools } from '@kazzle/app/tools';
20
- import { kazzleAppVite } from '@kazzle/app/vite';
20
+ import { kazzleAppVite, kazzleAppPwa } from '@kazzle/app/vite';
21
+ import { registerAppSW } from '@kazzle/app/pwa';
21
22
  ```
22
23
 
23
24
  ## Subpath exports — keep them separate
@@ -31,7 +32,8 @@ and the root export must stay tiny and dependency-free.
31
32
  | `@kazzle/app` | `defineConfig`, `KazzleConfig` + component/trigger/env types | none |
32
33
  | `@kazzle/app/tools` | `defineTools`, `KazzleTool` types, Zod input helper types | `zod` (peer, type-only) |
33
34
  | `@kazzle/app/client` | install-scoped client: per-install secrets, sibling component URLs | none |
34
- | `@kazzle/app/vite` | `kazzleAppVite` preview/runtime Vite helper | `vite` (peer, type-only) |
35
+ | `@kazzle/app/vite` | `kazzleAppVite` + `kazzleAppPwa` (HOST/PORT + production SW plugins) | `vite` (peer), `vite-plugin-pwa` |
36
+ | `@kazzle/app/pwa` | `registerAppSW()` browser registration (owns `virtual:pwa-register`) | none (virtual module at build) |
35
37
  | `@kazzle/app/templates` | programmatic template manifest + asset readers (server/generator only) | none |
36
38
  | `@kazzle/app/migrations` | versioned app upgrade metadata + codemods (server/CLI only) | none |
37
39
 
package/dist/pwa.d.ts ADDED
@@ -0,0 +1,15 @@
1
+ export type RegisterAppSWOptions = {
2
+ immediate?: boolean;
3
+ onNeedRefresh?: () => void;
4
+ onOfflineReady?: () => void;
5
+ onRegistered?: (registration: ServiceWorkerRegistration | undefined) => void;
6
+ onRegisterError?: (error: unknown) => void;
7
+ };
8
+ /**
9
+ * Register the production service worker. No-op in dev when
10
+ * `kazzleAppPwa()` left `devOptions.enabled: false` (required).
11
+ *
12
+ * `immediate: true` by default so autoUpdate can claim clients as soon as a
13
+ * new worker is waiting.
14
+ */
15
+ export declare function registerAppSW(options?: RegisterAppSWOptions): (reloadPage?: boolean) => Promise<void>;
package/dist/pwa.js ADDED
@@ -0,0 +1,21 @@
1
+ // @kazzle/app/pwa — browser service-worker registration for Kazzle UI apps.
2
+ //
3
+ // Published apps need a service worker with autoUpdate so users pick up new
4
+ // builds without a hard refresh. Templates MUST call `registerAppSW()` from
5
+ // this module and MUST NOT import `virtual:pwa-register` themselves — the
6
+ // virtual module + its types are owned here so a missing vite-env.d.ts cannot
7
+ // break `appdev.check` (TS2307) on every fresh create.
8
+ //
9
+ // Pair with `kazzleAppPwa()` from `@kazzle/app/vite` in vite.config.ts.
10
+ // Never enable the PWA plugin's `devOptions` — a SW in dev breaks Vite HMR.
11
+ import { registerSW } from 'virtual:pwa-register';
12
+ /**
13
+ * Register the production service worker. No-op in dev when
14
+ * `kazzleAppPwa()` left `devOptions.enabled: false` (required).
15
+ *
16
+ * `immediate: true` by default so autoUpdate can claim clients as soon as a
17
+ * new worker is waiting.
18
+ */
19
+ export function registerAppSW(options = {}) {
20
+ return registerSW({ immediate: true, ...options });
21
+ }
package/dist/vite.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- import type { UserConfig } from 'vite';
1
+ import type { Plugin, UserConfig } from 'vite';
2
+ import { type VitePWAOptions } from 'vite-plugin-pwa';
2
3
  export interface KazzleAppViteOptions {
3
4
  /**
4
5
  * Name of the sibling process component that owns this app's backend/API
@@ -18,12 +19,28 @@ export interface KazzleAppViteOptions {
18
19
  */
19
20
  proxyPaths?: string[];
20
21
  }
22
+ export type KazzleAppPwaOptions = {
23
+ /** Merged over Kazzle's default workbox precache/runtime config. */
24
+ workbox?: VitePWAOptions['workbox'];
25
+ /** Optional web app manifest. Omit for a SW-only install (no install prompt). */
26
+ manifest?: VitePWAOptions['manifest'];
27
+ includeAssets?: VitePWAOptions['includeAssets'];
28
+ };
21
29
  /**
22
30
  * Kazzle preview/runtime Vite config. Spread the result into your Vite config:
23
31
  *
24
32
  * export default defineConfig(({ command }) => ({
25
- * plugins: [react(), tailwindcss()],
33
+ * plugins: [react(), tailwindcss(), ...kazzleAppPwa()],
26
34
  * ...kazzleAppVite(command, { apiComponent: 'server', proxyPaths: ['/todos'] }),
27
35
  * }));
28
36
  */
29
37
  export declare function kazzleAppVite(command: string, options?: KazzleAppViteOptions): UserConfig;
38
+ /**
39
+ * Kazzle production PWA plugins. Spread into `plugins` next to react/tailwind:
40
+ *
41
+ * plugins: [react(), tailwindcss(), ...kazzleAppPwa()]
42
+ *
43
+ * Always pair with `registerAppSW()` from `@kazzle/app/pwa` in main.tsx.
44
+ * Do not import `vite-plugin-pwa` or `virtual:pwa-register` in the app.
45
+ */
46
+ export declare function kazzleAppPwa(options?: KazzleAppPwaOptions): Plugin[];
package/dist/vite.js CHANGED
@@ -1,9 +1,9 @@
1
- // @kazzle/app/vite — Kazzle preview/runtime Vite helper.
1
+ // @kazzle/app/vite — Kazzle preview/runtime Vite helpers.
2
2
  //
3
- // BUILD-TIME ONLY. This module imports vite types and is meant for a UI
4
- // component's vite.config.ts. Never import it from process/runtime code — the
5
- // subpath split exists so process apps don't pull vite into their dependency
6
- // graph. See README.md.
3
+ // BUILD-TIME ONLY. This module imports vite (+ vite-plugin-pwa) and is meant
4
+ // for a UI component's vite.config.ts. Never import it from process/runtime
5
+ // code — the subpath split exists so process apps don't pull vite into their
6
+ // dependency graph. See README.md.
7
7
  //
8
8
  // READ THIS BEFORE TOUCHING vite.config.ts (human or AI):
9
9
  // Kazzle assigns every UI component a specific HOST and PORT at preview/run
@@ -12,8 +12,19 @@
12
12
  // 5173) the tunnel gets ECONNREFUSED and the preview looks broken. This helper
13
13
  // is the ONE place that reads HOST/PORT and pins Vite to them (strictPort).
14
14
  //
15
- // Every UI component's vite.config.ts MUST spread `...kazzleAppVite(command)`
16
- // into its config instead of hand-writing server.host / server.port.
15
+ // Every UI component's vite.config.ts MUST:
16
+ // 1. Spread `...kazzleAppVite(command)` — never hand-write server.host/port.
17
+ // 2. Spread `...kazzleAppPwa()` into plugins — never import vite-plugin-pwa
18
+ // or `virtual:pwa-register` in the app. Browser entry calls
19
+ // `registerAppSW()` from `@kazzle/app/pwa`.
20
+ //
21
+ // Keep this file self-contained (no relative imports). Vite loads the
22
+ // published entry under Node ESM, which requires explicit .js extensions for
23
+ // relatives — a single file avoids that footgun.
24
+ import { createRequire } from 'node:module';
25
+ import { VitePWA } from 'vite-plugin-pwa';
26
+ const requireFromSdk = createRequire(import.meta.url);
27
+ const DEFAULT_PWA_GLOB_PATTERNS = ['**/*.{js,css,html,ico,png,svg,woff2}'];
17
28
  function componentUrlEnvName(componentName) {
18
29
  const normalized = componentName.toUpperCase().replace(/[^A-Z0-9]+/g, '_').replace(/^_|_$/g, '');
19
30
  return `KAZZLE_APP_COMPONENT_${normalized}_URL`;
@@ -22,7 +33,7 @@ function componentUrlEnvName(componentName) {
22
33
  * Kazzle preview/runtime Vite config. Spread the result into your Vite config:
23
34
  *
24
35
  * export default defineConfig(({ command }) => ({
25
- * plugins: [react(), tailwindcss()],
36
+ * plugins: [react(), tailwindcss(), ...kazzleAppPwa()],
26
37
  * ...kazzleAppVite(command, { apiComponent: 'server', proxyPaths: ['/todos'] }),
27
38
  * }));
28
39
  */
@@ -75,3 +86,90 @@ export function kazzleAppVite(command, options = {}) {
75
86
  },
76
87
  };
77
88
  }
89
+ /**
90
+ * Dev-only: a browser that previously visited a built/PWA version of this app
91
+ * at the same origin still has a service worker. With `devOptions.enabled:
92
+ * false` we don't serve a SW in dev, so the stale SW keeps intercepting with
93
+ * its old precache. This plugin serves a tiny unregister script at /sw.js and
94
+ * /registerSW.js during `vite serve` only.
95
+ */
96
+ function devUnregisterServiceWorker() {
97
+ const body = [
98
+ "self.addEventListener('install', e => self.skipWaiting());",
99
+ "self.addEventListener('activate', e => e.waitUntil((async () => {",
100
+ " try { await self.registration.unregister(); } catch {}",
101
+ " try { const keys = await caches.keys(); await Promise.all(keys.map(k => caches.delete(k))); } catch {}",
102
+ " const clients = await self.clients.matchAll({ type: 'window' });",
103
+ " for (const c of clients) { try { c.navigate(c.url); } catch {} }",
104
+ "})());",
105
+ ].join('\n');
106
+ return {
107
+ name: 'kazzle:dev-unregister-sw',
108
+ apply: 'serve',
109
+ configureServer(server) {
110
+ server.middlewares.use((req, res, next) => {
111
+ if (req.method !== 'GET')
112
+ return next();
113
+ const url = req.url?.split('?')[0];
114
+ if (url !== '/sw.js' && url !== '/registerSW.js')
115
+ return next();
116
+ res.setHeader('content-type', 'application/javascript; charset=utf-8');
117
+ res.setHeader('cache-control', 'no-store');
118
+ res.end(body);
119
+ });
120
+ },
121
+ };
122
+ }
123
+ /**
124
+ * Keep Vite from prebundling `@kazzle/app/pwa` — that entry imports
125
+ * `virtual:pwa-register`, which only exists after VitePWA's resolve hooks run.
126
+ * Also pin `workbox-window` to the SDK install so the app root does not need
127
+ * its own copy (vite-plugin-pwa's virtual register imports it).
128
+ */
129
+ function excludeKazzlePwaFromOptimizeDeps() {
130
+ const workboxWindow = requireFromSdk.resolve('workbox-window');
131
+ return {
132
+ name: 'kazzle:exclude-pwa-optimize-deps',
133
+ config() {
134
+ return {
135
+ optimizeDeps: {
136
+ exclude: ['@kazzle/app/pwa'],
137
+ },
138
+ resolve: {
139
+ alias: {
140
+ 'workbox-window': workboxWindow,
141
+ },
142
+ },
143
+ };
144
+ },
145
+ };
146
+ }
147
+ /**
148
+ * Kazzle production PWA plugins. Spread into `plugins` next to react/tailwind:
149
+ *
150
+ * plugins: [react(), tailwindcss(), ...kazzleAppPwa()]
151
+ *
152
+ * Always pair with `registerAppSW()` from `@kazzle/app/pwa` in main.tsx.
153
+ * Do not import `vite-plugin-pwa` or `virtual:pwa-register` in the app.
154
+ */
155
+ export function kazzleAppPwa(options = {}) {
156
+ const workbox = options.workbox ?? {};
157
+ const pwaPlugins = VitePWA({
158
+ registerType: 'autoUpdate',
159
+ workbox: {
160
+ globPatterns: DEFAULT_PWA_GLOB_PATTERNS,
161
+ ...workbox,
162
+ },
163
+ manifest: options.manifest,
164
+ includeAssets: options.includeAssets,
165
+ // Keep disabled in dev — enabling the SW breaks Vite HMR.
166
+ devOptions: {
167
+ enabled: false,
168
+ },
169
+ });
170
+ return [
171
+ excludeKazzlePwaFromOptimizeDeps(),
172
+ devUnregisterServiceWorker(),
173
+ ...(Array.isArray(pwaPlugins) ? pwaPlugins : [pwaPlugins]),
174
+ ];
175
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kazzle/app",
3
- "version": "0.1.800",
3
+ "version": "0.1.802",
4
4
  "description": "Contracts, tool helpers, Vite helper, and templates for building Kazzle apps.",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {
@@ -28,6 +28,10 @@
28
28
  "types": "./dist/vite.d.ts",
29
29
  "import": "./dist/vite.js"
30
30
  },
31
+ "./pwa": {
32
+ "types": "./dist/pwa.d.ts",
33
+ "import": "./dist/pwa.js"
34
+ },
31
35
  "./templates": {
32
36
  "types": "./dist/templates.d.ts",
33
37
  "import": "./dist/templates.js"
@@ -52,6 +56,10 @@
52
56
  "check": "bun run scripts/build.ts && bun test src",
53
57
  "test": "bun test src"
54
58
  },
59
+ "dependencies": {
60
+ "vite-plugin-pwa": "^1.0.0",
61
+ "workbox-window": "^7.4.1"
62
+ },
55
63
  "peerDependencies": {
56
64
  "vite": ">=5",
57
65
  "zod": "^3 || ^4"
@@ -1,9 +1,12 @@
1
1
  import React from 'react';
2
2
  import { createRoot } from 'react-dom/client';
3
+ import { registerAppSW } from '@kazzle/app/pwa';
3
4
  import { App } from './App';
4
5
  import './styles/theme.css';
5
6
  import './styles/app.css';
6
7
 
8
+ registerAppSW();
9
+
7
10
  createRoot(document.getElementById('root')!).render(
8
11
  <React.StrictMode>
9
12
  <App />
@@ -1,20 +1,20 @@
1
1
  /**
2
2
  * Vite config for the UI component.
3
3
  *
4
- * Kazzle preview/runtime wiring (HOST/PORT binding, strictPort, allowedHosts,
5
- * backend proxy) lives in @kazzle/app/vite — the ONE shared place that must
6
- * never be reinvented. This template declares a backend `server` component; the
4
+ * Kazzle owns HOST/PORT (`kazzleAppVite`) and production PWA (`kazzleAppPwa`)
5
+ * in `@kazzle/app/vite`. This template declares a backend `server` component; the
7
6
  * browser calls relative owned routes (/chat, /image, …) and the helper proxies
8
7
  * them to the sibling component. Add new backend routes to `proxyPaths`. Do not
9
8
  * expose Kazzle's internal component URLs to the browser.
10
9
  */
10
+
11
11
  import { defineConfig } from 'vite';
12
12
  import react from '@vitejs/plugin-react';
13
13
  import tailwindcss from '@tailwindcss/vite';
14
- import { kazzleAppVite } from '@kazzle/app/vite';
14
+ import { kazzleAppVite, kazzleAppPwa } from '@kazzle/app/vite';
15
15
 
16
16
  export default defineConfig(({ command }) => ({
17
- plugins: [react(), tailwindcss()],
17
+ plugins: [react(), tailwindcss(), ...kazzleAppPwa()],
18
18
  ...kazzleAppVite(command, {
19
19
  apiComponent: 'server',
20
20
  proxyPaths: ['/chat', '/image', '/speech', '/transcribe', '/video', '/health'],
@@ -29,8 +29,6 @@
29
29
  "@vitejs/plugin-react": "^4.5.2",
30
30
  "tailwindcss": "^4.1.0",
31
31
  "typescript": "^5.8.3",
32
- "vite": "^6.3.5",
33
- "vite-plugin-pwa": "^1.0.0",
34
- "workbox-window": "^7.3.0"
32
+ "vite": "^6.3.5"
35
33
  }
36
34
  }
@@ -25,7 +25,7 @@
25
25
  import { StrictMode } from 'react';
26
26
  import { createRoot } from 'react-dom/client';
27
27
  import { PowerSyncContext } from '@powersync/react';
28
- import { registerSW } from 'virtual:pwa-register';
28
+ import { registerAppSW } from '@kazzle/app/pwa';
29
29
  import { db } from './lib/sync';
30
30
  import { AppConnector } from './lib/connector';
31
31
  import App from './App';
@@ -34,11 +34,8 @@ import './styles/theme.css';
34
34
  db.init();
35
35
  db.connect(new AppConnector());
36
36
 
37
- // Register the service worker in production builds only. The plugin's
38
- // `virtual:pwa-register` is a no-op in dev (devOptions.enabled: false).
39
- // `autoUpdate` swaps to a new SW as soon as one is available; subsequent
40
- // reloads serve from the new cache.
41
- registerSW({ immediate: true });
37
+ // Production SW — owned by `@kazzle/app/pwa` + `kazzleAppPwa()` in vite.config.
38
+ registerAppSW();
42
39
 
43
40
  createRoot(document.getElementById('root')!).render(
44
41
  <StrictMode>
@@ -1,2 +1 @@
1
1
  /// <reference types="vite/client" />
2
- /// <reference types="vite-plugin-pwa/client" />
@@ -1,71 +1,25 @@
1
1
  /**
2
2
  * Vite config for the UI component.
3
3
  *
4
- * Kazzle preview/runtime wiring (HOST/PORT binding, strictPort, allowedHosts,
5
- * backend proxy) lives in @kazzle/app/vite — the ONE shared place that must
6
- * never be reinvented. This template declares a backend `server` component
4
+ * Kazzle owns HOST/PORT (`kazzleAppVite`) and production PWA (`kazzleAppPwa`)
5
+ * in `@kazzle/app/vite`. This template declares a backend `server` component
7
6
  * (PowerSync sync endpoint); we proxy `/sync` to it via the helper. Add new
8
7
  * backend routes to `proxyPaths`.
9
8
  *
10
- * PWA + worker config below is realtime-specific (offline-first PowerSync) and
11
- * stays here on top of the shared port contract.
9
+ * Offline-first extras (manifest, worker format) stay here on top of the
10
+ * shared PWA + port contract. Do not import `vite-plugin-pwa` directly.
12
11
  */
13
- import { defineConfig, type Plugin } from 'vite';
12
+
13
+ import { defineConfig } from 'vite';
14
14
  import react from '@vitejs/plugin-react';
15
15
  import tailwindcss from '@tailwindcss/vite';
16
- import { VitePWA } from 'vite-plugin-pwa';
17
- import { kazzleAppVite } from '@kazzle/app/vite';
18
-
19
- /**
20
- * Dev-only: any browser that previously visited a built/PWA-enabled version
21
- * of this app at the same origin still has a service worker installed. With
22
- * `devOptions.enabled: false` we don't serve a SW in dev, so the stale SW
23
- * keeps intercepting requests with its old precache and the page renders
24
- * without current CSS/JS. This plugin responds to /sw.js (and the PWA
25
- * register helper) with a tiny script that unregisters every SW, clears all
26
- * caches, and reloads the page once. Once cleaned, the SW is gone and Vite
27
- * HMR works normally. Safe to keep enabled — production builds skip this
28
- * plugin (the `serve` command guard).
29
- */
30
- function devUnregisterServiceWorker(): Plugin {
31
- const body = [
32
- "self.addEventListener('install', e => self.skipWaiting());",
33
- "self.addEventListener('activate', e => e.waitUntil((async () => {",
34
- " try { await self.registration.unregister(); } catch {}",
35
- " try { const keys = await caches.keys(); await Promise.all(keys.map(k => caches.delete(k))); } catch {}",
36
- " const clients = await self.clients.matchAll({ type: 'window' });",
37
- " for (const c of clients) { try { c.navigate(c.url); } catch {} }",
38
- "})());",
39
- ].join('\n');
40
- return {
41
- name: 'kazzle:dev-unregister-sw',
42
- apply: 'serve',
43
- configureServer(server) {
44
- server.middlewares.use((req, res, next) => {
45
- if (req.method !== 'GET') return next();
46
- const url = req.url?.split('?')[0];
47
- if (url !== '/sw.js' && url !== '/registerSW.js') return next();
48
- res.setHeader('content-type', 'application/javascript; charset=utf-8');
49
- res.setHeader('cache-control', 'no-store');
50
- res.end(body);
51
- });
52
- },
53
- };
54
- }
16
+ import { kazzleAppVite, kazzleAppPwa } from '@kazzle/app/vite';
55
17
 
56
18
  export default defineConfig(({ command }) => ({
57
19
  plugins: [
58
20
  react(),
59
21
  tailwindcss(),
60
- devUnregisterServiceWorker(),
61
- // Service worker for offline-first behavior. `devOptions.enabled: false`
62
- // is intentional — enabling the SW in dev breaks Vite HMR
63
- // (templates.mdc note). The SW only ships in the production build, where
64
- // it pre-caches the app shell + JS bundles so reload-while-offline keeps
65
- // working. PowerSync's local SQLite stays readable in both modes.
66
- VitePWA({
67
- registerType: 'autoUpdate',
68
- devOptions: { enabled: false },
22
+ ...kazzleAppPwa({
69
23
  workbox: { globPatterns: ['**/*.{js,css,html,svg,png,ico}'] },
70
24
  manifest: {
71
25
  name: 'Kazzle App',
@@ -23,8 +23,6 @@
23
23
  "@vitejs/plugin-react": "^4.5.2",
24
24
  "tailwindcss": "^4.1.0",
25
25
  "typescript": "^5.8.3",
26
- "vite": "^6.3.5",
27
- "vite-plugin-pwa": "^0.21.1",
28
- "workbox-window": "^7.3.0"
26
+ "vite": "^6.3.5"
29
27
  }
30
28
  }
@@ -11,23 +11,18 @@
11
11
  * `StrictMode` double-invokes effects in dev to catch bugs from accidental
12
12
  * state mutation. It has no effect in production builds.
13
13
  *
14
- * Service worker: `vite-plugin-pwa` with `registerType: 'autoUpdate'` only
15
- * activates/reloads when the app calls `registerSW` from
16
- * `virtual:pwa-register`. Leaving that out leaves a registered worker that
17
- * never claims updates — published builds would stay stale until a hard
18
- * refresh. Keep this registration; do not enable `devOptions`.
14
+ * Service worker: call `registerAppSW()` from `@kazzle/app/pwa` (paired with
15
+ * `kazzleAppPwa()` in vite.config.ts). Do not import `virtual:pwa-register`.
19
16
  */
20
17
 
21
18
  import { StrictMode } from 'react';
22
19
  import { createRoot } from 'react-dom/client';
23
- import { registerSW } from 'virtual:pwa-register';
20
+ import { registerAppSW } from '@kazzle/app/pwa';
24
21
  import App from './App';
25
22
  import './styles/theme.css';
26
23
 
27
- // Production only — `virtual:pwa-register` is a no-op when
28
- // `devOptions.enabled: false`. `immediate: true` + autoUpdate swaps in a new
29
- // worker as soon as `registration.update()` (or periodic checks) finds one.
30
- registerSW({ immediate: true });
24
+ // Production only — no-op in dev (`kazzleAppPwa` keeps `devOptions.enabled: false`).
25
+ registerAppSW();
31
26
 
32
27
  createRoot(document.getElementById('root')!).render(
33
28
  <StrictMode>
@@ -0,0 +1 @@
1
+ /// <reference types="vite/client" />
@@ -1,62 +1,19 @@
1
1
  /**
2
2
  * Vite config for the UI component.
3
3
  *
4
- * Kazzle preview/runtime wiring (HOST/PORT binding, strictPort, allowedHosts,
5
- * backend proxy) lives in @kazzle/app/vite — the ONE shared place that must
6
- * never be reinvented. This file only declares plugins and spreads
7
- * `kazzleAppVite(command)`. This template has no backend component, so no
8
- * proxy options are passed; if you add one later, pass
9
- * `{ apiComponent, proxyPaths }` (see @kazzle/app/vite).
4
+ * Kazzle owns HOST/PORT (`kazzleAppVite`) and production PWA (`kazzleAppPwa`)
5
+ * in `@kazzle/app/vite`. This file only declares app plugins and spreads those
6
+ * helpers. Do not import `vite-plugin-pwa` or `virtual:pwa-register` here —
7
+ * browser entry calls `registerAppSW()` from `@kazzle/app/pwa`.
10
8
  *
11
- * The one non-obvious thing left here: we register a service worker via
12
- * `vite-plugin-pwa`, but ONLY for production builds. In dev, a service worker
13
- * would aggressively cache module responses and break Vite's HMR. The
14
- * `devUnregisterServiceWorker` plugin below cleans up stale SWs left by a
15
- * previous built visit to the same origin. Do not enable `devOptions`.
9
+ * This template has no backend component, so no proxy options are passed; if
10
+ * you add one later, pass `{ apiComponent, proxyPaths }` (see @kazzle/app/vite).
16
11
  */
17
12
 
18
- import { defineConfig, type Plugin } from 'vite';
13
+ import { defineConfig } from 'vite';
19
14
  import react from '@vitejs/plugin-react';
20
15
  import tailwindcss from '@tailwindcss/vite';
21
- import { VitePWA } from 'vite-plugin-pwa';
22
- import { kazzleAppVite } from '@kazzle/app/vite';
23
-
24
- /**
25
- * Dev-only: any browser that previously visited a built/PWA-enabled version
26
- * of this app at the same origin still has a service worker installed. With
27
- * `devOptions.enabled: false` we don't serve a SW in dev, so the stale SW
28
- * keeps intercepting requests with its old precache and the page renders
29
- * without current CSS/JS. This plugin responds to /sw.js (and the PWA
30
- * register helper) with a tiny script that unregisters every SW, clears all
31
- * caches, and reloads the page once. Once cleaned, the SW is gone and Vite
32
- * HMR works normally. Safe to keep enabled — production builds skip this
33
- * plugin (the `serve` command guard).
34
- */
35
- function devUnregisterServiceWorker(): Plugin {
36
- const body = [
37
- "self.addEventListener('install', e => self.skipWaiting());",
38
- "self.addEventListener('activate', e => e.waitUntil((async () => {",
39
- " try { await self.registration.unregister(); } catch {}",
40
- " try { const keys = await caches.keys(); await Promise.all(keys.map(k => caches.delete(k))); } catch {}",
41
- " const clients = await self.clients.matchAll({ type: 'window' });",
42
- " for (const c of clients) { try { c.navigate(c.url); } catch {} }",
43
- "})());",
44
- ].join('\n');
45
- return {
46
- name: 'kazzle:dev-unregister-sw',
47
- apply: 'serve',
48
- configureServer(server) {
49
- server.middlewares.use((req, res, next) => {
50
- if (req.method !== 'GET') return next();
51
- const url = req.url?.split('?')[0];
52
- if (url !== '/sw.js' && url !== '/registerSW.js') return next();
53
- res.setHeader('content-type', 'application/javascript; charset=utf-8');
54
- res.setHeader('cache-control', 'no-store');
55
- res.end(body);
56
- });
57
- },
58
- };
59
- }
16
+ import { kazzleAppVite, kazzleAppPwa } from '@kazzle/app/vite';
60
17
 
61
18
  export default defineConfig(({ command }) => ({
62
19
  plugins: [
@@ -65,13 +22,8 @@ export default defineConfig(({ command }) => ({
65
22
  // tailwind.config.js or PostCSS setup needed; tokens/primitives live in
66
23
  // src/styles/theme.css.
67
24
  tailwindcss(),
68
- devUnregisterServiceWorker(),
69
- VitePWA({
70
- registerType: 'autoUpdate',
25
+ ...kazzleAppPwa({
71
26
  workbox: {
72
- // Precache the static asset bundle. Network requests to anything
73
- // ending in these extensions get served from cache first.
74
- globPatterns: ['**/*.{js,css,html,ico,png,svg,woff2}'],
75
27
  runtimeCaching: [
76
28
  {
77
29
  // External API responses get cached briefly and revalidated in
@@ -86,10 +38,6 @@ export default defineConfig(({ command }) => ({
86
38
  },
87
39
  ],
88
40
  },
89
- // Keep disabled in dev — see header comment.
90
- devOptions: {
91
- enabled: false,
92
- },
93
41
  }),
94
42
  ],
95
43
  // HOST/PORT/strictPort/allowedHosts — the port contract. See @kazzle/app/vite.
@@ -1,8 +1,11 @@
1
1
  import React from 'react';
2
2
  import { createRoot } from 'react-dom/client';
3
+ import { registerAppSW } from '@kazzle/app/pwa';
3
4
  import { App } from './App';
4
5
  import './styles/theme.css';
5
6
 
7
+ registerAppSW();
8
+
6
9
  createRoot(document.getElementById('root')!).render(
7
10
  <React.StrictMode>
8
11
  <App />
@@ -1,19 +1,19 @@
1
1
  /**
2
2
  * Vite config for the UI component.
3
3
  *
4
- * Kazzle preview/runtime wiring (HOST/PORT binding, strictPort, allowedHosts,
5
- * backend proxy) lives in @kazzle/app/vite — the ONE shared place that must
6
- * never be reinvented. This template declares a backend `server` component, so
4
+ * Kazzle owns HOST/PORT (`kazzleAppVite`) and production PWA (`kazzleAppPwa`)
5
+ * in `@kazzle/app/vite`. This template declares a backend `server` component, so
7
6
  * we pass its name plus the relative routes the browser calls; the helper
8
7
  * proxies them to the sibling component and refuses to serve until Kazzle has
9
8
  * injected the backend URL. Add new backend routes to `proxyPaths`.
10
9
  */
10
+
11
11
  import { defineConfig } from 'vite';
12
12
  import react from '@vitejs/plugin-react';
13
13
  import tailwindcss from '@tailwindcss/vite';
14
- import { kazzleAppVite } from '@kazzle/app/vite';
14
+ import { kazzleAppVite, kazzleAppPwa } from '@kazzle/app/vite';
15
15
 
16
16
  export default defineConfig(({ command }) => ({
17
- plugins: [react(), tailwindcss()],
17
+ plugins: [react(), tailwindcss(), ...kazzleAppPwa()],
18
18
  ...kazzleAppVite(command, { apiComponent: 'server', proxyPaths: ['/categories', '/todos'] }),
19
19
  }));