Skip to content

feat: scaffold Astro + Vue migration (phases 0-1) - #199

Open
sinduri-g wants to merge 69 commits into
mainfrom
feat/astro-migration
Open

feat: scaffold Astro + Vue migration (phases 0-1)#199
sinduri-g wants to merge 69 commits into
mainfrom
feat/astro-migration

Conversation

@sinduri-g

@sinduri-g sinduri-g commented Jul 21, 2026

Copy link
Copy Markdown
Contributor
  • add nested astro/ app: static output, trailingSlash always, base from VITE_BASE_PATH
  • serve real public/ via publicDir; read real adventure YAML via glob content collection
  • port root.tsx head into Layout.astro: CSP, favicons, theme pre-hydration + after-swap reapply, GA4 consent bootstrap, dual JSON-LD, base-aware font preloads
  • add ClientRouter view transitions and native prefetch (replaces speculationrules script)
  • prove YAML -> Zod -> createMarkdownProcessor + rehype-sanitize field HTML at build time
  • port index.css (@theme) verbatim; wire Tailwind 4, astro-icon, Vue appEntrypoint
  • stub nanostores theme/consent persistent stores

Type of change

  • feat new feature
  • fix bug fix
  • refactor no behavior change
  • docs / chore / config / perf / style / security

Manual checks

  • Screen reader tested (UI changes only)
  • New routes added to sitemap.xml, prerender array, README (routes only)
  • UI verified at 375px, 768px, and 1280px against the production build (npm run build && npm run preview) (UI changes only)
  • Re-read every changed file; checked all call sites of any modified exports (all changes)
  • Per-level discussion JSON exists with correct discussionUrl (adventure/level changes only)

- add nested astro/ app: static output, trailingSlash always, base from VITE_BASE_PATH
- serve real public/ via publicDir; read real adventure YAML via glob content collection
- port root.tsx head into Layout.astro: CSP, favicons, theme pre-hydration + after-swap reapply, GA4 consent bootstrap, dual JSON-LD, base-aware font preloads
- add ClientRouter view transitions and native prefetch (replaces speculationrules script)
- prove YAML -> Zod -> createMarkdownProcessor + rehype-sanitize field HTML at build time
- port index.css (@theme) verbatim; wire Tailwind 4, astro-icon, Vue appEntrypoint
- stub nanostores theme/consent persistent stores

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
@github-actions

github-actions Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor
PR Preview Action v1.8.1

QR code for preview link

🚀 View preview at
https://off-on-dev.github.io/website/pr-preview/pr-199/

Built to branch gh-pages at 2026-07-28 17:20 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

