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```css2122.peach-shell[data-surface="extension"] { border-radius: 20px; } 23.peach-screen { border-radius: 20px; } 242526.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```tsx4445<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```tsx165166const [showGenerator, setShowGenerator] = createSignal(false); 167168169<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; 222 overflow-wrap: break-word;223 white-space: normal; 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```tsx299300<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; 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; 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; 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.