Astro 5 → 6 migration#4
Merged
Merged
Conversation
Adds §8.4 covering: - Repository visibility: public as of 2026-04-23, post secret scan - Pre-flip secret scan procedure (gitleaks) - Branch protection rulesets: protect-main and protect-dev - Rationale for deferring the required-status-checks toggle to Pass 2 Also trims §2.1's visibility bullet to cross-reference §8.4 instead of duplicating the pre-flip scan procedure. Also adds Pass 2 backlog item #5 for adding a PR-triggered CI workflow that can serve as the required status check once in place (backlog file itself is under plans/ and gitignored — this commit is docs-only). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- SiteHeader.astro: change .site-header from position: relative to position: sticky; top: 0; z-index: 50. Keeps the hairline bottom border. Background is already opaque (var(--color-bg)) — header will not show through to scrolled content. - global.css: add scroll-padding-top: 5rem to html so the "Skip to main content" link's target (#main) lands below the sticky header instead of behind it. Also covers any future anchor jumps. No JS, no scroll-driven effects, no frosted/translucent surface — plain position: sticky per CLAUDE_DESIGN_BRIEF §8 (paper before screen, no ornament). No animation, so prefers-reduced-motion is unaffected. Z-index 50 sits above the in-header mobile-nav panel (10) and ThemeToggle menu (20), well below the SkipLink (1000) so the skip link stays visible on focus. sticky also qualifies as a "positioned" value, so the mobile nav panel's `position: absolute; top: 100%` still anchors to the header correctly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Standard mobile modal pattern: when the hamburger menu is open, body
scrolling is disabled via `body.menu-open { overflow: hidden }` gated
to mobile breakpoints only. Preserves scroll position on close
(no position: fixed reset).
Includes focus management (move to first nav item on open, return to
hamburger on close via Escape or tap-outside), focus trap within the
menu panel (Tab / Shift+Tab cycle), aria-expanded toggling on the
hamburger button, and auto-close if the viewport resizes to desktop
while the menu is still open. Honors prefers-reduced-motion (the menu
has no transitions, so nothing to disable beyond the existing global
@media gate in global.css).
Desktop nav unaffected — always visible, never locks body scroll.
Smoke-tested via plans/diagnose-mobile-menu.mjs (Playwright, 360px
viewport): 12/12 checks pass covering class toggle, aria-expanded,
body overflow state, nav data-open, focus placement, scroll
preservation, Escape/focus-restore, focus trap, resize auto-close,
and desktop no-op gating.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two new sections between "Implementation phasing" and "Skills": - "Gate-based handoff pattern" — four rules on how to sequence work with Claude: plan-first for multi-decision tasks, pause at named gates, report diagnostics before editing, surface uncertainty as open questions rather than silent guesses. The discipline Pass 1 shipped on. - "Process rules from Pass 1" — three specific technical lessons extracted from bugs we caught during G1b / G4 / post-deploy: token-conversion verification scope (diff both preset and companion CSS), wrangler env-inheritance gotcha (observability / vars / routes don't inherit into env.* blocks), and primary- source verification for version-dependent API behavior. Closes items #2, #3 of the Pass 2 backlog (which also had them scheduled for a CLAUDE.md touch at Pass 2 start; landing them now keeps the rules in the file when the next session picks up). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 0.1 — TECH_STACK.md §3.6 (new subsection under Hosting — Cloudflare Workers). Documents that Cloudflare's zone-level "Manage robots.txt" is deliberately enabled: - Production: the managed block names known AI-training crawlers and Cloudflare maintains the list over time (no manual maintenance). - Staging: the managed block prepends User-agent: * / Allow: / ahead of our Disallow: /, which a crawler may read as an override; the X-Robots-Tag header and <meta name="robots"> (§3.3) remain the authoritative noindex signals. - Toggle at Cloudflare dashboard → Bots / Scrape Shield. Phase 0.2 Part 1 — IHK Hamburg → IHK zu Lübeck correction in the two real occurrences (verified via pre-write grep; the plan's three-places claim was reconciled on-disk in the v2.1 changelog note): - docs/BASELINE_COPY.md:222 — §9 "Disclaimer for the owner" callout - docs/TECH_STACK.md:648 — §11.2 Datenschutzerklärung disclaimer Reinbek is in Kreis Stormarn, Schleswig-Holstein. IHK zu Lübeck covers Ostholstein, Segeberg, Stormarn, Herzogtum Lauenburg, and Hansestadt Lübeck, with an Ahrensburg Geschäftsstelle ~9 km from the registered address — relevant for the planned post-launch IHK consultation (per OQ-8 resolution in the Pass 2 plan). Pass 2 Phase 0 Commit A (of three). §9.8 / §10.8 CWA carve-out (Commit B) awaits drafted text from the fresh reviewer session. Phase 0.3 CI workflow follows. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 0.2 Part 2 — amend the "no external scripts" clause in §9.8 (DE, legally authoritative) and §10.8 (EN, courtesy translation) so the disclosure tracks reality once the CWA beacon lands in Phase B.3. Replacement text drafted in a fresh Claude.ai reviewer session per the §11.4 standing rule (Pass 2 plan). Lars-approved verbatim; Claude Code implements without edits. DE-EN register asymmetry preserved — DE closes with "vom eigenen Server" (stronger), EN closes with "site's own origin" (softer), matching the original text's asymmetry. Phase 0 Commit B (of three). Phase 0.3 CI workflow is next. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 0.3 sub-step 2 — author the PR-triggered CI workflow that will become a required status check on the protect-main ruleset (docs/TECH_STACK.md §8.4). Workflow shape: - Trigger: pull_request → main - Node 24 + npm cache (matches deploy-staging.yml exactly) - Steps: npm ci → npm run check → npm run build - Job key + display name both `build-pr` so the ruleset UI's required-check dropdown surfaces a single unambiguous match - Concurrency: per-PR with cancel-in-progress: true (different from deploys, which serialise) — fresh pushes cancel in-flight checks - permissions: contents: read (minimum needed) Forward-defense: workflow-level env FORCE_JAVASCRIPT_ACTIONS_TO_NODE24 opts into Node 24 for JS-based actions ahead of GitHub's runtime default flip on 2026-06-02. Per the Pass 2 backlog Node 20 deprecation item — the existing deploy-* workflows stay untouched until that backlog item is picked up properly. Pre-flight verified before authoring: package.json already has scripts.check = "astro check" and @astrojs/check in devDeps, so no package.json edit lands here. Sub-step 1 of Phase 0.3 is a no-op. Phase 0 Commit C (of three on dev). Sub-steps 3–5 of Phase 0.3 (throwaway PR to register the check, ruleset update, end-to-end verify) follow as session actions, not commits. G1 closes Phase 0. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace `export type UiStrings = typeof de` with a recursive
WidenStrings<T> helper that preserves the nested object shape but
widens every string leaf to `string`. Resolves 12 ts2322 errors in
en.ts where English literals (e.g. 'About') were rejected against
German literal types ('Über').
Type-only change; runtime values are unchanged. Verified via
`npm run check` (12 cluster-B errors gone, 19 cluster-A errors
remaining as expected) and against the A.0 consolidator's worktree.
Phase A.0 (1 of 2) — closes part of Pass 2 backlog §7.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
SiteHeader.astro: - Re-bind `trigger` and `nav` to triggerEl / navEl typed locals after the existing L67 `instanceof HTMLElement` guard so the narrowing propagates into every inner-function closure - Apply the rename consistently across the IIFE, including spillover sites at L106, L116, L138 that aren't in the original error list but would otherwise produce new ts18047 errors after the rebind - Annotate `close(restoreFocus: boolean)` - Use `navEl.querySelectorAll<HTMLElement>(selector)` so getFocusables() returns HTMLElement[], unblocking .focus() and .offsetWidth/Height ThemeToggle.astro: - Add a local `type ThemeChoice = 'light' | 'dark' | 'system'` alias - Annotate `resolve`, `apply`, `persist` with `choice: ThemeChoice`; add explicit return types on `readChoice()` and `resolve()` - Use `querySelectorAll<HTMLElement>(...)` for triggers/menus/options so .hidden IDL access works - Add a boundary-validation guard in the option-click handler that narrows the data-theme-value attribute string to ThemeChoice before passing to persist/apply Type-only changes — no runtime behavior change. Pass 1 G4 walkthrough behaviors (mobile menu open/close, focus trap on Tab/Shift+Tab, body-scroll-lock, focus restoration, ARIA states, theme apply/persist/ resolve flow) preserved. Verified via `npm run check` (0 errors / 0 warnings / 1 hint on the read-only handoff bundle) and `npm run build` (4 pages, 355 ms, no warnings). Phase A.0 (2 of 2) — closes Pass 2 backlog §7 fix portion. Bootstrap-PR re-run + protect-main ruleset flip follow as session actions. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase A of Pass 2: shared foundations. Adds Pass 2 routes to the counterpart map; adds nav + footer strings for the upcoming pages; declares editorial / products / releases collections; migrates Home + About copy from inline literals to Markdown. The editorial schema gains intro[] and closing fields to preserve the asymmetric .about__top grid and styled .about__closing footer without breaking visual parity. Smartypants disabled in markdown config so BASELINE_COPY apostrophes survive verbatim. No visible change on staging. Phase B (pages) consumes these foundations. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Nine findings drafted in the auditor/drafter-separated review cycle and approved verbatim by Lars. Affects Impressum (§7/§8) and Datenschutz (§9/§10). Introduces three Phase D build-time placeholders for Cloudflare-published facts. Stream C (§6 contact form) deferred to Phase B.1. See plans/g2-5-legal-amendment-drafts.md for the full token inventory and per-finding rationale. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a 17-line ## Workspace bootstrap section between Priority reading and Core principles. Points fresh Claude Code sessions at plans/BOOTSTRAP.md → plans/INDEX.md → plans/HANDOFFS/ for orientation. Documents the three orchestrator standing rules: per-occurrence propose-then-validate INDEX maintenance (never hand-edited), Opus default for sub-agent dispatch (Sonnet only for mechanical tasks with frontmatter justification), and the co-located prompt-file convention at plans/active/<topic>/prompts/. The supporting workspace structure under plans/ is gitignored — this commit only persists the CLAUDE.md entry point. Workspace design, implementation, and verification are all complete (A6 v1-v4, A6b, A6c). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- package.json: astro ^5.2.0 → ^6.1.9, @astrojs/check ^0.9.4 → ^0.9.8,
add overrides: { vite: "^7" } per Astro PR #16062 (fixes Vite 7/8
hoisting collision with @tailwindcss/vite@4.2.x)
- src/content.config.ts: split z import to zod direct; migrate
z.string().url() → z.url() (×2) for Zod 4
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reconciles the squash-merge commit d00d6f1 (Pass 1 follow-ups PR #1) that landed on main on 2026-04-24 with the original commits on dev. Conflict in src/components/SiteHeader.astro resolved by keeping dev's typed-locals form from 78c0202 (TS strict-mode fix) — main's squash predates that fix. Pre-step for the natural Pass 2 dev → main release PR (#4) so the merge becomes a clean fast-forward instead of carrying a conflict into review. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
blackbrowed-labs
added a commit
that referenced
this pull request
Apr 28, 2026
Same pattern as commit 7d83c4b: PR #4 was rebase-merged onto main on 2026-04-28, replaying dev's 13 commits with new SHAs. dev's tree already matches main content-wise via the earlier merge 7d83c4b, but git sees the histories as divergent. Conflict in src/lib/i18n.ts resolved by keeping HEAD (the /404 ↔ /en/404 counterparts added in 89129ce — main doesn't yet have them). This unblocks PR #5 (Phase 404 → main). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
blackbrowed-labs
added a commit
that referenced
this pull request
Apr 28, 2026
Initially set to `enabled` at Pass 1 branch-protection config. Three sequential dev→main PRs (Phase M #4, Phase 404 #5, Phase B.1 #6) each tripped on the same friction: GitHub's rebase-merge rewrites SHAs, leaving dev's history "stale" against main's new SHAs. Each time required a manual realignment — temp-disable protect-dev's force-push block, reset dev to main, force-push, re-enable. Three toggle dances in two days. Allowing merge commits on main removes the rewrite, preserving original SHAs across both branches. Future dev→main PRs use "Create a merge commit" (now the GitHub default since linear history is off). dev and main never drift. Trade-off: main's git log gains one merge commit per release. `git log --first-parent main` gives the linear release-only view. Squash and rebase merges remain available but are not the default. Documents the change in §8.4 with the full reasoning chain so the next contributor (or future Lars) understands why merge commits sit alongside otherwise-strict main protection. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
blackbrowed-labs
added a commit
that referenced
this pull request
Apr 29, 2026
…ed pages Closes the breadcrumb i18n retrofit gap surfaced by both G C.2.b reviewers: my spec said "all 8 breadcrumb-bearing pages" but the site actually has 12. Four pages were left out and continued to hardcode aria-label="Breadcrumb", including two DE pages (ueber, kontakt) that consequently served the English label to German screen-reader users — the locale-mismatch bug fold-in #4 was meant to eliminate. Pages retrofitted: - src/pages/ueber.astro (DE → Brotkrümelnavigation) - src/pages/kontakt.astro (DE → Brotkrümelnavigation) - src/pages/en/about.astro (EN → Breadcrumb, now i18n-driven) - src/pages/en/contact.astro (EN → Breadcrumb, now i18n-driven) Each page gains: - Import: getUiStrings from the appropriate relative path - Const: t = getUiStrings('de'|'en') - aria-label="Breadcrumb" → aria-label={t.breadcrumb.ariaLabel} No other changes; no content/whitespace/CSS drift on these pages. Verification: - npm run check: 0 errors. - npm run build: 15 pages. - All 12 breadcrumb-bearing pages now serve their locale-correct aria-label: 6 DE pages ("Brotkrümelnavigation"), 6 EN pages ("Breadcrumb"). Zero pages still hardcode "Breadcrumb" in DE markup.
blackbrowed-labs
added a commit
that referenced
this pull request
Apr 29, 2026
…failures" design
Adds verify-cloudflare-facts.yml weekly cron workflow + scripts/checks/dpf.mjs
+ scripts/checks/cwa-retention.mjs + 6 fixtures + scripts/run-verifier.mjs
orchestrator. Extends scripts/check-cloudflare-facts-freshness.mjs to read
_meta.last_check_attempt with 30-day threshold (down from 90).
Migrates src/data/cloudflare-facts.json to verifier-era schema (schema_version
1; per-fact status + value fields; _meta block; structured cwa_retention with
integer month values; raw_events_retention_months explicitly null per
Cloudflare's documented absence). Updates src/lib/cloudflare-facts.ts type +
adds getEffectiveVerifiedDate helper (worst-case freshness signal: older of
the two per-fact verified_at). Privacy pages (datenschutz.astro, en/privacy.astro)
read via the helper.
Verifier shape per plans/active/pass-2/g-d-2/spec.md: 1/5/15-min retry budget
(21 min total, fits 30-min workflow timeout); status-return error handling
(no throws cross check boundaries); explicit registry per spec §9.6;
hand-written validator per §9.7; status-enum rename ('ok' CheckResult →
'active' JSON) per §6.2; v1-coverage smoke step inside verifier workflow only,
mode #8 deploy-triggered limitation acknowledged per §3.8.
All synthetic failure modes (#2, #4, #5, #6) verified via MOCK_SCENARIO=
fixture routing. Mode #1 (freshness gate) verified via stale last_check_attempt.
Modes #3, #7, #8, #9 deferred to controller-side post-commit per prompt's
"may defer" guidance. Outcome record at
plans/active/pass-2/g-d-7/verifier-test-matrix.md (workspace, gitignored).
Closes backlog #8.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced May 12, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Astro 5 → 6 migration. Zero behavioural change; build emits identical static output. End-state:
dev.blackbrowedlabs.comandblackbrowedlabs.comboth serve identical HTML/CSS/JS to pre-migration, but built by Astro 6 / Vite 7 / Zod 4.Sub-step diffs (atomic single commit):
package.json— astro^5.2.0→^6.1.9;@astrojs/check^0.9.4→^0.9.8; newoverrides: { vite: "^7" }per Astro PR #16062 (canonical fix for the Vite 7/8 hoisting collision with@tailwindcss/vite@4.2.x)src/content.config.ts— Zod-4 schema deltas (z.string().url()→z.url()×2);zimport moved tozoddirect (clears the soft-deprecation hints)Workspace docs (gitignored under
plans/) refreshed in the same session — Wave-1 A5 inventory swept end-to-end (~9 actionable rewrites + ~21 verified-no-edit rows). Not in this diff becauseplans/is gitignored perdocs/TECH_STACK.md§8.3.Backed by seven audit reports under
plans/active/pass-2/astro-6-migration/:A1 code readiness · A2 tech-stack ripple · A3 Cloudflare/Wrangler · A4 Zod 4 · A5 docs inventory · A8 / A9 / A10 plan coverage / executability / freshness.
Mid-flight diagnostic:
viteoverride added after Tailwind 4.2.x hoisted Vite 8 broke the local build withMissing field tsconfigPaths on BindingViteResolvePluginConfig.resolveOptions. Canonical fix per Astro maintainers (PR #16062) shipped in Astro 6.1; we missed the codemod by bumping deps manually.Test plan
npm run check— 0 errors, 0 warnings, 1 pre-existing hintnpm run build— 4 pages built;dist/shape unchangedx-robots-tag: noindex, nofollowintactbuild-prCI green on this PRblackbrowedlabs.com)🤖 Generated with Claude Code