sinduri-g added 28 commits July 21, 2026 12:02
- add **/*.astro and **/*.vue globs to REUSE.toml MIT block (reuse-tool does not auto-recognize .astro, so reuse lint fails on the scaffold otherwise)
- exclude astro/** from root eslint; the nested app imports astro:* virtual modules that root tooling cannot resolve, and gets its own lint later

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
…e 2)

- translate schemas/adventure.schema.json to a strict Zod schema (unknown fields fail the build, preserving the ajv validation gate)
- custom yaml-package loader instead of glob(): Astro's glob YAML parser auto-casts unquoted ISO deadlines to Date; the yaml package keeps them as strings, matching the generator. digest-gated
- port the generator's markdown pipeline verbatim (sanitize schema, abbr-tooltip expansion, external-link annotation, non-public-url unwrapping, md-inline/md-content)
- port field normalization: title/name, story, icon/emoji, difficulty/emoji, learnings aliases, intro/summary, codespacesUrl, discussionUrl, deadline, rewards defaults
- add verify-gate.mjs: renders real fields through the pipeline and diffs against committed *.generated.ts (155 matched, 0 mismatched)
- defer to phase 2b (TODO markers): architectureDiagram assets, meta-description synthesis, services step injection, solutions collection, discussion/leaderboard references

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
…phase 2b)

- port buildAdventureMetaDescription/buildLevelMetaDescription; always emit metaDescription (synthesized when absent), matching the generator
- port services -> "Explore the UIs" how_to_play step injection
- extract pure derivations to src/lib/adventure-derive.mjs, shared by the loader and the verification gate
- extend verify-gate to cover metaDescription and the injected services step (191 matched, 0 mismatched)
- add npm scripts: sync (astro sync validation gate) and verify:content (byte-fidelity gate)
- reclassify solutions (pre-built TS) and discussion/leaderboard (refreshed JSON) out of the content collection; they become phase 3 import.meta.glob loads

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
- add SEO.astro (canonical, OG incl article type, Twitter, fixed brand og:image); Layout delegates per-page meta to it
- add lib/site.ts (SITE_URL, BRAND_NAME, OG_IMAGE_ALT, canonicalUrl) and lib/markdown.ts (stripLinks, stripHtml, slugify ported)
- add /adventures/[id] detail route via getStaticPaths from the collection; prose via set:html with md-inline and stripLinks in link cards
- add /adventures list page (month-sorted, stripHtml story in cards)
- verified in dist: titles, canonical (trailing slash), OG/Twitter, synthesized descriptions, unescaped prose, trailing-slash routing

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
…d (phase 3 increment 2)

- add /adventures/[id]/levels/[levelId] via getStaticPaths (all 17 level routes)
- render hook, intro, scenario, objective, toolbox, walkthrough, verification, helpful links, audience via set:html; abbr tooltip expansion confirmed on a real page
- load discussion posts and leaderboard at build time via node fs (community-data.ts) -> fully static, no client fetch
- resolve external data dir from process.cwd(), not import.meta.url: page-imported modules are bundled by Vite which rewrites import.meta.url

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
…nt 3)

- add /challenges/[...tag] (base + 25 tag routes) via getStaticPaths from the collection
- add ChallengesFilter.vue island (client:load): SSR renders the full/pre-filtered grid so no-JS users get content, hydrates for topic/difficulty filtering
- URL-synced state via history.replaceState (scroll-preserving); hydration-safe seed from the route tag, restore ?topics/?difficulty in onMounted
- add lib/challenges.ts deriving the flat level list and tag set (mirrors filter-utils/tag-utils)
- solution page deferred: pre-built solution TS import from the React src via the @/ alias; bridge at cutover

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
… increment 4)

- add Navbar.astro (nav landmark, links, active state) and Footer.astro (Explore/Community landmarks, social, copyright)
- wire into Layout with skip-nav, new-tab-hint span, and focus-to-main on astro:after-swap
- add ThemeToggle.vue island (nanostores $theme + lucide-vue-next): hydration-safe, persists to localStorage, client:load + transition:persist
- replace placeholder home with a real landing (hero + featured adventures from the collection)
- add nav/site constants to lib/site.ts; remove scaffold smoke island

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
…ment 5)

- port the full consent state machine from useConsent.tsx to stores/consent.ts: plain nanostores atom (hydration-safe, default null), React storage format ({value,timestamp}+180d expiry), gtag injector (queue update/js/config before appendChild, module-scoped once-guard), revoke, GPC auto-deny, _ga cookie clearing, firePageView on astro:page-load
- add ConsentBanner.vue (client:load + transition:persist): SSR renders the banner with aria-live; floating cookie-preferences button after a decision
- mount in Layout; gated load verified statically (zero gtag.js prerendered)
- NOTE: runtime transitions (accept/decline/GPC/re-grant/expiry) still need real-browser regression tests before merge

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
…rement 6)

- port Hero.astro (fireflies, hero-badge, brand slogan/line, animate-fade-up) and AdventureCard.astro with sub-components DifficultyBadge, ContributorBadge, LivePill, AdventureIcon
- add lib/difficulty.ts and brand slogan constants
- use Astro class:list in place of cn; astro-icon (lucide, inline SVG, zero JS) in place of lucide-react
- home page uses Hero + card grid; /adventures uses the same cards; build-time isLive

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
… (phase 3 increment 7)

- port FilteredLevelCard design into ChallengesFilter.vue: card-glow, difficulty badge with dot, learnings, estimated-time pill, live pill, adventure icon
- inline badge/pill/icon markup with lucide-vue-next since .astro components can't render inside a Vue island
- extend challenges.ts ChallengeEntry with learnings, adventureIcon, and build-time isLive

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
- port the two-column AdventureDetail layout: breadcrumb, header (title/icon/contributor badge/month/live pill/tag chips/story), main column (overview, challenge level cards, rewards, backstory), sidebar (leaderboard + contributor)
- add components: Breadcrumb, TagChips, InlineProse, PersonNameLink, RewardsCard, and lib/utils.ts (formatDeadline, isDeadlinePast)
- reuse ported AdventureIcon/DifficultyBadge/ContributorBadge/LivePill; build-time deadline gating for live pill and rewards placement

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
- add src/pages/404.astro; Astro emits dist/404.html which GitHub Pages serves for unmatched paths as a real HTTP 404 (no soft-404 catch-all)

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
…ook (phase 3)

- port four nav/footer-linked static pages to .astro with faithful content, correct SEO, and chrome via Layout
- add noindex prop to Layout/SEO; privacy is noindex (kept out of the index, matching the React page)
- notes: CommunityLeaders sidebar rendered as a build-time snapshot; abbr tooltip keeps CSS hover/focus, JS click/Escape toggle deferred

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
- port About (board members + challenge builders), Sponsors (sponsor data + CSS theme-swapped logo), Brand Guidelines (all 8 sections), and Presentation Templates to .astro with faithful content
- copy sponsors.ts and team.ts data into astro/src/data/; add Dynatrace logos to public/brand/
- add redirects (/docs, /docs/community-guide, /community-guide -> /handbook/) via astro.config; presentation-templates is noindex
- nav and footer now have zero 404s; notes: brand TOC scroll-spy and community-leaders live refresh are static snapshots

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
…(phase 3)

- port the ChallengeDetail two-column layout: header, objective/learnings cards, collapsible audience/story/architecture (native details, zero JS), synthetic Get Started walkthrough step, verification, toolbox/docs grid, share links, sidebar (compact rewards, codespaces, community sidebar, other levels)
- add components: CollapsibleSection, CodespacesButton, OtherLevelsCard, ChallengeShareLinks, CommunitySidebar; extend RewardsCard with the compact variant
- wire architectureDiagram SVGs (copied to astro/src/assets/diagrams, matched by filename) with architectureAscii fallback
- content gate still 191 matched, 0 mismatched

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
- bridge the React src solution modules with two exact-find Vite aliases (@/data/solutions/types, @/data/adventures/contributors); astro's own @/ alias unaffected
- add lib/solutions.ts (import.meta.glob over src/data/solutions) and the /adventures/[id]/levels/[levelId]/solution route with getStaticPaths over authored solutions only
- port the SolutionDetail layout: spoiler warning, step nav, context card, details step cards with takeaways, block renderer (text/code/image/callout), final result, outro, sidebar
- build-time deadline gating with locked-state fallback (noindex)

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
…hase 5)

- add Playwright + axe a11y suite (e2e/a11y.spec.ts) over 15 representative routes: axe dark/light/forced-colors with the full WCAG tag set, touch targets (2.5.8), focus rings dark/light, focus traps, 200% zoom reflow. 105 tests pass
- reuseExistingServer:false so tests never run against a stray dev server
- fix heading order: sr-only h2 before card grids on /adventures and /challenges
- fix touch targets: nav links min-h-[44px], footer links min-h-[48px], clear-filters button min-h-6
- fix 200% zoom reflow: abbr tooltip ::after uses display:none when hidden (was opacity:0, which kept layout and extended page width); the React JS tooltip that suppresses this CSS is a follow-up
- constrain walkthrough step grid track with minmax(0,1fr) so code blocks scroll internally

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
- add e2e/smoke.spec.ts: every route has a unique correct <title>, canonical (SITE_URL+path), meta description, matching og:title/og:url, exactly one <h1>, and no page errors
- island hydration checks: theme toggle switches theme + sets .light class; challenges filter filters and syncs ?difficulty
- 19 tests pass

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
…tover stage 1)

- deploy.yml: output dist/ (not dist/client/), drop the 404.html copy (astro emits dist/404.html natively), keep JamesIves/github-pages-deploy-action
- preview.yml: check job runs sync + verify:content gates; build/e2e/preview use dist/; e2e runs playwright (webServer serves via astro preview); preview build uses astro base (VITE_BASE_PATH) so public assets deploy from dist/ with no per-directory copy step
- these target the post-move root structure; inert on the branch until the cutover move lands and the branch is merged
- TODO(cutover): add npm run lint once eslint-astro/vue config is ported

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
…e (stage A)

- copy adventure YAML, discussion/leaderboard JSON, solutions, contributors.ts and types.ts into astro/src/data (the app no longer reads the React src/data tree)
- re-point content.config (data/adventures), community-data (cwd/src/data/adventures), and solutions (../data/solutions) to the local copies
- drop the two interim Vite aliases; @/data/solutions/types and @/data/adventures/contributors now resolve via the app's @/ -> src alias
- build green (63 pages), content gate 191/0, all 124 e2e tests pass

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
…age B)

BREAKING: the React Router app is removed; offon.dev now builds from Astro.

- delete the React app: src/ (root.tsx, entry.*, routes.ts, ~50 .tsx pages/components, hooks), react-router.config.ts, serve.json, the React vite/vitest/eslint/tsconfig configs, e2e/, and the shadcn components.json
- delete the content generator: scripts/generate-adventures.mjs, generate-solutions.mjs, create-data-aliases.mjs, schemas/adventure.schema.json, and all *.generated.ts / index.ts / summaries.ts (replaced by the Zod content collection)
- move the Astro app from astro/ to the repo root (src/, astro.config.mjs, package.json, tsconfig.json, e2e/, playwright.config.ts)
- drop publicDir override (public/ is now local); remove the obsolete verify:content gate (nothing left to diff against) and its verify-gate.mjs
- preview.yml: content gate is now `astro sync` only
- kept: public/, refresh-*/sync/community-sitemap/check-docs scripts, docs, LICENSES, REUSE.toml
- verified at root: build (63 pages), astro sync, 124 e2e tests (105 a11y + 19 smoke) all pass
- follow-ups: rewrite sync-adventure/add-discussion-url/validate-adventures workflows (still reference the removed generator), add eslint-astro/vue lint, custom sitemap endpoint, update CLAUDE.md/README/REUSE.toml, port abbr JS tooltip + Shiki highlighting + click tracking

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
…tover follow-up)

