Skip to content

#23 — React Migration Foundation V1 - #23

Merged
UAJOP merged 5 commits into
mainfrom
feat/react-foundation-v1
Aug 23, 2026
Merged

#23 — React Migration Foundation V1#23
UAJOP merged 5 commits into
mainfrom
feat/react-foundation-v1

Conversation

@UAJOP

@UAJOP UAJOP commented Aug 23, 2026

Copy link
Copy Markdown
Owner

#23 — React Migration Foundation V1

Installs and proves the React architecture beside the existing production site. No public page is replaced, and no legacy code is removed.

DO NOT MERGE — draft, pending architecture review and visual/product testing.

What this is

React 19 + Vite 8 + React Router 7 running as a parallel architecture, with three pre-rendered preview routes under /react-preview/. The live site is byte-identical apart from one Build Log entry.

This is not a migration. Nothing on kaanbalci.com is served by React.

Production isolation

Vite root src/react/ — the repository root is not a Vite project and has no Vite entry
Build output dist-react/, git-ignored, so GitHub Pages cannot publish it
Mount path /react-preview/, never a production route
Discoverability noindex, nofollow; absent from sitemap.xml, navigation, Recruiter Mode and Ajoop

Diff against main touches zero production HTML, CSS, or runtime JS. The only production-visible edit is a 7-line Build Log entry in portfolio-data.js.

Pre-render proof

Every route contains real content before any JavaScript runs — an empty <div id="root"></div> is explicitly treated as failure:

Route Pre-rendered markup
/react-preview/ 3,452 B
/react-preview/about 3,342 B
404.html 2,784 B

Each carries its own <title>, description, robots noindex, and header/nav/main/footer landmarks. The build fails under 1 KB of markup; the guard fails on an empty root. Both failure paths were exercised and confirmed to exit non-zero.

Hydration

0 errors, 0 React warnings across every route — including a reload with non-default preferences (TR + light) stored, and 404.html served at an arbitrary unknown URL.

Theme and language start at their server-rendered defaults and reconcile after hydration, so the first client render matches the pre-rendered markup exactly. The blocking inline theme script applies the stored theme before first paint, as the production pages do.

Routing, and what it says about hosting

URL Status Served
/react-preview/ 200 pre-rendered home
/react-preview/about 200 pre-rendered about
/react-preview/does-not-exist 404 pre-rendered NotFound

Client navigation, Back and Forward were each confirmed to be real client transitions (a window value survived them).

The preview server is deliberately configured not to use an SPA fallback. GitHub Pages has no rewrite rules, so a route is directly reachable only if a file exists for it. vite dev does fall back — that is a development convenience and is not evidence about production. This distinction drove the conclusion in the plan that migrated routes must emit works.html, not works/index.html, so existing canonical URLs survive.

QA

Check Result
npm run qa pass — 18/18 JS, consistency, assets, links, 0 HTML errors, 0 spelling
npm run build:react pass
npm run qa:react pass
npm run qa:a11y (production, 11 pages) 11/11, 0 errors
npm run qa:a11y:react (preview, 3 routes) 3/3, 0 errors
Production Lighthouse 95/95/93/90 perf, 100 a11y / 100 BP / 100 SEO — unchanged
Production console 0 errors across 6 regression pages

Three new blocking CI steps. No existing gate weakened; Lighthouse and the external link scan remain the only report-only checks. Production Pa11y and Lighthouse still cover the same 11 pages — the preview has its own config so it adds coverage rather than displacing any, and the guard asserts this.

Bundle

254.5 KB raw / 81.6 KB gzip total; 1 JS chunk, 1 CSS chunk, 3 HTML files. Essentially React + React Router. Not comparable to the legacy production bundle, and not production cost today.

Truth guards

qa-react-foundation.js loads the real registry and fails on drift in the primary title, background title, footer positioning copy, or any of the five canonical social URLs. The comparisons are field-scoped, because the footer tagline contains the primary title and a whole-document search would miss an invented job title in the header — verified by deliberately introducing one.

Forward Deployed Engineer and AI Designer & Software Developer are unchanged.

Two fixes made along the way

Contrast failures found while testing the preview, fixed rather than accepted:

  • preview badge: 2.87:1 on dark
  • accent text using --brand on light: 3.94:1

Minimums now 4.61:1 light / 6.06:1 dark.

Documentation

  • REACT_MIGRATION_PLAN.md (new) — target stack, rationale, the locked #23 — React Migration Foundation V1 #23–#33 master roadmap (§4, authoritative), parity rules, pre-render and route policy, data and legacy-removal policy, mini-game strategy, QA strategy, rollback principle
  • PORTFOLIO_ARCHITECTURE.md — transitional architecture section
  • README.md, SITE_PREFLIGHT.md, QA_BASELINE.md updated with measured numbers

