Skip to content

refactor: simplify hero scene and improve live visitor tracking - #17

Merged
Ouzx merged 9 commits into
mainfrom
claude/hopeful-cori-fmz7tm
Oct 2, 2026
Merged

Ouzx merged 9 commits into
mainfrom
claude/hopeful-cori-fmz7tm

Conversation

@Ouzx

@Ouzx Ouzx commented Oct 2, 2026

Copy link
Copy Markdown
Owner

What and why

This PR makes two major improvements:

  1. Hero scene: Removes React island overhead by converting the hero scene from a React component (HeroScene, HeroCanvas) to a plain script that progressively loads the Three.js pollen visualization. The home page now ships zero framework runtime, with the scene fetched and mounted only after the page is idle and when WebGL 2 is available.

  2. Live visitor tracking: Refactors the presence system to count visitors without broadcasting messages. The room now only tracks open sockets per visitor IP (with a limit of 10 per IP to prevent abuse), and the admin dashboard reads the count via RPC on its own schedule. This reduces memory usage and makes joins/leaves O(1) regardless of audience size.

Additional improvements:

  • Image variant serving now includes deterministic ETags for efficient revalidation without re-transforming
  • Stats rollup batches D1 inserts to respect the 100-parameter limit per statement
  • Turnstile validation now checks that tokens were solved on the correct hostname
  • JSON-LD serialization escapes < to prevent script injection
  • Post validation rejects node types the editor cannot produce as a 422 error
  • Contact form tests verify Turnstile hostname validation

Closes #[issue-number]

Checklist

  • Conventional commit title with a workspace scope
  • pnpm check, pnpm typecheck and pnpm test pass
  • Types are derived (Drizzle -> @orkide/validators), none hand-written
  • User-facing strings go through Paraglide (en and tr)
  • Errors are ApiError problems; public pages stay a pure function of their URL
  • Docs updated in docs/decisions.md

https://claude.ai/code/session_016v6LHswjLWAHsncyuRpCE5

claude added 9 commits October 2, 2026 07:44
Fresh cloud containers start with an empty local D1, so pnpm dev served 500s
(no such table: post) until migrations were applied by hand.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016v6LHswjLWAHsncyuRpCE5
The escape had degraded into replacing "<" with "<", a no-op, so a title
containing </script> could inject markup. Adds a regression test and lets
the web unit tests resolve the @/ alias and SITE.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016v6LHswjLWAHsncyuRpCE5
R2 rejected the tee'd transform stream (unknown length), so variants were
never stored and every cache miss re-ran a billable transform. Variants are
now buffered, stored and served; their ETag is known up front, so
revalidations are answered with a 304 before any R2 read or transform, and
the global etag() middleware no longer buffers and hashes image bodies. The
original is only read when the variant is missing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016v6LHswjLWAHsncyuRpCE5
One INSERT for every path exceeded D1's 100 bound parameters as soon as a
day had more than 25 distinct paths, failing the whole rollup. Rows are now
written in chunks of 25 in one atomic batch, and only the 1000 most viewed
paths per day are kept, since the beacon accepts any path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016v6LHswjLWAHsncyuRpCE5
…ures

The renderer throws on nodes and marks the editor cannot produce; that
surfaced as a 500 and an unhandled-error log instead of a 422.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016v6LHswjLWAHsncyuRpCE5
…dcasting

Any page or script could open presence sockets and inflate the visitor
count. The room now refuses handshakes whose Origin is not the site's own
(CSRF checks skip GET, so WebSockets had no origin check) and caps open
sockets per visitor at 10, keyed by a hashed IP.

Nobody reads the room's messages: public pages only need to be counted and
the dashboard polls count() over SSE. Dropping the broadcast removes the
O(n) fan-out on every join and leave (O(n^2) traffic overall).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016v6LHswjLWAHsncyuRpCE5
…name

A token from another site embedding the same widget no longer passes.
Cloudflare's dummy test secrets report example.com, so they skip the check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016v6LHswjLWAHsncyuRpCE5
The home page hydrated React only to run an idle timer that lazy-loaded the
Three.js scene. A plain Astro script now applies the same gates (flag,
reduced motion, Save-Data, WebGL 2) and mounts the scene, so the home page
ships no framework runtime: 6 instead of 10 scripts and 127 instead of 194
KiB of JavaScript. Lab scores stay within noise (median 98); ADR-024 records
the measurements and why the poster stays lazily loaded.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016v6LHswjLWAHsncyuRpCE5
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
orkide-web 5e26936 Commit Preview URL

Branch Preview URL
Oct 02 2026, 09:21 AM

@Ouzx
Ouzx merged commit 78dfa19 into main Oct 2, 2026
9 checks passed
@Ouzx
Ouzx deleted the claude/hopeful-cori-fmz7tm branch October 2, 2026 10:41
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.

2 participants