- replace the deleted `generate-adventures.mjs --validate-only` with `npm run sync` (astro sync runs the Zod content schema and fails on invalid adventure YAML)
- drop the obsolete checks: generated TS existence, index.ts import, react-router prerender entries, and sitemap.xml grep (routes now come from getStaticPaths; sitemap endpoint is a follow-up)
- keep: per-level discussion JSON existence and ADVENTURE_CATEGORIES registration in refresh-leaderboard.mjs
- update path triggers (src/content.config.ts instead of schemas/react-router.config)

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
…nerator (cutover follow-up)

- replace `node scripts/generate-adventures.mjs` with `npm run sync` (Zod validation) in both workflows
- commit only the collection inputs (adventure.yaml, *-posts.json, diagrams, refresh-leaderboard.mjs); stop staging the removed *.generated.ts / index.ts / summaries.ts
- rewrite PR-body prose: routes/sitemap now come from getStaticPaths at build time (nothing to regenerate); checks use `npm run sync && npm run build && npm run test:e2e`

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
…llow-up)

- replace all React Router / generator guidance with Astro: stack, repo layout, commands, content collection, components/islands, hydration safety, SEO (<SEO> component), deployment (dist/, trailingSlash, PR-preview base)
- rewrite the analytics/consent section for the nanostores $consent store + Vue island (state machine preserved); update constants home to src/lib/site.ts
- preserve framework-agnostic rules verbatim: Content & Copy, Git, Brand, URLs, Stability, Debugging evidence rules
- add a Known follow-ups section (lint, sitemap endpoint, abbr JS, Shiki, click tracking, REUSE cleanup)

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
…llow-up)

- README: Astro + Vue stack, commands (astro build/sync/test:e2e, dev port 4321), project structure, content collection, routes (.astro + getStaticPaths), SEO (<SEO> component), analytics (nanostores), deployment (dist/, base)
- REUSE.toml: drop entries for deleted files — *.generated.ts (block 3 + per-contributor blocks), src/components/ui + shadcn block, and community-leaders.json (CC0 block); src/lib/utils.ts now falls under the MIT **/*.ts glob

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
…tion (cutover follow-up)

- ADVENTURES.md: rewrite the content pipeline (YAML -> content collection, no generation), the sync-workflow steps, the solutions flow (import.meta.glob, no barrel), the checks command (npm run sync && build && test:e2e), the validate-adventures row, and the constants pointer (src/lib/site.ts)
- PERFORMANCE.md: add a migration note (Layout.astro preloads, Astro islands code-splitting, content-collection markdown, native prefetch, dist/ output, getStaticPaths) — principles unchanged
- styleguide.md: add a migration banner (design system stays authoritative; component entries describe the former React impl; .astro/.vue components are source of truth). Full component rewrite tracked as follow-up

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
…follow-up)

- add eslint.config.js (flat): @eslint/js + typescript-eslint + eslint-plugin-astro + eslint-plugin-vue; ignore dist/.astro/node_modules/public/.claude; quiet purely-stylistic vue formatting rules; keep no-unused-vars as error
- pin typescript to 6.x (typescript-eslint does not support TS 7 yet); build still green on TS 6
- add `npm run lint` script and run it in preview.yml's check job (removes the cutover TODO)
- fix the two real lint errors: unused `base` in the level page and unused `SITE_URL` import in brand.astro

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
…r follow-up)

- add src/pages/sitemap.xml.ts endpoint: enumerates home, /adventures/, adventure + level routes, solution routes, /challenges/ + tag routes, and the indexable static pages, with a build-date lastmod
- exclude /privacy/, /presentation-templates/ (both noindex), and /404/
- delete the stale hand-maintained public/sitemap.xml (the generator that patched it is gone); /sitemap.xml is now generated at build (60 URLs)
- robots.txt, api-catalog, and the Layout sitemap link all reference /sitemap.xml, served by the endpoint

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
sinduri-g added 30 commits July 24, 2026 16:12
- DifficultyBadge: add font-semibold (was dropped site-wide, rendered normal weight)
- 404 page: restore the 3-card Helpful links grid (Handbook/Challenges/About), title-case heading, and noindex (was a stripped minimal page, indexable)
- adventure detail: wrap Overview and The Story in CollapsibleSection cards (were plain static headings); upgrade the sidebar leaderboard to avatar rows via new LeaderboardList.astro (was a no-avatar text list); sidebar sticky offset lg:top-8 to lg:top-28
- level page: make each walkthrough step an individually collapsible <details> with a chevron (was static/expanded); add the "Check Out the Solution Walkthrough" CTA card (post-deadline, when a solution exists)
- all 139 e2e checks pass

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
- add a progressive-enhancement script in Layout that adds a language-label header and a Copy button to every code block, ported from the React MarkdownContent/CodeBlock DOM injection
- prose code blocks get a full code-block-header bar; solution code blocks reuse their existing title bar and gain a Copy button
- runs on astro:page-load so it re-applies after View Transitions; uses the existing (previously dead) .code-block-header/.code-lang-label/.code-header-btn CSS
- verified at runtime: 15 copy buttons on a solution page, 5 buttons + 5 labels on a prose level page
- all 139 e2e checks pass

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
- inventory the components after the visual-parity restoration (chrome, landing sections, filter island, cards/atoms) so the guide reflects the current .astro/.vue source ahead of the full component-by-component rewrite (still a tracked follow-up)

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
- code-block enhancer: label prose blocks with the real fenced language (read code.class="language-*", was always "code"); add a polite live region announcing "Code copied to clipboard" (a focused button's aria-label change is not reliably re-announced) — both restore React MarkdownContent/CodeBlock parity
- level page: gate the solution CTA on the solution-unlock predicate (!deadline || isDeadlinePast) instead of rewardsBelowFold, so a no-deadline level with a solution links to its (reachable) solution page
- ChallengesFilter: clearing all tags on a /challenges/:tag/ route now drops the path segment to /challenges/, so the filter no longer re-seeds from the tag on reload/share/back
- ThemeToggle: subscribe to the theme store post-mount so the desktop and mobile instances stay in sync (fixes a stale icon + dead first-click after toggling then crossing the md breakpoint); kept hydration-safe (no store read at render)
- MobileMenu: force-close the persisted drawer on astro:before-swap (a logo/back navigation could otherwise leave it open with a live focus trap) and on crossing to >=md width
- CommunityLeaders: guard the section icon lookup against an unmapped id
- verified at runtime: prose labels show "sh", tag-clear drops the path, live region present up front; all 139 e2e pass

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
…allenges

- add sortAdventuresByMonthDesc and isAdventureLive to src/lib/challenges.ts
- replace the copy-pasted MONTHS/monthKey/isLive blocks in index.astro, adventures/index.astro, and challenges/[...tag].astro (per CLAUDE.md: shared derivations live in src/lib, not re-derived ad hoc in pages)
- behavior-preserving; all 139 e2e pass

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
Brings the branch up to date with main so the migration PR is conflict-free.
All conflicts were mechanical and resolved deterministically:

- kept the branch (Astro) versions of CLAUDE.md, ADVENTURES.md, e2e/smoke.spec.ts, package-lock.json
- kept deleted the React-era files main still edits: the six *.generated.ts, summaries.ts, ChallengeDetail.tsx, react-router.config.ts, generate-adventures.mjs, prerender.test.ts, seo.test.ts, and public/sitemap.xml (the branch generates the sitemap via src/pages/sitemap.xml.ts)
- auto-merged the incoming community-data refreshes, community-sitemap.xml, and the sync-adventure.mjs local-diagram fallback

Adventure YAML, the expert diagram, and community-leaders were already absorbed, so no content conflicts arose.

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
- port the React useAbbrTooltips behaviour as a Layout client enhancer: replace the CSS ::after tooltip with a position:fixed portal appended to <body>
- fixes two mobile bugs: the tooltip no longer overflows the viewport right edge (clamped so its right edge stays within the viewport), and it no longer gets clipped/cut off by overflow ancestors in walkthrough cards and scroll containers (it renders at body level, escaping them), so the full expansion text is shown
- tap forces focus (iOS), Escape dismisses, hoverable bridge preserved; one shared scroll/resize handler repositions the visible tip; the CSS ::after remains the no-JS fallback (suppressed via abbr-js-tooltip once the portal is live)
- verified at 375px: tips stay within the viewport, unclipped, full text wraps; all 139 e2e pass
- clears the abbr JS tooltip known follow-up in CLAUDE.md

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
…w-ups

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
- the cutover 'prune stale REUSE entries' commit removed the CC0-1.0 block for src/data/community-leaders.json, leaving LICENSES/CC0-1.0.txt unused (REUSE lint failure) and silently relicensing the scraped Discourse data as MIT via the **/*.json glob
- re-add the annotation (placed last so it overrides the glob), matching origin/main: usernames/avatars/counts are factual scraped data, dedicated to the public domain under CC0-1.0
- verified locally: reuse lint passes (Unused licenses: 0; Used: CC-BY-4.0, CC0-1.0, MIT, OFL-1.1)

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
Best-practices audit follow-ups (Astro islands + hydration):

