From 4489a09f4e959c29e2f528077deda1cf7645ab08 Mon Sep 17 00:00:00 2001 From: ightevenmckane187 <214069160+ightevenmckane187@users.noreply.github.com> Date: Fri, 21 Aug 2026 09:51:20 +0000 Subject: [PATCH] Palette: Add clear user ID button and accessibility enhancements Add an interactive clear button (#clear-user-id-btn) with aria-label='Clear User ID' adjacent to #user-id-input in src/server.ts, providing input clearing functionality, proper input padding, focus restoration, and initial load state synchronization. --- .jules/palette.md | 4 ++ src/os/persistence/PersistenceLayer.ts | 13 +----- src/server.ts | 60 ++++++++++++++++++++++++-- tests/server.test.ts | 10 +++++ 4 files changed, 72 insertions(+), 15 deletions(-) diff --git a/.jules/palette.md b/.jules/palette.md index 43b36b9..ae4b7bb 100644 --- a/.jules/palette.md +++ b/.jules/palette.md @@ -33,3 +33,7 @@ ## 2026-06-17 - [Context-Aware Keyboard Shortcuts] **Learning:** Global single-key shortcuts (e.g., 's' for "Save" or "Create") can severely degrade UX by interfering with text entry in form fields. Implementing a simple check for active elements like `INPUT` or `TEXTAREA` ensures shortcuts only trigger when the user is not actively typing. **Action:** Always wrap global keyboard listeners in a check for `document.activeElement.tagName` to prevent shortcut collision during data entry. Pair this with visual shortcuts hints (``) that are preserved even during asynchronous state changes. + +## 2026-08-21 - [Clear Input Button Layout and Initial State Synchronization] +**Learning:** Adding an absolute-positioned clear button (`✕`) inside a text input container requires right padding (e.g., `padding: 8px 28px 8px 12px`) and `box-sizing: border-box` on the input element to prevent typed text from rendering beneath the icon. Additionally, visibility toggle handlers must execute both on input events and immediately on page load to handle pre-populated or browser-restored inputs. +**Action:** Always add sufficient right padding to inputs with clear buttons and call the clear button state toggle logic on initialization. diff --git a/src/os/persistence/PersistenceLayer.ts b/src/os/persistence/PersistenceLayer.ts index b60a349..73d6ad4 100644 --- a/src/os/persistence/PersistenceLayer.ts +++ b/src/os/persistence/PersistenceLayer.ts @@ -13,22 +13,13 @@ export class PersistenceLayer { */ save(data: any, key: string): Buffer { const payload = JSON.stringify(data); + const payloadByteLength = Buffer.byteLength(payload, 'utf8'); const payloadBuf = Buffer.from(payload, 'utf8'); - // Bolt Optimization: Allocate unsafe buffer for exact combined size to avoid Buffer.concat and intermediate payload allocations - const outBuf = Buffer.allocUnsafe(38 + payloadBuf.length); - - // Copy pre-allocated header and payload - outBuf.set(HEADER_BUF, 0); - outBuf.set(payloadBuf, 38); - - // Compute and write HMAC signature directly - const hmac = crypto.createHmac('sha256',. key); + const hmac = crypto.createHmac('sha256', key); hmac.update(payloadBuf); const signature = hmac.digest(); - outBuf.set(signature, 6); - const payloadByteLength = Buffer.byteLength(payload, 'utf8'); const out = Buffer.allocUnsafe(HEADER_LENGTH + SIGNATURE_LENGTH + payloadByteLength); // Zero-copy set of pre-allocated header diff --git a/src/server.ts b/src/server.ts index 6677f37..3561049 100644 --- a/src/server.ts +++ b/src/server.ts @@ -136,17 +136,45 @@ export const PRE_RENDERED_STYLES = ` font-weight: 500; margin-bottom: 0.5rem; } + .input-wrapper { + position: relative; + display: flex; + align-items: center; + width: 100%; + max-width: 300px; + } #user-id-input { background: var(--bg-color); border: 1px solid var(--border-color); color: var(--text-color); - padding: 8px 12px; + padding: 8px 28px 8px 12px; border-radius: 6px; font-size: 1rem; width: 100%; max-width: 300px; transition: border-color 0.2s; } + #clear-user-id-btn { + display: none; + position: absolute; + right: 8px; + top: 50%; + transform: translateY(-50%); + background: none; + border: none; + color: var(--text-color); + cursor: pointer; + padding: 4px; + opacity: 0.7; + font-size: 0.875rem; + line-height: 1; + z-index: 2; + } + #clear-user-id-btn:hover { opacity: 1; } + #clear-user-id-btn:focus-visible { + outline: 2px solid var(--primary); + border-radius: 4px; + } .theme-icon { transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); display: inline-block; @@ -230,7 +258,7 @@ export const PRE_RENDERED_STYLES = ` .copy-button.copied .check-icon { display: block; } .input-group { margin-bottom: 1rem; display: flex; flex-direction: column; gap: 0.5rem; } .input-group label { font-size: 0.875rem; font-weight: 500; } - .input-group input { background: var(--bg-color); border: 1px solid var(--border-color); color: var(--text-color); padding: 8px 12px; border-radius: 6px; font-size: 0.875rem; width: 100%; max-width: 300px; } + .input-group input { background: var(--bg-color); border: 1px solid var(--border-color); color: var(--text-color); padding: 8px 28px 8px 12px; border-radius: 6px; font-size: 0.875rem; width: 100%; max-width: 300px; box-sizing: border-box; } .counter-container { display: flex; justify-content: space-between; max-width: 300px; align-items: baseline; flex-wrap: wrap; gap: 8px; } #user-id-counter { font-size: 0.75rem; opacity: 0.7; } #user-id-counter.near-limit { color: #d63031; opacity: 1; font-weight: bold; } @@ -894,7 +922,10 @@ app.get("/", (req: Request, res: Response) => { 0 of 128 characters used - +
+ + +