Skip to content

Perf/speed index improvements#111

Merged
sinduri-g merged 12 commits into
mainfrom
perf/speed-index-improvements
Jun 5, 2026
Merged

Perf/speed index improvements#111
sinduri-g merged 12 commits into
mainfrom
perf/speed-index-improvements

Conversation

@sinduri-g
Copy link
Copy Markdown
Contributor

@sinduri-g sinduri-g commented Jun 4, 2026

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)

sinduri-g added 2 commits June 4, 2026 15:12
- Change Syne font-display from swap to optional (h1/h2 headings).
  swap caused FOUT: the LCP element repainted when Syne loaded. With
  optional and the existing Syne 700 preload in root.tsx, the font
  renders in the initial paint window on most connections with no
  swap repaint.

- Remove dead Syne 600 @font-face declarations. No CSS rule assigns
  font-weight 600 to any Syne element (h1/h2 use 700). No preload
  existed for them either.

- Shorten hero fadeUp animation: duration 0.6s to 0.35s, delays
  halved, from-opacity raised from 0 to 0.7. Above-fold content is
  no longer fully invisible on initial paint, directly improving
  Speed Index.

- Scope will-change to first three firefly elements. Previously
  applied to all 8, violating the <=3 simultaneous will-change rule
  in PERFORMANCE.md.

- Remove dead firefly CSS rules for :nth-child(9-12). Hero.tsx
  renders 8 spans; rules 9-12 were never matched. Update mobile
  hide comment to reflect the real count (7-8, not 7-12).

- Replace hardcoded "offon.dev" string in ConsentBanner.tsx and
  Footer.tsx with the SITE_NAME constant from src/data/constants.ts.

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

size-adjust fallback for Syne (font-display: optional):
- Add 'Syne Fallback' @font-face using local('Arial') with metrics measured
  via canvas.measureText() on the live site: size-adjust 114.62%,
  ascent-override 81.14%, descent-override 23.56%. When the optional load
  window is missed, h1/h2 render in a metric-adjusted Arial that keeps
  character widths and line heights close to Syne so the fallback looks
  intentional rather than broken.
- Insert 'Syne Fallback' into --font-heading and the h1/h2 @layer base rule.

Motion guard alignment (ACCESSIBILITY.md + PERFORMANCE.md):
- Move .animate-fade-up*, .animate-marquee, and .firefly animations inside
  @media (prefers-reduced-motion: no-preference) instead of defining them
  unconditionally and suppressing with a reduce override. Both approaches
  are functionally equivalent but the no-preference guard is the pattern
  the project docs prescribe. Remove the now-redundant reduce block.

Deduplicate ALL_LEVEL_SUMMARIES (filter-utils):
- Export ALL_LEVEL_SUMMARIES from src/data/adventures/filter-utils.ts.
  Challenges.tsx had an identical module-level flatMap that reproduced
  the same projection already done by getLevelSummariesByFilters. Remove
  the local ALL_LEVELS constant and import the shared export instead.

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

github-actions Bot commented Jun 4, 2026

PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-06-05 07:08 UTC

sinduri-g added 10 commits June 4, 2026 15:34
- styleguide.md animations table: correct fadeUp values to match the
  current implementation (opacity 0.7 start, 8px translate, 0.35s
  duration, halved delays). Add note that all animation classes live
  inside @media (prefers-reduced-motion: no-preference).
- styleguide.md firefly entry: correct duration range (5.5-8.5s, not
  6.5-11s), document the will-change cap at three particles.
- styleguide.md fonts table: remove Syne weight 600 -- the @font-face
  declaration was removed; only weight 700 is declared and used.
- filterUtils.test.ts: add three tests covering ALL_LEVEL_SUMMARIES
  (count, shape, and equality with getLevelSummariesByFilters([], null)).

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
The /challenges page is the canonical listing. /adventures now redirects
there so existing links and breadcrumbs in AdventureDetail and
ChallengeDetail continue to work without 404s.

- Add src/pages/redirects/ChallengesRedirect.tsx (client-side redirect
  to /challenges, following the HandbookRedirect pattern)
- routes.ts: replace Adventures.tsx with ChallengesRedirect for the
  /adventures route; keep /adventures/:id and /adventures/:id/levels/:levelId
  unchanged since those are real content pages