- ChallengesFilter: client:load -> client:visible on the home (it sits below the hero; SSR already renders the adventure grid) and client:idle on the challenges page (keeps ?difficulty= deep links resolving without scroll). Stops the heaviest island blocking initial load per the CLAUDE.md above-the-fold rule.
- Navbar: bind the three islands to their breakpoint with client:media so a desktop visitor no longer hydrates the mobile ThemeToggle + MobileMenu (focus-trap/inert/matchMedia) and vice-versa; also makes the two-toggle desync structurally impossible (one live per viewport).
- trim the ChallengesFilter SSR props: drop the never-read ChallengeEntry.topics field and cap learnings to the 3 the card previews (was serializing the full array into every page's HTML) — ~29.8KB -> ~25.4KB island props/page.
- reconcile the  store docs with reality: it IS a persistentAtom (owns the localStorage write); document the never-read-at-render invariant in theme.ts + CLAUDE.md (the old text wrongly called it a plain atom).
- remove a stale TODO in content.config.ts (architectureDiagram asset wiring is implemented).
- all 139 e2e pass.

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
Addresses the top Astro-idioms audit finding (client-side DOM injection of static chrome caused layout shift and vanished with JS off):

- markdown-pipeline.mjs now emits the full code-block chrome (language-label header + Copy button + flush body) for prose <pre><code> at build time; language is read from the fenced class, so labels are correct (was always "code" when injected client-side)
- SolutionBlocks.astro renders the same structure for solution code blocks (astro-icon Copy button), replacing the client-injected button
- Layout.astro is reduced to a thin click-wirer over [data-copy-code] buttons (removed all DOM restructuring); keeps the polite live region for the copy announcement
- result: code-block header + label + button are in the static HTML, so no CLS and they work with JS disabled; verified no-JS (5 headers/buttons/labels present) + wired-on-JS (15/15) + all 139 e2e
- document the loader's digest gotcha: the digest is over YAML only, so pipeline-code changes need a local node_modules/.astro cache clear (CI's fresh build is unaffected)

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
…O, and content

Technical stability:
- `content.config.ts`: add `RenderedLevel` interface replacing opaque `Record<string, unknown>` return type; assert YAML slug matches directory name at build time
- `solutions.ts`: split opaque error into two specific messages (path vs missing export)
- `CommunitySidebar.astro`: wrap `displaySnippet(post)` in `stripHtml()` to prevent raw Discourse HTML appearing as text
- `challenges/[...tag].astro`: fetch `getCollection` once in `getStaticPaths` and pass adventures + entries + tags as props instead of calling it twice at render time
- `about.astro`, `contribute.astro`: replace hardcoded `LEADER_SECTIONS` arrays (stale build-time snapshot) with `<CommunityLeaders>` component reading from the CI-refreshed JSON
- `sponsors.astro`: replace hardcoded brand secondary line with `BRAND_SECONDARY_LINE_PARTS.join(" ")`
- `markdown-pipeline.mjs`: document abbrExpansionCounter synchronous-iteration assumption; add `console.warn` when stripping non-public hrefs
- `sitemap.xml.ts`: filter locked solutions (deadline not yet past) from the sitemap

Performance:
- `index.astro`: pass `visibleAdventures.length` (the 6 shown) not `adventureCount` (total) to the ARIA live region count

SEO:
- `solution.astro`: branch `description` meta tag on `unlocked` so locked pages don't claim to show a walkthrough

Accessibility:
- `Navbar.astro`: wrap `<nav>` in `<header>` to provide the banner landmark
- `CollapsibleSection.astro`: move heading outside `<details>` as `.sr-only` for document outline; replace heading element inside `<summary>` (forbidden by ARIA spec) with `<span>`
- `MobileMenu.vue`: change static `aria-label="Menu"` to dynamic `:aria-label="open ? 'Close menu' : 'Open menu'"`
- `StarterNudge.vue`: add persistent `aria-live="polite" aria-atomic="true"` wrapper so AT users are notified when nudge appears post-hydration
- `ChallengesFilter.vue`: remove `aria-controls` on disclosure buttons (near-zero AT support; non-standard pairing with `role="group"`)
- `handbook.astro`: remove `tabindex="0"` from `<abbr>` inside `<h3>` (unexpected Tab stop)
- `solution.astro`: add `.sr-only` `<h2>Solution Steps</h2>` when `solution.context` is absent to bridge H1 to H3 heading gap
- `brand.astro`: remove always-active `aria-current="location"` from TOC links (no scroll-spy; was permanently marking "Mission")
- `about.astro`: fix board portrait `alt={member.name}` to `alt=""` (decorative, name is adjacent visible text); fix internal adventure link using `docs-ext-link` to use internal link utilities
- `e2e/a11y.spec.ts`: fix `collectFocusViolations` cycle detection to require `seenKeys.size > 1` before breaking, preventing early exit when two identical buttons share the same key

Content / tone:
- `e2e/smoke.spec.ts`: derive beginner solution page title from the solution module instead of hardcoding
- All files: replace em dashes in code comments with commas or colons per style rules

