Skip to content

Phase 3 + 4 + 5: assets, look and feel, verification - #22

Merged
patshone-gsl merged 51 commits into
mainfrom
phase-4/look-and-feel
Sep 2, 2026
Merged

Phase 3 + 4 + 5: assets, look and feel, verification#22
patshone-gsl merged 51 commits into
mainfrom
phase-4/look-and-feel

Conversation

@patshone-gsl

Copy link
Copy Markdown
Member

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.ai has been serving since 2026-08-27 11:19 UTC.

Gate

  • npm test — 260/260, pristine output
  • npx astro build — green, 205 pages (204 + 404.html)
  • node scripts/routes.mjs — route parity 204/204 against reference/sitemap-pages.xml
  • npm run verify:links — 0 new broken links, 0 new broken anchors
  • npm run verify:images — 0 images with no alt, 0 src values with no file
  • npm run verify:live — 207/207 serving 200
  • src/assets 49 MB against the 60 MB gate; largest file 1.12 MiB against Cloudflare's 25 MiB cap

Why this was urgent

Merging #20 into main triggered a Workers Builds deploy of main, 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 manual wrangler deploy from this branch, never in main. 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/ and astro.config.mjs.

The conversion pipeline is retired in this PR. CLAUDE.md hard rules 2 and 3 invert: src/content/docs/ is now hand-authored and is the source of truth, and npm run convert must never run again — it deletes that directory wholesale and rewrites it from the frozen source/ snapshot. The final conversion ran immediately before the merge and left the tree byte-identical, so the generated corpus and source/ 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:images was failing on this branch and the cause was not a counting bug. Seven images on three pages are still hotlinked from googleusercontent.com and freshdesk.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 empty alt exactly as local ones do. alt-audit.mjs is rewritten for Starlight dialect and now reads the authored pages rather than source/, which a retired pipeline makes structurally necessary.

Two measurement traps, both written up in MIGRATION-NOTES.md. Astro serialises alt="" as a bare alt, so any check matching alt="([^"]*)" silently misses every empty-alt image and returns a plausible wrong number. And a work list built from source/ describes a corpus nobody serves.

Known deviations and outstanding work

  • 455 images ship with an empty alt — 152 with no caption at all, 303 under a visible caption. reports/alt-text-todo.md is the work list, uncaptioned rows first. Deriving alt from the caption was considered and rejected: it would duplicate text a screen reader already announces on the 303, and do nothing for the 152.
  • Phase 5 is not complete. Task 6 (axe-core audit) is not started; Task 7 (Phase 5 gate write-up) is not done.
  • Two non-video embeds render as plain linksfetchify.com and webaim.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.
  • 9 inherited broken links and 16 inherited broken anchors, all broken on GitBook too. Inventoried, not fixed — fixing them means editing prose.
  • Clears the sharp advisory (0.34.5 → 0.35.3, 4 libvips CVEs). The js-yaml (4.3.0) and nanoid (3.3.16) high advisories are not addressed here and remain open.

greenshootbot and others added 30 commits July 29, 2026 13:23
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
greenshootbot and others added 16 commits July 30, 2026 18:48
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
patshone-gsl and others added 2 commits August 28, 2026 14:43
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

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 the Embed.astro component.
  • 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.

Comment thread package.json
"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."
greenshootbot and others added 3 commits August 28, 2026 14:52
`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
@github-actions

Copy link
Copy Markdown

🔍 Preview

https://29c139e3-opendialog-docs.opendialog.workers.dev

Built from 0857f86. Uploaded as a Worker version — production is unaffected until merge.

@stu-greenshoots stu-greenshoots left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good and seems to be working as expected, let's get it merged so we can build on top of it

@patshone-gsl
patshone-gsl merged commit 6cd3837 into main Sep 2, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants