@ultimat3/pwa 16.0.0 → 17.0.0

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/CLAUDE.md CHANGED
@@ -16,6 +16,7 @@ Tier 4. May import tiers 0–3: `core`, `schema`, `i18n`, `money`, `time`, `cach
16
16
  | Route input | `PwaRoute` is a **structural** view of render's `RouteDescriptor`. Never import render. |
17
17
  | Strategy choice | derived from render mode via `MODE_STRATEGY`. Per-route override only. |
18
18
  | Precache revision | content hash. Never the build id — that re-downloads everything per deploy. |
19
+ | Every byte count and every threshold | a whole number of 0 or more, screened with `finiteCount` where it is READ — `buildPrecacheManifest`'s `warnBytes` AND each entry's own `bytes` (shell, route, asset), `createInstallController`'s `minEngagementMs`, `retentionPlan`'s `keep`. `totalBytes > warnBytes` is false when EITHER side is `NaN`, so one asset whose byte count did not arrive as a number took down the install-size warning for every other entry, silently, in the function whose output is otherwise byte-identical per commit; `now() - startedAt < NaN` is false at every instant, so the install prompt fired on first paint; and `Math.max(1, NaN)` is `NaN` with `slice(0, NaN)` `[]`, so a retention plan evicted every deploy including the running one. `Math.max` is not a validator — it propagates. `keep: 0` still means 1 (`version-skew.test.ts` pins it), so the floor is 0 everywhere here. `bun run finite-bounds` is the ratchet. |
19
20
  | Precache KEY | the bare URL, always. The revision addresses the FETCH (`?v=<hash>`), never the key: every strategy looks an entry up with `caches.match(req)` and `ignoreSearch` defaults to `false`, so an entry left keyed under `?v=` is a permanent miss — offline serves the fallback document instead of the precached page, and online every precached byte is downloaded twice. `addAll` still does the fetching, because its all-or-nothing failure is what stops a half-populated precache from activating; the install block only re-keys what it stored. `service-worker-runtime.test.ts` executes the emitted `sw.js` against stub `caches`/`fetch` rather than asserting its text. |
20
21
  | Precache FETCH | the revision is appended with `?` or `&`, picked per entry. `PrecacheAsset.url` is public API and a bundler emits `?v=<hash>` of its own, so a fixed `?` built `...?locale=en?v=<rev>`; `addAll` is all-or-nothing, so one non-200 there means `install` rejects and the worker never activates at all. |
21
22
  | SW scope | `assertScope` refuses a relative `swPath`. `lastIndexOf('/')` on `sw.js` is `-1`, so the directory was `''` and `scope.startsWith('')` passed for every scope — the check waved through exactly the config most likely to be wrong. |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ultimat3/pwa",
3
- "version": "16.0.0",
3
+ "version": "17.0.0",
4
4
  "description": "Generated service worker, web manifest, icons, push and version-skew handling.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -31,7 +31,7 @@
31
31
  "test": "bun test"
32
32
  },
33
33
  "dependencies": {
34
- "@ultimat3/core": "16.0.0",
35
- "@ultimat3/seo": "16.0.0"
34
+ "@ultimat3/core": "17.0.0",
35
+ "@ultimat3/seo": "17.0.0"
36
36
  }
37
37
  }
package/src/install.ts CHANGED
@@ -5,6 +5,8 @@
5
5
  * permanently on some platforms.
6
6
  */
7
7
 
8
+ import { finiteCount } from '@ultimat3/core';
9
+
8
10
  /** A minimal signal so this file needs no framework runtime. */
9
11
  export interface ReadSignal<T> {
10
12
  (): T;
@@ -77,7 +79,14 @@ export interface InstallController {
77
79
 
78
80
  export function createInstallController(options: InstallOptions): InstallController {
79
81
  const host = options.host;
80
- const minEngagementMs = options.minEngagementMs ?? MIN_ENGAGEMENT_MS;
82
+ // `now() - startedAt < NaN` is false at every instant, so a threshold that arrived non-finite
83
+ // does not shorten the wait — it deletes it, and the prompt fires on first paint. `0` stays
84
+ // legal: "ask as soon as the browser offers" is a decision, and it is a comparison that works.
85
+ const minEngagementMs = finiteCount(
86
+ 'createInstallController',
87
+ 'minEngagementMs',
88
+ options.minEngagementMs ?? MIN_ENGAGEMENT_MS,
89
+ );
81
90
  const startedAt = host.now();
82
91
 
83
92
  const canInstall = createSignal(false);
package/src/precache.ts CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  // One formatter, in `@ultimat3/core`: the copy that lived here stopped at `mb`, and the route
8
8
  // budget message on the other side of the build stopped at `kb`, for the same byte count.
9
- import { formatBytes } from '@ultimat3/core';
9
+ import { finiteCount, formatBytes } from '@ultimat3/core';
10
10
  import type { PwaRoute } from './strategies';
11
11
 
12
12
  export interface PrecacheAsset {
@@ -54,6 +54,11 @@ export const DEFAULT_PRECACHE_WARN_BYTES = 5 * 1024 * 1024;
54
54
  export function buildPrecacheManifest(input: PrecacheInput): PrecacheManifest {
55
55
  const entries = new Map<string, PrecacheEntry>();
56
56
  const add = (entry: PrecacheEntry): void => {
57
+ // Screened per entry, not on the total: `totalBytes > warnBytes` is false when either side is
58
+ // `NaN`, so ONE asset whose byte count did not arrive as a number takes down the budget
59
+ // warning for every other entry — silently, in a function whose output is otherwise
60
+ // byte-identical per commit. This runs at build time, where a refusal is the right answer.
61
+ finiteCount('buildPrecacheManifest', `bytes for ${entry.url}`, entry.bytes);
57
62
  if (!entries.has(entry.url)) entries.set(entry.url, entry);
58
63
  };
59
64
 
@@ -106,7 +111,11 @@ export function buildPrecacheManifest(input: PrecacheInput): PrecacheManifest {
106
111
  // rule tie-break and `@ultimat3/jobs`' `job.ts`.
107
112
  const sorted = [...entries.values()].sort((a, b) => (a.url < b.url ? -1 : a.url > b.url ? 1 : 0));
108
113
  const totalBytes = sorted.reduce((sum, entry) => sum + entry.bytes, 0);
109
- const warnBytes = input.warnBytes ?? DEFAULT_PRECACHE_WARN_BYTES;
114
+ const warnBytes = finiteCount(
115
+ 'buildPrecacheManifest',
116
+ 'warnBytes',
117
+ input.warnBytes ?? DEFAULT_PRECACHE_WARN_BYTES,
118
+ );
110
119
 
111
120
  const warnings: string[] = [];
112
121
  if (totalBytes > warnBytes) {
@@ -10,6 +10,7 @@
10
10
  * The app decides what to do with that message. This package never navigates a client.
11
11
  */
12
12
 
13
+ import { finiteCount } from '@ultimat3/core';
13
14
  import { BuildIdMissingError } from './errors';
14
15
 
15
16
  export const BUILD_ID_HEADER = 'x-ultimate-build';
@@ -90,9 +91,13 @@ export interface RetentionPlan {
90
91
  * before it is allowed to break; 3 is the sane default for a daily-deploy team.
91
92
  */
92
93
  export function retentionPlan(deploys: readonly Deploy[], keep = 3): RetentionPlan {
94
+ // `Math.max` is not a validator, it PROPAGATES: `Math.max(1, NaN)` is `NaN`, `slice(0, NaN)` is
95
+ // `[]`, and the plan then retains nothing and evicts every deploy — the running one included,
96
+ // which is the case `never evicts everything` pins as impossible. `0` keeps its meaning below.
97
+ const bound = Math.max(1, finiteCount('retentionPlan', 'keep', keep));
93
98
  const ordered = [...deploys].sort((a, b) => b.deployedAt - a.deployedAt);
94
- const retained = ordered.slice(0, Math.max(1, keep));
95
- const evicted = ordered.slice(Math.max(1, keep));
99
+ const retained = ordered.slice(0, bound);
100
+ const evicted = ordered.slice(bound);
96
101
  return {
97
102
  retain: retained.map((d) => d.buildId),
98
103
  evict: evicted.map((d) => d.buildId),