Skip to content

feat(web): port PoW challenge page from vanilla JS to Preact#1522

Open
Xe wants to merge 11 commits intomainfrom
Xe/feat-main-challenge-uses-preact
Open

feat(web): port PoW challenge page from vanilla JS to Preact#1522
Xe wants to merge 11 commits intomainfrom
Xe/feat-main-challenge-uses-preact

Conversation

@Xe
Copy link
Contributor

@Xe Xe commented Mar 19, 2026

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-by footer 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:

  • 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
    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

Assisted-by: Claude Opus 4.6 via Claude Code

Checklist:

  • Added a description of the changes to the [Unreleased] section of docs/docs/CHANGELOG.md
  • Added test cases to the relevant parts of the codebase
  • Ran integration tests npm run test:integration (unsupported on Windows, please use WSL)
  • All of my commits have verified signatures

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>
@Xe Xe self-assigned this Mar 19, 2026
@Xe Xe requested a review from Copilot March 19, 2026 19:26
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.ts with a Preact-based web/js/main.tsx state-machine UI mounted on a new #app root.
  • Updated the web asset build pipeline to bundle .tsx via esbuild JSX automatic runtime, and added an IDE-only web/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.

Xe added 3 commits March 19, 2026 19:36
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>
Comment on lines +255 to +271
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>
</>
);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Contributor

@SlyEcho SlyEcho left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems to be working.

Xe added 3 commits March 20, 2026 10:38
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>
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Xe added 4 commits March 20, 2026 11:23
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>
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.

chore: Port the JS based proof of work frontend to Preact

3 participants