Skip to content

fix(ui-13): login chip text reflects server error per status#122

Merged
VDP89 merged 1 commit into
mainfrom
fix/ui-13-login-chip-status-discrimination
May 16, 2026
Merged

fix(ui-13): login chip text reflects server error per status#122
VDP89 merged 1 commit into
mainfrom
fix/ui-13-login-chip-status-discrimination

Conversation

@VDP89
Copy link
Copy Markdown
Owner

@VDP89 VDP89 commented May 16, 2026

Summary

Fix

The JS handler in login.html now:

  1. Reads the response body via r.text() (previously discarded).
  2. Parses the canonical {"error":"..."} JSON.
  3. Renders the server's error phrase as the chip via slot.textContent (capitalised first char + trailing period added).
  4. Falls back to "Could not sign in." for non-JSON bodies and to "Request too large." for 413 (server ships plain text there).

The JS never invents text. It only RENDERS what the server already declared.

§3-G online-guessing invariant preserved

401 still shows the generic "Could not sign in." (server JSON {"error":"could not sign in"} → chip same after capitalisation). No username-vs-password leak. The chip-text discrimination scopes to PRE-auth rejection paths (403/429/400/422/503) where there is no credential disclosure risk — the server cannot leak credentials before it has checked them.

Files

  • src/karasu/ui/static/login.html — added chipText(status, body) helper inside the existing inline handler. r.text() is now awaited so the body can drive the chip.
  • tests/test_ui_sw.py::test_login_html_chip_text_reflects_server_error_per_status — parses login.html and pins: fallback string present, r.text() reads body, slot.textContent is assigned dynamically, 413 fallback present, data.error is the field consulted (guards against a future drift to an invented status map).
  • docs/ui/ui-13-design-brief.md — §3-E amended in-place to scope the generic-copy invariant to status 401 and seal the new pre-auth discriminating shape.

What's NOT in this PR

Test plan

  • pytest tests/test_ui_sw.py — 18/18 (17 pre-existing + 1 new)
  • pytest tests/test_auth_server.py — 52/52 (no regression at the server boundary)
  • After merge: dev operator submitting against a misconfigured origin will see "Forbidden." in the chip instead of "Could not sign in.", removing the diagnosis bias logged in 2026-05-09.

Relation to other Phase 4 PRs

Independent of #116-#121. The new test only reads login.html source; it doesn't touch any other surface.

🤖 Generated with Claude Code

Closes the phase-4-dogfood "Could not sign in" diagnosis
hygiene #5(a) (logged in PR #116's resolution block).

Background: the login form JS handler in login.html
unhid a static "Could not sign in." chip for every 4xx,
regardless of cause. The dogfood diagnosis surfaced this
as a real bias: a 403 (origin/CSRF mismatch) looked
identical to a 401 to the operator, and the hypothesis
order skewed toward credentials/rate-limit when the real
cause was origin. Same trap for 429 (rate-limit) and 503
(auth-not-configured).

Fix: the JS handler now reads the response body via
r.text(), parses the canonical {"error":...} JSON, and
renders the server's error phrase as the chip text via
slot.textContent (capitalised + trailing period added).
The JS never invents text; it only RENDERS what the
server already declared. Fallback for non-JSON bodies
keeps the generic "Could not sign in.". 413 ships plain
text ("payload too large") so it gets a dedicated
"Request too large." chip.

§3-G online-guessing invariant preserved: 401 still
shows the generic "Could not sign in." (server JSON:
"could not sign in" → chip same after capitalisation),
no username-vs-password leak. The chip-text status
discrimination scopes to PRE-auth rejection paths
(403/429/400/422/503) where there is no credential
disclosure risk.

Brief §3-E amended in-place to scope the generic-copy
invariant to status 401 only and to seal the new
discriminating shape for the pre-auth paths.

Tests:

- tests/test_ui_sw.py::test_login_html_chip_text_reflects_server_error_per_status
  — parses login.html and asserts: fallback string
  present, r.text() reads body, slot.textContent is
  assigned dynamically, 413 fallback present, data.error
  is the field consulted (not an invented status map).

Suite: 18/18 in test_ui_sw.py, 52/52 in test_auth_server.py.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@VDP89 VDP89 merged commit 434e5ac into main May 16, 2026
2 checks passed
@VDP89 VDP89 deleted the fix/ui-13-login-chip-status-discrimination branch May 16, 2026 13:54
VDP89 added a commit that referenced this pull request May 16, 2026
Reconciles the cross-PR references that the individual fix
PRs could not touch because they branched off main while the
hygiene items only existed in PR #116's resolution block.

Updates:

- Hygiene #5(a) (login chip text per status) marked resolved
  by PR #122.
- Hygiene #5(b) (`!unknown:` sentinel for direct loopback)
  marked resolved by PR #121.
- Finding #3 sub-friction 3 (modal does not surface CLI
  command) marked resolved by PR #123.

The "Outstanding sprint items" section is rewritten as
"Sprint items closed 2026-05-16" — an audit trail of the
five items addressed in one session, each linked to its
landing PR. Adjacent housekeeping (the Karasu- → Karasu
rename + PR #120 reference cleanup) is also captured for
the historical record.

Final line marks path C VPS deploy as unblocked at the code
surface; remaining gate is operational (domain + VPS +
caddy/Let's Encrypt per docs/deploy-runbook.md).

Pure docs change — no code, no tests, no brief touched.

Co-authored-by: Victor Del Puerto <VDP89@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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