diff --git a/openspec/changes/lighthouse-remediation/.openspec.yaml b/openspec/changes/archive/2026-02-11-remember-theme/.openspec.yaml similarity index 50% rename from openspec/changes/lighthouse-remediation/.openspec.yaml rename to openspec/changes/archive/2026-02-11-remember-theme/.openspec.yaml index 70eb9e0..4465244 100644 --- a/openspec/changes/lighthouse-remediation/.openspec.yaml +++ b/openspec/changes/archive/2026-02-11-remember-theme/.openspec.yaml @@ -1,2 +1,2 @@ schema: spec-driven -created: 2026-02-10 +created: 2026-02-11 diff --git a/openspec/changes/archive/2026-02-11-remember-theme/design.md b/openspec/changes/archive/2026-02-11-remember-theme/design.md new file mode 100644 index 0000000..c8cff3c --- /dev/null +++ b/openspec/changes/archive/2026-02-11-remember-theme/design.md @@ -0,0 +1,62 @@ +## Context + +The site already supports theme selection via a floating notch and persists the user's choice using `localStorage` (key: `site.theme`). Theme selection affects the root document via `html[data-theme]` and is initialized before first paint via an inline head script. + +Two gaps remain: +- Returning users should reliably see their last-selected theme even in environments where `localStorage` is unavailable. +- We need analytics to measure theme switcher usage and preferred themes. + +The site uses Umami for analytics. Most interactions are tracked via `data-umami-event*` attributes, and runtime-only events use `window.umami.track(...)`. + +## Goals / Non-Goals + +**Goals:** +- Persist theme selection across visits with a robust fallback: `localStorage` primary, client-side cookie fallback. +- Apply the stored theme before first paint when possible. +- Emit a deterministic Umami event when a user changes theme via the theme notch. + +**Non-Goals:** +- Server-side rendering of theme choice (the site is statically built; no request-time HTML variation). +- Tracking theme selection on initial page load (only user-initiated changes). +- Adding new UI beyond the existing theme notch. + +## Decisions + +1) Persistence mechanism and precedence + +- **Decision**: Read theme preference in this order: + 1. `localStorage` (`site.theme`) + 2. Cookie (`site_theme`) + 3. Environment signals (forced-colors, prefers-color-scheme) +- **Rationale**: `localStorage` is already in use and provides a stable primary store. Cookies provide a resilient fallback when storage access is blocked or throws. +- **Alternatives considered**: + - Cookie-only: simpler but unnecessary regression from existing behavior. + - URL param: not persistent and adds user-visible noise. + +2) Cookie format and attributes + +- **Decision**: Store `site_theme=` with `Max-Age=31536000`, `Path=/`, `SameSite=Lax`. Set `Secure` when running under HTTPS. +- **Rationale**: First-party cookie with long TTL provides continuity across visits. The cookie is readable from the inline head script for pre-paint initialization. + +3) Analytics event shape + +- **Decision**: Emit a custom Umami event via `window.umami.track("theme_switch", data)` only on user-initiated changes. +- **Event properties**: + - `target_id`: `theme.switch.` + - `placement`: `theme_notch` + - `theme`: new theme value (`dark` | `light` | `high-contrast`) + - `prev_theme`: previous theme value (same enum) when known +- **Rationale**: A dedicated event name makes reporting straightforward (no need to filter general `click`). Using `target_id`/`placement` keeps it compatible with the site's interaction taxonomy. +- **Alternatives considered**: + - Reuse `click` event: consistent, but mixes preference changes into general click reporting. + +4) Avoid tracking initial theme restoration + +- **Decision**: Do not emit `theme_switch` from the head theme-init script. +- **Rationale**: We want to measure explicit user interaction with the notch, not implicit restoration. + +## Risks / Trade-offs + +- Cookie and storage may both be blocked in restrictive environments → fallback to environment signals; no persistence. +- Umami may be disabled/unconfigured or not loaded at event time → guard with `typeof window.umami !== "undefined"` and keep behavior non-fatal. +- Using cookies introduces another persistence layer → must keep `localStorage` and cookie consistent on successful theme changes. diff --git a/openspec/changes/archive/2026-02-11-remember-theme/proposal.md b/openspec/changes/archive/2026-02-11-remember-theme/proposal.md new file mode 100644 index 0000000..1836445 --- /dev/null +++ b/openspec/changes/archive/2026-02-11-remember-theme/proposal.md @@ -0,0 +1,25 @@ +## Why + +Theme choice is part of a user's identity and comfort on the site; returning visitors should land in the theme they previously selected. We also need measurement to understand whether the theme switcher is being used and which themes are preferred. + +## What Changes + +- Persist the user's selected theme across visits so returning users see the last-selected theme immediately. +- Add Umami tracking for theme selection changes so theme switch usage can be measured and segmented. +- Improve robustness of persistence by supporting either localStorage or a client-side cookie (cookie fallback when localStorage is unavailable). + +## Capabilities + +### New Capabilities +- (none) + +### Modified Capabilities +- `site-theming`: Extend theme persistence requirements to explicitly cover returning visits and define acceptable client-side persistence mechanisms / fallback behavior. +- `analytics-umami`: Add a custom event emitted from client-side code for theme selection changes (using Umami's JS API when needed). +- `interaction-tracking-taxonomy`: Define the theme selection event name and required event properties (at minimum `target_id` and `placement`, plus theme metadata). + +## Impact + +- Frontend: update theme switcher behavior in `site/src/layouts/BaseLayout.astro` (persistence/fallback and event emission). +- Analytics: new Umami event(s) added; dashboards/filters can segment by selected theme and placement. +- Specs: update the modified capabilities above to reflect the new requirements. diff --git a/openspec/changes/archive/2026-02-11-remember-theme/specs/analytics-umami/spec.md b/openspec/changes/archive/2026-02-11-remember-theme/specs/analytics-umami/spec.md new file mode 100644 index 0000000..f40103f --- /dev/null +++ b/openspec/changes/archive/2026-02-11-remember-theme/specs/analytics-umami/spec.md @@ -0,0 +1,21 @@ +## ADDED Requirements + +### Requirement: Theme switch tracking event +When Umami is enabled, the site MUST emit a custom event when the user changes theme via the theme switcher UI. + +The site MUST emit the event using Umami's JavaScript API (`umami.track(...)`) so runtime properties can be included. + +The event name MUST be `theme_switch`. + +The emitted event MUST include, at minimum: +- `target_id` +- `placement` +- `theme` + +#### Scenario: Theme switch emits event +- **WHEN** a user selects `high-contrast` in the theme switcher notch +- **THEN** the site emits a `theme_switch` event with `theme=high-contrast` and a stable `target_id` + +#### Scenario: Missing Umami does not break switching +- **WHEN** Umami is not configured or the Umami script is not present +- **THEN** theme switching and persistence still work and no browser error is thrown diff --git a/openspec/changes/archive/2026-02-11-remember-theme/specs/interaction-tracking-taxonomy/spec.md b/openspec/changes/archive/2026-02-11-remember-theme/specs/interaction-tracking-taxonomy/spec.md new file mode 100644 index 0000000..5134cd7 --- /dev/null +++ b/openspec/changes/archive/2026-02-11-remember-theme/specs/interaction-tracking-taxonomy/spec.md @@ -0,0 +1,23 @@ +## ADDED Requirements + +### Requirement: Theme switch event taxonomy +The tracking taxonomy MUST define an event for theme switching. + +The event name MUST be `theme_switch`. + +The `theme_switch` event MUST include, at minimum: +- `target_id` +- `placement` +- `theme` + +The event SHOULD include `prev_theme` when available. + +The taxonomy MUST define the `target_id` namespace for theme switching as: +- `theme.switch.` + +The taxonomy MUST define the `placement` value for the theme switcher notch as: +- `theme_notch` + +#### Scenario: Theme switch target_id is deterministic +- **WHEN** a user selects `light` theme using the theme notch +- **THEN** the event is emitted with `target_id=theme.switch.light` and `placement=theme_notch` diff --git a/openspec/changes/archive/2026-02-11-remember-theme/specs/site-theming/spec.md b/openspec/changes/archive/2026-02-11-remember-theme/specs/site-theming/spec.md new file mode 100644 index 0000000..2bf7fa2 --- /dev/null +++ b/openspec/changes/archive/2026-02-11-remember-theme/specs/site-theming/spec.md @@ -0,0 +1,25 @@ +## ADDED Requirements + +### Requirement: Theme persistence works across visits with fallback +The site MUST persist the user's theme selection across visits so returning users see the last-selected theme. + +The site MUST use client-side persistence and MUST support a fallback mechanism: +- Primary: `localStorage` +- Fallback: a client-side cookie + +The effective theme selection order MUST be: +1) Stored theme in `localStorage` (if available) +2) Stored theme in a cookie (if localStorage is unavailable) +3) Default selection using environment signals + +#### Scenario: LocalStorage persists across a later visit +- **WHEN** a user selects `light` theme and later returns to the site in the same browser +- **THEN** the site initializes in `light` theme before first paint + +#### Scenario: Cookie fallback is used when localStorage is unavailable +- **WHEN** the browser environment blocks `localStorage` access and the user selects `dark` theme +- **THEN** the theme is persisted using a client-side cookie and is restored on the next visit + +#### Scenario: No persistence available falls back to defaults +- **WHEN** both `localStorage` and cookie persistence are unavailable +- **THEN** the site falls back to default theme selection using environment signals diff --git a/openspec/changes/archive/2026-02-11-remember-theme/tasks.md b/openspec/changes/archive/2026-02-11-remember-theme/tasks.md new file mode 100644 index 0000000..fefb89e --- /dev/null +++ b/openspec/changes/archive/2026-02-11-remember-theme/tasks.md @@ -0,0 +1,17 @@ +## 1. Theme Persistence Across Visits + +- [x] 1.1 Add cookie helpers in `site/src/layouts/BaseLayout.astro` to read/write a `site_theme` cookie (Path=/, SameSite=Lax, 1y TTL; Secure on HTTPS) +- [x] 1.2 Update the head theme-init script in `site/src/layouts/BaseLayout.astro` to prefer localStorage, then cookie, then environment signals +- [x] 1.3 Update the theme setter in `site/src/layouts/BaseLayout.astro` to keep localStorage and cookie in sync on user selection (cookie fallback when localStorage throws) + +## 2. Umami Tracking For Theme Switch + +- [x] 2.1 Define event emission on user-initiated theme changes using `window.umami.track("theme_switch", ...)` (guarded when Umami is missing) +- [x] 2.2 Use taxonomy fields for the event payload: `target_id=theme.switch.`, `placement=theme_notch`, include `theme` and `prev_theme` when available +- [x] 2.3 Ensure theme restoration on page load does NOT emit `theme_switch` (only explicit user interaction) + +## 3. Verification + +- [x] 3.1 Run `npm run build` and verify output includes cookie read fallback in the head init script +- [x] 3.2 Run `npm test` and ensure no new failures are introduced +- [x] 3.3 Manual: switch theme, reload, and confirm persistence; repeat with localStorage disabled to confirm cookie fallback diff --git a/openspec/changes/lighthouse-remediation/design.md b/openspec/changes/lighthouse-remediation/design.md deleted file mode 100644 index 5711d72..0000000 --- a/openspec/changes/lighthouse-remediation/design.md +++ /dev/null @@ -1,92 +0,0 @@ -## Context - -Chrome Lighthouse runs against `https://santhoshj.com/` (desktop + mobile) report several audits that prevent a 100 score. - -Inputs: -- Desktop report: `C:\Users\simpl\Downloads\santhoshj.com-20260210T182644.json` -- Mobile report: `C:\Users\simpl\Downloads\santhoshj.com-20260210T182538.json` - -Key failing audits (non-exhaustive): -- Accessibility: `color-contrast` -- SEO: `robots-txt`, `crawlable-anchors` -- Best Practices: `inspector-issues` (Content Security Policy) -- Performance: `render-blocking-insight`, `image-delivery-insight`, `unminified-css`, `unused-css-rules`, `unused-javascript`, `cache-insight` (plus mobile LCP/TTI pressure) - -Constraints: -- Site is a static Astro build served behind Docker Compose + reverse proxy. -- Some assets are third-party (YouTube thumbnails, CloudFront podcast images, Umami script). These can influence some performance/cache audits and must be handled carefully (reduce impact where possible, but avoid breaking content). -- Service worker script MUST remain at stable URL `/sw.js` and should not be versioned via query string. - -## Goals / Non-Goals - -**Goals:** -- Achieve a 100 Lighthouse rating on the homepage in all categories (Performance, Accessibility, Best Practices, SEO) using the audits provided. -- Make contrast compliant (WCAG AA) for secondary text and pill/chip labels. -- Ensure SEO hygiene: - - `robots.txt` includes a valid (absolute) sitemap URL - - interactive elements do not render anchor tags without `href`. -- Remove DevTools Issues panel findings related to CSP by implementing an explicit CSP baseline that matches site needs. -- Reduce render-blocking requests and improve asset delivery so mobile LCP/TTI is consistently fast. - -**Non-Goals:** -- Redesigning the site's visual identity or typography scale. -- Removing all third-party content sources (YouTube thumbnails, podcast cover images) or analytics. -- Building a full PWA manifest/offline-first experience (out of scope). - -## Decisions - -1. **Contrast remediation via token-level CSS adjustments** -Rationale: Lighthouse flags specific selectors in card footers and pills. Fixing contrast at the token level (e.g., `--muted`, pill bg/fg) avoids per-component overrides and reduces regressions. -Alternatives: -- Component-local overrides (harder to maintain, easy to miss). - -2. **Robots sitemap MUST be absolute** -Rationale: Lighthouse treats `Sitemap: /sitemap-index.xml` as invalid. Robots will be updated to point at the full absolute URL. -Alternatives: -- Switch to `sitemap.xml` only (not desired; site already emits sitemap-index). - -3. **No anchor elements without href in rendered HTML** -Rationale: Lighthouse flags the media modal anchors because they exist at load time without `href` (populated later by JS). Fix by using buttons for non-navigational actions, and ensuring any `` is rendered with a valid `href` in initial HTML (or not rendered until it has one). -Alternatives: -- Keep anchors and set `href="#"` (still crawlable but semantically wrong, and can degrade UX). - -4. **CSP baseline implemented at the edge (reverse proxy), compatible with site JS** -Rationale: DevTools Issues panel reports CSP issues. Implement a CSP that matches current needs (site inline scripts, Umami, fonts, images, frames) and remove/avoid inline scripts where possible to keep CSP strict. -Alternatives: -- Avoid CSP entirely (does not resolve audit and leaves security posture ambiguous). - -5. **Font delivery: prefer self-hosting to remove render-blocking third-party CSS** -Rationale: Lighthouse render-blocking points to Google Fonts stylesheet. Self-hosting WOFF2 and using `@font-face` reduces blocking and improves reliability. -Alternatives: -- Keep Google Fonts and rely on preload hints (still incurs third-party CSS request; harder to reach 100). - -6. **CSS delivery: move global CSS into the build pipeline (minified output)** -Rationale: Lighthouse flags unminified/unused CSS. Keeping `global.css` as a raw file in `public/` makes it harder to guarantee minification/unused pruning. Prefer having Astro/Vite handle minification and (where possible) pruning. -Alternatives: -- Add a bespoke minify step for `public/styles/global.css` (works, but adds build complexity and can drift). - -7. **Caching headers: stable URLs get revalidated; fingerprinted assets get long-lived caching** -Rationale: Lighthouse cache-lifetime audit penalizes short cache lifetimes on first-party CSS/JS. For assets that are not fingerprinted (e.g., `/sw.js`, possibly `/styles/global.css`), use `no-cache` or revalidation to avoid staleness. For fingerprinted build outputs, use long-lived caching. -Alternatives: -- Querystring versioning on SW (known pitfall; can break update chain). - -## Risks / Trade-offs - -- **[CSP breaks site behavior]** → Start with Report-Only CSP, verify in production, then enforce. Prefer eliminating inline scripts to avoid `unsafe-inline`. -- **[Self-hosting fonts changes appearance slightly]** → Keep the same Manrope font files and weights, verify typography visually. -- **[Optimizing images reduces perceived sharpness]** → Use responsive images and appropriate sizes; keep high-DPR support via srcset. -- **[Third-party cache lifetimes cannot be controlled]** → Focus on first-party cache headers and reduce critical path reliance on third-party where possible. - -## Migration Plan - -1. Reproduce Lighthouse findings from a clean Chrome profile (no extensions) for both mobile/desktop. -2. Apply fixes in small slices (contrast, robots/anchors, CSP, fonts, CSS pipeline/minify, image delivery). -3. Deploy behind the reverse proxy with Report-Only CSP first. -4. Re-run Lighthouse on production URL until 100 is reached and stable. -5. Enable enforced CSP after confirming no violations. - -## Open Questions - -- What exact CSP issue is being reported in the DevTools Issues panel (violation message)? Lighthouse only surfaces the issue type without sub-items. -- Do we want to keep Google Fonts or commit to self-hosting fonts for maximum Lighthouse consistency? -- For cache-lifetime scoring: do we want to introduce fingerprinted CSS output (preferred) or add explicit versioning for `/styles/global.css`? diff --git a/openspec/changes/lighthouse-remediation/proposal.md b/openspec/changes/lighthouse-remediation/proposal.md deleted file mode 100644 index 38089c4..0000000 --- a/openspec/changes/lighthouse-remediation/proposal.md +++ /dev/null @@ -1,37 +0,0 @@ -## Why - -Increase technical robustness by remediating the specific issues flagged by Chrome Lighthouse so the primary surface (homepage) can achieve a 100 score across categories. - -Lighthouse sources: -- `C:\Users\simpl\Downloads\santhoshj.com-20260210T182644.json` (desktop) -- `C:\Users\simpl\Downloads\santhoshj.com-20260210T182538.json` (mobile) - -## What Changes - -- Fix accessibility contrast failures (WCAG AA) for card metadata and pill chips. -- Fix SEO hygiene issues: - - `robots.txt` must reference a valid sitemap URL - - eliminate non-crawlable anchor markup that Lighthouse flags (e.g., anchors without `href` in modal UI) -- Eliminate Best Practices "Issues" panel findings related to Content Security Policy. -- Improve Performance audits that prevent a perfect score, primarily on mobile: - - optimize above-the-fold image delivery (thumbnails/covers) - - reduce render-blocking resources (font CSS) - - ensure CSS/JS delivery is optimized (minification and unused code reduction) - - improve cache lifetimes where applicable for first-party assets, and mitigate third-party cache lifetime penalties where feasible. - -## Capabilities - -### New Capabilities -- `asset-delivery-optimization`: Ensure critical assets (CSS/fonts/images) are delivered in a Lighthouse-friendly way (minified, non-blocking where possible, and appropriately cached) and that mobile LCP is consistently fast. -- `security-headers`: Define and implement a CSP baseline and related headers that eliminate DevTools "Issues" panel findings without breaking third-party integrations. - -### Modified Capabilities -- `wcag-responsive-ui`: Strengthen the baseline to explicitly require sufficient text contrast for secondary/muted UI text and pill/chip labels so the site passes Lighthouse contrast checks. -- `seo-content-surface`: Strengthen robots + sitemap correctness and require that link-like UI uses crawlable markup (valid `href` when an anchor is used). - -## Impact - -- Affected UI/CSS: card footer metadata (`.muted`) and pill styling; modal CTA markup. -- Affected SEO assets: `robots.txt` sitemap line. -- Affected security posture: CSP and related headers (may require changes to how third-party scripts are loaded). -- Affected performance: image source/size strategy for thumbnails/covers; font delivery strategy; CSS/JS build pipeline and cache headers. diff --git a/openspec/changes/lighthouse-remediation/specs/asset-delivery-optimization/spec.md b/openspec/changes/lighthouse-remediation/specs/asset-delivery-optimization/spec.md deleted file mode 100644 index 1497982..0000000 --- a/openspec/changes/lighthouse-remediation/specs/asset-delivery-optimization/spec.md +++ /dev/null @@ -1,47 +0,0 @@ -## ADDED Requirements - -### Requirement: Render-blocking resources are minimized -The site MUST minimize render-blocking resources on the critical path. - -Font delivery MUST NOT rely on a render-blocking third-party stylesheet. - -#### Scenario: Homepage avoids render-blocking font CSS -- **WHEN** Lighthouse audits the homepage -- **THEN** the Google Fonts stylesheet request is not present as a render-blocking resource (fonts are self-hosted or otherwise delivered without a blocking CSS request) - -### Requirement: First-party CSS and JS are optimized for Lighthouse -First-party CSS and JS delivered to the browser MUST be minified in production builds. - -The site MUST minimize unused CSS and unused JavaScript on the homepage. - -#### Scenario: CSS is minified -- **WHEN** a production build is served -- **THEN** `styles/global.css` (or its replacement) is minified - -#### Scenario: Homepage avoids unused JS penalties -- **WHEN** Lighthouse audits the homepage -- **THEN** the amount of unused JavaScript on initial load is below Lighthouse's failing threshold - -### Requirement: Images are delivered efficiently -Images used on listing surfaces MUST be delivered in a size appropriate to their rendered dimensions. - -For thumbnail-like images, the site SHOULD prefer image sources that support resizing or multiple resolutions when feasible. - -#### Scenario: Podcast cover image is not oversized -- **WHEN** the homepage renders a podcast episode card -- **THEN** the fetched cover image size is reasonably close to the displayed size (no large wasted bytes flagged by Lighthouse) - -### Requirement: Cache lifetimes are efficient for first-party assets -First-party static assets (CSS/JS/fonts/images served from the site origin) MUST be served with cache headers that enable efficient repeat visits. - -Non-fingerprinted assets MUST be served with revalidation (e.g., `no-cache` or `max-age=0,must-revalidate`) to avoid staleness. - -Fingerprinted assets (build outputs) MUST be served with a long-lived immutable cache policy. - -#### Scenario: First-party CSS has efficient caching -- **WHEN** Lighthouse audits the homepage -- **THEN** first-party CSS cache lifetimes are not flagged as inefficient - -#### Scenario: Service worker script is revalidated -- **WHEN** the browser checks `/sw.js` for updates -- **THEN** the HTTP cache is bypassed or revalidated so an updated service worker can be fetched promptly diff --git a/openspec/changes/lighthouse-remediation/specs/security-headers/spec.md b/openspec/changes/lighthouse-remediation/specs/security-headers/spec.md deleted file mode 100644 index ef2a5d3..0000000 --- a/openspec/changes/lighthouse-remediation/specs/security-headers/spec.md +++ /dev/null @@ -1,25 +0,0 @@ -## ADDED Requirements - -### Requirement: Content Security Policy baseline -The deployed site MUST include a Content Security Policy (CSP) that is compatible with the site's runtime behavior and third-party integrations. - -The CSP MUST be strict enough to avoid DevTools Issues panel findings related to CSP and MUST NOT rely on a permissive `*` wildcard for script sources. - -The CSP MUST allow: -- the site's own scripts and styles -- the configured analytics script origin (Umami) -- required image origins (e.g., YouTube thumbnail host and podcast image CDN) -- required frame origins (e.g., YouTube and Spotify embeds) - -#### Scenario: No CSP issues logged -- **WHEN** a user loads the homepage in Chrome -- **THEN** no CSP-related issues are reported in the DevTools Issues panel - -### Requirement: Avoid inline-script CSP violations -The site SHOULD minimize the use of inline scripts to avoid requiring `unsafe-inline` in CSP. - -If inline scripts are necessary, the CSP MUST use a nonce-based or hash-based approach. - -#### Scenario: Inline scripts do not require unsafe-inline -- **WHEN** the site is served with CSP enabled -- **THEN** the policy does not require `script-src 'unsafe-inline'` to function diff --git a/openspec/changes/lighthouse-remediation/specs/seo-content-surface/spec.md b/openspec/changes/lighthouse-remediation/specs/seo-content-surface/spec.md deleted file mode 100644 index e1140ea..0000000 --- a/openspec/changes/lighthouse-remediation/specs/seo-content-surface/spec.md +++ /dev/null @@ -1,39 +0,0 @@ -## MODIFIED Requirements - -### Requirement: Sitemap and robots -The site MUST provide: -- `sitemap.xml` enumerating indexable pages -- `robots.txt` that allows indexing of indexable pages - -The sitemap MUST include the blog surface routes: -- `/blog` -- blog post detail routes -- blog page detail routes -- blog category listing routes - -`robots.txt` MUST include a `Sitemap:` directive with an absolute URL to the sitemap (or sitemap index) and MUST NOT use a relative sitemap URL. - -#### Scenario: Sitemap is available -- **WHEN** a crawler requests `/sitemap.xml` -- **THEN** the server returns an XML sitemap listing `/`, `/videos`, `/podcast`, `/about`, and `/blog` - -#### Scenario: Blog URLs appear in sitemap -- **WHEN** WordPress content is available in the cache at build time -- **THEN** the generated sitemap includes the blog detail URLs for those items - -#### Scenario: robots.txt includes absolute sitemap URL -- **WHEN** a crawler requests `/robots.txt` -- **THEN** the response includes a `Sitemap:` directive with an absolute URL (e.g., `https:///sitemap-index.xml`) that Lighthouse and crawlers can parse - -## ADDED Requirements - -### Requirement: Crawlable link markup -The site MUST NOT render anchor elements (``) without an `href` attribute. - -Interactive UI that does not navigate MUST use a `