From 6bca0b3526edf674015a9b81e3c93590bae265ee Mon Sep 17 00:00:00 2001 From: adlee-was-taken Date: Sat, 2 May 2026 18:49:56 -0400 Subject: [PATCH] feat(ext/popup/item-detail): colorize revealed password field Add data-field-kind attribute to renderConcealedRow so wireFieldHandlers can distinguish password fields from other concealed rows (TOTP secrets, CVV, PIN, private keys). Apply colorizePassword() on reveal when kind is "password"; plain textContent otherwise. Pass kind through renderSections for custom-section password fields. --- extension/src/popup/components/fields.ts | 18 +++++++++++++++--- extension/src/popup/components/types/login.ts | 2 +- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/extension/src/popup/components/fields.ts b/extension/src/popup/components/fields.ts index f89c325..e269f6b 100644 --- a/extension/src/popup/components/fields.ts +++ b/extension/src/popup/components/fields.ts @@ -6,6 +6,7 @@ /// copy click handlers on any rendered rows. import { escapeHtml } from '../../shared/state'; +import { colorizePassword } from '../../shared/password-coloring'; import type { Item, Section, Field, FieldValue } from '../../shared/types'; export interface RowOpts { @@ -46,6 +47,7 @@ export interface ConcealedRowOpts { id: string; label: string; value: string; + kind?: 'password' | 'concealed'; monospace?: boolean; multiline?: boolean; } @@ -53,12 +55,15 @@ export interface ConcealedRowOpts { /// Concealed row — value rendered hidden until the user clicks "show". /// Plaintext is stored in `data-field-value` on the row element and copied /// to the visible value span on reveal. Copy button always copies plaintext. +/// When `kind` is "password", wireFieldHandlers applies colorizePassword on +/// reveal so digits/symbols/letters are rendered in distinct colours. export function renderConcealedRow(opts: ConcealedRowOpts): string { - const { id, label, value, monospace, multiline } = opts; + const { id, label, value, kind, monospace, multiline } = opts; const placeholder = multiline ? `•••• (${value.length} chars)` : '••••'; const valueClass = `field-row__value${monospace ? ' monospace' : ''}`; + const kindAttr = kind ? ` data-field-kind="${escapeHtml(kind)}"` : ''; return ` -
+
${escapeHtml(label)} ${escapeHtml(placeholder)} @@ -101,7 +106,13 @@ export function wireFieldHandlers(scope: HTMLElement): void { row.setAttribute('data-revealed', 'false'); btn.textContent = 'show'; } else { - valueEl.textContent = plaintext; + const isPassword = row.getAttribute('data-field-kind') === 'password'; + valueEl.textContent = ''; + if (isPassword) { + valueEl.appendChild(colorizePassword(plaintext)); + } else { + valueEl.textContent = plaintext; + } row.setAttribute('data-revealed', 'true'); btn.textContent = 'hide'; } @@ -150,6 +161,7 @@ export function renderSections(item: Item, idPrefix: string): string { id: `${idPrefix}-s${sIdx}-f${fIdx}`, label: field.label, value: field.value.value, + kind: field.value.kind, }); } }); diff --git a/extension/src/popup/components/types/login.ts b/extension/src/popup/components/types/login.ts index d051e57..4a1c2d3 100644 --- a/extension/src/popup/components/types/login.ts +++ b/extension/src/popup/components/types/login.ts @@ -84,7 +84,7 @@ export async function renderDetail(app: HTMLElement, item: Item): Promise ${renderSignatureBlock({ accent: 'gold', children: sigInner })}
${username ? renderRow({ label: 'username', value: username, copyable: true }) : ''} - ${renderConcealedRow({ id: 'login-password', label: 'password', value: password })} + ${renderConcealedRow({ id: 'login-password', label: 'password', value: password, kind: 'password' })} ${url ? renderRow({ label: 'url', value: url, href: url }) : ''} ${hasTotp ? `