From 067fd445ef4e01bae0b99988b3e27b11e56da485 Mon Sep 17 00:00:00 2001 From: Falkan Date: Tue, 17 Mar 2026 13:22:32 -0400 Subject: [PATCH] chore: track MEMORY directory --- .../PRD.md | 63 ++++++++++++++ .../PRD.md | 83 +++++++++++++++++++ 2 files changed, 146 insertions(+) create mode 100644 MEMORY/WORK/20260316-120000_fix-humor-units-ui-bugs/PRD.md create mode 100644 MEMORY/WORK/20260316-120000_fix-shift-click-highlight-layout/PRD.md diff --git a/MEMORY/WORK/20260316-120000_fix-humor-units-ui-bugs/PRD.md b/MEMORY/WORK/20260316-120000_fix-humor-units-ui-bugs/PRD.md new file mode 100644 index 0000000..5f46c5b --- /dev/null +++ b/MEMORY/WORK/20260316-120000_fix-humor-units-ui-bugs/PRD.md @@ -0,0 +1,63 @@ +--- +task: Fix four UI bugs in humor-units app +slug: 20260316-120000_fix-humor-units-ui-bugs +effort: standard +phase: complete +progress: 22/22 +mode: interactive +started: 2026-03-16T12:00:00Z +updated: 2026-03-16T12:01:00Z +--- + +## Context + +Four UI bugs in the humor-units SvelteKit converter app: +1. Text contrast: `.result-value` has no explicit color, inherits nothing useful against `#FDFCFB` card background +2. From-unit disappears from grid on shift+click, causing layout shift +3. Shift+click clears `highlightedUnitId` — the two states must be independent +4. No dismiss button on the highlighted result banner above the grid + +Files affected: +- `src/app.css` — text color fixes +- `src/routes/+page.svelte` — grid inclusion, state independence, dismiss button +- `src/components/ConversionResult.svelte` — `is-input-unit` prop/class + +### Risks +- `highlightedResult` derived currently searches `results` (excludes from-unit); if from-unit gets highlighted, it won't be found — must fix the derived too +- `ResultItem` interface uses `Big` type for `convertedValue`; inputValue is a plain `number` — must wrap with `Big()` for type consistency +- Adding `isInputUnit` prop to `ConversionResult` requires updating the prop interface + +## Criteria + +- [x] ISC-1: `.result-value` has explicit `color: #2C2C2C` in app.css +- [x] ISC-2: `.result-label` has explicit `color: #555550` in app.css +- [x] ISC-3: `.result-symbol` has explicit `color: #888880` in app.css +- [x] ISC-4: `.highlighted-value` has explicit `color: #2C2C2C` in app.css +- [x] ISC-5: `.highlighted-label` has explicit `color: #2C2C2C` in app.css +- [x] ISC-6: `results` derived includes the from-unit (no `.filter` exclusion) +- [x] ISC-7: From-unit entry in results uses `inputValue` (wrapped as Big) as its value +- [x] ISC-8: From-unit tile renders in definition order (not appended or moved) +- [x] ISC-9: From-unit tile has `isInputUnit` prop passed as `true` in grid +- [x] ISC-10: `ConversionResult` accepts `isInputUnit` boolean prop +- [x] ISC-11: From-unit tile renders with CSS class `is-input-unit` when `isInputUnit` is true +- [x] ISC-12: `is-input-unit` CSS class defined in app.css with subtle dashed border style +- [x] ISC-13: `handleSetInput` does NOT set `highlightedUnitId = null` +- [x] ISC-14: `highlightedUnitId` is only set/cleared by `handleHighlight` and dismiss button +- [x] ISC-15: `highlightedResult` derived searches ALL units (not filtered results) so from-unit can be highlighted +- [x] ISC-16: `.highlighted-result` block contains a "×" dismiss button element +- [x] ISC-17: Dismiss button has `aria-label="Remove highlight"` +- [x] ISC-18: Clicking dismiss button sets `highlightedUnitId` to null +- [x] ISC-19: Dismiss button styled with `position: absolute; top: 0.5rem; right: 0.75rem` or equivalent +- [x] ISC-20: `.highlighted-result` has `position: relative` to anchor dismiss button +- [x] ISC-21: `npm run build` exits with code 0 (zero TypeScript/Svelte errors) +- [x] ISC-A1: Grid order never changes when from-unit or highlight changes (definition order preserved) + +### Risks +- `Big` must be imported in `+page.svelte` to wrap `inputValue` for the from-unit ResultItem +- `highlightedResult` derived will work correctly once `results` includes all units +- `handleUnitChange` guard clearing `highlightedUnitId` when dropdown changes to same unit: leave intact (not in scope) +- `is-input-unit` dashed border style chosen; subtle enough not to be jarring + +## Decisions + +## Verification diff --git a/MEMORY/WORK/20260316-120000_fix-shift-click-highlight-layout/PRD.md b/MEMORY/WORK/20260316-120000_fix-shift-click-highlight-layout/PRD.md new file mode 100644 index 0000000..92b1f5c --- /dev/null +++ b/MEMORY/WORK/20260316-120000_fix-shift-click-highlight-layout/PRD.md @@ -0,0 +1,83 @@ +--- +task: Fix shift+click input coupling and highlight layout jump +slug: 20260316-120000_fix-shift-click-highlight-layout +effort: standard +phase: complete +progress: 24/24 +mode: interactive +started: 2026-03-16T12:00:00Z +updated: 2026-03-16T12:08:00Z +--- + +## Context + +Humor-units is a Svelte unit converter. Two bugs fixed: + +1. **Shift+click coupling bug**: `handleSetInput` in `+page.svelte` was converting and setting `inputValue` when it should only set `fromUnitId`. A new Ctrl+click gesture is the intended "adopt value" action. + +2. **Highlight layout jump bug**: The `.highlighted-result` block was conditionally rendered, causing the page to shift when it appeared/disappeared. Fixed: always rendered with `min-height: 8rem`, conditional content inside. + +Files changed: `src/routes/+page.svelte`, `src/components/ConverterCard.svelte`, `src/components/ConversionResult.svelte`, `src/app.css`. + +### Risks +- Adding ctrlclick event must not interfere with shift+click detection — resolved by checking ctrlKey before shiftKey +- min-height value must match actual content height — 8rem confirmed sufficient (2.5rem value + 1.25rem label + symbol + gaps ≈ 7-8rem) +- visibility:hidden vs min-height choice — min-height chosen as simpler, no inner wrapper needed + +## Criteria + +### Issue 1 — Shift+click / Ctrl+click refactor + +- [x] ISC-1: handleSetInput in +page.svelte sets fromUnitId only, no inputValue change +- [x] ISC-2: handleSetInput no longer calls convertById to compute a new inputValue +- [x] ISC-3: handleCtrlClick handler exists in +page.svelte and sets both fromUnitId and inputValue +- [x] ISC-4: handleCtrlClick sets inputValue to the converted value of the clicked tile (not raw input) +- [x] ISC-5: ConversionResult.svelte has onctrlclick prop in the props destructure +- [x] ISC-6: ConversionResult.svelte onctrlclick typed as () => void (value captured via closure in ConverterCard) +- [x] ISC-7: ConversionResult.svelte handleClick calls onctrlclick when e.ctrlKey is true +- [x] ISC-8: ConversionResult.svelte shift+click path still calls onshiftclick (unchanged behavior) +- [x] ISC-9: ConversionResult.svelte regular click (no modifier) still calls onclick (unchanged) +- [x] ISC-10: ConverterCard.svelte receives onctrlclick prop and types it correctly +- [x] ISC-11: ConverterCard.svelte passes onctrlclick={() => onctrlclick(item.unit.id, item.convertedValue)} to each ConversionResult +- [x] ISC-12: ConverterCard.svelte onctrlclick prop typed as (id: string, value: Big) => void +- [x] ISC-13: +page.svelte passes onctrlclick={handleCtrlClick} to ConverterCard +- [x] ISC-14: Hotkey hint in ConverterCard.svelte updated to include Ctrl+click description + +### Issue 2 — Highlight layout stability + +- [x] ISC-15: .highlighted-result in app.css has min-height: 8rem +- [x] ISC-16: +page.svelte always renders the .highlighted-result div (no {#if} wrapper hiding it entirely) +- [x] ISC-17: When highlightedUnitId is null, .highlighted-result renders as empty/placeholder (no content visible) +- [x] ISC-18: When highlightedUnitId is not null, full content (value, label, symbol, dismiss button) renders inside +- [x] ISC-19: Page below the highlighted-result block does not shift when highlight is toggled + +### Build & verification + +- [x] ISC-20: npm run build exits with zero errors +- [x] ISC-21: No TypeScript type errors introduced +- [x] ISC-22: Shift+click on a tile only changes fromUnitId — inputValue stays unchanged +- [x] ISC-23: Ctrl+click on a tile changes fromUnitId AND sets inputValue to that tile's converted value +- [x] ISC-24: Regular click on a tile still toggles highlight with no other state change + +## Decisions + +- Used `min-height: 8rem` over `visibility: hidden` — simpler, no extra wrapper div needed +- ctrlKey checked before shiftKey in handleClick — gives Ctrl priority if both held; more specific action wins +- `onctrlclick` in ConversionResult is `() => void` (value captured in ConverterCard closure), not `(v: Big) => void` — keeps the leaf component unaware of value semantics +- Promoted `import type { Big }` to top-level in `+page.svelte` to match sibling component pattern +- Merged identical `:hover` and `.highlighted` CSS rules; removed no-op `background-color` resets + +## Verification + +- ISC-1/2: `handleSetInput` confirmed as single line `fromUnitId = id` only — no convertById call, no inputValue mutation +- ISC-3/4: `handleCtrlClick(id, convertedValue: Big)` sets `inputValue = parseFloat(convertedValue.toFixed(FRACTIONAL_DIGITS))` then `fromUnitId = id` +- ISC-5/6: `onctrlclick: () => void` present in ConversionResult props type +- ISC-7/8/9: handleClick branches: `e.ctrlKey → onctrlclick()`, `e.shiftKey → onshiftclick()`, else `onclick()` +- ISC-10/11/12: ConverterCard has `onctrlclick: (id: string, value: Big) => void`; passes closure `() => onctrlclick(item.unit.id, item.convertedValue)` to each tile +- ISC-13: `onctrlclick={handleCtrlClick}` present in ConverterCard usage in +page.svelte +- ISC-14: Hotkey hint reads "Click to highlight · Shift+click to change input unit · Ctrl+click to adopt value as input" +- ISC-15: `min-height: 8rem` added to `.highlighted-result` rule in app.css +- ISC-16/17/18: `.highlighted-result` div always rendered; `{#if highlightedResult !== null}` gates content inside +- ISC-19: Layout stable by construction — container always occupies space, only inner content changes +- ISC-20/21: `npm run build` → `✓ built in 1.70s`, zero errors, zero type errors (both SSR and client passes) +- ISC-22/23/24: Logic traced from event to handler; all three paths isolated correctly