Docs:
- `ADVENTURES.md`: note that YAML `slug` must match the adventure directory name
- `ACCESSIBILITY.md`: add rules for navbar-in-header and heading-not-in-summary
- `styleguide.md`: clarify `docs-ext-link` is for external links only
- `CLAUDE.md`: add `rm -rf .astro` pipeline-cache bust to Commands table

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
- Fix solution page sidebar `lg:top-8` → `lg:top-28` (was scrolling under fixed navbar)
- Fix adventure detail `isLive` to use `isAdventureLive()` (was ignoring level deadlines)
- Fix `SolutionBlocks` null guard on `calloutConfig[variant]` (unknown variant crashed build)
- Fix `ChallengesFilter` tags pill: inline `:style` was overriding `pill-active` class
- Fix `adventure-derive`: filter out services with no `port` (was emitting "Port undefined:")
- Fix `content.config.ts` watcher: `watcher?.add()` before digest-skip so all YAML files are watched in dev HMR
- Fix `renderRewards` return type `Record<string, unknown>` → `Promise<AdventureRewards>`
- Fix `contributors.ts`: `about` → `aboutHtml` to match `Adventure["contributor"]` type
- Fix `types.ts`: `intro` optional (matches Zod schema); remove dead `TopPlayer`/`solvedCount`/`topPlayers`
- Fix `community-data.ts`: wrap `JSON.parse` in try/catch (malformed JSON no longer crashes build)
- Add `aria-controls` to both mobile dropdown triggers in `ChallengesFilter`
- Add live status to `aria-label` in `AdventureCard` and `ChallengesFilter` filtered cards
- Move heading inside `<summary>` in `CollapsibleSection` (eliminated duplicate SR heading)
- Add `onUnmounted` cleanup for `astro:page-load` listener in `ConsentBanner`
- Remove duplicate `isLive` closure in `challenges.ts` (was identical to exported `isAdventureLive`)
- Remove duplicate `COMMUNITY_URL` constant in `content.config.ts` (now imported from `lib/site`)
- Remove duplicate `AdventureContributor` type in `team.ts` (now imported from `types.ts`)
- Remove redundant Tailwind `uppercase` from `SectionLabel` (CSS class already applies it)
- Remove dead `adventureCount` variable in `index.astro`
- Add `npm run sync` + `npm run lint` gates before build in `deploy.yml`
- Fix stale React-era paths in `validate-docs.yml` (`src/hooks/**`, `src/data/constants.ts`)
- Add `lint:reuse` script; rename package from `astro-spike` → `offon-website`
- Add `/404/` to a11y spec PAGES array
- Add `granted→reset→denied`, `denied→reset→granted`, and GPC transition tests to `consent.spec.ts`
- Auto-fix pre-existing indentation warnings in `StarterNudge.vue`

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
- Fix missing BRAND_NAME/SITE_URL/COMMUNITY_URL imports in Layout.astro;
  JSON-LD template strings referenced them without importing
- Fix annotateExternalLinks regex to match href in any attribute
  position, not just when href comes first (rehype-sanitize reorders)
- Fix preview.yml download-artifact@v8 to v7 to match upload@v7
- Fix sync-adventure.yml shell injection; move inputs.adventure_url
  to an env var instead of direct interpolation in run:
- Add aria-label to ChallengesFilter mobile difficulty and tags
  trigger buttons; upgrade hydration from client:idle to client:load
- Remove dead hook field from RenderedLevel and content.config.ts
- Remove exported resetAbbrCounter (zero call sites confirmed)
- Extract PROPOSE_ADVENTURE_URL to site.ts; replace three hardcoded
  copies in index.astro, contribute.astro, handbook.astro
- Replace hardcoded CURRENT_YEAR with new Date().getFullYear()
- Use BRAND_NAME/SITE_URL/COMMUNITY_URL constants in JSON-LD strings
- Add npm run sync and Playwright browser cache to a11y-scan.yml
- Replace fragile awk YAML parser in validate-adventures.yml with
  node/yaml for reliable level ID extraction
- Fix em dashes in deadline.mjs, markdown-pipeline.mjs, types.ts
- Add trust-model comment to html fields in solutions/types.ts
- Fix stale comment in astro.config.mjs referencing old file path

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
- Resolve all 11 merge conflicts in favour of Astro stack
- Take CHALLENGES_REPO_URL and PROPOSE_ADVENTURE_URL from main into
  src/lib/site.ts (constants were added in parallel on main)
- Remove 6 modify/delete conflicts: React files deleted in Astro branch
  (constants.ts, Index.tsx, Contribute.tsx, CommunityGuide.tsx,
  communityGuide.test.tsx, generate-solutions.mjs)
- Keep Astro version of validate-adventures.yml (node/yaml parser
  replaces fragile awk YAML extraction)
- Resolve CLAUDE.md: use .ai/templates/ paths from main, keep HEAD
  consent state machine table (Astro/nanostore variant)
- Resolve README.md: drop React-only scripts, update template paths to
  .ai/templates/
- Resolve package.json: keep Astro dependency tree; incorporate
  engines/overrides security patches and higher semver versions from main
- Regenerate package-lock.json for updated dependency versions

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
- Restore ConsentBanner.vue to match production: title heading, full
  Google Analytics description with Privacy Policy link, role=region +
  aria-labelledby, two-column layout, shadow-lg, "Accept Analytics"
  button text, proper aria-labels, h-11 w-11 cookie button with
  safe-area positioning, "Change cookie preferences" label, mounted
  guard to prevent SSR banner flash, focus management on all state
  transitions
- Fix MobileMenu.vue: static "Menu" aria-label (eliminates
  double-announce with aria-expanded), aria-current on active
  drawer links, astro:after-swap listener for View Transitions
- Fix ThemeToggle.vue: announce theme change via #theme-status
  live region in Layout.astro
- Fix ChallengesFilter.vue: radiogroup + roving tabindex pattern for
  difficulty pills (was role=group + aria-pressed)
- Add explicit animate-ping gate inside prefers-reduced-motion block
- Layout.astro: add Inter 700 font preload, theme-status sr-only span,
  SITE_NAME meta author, X/Bluesky in Organization sameAs, use
  BRAND_SHORT_DESCRIPTION in WebSite JSON-LD
- Remove dead slugify export from src/lib/markdown.ts
- Remove dead LEVEL_DIFFICULTY_BY_ID and LEVEL_ORDER from
  src/lib/level-constants.mjs
