Skip to content

Make the Console's /set-password/code page follow the system's light or dark mode - #1166

Merged
JacobStephens2 merged 5 commits into
mainfrom
issue-1054
Oct 23, 2026
Merged

JacobStephens2 merged 5 commits into
mainfrom
issue-1054

Conversation

@JacobStephens2

@JacobStephens2 JacobStephens2 commented Oct 23, 2026 •

Copy link
Copy Markdown
Owner

Summary

POST /set-password/code used to answer with a bare HTML fragment that had no stylesheet and no data-theme, so browsers showed it in their default light colours. It now renders on the Console's account layout, like /set-password. With the default Theme, focus-auto, the page follows the system's light or dark mode, and a pinned -light/-dark Theme still wins (ADR 0018).

 code(form)
   check no-sign-in, CSRF
-  key? = ResendKey::find()
-  if key?  spawn delivery; reply Html(SENT)              # bare fragment, browser default colours
-  else     reply 503 OWNER                               # plain text
+  key?, config = ResendKey::find(), UserConfig::load()
+  if key?  spawn delivery (uses that config)  → 200
+  else                                        → 503
+  render set_password_code.html (extends account_base.html, data-theme = config.console.theme)
 src/console/
+├── set_password_code.html   # sent-code / email-off reply on the account layout
+├── password_off.html        # "Email password setup is off…" shared with set_password.html
 ├── set_password.html        # now includes password_off.html
 └── auth/password.rs         # code(): load config once, render through console::page

The answer is still the same for allowed and unknown addresses, and the handler still waits only on address-independent reads: the Resend key and the User config. One change is visible: a malformed User config now gets a 500 here, as it already did on GET /set-password.

Evidence

  • Before: the new browser test fails on main's handler. The page has no stylesheet, so the body is transparent:
    not ok 1 - the sent-code page follows a dark system color scheme
      expected: 'rgb(18, 18, 20)'    actual: 'rgba(0, 0, 0, 0)'
    not ok 2 - the sent-code page follows a light system color scheme
      expected: 'rgb(247, 247, 245)' actual: 'rgba(0, 0, 0, 0)'
    
    the_code_page_uses_the_console_theme_and_stylesheet also failed, and its panic printed the bare If this address is allowed, a code has been sent… fragment.
    After:
    console/tests/set-password.test.cjs   # no JS; Send code with system dark / light
      ok 1 - the sent-code page follows a dark system color scheme
      ok 2 - the sent-code page follows a light system color scheme
    tests/console/password_codes.rs
      the_code_page_uses_the_console_theme_and_stylesheet   # theme = garden-dark; 200 and no-key 503
        both contain data-theme="garden-dark" and /assets/console.css   ok
    
  • cd console && npm test: 61/61 passed. That run predates the new 503 assertion, which is in Rust.
  • cargo test --test console: 65 passed.
  • clippy -D warnings and cargo fmt --check are clean.
  • scripts/affected-tests origin/main selected the full Rust suite. Nextest isn't installed, so it fell back to cargo test. I ran cargo test --no-fail-fast before the last test-only commit: 2377 passed and 3 failed. The three failures are worktree::synchronization_tests::*_shadowing_branch_and_tag_names, and they come from this machine's global tag.gpgsign=true (git tag … fatal: no tag message?). With a clean global git config they pass (36/36). The selector's own cargo test stopped early on acquisition_advice only because I had overridden GIT_CONFIG_GLOBAL. Under the normal config that file passes (8/8). After the last commit I reran cargo test --test console and the synchronization tests.

Merge Danger

Door: two-way

Templates and one handler change. A revert restores the bare reply.

Blast Radius: cosmetic

Only the reply page of POST /set-password/code changes, plus a template partial shared with /set-password whose text is unchanged. Status codes, wording and the email are the same. The one behaviour change: a broken User config now gives 500 on this route.

Unaddressed findings

Standards

  • Test #1, duplicated theme/key loading with anonymous_page (src/console/auth.rs:220-221): not shared. anonymous_page also mints the sign-in cookie and form CSRF (auth.rs:226-227). code needs the whole UserConfig for delivery (src/console/auth/password.rs:90, &config.email). A shared loader would be a new type for one line.
  • Pre-flight checks #4, theme: String, email_enabled: bool data clump / Primitive Obsession: this predates the change. Every account template struct carries theme: String (auth.rs:220,297,335). CodePage follows them.
  • The Failed run path #5 (part), CodePage rename to SentCodePage: kept. It names the /set-password/code page, as PasswordPage names /set-password. It also renders the email-off 503 notice, so "Sent" would be wrong. (found was renamed loaded.)
  • Continuation of an existing Issue branch #6 and follow-up Spec: thirdshift, turn a GitHub issue into a ready-for-review PR unattended #2, other unthemed account replies and the OWNER duplicate: declined as out of scope. Make the web console's /set-password/code page follow the user's system for light / dark mode #1054 names only /set-password/code. The finding's test, a_wrong_code_reply_uses_the_console_theme on POST /set-password, was run and fails as described (the response is the bare Password not set… fragment). Theming /set-password (password.rs:139-140) and the sign-in failures (auth.rs:265,271) is a scope call for the Day shift: Theme the Console's other sign-in and set-password replies #1167.
  • Numbered Issue branches after a merged or closed PR #7, a malformed config now returns 500: no change. It matches GET /set-password, which loads the same config in anonymous_page (auth.rs:220) and returns 500 through console::page. The answer still doesn't depend on the address.
  • Keeping the PR mergeable: merge the Base branch and conflict Repair #8 (part), no "Sign in" link on the sent page: not added. The page links on to /set-password, which has the Sign in link (src/console/set_password.html:24).
  • Progress lines on stderr #10, duplicated browser-test harness: the reviewer accepts it as local style. Every console/tests/*.test.cjs starts its own server this way.
  • Follow-up Test #1, console::page(Ok(…)) then status_mut(): kept. It reuses the page helper's error reply. Overriding the status only after is_success() is the idiom anonymous_page already uses for its cookie (auth.rs:226).
  • Follow-up Pre-flight checks #4, key/config load errors go through failed ("Can't read Console accounts"): this predates the change. Main's handler already sent ResendKey::find errors there (main password.rs:87, error => failed(error)).

Spec

Every changed file was read by both reviewers. No changed file was left unread, and there were no generated-output exclusions.

Closes #1054

Built with claude · claude-opus-5-5 · high

Security review outcome

No unaddressed introduced findings.

/set-password/code answered with a bare HTML fragment, so the browser
showed it in its default colors whatever the system or Console Theme.
It now extends the account layout, like /set-password, with the
configured Theme; the default focus-auto follows the system's light or
dark mode. The no-key reply uses the same page with its 503.
…Theme

Address review: the no-key notice is one partial for both password pages,
the code reply renders through the Console's page helper and picks its
status once, and the test now configures a Theme and covers the 503 page.
@JacobStephens2
JacobStephens2 merged commit c7b644f into main Oct 23, 2026
18 checks passed
@JacobStephens2
JacobStephens2 deleted the issue-1054 branch October 23, 2026 17:24
@JacobStephens2 JacobStephens2 mentioned this pull request Oct 23, 2026
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.

Make the web console's /set-password/code page follow the user's system for light / dark mode

1 participant