Perf/speed index improvements#111
Merged
Merged
Conversation
- 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>
Contributor
|
- 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>
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.
Type of change
featnew featurefixbug fixrefactorno behavior changedocs/chore/config/perf/style/securityManual checks