- Fix src/lib/deadline.mjs: return far-future date on unrecognized
  timezone (prevents solution silently appearing as unlocked)
- Fix src/lib/markdown-pipeline.mjs: stable content-based abbr IDs
  replacing non-deterministic module-level counter
- Fix stale React comments in types.ts and team.ts
- Import CHALLENGES_REPO_URL from site.ts in BottomCTA.astro
- Add hook field to RenderedLevel interface in content.config.ts
- package.json: remove unused reka-ui; move markdown/unified packages
  to devDependencies (build-time only)
- deploy.yml: add node_modules cache (mirrors preview.yml)
- sync-adventure.yml: remove em dashes, add set -euo pipefail
- preview.yml: move github.event.number to env var
- validate-docs.yml: remove redundant src/lib/site.ts path entry
- public/llms.txt: add per-level URLs for 5 adventures
- public/llms-full.txt: add Dead Reckoning section
- e2e/a11y.spec.ts + smoke.spec.ts: add intermediate/expert solution
  routes for echoes-lost-in-orbit
- e2e/consent.spec.ts: fix button locators for renamed buttons; add
  granted-to-denied and GPC+stored-granted transition tests
- ACCESSIBILITY.md: fix 5 stale references (axe file, Vitest claim,
  root.tsx, Layout.tsx, Radix UI Tooltip/Abbr)
- ADVENTURES.md: remove generate-adventures.mjs refs, barrel line
- README.md: fix sitemap description, remove Reka UI tooltip ref

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
…ents

- Add Vitest with happy-dom environment, @vue/test-utils for component
  tests, and @vitest/coverage-v8 for coverage reporting
- src/test/setup.ts: in-memory localStorage stub (Node 26 compatibility),
  Map-backed document.cookie mock (happy-dom 18 ignores expiry deletion),
  window.gtag/dataLayer stubs; per-test reset via global beforeEach
- src/test/lib/deadline.test.ts (23): all timezone abbreviations, unknown
  timezone returns far-future sentinel + calls console.error, format edge
  cases, non-string input throws
- src/test/lib/markdown.test.ts (41): stripLinks tag/attribute removal,
  stripHtml all entity forms (named, decimal, hex), combined stripping
- src/test/lib/markdown-pipeline.test.ts (44): mdToInline/mdToBlock inline
  formatting, external link annotation, sanitization (script/style stripped),
  abbr expansion (stable content-based IDs, data-title, aria-describedby),
  code block chrome, array variants
- src/test/lib/utils.test.ts (39): formatDeadline timezone handling,
  isDeadlinePast past/future/invalid/TODO, escapeHtmlAttr all four chars
- src/test/lib/difficulty.test.ts (18): DIFFICULTY_VAR mappings,
  difficultyStyle CSS var output per level, unknown level fallback
- src/test/lib/challenges.test.ts (46): tagToSlug special chars,
  sortAdventuresByMonthDesc order/stability, isAdventureLive deadline gating,
  getChallengeData shape/URL format/learnings cap/tag dedup
- src/test/lib/level-constants.test.ts (7): LEVEL_DIFFICULTY_BY_EMOJI
  all entries, uniqueness, canonical membership
- src/test/stores/consent.test.ts (48): all 11 state machine transitions,
  module-guard tests via vi.resetModules() + dynamic import, firePageView
  gating, trackClicks delegation and link_url extraction, cookie clearing,
  GPC detection, malformed JSON resilience
- src/test/components/ConsentBanner.test.ts (35): SSR safety (mounted guard),
  all three consent states (null/granted/denied), ARIA attributes, click
  handlers, reactive DOM transitions
- src/test/components/ThemeToggle.test.ts (23): aria-label strings, html
  classList mutation, #theme-status announcement with fake timers, $theme sync
- src/test/components/MobileMenu.test.ts (22): aria-expanded toggle,
  aria-current on active link, external links excluded, Escape key close
- src/test/components/ChallengesFilter.test.ts (56): radiogroup structure,
  aria-checked transitions, arrow-key navigation with wrap, URL sync via
  history.replaceState spy, tag group aria-pressed, mobile dropdowns
- Fix ConsentBanner.vue: watch(consent, ...) instead of watch($consent, ...)
  so Vue's watcher fires correctly (raw nanostores atom is not a valid Vue
  watch source; focus management callbacks were silently never firing)
- vitest.config.ts: 80% line/function coverage thresholds; excludes
  community-data.ts and solutions.ts (fs/import.meta.glob, untestable without
  integration harness)
- package.json: add test:unit, test:unit:watch, test:unit:coverage scripts
- CLAUDE.md + README.md: document unit test commands and add test:unit to the
  pre-submit checklist (step 5, before e2e)

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
- Replace astro-icon + lucide-vue-next with unplugin-icons (unified icon
  solution for both .astro components and Vue islands)
- Add Icons({ compiler: 'vue3' }) to astro.config.mjs vite plugins;
  remove icon() integration and astro-icon import
