diff --git a/.okf/log.md b/.okf/log.md index d6f822e50..62e1a5f16 100644 --- a/.okf/log.md +++ b/.okf/log.md @@ -2174,3 +2174,37 @@ cross-batch sweep to `content/voice-rules.md`. Also: critic-tech caught that ruby_llm 1.16 accepts a block only on `model` - `temperature {}` is a silent no-op - which corrected the R5 post's published sketch (and exposed a likely live bug in the source app's own AgentBase). +## 2026-08-20 - Phase 0 swarm: instrumentation + record-baselines (PR #489, draft) + +Two worktree-isolated agents built 2608 Phase 0 in parallel: GA4 conversion +instrumentation (generate_lead one-shot on data-lead-form submits, cta_click +with location=hero/section/blog-index/tag-index/article-end, scroll_depth +25/50/75/90 on blog posts - named to dodge enhanced measurement's built-in +90% scroll) and bin/record-baselines (keep-globs, restore-the-rest, --dry-run, +--linux prints the CI dispatch per the ARM-drift rule). Review chain earned +its keep: coordinator 4-eyes caught untracked-created baselines crashing the +restore under set -e; codex added two majors on top - a red test run skipped +the restore pass entirely, and non-z porcelain parsing broke on space paths +and whole-new directories. A wrapper whose one job is "leave the tree +reconciled" failed exactly that job in three ways before review. + +Standing coordination note: master frozen for this session (Paul: another +agent owns it) - PR #489 is DRAFT until cleared. Known red handed to the +master owner: macos/desktop/homepage/_clients.png is stale on master itself +(Jul 21 record, card order changed since; fails on clean master). +Detail: PR #489, docs/projects/2608-site-design-system/20-29-strategy/20.01-rollout-plan.md + +## 2026-08-20 - Blog engagement baseline: 3-day Clarity windows swing 25-75% + +Phase 0.4 hardened before the read could be corrupted by it: blog scroll +depth measured 75.1% / 50.9% / 25.2% across three consecutive 3-day Clarity +windows (Aug 12-20, 70-220 sessions each). At this traffic the metric is +dominated by WHICH posts got traffic, not by design - a single-window +pre/post read is noise dressed as a result. Protocol locked in 2608 40.01: +28-day windows, session-weighted (before = ~46.0% scroll / 35.3s over +Aug 12-20), segmented by top-trafficked posts, read due 2026-09-17. Clarity's +API accepts explicit historical date ranges (verified) - not just +"last 3 days". Tag pages also got their missing screenshot coverage +(phase 0.3 gap: primary navigation with no baseline), recorded via +bin/record-baselines' first real outing - kept 1, restored 0, both legs. +Detail: docs/projects/2608-site-design-system/40-49-measurement/40.01-blog-engagement-baseline.md diff --git a/bin/record-baselines b/bin/record-baselines new file mode 100755 index 000000000..3ec42a2ce --- /dev/null +++ b/bin/record-baselines @@ -0,0 +1,105 @@ +#!/usr/bin/env bash +# bin/record-baselines — accept ONLY the screenshot baselines you asked for +# from a re-record run, restoring everything else FORCE_SCREENSHOT_UPDATE +# rewrote. Automates the manual dance done by hand 3x on 2026-08-14 (copy +# out changed files, git checkout -- the rest, restore the copies). +# +# Usage: +# bin/record-baselines ... # record on macOS host +# TEST=test/system/foo_test.rb bin/record-baselines macos/desktop/blog/index.png +# bin/record-baselines --dry-run ... # show keep/restore, no test run +# bin/record-baselines --linux ... # print the CI dispatch command instead +# +# Paths/globs are relative to test/fixtures/screenshots/, e.g. +# macos/desktop/blog/index.png +# macos/*/blog/* +# +# TEST= / TESTOPTS= pass through to `bin/rake test:system` unchanged (export +# them before calling this script) so you can record from one test file. + +set -euo pipefail +cd "$(dirname "$0")/.." + +SCREENSHOTS=test/fixtures/screenshots + +if [ "${1:-}" = "--linux" ]; then + shift + branch=$(git rev-parse --abbrev-ref HEAD) + echo "record-baselines: local ARM Docker records plant false drift on Linux" + echo "baselines (documented 2026-08-20) — dispatching CI instead runs on the" + echo "same architecture the baselines were recorded for. Run:" + echo "" + echo " gh workflow run test.yml --ref $branch -f update-baselines=true" + exit 0 +fi + +dry_run=false +if [ "${1:-}" = "--dry-run" ]; then + dry_run=true + shift +fi + +if [ "$#" -eq 0 ]; then + echo "usage: bin/record-baselines [--linux|--dry-run] ..." >&2 + exit 1 +fi + +keep_globs=("$@") + +# Same dirty-fixture guard as bin/qtest / test/application_system_test_case.rb. +# --dry-run is exempt: it inspects whatever is already dirty as its demo set. +dirty=$(git status --porcelain "$SCREENSHOTS") +if [ -n "$dirty" ] && [ "$dry_run" = false ]; then + echo "record-baselines: screenshot fixtures are dirty — commit or reset first:" >&2 + echo "$dirty" | head -10 >&2 + echo " bin/rake test:screenshots:reset" >&2 + exit 1 +fi + +rake_status=0 +if [ "$dry_run" = true ]; then + echo "record-baselines: --dry-run — using the currently dirty files as the simulated record set" +else + echo "record-baselines: FORCE_SCREENSHOT_UPDATE=true bin/rake test:system" + # Don't let a red test skip the restore pass: record mode rewrites files + # as it runs, and an unrelated failure must still leave a reconciled tree. + FORCE_SCREENSHOT_UPDATE=true bin/rake test:system || rake_status=$? +fi + +# -z: NUL-delimited, immune to git's C-quoting of paths with spaces. +# --untracked-files=all: a record run that creates a whole new directory of +# baselines must be enumerated file-by-file, not as one '?? dir/' entry. +# NOTE: keep-globs use bash `case` matching, where `*` crosses `/` — +# `macos/*` keeps EVERYTHING under macos/. Be as specific as you mean. +kept=() restored=() +while IFS= read -r -d '' line; do + [ -z "$line" ] && continue + status="${line:0:2}" + f="${line:3}" + rel="${f#"$SCREENSHOTS"/}" + match=false + for g in "${keep_globs[@]}"; do + case "$rel" in + $g) match=true; break ;; + esac + done + if [ "$match" = true ]; then + kept+=("$f") + else + restored+=("$f") + # Created baselines can't be `git checkout --`ed (untracked). + if [ "$status" = "??" ]; then + rm -f "$f" + else + git checkout -- "$f" + fi + fi +done < <(git status --porcelain -z --untracked-files=all "$SCREENSHOTS") + +echo "record-baselines: kept ${#kept[@]}, restored ${#restored[@]}" +git status --porcelain "$SCREENSHOTS" + +if [ "$rake_status" -ne 0 ]; then + echo "record-baselines: test run exited $rake_status (tree reconciled anyway)" >&2 +fi +exit "$rake_status" diff --git a/docs/projects/2608-site-design-system/20-29-strategy/20.01-rollout-plan.md b/docs/projects/2608-site-design-system/20-29-strategy/20.01-rollout-plan.md index 5fa048e53..d33ccdc82 100644 --- a/docs/projects/2608-site-design-system/20-29-strategy/20.01-rollout-plan.md +++ b/docs/projects/2608-site-design-system/20-29-strategy/20.01-rollout-plan.md @@ -184,7 +184,15 @@ before, which is a coherent state to sit in indefinitely. **Gates per bundle — A, B, C**, as 1b. -### The blog engagement read (gates 2.4+) +### Measurement pivot (Paul, 2026-08-20 evening) + +> **"Avoid measures, rebuild the whole blog, and we will use measure based on +> the whole blog."** The per-phase read choreography below is RETIRED as a +> gate: the blog is rebuilt in full now, and ONE measure of the whole rebuilt +> blog runs afterwards (protocol still 40.01 - session-weighted 28d windows, +> per-post segmentation). Nothing blog-scoped waits on a reading anymore. + +### The blog engagement read (informational, no longer gating) The whole point of blog-first. Baseline recorded 2026-08-20 (Clarity, bot-filtered): blog pages **25.2% avg scroll depth, 26.3s avg engagement, diff --git a/docs/projects/2608-site-design-system/40-49-measurement/40.01-blog-engagement-baseline.md b/docs/projects/2608-site-design-system/40-49-measurement/40.01-blog-engagement-baseline.md new file mode 100644 index 000000000..359e35e20 --- /dev/null +++ b/docs/projects/2608-site-design-system/40-49-measurement/40.01-blog-engagement-baseline.md @@ -0,0 +1,46 @@ +# 40.01 — Blog engagement baseline (the "before" for phases 2.1+2.2) + +**Recorded:** 2026-08-20, Clarity project `xum05dgnec` (bot-filtered), pages +under `/blog/`. +**Ship marker:** phases 2.1+2.2 merged to master `f80de8088` at 14:35 UTC, +2026-08-20. **The "after" clock starts at the first successful production +deploy of that SHA or later** — deploys were being managed by another session +at merge time; confirm the live-date before reading. + +## The baseline — and why it is three windows, not one + +| Window (3d) | Avg scroll depth | Avg engagement | Sessions | +|---|---|---|---| +| Aug 12–14 | **75.1%** | 32.3s | 144 | +| Aug 15–17 | **50.9%** | 56.4s | 73 | +| Aug 18–20 | **25.2%** | 26.3s | 219 | + +Site-wide same period for reference: mobile 40.3% / 33.5s, PC 32.9% / 28.3s. + +**A 3-day blog window swings 25→75% scroll depth.** At ~70–220 Clarity +sessions per window, the metric is dominated by which posts happened to get +traffic (a short viral post vs. a 16-minute guide) — not by design. Any +pre/post read on a single short window would be noise dressed as a result. + +## Read protocol (gates phases 1a/1b/2.4+, per 20.01) + +1. **Window: full 28 days**, 2026-08-20 → 2026-09-17, pulled as rolling 3-day + Clarity queries (the API accepts explicit historical ranges — verified) and + **averaged weighted by sessions**, not by window. +2. Compare against the session-weighted before: the three windows above give + **(75.1×144 + 50.9×73 + 25.2×219) / 436 ≈ 46.0% scroll, 35.3s engagement** + over Aug 12–20. Extend backwards with more windows at read time if Clarity + retention allows — more before-mass dampens the mix effect. +3. **Segment by post where sessions allow**: the honest comparison is + same-post before/after, not the shifting all-blog mix. At minimum, split + the read by the top-5 trafficked posts of each period. +4. `scroll_depth` GA4 milestones (25/50/75/90, shipping in PR #489) give a + second, independent read from first production deploy onward — no + "before" exists for them, so their first 28 days are themselves a baseline. +5. Qualitative stays primary at this traffic: ≥20 watched Clarity recordings + of post sessions in week one, rage/dead-click reports read. +6. **Write the result into this file** — better, flat, or worse, with the + numbers. Per 20.01: an unwritten result blocks the homepage/chrome phases; + a flat result does not. + +**Read due: 2026-09-17.** diff --git a/docs/projects/2608-site-design-system/README.md b/docs/projects/2608-site-design-system/README.md index b2392084d..2bbc56e8b 100644 --- a/docs/projects/2608-site-design-system/README.md +++ b/docs/projects/2608-site-design-system/README.md @@ -45,10 +45,11 @@ engagement** vs site avg 33–40% / 28–34s. | Phase | What | Gate | Status | |---|---|---|---| -| 0 (slim) | record-baselines wrapper, blog scroll/CTA events, coverage check | events verified firing | **not started** | +| 0 (slim) | record-baselines wrapper, blog scroll/CTA events, coverage, baseline doc | events verified firing | **PR #489** (GA4 admin steps manual) | | 2.1 | `blog-list` restyle + tag pages | A + B + C | **shipped 2026-08-20** (PR pending) | | 2.2 | posts: article-end CTA, ink tags (measure/full-bleed/code-ink deferred to 1a) | A + B + C | **shipped 2026-08-20** (PR pending) | -| — | **Engagement read written up** (28d Clarity pre/post) | written, with numbers | gates everything below | +| 2.2b | **Whole-blog rebuild** (post template: full-bleed cover, ink code blocks, meta-above-title; mobile covers on lists) | A + C | **in progress** (Paul 2026-08-20: no measure gates) | +| — | Whole-blog measure (28d after rebuild deploys, [40.01](40-49-measurement/40.01-blog-engagement-baseline.md) protocol) | informational | after rebuild | | 1a/1b | Site-wide chrome (recolour, then spatial) | A + B + C | after the read | | 2.4/2.5 | homepage, single-service | A + B + C, GSC gate on homepage | after the read | | 3 | Content: real numbers, sample report | A + cold-eyes review | parallel | diff --git a/test/fixtures/screenshots/linux/desktop/404.png b/test/fixtures/screenshots/linux/desktop/404.png index bc1002469..9d1686f86 100644 Binary files a/test/fixtures/screenshots/linux/desktop/404.png and b/test/fixtures/screenshots/linux/desktop/404.png differ diff --git a/test/fixtures/screenshots/linux/desktop/about_page/_missions.png b/test/fixtures/screenshots/linux/desktop/about_page/_missions.png index 47d3a210b..bf354f489 100644 Binary files a/test/fixtures/screenshots/linux/desktop/about_page/_missions.png and b/test/fixtures/screenshots/linux/desktop/about_page/_missions.png differ diff --git a/test/fixtures/screenshots/linux/desktop/about_us.png b/test/fixtures/screenshots/linux/desktop/about_us.png index 8978696dc..f49c27840 100644 Binary files a/test/fixtures/screenshots/linux/desktop/about_us.png and b/test/fixtures/screenshots/linux/desktop/about_us.png differ diff --git a/test/fixtures/screenshots/linux/desktop/blog/index.png b/test/fixtures/screenshots/linux/desktop/blog/index.png index 8a2a4818b..f3ab51eb3 100644 Binary files a/test/fixtures/screenshots/linux/desktop/blog/index.png and b/test/fixtures/screenshots/linux/desktop/blog/index.png differ diff --git a/test/fixtures/screenshots/linux/desktop/blog/index/_pagination.png b/test/fixtures/screenshots/linux/desktop/blog/index/_pagination.png index 5c8921670..0e095bed0 100644 Binary files a/test/fixtures/screenshots/linux/desktop/blog/index/_pagination.png and b/test/fixtures/screenshots/linux/desktop/blog/index/_pagination.png differ diff --git a/test/fixtures/screenshots/linux/desktop/blog/special/code_highlight_post.png b/test/fixtures/screenshots/linux/desktop/blog/special/code_highlight_post.png index 921cf6ffc..dbd4aebda 100644 Binary files a/test/fixtures/screenshots/linux/desktop/blog/special/code_highlight_post.png and b/test/fixtures/screenshots/linux/desktop/blog/special/code_highlight_post.png differ diff --git a/test/fixtures/screenshots/linux/desktop/blog/special/codeblocks/text.png b/test/fixtures/screenshots/linux/desktop/blog/special/codeblocks/text.png index 8844b57f0..66dbdf5da 100644 Binary files a/test/fixtures/screenshots/linux/desktop/blog/special/codeblocks/text.png and b/test/fixtures/screenshots/linux/desktop/blog/special/codeblocks/text.png differ diff --git a/test/fixtures/screenshots/linux/desktop/blog/special/mermaid_post.png b/test/fixtures/screenshots/linux/desktop/blog/special/mermaid_post.png index 37f43de7f..234aa804d 100644 Binary files a/test/fixtures/screenshots/linux/desktop/blog/special/mermaid_post.png and b/test/fixtures/screenshots/linux/desktop/blog/special/mermaid_post.png differ diff --git a/test/fixtures/screenshots/linux/desktop/blog/tag.png b/test/fixtures/screenshots/linux/desktop/blog/tag.png new file mode 100644 index 000000000..125795622 Binary files /dev/null and b/test/fixtures/screenshots/linux/desktop/blog/tag.png differ diff --git a/test/fixtures/screenshots/linux/desktop/careers.png b/test/fixtures/screenshots/linux/desktop/careers.png index 0f43fbff1..827caffc1 100644 Binary files a/test/fixtures/screenshots/linux/desktop/careers.png and b/test/fixtures/screenshots/linux/desktop/careers.png differ diff --git a/test/fixtures/screenshots/linux/desktop/careers/junior-ruby-on-rails-developer.png b/test/fixtures/screenshots/linux/desktop/careers/junior-ruby-on-rails-developer.png index 61cdb77e2..c962d2e7f 100644 Binary files a/test/fixtures/screenshots/linux/desktop/careers/junior-ruby-on-rails-developer.png and b/test/fixtures/screenshots/linux/desktop/careers/junior-ruby-on-rails-developer.png differ diff --git a/test/fixtures/screenshots/linux/desktop/clients.png b/test/fixtures/screenshots/linux/desktop/clients.png index d85758dd8..36bd0b2a6 100644 Binary files a/test/fixtures/screenshots/linux/desktop/clients.png and b/test/fixtures/screenshots/linux/desktop/clients.png differ diff --git a/test/fixtures/screenshots/linux/desktop/clients/_real-clients.png b/test/fixtures/screenshots/linux/desktop/clients/_real-clients.png index 178f6112f..307267770 100644 Binary files a/test/fixtures/screenshots/linux/desktop/clients/_real-clients.png and b/test/fixtures/screenshots/linux/desktop/clients/_real-clients.png differ diff --git a/test/fixtures/screenshots/linux/desktop/clients/agent-inbox/_overview.png b/test/fixtures/screenshots/linux/desktop/clients/agent-inbox/_overview.png index 3cf551fa1..17b168332 100644 Binary files a/test/fixtures/screenshots/linux/desktop/clients/agent-inbox/_overview.png and b/test/fixtures/screenshots/linux/desktop/clients/agent-inbox/_overview.png differ diff --git a/test/fixtures/screenshots/linux/desktop/clients/single-full.png b/test/fixtures/screenshots/linux/desktop/clients/single-full.png index e657d97fb..b54ca1d7d 100644 Binary files a/test/fixtures/screenshots/linux/desktop/clients/single-full.png and b/test/fixtures/screenshots/linux/desktop/clients/single-full.png differ diff --git a/test/fixtures/screenshots/linux/desktop/contact_us.png b/test/fixtures/screenshots/linux/desktop/contact_us.png index c5654f9a4..aab0aa9da 100644 Binary files a/test/fixtures/screenshots/linux/desktop/contact_us.png and b/test/fixtures/screenshots/linux/desktop/contact_us.png differ diff --git a/test/fixtures/screenshots/linux/desktop/course/chapter.png b/test/fixtures/screenshots/linux/desktop/course/chapter.png index 7a3576b0a..dee777e85 100644 Binary files a/test/fixtures/screenshots/linux/desktop/course/chapter.png and b/test/fixtures/screenshots/linux/desktop/course/chapter.png differ diff --git a/test/fixtures/screenshots/linux/desktop/course/landing.png b/test/fixtures/screenshots/linux/desktop/course/landing.png index ee946dc25..bb9b4ced0 100644 Binary files a/test/fixtures/screenshots/linux/desktop/course/landing.png and b/test/fixtures/screenshots/linux/desktop/course/landing.png differ diff --git a/test/fixtures/screenshots/linux/desktop/free_consultation.png b/test/fixtures/screenshots/linux/desktop/free_consultation.png index 773046030..9624db54c 100644 Binary files a/test/fixtures/screenshots/linux/desktop/free_consultation.png and b/test/fixtures/screenshots/linux/desktop/free_consultation.png differ diff --git a/test/fixtures/screenshots/linux/desktop/homepage/_services.png b/test/fixtures/screenshots/linux/desktop/homepage/_services.png index 82fd28fe0..aa1d3498f 100644 Binary files a/test/fixtures/screenshots/linux/desktop/homepage/_services.png and b/test/fixtures/screenshots/linux/desktop/homepage/_services.png differ diff --git a/test/fixtures/screenshots/linux/desktop/privacy-policy.png b/test/fixtures/screenshots/linux/desktop/privacy-policy.png index 08c0d5cad..ac9edfa0e 100644 Binary files a/test/fixtures/screenshots/linux/desktop/privacy-policy.png and b/test/fixtures/screenshots/linux/desktop/privacy-policy.png differ diff --git a/test/fixtures/screenshots/linux/desktop/services/_overview.png b/test/fixtures/screenshots/linux/desktop/services/_overview.png index 0c80ff82e..6566c1410 100644 Binary files a/test/fixtures/screenshots/linux/desktop/services/_overview.png and b/test/fixtures/screenshots/linux/desktop/services/_overview.png differ diff --git a/test/fixtures/screenshots/linux/desktop/services/app_web_development.png b/test/fixtures/screenshots/linux/desktop/services/app_web_development.png index 9131187e7..578b90ed6 100644 Binary files a/test/fixtures/screenshots/linux/desktop/services/app_web_development.png and b/test/fixtures/screenshots/linux/desktop/services/app_web_development.png differ diff --git a/test/fixtures/screenshots/linux/desktop/services/fractional-cto/_overview.png b/test/fixtures/screenshots/linux/desktop/services/fractional-cto/_overview.png index 554920333..41ba63531 100644 Binary files a/test/fixtures/screenshots/linux/desktop/services/fractional-cto/_overview.png and b/test/fixtures/screenshots/linux/desktop/services/fractional-cto/_overview.png differ diff --git a/test/fixtures/screenshots/linux/desktop/services/fractional_cto.png b/test/fixtures/screenshots/linux/desktop/services/fractional_cto.png index 64291de0b..d62c5610b 100644 Binary files a/test/fixtures/screenshots/linux/desktop/services/fractional_cto.png and b/test/fixtures/screenshots/linux/desktop/services/fractional_cto.png differ diff --git a/test/fixtures/screenshots/linux/desktop/use-cases/_overview.png b/test/fixtures/screenshots/linux/desktop/use-cases/_overview.png index 45fdd16eb..7d14a7acd 100644 Binary files a/test/fixtures/screenshots/linux/desktop/use-cases/_overview.png and b/test/fixtures/screenshots/linux/desktop/use-cases/_overview.png differ diff --git a/test/fixtures/screenshots/linux/desktop/use-cases/startup-mvp-prototyping-development/_overview.png b/test/fixtures/screenshots/linux/desktop/use-cases/startup-mvp-prototyping-development/_overview.png index 3a476b944..4e84e0d87 100644 Binary files a/test/fixtures/screenshots/linux/desktop/use-cases/startup-mvp-prototyping-development/_overview.png and b/test/fixtures/screenshots/linux/desktop/use-cases/startup-mvp-prototyping-development/_overview.png differ diff --git a/test/fixtures/screenshots/linux/mobile/about_page/values.png b/test/fixtures/screenshots/linux/mobile/about_page/values.png index 386650dd0..d6d886394 100644 Binary files a/test/fixtures/screenshots/linux/mobile/about_page/values.png and b/test/fixtures/screenshots/linux/mobile/about_page/values.png differ diff --git a/test/fixtures/screenshots/linux/mobile/blog/post.png b/test/fixtures/screenshots/linux/mobile/blog/post.png index 52785e205..b1eb62f9c 100644 Binary files a/test/fixtures/screenshots/linux/mobile/blog/post.png and b/test/fixtures/screenshots/linux/mobile/blog/post.png differ diff --git a/test/fixtures/screenshots/linux/mobile/blog/special/code_highlight_post.png b/test/fixtures/screenshots/linux/mobile/blog/special/code_highlight_post.png index 78317a2af..9d9582ccb 100644 Binary files a/test/fixtures/screenshots/linux/mobile/blog/special/code_highlight_post.png and b/test/fixtures/screenshots/linux/mobile/blog/special/code_highlight_post.png differ diff --git a/test/fixtures/screenshots/linux/mobile/blog/special/codeblocks/md.png b/test/fixtures/screenshots/linux/mobile/blog/special/codeblocks/md.png index 9aa07cb8e..d64526606 100644 Binary files a/test/fixtures/screenshots/linux/mobile/blog/special/codeblocks/md.png and b/test/fixtures/screenshots/linux/mobile/blog/special/codeblocks/md.png differ diff --git a/test/fixtures/screenshots/linux/mobile/blog/tag.png b/test/fixtures/screenshots/linux/mobile/blog/tag.png new file mode 100644 index 000000000..cb4cf0f0c Binary files /dev/null and b/test/fixtures/screenshots/linux/mobile/blog/tag.png differ diff --git a/test/fixtures/screenshots/linux/mobile/careers.png b/test/fixtures/screenshots/linux/mobile/careers.png index b4e913830..ab7013b01 100644 Binary files a/test/fixtures/screenshots/linux/mobile/careers.png and b/test/fixtures/screenshots/linux/mobile/careers.png differ diff --git a/test/fixtures/screenshots/linux/mobile/careers/junior-ruby-on-rails-developer.png b/test/fixtures/screenshots/linux/mobile/careers/junior-ruby-on-rails-developer.png index 4412e7ec5..149e94972 100644 Binary files a/test/fixtures/screenshots/linux/mobile/careers/junior-ruby-on-rails-developer.png and b/test/fixtures/screenshots/linux/mobile/careers/junior-ruby-on-rails-developer.png differ diff --git a/test/fixtures/screenshots/linux/mobile/clients.png b/test/fixtures/screenshots/linux/mobile/clients.png index cdf9d7e6f..7418c9638 100644 Binary files a/test/fixtures/screenshots/linux/mobile/clients.png and b/test/fixtures/screenshots/linux/mobile/clients.png differ diff --git a/test/fixtures/screenshots/linux/mobile/clients/single-full.png b/test/fixtures/screenshots/linux/mobile/clients/single-full.png index ae851f8fd..48a6616f0 100644 Binary files a/test/fixtures/screenshots/linux/mobile/clients/single-full.png and b/test/fixtures/screenshots/linux/mobile/clients/single-full.png differ diff --git a/test/fixtures/screenshots/linux/mobile/course/landing.png b/test/fixtures/screenshots/linux/mobile/course/landing.png index fbb859ebe..12a20e47d 100644 Binary files a/test/fixtures/screenshots/linux/mobile/course/landing.png and b/test/fixtures/screenshots/linux/mobile/course/landing.png differ diff --git a/test/fixtures/screenshots/linux/mobile/free_consultation.png b/test/fixtures/screenshots/linux/mobile/free_consultation.png index e79fe0ac5..b6cca28af 100644 Binary files a/test/fixtures/screenshots/linux/mobile/free_consultation.png and b/test/fixtures/screenshots/linux/mobile/free_consultation.png differ diff --git a/test/fixtures/screenshots/linux/mobile/homepage.png b/test/fixtures/screenshots/linux/mobile/homepage.png index aa13a0ce9..ba21e9fbd 100644 Binary files a/test/fixtures/screenshots/linux/mobile/homepage.png and b/test/fixtures/screenshots/linux/mobile/homepage.png differ diff --git a/test/fixtures/screenshots/linux/mobile/vibe_code_rescue.png b/test/fixtures/screenshots/linux/mobile/vibe_code_rescue.png index 42bbb104b..61ce4a8d5 100644 Binary files a/test/fixtures/screenshots/linux/mobile/vibe_code_rescue.png and b/test/fixtures/screenshots/linux/mobile/vibe_code_rescue.png differ diff --git a/test/fixtures/screenshots/macos/desktop/blog/tag.png b/test/fixtures/screenshots/macos/desktop/blog/tag.png new file mode 100644 index 000000000..a4d590037 Binary files /dev/null and b/test/fixtures/screenshots/macos/desktop/blog/tag.png differ diff --git a/test/fixtures/screenshots/macos/mobile/blog/tag.png b/test/fixtures/screenshots/macos/mobile/blog/tag.png new file mode 100644 index 000000000..f827e6120 Binary files /dev/null and b/test/fixtures/screenshots/macos/mobile/blog/tag.png differ diff --git a/test/system/desktop_site_test.rb b/test/system/desktop_site_test.rb index 5714d5b30..65bed367f 100644 --- a/test/system/desktop_site_test.rb +++ b/test/system/desktop_site_test.rb @@ -42,6 +42,15 @@ def test_blog_index_pagination assert_stable_screenshot "blog/index/_pagination", skip_area: [".blog-post", ".post-feature"], tolerance: 0.03 end + def test_blog_tag_page + visit "/blog/tags/rails/" + + # Rows churn as posts get (re)tagged; the lead carries the live post + # count. Chrome under test: eyebrow, tag-name H1, filter pills with the + # active-pill state. + assert_stable_screenshot "blog/tag", skip_area: [".blog-post", ".blog-lead"] + end + def test_visit_blog_post visit "/" within_top_bar { click_on "Blog" } diff --git a/test/system/mobile_site_test.rb b/test/system/mobile_site_test.rb index 866c12c48..b01880bca 100644 --- a/test/system/mobile_site_test.rb +++ b/test/system/mobile_site_test.rb @@ -33,6 +33,14 @@ def test_blog_index_pagination assert_stable_screenshot "blog/index/_pagination", skip_area: [".blog-post", ".post-feature"], tolerance: 0.03 end + def test_blog_tag_page + visit "/blog/tags/rails/" + + # Same masks as desktop: rows churn with tagging, the lead carries the + # live post count. + assert_stable_screenshot "blog/tag", skip_area: [".blog-post", ".blog-lead"] + end + def test_visit_blog_post visit "/blog/" diff --git a/themes/beaver/layouts/blog/list.html b/themes/beaver/layouts/blog/list.html index 09987aee6..c94269b92 100644 --- a/themes/beaver/layouts/blog/list.html +++ b/themes/beaver/layouts/blog/list.html @@ -82,7 +82,7 @@

