feat(web): port PoW challenge page from vanilla JS to Preact#1522
feat(web): port PoW challenge page from vanilla JS to Preact#1522
Conversation
Replace the imperative DOM manipulation in web/js/main.ts with a
declarative Preact component (web/js/main.tsx). The project already
uses Preact for the timed-delay challenge (lib/challenge/preact/),
so this aligns the PoW challenge with the existing codebase direction.
## Approach
Convert web/js/main.ts to a Preact TSX component. The worker
orchestration layer (web/js/algorithms/fast.ts) stays untouched --
it is already cleanly separated and works via a Promise API.
## What changed
web/js/main.ts -> web/js/main.tsx:
- Phase-based state machine (loading -> computing -> reading/error)
replaces scattered imperative DOM updates.
- Worker lifecycle managed in useEffect; progress callback drives
state setters for speed and progress percentage.
- Speed updates remain throttled to 1 second intervals.
- i18n functions (initTranslations, t(), loadTranslations) kept as
module-level state -- no need for React context in a single-
component app.
- The <details> section stays in the templ file as server-rendered
HTML; the Preact component tracks its toggle state via useRef.
- Uses esbuild automatic JSX transform (--jsx=automatic
--jsx-import-source=preact) instead of classic pragmas.
web/build.sh:
- Add js/**/*.tsx to the glob so esbuild bundles TSX files.
- Pass --jsx=automatic --jsx-import-source=preact for .tsx files.
web/tsconfig.json (new):
- IDE-only config (noEmit) so TypeScript understands Preact JSX
types for editor diagnostics and autocompletion.
lib/challenge/proofofwork/proofofwork.templ:
- Replace individual DOM elements (img#image, p#status,
div#progress) with a <div id="app"> Preact mount point
containing server-rendered fallback (pensive image + loading
text).
- Keep <details>, <noscript>, and <div id="testarea"> outside the
Preact tree as server-rendered content.
lib/anubis.go:
- Add challenge method to the "new challenge issued" log line.
docs/docs/CHANGELOG.md:
- Add entry for the Preact rewrite.
## What stayed the same
- web/js/algorithms/fast.ts -- untouched
- web/js/algorithms/index.ts -- untouched
- web/js/worker/sha256-*.ts -- untouched
- Server-side Go code (proofofwork.go) -- untouched
- JSON script data embedding -- untouched
- Redirect URL construction -- same logic, same parameters
- Progress bar CSS in web/index.templ -- untouched
Signed-off-by: Xe Iaso <me@xeiaso.net>
Assisted-by: Claude Opus 4.6 via Claude Code
Signed-off-by: Xe Iaso <me@xeiaso.net>
There was a problem hiding this comment.
Pull request overview
Ports the Proof-of-Work (PoW) challenge page frontend from imperative DOM manipulation to a declarative Preact implementation, aligning it with the project’s existing Preact usage for other challenge UIs.
Changes:
- Replaced
web/js/main.tswith a Preact-basedweb/js/main.tsxstate-machine UI mounted on a new#approot. - Updated the web asset build pipeline to bundle
.tsxvia esbuild JSX automatic runtime, and added an IDE-onlyweb/tsconfig.json. - Updated the PoW templ markup to provide a Preact mount point + fallback content; added challenge method to the “new challenge issued” log line; documented the change in the changelog.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| web/tsconfig.json | Adds an editor/typechecking config for TS/TSX with Preact JSX types. |
| web/js/main.tsx | New Preact implementation of the PoW UI and worker orchestration glue. |
| web/js/main.ts | Removes the prior vanilla/imperative implementation. |
| web/build.sh | Bundles .tsx inputs and passes JSX flags to esbuild for TSX builds. |
| lib/challenge/proofofwork/proofofwork.templ | Introduces #app mount point with server-rendered fallback and removes standalone progress DOM. |
| lib/challenge/proofofwork/proofofwork_templ.go | Regenerated templ output reflecting the updated .templ markup. |
| lib/anubis.go | Adds the challenge method to the “new challenge issued” structured log event. |
| docs/docs/CHANGELOG.md | Notes the PoW Preact rewrite in the Unreleased section. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Translate the new challenge data missing error message across all 25 supported locales. Assisted-by: Claude Opus 4.6 via Claude Code Signed-off-by: Xe Iaso <me@xeiaso.net>
- Remove duplicate style attribute on happy image preload in templ - Remove unused getAvailableLanguages function from main.tsx - Add type annotation to currentLang variable - Add null check for missing challenge data with localized error Assisted-by: Claude Opus 4.6 via Claude Code Signed-off-by: Xe Iaso <me@xeiaso.net>
- Guard j() helper against null/empty textContent before JSON.parse - Use <button> instead of clickable <div> for "finished reading" control to support keyboard navigation and screen readers - Make currentLang a local const inside initTranslations since it is not read elsewhere Assisted-by: Claude Opus 4.6 via Claude Code Signed-off-by: Xe Iaso <me@xeiaso.net>
| if (phase === "error") { | ||
| return ( | ||
| <> | ||
| <img style="width:100%;max-width:256px;" src={errorImage} /> | ||
| <p id="status">{errorMessage}</p> | ||
| </> | ||
| ); | ||
| } | ||
|
|
||
| if (phase === "loading") { | ||
| return ( | ||
| <> | ||
| <img style="width:100%;max-width:256px;" src={pensiveURL} /> | ||
| <p id="status">{t("calculating")}</p> | ||
| </> | ||
| ); | ||
| } |
There was a problem hiding this comment.
This seems kind of redundant. Probably could use the same <img> and <p id=status> tags that are inside the part // computing or reading below.
Signed-off-by: Xe Iaso <me@xeiaso.net>
… function Signed-off-by: Xe Iaso <me@xeiaso.net>
Signed-off-by: Xe Iaso <me@xeiaso.net>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 33 out of 33 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Replace <br/> tags with em dashes and strip all other HTML tags (keeping their text content) across all 25 locale files. Assisted-by: claude-sonnet-4-6 via Claude Code Signed-off-by: Xe Iaso <me@xeiaso.net>
Use literal spaced em dash ( — ) consistent with other locale files instead of unspaced \u2014 escape sequence. Assisted-by: claude-sonnet-4-6 via Claude Code Signed-off-by: Xe Iaso <me@xeiaso.net>
The process() function in fast.ts declared Promise<string> but actually
resolved with the full worker message object { hash, data, difficulty,
nonce }. main.tsx papered over this with `any`. Introduce a shared
ChallengeResult type so the contract between workers, algorithms, and
the main component is enforced by the type checker.
Assisted-by: Claude Opus 4.6 via Claude Code
Signed-off-by: Xe Iaso <me@xeiaso.net>
The useEffect registered a toggle listener on the <details> element but never removed it, which could leak handlers on remounts or in tests. Extract the handler to a named function and return a cleanup that calls removeEventListener. Assisted-by: Claude Opus 4.6 via Claude Code Signed-off-by: Xe Iaso <me@xeiaso.net>
Closes: #1149
This is an experimental use of Claude Opus 4.6 via Claude Code to do a fairly nontrivial transformation in the Anubis project. Fine-combed review is welcome and encouraged. The rest of this PR body is generated by Claude and contains the plan that it generated when doing the big rewrite.
Civil comments in the thread are welcome. Uncivil comments will be deleted. Please take your debates about the tools being used to another forum. This use of Claude Opus is clearly disclosed in both the
Assisted-byfooter and by me stating that I used Claude Opus to do this work while recovering from major surgery.I am not paying Anthropic for this use of Claude Opus. They gave me free access with their open source program. I was surprised that I got in it, but it's been a neat tool to use regardless.
Replace the imperative DOM manipulation in web/js/main.ts with a declarative Preact component (web/js/main.tsx). The project already uses Preact for the timed-delay challenge (lib/challenge/preact/), so this aligns the PoW challenge with the existing codebase direction.
Approach
Convert web/js/main.ts to a Preact TSX component. The worker orchestration layer (web/js/algorithms/fast.ts) stays untouched -- it is already cleanly separated and works via a Promise API.
What changed
web/js/main.ts -> web/js/main.tsx:
<details>section stays in the templ file as server-rendered HTML; the Preact component tracks its toggle state via useRef.web/build.sh:
web/tsconfig.json (new):
lib/challenge/proofofwork/proofofwork.templ:
<details>,<noscript>, and<div id="testarea">outside the Preact tree as server-rendered content.lib/anubis.go:
docs/docs/CHANGELOG.md:
What stayed the same
Assisted-by: Claude Opus 4.6 via Claude Code
Checklist:
[Unreleased]section of docs/docs/CHANGELOG.mdnpm run test:integration(unsupported on Windows, please use WSL)