Phase 3 + 4 + 5: assets, look and feel, verification - #22
Conversation
Workers Builds has no per-branch exclusion list, only an all-or-nothing checkbox for non-production branches. Earlier guidance in this file described a capability that does not exist. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Measured five corrections to the brief: orphan deletion is not a step since source/ is git-ignored and regenerable, all 50 extension-less files are orphans, the 28 MB GIF is unreferenced, resizing alone cannot meet the 60 MB gate but palette quantisation can, and the ~/* alias removes the need for per-file relative paths. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017g54jmeHWdVo2uJ8vScwD5
Eight tasks: cover target extraction, pure naming and treatment planning, copy-set extraction, the asset script copying verbatim, map-aware reference emission, image encoding, video encoding, and the phase gate. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017g54jmeHWdVo2uJ8vScwD5
A missing or non-numeric byte count previously fell through the size comparison as NaN >= threshold (false), silently copying a large GIF into src/assets instead of throwing. Guard mirrors the existing still-image colours check per the loud-failure constraint.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017g54jmeHWdVo2uJ8vScwD5
Removed claims of re-encoding and hash-based skip that this commit does not implement; this task copies byte-for-byte and always re-copies. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017g54jmeHWdVo2uJ8vScwD5
convert.mjs now consumes asset-map.json to resolve every .gitbook/assets
reference to its real src/assets/, public/media/ or public/files/ location,
replacing the root-absolute placeholders that made every image 404. Without
a map (source/ has never had assets.mjs run against it) the placeholder is
kept so the build stays green; a reference missing from a map that does
exist throws.
convertFile (the {% file %} block, the corpus's one download rather than
image) is made map-aware the same way, reusing figures.mjs's NEEDS_ANGLE
predicate rather than always bracketing the link. The Step 6 on-disk
assertion is extended to resolve markdown-link destinations
([name](/files/…)) as well as quoted attributes (<video src="/media/…">),
so the download's single reference is checked too.
images drops from 529 to 528: the corpus's one video-kind asset
(Knowledge Base Demo.gif) now renders as a <video> element instead of a
markdown image, and that count was measured before the map distinguished
kinds.
npm run convert now runs assets.mjs before convert.mjs so the map exists
by the time it's needed.
- Reword the EXPECTED.images comment: it counts markdown-image occurrences across the corpus, not distinct assets, and states the one-occurrence delta caused by the sole video-kind asset explicitly rather than in a way that read as 500 - 1 = 499. - Consolidate the .gitbook/assets name-unescaping rule into asset-refs.mjs (exported as ASSET_SRC and unescapeAssetName, with the try/catch and named error) and have figures.mjs import it, rather than keeping a second, silently driftable copy of the same regex and decode chain. - Carry a video's alt text forward as an aria-label, guarded so an empty alt emits no empty attribute — the alt text was being dropped with no fallback for the one asset this task turns into a <video> element.
Resizes stills to a 2000px width ceiling and palette-quantises PNGs below a colour threshold, preserving each source's own format so a .jpg file never ends up holding PNG bytes. A content-hash cache skips re-encoding unchanged assets, and the destination sweep is driven by the previous run's map rather than a directory listing, so files this script never wrote (the site logo) are never touched. Brings src/assets from 121.4 MiB to 32.0 MiB, under the 60 MB gate. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017g54jmeHWdVo2uJ8vScwD5
A slug is not unique across destinations over time: a GIF crossing GIF_VIDEO_THRESHOLD keeps its filename but moves between src/assets and public/media between runs. Keying the sweep on slug alone let a newly claimed slug in its new destination mask a stale copy left behind in its old one, which then had no run that would ever remove it. Legacy map entries without a destination field still resolve via DESTINATION_FOR_KIND, since kind has always determined destination one-to-one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017g54jmeHWdVo2uJ8vScwD5
Knowledge Base Demo.gif was classified kind: video but copied verbatim, so the page emitted a <video> pointing at a .gif that no browser can play. Adds a deterministic ffmpeg encode (bitexact flags, stripped metadata) so the same source always produces byte-identical output, and a guard in figures.mjs that throws if a video-kind map entry ever again resolves to a non-video container. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017g54jmeHWdVo2uJ8vScwD5
Assert no .gitbook/assets path survives conversion when the asset map is present, route the sweep's claimed set through destinationKeyFor, correct CLAUDE.md's stale gotchas and the assets.mjs command description, and record the deferred findings and the corpus-count adjudication procedure. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017g54jmeHWdVo2uJ8vScwD5
Carries the look-and-feel scope with its measured specifics: the 41 repeated sidebar parents, the 13 wrapper divs across 9 pages and 31 images, the 13/10/9 card-cover chain, the 64 dropped width attributes, and the progress-bar-message markup decision still awaiting a human answer. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017g54jmeHWdVo2uJ8vScwD5
The GitBook site this replaces computes Poppins for headings, body and nav, and IBM Plex Mono for code. The build served Inter and Fragment Mono, which match neither that site nor opendialog.ai — measured today, the marketing site loads Sofia Pro, not Inter. Only the latin subset is imported. The corpus uses 27 non-ASCII codepoints, all either inside latin's range or emoji that fall back to the system face; Poppins' Devanagari subset is never needed, and no page uses bold italic. Raw <pre> blocks reach neither Expressive Code nor Starlight's code styling, so 21 blocks across two pages rendered in the browser's default monospace while every other code block used the theme face. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017g54jmeHWdVo2uJ8vScwD5
The title rendered as 24px brand blue against GitBook's 18px/600 near-black
with -0.025em tracking, and the logo at 40px against 32px.
GitBook constrains its layout to a 1440px box centred in the viewport with
32px inner padding while the header band stays full-bleed; Starlight's frame
is full-bleed throughout, so a 1920 viewport put the nav hard against the
left edge with the right third empty. One inset now drives the header, the
fixed sidebar and the main frame so they cannot drift apart.
The logo sat 4px right of the sidebar edge because Starlight cancels
.title-wrapper's padding with a negative margin in the starlight.core layer,
which starlight.reset's `* { margin: 0 }` outranks — core is the lowest
priority layer, so that compensation has never applied. The replacement sits
outside @layer, the only way to beat the reset.
Measured, no horizontal overflow at any width:
375 16px pad, mobile pane untouched at full width
1024 32px inset
1440 logo and sidebar both x=32, matching GitBook exactly
1920 x=272 against GitBook's 265, the half-scrollbar offset 100vw implies
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017g54jmeHWdVo2uJ8vScwD5
The header offered three social icons GitBook does not have and a theme switch for a mode it has never had, while dropping the two links it does carry. GitBook orders these opendialog.ai, "Talk to an expert", search, with search rightmost; Starlight put search in a middle column sized to line up with the content column, which GitBook does not do, so a flex row replaces that grid. The call to action is a quiet outlined button, not a filled brand-blue one: white fill, 1px hairline, 12px radius, 8/16 padding. Link and button colours come from the grey ramp rather than GitBook's exact greys, landing a shade darker with more contrast. Light-only follows the live site, whose <html> carries a hard-coded `light` class with no switcher and no theme key in storage. ThemeProvider ignores a stored preference, so anyone who chose dark on the deployed preview still gets light. The dark tokens stay in custom.css, so this is one file to undo. ThemeSelect and SocialIcons each render in two places, so the mobile menu footer was left an empty 17px strip that still drew its own border; it is overridden away. Measured against GitBook at 1440 — call to action 149x38 against 149x39, link height 20 against 20, gap 24 against 24. No overflow at 375 or 1440; mobile keeps search and hides the links. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017g54jmeHWdVo2uJ8vScwD5
GitBook shows the ancestor-group chain above the page title, from section
down to immediate parent, excluding the page itself. It links the top-level
crumb to paths like /core-concepts, which are not pages — GitBook 307s them
to the section's first page — and they appear in neither the live sitemap
nor route-map.json, so the crumb points straight at the redirect target
instead. Same destination, no hop, no route invented. All 636 breadcrumb
links across 204 pages resolve to a real route.
An intermediate crumb is a page, so it is dropped when it is the page being
viewed. A section is a structural bucket and never a page, so it always
shows; where its first page is the current page the crumb renders as plain
text rather than linking to here. That covers 6 pages.
Headings ran about 17% large and untracked. Measured GitBook, now matched
exactly at h1 36/700/45/-0.9, h2 30/600/36/-0.375, h3 24/600/32/-0.3 and
h4 20/600/28/-0.25. --sl-text-h5 is left alone because asides.css uses it
for the aside title and 258 hint blocks depend on it.
A heading that is itself a link rendered in link blue with an underline,
reading as a mis-styled heading; GitBook renders it in the heading colour,
undecorated.
Fixes a pre-existing defect: PageTitle's h1 and description margins were in
the starlight.core layer, which starlight.reset's `* { margin: 0 }`
outranks, so neither had ever applied.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017g54jmeHWdVo2uJ8vScwD5
The nav rendered every one of its 47 groups expanded — 207 rows and 7,760px of scroll against GitBook's 41 and 936 — and repeated 41 parent labels, because a Starlight group cannot carry a link so sidebar-tree.mjs gives a parent page an entry of its own inside its group. Nested groups now start collapsed. Starlight already opens a collapsed group that holds the current page, so the nav opens along the path to the page being read and nothing else. Sections stay expanded, as on GitBook. SidebarSublist folds a parent's duplicate entry into the group's own row: the label becomes a link, the caret beside it toggles. The rule keys on the first entry being a link carrying the group's label, and is gated to nested groups — ungated, the "Release Notes" section promotes itself because its label happens to match its only page's. SidebarSublist is not an overridable component; naming it in astro.config is accepted and silently does nothing. It has to be reached through Sidebar. Clicking a group label also toggled the enclosing summary, and SidebarPersister carried that collapsed state across the navigation, so landing on a parent page hid the children beneath it. The click no longer reaches the summary. Type now matches GitBook: section headers 12px/600 uppercase against 16px/600, rows 14px/400 in a muted grey, current page marked by weight, colour and a 1px rule rather than a filled block. Rows sit on a 34px pitch and the hairline runs only from the second level down. Measured: 44 displayed rows against GitBook's 44, 34px pitch against 34, 21px indent per level against 21. All 204 routes reachable from the nav, 204 links, zero duplicated, zero orphaned. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017g54jmeHWdVo2uJ8vScwD5
The search modal opened pinned to the top-left corner instead of centred.
Root cause is not the dialog: Starlight sets `margin: 4rem auto auto` on it
in the starlight.core layer, and that margin was computing to 0.
Starlight declares its layer order in style/layers.css as base, reset, core,
content, components, utils — reset first so it can zero margins, core after
so it can set them. A layer's priority is fixed by where it first appears,
and Astro bundles customCss ahead of Starlight's own stylesheets. custom.css
opened `@layer starlight.core` at byte 2,086 while Starlight's layers did not
appear until 38,915, so core registered first and became the lowest-priority
layer, dropping beneath reset. The reset's `* { margin: 0 }` then silently
beat every margin Starlight sets in core.
That had been true since Phase 1 and reached well beyond the dialog: the page
title and description margins in PageTitle.astro had never applied, nor had
Starlight's own .title-wrapper compensation, nor the 1.5rem rhythm between
content blocks.
Restating the order as the first statement in custom.css pins each layer
where Starlight intends. The dialog now centres at x=400 of 1440 at 64px from
the top. The .title-wrapper override added earlier is removed as redundant —
Starlight's own rule works again — and three component comments that blamed
the layer order are corrected.
Verified unchanged after the fix: sidebar 44 rows at 34px pitch with 204
links, logo aligned to the sidebar, breadcrumb and title gaps 12px, heading
scale exact, light theme pinned, no horizontal overflow.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017g54jmeHWdVo2uJ8vScwD5
64 img width attributes set an image's display width in the source and were dropped in Phase 2 because markdown has nowhere to put one. 21 are inside the wrapper divs that lay figures out in a row; the other 43 are on standalone figures, which GitBook sizes just the same — measured, it renders those at 361, 366 and 375 where unsized images fill the column. One 188px thumbnail was rendering at 720x1075. The width rides in the markdown title slot and rehype-image-width turns it into an inline width and strips the title, so none reaches the page. The alternatives were worse: a raw img bypasses astro:assets, and a wrapper div is block markup that would break the enclosing list for the one width-bearing figure inside a list item — the same failure that shipped two non-rendering screenshots in Phase 2. Laying the rows out then exposed a second problem: figures.mjs emits a caption as a separate paragraph, so inside a wrapper each caption became a flex item of its own and forced the row to wrap. rehype-figures pairs an image with the caption beneath it into figure/figcaption, which fixes the row and restores the semantics Phase 2 lost. Pairing is inferred, and the inference is exact here: the source holds 329 figures with a caption and the generated markdown holds exactly 329 italic-only paragraphs directly after an image; the one other italic paragraph follows no image and is left alone. The built output carries exactly 329 content figures, all with an image and a caption. The 101 empty-caption figures stay plain paragraphs. A figure takes its image's width, or a caption longer than its thumbnail sets the row's width and four 188px images no longer fit. Rows shrink rather than wrap above the mobile breakpoint, as GitBook does, and wrap below it. Captions are styled to GitBook's 12px centred roman grey rather than Starlight's body-sized left italic. All 15 wrappers now render on one row, none stacked, no horizontal overflow at 1440 or 375. 211 tests pass, route parity OK, convert is byte-identical across consecutive runs. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017g54jmeHWdVo2uJ8vScwD5
Starlight has no shortcode support, so `:white_check_mark:`, `:x:`, `:tada:` and `:heavy_plus_sign:` reached the page as literal text. GitBook renders them — measured on the live site, `:heavy_plus_sign: Adding a new user` appears there as `➕ Adding a new user`. Four distinct codes across five occurrences on three pages, so they are mapped by hand rather than by adding an emoji dependency. convert.mjs now asserts both counts: `emoji 5` for what was converted and `survivingShortcodes 0` for anything left that the map cannot render, so a sync introducing a fifth code fails the run rather than shipping literal text into a published page. Two shapes must not be mistaken for a shortcode, and both are covered by tests: a time or ratio such as 12:30:45, and Starlight's own `:::note` aside markers. Conversion runs before underscores are escaped for MDX, and matches either form regardless. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017g54jmeHWdVo2uJ8vScwD5
An <iframe> is not a Starlight component, so CLAUDE.md's rule — promote to .mdx only when a component is genuinely required — was never met by an embed. Rendering one through a component promoted 28 pages to .mdx for no other reason, and MDX parses a raw <pre> as JSX with markdown children. On progress-bar-message that silently corrupted an XML snippet readers copy: indentation stripped, straight quotes rewritten as curly by SmartyPants, and an invalid <p> injected inside the <pre>. The same markup in the .md page using-jmespath-expressions was untouched across all 20 of its blocks. That page is now .md and its <pre> is byte-identical to the source. This also retires the decision open since Phase 2: unwrapPreCode no longer runs over it, so neither the dropped <code> nor the joined <strong> happens at all, and no markup change to documentation content is made. mdx drops 46 to 18. Embed.astro is deleted and its styles move to custom.css. Two hazards the build caught rather than shipping: - reflowImageDiv and convertFigures both reject markup they do not expect, and an embed's <figure> wrapping a <div> is exactly that shape. Embeds now convert after both passes rather than weakening either guard. - A block element at column 0 closes any enclosing list. 7 embeds on ai-agent-creation-overview sit in list items inside a stepper, and Starlight's <Steps> requires a single <ol> child, so the page failed the build outright. Every emitted line now carries the indentation of the block it replaces — the same hazard figures.mjs handles for captions. The embeds invariant matches either class or className, since normaliseForMdx rewrites it on the two pages that still need MDX for a real component. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017g54jmeHWdVo2uJ8vScwD5
GitBook renders a data-card-cover card table as image-topped cards; Starlight's <LinkCard> has no image slot, so conversion discarded the cover and emitted a plain card. 13 references across 5 pages, pointing at 10 distinct assets. CoverCard.astro renders the cover through astro:assets, resolving the image with a lazy import.meta.glob so only the covers actually referenced are bundled rather than all 509 assets. An unresolvable cover throws rather than rendering a broken card, matching how the rest of the pipeline treats a reference it cannot satisfy. Without an asset map at all, no cover is emitted and the card falls back to a LinkCard, matching how figures.mjs degrades. assets.mjs stops subtracting covers from the copy set. That subtraction was correct while nothing rendered them — copying would have shipped images no page displayed — and is wrong now. copySet and mapEntries both move 500 to 509, exactly the 9 covers referenced nowhere else. src/assets goes 32.0 to 48.4 MiB against the 60 MB gate, and the largest file 1.1 to 4.0 MiB. Four of the nine are photographic PNGs of 11-14 MB that the pipeline preserves as PNG by design; format preservation is a Phase 3 decision and redesigning the asset pipeline is out of scope here. astro:assets still optimises what ships. The droppedCovers invariant becomes covers, same count of 13, since they are no longer dropped. CARD_ELEMENT counts CoverCard too, or cardTableCards silently lost the 13 rows it had been counting. 231 tests pass, route parity OK, convert byte-identical across consecutive runs. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017g54jmeHWdVo2uJ8vScwD5
Includes three corrections to earlier entries in the file: the brand-token font row records Inter from opendialog.ai, which measures as Sofia Pro today and was never what the docs site served; sidebar.mjs's nav-entry counter and comment were stale once a parent page stopped being two rows; and reference/ holds no visual snapshots, so the phase's definition of done could not be met as written and all visual comparison was made against the live GitBook site. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017g54jmeHWdVo2uJ8vScwD5
rewriteLinks only ever handled markdown links, so a relative .md link written as a raw <a> shipped unrewritten. twilio-content-template-message carries its Button Message link that way inside an HTML table cell, and it 404'd on the live site — while the same page links the same target correctly two lines below, in markdown. The target page exists and is in route-map.json. An earlier note recorded this as "does not resolve and is broken on GitBook today too". The second half may hold, but the first is wrong here: the page is real, so on this site the link is fixable rather than inherently broken. Anchors inside code fences are untouched, since protectCode masks them, so an HTML sample showing a link is not rewritten. External URLs, in-page anchors and GitBook's /broken/pages/ markers are left alone as before. Verified against the built output: 0 broken internal links, and all 1,226 in-page anchors resolve to a real id — which also settles the anchor parity the Phase 2 handoff recorded as assumed rather than verified. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017g54jmeHWdVo2uJ8vScwD5
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017g54jmeHWdVo2uJ8vScwD5
Four decisions agreed with Pat: leave alt empty and report the work list, leave the two non-video embeds as links, fix the light-only pin in a build hook rather than a Page override, and audit with axe-core rather than Lighthouse. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017g54jmeHWdVo2uJ8vScwD5
Seven tasks: the sharp bump, the light-theme build hook, committed link, live-URL and image checks, an axe-core pass, and the gate write-up. Includes the link and anchor measurement taken while planning: 9 links and 16 anchors are inherited GitBook content defects, and the heading slugs match GitBook's, evidenced by 42 working anchors of the shapes that looked like divergence. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017g54jmeHWdVo2uJ8vScwD5
Clears the high-severity libvips advisory Dependabot flags on the default branch. asset-map.json caches on content hash, so it was deleted before re-running or every asset would have cache-hit and the new encoder would never have taken effect. copySet and mapEntries hold at 509, src/assets stays under the 60 MB gate and src/content/docs is byte-identical.
Starlight hard-codes data-theme="dark" on <html> and relies on an inline script to correct it, so JavaScript-disabled readers got a dark site where GitBook served light. An astro:build:done hook rewrites the attribute in the emitted HTML, which avoids copying Starlight's 126-line Page component and the silent drift that copy would carry across upgrades. Rejects any page whose <html> tag is not the expected shape rather than passing it through.
Phase 4 measured both with an ad hoc script that was never committed, so neither claim was reproducible. This checks all 2,650 content links and 1,304 anchors across the built site. It gates on regressions rather than absolute health: 9 links point at GitBook's own /broken/pages markers and 16 anchors name headings that exist on no page, all present in source/ and all broken on GitBook too. They are inventoried, and the check also fails on an inventory entry that has started resolving, so the list cannot rot. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017g54jmeHWdVo2uJ8vScwD5
Fetches all 204 URLs from the committed sitemap snapshot plus the CSV, the MP4 and the sitemap index, and fails on anything not 200. Phase 4 ran this ad hoc and never committed it, so the claim could not be reproduced. Redirects are not followed: a 301 to a trailing-slash variant would otherwise report as 200 and hide the redirect hop trailingSlash: 'never' exists to prevent.
Blocking: every <img> in the build carries an alt attribute and resolves to a file the build emitted. Reporting: the 455 content images that ship with an empty alt, with the live URL, the GitBook asset filename and the existing caption where there is one. Writing the alt text here would be authoring documentation prose and would be destroyed by the next conversion run, so the work list goes to the docs team to fill in GitBook, where it rides back through the pipeline. The row count is checked against the built output so a report that has drifted from what ships fails rather than reading as complete.
Workers Builds deploys main to production but reports nothing back to GitHub, so PRs had no build check and no preview. This workflow builds every PR, uploads the result as a Worker version (production untouched) and posts a sticky comment with the preview URL. Without the Cloudflare secrets the build still runs as a check and the upload is skipped. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HVg4d3Fg3MLk6MEyjeYowp
Brings the April-August 2026 release notes onto the branch. The page existed on both sides: main carried the new sections over Phase 2 output, phase-4 carried the same page regenerated with optimised asset paths. The resolution keeps both - phase-4's body, with the five new sections above March 2026. MIGRATION-NOTES.md conflicted only because both sides appended at the end; both entries are kept. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S2x1MQ8UBvTGKnSw9inBUy
Conversion was always a one-time process and it has run: the last `npm run convert` left the tree byte-identical, so the generated corpus and `source/` agree exactly. From here `src/content/docs/` is the source of truth and is authored in Starlight dialect. Hard rule 2 inverts: the directory is authored, not generated. Hard rule 3 now forbids `npm run convert` outright — `convert.mjs` deletes `src/content/docs/` wholesale and would discard anything authored since. `assets.mjs` and `routes.mjs` stay safe to run alone. `source/` stays frozen and git-ignored as the record of what GitBook held at cutover. Also logs an inherited failure: `verify:images` exits 1, and already did at dfb58cf. It builds its work list from `source/` but counts empty alts from `dist/`, so retirement makes the two diverge by design. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S2x1MQ8UBvTGKnSw9inBUy
PHASE-5-CONTINUATION.md is pasted verbatim as the opening prompt of a fresh session, so a false rule in it is a live hazard: it still told the reader that `src/content/docs/` is generated and to fix the script and re-run. Both it and the Phase 5 plan now carry the current rules. Also refreshes the inherited state, which described a branch that had not been merged, a `main` without the release notes, and a deploy that has since been superseded. Records the four baseline figures the merge moved - tests, content links, fragment links, heading ids - with the deltas explained, so the plan's "any movement means something broke" test still works, and notes the inherited `verify:images` failure. Completed-phase records are left untouched; they describe the rules that were in force at the time. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S2x1MQ8UBvTGKnSw9inBUy
`verify:images` exited 1: it counted empty alts in `dist/` but built its work list from `source/`, and the two disagreed 448 vs 455. Now that `src/content/docs/` is authored rather than generated, `source/` describes a corpus nobody serves and the gap would widen with every page written. The work list now reads the authored pages. `alt-audit.mjs` parses Starlight dialect: `~/assets/…` destinations, markdown titles, and the emphasis-paragraph-after-image convention rehype-figures uses to build a `<figcaption>`, so a caption is reported only where a reader sees one. The 7-image gap was not a counting bug. Seven images on three pages are hotlinked from googleusercontent and freshdesk, and both the old parser and its tests skipped anything that was not a GitBook asset. They ship with an empty alt exactly as local images do, so they are now reported; that is what closes 448 to 455. Uncaptioned rows come first - nothing is announced for those at all - and the report no longer tells the reader to fix it in GitBook. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S2x1MQ8UBvTGKnSw9inBUy
The earlier entry left the 7-image gap undiagnosed and the failure open. Both are now closed, and the two traps found on the way are worth keeping: a test that pinned the blind spot in place, and Astro's bare `alt` serialisation, which produces a plausible wrong number for anyone who matches on `alt=""`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S2x1MQ8UBvTGKnSw9inBUy
The three design specs, three implementation plans and two continuation prompts under docs/superpowers/ documented how the migration was built, not the site. MIGRATION-NOTES.md is the durable record; they are in git history at 9ea473e if a detail is ever wanted back. Transcribes what would otherwise only have survived in the git-ignored ledgers: the two unfinished Phase 5 tasks, the five hotlinked images that 404 on a live page, the inventory note that overclaims the anchors are logged, and the Phase 2 caution that alt text containing `]` breaks the emitted markdown - which matters more now the docs team writes alt text into src/content/docs/ by hand. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S2x1MQ8UBvTGKnSw9inBUy
Add PR preview builds with Cloudflare preview URLs
Resolves the MIGRATION-NOTES.md conflict, which was only ever an append collision: both sides added entries at the end of the file. Both are kept, in date order — the 2026-07-29 branch-control correction sits ahead of the 2026-07-30 Phase 2 gate, where it belongs chronologically. The resolved file differs from phase-4's by exactly the 31 lines this branch adds, and no other file differs at all. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S2x1MQ8UBvTGKnSw9inBUy
There was a problem hiding this comment.
Pull request overview
This PR advances the GitBook → Astro Starlight migration by (1) switching many docs pages over to Starlight-friendly asset paths and embed markup, (2) restoring GitBook-like “card cover” visuals via a new CoverCard component, (3) pinning the site to a light theme (UI + post-build rewrite), and (4) adding verification scripts for link, image-alt, and live-route health.
Changes:
- Replace
{% embed %}-style video embeds with raw<figure>/<iframe>markup across multiple docs pages and remove theEmbed.astrocomponent. - Migrate large numbers of image references from
/.gitbook/assets/...to~/assets/...(and introduce tooling for preserving GitBook image widths + figure captions via rehype plugins). - Add verification tooling (
verify-links,verify-live,verify-images) and UI overrides (light-only theme components, Sidebar override) to match GitBook behavior more closely.
Reviewed changes
Copilot reviewed 41 out of 681 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/content/docs/tutorials/test-and-tweak-your-ai-agent/index.md | Replace Embed usage with inline YouTube iframe + migrate image paths to ~/assets. |
| src/content/docs/tutorials/publish-your-ai-agent/index.md | Replace Embed usage with inline YouTube iframe + migrate image paths to ~/assets. |
| src/content/docs/tutorials/integrate/index.md | Replace Embed usage with inline YouTube iframe + migrate image paths to ~/assets. |
| src/content/docs/tutorials/adding-a-structured-conversation/index.md | Replace Embed usage with inline YouTube iframe + migrate image paths; minor HTML normalization. |
| src/content/docs/tutorials/add-a-new-topic-of-discussion/index.md | Replace Embed usage with inline YouTube iframe + migrate image paths. |
| src/content/docs/opendialog-platform/troubleshooting-and-common-problems/index.md | Migrate image paths to ~/assets (including width-carrying titles). |
| src/content/docs/opendialog-platform/translating-your-application/index.md | Migrate image paths to ~/assets (some with width titles). |
| src/content/docs/opendialog-platform/previewing-your-application/index.md | Migrate image paths to ~/assets. |
| src/content/docs/opendialog-platform/launching-your-application/index.md | Migrate image paths + replace Embed with inline iframe. |
| src/content/docs/opendialog-platform/interpreters-and-natural-language-understanding/llm-actions/openai/index.md | Migrate image paths to ~/assets. |
| src/content/docs/opendialog-platform/interpreters-and-natural-language-understanding/llm-actions/llm-action-analytics/index.md | Migrate image paths to ~/assets. |
| src/content/docs/opendialog-platform/interpreters-and-natural-language-understanding/llm-actions/azure-openai/index.md | Migrate image paths to ~/assets. |
| src/content/docs/opendialog-platform/interpreters-and-natural-language-understanding/language-services/semantic-intent-classifier/openai/index.md | Migrate image paths to ~/assets. |
| src/content/docs/opendialog-platform/interpreters-and-natural-language-understanding/language-services/semantic-intent-classifier/google-gemini/index.md | Migrate image paths to ~/assets. |
| src/content/docs/opendialog-platform/interpreters-and-natural-language-understanding/language-services/semantic-intent-classifier/azure/index.md | Migrate image paths to ~/assets. |
| src/content/docs/opendialog-platform/interpreters-and-natural-language-understanding/interpreters/troubleshooting-interpreters/index.md | Migrate image paths to ~/assets (including multi-image lines). |
| src/content/docs/opendialog-platform/interpreters-and-natural-language-understanding/interpreters/interpreter-orchestration/index.md | Migrate images + replace Loom embed with inline iframe. |
| src/content/docs/opendialog-platform/interpreters-and-natural-language-understanding/interpreters/index.md | Migrate image paths to ~/assets. |
| src/content/docs/opendialog-platform/interpreters-and-natural-language-understanding/interpreters/available-interpreters/openai-interpreter/index.md | Migrate image paths to ~/assets. |
| src/content/docs/opendialog-platform/interpreters-and-natural-language-understanding/interpreters/available-interpreters/dialogflow-interpreter/index.md | Migrate image paths to ~/assets. |
| src/content/docs/opendialog-platform/interpreters-and-natural-language-understanding/interpreters/available-interpreters/dialogflow-interpreter/google-dialogflow-interpreter/index.md | Migrate image paths to ~/assets. |
| src/content/docs/opendialog-platform/conversation-designer/webchat-interface-design/webchat-interface-settings/index.md | Migrate image paths to ~/assets (including inline images in text). |
| src/content/docs/opendialog-platform/conversation-designer/webchat-interface-design/webchat-controls/index.md | Migrate image paths to ~/assets (some with width titles). |
| src/content/docs/opendialog-platform/conversation-designer/message-design/using-markdown-in-messages/index.md | Migrate image paths to ~/assets. |
| src/content/docs/opendialog-platform/conversation-designer/message-design/using-attributes-in-messages/index.md | Migrate image paths to ~/assets. |
| src/content/docs/opendialog-platform/conversation-designer/message-design/subsequent-messages-virtual-intents/index.md | Migrate image paths to ~/assets (some with width titles). |
| src/content/docs/opendialog-platform/conversation-designer/message-design/messages-best-practices/index.md | Migrate image paths to ~/assets (some with width titles). |
| src/content/docs/opendialog-platform/conversation-designer/message-design/message-types/twilio-content-template-message/index.md | Migrate images + fix raw HTML link to absolute site path. |
| src/content/docs/opendialog-platform/conversation-designer/message-design/message-types/text-message/index.md | Migrate images + replace Loom embed with inline iframe + unescape example braces. |
| src/content/docs/opendialog-platform/conversation-designer/message-design/message-types/rich-message/index.md | Migrate images + replace Loom embed with inline iframe. |
| src/content/docs/opendialog-platform/conversation-designer/message-design/message-types/meta-messages/progress-bar-message/index.md | Migrate images + replace Loom embed with inline iframe + adjust <pre>/<code> markup. |
| src/content/docs/opendialog-platform/conversation-designer/message-design/message-types/location-message/index.md | Migrate images + replace Loom embed with inline iframe. |
| src/content/docs/opendialog-platform/conversation-designer/message-design/message-types/list-message/index.md | Migrate images + replace Loom embed with inline iframe. |
| src/content/docs/opendialog-platform/conversation-designer/message-design/message-types/image-message/index.md | Migrate images + replace Loom embed with inline iframe. |
| src/content/docs/opendialog-platform/conversation-designer/message-design/message-types/full-page-message/index.md | Migrate images + replace Loom embed with inline iframe. |
| src/content/docs/opendialog-platform/conversation-designer/message-design/message-types/file-upload-message/index.md | Migrate images + replace Loom embed with inline iframe. |
| src/content/docs/opendialog-platform/conversation-designer/message-design/message-types/e-sign-message/index.md | Migrate images + replace Loom embed with inline iframe. |
| src/content/docs/opendialog-platform/conversation-designer/message-design/message-types/date-picker-message/index.md | Migrate images to ~/assets (some with width titles). |
| src/content/docs/opendialog-platform/conversation-designer/message-design/message-types/conversation-handover-message/index.md | Migrate images + replace Loom embed with inline iframe. |
| src/content/docs/opendialog-platform/conversation-designer/message-design/message-types/autocomplete-message/index.md | Migrate images + replace Loom embed with inline iframe. |
| src/content/docs/opendialog-platform/conversation-designer/message-design/message-types/audio-message/index.md | Migrate images + replace Loom embed with inline iframe. |
| src/content/docs/opendialog-platform/conversation-designer/message-design/message-types/attribute-message/index.md | Migrate image path to ~/assets. |
| src/content/docs/opendialog-platform/conversation-designer/message-design/message-types/address-autocomplete-message/index.md | Migrate images + replace Loom embed with inline iframe. |
| src/content/docs/opendialog-platform/conversation-designer/message-design/message-conditions/index.md | Migrate multiple images to ~/assets. |
| src/content/docs/opendialog-platform/conversation-designer/message-design/index.mdx | Swap LinkCards for cover-image cards using CoverCard. |
| src/content/docs/opendialog-platform/conversation-designer/message-design/constructing-messages/index.md | Migrate images to ~/assets (some with width titles). |
| src/content/docs/opendialog-platform/conversation-designer/inclusive-design-and-conversation-design/index.md | Migrate images to ~/assets. |
| src/content/docs/opendialog-platform/conversation-designer/conversation-design/introduction/user-needs/outcome-user-profile/index.md | Migrate image path to ~/assets. |
| src/content/docs/opendialog-platform/conversation-designer/conversation-design/introduction/sample-dialogs/index.md | Migrate image path to ~/assets. |
| src/content/docs/opendialog-platform/conversation-designer/conversation-design/introduction/nlu-modeling/index.md | Migrate multiple image paths to ~/assets. |
| src/content/docs/opendialog-platform/conversation-designer/conversation-design/introduction/index.md | Migrate image path to ~/assets. |
| src/content/docs/opendialog-platform/conversation-designer/conversation-design/introduction/conversation-structure/index.md | Migrate images to ~/assets (with width titles). |
| src/content/docs/opendialog-platform/conversation-designer/conversation-design/introduction/assistant-personality/index.md | Migrate image path to ~/assets. |
| src/content/docs/opendialog-platform/conversation-designer/conversation-design/index.md | Migrate multiple images to ~/assets. |
| src/content/docs/opendialog-platform/conversation-designer/conversation-design/conversational-patterns/transfer/components/index.md | Migrate image path to ~/assets (with width title). |
| src/content/docs/opendialog-platform/conversation-designer/conversation-design/conversational-patterns/openings/transactional-openings/index.md | Migrate multiple images to ~/assets (incl. multi-image line). |
| src/content/docs/opendialog-platform/conversation-designer/conversation-design/conversational-patterns/openings/index.md | Migrate multiple images to ~/assets. |
| src/content/docs/opendialog-platform/conversation-designer/conversation-design/conversational-patterns/building-robust-assistants/the-contextual-faq-pattern/index.md | Migrate multiple images to ~/assets. |
| src/content/docs/opendialog-platform/conversation-designer/conversation-design/conversational-patterns/building-robust-assistants/contextual-restart-chat-end/index.md | Migrate multiple images to ~/assets (some with width titles). |
| src/content/docs/opendialog-platform/conversation-designer/conversation-design/conversational-patterns/building-robust-assistants/contextual-restart-chat-end-1/index.md | Migrate images to ~/assets (with width titles). |
| src/content/docs/opendialog-platform/conversation-designer/conversation-design/conversational-patterns/building-robust-assistants/contextual-no-match-pattern/index.md | Migrate images to ~/assets (some with width titles). |
| src/content/docs/opendialog-platform/conversation-designer/conversation-design/conversational-patterns/building-robust-assistants/contextual-help/index.md | Migrate multiple images to ~/assets. |
| src/content/docs/opendialog-platform/conversation-designer/conversation-design/conversational-patterns/authentication/using-in-opendialog/index.md | Migrate image path to ~/assets. |
| src/content/docs/opendialog-platform/additional-menu-links/index.md | Migrate multiple images to ~/assets. |
| src/content/docs/opendialog-platform/actions/webhook-action/webhook-action/index.md | Migrate many images to ~/assets (some with width titles). |
| src/content/docs/opendialog-platform/actions/webhook-action/using-jmespath-expressions/index.md | Migrate image path to ~/assets. |
| src/content/docs/opendialog-platform/actions/webhook-action/execution-logs-and-troubleshooting/index.md | Migrate multiple images to ~/assets. |
| src/content/docs/opendialog-platform/actions/index.mdx | Migrate multiple images to ~/assets. |
| src/content/docs/opendialog-platform/actions/conversation-hand-off/index.md | Migrate image path to ~/assets. |
| src/content/docs/opendialog-platform/actions/conversation-hand-off/chatwoot/index.md | Migrate multiple images to ~/assets. |
| src/content/docs/opendialog-platform/actions/actions-from-library/set-attributes-action/index.md | Migrate multiple images to ~/assets. |
| src/content/docs/opendialog-platform/actions/actions-from-library/send-to-email-action/index.md | Migrate multiple images to ~/assets. |
| src/content/docs/opendialog-platform/actions/actions-from-library/freshdesk-action/index.md | Migrate images + fix <mark> syntax for non-MDX + replace YouTube embed with inline iframe. |
| src/content/docs/opendialog-platform/actions/actions-from-library/datetime-action/index.md | Replace Loom embed with inline iframe + migrate image path to ~/assets. |
| src/content/docs/index.mdx | Migrate hero/feature images to ~/assets. |
| src/content/docs/getting-started-1/quick-start-ai-agents/the-start-from-scratch-ai-agent/welcome-conversation/index.md | Migrate multiple images to ~/assets. |
| src/content/docs/getting-started-1/quick-start-ai-agents/the-start-from-scratch-ai-agent/topic-conversation/index.md | Migrate multiple images to ~/assets. |
| src/content/docs/getting-started-1/quick-start-ai-agents/the-start-from-scratch-ai-agent/supporting-llm-actions/index.md | Migrate image path to ~/assets. |
| src/content/docs/getting-started-1/quick-start-ai-agents/the-start-from-scratch-ai-agent/semantic-classifier-query-classifier/index.md | Migrate image path to ~/assets. |
| src/content/docs/getting-started-1/quick-start-ai-agents/the-start-from-scratch-ai-agent/index.mdx | Migrate multiple images to ~/assets (some with width titles). |
| src/content/docs/getting-started-1/quick-start-ai-agents/the-start-from-scratch-ai-agent/global-no-match-conversation/index.md | Migrate multiple images to ~/assets. |
| src/content/docs/getting-started-1/quick-start-ai-agents/the-start-from-scratch-ai-agent/chat-management-conversation/index.md | Migrate image path to ~/assets (with width title). |
| src/content/docs/getting-started-1/quick-start-ai-agents/quick-start-ai-agent/index.md | Replace YouTube embed with inline iframe + migrate many images to ~/assets. |
| src/content/docs/developing-with-opendialog/webchat/webchat-authentication/index.md | Migrate image path to ~/assets (with width title). |
| src/content/docs/developing-with-opendialog/public-apis/index.md | Migrate multiple images to ~/assets. |
| src/content/docs/developing-with-opendialog/interaction-api/index.md | Migrate multiple images to ~/assets. |
| src/content/docs/core-concepts/the-opendialog-workspace/scenarios/turns-and-intents/index.md | Migrate multiple images to ~/assets (some with width titles). |
| src/content/docs/core-concepts/the-opendialog-workspace/scenarios/scenes/index.md | Migrate image path to ~/assets. |
| src/content/docs/core-concepts/the-opendialog-workspace/scenarios/index.mdx | Introduce CoverCard cards + migrate multiple images to ~/assets. |
| src/content/docs/core-concepts/the-opendialog-workspace/scenarios/conversations/index.md | Migrate multiple images to ~/assets. |
| src/content/docs/core-concepts/the-opendialog-workspace/opendialog-account-management/create-and-manage-users/index.md | Replace GitBook shortcode heading glyph with emoji + migrate images. |
| src/content/docs/core-concepts/the-opendialog-workspace/language-services/index.mdx | Introduce CoverCard cards + migrate images to ~/assets. |
| src/content/docs/core-concepts/the-opendialog-workspace/index.md | Migrate multiple images to ~/assets. |
| src/content/docs/core-concepts/the-opendialog-model/index.mdx | Migrate image path to ~/assets (case/slug normalization). |
| src/content/docs/core-concepts/the-opendialog-model/designing-conversational-ai-agents/index.md | Migrate many images to ~/assets. |
| src/content/docs/core-concepts/how-the-conversation-engine-thinks/index.md | Migrate multiple images to ~/assets. |
| src/content/docs/core-concepts/contexts-and-attributes/secret-context/index.mdx | Migrate images to ~/assets (slug normalization). |
| src/content/docs/core-concepts/contexts-and-attributes/conditions-and-operators/index.mdx | Migrate multiple images to ~/assets. |
| src/content/docs/core-concepts/contexts-and-attributes/about-attributes/index.mdx | Migrate multiple images to ~/assets (some with width titles). |
| src/components/ThemeSelect.astro | Override Starlight theme picker to render nothing (light-only UX). |
| src/components/ThemeProvider.astro | Inline script to pin data-theme="light" early + keep Starlight global contract. |
| src/components/Sidebar.astro | Override Sidebar to use local SidebarSublist (linkable parent rows). |
| src/components/MobileMenuFooter.astro | Override mobile footer to render nothing (remove empty strip). |
| src/components/Embed.astro | Remove the old embed component (replaced by inline HTML + CSS). |
| src/components/CoverCard.astro | Add image-cover cards that route images through astro:assets. |
| scripts/verify-live.mjs | Add production/live URL verification against the reference sitemap list. |
| scripts/verify-links.mjs | Add dist-based internal link + anchor verification with inherited-defect inventory. |
| scripts/sidebar.mjs | Improve sidebar counting/reporting to account for parent pages represented twice in data. |
| scripts/lib/sidebar-tree.test.mjs | Update sidebar tree tests for collapsed nested groups and leaf behavior. |
| scripts/lib/sidebar-tree.mjs | Collapse nested groups by default to match GitBook nav expansion behavior. |
| scripts/lib/rehype-image-width.test.mjs | Add tests for width-from-title markdown behavior. |
| scripts/lib/rehype-image-width.mjs | Implement rehype plugin to convert numeric image titles into inline width styling. |
| scripts/lib/rehype-figures.mjs | Implement rehype plugin to pair image + italic caption into <figure>/<figcaption>. |
| scripts/lib/links.test.mjs | Extend link rewriting tests to cover raw HTML <a href="..."> targets. |
| scripts/lib/links.mjs | Rewrite internal .md links inside raw HTML anchors as well as markdown links. |
| scripts/lib/light-theme.test.mjs | Add tests for HTML post-processing that pins data-theme="light". |
| scripts/lib/light-theme.mjs | Implement build-output HTML rewrite to pin light theme without JS. |
| scripts/lib/emoji.test.mjs | Add tests for shortcode-to-emoji conversion and counting. |
| scripts/lib/emoji.mjs | Implement a minimal emoji shortcode converter + counters (with code-block protection). |
| scripts/lib/dist-links.test.mjs | Add tests for extracting href/id and resolving internal links in built HTML. |
| scripts/lib/dist-links.mjs | Implement dist HTML scanners for link + anchor verification. |
| scripts/lib/asset-refs.test.mjs | Add tests for robust .gitbook/assets reference scanning. |
| scripts/lib/asset-refs.mjs | Implement .gitbook/assets reference extraction used by asset mapping/audits. |
| scripts/lib/asset-plan.test.mjs | Add tests for deterministic slug assignment and treatment planning. |
| scripts/lib/asset-plan.mjs | Implement pure asset slug/treatment planning (resize/quantise/video/file copy). |
| scripts/lib/alt-audit.test.mjs | Add tests for authored-page empty-alt inventory generation. |
| scripts/lib/alt-audit.mjs | Implement empty-alt audit over src/content/docs with caption pairing inference. |
| reports/inherited-broken-links.json | Add explicit inventory of inherited broken links/anchors for regression gating. |
| public/files/deliveryknowledgebase.csv | Add downloadable CSV example used by the docs site. |
| package.json | Add verify scripts, update convert pipeline steps, adjust dependencies. |
| astro.config.mjs | Add rehype plugins, add light-theme post-build integration, wire Starlight component overrides. |
| .gitignore | Add .DS_Store. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| "astro": "astro", | ||
| "convert": "node scripts/routes.mjs && node scripts/convert.mjs && node scripts/sidebar.mjs", | ||
| "test": "node --test \"scripts/lib/*.test.mjs\"" | ||
| "convert": "node scripts/routes.mjs && node scripts/assets.mjs && node scripts/convert.mjs && node scripts/sidebar.mjs", |
| My order never arrived?,We're sorry to hear that! You can track your order on the website in the My Account section. | ||
| Do you offer click and collect?,"Unfortunately, we don't currently offer a click and collect service." | ||
| Do I need to sign for my parcel?,"No, you won't have to sign for it." | ||
| Can I select a delivery location?,"Unfortunately , we don't currently offer a click and collect service." |
`npm ci` fails on Node 24.18.0 / npm 11.16.0 - the toolchain .node-version pins CI to - because @napi-rs/wasm-runtime requires @emnapi/core ^2.0.0-alpha.3 and nothing in the lock satisfied it. The three top-level @emnapi entries were dropped at 22cf3dd on 30 July, when an npm install adding the font packages rewrote the lock under a local npm 11.6.0. Four weeks passed without detection because nothing ran `npm ci` on this branch: Workers Builds builds main, and every deploy from here used an existing node_modules. The preview workflow merged today is the first thing to install from the lock, so it surfaced the defect rather than causing it. Reproduced on Node 24.18.0 before fixing, then regenerated with `npm install --package-lock-only` under npm 11.16.0. Pure addition of 34 lines; no dependency version changed. `npm ci` now exits 0 under both 11.16.0 and 11.6.0, and neither rewrites the lock. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S2x1MQ8UBvTGKnSw9inBUy
Correct the Workers Builds branch control note
The preview job failed with "Missing entry-point". wrangler-action probes for wrangler with `npx --no-install`, finds none - wrangler is not a project dependency, it is invoked through npx - and installs its own default, 3.90.0. That version predates assets-only Workers, so it demands a `main` that wrangler.jsonc does not have and should not have: this Worker serves static assets and runs no script. Pins the version the project already uses everywhere else. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S2x1MQ8UBvTGKnSw9inBUy
🔍 Previewhttps://29c139e3-opendialog-docs.opendialog.workers.dev Built from |
stu-greenshoots
left a comment
There was a problem hiding this comment.
This looks good and seems to be working as expected, let's get it merged so we can build on top of it
Lands Phase 3 (assets), Phase 4 (look and feel) and most of Phase 5 (verification), and retires the conversion pipeline. Supersedes #18 — this branch contains all of
phase-3/assets.Already deployed: this is what
docs.opendialog.aihas been serving since 2026-08-27 11:19 UTC.Gate
npm test— 260/260, pristine outputnpx astro build— green, 205 pages (204 +404.html)node scripts/routes.mjs— route parity 204/204 againstreference/sitemap-pages.xmlnpm run verify:links— 0 new broken links, 0 new broken anchorsnpm run verify:images— 0 images with noalt, 0srcvalues with no filenpm run verify:live— 207/207 serving 200src/assets49 MB against the 60 MB gate; largest file 1.12 MiB against Cloudflare's 25 MiB capWhy this was urgent
Merging #20 into
maintriggered a Workers Builds deploy ofmain, which is Phase 2. Every content image on the production site 404'd for about 45 minutes — 60 of 60 across a 20-page sample — and the site lost its fonts and header styling. The styled site had only ever existed as a manualwrangler deployfrom this branch, never inmain. Landing this is what stops that recurring on the next merge.What to know before reviewing
684 files, but only 44 are hand-written. 509 are image assets, 118 are generated content. The code is in
scripts/,src/components/,src/styles/andastro.config.mjs.The conversion pipeline is retired in this PR.
CLAUDE.mdhard rules 2 and 3 invert:src/content/docs/is now hand-authored and is the source of truth, andnpm run convertmust never run again — it deletes that directory wholesale and rewrites it from the frozensource/snapshot. The final conversion ran immediately before the merge and left the tree byte-identical, so the generated corpus andsource/agree exactly at the point they stopped being coupled.The release-notes page is the first hand-authored content. #20 added the April–August 2026 entries to the Phase 2 version of that page; this branch had regenerated the same page with optimised asset paths. Both sides are kept — the new sections above
## March 2026, on phase-4's body.Findings
verify:imageswas failing on this branch and the cause was not a counting bug. Seven images on three pages are still hotlinked fromgoogleusercontent.comandfreshdesk.com. The old audit matched only.gitbook/assets/…, and one of its tests asserted anything else was skipped — so the blind spot was pinned in place by a test. Those images ship with an emptyaltexactly as local ones do.alt-audit.mjsis rewritten for Starlight dialect and now reads the authored pages rather thansource/, which a retired pipeline makes structurally necessary.Two measurement traps, both written up in
MIGRATION-NOTES.md. Astro serialisesalt=""as a barealt, so any check matchingalt="([^"]*)"silently misses every empty-alt image and returns a plausible wrong number. And a work list built fromsource/describes a corpus nobody serves.Known deviations and outstanding work
alt— 152 with no caption at all, 303 under a visible caption.reports/alt-text-todo.mdis the work list, uncaptioned rows first. Derivingaltfrom the caption was considered and rejected: it would duplicate text a screen reader already announces on the 303, and do nothing for the 152.fetchify.comandwebaim.org. GitBook fetched the target's title and favicon at render time; reproducing that needs a network call in every build. Left as links deliberately.sharpadvisory (0.34.5 → 0.35.3, 4 libvips CVEs). Thejs-yaml(4.3.0) andnanoid(3.3.16) high advisories are not addressed here and remain open.