Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 18 additions & 6 deletions docs/dev-net-core-seo-plan-handoff.md
Original file line number Diff line number Diff line change
Expand Up @@ -478,7 +478,8 @@ The merged implementation completes the next readiness steps:
- Invalid content and generic routes remain Not Found with `noindex`, no
canonical, and no structured data.
- The Pages workflow is now manual-only, runs the complete dual-build gate, and
still deploys the existing `dist/` SPA from `main`.
completed its first confirmed `framework` deployment from `main` at
`27d6f39`. The `spa-rollback` target remains available.
- A read-only framework candidate workflow validates pull requests and pushes
to `main` without uploading or deploying a Pages artifact.
- Four stable candidate runs completed in 30-32 seconds. `Validate framework
Expand All @@ -492,6 +493,13 @@ The merged implementation completes the next readiness steps:
`dist/CNAME`, and `build-framework/client/CNAME`.
- Cloudflare configuration remains unchanged for cutover; the unrelated
Cloudflare Workers build integration was removed.
- Production validation passed all 227 sitemap URLs, canonical redirects,
sitemap discovery, dedicated Not Found behavior, and representative route
hydration.
- Homepage browser validation exposed one hydration mismatch: randomized Live
Drills differed between SSG and the first browser render. The local follow-up
now uses deterministic first, middle, and last candidates and passes repeated
browser loads without console errors.

## Work Not Yet Completed

Expand Down Expand Up @@ -543,6 +551,10 @@ Do not deploy `build-framework/server`; React Router removes it under

After cutover, test the Cloudflare-managed custom domain on GitHub Pages.

The first production route matrix passed on June 28, 2026. Keep this gate open
until the deterministic homepage drill fix is merged, redeployed, and confirmed
against the production browser console.

For every sitemap URL, verify:

- direct request returns `200 OK`
Expand Down Expand Up @@ -617,12 +629,12 @@ Then:

## Git State at Handoff

The latest merged readiness commit on `main` is:
The latest merged and deployed readiness commit on `main` is:

```txt
023d0f8 Merge pull request #3 from CircleQMinh/codex/require-framework-check
27d6f39 Merge pull request #4 from CircleQMinh/codex/framework-pages-cutover
```

Step 15 pre-cutover work is developed on
`codex/framework-pages-cutover`. Commit each completed implementation step
locally. Do not push, merge, revert, or deploy without user approval.
The Step 15 homepage hydration follow-up is developed on
`codex/fix-homepage-drill-hydration`. Commit each completed implementation
step locally. Do not push, merge, revert, or deploy without user approval.
17 changes: 17 additions & 0 deletions docs/plans/dev-net-core-seo-improvement-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -2982,6 +2982,23 @@ Validation:
- Confirm non-canonical route variants redirect to or canonicalize to trailing-slash URLs.
- Confirm valid trailing-slash routes direct-load in production.

Current Step 15 status as of June 28, 2026:

- The first guarded `framework` deployment completed successfully from
`main` at commit `27d6f39`.
- All 227 sitemap URLs passed direct production checks for `200 OK`, canonical
trailing-slash URLs, indexability, source titles, and visible headings.
- HTTPS, apex-to-`www`, non-trailing route redirects, `robots.txt`, and the
dedicated `404` + `noindex` response passed.
- Initial browser validation found a homepage-only React hydration mismatch
because the Live Drills list used `Math.random()` independently during SSG
and browser hydration.
- The homepage now selects deterministic first, middle, and last drill
candidates. Local framework preview validation passes repeated homepage
loads with identical drill content and no console errors.
- Keep Step 15 open until this fix is merged, redeployed, and the production
browser hydration check passes.

### Task 16: Search Console Setup

No code changes required.
Expand Down
31 changes: 13 additions & 18 deletions src/pages/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import StorageOutlinedIcon from "@mui/icons-material/StorageOutlined";
import SyncOutlinedIcon from "@mui/icons-material/SyncOutlined";
import TerminalOutlinedIcon from "@mui/icons-material/TerminalOutlined";
import { Box, Button, Container, Stack } from "@mui/material";
import { useMemo } from "react";
import type { ReactNode } from "react";
import { Link as RouterLink } from "react-router-dom";
import {
Expand Down Expand Up @@ -199,27 +198,25 @@ function scrollToPageTop() {
});
}

function getRandomDrills() {
function getFeaturedDrills() {
const candidates =
liveDrillCandidates.length >= 3 ? liveDrillCandidates : fallbackDrills;

return shuffleDrills(candidates).slice(0, 3);
}

function shuffleDrills(drills: Drill[]) {
const shuffledDrills = [...drills];

for (let index = shuffledDrills.length - 1; index > 0; index -= 1) {
const randomIndex = Math.floor(Math.random() * (index + 1));
const currentDrill = shuffledDrills[index];

shuffledDrills[index] = shuffledDrills[randomIndex];
shuffledDrills[randomIndex] = currentDrill;
if (candidates.length <= 3) {
return candidates.slice(0, 3);
}

return shuffledDrills;
const lastIndex = candidates.length - 1;

return [
candidates[0],
candidates[Math.floor(lastIndex / 2)],
candidates[lastIndex],
];
}

const featuredDrills = getFeaturedDrills();

const roadmapSteps: RoadmapStep[] = [
{
id: "01",
Expand Down Expand Up @@ -530,8 +527,6 @@ function RoadmapStepCard({ step }: { step: RoadmapStep }) {
}

export default function Home() {
const drills = useMemo(() => getRandomDrills(), []);

return (
<Box className="theme-page min-h-screen overflow-hidden">
<Box className="flex flex-col items-center gap-20 pb-20 pt-24 md:pt-32">
Expand Down Expand Up @@ -630,7 +625,7 @@ export default function Home() {
</div>

<div className="grid grid-cols-1 gap-8 lg:grid-cols-3">
{drills.map((drill) => (
{featuredDrills.map((drill) => (
<DrillCard drill={drill} key={drill.title} />
))}
</div>
Expand Down