feat(web): eye-toggle on every password / secret field#1873
Conversation
Adds a built-in show/hide affordance to the InputField primitive whenever type='password'. The trailing eye / eye-off icon flips the rendered DOM type without mutating the caller's prop, so the call-site semantic stays 'this is a secret'. A co-located PasswordVisibilityGroup context lets paired fields (e.g. Password + Confirm Password on the Create Admin Account screen) share a single toggle, while unrelated secrets in the same dialog (provider API keys, OAuth client secrets, connection credentials) keep independent toggles.
Also fixes a parse5 warning surfaced by Vite on web/index.html: the CSP nonce meta tag now uses single-quoted attribute syntax so the embedded Caddy template '{{placeholder "http.request.uuid"}}' parses cleanly without changing what Caddy substitutes at serve time.
Pre-reviewed by 6 agents, 8 findings addressed: docs/reference/web-design-system.md now documents the new toggle, hidePasswordToggle prop, and PasswordVisibilityGroup; docs/security.md notes the single-quoted attribute syntax used by the CSP-nonce meta tag; input-field tests gain keyboard-activation and value-capture cases; Storybook gains PasswordDisabled / PasswordWithError / PasswordNoToggle / PasswordCustomTrailing stories; LoginPage form container uses the space-y-section-gap density token; AccountStep password fields gain autoComplete=new-password for password-manager hinting.
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Scanned FilesNone |
There was a problem hiding this comment.
Code Review
This pull request introduces a built-in password visibility toggle for the InputField component and a PasswordVisibilityGroup context provider to synchronize visibility across multiple password fields. The changes include comprehensive unit tests, updated Storybook stories, and integration into the login and account setup pages. Additionally, the CSP nonce meta tag in web/index.html was updated to use single quotes for better compatibility with HTML parsers. A performance improvement was suggested to wrap the visibility toggle function in useCallback to prevent unnecessary re-renders of the toggle button.
| const toggleVisible = () => { | ||
| if (groupContext !== null) { | ||
| groupContext.setVisible(!groupContext.visible) | ||
| } else { | ||
| setLocalVisible((prev) => !prev) | ||
| } | ||
| } |
There was a problem hiding this comment.
For performance optimization and to follow React best practices, it's recommended to wrap the toggleVisible function in useCallback. This will prevent the function from being recreated on every render of InputVariant, which in turn prevents unnecessary re-renders of the PasswordToggleButton component. You'll also need to add useCallback to your React imports.
| const toggleVisible = () => { | |
| if (groupContext !== null) { | |
| groupContext.setVisible(!groupContext.visible) | |
| } else { | |
| setLocalVisible((prev) => !prev) | |
| } | |
| } | |
| const toggleVisible = useCallback(() => { | |
| if (groupContext !== null) { | |
| groupContext.setVisible(!groupContext.visible) | |
| } else { | |
| setLocalVisible((prev) => !prev) | |
| } | |
| }, [groupContext]) |
Merging this PR will not alter performance
Comparing Footnotes
|
Wrap toggleVisible in useCallback (gemini-code-assist suggestion on input-field.tsx:238) to stabilize the handler reference and avoid recreating the closure on every render.
<!-- HIGHLIGHTS_START --> ## Highlights > _AI-generated summary (model: `openai/gpt-4.1-mini` via GitHub Models). Commit-based changelog below._ ### What you'll notice - Password and secret fields now include an eye-toggle for easier visibility control. - Containers running without probes are shown as healthy in the doctor command. - Unloaded and missing PR-review agents are restored and available again. ### What's new - Gate baseline protection is enhanced to block em-dashes during writing. ### Under the hood - Replaced Atlas with yoyo-migrations for persistence management. - Refactored codebase extensively, including context-bound user authentication and registry pattern for enums. - Improved linting by draining magic number usages and tightening mock and constant checks. - Updated CI to retry Docker pushes on network timeout errors. - Updated apko lockfiles for dependency management. <!-- HIGHLIGHTS_END --> :robot: I have created a release *beep* *boop* --- ## [0.8.3](v0.8.2...v0.8.3) (2026-05-12) ### Features * harden gate baseline protection + block em-dashes at write time ([#1860](#1860)) ([b41f151](b41f151)) * **web:** eye-toggle on every password / secret field ([#1873](#1873)) ([9070387](9070387)) ### Bug Fixes * **ci:** retry Docker push on Go net/http deadline + cancellation errors ([#1877](#1877)) ([23a0bfa](23a0bfa)) * **cli:** render running-no-probe containers as healthy in doctor ([#1870](#1870)) ([6263795](6263795)) * restore unloaded and missing PR-review agents ([#1875](#1875)) ([db004fd](db004fd)), closes [#1871](#1871) ### Refactoring * bind authenticated user via ContextVar ([#1858](#1858)) ([57ed0b4](57ed0b4)) * code-structure cleanup (sub-tasks D + F + G + H + I) ([#1859](#1859)) ([362e5c8](362e5c8)) * convert enum dispatch to registry pattern ([#1854](#1854)) ([e90550e](e90550e)) * drain no_magic_numbers baseline to zero via Final hoists ([#1856](#1856) phase 2) ([#1872](#1872)) ([ec8109e](ec8109e)) * drain pagination + loop-init + kill-switch baselines ([#1857](#1857)) ([#1868](#1868)) ([115c3c2](115c3c2)) * **persistence:** replace Atlas with yoyo-migrations ([#1876](#1876)) ([1b7e975](1b7e975)), closes [#1874](#1874) * protocols audit follow-up (REVIEW + fold pass) ([#1869](#1869)) ([af33ddb](af33ddb)) * protocols audit follow-up REMOVE pass ([#1867](#1867)) ([dd1eebc](dd1eebc)) * tighten check_mock_spec gate, add mock_of[T], drain baseline ([#1862](#1862)) ([240a253](240a253)) * tighten check_no_magic_numbers for named module constants ([#1856](#1856)) ([#1866](#1866)) ([90c933b](90c933b)) ### CI/CD * update apko lockfiles ([#1863](#1863)) ([2bd32e6](2bd32e6)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). --------- Co-authored-by: synthorg-repo-bot[bot] <279117679+synthorg-repo-bot[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Summary
Adds a built-in show / hide affordance to every password / secret input across the dashboard.
web/src/components/ui/input-field.tsx: whentype="password", renders anEye/EyeOffbutton as the trailing element. Internally deriveseffectiveTypeso the caller'stype="password"semantic stays put. Co-locates aPasswordVisibilityGroupReact Context provider so paired fields share a single toggle.web/src/pages/setup/AccountStep.tsxandweb/src/pages/LoginPage.tsx(setup mode) wrap their Password + Confirm Password fields in<PasswordVisibilityGroup>so toggling one reveals both.connection-type-fields.ts) inherit the toggle automatically with independent state per field, because they all flowtype="password"through the same primitive.Password,PasswordGrouped,PasswordDisabled,PasswordWithError,PasswordNoToggle,PasswordCustomTrailingstories.aria-pressed,aria-label,type="button"), keyboard activation (Enter / Space), value capture while revealed, opt-out, caller-suppliedtrailingElement, grouped vs independent visibility, and form-submission safety.Adjacent fixes folded in (caught during pre-PR review)
web/index.html: switched the CSP-nonce meta tag to single-quoted attribute syntax so the embedded Caddy template{{placeholder "http.request.uuid"}}parses cleanly under parse5 / Vite (no behavioural change at serve time, the Caddytemplatesengine substitutes the token regardless of outer-quote style).web/src/pages/LoginPage.tsx: form container now usesspace-y-section-gapinstead of hardcodedspace-y-6so the layout respects density tokens.web/src/pages/setup/AccountStep.tsx: password and confirm-password fields now setautoComplete="new-password"so password managers categorise them correctly during initial setup.docs/reference/web-design-system.md: documents the newInputFieldtoggle,hidePasswordToggleopt-out, andPasswordVisibilityGroup.docs/security.md: notes the meta tag's single-quoted attribute syntax in the CSP Nonce Infrastructure section.Test plan
npm --prefix web run lint(ESLint, zero warnings).npm --prefix web run type-check(tsc).npm --prefix web run test -- --run(3060 tests pass).Review coverage
Pre-reviewed by 6 agents (frontend-reviewer, design-token-audit, security-reviewer with frontend supplemental checks, test-quality-reviewer, docs-consistency, comment-quality-rot). 8 valid findings; all 8 implemented in commit
059d868e2.Tooling note
The
/pre-pr-reviewrun surfaced an unrelated harness bug: three sub-agents (pr-test-analyzer,silent-failure-hunter,comment-analyzer) declared in.claude/agents/are not loaded by the Claude Code harness, so the skill'ssubagent_type: pr-test-analyzerdispatch fails. Filed as #1871; falls back tocode-reviewerwith a custom prompt for now.