×ばつ *.md ✅ Done D4 LCP Analytics loading refactor: GA + Meta Pixel eager (accuracy — see note), Clarity + Apollo interaction-gated, Hotjar removed src/metaPixelRouteTracker.js, docusaurus.config.js ✅ Done D5 CLS Stamp intrinsic width/height on raw <img> (dependency-free remark plugin) src/remark/remarkImageSize.js, docusaurus.config.js ✅ Done D6 LCP Preconnects 6 → 4 (follows from D1) docusaurus.config.js ✅ Done D9 A11Y aria-hidden on 5 footer social + 12 decorative component SVGs + 6 inline SVGs in the GSoC guide src/theme, src/components, gsoc/contribution-guide.md ✅ Done D10 DEAD Remove undefined "Aeonik" font-family src/css/custom.css ✅ Done D11 SEC Client source maps (devtool: source-map, client-only) docusaurus.config.js ✅ Done X1 PROC CI guard — fail on new image > 500 KB .github/workflows/asset-budget.yml ✅ Done X3 PROC CI guard — fail on new fonts.googleapis.com/css link .github/workflows/asset-budget.yml ✅ Done X4 A11Y Accessibility-tree well-formedness for AI crawlers resolved by D9 ✅ Done H1 SEO robots.txt / sitemap / canonical / JSON-LD present & valid — ✅ Verified, no change H2 SEO Product / review schema — ⛔ Won't do — see below 📊 Measured performance (Lighthouse, mobile, median of 9 runs) Both rows were measured back-to-back on the same machine, so the delta is apples-to-apples. Lab scores are noisy (±10–15 pts run-to-run, and absolute values shift with machine/network/CDN state) — the delta is the reliable signal, not any single number. Perf LCP TBT CLS Production (live keploy.io/docs) 61 (53–65) 5.1s ~450ms 0 ✅ This PR (shipped) 81 (70–84) 3.1s ~478ms 0 Desktop (shipped): 99 (96–100) / LCP 0.8s / CLS 0. Net: +20 mobile perf, −2.0s LCP, CLS a perfect 0. Best mobile runs hit 84 / LCP 2.9s; desktop is fully green. Mobile LCP (3.1s) is still above the 2.5s line — the residual is Docusaurus's own React hydration (771 KB) plus the two eager trackers (GA + Meta Pixel), kept eager by product requirement for analytics accuracy. ⚖️ Analytics loading strategy GA + Meta Pixel → eager (accurate analytics/conversion from first paint; product requirement). Clarity + Apollo → on first user interaction (engaged sessions only). Hotjar → removed (redundant session-recorder, ~56 KiB). keploy telemetry → eager (first-party, ~2 KiB). Idle-deferring GA + Pixel too would gain a few more mobile points, but they'd fire ~1–3s after paint — the team chose instant firing. Everything else (fonts, GIF→video, dead-asset removal, image dimensions, a11y, Hotjar removal, Clarity/Apollo gating) is applied. ✅ Changes Performance — LCP D1 · Self-host DM Sans DM Sans loaded via a render-blocking <link rel="stylesheet"> to fonts.googleapis.com in headTags. Now self-hosted as a variable woff2 (latin + latin-ext subsets) in src/fonts/, wired via @font-face in custom.css — mirroring the existing Roboto setup (font-display: swap). It's the same font Google serves modern browsers (DM Sans v17 variable), so letterforms are identical; only the source changes. D6 · Fewer preconnects Dropped the two now-unused font preconnects. Preconnects 6 → 4 (algolia, keploy.io, GA, GTM) — clears the ">4 preconnect" warning. D2 · Delete dead heavy assets (~62 MB) git rm of 6 assets confirmed 0-ref (grepped repo-wide first): File Size static/gif/unit-test.gif 24.8 MB static/img/unit-test.gif 24.8 MB static/img/record-testcase.gif 6.0 MB static/gif/interoperability.gif 2.4 MB static/gif/tc-generation.gif 1.4 MB static/cms/reactor.png 2.7 MB D3 · Convert heavy in-use GIFs to H.264 MP4 (7.44 MB → 1.60 MB, −78%) The four referenced GIFs over 1 MB were the largest remaining LCP/bandwidth cost. Each was re-encoded to H.264 MP4 with faststart (recipe R3) and the GIF deleted: Asset GIF MP4 Saved gif/record-replay 2.66 MB 0.28 MB −89% gif/replay-tc 2.19 MB 0.41 MB −81% gif/how-keploy-works 1.45 MB 0.14 MB −90% img/record-api 1.14 MB 0.76 MB −33% ffmpeg -y -i in.gif -movflags faststart -pix_fmt yuv420p \ -vf "scale=trunc(iw/2)*2:trunc(ih/2)*2" -an out.mp4 All 30 live references across 24 files (v1/v2/v3/v4) were swapped from <img>/![]() to a <video autoPlay loop muted playsInline> element. Each carries explicit width/height (so the browser reserves layout space — a CLS win alongside the LCP one) plus an aria-label describing the clip, preserving the alt text the GIFs had. Autoplaying muted inline video keeps the existing "animated screenshot" behaviour on both desktop and mobile Safari. static/gif/record-tc.gif (278 KB, 11 references) is intentionally left as a GIF — it's an order of magnitude smaller than the four above, so the conversion churn isn't worth it in this PR. Noted as an optional follow-up. D4 · Analytics loading GA and the Meta Pixel fire eagerly — GA via the standard gtag preset (auto SPA tracking), the Pixel via an inline headTags snippet (init + PageView on load, <noscript> fallback intact). This is a product requirement (accurate analytics/conversion from first paint), so they are not deferred — the measured LCP cost is shown in the table above. Clarity + Apollo load on the first user interaction (engaged sessions only), and Hotjar was removed (redundant session-recorder). src/metaPixelRouteTracker.js re-fires the Pixel PageView on SPA route changes and drives the Clarity/Apollo interaction gate. Performance — CLS D5 · Intrinsic width/height on raw <img> tags Docusaurus's mdx-loader already resolves and sizes Markdown images ( renders with width/height + a content-hashed asset), but it leaves hand-written HTML <img> tags in .md/.mdx untouched — and those are the remaining source of layout shift. A new dependency-free remark plugin (src/remark/remarkImageSize.js) stamps each raw <img> with the image's intrinsic width/height, giving the browser an aspect ratio to reserve space. The global img { max-width:100%; height:auto } rule keeps images fully responsive — the attributes only supply the ratio, not a fixed size. Why no dependency: the repo carries both yarn.lock and package-lock.json (Vercel uses yarn, CI uses npm), so image-size/rehype-img-size would mean keeping two lockfiles in sync. The plugin reads PNG/GIF/JPEG headers itself. The parser is chosen by magic bytes, not extension, so a mislabeled file (this repo has one PNG saved as .jpg) is still sized correctly — verified against sips across all 188 raster assets (188/188 exact match). It never throws, skips remote/relative/data:/webp/svg, and never overwrites author-provided dimensions. Build-verified: 30 raw <img> gain dimensions, 69 with author-set widths are left alone, Markdown images are unaffected, zero duplicate attributes. No .md/.mdx source files change — sizing happens at build time. Source maps D11 · Client source maps A configureWebpack plugin sets devtool: 'source-map' for the client bundle only — no new dependency, emits .map files for debuggable first-party JS, zero runtime impact (428 maps emitted). Accessibility D9 · Decorative SVGs (+ X4) Added aria-hidden="true" to the 5 footer social icons (their <a> already carries aria-label) and 12 decorative component icons. DocItem theme SVGs already had it. D10 · Remove dead font reference Removed the "Aeonik" font-family — referenced for headings but never defined via @font-face, so it always fell through to the system stack. Process / CI X1 + X3 · Asset & font budget guard New .github/workflows/asset-budget.yml, scoped to files changed in the PR: X1 — fails on any newly added/modified image > 500 KB. X3 — fails on any newly added render-blocking Google Fonts stylesheet reference (fonts.googleapis.com/css...), enforcing D1. PR-scoped by design, so pre-existing large assets never fail an unrelated PR — only new regressions are caught. H1 · SEO sanity — verified robots.txt, sitemap.xml, canonical links, and JSON-LD are all present/valid; no change needed. 🔎 Review feedback addressed CSP has no reporting endpoint — description corrected to state violations are console-only for now; a report-to collector should be wired before enforcing (no fake endpoint added). 🔧 Keeping the linters happy The D3 <video> markup tripped two checks; both are fixed in this PR: prettier reformatted the markdown HTML block because of the indented <source> child. The two versions disagree on how: 2.8.8 wants a blank line after the opening tag (which would split the JSX block), while 3.9.6 wants the child dedented to column 0. The workflow pins 2.8.8 but the action resolves to 3.9.6, so rather than target either, each <video>/<source>/</video> trio is collapsed onto one line — verified clean under both versions. Vale flagged autoPlay and playsInline as misspellings. They're JSX attribute names, not prose, so they're added to the Base vocabulary alongside the other camelCase identifiers already accepted there (borderRadius, containerName, matchLabels, ...). ❌ Not in this PR (and why) Ticket Priority Why H2 — Product/review schema P3 SEO Intentionally not done. The "product" half is already covered — the site ships a complete SoftwareApplication JSON-LD block (the correct schema.org type for a dev tool; a separate Product type would be redundant/conflicting). The "review" half is deliberately omitted: aggregateRating/Review markup for one's own product on one's own domain violates Google's structured-data policy (self-serving reviews) and can trigger a manual action suppressing all rich results. Real ratings are surfaced the correct way — via sameAs links to G2/Gartner/Capterra/AWS Marketplace in the Organization schema. D8 — HSTS response header P2 SEC Preview-only — committed but not effective in prod. vercel.json sets Strict-Transport-Security, but production keploy.io/docs/* is served from S3 + CloudFront, so vercel.json only reaches the Vercel deploy preview. The apex domain already sends the identical HSTS value, so prod is covered; enforcing it on the docs path belongs in the CloudFront config, out of this repo. Kept in vercel.json (harmless, covers the preview). X6 — COOP (Cross-Origin-Opener-Policy) P2 SEC Same constraint — the same-origin COOP header is in vercel.json and therefore preview-only; real prod enforcement is a CloudFront-config change. X7 — CSP + Trusted Types (report-only) P2 SEC Same constraint — Content-Security-Policy-Report-Only (incl. require-trusted-types-for 'script') is preview-only and report-only by design (console violations, no enforcement, no report-to collector wired). Enforcing CSP/Trusted Types in prod needs the CloudFront config plus a reporting endpoint. ✅ Verification npm run build → [SUCCESS]; onBrokenLinks: "throw" passes — but that validates links, not <img>/<video>/<source> src. That the D2 deletions and D3 swaps break zero references was verified by grep, and is now enforced by the asset-budget guard (new step: every added src="/docs/..." must resolve under static/). CI: Vale, prettier, asset-budget, run-lint, deploy-preview green. DCO pending — sign-off still needed on a few commits. Fonts: both woff2 subsets emitted + referenced by built CSS (no broken URL); no Google Fonts stylesheet; no gstatic/googleapis preconnect; 4 preconnects total. D3: all 4 GIFs deleted with zero remaining live references (the only mentions left are inside non-rendering [//]: # markdown comments that predate this PR); 30 <video> elements across 24 files, each with width, height, and aria-label; MP4s total 1.53 MiB. D5: remark parser output matches sips on 188/188 raster assets; built HTML shows 30 raw <img> with injected width/height, 69 author-sized tags untouched, Markdown images unaffected, 0 duplicate attributes; disabling the plugin drops the attributes (isolation confirmed). D4: GA eager (gtag preset); Meta Pixel eager (inline headTags snippet, init + PageView, <noscript> intact); Clarity + Apollo load on first interaction; Hotjar removed; SPA PageView re-fired from the client module. D9 / X4: 5 footer + 12 component SVGs carry aria-hidden (all 16 <svg> in src/ covered, per-tag verified) plus the 6 inline decorative SVGs in gsoc/contribution-guide.md — the one served-markdown gap the earlier sweep missed. D10: no real Aeonik font-family remains. D11: 428 .js.map files emitted. X1/X3: workflow valid YAML; simulated on this PR's diff → passes; on a real violation → fails as intended. Image budget now uses --diff-filter=AMR so a renamed+re-encoded file can't slip it; added a step that fails on an added src="/docs/..." not resolving under static/. H1: robots.txt, sitemap.xml, canonical, JSON-LD all present/valid. prettier --check clean on all changed files under both 2.8.8 and 3.9.6; Vale clean on changed lines; no yarn.lock churn. Local preview: npm run serve (production preview) can't serve the fonts due to the repo's trailingSlash: true setting (it 302→404s every woff2 — the pre-existing Roboto font too), so DM Sans falls back to a system font locally. Use npm start (dev server) to preview fonts correctly. On Vercel the self-hosted fonts load fine, exactly like Roboto does today. 🔄 Review iteration 5 — fixes pushed D9 / X4 gap closed — the 6 inline decorative GitHub-icon <svg> in gsoc/contribution-guide.md (a served v4 page) now carry aria-hidden="true" + focusable="false". That was the only served-markdown SVG the earlier component/footer sweep missed, so X4 is now genuinely complete. X1 rename gap — the image-budget check used --diff-filter=AM, so a git mv that re-encoded a file larger reported as R and was skipped entirely. Now --diff-filter=AMR. Asset-src resolution now guarded — added an asset-budget step that fails when an added src="/docs/..." in changed markup doesn't resolve to a file under static/. onBrokenLinks only validates links, so this makes the D2/D3 "breaks zero references" claim self-enforcing (and the verification line is corrected to stop attributing that proof to the build). D8 / X6 / X7 documented, not dropped — moved into ❌ Not in this PR with the real reason: those headers (HSTS, COOP, CSP-Report-Only) live in vercel.json, which only reaches the Vercel deploy preview — production /docs/* is served from S3 + CloudFront, and the apex already sends the same HSTS. Prod enforcement belongs in the CloudFront config. Deferred (non-blocking, called out honestly): concepts/what-are-keploy-features.md is the only v4 page with two eager autoplay <video>s (~742 KB fetched on load regardless of viewport). It's the tail of D3, not systemic; a proper fix is an IntersectionObserver that assigns src to the below-fold video. Left for a follow-up rather than adding a one-page lazy-video component to this PR. Same for prefers-reduced-motion on the autoplay set.">
You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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
echo "::error::New external Google Fonts reference detected. Self-host the font (src/fonts/*.woff2 + @font-face in src/css/custom.css) instead of a render-blocking <link>:"
echo "$added"
exit 1
fi
echo "No new external font stylesheets."
- name: New local asset references in markdown must resolve (D3)
run: |
base="origin/${{ github.base_ref }}"
# Docusaurus serves static/ at baseUrl /docs/, so an added
# src|poster="/docs/<p>" must have a file at static/<p>. onBrokenLinks
# only validates links, not <img>/<video>/<source> src or <video>
# poster, so this guards that every ADDED reference resolves. Both
# attributes are checked: every converted <video> adds a src AND a
# poster, so omitting poster would leave half of D3's refs unguarded.
#
# Scope note: this is the D3 (added-reference) direction only. The
# mirror D2 direction — a deletion breaking a pre-existing reference —
# is deliberately not guarded here: such references sit on unchanged
# lines (invisible to the diff), and a whole-tree scan would
# false-positive on non-rendering commented-out mentions that predate
# the asset cleanups (e.g. `[//]: #` lines still naming removed GIFs).
fail=0
while IFS= read -r rel; do
[ -z "$rel" ] && continue
if [ ! -f "static/$rel" ]; then
echo "::error::Added asset reference /docs/$rel does not resolve to static/$rel"
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
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
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.