- Footer: replace Adventures link with Challenges pointing to /challenges
- Challenges hero description: clarify the adventure and challenge
  structure with a community angle
- sitemap.xml: remove /adventures/ listing entry (redirect routes are
  not indexed per project rules)
- react-router.config.ts: remove /adventures from prerender array
- e2e/smoke.spec.ts, seo.test.ts, prerender.test.ts: remove /adventures
  listing route entries; the individual adventure pages are untouched
- footer.test.tsx: update Adventures assertion to Challenges
- README.md: mark /adventures as a redirect in the routes table

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
- Replace font-semibold with font-bold on font-heading constants in
  Accessibility.tsx and Privacy.tsx — Syne 600 @font-face was removed,
  only weight 700 is declared
- Delete syne-latin-600-normal.woff2 and syne-latin-ext-600-normal.woff2
  from public/fonts/ — unreferenced after the @font-face removal
- Update breadcrumb href from /adventures to /challenges in
  AdventureDetail.tsx and ChallengeDetail.tsx; label stays Adventures
  to describe the content category; JSON-LD BreadcrumbList item URL
  at position 2 updated to /challenges/ accordingly
- Update Contribute.tsx Browse adventures link to /challenges
- Update NotFound.tsx helpful links card from Adventures//adventures
  to Challenges//challenges
- Update notFound.test.tsx assertion to match the renamed link
- Replace /adventures with /challenges in e2e/a11y.spec.ts PAGES — the
  redirect has no prerendered HTML so the preview server served a
  directory listing that failed the WCAG 2.5.8 touch-target check
- Remove orphaned blank lines in react-router.config.ts,
  public/sitemap.xml, and src/test/prerender.test.ts

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
buildPageMeta normalizes every page canonical to end with /. The sitemap
and GSC both index trailing-slash URLs. Without this change, client-side
navigation produced URLs that disagreed with their own canonical tag,
forcing Google to reconcile /challenges vs /challenges/ on every crawl.

Static pages updated: Navbar, Footer, SponsorStrip, ChallengesGrid,
ConsentBanner, Contribute, Accessibility, NotFound.

Dynamic pages updated: AdventureCard, ChallengeBuildersSection,
FilteredLevelCard, OtherLevelsCard, StarterNudge, TagChips,
AdventureDetail level links, ChallengeDetail breadcrumb href.

All href assertions in footer.test, navbar.test, notFound.test,
adventureCard.test, adventureDetail.test, challenges.test,
challengesGrid.test, and filteredLevelCard.test updated to match.

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
- Restore Adventures.tsx at /adventures/ (real page, not a redirect)
- Remove ChallengesRedirect.tsx; /adventures is no longer a redirect
- Add /adventures back to prerender array, sitemap.xml, smoke/seo/prerender tests
- Fix breadcrumb label and JSON-LD in AdventureDetail + ChallengeDetail:
  item was pointing to /challenges/ while label said "Adventures" — now
  both the href and the JSON-LD item correctly point to /adventures/
- Fix getLevelSummariesByFilters: AND semantics (every) → OR semantics (some)
  so multi-tag filter returns adventures that match any selected tag
- Move DIFFICULTIES + Difficulty type into filter-utils.ts (single source
  of truth); ChallengeFilters re-exports Difficulty for call sites
- Simplify ALL_LEVEL_SUMMARIES to getLevelSummariesByFilters([], null)
- Challenges.tsx: store active filters in ?topics= and ?difficulty= query
  params instead of component state, enabling shareable/bookmarkable URLs
- Layout.tsx ScrollToTop: suppress scroll reset for /challenges ↔
  /challenges/:tag navigations to avoid jarring scroll jumps on filter change
- index.css: consolidate all motion rules (animations + fireflies +
  will-change) inside @media (prefers-reduced-motion: no-preference) so
  GPU compositing layers are never allocated for reduced-motion users
- index.css: remove opacity from fadeUp keyframe (translate-only animation)
- ChallengesGrid: show adventure + challenge count in unfiltered view
- Update styleguide.md to reflect fadeUp keyframe change
- Update URL-state tests in challenges.test.tsx and OR-semantics tests
  in filterUtils.test.ts

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
Replace the copy-of-challenges page with a focused landing page:

- New hero: "Real-World Scenarios. Practical Skills." with a Codespaces
  CTA linking to /challenges/
- "How Adventures Work" section: 3-step explainer (Pick a Scenario,
  Launch in Codespaces, Apply / Fork / Build) using BookOpen, Laptop,
  and GitFork icons from lucide-react
- Adventure card grid using AdventureCard with a count header and a
  "Filter challenges by technology" link to /challenges/
- ChallengeBuildersSection with CommunityLeaders sidebar retained so
  contributor credit and leaderboard stay visible
- Updated page title and meta description to match the new purpose
- Updated prerender test title and smoke test regex to match new title
- Fix challenges.test: scope the "replaces adventure cards" assertion to
  exclude #challenge-builders, which always renders adventure links
  regardless of filter state

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
The builders section was removed from Challenges when Adventures
redirected to it. Now that Adventures is its own page again, both pages
carry the builders and community leaders sidebar independently.

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

- Fix hasFiltered hydration mismatch: initialize to false, sync from
  searchParams in useEffect to avoid prerender/client state divergence
- Fix count-paragraph contrast: all visible count paragraphs in
  Challenges.tsx and ChallengesGrid.tsx use text-muted-foreground, not
  text-primary (amber fails light-mode contrast)
- Fix Adventures and Challenges heading hierarchy: add sr-only h2
  elements so both pages have a visible h1 and logical section headings
- Extract tag-utils.ts from index.ts: SUMMARY_TAGS, TAGS_BY_TYPE, and
  ALL_TOPICS moved to src/data/adventures/tag-utils.ts to avoid pulling
  full adventure data into tag-only consumers
- Add hydration test harness (e2e/hydration.spec.ts): covers all
  prerendered routes, /challenges/?topics= search-param hydration, and
  /challenges with stored light theme in localStorage
- Add README routes regression test (src/test/readme.test.ts): asserts
  every static route in src/routes.ts appears in README.md routes table
- Remove em dashes from docs and comments across CLAUDE.md, PERFORMANCE.md,
  ACCESSIBILITY.md, styleguide.md, and source files
- Update styleguide.md, README.md docs to reflect tag-utils extraction
  and new test patterns

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
- Extract InlineProse component with BLOCK_ELEMENT_RE covering all 15
  block-level elements (p, ul, ol, blockquote, h1-h6, pre, table, hr,
  figure, div) as the single safe renderer for author-prose HTML fields
- Replace bare <p dangerouslySetInnerHTML> at 6 sites: ScenarioSection,
  RewardsCard (tier.description), AdventureDetail (story, backstory items,
  contributor.about), ChallengeBuildersSection (contributor.about)
- Add generator hard error for rewards.ranking_note producing block-level
  HTML; that field renders inside <span> inside <p> and cannot use the
  component fix, so invalid content must fail the build instead
- Add src/test/inlineProse.test.tsx with 91 tests covering every block
  element, inline cases, className ordering, and regex boundary conditions
- Expand e2e/smoke.spec.ts console-error guard to both dark and light mode
  passes; add console.error listener alongside pageerror
- Switch playwright webServer to npm run preview so the 404 fallback copy
  runs before serving; probe a deep route to confirm readiness
- Document InlineProse in styleguide.md with usage rule and rankingNote
  exception

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

- Replace fragile prefix-concat with filter(Boolean).join(" ") so
  whitespace-only or absent className never produces a spurious leading
  space in the rendered class attribute
- Migrate level.intro and level.audience in ChallengeDetail to InlineProse,
  closing the last two bare dangerouslySetInnerHTML sites for author-prose fields
- Remove 30 redundant "does not render <p>/<div>" test assertions that were
  fully implied by the existing tag-name checks; add whitespace className
  edge-case tests to cover the new trim() behaviour
- Surface actual error text in smoke-test assertion messages so CI failures
  are immediately debuggable without re-running locally

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
@sinduri-g sinduri-g merged commit c5ecc1b into main Jun 5, 2026
5 checks passed
@sinduri-g sinduri-g deleted the perf/speed-index-improvements branch June 5, 2026 07:08
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