peach_shine_spec.md

19.0 KB · raw

rendered · source

1# "Make Every Surface Shine" — Polish & Cohesion Spec2_Peach v1.4.0 target_34---56## Philosophy78Every surface should feel like it was designed as a single, deliberate object — not assembled from parts. The existing design tokens (Cormorant Garamond, IBM Plex Mono, the amber/bone palette, the spring curves) are excellent. The problems are structural and consistency failures, not aesthetic ones. No new visual language is needed. The work here is surgical: fix what breaks the spell.910---1112## 1. Strip the border-radius from the popup root1314**Problem:** `.peach-screen`, `.peach-shell`, and `.peach-shell[data-surface="extension"]` all declare `border-radius: 20px`. Chrome clips the popup viewport with a hard rectangular frame. The rounded corners are invisible — the popup just looks like it has 20px of wasted inset on each corner, slightly narrowing the effective content area. This is purely wasteful.1516**Fix:**1718In `popup.css`, remove `border-radius` from all popup root rules:1920```css21/* REMOVE these lines: */22.peach-shell[data-surface="extension"] { border-radius: 20px; }  /* delete */23.peach-screen { border-radius: 20px; }                            /* delete */2425/* On the outer container, zero out: */26.peach-shell, .peach-screen {27  border-radius: 0;28}29```3031The `border-radius` token on *inner* cards (`.peach-lock-panel`, `.peach-field-card`, etc.) stays — those are visible and intentional. Only the popup root shell gets squared off.3233---3435## 2. Rebuild the Lock / Unlock screen3637**Problem:** The lock screen tries to do three jobs at once: unlock, onboard, and provide restore paths. On the *unlock* path (user already has a vault), the "restore instead" panel is terrifying — it leads to actions that replace the locked vault, and the tag even says "replaces locked vault". There's no reason a user who just wants to type their password should see those options at all. On the *onboarding* path it makes more sense, but the layout is still dense and slightly panicked.3839**Unlock screen redesign (hasVault = true):**4041Strip it to a single focused surface. No secondary panel. No footer links. No restore paths. The lock screen's entire job is to verify identity. If someone doesn't know their master password, the answer is in Settings → Vault, not here.4243```tsx44// Unlock surface (hasVault = true) — password only, nothing else45<section class="peach-screen peach-theme lock-screen lock-screen--unlock">46  <div class="lock-centered">47    <PeachWordmark />48    <h1 class="peach-unlock-headline">49      unlock<br/>your vault.50    </h1>51    <input ... />52    <Show when={error()}><p class="peach-hint peach-hint-error">{error()}</p></Show>53    <button class="peach-btn peach-btn--accent peach-btn--md peach-lock-submit">54      unlock55    </button>56  </div>57</section>58```5960The `openRestore` function, the `restoreSurface` signal, the `RestoreSurface` type, and the secondary panel JSX are all deleted from `Lock.tsx` when `hasVault = true`. The `replacesExistingVault` prop passed to `ImportView` becomes irrelevant on the lock screen and can be removed from that code path entirely.6162**Onboarding screen redesign (hasVault = false):**6364The onboarding headline, subline, and the two restore/import paths make sense here — there's no existing vault to destroy, so offering these paths is genuinely helpful. Keep the structure but clean up the visual hierarchy:65- Wordmark top-left66- Hero: headline + one-sentence subline (shorten current copy)67- Card for "create vault" (primary action)68- Below it, flat link buttons for the two restore paths — not a full secondary panel with its own header. They're supplementary actions.6970The current onboarding note `"If you lose this password, Peach cannot recover your vault."` should be styled as a `peach-hint` below the create button, not as a standalone paragraph. It reads as an afterthought right now; it should feel like a grounding statement.7172**Restore vault → Settings:** The `openRestore("cjk")` and `openRestore("file")` paths move to **Settings → Vault** as a dedicated "Replace vault from backup" section. It should open with a disclaimer card styled like `.peach-restore-warning` (see below), require the user to confirm they understand the action is destructive, and then present the two restore options. This is a deliberate navigation, not something a user stumbles into while trying to log in.7374**CSS additions for `.lock-screen--unlock`:**7576```css77.lock-screen--unlock {78  display: flex;79  flex-direction: column;80  align-items: center;81  justify-content: center;82  gap: 0;83  padding: 28px 28px 24px;84}8586.lock-centered {87  width: 100%;88  max-width: 260px;89  display: flex;90  flex-direction: column;91  align-items: center;92  gap: 16px;93  text-align: center;94}9596.lock-centered .peach-wordmark-wrap {97  margin-bottom: 8px;98}99100.lock-centered .peach-unlock-headline {101  font-size: 36px;102  line-height: 0.95;103  margin: 0;104}105106.lock-centered .peach-underline-input {107  text-align: center;108  width: 100%;109}110111.lock-centered .peach-lock-submit {112  width: 100%;113}114115.peach-lock-restore-link {116  background: transparent;117  border: none;118  cursor: pointer;119  font-family: var(--font-mono);120  font-size: 10px;121  letter-spacing: 0.12em;122  color: var(--t2);123  padding: 4px 0;124  transition: color 150ms ease;125}126127.peach-lock-restore-link:hover {128  color: var(--t1);129}130131.peach-restore-warning {132  font-size: 11px;133  line-height: 1.6;134  color: var(--t1);135  background: rgba(220, 80, 60, 0.07);136  border: 1px solid rgba(220, 80, 60, 0.15);137  border-radius: 8px;138  padding: 10px 12px;139}140```141142---143144## 3. Inline the Generator into Add.tsx145146**Problem:** The password generator is a full navigation target. You click "generate" in Add.tsx, navigate away to GeneratorView, configure, click "use", navigate back. This is jarring. The generator controls are simple enough (one slider, four toggles, one output) to live inline as a collapsible drawer.147148**Solution:** Replace the `onOpenGenerator` navigation prop with an inline `GeneratorDrawer` component that mounts directly in the Add form below the password field. It is hidden (`height: 0`, `overflow: hidden`) until the user clicks "generate", then it expands with a smooth animation.149150**GeneratorDrawer interface:**151152```tsx153interface GeneratorDrawerProps {154  open: boolean;155  policy?: PasswordPolicy;156  onUse: (password: string) => void;157}158```159160The drawer contains the same logic as `GeneratorView` — length slider, four toggles, output display, regenerate and copy buttons — minus the overlay header and navigation. The "use" button calls `onUse(password())` and `setDrawerOpen(false)`. The "generate" button in the password field row becomes a toggle: click once to open, click again (or after using) to close.161162**Add.tsx changes:**163164```tsx165// Replace:166const [showGenerator, setShowGenerator] = createSignal(false); // replaces onOpenGenerator167168// Password field row becomes:169<div class="peach-field-value-row">170  <input class="peach-field-value peach-new-field-input" ... />171  <button172    class={`peach-btn peach-btn--ghost peach-btn--sm peach-inline-action ${showGenerator() ? "is-active" : ""}`}173    onClick={() => setShowGenerator(!showGenerator())}174  >175    generate176  </button>177</div>178<GeneratorDrawer179  open={showGenerator()}180  onUse={(pwd) => { setPassword(pwd); setShowGenerator(false); }}181/>182```183184**GeneratorDrawer CSS:**185186```css187.peach-generator-drawer {188  display: grid;189  grid-template-rows: 0fr;190  transition: grid-template-rows 320ms var(--peach-curve-arrival);191  overflow: hidden;192}193.peach-generator-drawer.is-open {194  grid-template-rows: 1fr;195}196.peach-generator-drawer-inner {197  min-height: 0;198  overflow: hidden;199}200.peach-generator-drawer-content {201  border-top: 1px solid var(--t3);202  padding: 12px 0 4px;203  display: flex;204  flex-direction: column;205  gap: 12px;206}207```208209The `grid-template-rows: 0fr → 1fr` trick gives a smooth height animation without needing a fixed height or JS measurement.210211**Fix long password overflow:**212213`peach-pwgen-output` must not overflow. Current state: no `overflow` or `word-break` set, so long generated passwords (128 chars) can push the container horizontally.214215```css216.peach-pwgen-output {217  font-family: var(--font-mono);218  font-size: 13px;219  line-height: 1.6;220  color: var(--t0);221  word-break: break-all;       /* ← key line */222  overflow-wrap: break-word;223  white-space: normal;          /* not nowrap */224  margin: 0;225  padding: 10px 12px;226  background: var(--t4);227  border-radius: 6px;228  min-height: 36px;229}230```231232**The GeneratorView file** (`src/popup/views/Generator.tsx`) stays as-is — it's still used for the standalone generator accessible from the main vault toolbar. The inline drawer is a new `GeneratorDrawer` component extracted into `src/popup/components/GeneratorDrawer.tsx`.233234---235236## 4. Fix Add.tsx — type tabs, canary card, note form237238### 4a. Type selector tabs239240The `.peach-type-tab` class has no defined styles in popup.css. The tabs render as raw browser buttons. They need to be styled as a segmented pill control that matches the rest of the design system.241242**CSS:**243244```css245.peach-type-tabs {246  display: flex;247  gap: 0;248  background: var(--t4);249  border-radius: 8px;250  padding: 3px;251}252253.peach-type-tab {254  flex: 1;255  display: flex;256  align-items: center;257  justify-content: center;258  gap: 6px;259  padding: 6px 10px;260  border-radius: 6px;261  border: none;262  background: transparent;263  cursor: pointer;264  font-family: var(--font-ui);265  font-size: 11px;266  font-weight: 400;267  letter-spacing: 0.04em;268  color: var(--t1);269  transition: background 150ms ease, color 150ms ease;270}271272.peach-type-tab.is-active {273  background: var(--s2);274  color: var(--t0);275  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);276}277278.peach-type-tab:not(.is-active):hover {279  color: var(--t0);280}281282.peach-type-tab svg {283  opacity: 0.7;284  flex-shrink: 0;285}286287.peach-type-tab.is-active svg {288  opacity: 1;289}290```291292### 4b. Canary card293294The canary section in Add.tsx uses `style="border:1px solid rgba(217,160,48,0.25);background:rgba(217,160,48,0.04);"` inline — meaning it can't be themed, can't be overridden cleanly, and breaks the light theme. Extract to a CSS class.295296**Replace the inline style on the canary `.peach-field-card`:**297298```tsx299// Before:300<div class="peach-field-card" style="border:1px solid rgba(217,160,48,0.25);background:rgba(217,160,48,0.04);">301302// After:303<div class="peach-field-card peach-canary-card">304```305306**CSS:**307308```css309.peach-canary-card {310  border: 1px solid rgba(217, 160, 48, 0.2);311  background: rgba(217, 160, 48, 0.03);312  border-radius: 8px;313  padding: 12px;314}315316.peach-canary-settings-header {317  display: flex;318  align-items: center;319  justify-content: space-between;320  cursor: pointer;321  user-select: none;322}323324.peach-canary-card .peach-label {325  color: rgba(217, 160, 48, 0.85);326}327328.peach-canary-card .peach-new-field-input {329  border-color: rgba(217, 160, 48, 0.25);330}331332.peach-canary-card .peach-new-field-input:focus {333  border-color: rgba(217, 160, 48, 0.5);334}335336.peach-canary-body {337  margin-top: 12px;338  display: flex;339  flex-direction: column;340  gap: 10px;341}342343.peach-canary-hint {344  font-size: 10px;345  color: var(--t2);346  font-family: var(--font-ui);347  line-height: 1.5;348  margin-top: 4px;349}350351.peach-canary-actions-row {352  display: flex;353  align-items: center;354  gap: 8px;355}356357.peach-canary-test-btn {358  border-color: rgba(217, 160, 48, 0.3);359  color: rgba(217, 160, 48, 0.85);360}361362.peach-canary-test-result {363  font-size: 10px;364  font-family: var(--font-ui);365}366367.peach-canary-test-result.ok { color: rgba(100, 210, 100, 0.9); }368.peach-canary-test-result.err { color: rgba(220, 80, 60, 0.9); }369```370371Remove all the matching inline styles from Add.tsx and replace with the class names above.372373### 4c. Note entry form374375The note form (`selectedType() === "note"`) renders just a textarea with `style="min-height:200px;"`. It needs:376- A proper `.peach-note-form` container with consistent padding377- The textarea to use a class `peach-note-textarea` with appropriate sizing378- A character count hint in the bottom-right corner (`{notes().length} chars`)379380```css381.peach-note-textarea {382  width: 100%;383  min-height: 180px;384  max-height: 280px;385  resize: vertical;386  background: var(--t4);387  border: 1px solid var(--t3);388  border-radius: 8px;389  padding: 10px 12px;390  font-family: var(--font-mono);391  font-size: 12px;392  line-height: 1.7;393  color: var(--t1);394  outline: none;395  transition: border-color 150ms ease;396}397398.peach-note-textarea:focus {399  border-color: var(--t2);400}401402.peach-note-char-count {403  font-family: var(--font-mono);404  font-size: 9px;405  color: var(--t3);406  text-align: right;407  margin-top: 4px;408  letter-spacing: 0.1em;409}410```411412### 4d. Field input baseline413414All `peach-new-field-input` and `peach-new-name-input` elements need consistent focus rings and a visible resting border. Currently they likely inherit nothing or use browser defaults. Define:415416```css417.peach-new-field-input {418  width: 100%;419  background: transparent;420  border: none;421  border-bottom: 1px solid var(--t3);422  padding: 4px 0;423  color: var(--t1);424  font-family: var(--font-mono);425  font-size: 13px;426  outline: none;427  transition: border-color 150ms ease;428}429430.peach-new-field-input:focus {431  border-bottom-color: var(--t1);432  color: var(--t0);433}434435.peach-new-name-input {436  width: 100%;437  background: transparent;438  border: none;439  border-bottom: 1px solid var(--t3);440  padding: 6px 0;441  font-family: var(--font-display);442  font-style: italic;443  font-size: 28px;444  color: var(--t0);445  outline: none;446  transition: border-color 150ms ease;447}448449.peach-new-name-input:focus {450  border-bottom-color: var(--t1);451}452453.peach-new-name-input::placeholder {454  color: var(--t2);455  font-style: italic;456}457458.peach-new-field-input::placeholder {459  color: var(--t3);460}461```462463---464465## 5. Remove the standalone Generator navigation path from Add.tsx466467In `App.tsx`, the `"generator"` view is reachable from Add via `onOpenGenerator`. Once the drawer is in place, the Add form no longer needs this prop. The `"generator"` view from the main vault toolbar (the wand/sparkle button) remains available — that's a valid standalone use. Just remove it as a destination from Add's `onBack`/`onOpenGenerator` flow.468469In `App.tsx`:470- Remove the `openGenerator` function call that sets view to `"generator"` when coming from Add471- AddView no longer receives `onOpenGenerator` prop472- The `GeneratorView` standalone entry point from the toolbar is preserved473474---475476## 6. General surface cohesion items477478### 6a. Overlay headers479All overlay views (Add, Health, CJKExport, etc.) share `.peach-overlay-header`. Make sure the cancel/back button on the left and the action button on the right are always vertically centered and have consistent spacing. Audit that no overlay header has a different height from any other.480481```css482.peach-overlay-header {483  display: flex;484  align-items: center;485  justify-content: space-between;486  padding: 10px 16px;487  border-bottom: 1px solid var(--t3);488  flex-shrink: 0;489  min-height: 44px; /* lock the height */490}491492.peach-overlay-title {493  font-family: var(--font-mono);494  font-size: 10px;495  letter-spacing: 0.18em;496  text-transform: uppercase;497  color: var(--t1);498  position: absolute;499  left: 50%;500  transform: translateX(-50%);501}502503.peach-overlay-action {504  position: relative;505  z-index: 1;506  min-width: 44px; /* prevent layout shift as label changes */507  text-align: center;508}509```510511### 6b. Scrollable body overflow512Any overlay body that can exceed 520px (Add.tsx with all fields, Health with many entries) must be scrollable within the popup, not invisible below the fold. The body element must not overflow the popup frame.513514```css515.peach-overlay-view {516  display: flex;517  flex-direction: column;518  height: 520px; /* match popup height exactly */519  overflow: hidden;520}521522.peach-overlay-body,523.peach-add-form,524.peach-health-body,525.peach-pwgen-body {526  flex: 1;527  overflow-y: auto;528  overflow-x: hidden;529  overscroll-behavior: contain;530  scrollbar-width: thin;531  scrollbar-color: var(--t3) transparent;532  padding: 14px 16px;533}534```535536This is the fix for long passwords rendering off-screen — the symptom is actually the container not scrolling, not the password field itself overflowing.537538### 6c. Toggle component539The `.peach-toggle` / `.peach-toggle-knob` pattern is used in Generator, Settings, and Canary. Add a consistent `transition` so the knob slides smoothly:540541```css542.peach-toggle {543  position: relative;544  width: 28px;545  height: 16px;546  border-radius: 8px;547  background: var(--t3);548  border: none;549  cursor: pointer;550  flex-shrink: 0;551  transition: background 200ms ease;552}553554.peach-toggle.is-on {555  background: var(--peach-accent);556}557558.peach-toggle-knob {559  position: absolute;560  top: 2px;561  left: 2px;562  width: 12px;563  height: 12px;564  border-radius: 50%;565  background: var(--t0);566  transition: transform 200ms var(--peach-curve-settle);567}568569.peach-toggle.is-on .peach-toggle-knob {570  transform: translateX(12px);571}572```573574### 6d. Button states575Every `peach-btn` needs a `:focus-visible` ring that doesn't use the browser default outline (which looks wrong on the dark background):576577```css578.peach-btn:focus-visible {579  outline: none;580  box-shadow: 0 0 0 2px var(--bg), 0 0 0 4px rgba(217, 104, 48, 0.5);581}582```583584### 6e. Scrollbar in Vault list585The vault entry list scrollbar is currently browser-default (visible and ugly on Windows/Linux). Add:586587```css588.peach-vault-list {589  scrollbar-width: thin;590  scrollbar-color: var(--t3) transparent;591}592593.peach-vault-list::-webkit-scrollbar {594  width: 3px;595}596597.peach-vault-list::-webkit-scrollbar-thumb {598  background: var(--t3);599  border-radius: 2px;600}601```602603---604605## 7. File change summary606607| File | Changes |608|------|---------|609| `src/popup/styles/popup.css` | Remove root border-radius; add `.lock-screen--unlock`, `.lock-centered`, `.peach-lock-restore-link`, `.peach-restore-sheet`, `.peach-type-tabs`, `.peach-type-tab`, `.peach-canary-card` and all child classes, `.peach-note-textarea`, `.peach-new-field-input`, `.peach-new-name-input`, `.peach-generator-drawer`, `.peach-pwgen-output` overflow fix, `.peach-overlay-*` height lock, scrollbar polish, toggle transition, focus-visible rings |610| `src/popup/views/Lock.tsx` | Two distinct layouts: unlock (password only, nothing else) and onboarding (create + two flat restore links); delete `restoreSurface` signal, secondary panel, and all restore code from unlock path |611| `src/popup/views/Settings.tsx` | Add "Vault" section with "Replace vault from backup" — disclaimer card + CJK and file restore paths behind a destructive-action confirmation |612| `src/popup/views/Add.tsx` | Remove `onOpenGenerator` prop; add `showGenerator` signal; inline `<GeneratorDrawer>`; replace canary inline styles with classes; replace note textarea with `peach-note-textarea` class + char count; replace type tab inline styles |613| `src/popup/components/GeneratorDrawer.tsx` | New file — extracts generator logic as a collapsible inline drawer |614| `src/popup/views/Generator.tsx` | No changes — remains available standalone from vault toolbar |615| `src/popup/App.tsx` | Remove `openGenerator` as Add destination; keep it as toolbar destination |616617---618619## 8. What this is NOT620621- Not a redesign. Every color, every font, every animation curve stays.622- Not feature work. No new functionality.623- Not a theme change. The amber/bone palette is correct.624- Not a layout overhaul of the vault list or entry view — those are polished. Touch only the surfaces called out above.