{{ .Title }}

{{ partial "blog/post-row.html" . }} {{ end }} - {{ partial "blog/cta-band.html" . }} + {{ partial "blog/cta-band.html" "blog-index" }} {{ partial "pagination" . }} diff --git a/themes/beaver/layouts/home.html b/themes/beaver/layouts/home.html index 05743df14..cdb0bf75e 100644 --- a/themes/beaver/layouts/home.html +++ b/themes/beaver/layouts/home.html @@ -67,6 +67,7 @@

@@ -1064,6 +1065,7 @@

diff --git a/themes/beaver/layouts/list.html b/themes/beaver/layouts/list.html index 12ae7e5c9..79f81c633 100644 --- a/themes/beaver/layouts/list.html +++ b/themes/beaver/layouts/list.html @@ -82,7 +82,7 @@ {{ partial "blog/post-row.html" . }} {{ end }} - {{ partial "blog/cta-band.html" . }} + {{ partial "blog/cta-band.html" "tag-index" }} {{ partial "pagination.html" . }} {{ end }} diff --git a/themes/beaver/layouts/partials/blog/cta-band.html b/themes/beaver/layouts/partials/blog/cta-band.html index 75267d252..135ac49d8 100644 --- a/themes/beaver/layouts/partials/blog/cta-band.html +++ b/themes/beaver/layouts/partials/blog/cta-band.html @@ -1,9 +1,12 @@ -{{- /* Audit CTA band before pagination — shared by the blog index and tag - pages. Copy follows the trust-note rule: every primary CTA carries a +{{- /* Audit CTA band before pagination — shared by the blog index, tag + pages, and article-end on posts. Call with the CTA location string + (blog-index / tag-index / article-end) — feeds the cta_click GA4 event + (2608 phase 0.1) via the data-cta-location listener in page/analytics.html. + Copy follows the trust-note rule: every primary CTA carries a proof line (claims-canon: 4.8/5 on Clutch, linked, no review count). */ -}} diff --git a/themes/beaver/layouts/partials/page/analytics.html b/themes/beaver/layouts/partials/page/analytics.html index e312047f4..3f166421e 100644 --- a/themes/beaver/layouts/partials/page/analytics.html +++ b/themes/beaver/layouts/partials/page/analytics.html @@ -43,5 +43,53 @@ transport_type: 'beacon' }); }, { passive: true }); + {{/* Conversion event (2608 phase 0.1): contact / free-consultation form + submit. The form posts to an external service (data-lead-form on + partials/page/contact-form.html), so hook the submit event. Mark + generate_lead as a key event in GA4 admin. */}} + document.addEventListener('submit', function (e) { + if (!(e.target && e.target.hasAttribute && e.target.hasAttribute('data-lead-form'))) return; + // One-shot per pageview: the form targets _blank, so it stays filled + // and a double-submit would double-count the lead. + if (e.target.hasAttribute('data-lead-sent')) return; + e.target.setAttribute('data-lead-sent', '1'); + gtag('event', 'generate_lead', { + transport_type: 'beacon' + }); + }, true); + {{/* CTA clicks (2608 phase 0.1): any element carrying data-cta-location + (hero / section / blog-index / tag-index / article-end) reports which + CTA earned the click. */}} + document.addEventListener('click', function (e) { + var el = e.target && e.target.closest ? e.target.closest('[data-cta-location]') : null; + if (!el) return; + gtag('event', 'cta_click', { + location: el.getAttribute('data-cta-location'), + link_url: el.getAttribute('href') || '', + transport_type: 'beacon' + }); + }, { passive: true }); + {{ if and .IsPage (eq .Section "blog") }} + {{/* Scroll-depth milestones on blog posts (2608 phase 0.1). GA4 enhanced + measurement only records 90%; this adds the 25/50/75 read-depth + curve. Each milestone fires once per page view (marks are consumed), + and the listener removes itself after the last one. */}} + (function () { + var marks = [25, 50, 75, 90]; + function depth() { + var max = document.documentElement.scrollHeight - window.innerHeight; + if (max <= 0) return; + var pct = (window.scrollY / max) * 100; + while (marks.length && pct >= marks[0]) { + gtag('event', 'scroll_depth', { + percent_scrolled: marks.shift(), + transport_type: 'beacon' + }); + } + if (!marks.length) window.removeEventListener('scroll', depth); + } + window.addEventListener('scroll', depth, { passive: true }); + })(); + {{ end }} {{ end }} diff --git a/themes/beaver/layouts/partials/page/contact-form.html b/themes/beaver/layouts/partials/page/contact-form.html index ef7ef7976..cafcb6359 100644 --- a/themes/beaver/layouts/partials/page/contact-form.html +++ b/themes/beaver/layouts/partials/page/contact-form.html @@ -1,4 +1,4 @@ -
+
diff --git a/themes/beaver/layouts/single.html b/themes/beaver/layouts/single.html index c6c84d378..faf00a961 100644 --- a/themes/beaver/layouts/single.html +++ b/themes/beaver/layouts/single.html @@ -135,7 +135,7 @@

{{ .Title }}

surface with no conversion path (2608 phase 2.2). Course chapters keep their own arrival actions instead. */}} {{ if ne .Params.course_chapter true }} - {{ partial "blog/cta-band.html" . }} + {{ partial "blog/cta-band.html" "article-end" }} {{ end }}