- Add Icons plugin to vitest.config.ts so test environment resolves
  ~icons/lucide/* virtual modules
- Create src/env.d.ts with unplugin-icons/types/vue3 triple-slash
  reference for TypeScript resolution of ~icons/lucide/* imports
- Create src/lib/lucide-icons.ts: central registry of 42 Lucide icon
  components for dynamic-name lookups in .astro templates
- Migrate 32 .astro files from <Icon name="lucide:..."> to static
  import + direct component usage
- Migrate 5 .vue files from lucide-vue-next named imports to
  ~icons/lucide/* individual imports; replace :size shorthand with
  explicit width/height props
- Handle 5 dynamic-icon locations (AdventureIcon, CommunitySection,
  ChallengeHighlights, CommunityLeaders, adventures/index) via
  LUCIDE_ICONS map lookup
- Remove dead icon string fields from SolutionBlocks calloutConfig
- Fix unused beforeEach import in MobileMenu.test.ts
- Remove no-longer-needed eslint-disable directives from test files

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
- Update astro.config.mjs: remove astro-icon integration, add
  unplugin-icons Vite plugin
- Update package.json: remove astro-icon + lucide-vue-next deps, add
  unplugin-icons
- Update vitest.config.ts: add Icons plugin for test module resolution
- Migrate 32 .astro files from <Icon name="lucide:..."> to static
  ~icons/lucide/* imports and direct component usage
- Migrate 5 .vue files from lucide-vue-next to ~icons/lucide/* imports;
  replace :size prop with explicit width/height
- Handle dynamic icon locations via LUCIDE_ICONS map lookup
- Remove dead icon string fields from SolutionBlocks calloutConfig
- Update stale astro-icon references in comments
- Fix unused import in MobileMenu.test.ts; remove stale eslint-disable
  directives from test files
- Update README.md to reflect unified unplugin-icons icon system

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
…ribute pages

- Both pages were missing "challenge-rockstars" from their sections array
  passed to CommunityLeaders, carried over from when the Astro migration
  hardcoded community data snapshots that predated the section having users
- Restores parity with home, adventures, and challenges pages which
  correctly include it

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
- Difficulty pills use role="radio" (not role="button") after the a11y
  audit refactor; Playwright getByRole uses ARIA role so the old
  getByRole("button") locator never matched and the test timed out
- Update to getByRole("radio") to match the current rendered role

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
- Add BRAND_SLOGAN constant to site.ts; fix home page title to
  use it instead of the placeholder "Learn Open Source by Doing"
- Fix adventure-derive.mjs: import BRAND_NAME from site.ts instead of
  hardcoding; guard against undefined difficulty in meta description builder
- Fix difficulty.ts: narrow key type to discriminated union; add "starter"
  fallback so unknown difficulties never produce broken CSS variable names
- Fix solutions.ts: use @/ alias for imports; remove dead SolutionStep re-export
- Fix deadline.mjs: export UNRESOLVABLE_DEADLINE sentinel constant
- Fix utils.ts: hoist monthNames to module scope; add radix to parseInt calls
- Fix consent.ts + consent.test.ts: derive cookie_expires from CONSENT_EXPIRY_MS
  instead of a magic number
- Fix MobileMenu.vue: extract anonymous astro:after-swap listener to named
  function so it can be removed in onBeforeUnmount
- Fix ThemeToggle.vue: store setTimeout IDs; clear timers before each new pair
  and in onUnmounted to prevent stale announcements
- Fix community-data.ts: log parse errors instead of swallowing them silently
- Replace inline BottomCTA and SectionLabel duplicates in about.astro and
  contribute.astro with the canonical components
- Fix ConsentBanner.test.ts: correct inaccurate comment claiming watch($consent)
  is an invalid Vue source; add focus management assertions for null→granted and
  decided→null transitions
- Add adventure-derive.test.ts: full unit tests for all five exported functions
- Update difficulty.test.ts: test for the new fallback behavior instead of the
  old broken undefined-in-CSS-variable behavior
- Add missing adventures to e2e/smoke.spec.ts (blind-by-design,
  building-cloudhaven, lex-imperfecta, the-ai-observatory) and
  e2e/a11y.spec.ts (echoes-lost-in-orbit, building-cloudhaven,
  lex-imperfecta, the-ai-observatory)

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
- StarterNudge adventure link was missing focus-ring; WCAG 2.4.7 requires
  all interactive elements to have a visible focus indicator
- Dismiss button was 24×24px (min-h-6 min-w-6); raised to 32×32px to
  comfortably exceed the WCAG 2.5.8 24×24px touch target floor

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
…zoom

The verification step renders a <pre> inside a flex-item <span> with no
min-w-0, allowing the span to expand to the pre's intrinsic content width
and overflow the page at narrow viewports. Adding min-w-0 flex-1 lets the
span shrink to available space so overflow-x:auto on the pre takes effect.

Fixes the failing 200% zoom e2e test on echoes-lost-in-orbit/beginner.

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
- Correct CLAUDE.md UI primitives entry: Reka UI is not used; the abbr
  tooltip is a plain JS portal in Layout.astro
- Add missing blank line before list in Capitalisation section (MD032)
- Remove stale Phase 1 comment and Reka UI reference from _app.ts

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
- Initialise a lazy Shiki singleton (github-dark + github-light themes,
  langs loaded on demand) in markdown-pipeline.mjs
- Make renderCodeBlockChrome async; highlight recognised languages via
  Shiki defaultColor:false mode, which emits --shiki-light/--shiki-dark
  CSS custom properties on each token span
- Unknown languages or Shiki errors fall back to plain sanitised code
- Add CSS rules in index.css to apply the Shiki custom properties in
  dark (default) and light modes
- Update markdown-pipeline test to strip Shiki span tags before
  asserting code content is preserved

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
- Replace all stale React/TSX component entries with accurate Astro/Vue docs
- Document all 36 components: props, slots, hydration directive, a11y notes
- Catalogue every CSS utility class from index.css (buttons, pills, focus
  rings, link styles, prose containers, code blocks, animations)
- Add islands hydration quick-reference and new-component checklist
- Remove post-migration caveat banner — guide is now current

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
- Add /adventures/dead-reckoning/ to smoke.spec.ts ROUTES and a11y.spec.ts
  PAGES — it was the only adventure detail page not covered by either suite
- Remove stale references to deleted files (filter-utils.ts, summaries.ts)
  from comments in challenges.ts and adventures/types.ts

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
- Add /contribute/ page to public/llms.txt (was missing from key pages index)
- Update agent-skills/index.json SKILL.md digest after heading change
- Replace em dashes with hyphens/colons/commas across all source, test,
  store, style, public, and data files per project style rules
- llms-full.txt: use colon separator in level headings (Beginner: Name)
  instead of em dash; fix title heading
- lex-imperfecta adventure.yaml: replace em dashes in narrative content

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
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.

1 participant