Deferred (explicitly not in this PR)

Everything in phases #24–#33 of the locked roadmap: shared shell + JSON data foundation (#24) · Home + About (#25) · Works + Games (#26) · Recruiter Mode + Build Log (#27) · Ajoop + Command Palette (#28) · case studies + dynamic project routes (#29) · Labs + mini-game shells (#30) · legacy runtime removal (#31) · external dependency + bundle cleanup (#32) · hardening + V3 final (#33).

TypeScript is not scheduled in any V3 phase.

🤖 Generated with Claude Code

UAJOP and others added 5 commits August 23, 2026 20:04
Installs React 19, React DOM, React Router 7, Vite 8 and @vitejs/plugin-react
as a parallel build system. Nothing in the live site changes: Vite is rooted at
src/react/, so the repository root stays a plain static site with no Vite entry,
and the build writes only to dist-react/, which is git-ignored so GitHub Pages
cannot publish it.

The pre-render script is the reason this is not a client-only SPA. It builds the
client bundle, compiles a server entry, renders each route with renderToString
and writes real HTML per route. It fails the build if a route renders under 1 KB
of markup, so a silently empty render cannot ship.

vite.config.mjs uses the .mjs extension deliberately: qa-js-syntax.js parses
every root-level .js file as a classic script, and an ESM config would fail that
check for no useful reason. The pre-render script lives in scripts/ for the same
reason.

The preview server is configured to behave like a static host rather than an SPA
server -- exact file, directory index, or 404.html with a real 404 status. Vite's
SPA fallback would make client-side routing look like it works on GitHub Pages
when it does not, and would serve the home page's pre-rendered markup at a URL
whose client render is NotFound, which is a genuine hydration mismatch.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three preview routes under /react-preview/, driven by React Router and
pre-rendered to real HTML: an overview, an approach page and a NotFound view.
None of them is reachable from any public surface -- they are noindex, unlinked,
and absent from sitemap.xml, navigation, Recruiter Mode and Ajoop.

Header and footer render the canonical profile truth rather than re-typed copy:
Forward Deployed Engineer as the primary title, AI Designer & Software Developer
as the background, and exactly the five canonical social destinations. The
values live in a temporary parity fixture because portfolio-data.js is a browser
global that a Vite module graph cannot import; converting the registry to JSON is
#24, and until then a guard fails the build if the fixture drifts.

Theme and language reuse the production storage keys, so a visitor's choice
survives moving between the legacy site and the preview.

Both start at their server-rendered defaults and reconcile in an effect after
hydration, which is what keeps the first client render identical to the
pre-rendered markup. The blocking inline theme script in the document head
applies the stored theme before first paint, exactly as the production pages do,
so correct colors do not wait for that effect. Result is 0 hydration errors and
0 React warnings, including on a reload with non-default preferences stored.

The 404 view deliberately does not echo the attempted path: one pre-rendered
file is served for arbitrary unknown URLs, so echoing it would guarantee a
mismatch.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
qa-react-foundation.js proves the two properties that matter about this pass.

First, that the build really pre-renders. It fails if a route ships an empty
root container, if less than 1 KB was rendered into it, if a route heading is
missing, or if the semantic landmarks did not survive the build. Both failure
paths were exercised deliberately and confirmed to exit non-zero.

Second, that production is untouched. It checks that every production page still
boots script.js and references neither the build output nor the preview, that
the preview stays out of sitemap.xml and robots.txt, that dist-react is
git-ignored, and that the Vite config still points away from the site root.

It also loads the real registry and compares it against both the parity fixture
and the rendered output, so the fixture cannot become a second source of truth.
Those comparisons are field-scoped rather than whole-file: the footer tagline
contains the primary title, so a substring search over the document would pass
even with an invented job title in the header.

CI gains three blocking steps. The React build is also the JSX gate, since
qa:js parses root files as classic scripts and cannot represent JSX. The preview
gets its own Pa11y config so it adds accessibility coverage rather than
displacing any; the guard asserts production Pa11y and Lighthouse still cover
all 11 pages. No existing gate was weakened, and Lighthouse remains report-only.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
REACT_MIGRATION_PLAN.md is the contract for #23 through #33: the target stack,
why React and Vite rather than a meta-framework, why JSX before TypeScript, the
phase list, the parity rules a page must satisfy before it is migrated, and the
rule that legacy is removed only after parity is proven and only in a separate
pull request.

