6.x → 7.0.0, entry by entry
Four breaking entries, and only one of them can reach you at runtime. Three are compile errors the moment you upgrade; the fourth is a type you may never have named. None ships a codemod.
| # | Surface | Costs you an edit if |
|---|---|---|
| 1 | ScrapeTarget.pageErrors | you implement ScrapeDriver/ScrapeTarget yourself |
| 2 | PwaRenderMode | you import that type name |
| 3 | PwaOfflineStrategy | you import that type name |
| 4 | PrerenderReport.skipped | you read x build --target static --json, or the report in code |
1. ScrapeTarget gains a required pageErrors: PageErrorRing
Only a third-party driver author pays this, and nothing in an ordinary app implements
ScrapeTarget. If you build one — the shape packages/scraping/README.md's driver-author example
builds — construct the ring and, if your transport can observe uncaught page exceptions, push to it.
+ import { createRing, type PageErrorRing } from '@ultimat3/scraping';
const target: ScrapeTarget = {
// …
+ pageErrors: createRing(200),
};A driver that cannot observe them builds the ring and never pushes — which is exactly what the offline targets do. That is the whole migration.
Required rather than optional, deliberately. An optional ring lets a driver stay silent about
errors it can see, which is the gap this closed: nothing in @ultimat3/scraping subscribed to
pageerror at all, so an island that threw was invisible. A throw calls no console method, so
console() answered [] and a page whose script had died read as clean.
New on ScrapePage, and additive — no edit needed to consume them: pageErrors() and
pageErrorsDropped(). The dropped count makes the list a floor, not a total.
2 and 3. PwaRenderMode and PwaOfflineStrategy are deleted from @ultimat3/pwa
Two type-only renames. No member changed — only the name the type is declared under.
| Was | Is | Members, unchanged |
|---|---|---|
PwaRenderMode | RenderMode | 'static' | 'isr' | 'ssr' | 'stream' |
PwaOfflineStrategy | OfflineStrategy | 'precache' | 'runtime' | 'network-only' |
- import type { PwaRenderMode, PwaOfflineStrategy } from '@ultimat3/pwa';
+ import type { RenderMode, OfflineStrategy } from '@ultimat3/pwa';@ultimat3/pwa re-exports both under the canonical name, so the import path does not have to move —
@ultimat3/core is where they are declared and is equally correct.
Why the alias existed and why it could not stay. Tier 4 may not import tier 4, so @ultimat3/pwa
wrote its own copy of a set @ultimat3/render already had. That copy is what kept spa mapped to
cache-first after spa was deleted in 6.0.0 — the one strategy that gives an app/ route a
shared cache entry, i.e. one signed-in member's HTML served to the next. The vocabulary is now
declared once at tier 0, and bun run scripts/render-modes.ts --json refuses a second declaration
anywhere in packages/*/src.
4. PrerenderReport.skipped carries the reason, not just the path
readonly string[] → readonly SkippedRoute[], where a SkippedRoute is
{ route, surface, render, reason, why }. PrerenderedPage also gains route, the declared path a
concrete URL came from.
- for (const path of report.skipped) console.log(`skipped ${path}`);
+ for (const skipped of report.skipped) console.log(`skipped ${skipped.route}: ${skipped.why}`);x build --target static --json now returns emitted and skipped, and the human path prints the
same rows.
Why it changed. .x/static/ held a partial site and said nothing about the difference: app/
routes exist only through the server, so a tool pointed at the directory filed "the island did not
mount" against a route that was never emitted. A list of paths cannot distinguish "not emitted
because it needs a server" from "not emitted because it is broken", and those are opposite facts.