Two findings from this pass are written down because they constrain every later
phase. Production URLs are flat .html files at the site root and must not change,
so migrated routes emit works.html rather than works/index.html. And GitHub Pages
offers no rewrite rules or SPA fallback, so pre-rendering each route to its own
file is what makes a route directly reachable at all -- not an optimization. The
Vite dev-server fallback is explicitly called out as not being evidence about
production hosting.

PORTFOLIO_ARCHITECTURE.md gains the transitional section describing the two
architectures now in the repository and the boundary between them.

QA_BASELINE.md records measured numbers: build output with gzip sizes, per-route
pre-rendered markup, routing status codes, and contrast minimums in both themes.
Two contrast failures found during this pass were fixed rather than accepted.

The Build Log entry says a foundation was established beside the existing site.
It does not say the site was migrated to React, because it was not.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The migration plan had invented its own phase order instead of using the
locked master roadmap agreed with the owner. Replaced with the real structure:
#24 shared shell + JSON data foundation, #25 Home + About, #26 Works + Games,
#27 Recruiter Mode + Build Log, #28 Ajoop + Command Palette, #29 case studies +
dynamic project routes, #30 Labs + mini-game shells, #31 legacy runtime removal,
#32 external dependency + bundle cleanup, #33 hardening and V3 final.

Section 4 is now marked authoritative, so a later implementation pass cannot
quietly redesign it again.

The real roadmap is also better than the one I invented on a point that matters
to the legacy-removal policy: every migration phase runs before the first
removal phase, and removal is concentrated in #31 once the migration matrix
reaches zero legacy usage, rather than being spread across phases. Sections 9
and 10 and the architecture doc now reflect that, and #30 records that gameplay
may remain vanilla JS/canvas.

Also removed the claim that TypeScript is "the right long-term answer" and
scheduled after the JSON migration. That decision was not made. JavaScript/JSX
is the intentional language of V3, TypeScript is outside its scope, and any
reevaluation after #33 is the owner's call.

Turkish copy cleanup in the Build Log entry and the preview i18n proof:
"tüm herkese açık sayfalar" -> "herkese açık tüm sayfalar".

No React implementation change beyond that one translated string.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@UAJOP

UAJOP commented Aug 23, 2026

Copy link
Copy Markdown
Owner Author

Review fix — locked roadmap restored

Head: aefe9ed3a780a1. Documentation only; no React implementation change beyond one translated string.

1. Locked #23–#33 roadmap restored. REACT_MIGRATION_PLAN.md §4 now carries the exact agreed structure and is marked authoritative, so a later pass cannot redesign it again.

The real roadmap is better than the one I invented on a point that matters to §9: every migration phase (#24–#30) runs before the first removal phase (#31), and removal is concentrated there once the migration matrix reaches zero legacy usage — rather than being spread across phases as I had it. §9, §10 and PORTFOLIO_ARCHITECTURE.md now reflect that, and #30 records that gameplay may remain vanilla JS/canvas.

2. TypeScript commitment removed. The claim that TypeScript is "the right long-term answer" and scheduled after the JSON migration is gone. §3 now states JavaScript/JSX is the intentional language of V3, TypeScript is outside V3 scope, no adoption date is promised, and any reevaluation after #33 is the owner's call.

3. Architecture findings preserved — static/pre-render requirement, GitHub Pages route constraints, flat production URLs unchanged, per-route configurable output, hydration parity rule, production-before-legacy-removal, mini-game gameplay stays vanilla, rollback principle, QA additive-never-weaker.

4. Docs aligned. Every phase reference across REACT_MIGRATION_PLAN.md, PORTFOLIO_ARCHITECTURE.md, README.md, SITE_PREFLIGHT.md, QA_BASELINE.md and the React source comments checked. Source comments already matched (#23 foundation, #24 JSON) and needed no change. SITE_PREFLIGHT.md and QA_BASELINE.md contain no roadmap claims.

5. Turkish copy. tüm herkese açık sayfalarherkese açık tüm sayfalar, in the Build Log entry and the preview i18n proof.

QA on 3a780a1

Check Result
npm ci 528 packages, reproducible
npm run qa pass — 18/18 JS, 0 HTML errors, 0 spelling
npm run build:react pass — 3 routes pre-rendered
npm run qa:react pass
npm run qa:a11y 11/11, 0 errors
npm run qa:a11y:react 3/3, 0 errors
git diff --check clean

TR preview copy verified rendering in-browser with 0 console output.

No production truth or product facts changed. DO NOT MERGE.

@UAJOP
UAJOP marked this pull request as ready for review August 23, 2026 18:57
@UAJOP
UAJOP merged commit 23345d6 into main Aug 23, 2026
2 checks passed
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