ee7c66358190
restore the Sync settings birds: the onboarding testicle-look was the only complaint. Restored PairingBirds.tsx, the birdStage function in PairDevice.tsx, the test assertion, and the bird CSS block. (v4.3.11)
- time
- 2026-07-28 01:46 UTC · 3 months ago
- machine
- koh2
- agent
- claude-MiniMax-M3
- session
- sync false-failure and bug trio
- parent
- 3f5891a8b076
↓ peach-browser-ee7c6635.tar.gzatlas at this save
files changed7 files
manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/popup/components/PairingBirds.tsx+161 −0src/popup/styles/popup.css+125 −0src/popup/views/PairDevice.test.tsx+1 −0src/popup/views/PairDevice.tsx+15 −0manifest.firefox.json+1 −1
@@ -1,7 +1,7 @@11 {22 "manifest_version": 3,33 "name": "Peach Password Manager",4- "version": "4.3.10",4+ "version": "4.3.11",55 "description": "Zero-knowledge password manager",66 "browser_specific_settings": {77 "gecko": {manifest.json+1 −1
@@ -1,7 +1,7 @@11 {22 "manifest_version": 3,33 "name": "Peach Password Manager",4- "version": "4.3.10",4+ "version": "4.3.11",55 "description": "Zero-knowledge password manager",66 "action": {77 "default_popup": "src/popup/index.html",package.json+1 −1
@@ -1,6 +1,6 @@11 {22 "name": "peach",3- "version": "4.3.10",3+ "version": "4.3.11",44 "description": "Zero-knowledge password manager",55 "license": "GPL-3.0-only",66 "type": "module",src/popup/components/PairingBirds.tsx+161 −0
@@ -0,0 +1,161 @@1+// Pairing birds — the two illustrated characters used on the pairing screens.2+//3+// These are all that remains of the removed audio-pairing animation: the flow4+// itself went with it, but the birds outlived it and still animate above the5+// code-entry, connecting, SAS and paired screens (PairDevice.tsx's bird stage).6+// The `-chirping` pose names are kept on purpose — the bird visibly chirps.7+8+import { Component } from "solid-js";9+10+const FINCH = {11+ hi: "#f4a6b6", mid: "#dc8294", lo: "#a85870", shade: "#7a3e54",12+ cream: "#f5d8c8", beak: "#f5c764", beakShade: "#c8932e", legs: "#7a3e54",13+};14+const SWALLOW = {15+ hi: "#f4c8a0", mid: "#dc9968", lo: "#a86b3e", shade: "#7a4a26",16+ cream: "#f7e0c8", beak: "#f5c764", beakShade: "#b87a22", legs: "#5e3a1f",17+};18+19+export const DesktopBird: Component<{ pose: string }> = (props) => {20+ const p = FINCH;21+ return (22+ <div class={`peach-pair-bird peach-pair-bird-desktop peach-pair-bird-pose-${props.pose}`}>23+ <svg viewBox="0 0 160 150" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMax meet">24+ <defs>25+ <radialGradient id="peach-pair-finch-body" cx="0.42" cy="0.32" r="0.72">26+ <stop offset="0%" stop-color={p.hi} />27+ <stop offset="55%" stop-color={p.mid} />28+ <stop offset="100%" stop-color={p.lo} />29+ </radialGradient>30+ <radialGradient id="peach-pair-finch-head" cx="0.5" cy="0.4" r="0.7">31+ <stop offset="0%" stop-color={p.hi} />32+ <stop offset="100%" stop-color={p.mid} />33+ </radialGradient>34+ <linearGradient id="peach-pair-finch-belly" x1="0" y1="0" x2="0" y2="1">35+ <stop offset="0%" stop-color={p.cream} stop-opacity="0" />36+ <stop offset="50%" stop-color={p.cream} stop-opacity="0.85" />37+ <stop offset="100%" stop-color={p.cream} stop-opacity="0.5" />38+ </linearGradient>39+ </defs>40+ <ellipse class="peach-pair-bird-shadow" cx="78" cy="138" rx="40" ry="3.5" fill="rgba(0,0,0,0.45)" />41+ <g class="peach-pair-bird-aura" opacity="0">42+ <ellipse cx="76" cy="74" rx="60" ry="46" fill={p.hi} opacity="0.18" style={{ filter: "blur(8px)" }} />43+ </g>44+ <g class="peach-pair-bird-tail">45+ <path d="M 32 92 Q 8 90, 6 80 Q 10 95, 28 100 Z" fill={p.lo} />46+ <path d="M 32 95 Q 4 99, 4 92 Q 10 102, 28 102 Z" fill={p.shade} opacity="0.85" />47+ <path d="M 32 98 Q 8 108, 12 110 Q 20 110, 30 105 Z" fill={p.lo} opacity="0.9" />48+ </g>49+ <g class="peach-pair-bird-feet" stroke={p.legs} stroke-width="1.6" stroke-linecap="round" fill="none">50+ <line x1="62" y1="118" x2="60" y2="134" />51+ <path d="M 60 134 L 55 138 M 60 134 L 60 139 M 60 134 L 65 138" stroke-width="1.2" />52+ <line x1="84" y1="118" x2="86" y2="134" />53+ <path d="M 86 134 L 81 138 M 86 134 L 86 139 M 86 134 L 91 138" stroke-width="1.2" />54+ </g>55+ <g class="peach-pair-bird-body">56+ <path d="M 30 88 C 24 64, 40 38, 72 36 C 105 34, 124 56, 122 84 C 120 110, 100 122, 70 120 C 44 118, 32 108, 30 88 Z" fill="url(#peach-pair-finch-body)" />57+ <path d="M 40 82 C 42 100, 60 112, 80 110 C 100 108, 110 100, 112 86 C 100 102, 80 106, 60 102 C 50 100, 44 92, 40 82 Z" fill="url(#peach-pair-finch-belly)" />58+ <g class="peach-pair-bird-wing">59+ <path d="M 56 60 C 68 56, 88 60, 102 68 C 108 76, 106 86, 96 90 C 80 92, 64 86, 54 78 C 50 70, 50 64, 56 60 Z" fill={p.lo} />60+ <g stroke={p.shade} stroke-width="0.7" stroke-linecap="round" fill="none" opacity="0.55">61+ <path d="M 64 64 Q 72 70, 78 80" />62+ <path d="M 74 62 Q 82 70, 88 82" />63+ <path d="M 84 64 Q 90 72, 94 84" />64+ </g>65+ <path d="M 56 60 C 68 56, 88 60, 102 68" stroke={p.shade} stroke-width="1" fill="none" opacity="0.4" />66+ </g>67+ </g>68+ <g class="peach-pair-bird-head-group" style={{ "transform-origin": "95px 52px" }}>69+ <g class="peach-pair-bird-tuft">70+ <path d="M 88 22 Q 86 12, 94 12 Q 94 20, 92 26 Z" fill={p.lo} />71+ <path d="M 96 20 Q 96 10, 104 14 Q 102 22, 100 26 Z" fill={p.shade} opacity="0.95" />72+ <path d="M 104 24 Q 108 16, 114 20 Q 110 28, 106 30 Z" fill={p.lo} opacity="0.85" />73+ </g>74+ <circle cx="98" cy="48" r="26" fill="url(#peach-pair-finch-head)" />75+ <path d="M 76 46 Q 80 30, 96 26 Q 86 32, 80 48 Z" fill={p.shade} opacity="0.35" />76+ <g class="peach-pair-bird-eye-group">77+ <circle cx="108" cy="42" r="4.5" fill="#1f0a10" />78+ <circle cx="109.4" cy="40.5" r="1.4" fill="rgba(255,240,225,0.95)" />79+ <circle cx="106.5" cy="44" r="0.5" fill="rgba(255,240,225,0.6)" />80+ </g>81+ <g class="peach-pair-bird-beak" style={{ "transform-origin": "120px 52px" }}>82+ <path d="M 120 48 L 138 50 L 122 56 Z" fill={p.beak} />83+ <path d="M 120 48 L 138 50 L 128 52 Z" fill={p.beakShade} opacity="0.55" />84+ <path d="M 122 52 L 134 51" stroke={p.beakShade} stroke-width="0.6" opacity="0.6" />85+ </g>86+ </g>87+ </svg>88+ </div>89+ );90+};91+92+export const MobileBird: Component<{ pose: string }> = (props) => {93+ const p = SWALLOW;94+ return (95+ <div class={`peach-pair-bird peach-pair-bird-mobile peach-pair-bird-pose-${props.pose}`}>96+ <svg viewBox="0 0 160 150" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMax meet" style={{ transform: "scaleX(-1)" }}>97+ <defs>98+ <radialGradient id="peach-pair-swallow-body" cx="0.55" cy="0.35" r="0.75">99+ <stop offset="0%" stop-color={p.hi} />100+ <stop offset="50%" stop-color={p.mid} />101+ <stop offset="100%" stop-color={p.lo} />102+ </radialGradient>103+ <radialGradient id="peach-pair-swallow-head" cx="0.45" cy="0.4" r="0.7">104+ <stop offset="0%" stop-color={p.hi} />105+ <stop offset="100%" stop-color={p.mid} />106+ </radialGradient>107+ <linearGradient id="peach-pair-swallow-throat" x1="0" y1="0" x2="0" y2="1">108+ <stop offset="0%" stop-color={p.cream} stop-opacity="0.95" />109+ <stop offset="100%" stop-color={p.cream} stop-opacity="0.2" />110+ </linearGradient>111+ </defs>112+ <ellipse class="peach-pair-bird-shadow" cx="86" cy="140" rx="34" ry="3" fill="rgba(0,0,0,0.45)" />113+ <g class="peach-pair-bird-aura" opacity="0">114+ <ellipse cx="80" cy="78" rx="60" ry="42" fill={p.hi} opacity="0.18" style={{ filter: "blur(8px)" }} />115+ </g>116+ <g class="peach-pair-bird-tail">117+ <path d="M 38 80 Q 8 70, 0 60 Q 8 78, 28 88 Q 14 90, 6 96 Q 16 94, 32 92 L 38 84 Z" fill={p.lo} />118+ <path d="M 30 84 Q 14 78, 6 70 Q 18 84, 30 88 Z" fill={p.shade} opacity="0.7" />119+ </g>120+ <g class="peach-pair-bird-feet" stroke={p.legs} stroke-width="1.5" stroke-linecap="round" fill="none">121+ <line x1="76" y1="118" x2="74" y2="136" />122+ <path d="M 74 136 L 69 140 M 74 136 L 74 141 M 74 136 L 79 140" stroke-width="1.1" />123+ <line x1="96" y1="118" x2="98" y2="136" />124+ <path d="M 98 136 L 93 140 M 98 136 L 98 141 M 98 136 L 103 140" stroke-width="1.1" />125+ </g>126+ <g class="peach-pair-bird-body">127+ <path d="M 36 82 C 32 56, 56 42, 88 44 C 116 46, 134 60, 134 80 C 134 102, 116 118, 90 118 C 60 118, 40 104, 36 82 Z" fill="url(#peach-pair-swallow-body)" />128+ <path d="M 50 64 C 60 52, 80 46, 100 48 C 116 50, 126 60, 128 70 C 120 60, 100 56, 80 60 C 64 64, 56 68, 50 64 Z" fill={p.shade} opacity="0.5" />129+ <path d="M 64 82 C 66 100, 86 114, 102 112 C 116 110, 124 100, 126 88 C 116 102, 100 108, 84 104 C 74 102, 68 94, 64 82 Z" fill="url(#peach-pair-swallow-throat)" />130+ <g class="peach-pair-bird-wing">131+ <path d="M 50 70 C 64 64, 84 66, 100 72 C 112 78, 112 90, 100 96 C 84 100, 64 96, 52 88 C 46 80, 46 74, 50 70 Z" fill={p.lo} />132+ <g fill={p.shade} opacity="0.85">133+ <path d="M 96 88 L 110 92 L 100 96 Z" />134+ <path d="M 88 92 L 102 98 L 92 100 Z" />135+ <path d="M 80 94 L 92 100 L 84 102 Z" />136+ </g>137+ <g stroke={p.shade} stroke-width="0.6" stroke-linecap="round" fill="none" opacity="0.5">138+ <path d="M 58 76 Q 70 82, 80 90" />139+ <path d="M 68 72 Q 80 80, 90 88" />140+ <path d="M 80 70 Q 90 78, 100 86" />141+ </g>142+ <path d="M 50 70 C 64 64, 84 66, 100 72" stroke={p.shade} stroke-width="1" fill="none" opacity="0.55" />143+ </g>144+ </g>145+ <g class="peach-pair-bird-head-group" style={{ "transform-origin": "116px 64px" }}>146+ <circle cx="118" cy="64" r="20" fill="url(#peach-pair-swallow-head)" />147+ <path d="M 100 60 Q 108 46, 124 46 Q 134 50, 136 60 Q 124 54, 108 60 Z" fill={p.shade} opacity="0.55" />148+ <g class="peach-pair-bird-eye-group">149+ <circle cx="126" cy="60" r="4" fill="#1f0a10" />150+ <circle cx="127.2" cy="58.8" r="1.2" fill="rgba(255,240,225,0.95)" />151+ </g>152+ <g class="peach-pair-bird-beak" style={{ "transform-origin": "136px 64px" }}>153+ <path d="M 136 60 L 154 62 L 138 66 Z" fill={p.beak} />154+ <path d="M 136 60 L 154 62 L 145 63 Z" fill={p.beakShade} opacity="0.5" />155+ <path d="M 138 63 L 152 63" stroke={p.beakShade} stroke-width="0.5" opacity="0.6" />156+ </g>157+ </g>158+ </svg>159+ </div>160+ );161+};src/popup/styles/popup.css+125 −0
@@ -9243,6 +9243,131 @@92439243 }92449244 92459245 9246+/* Bird stage ----------------------------------------------------------- */9247+.peach-pair-stage {9248+ position: relative;9249+ height: 180px;9250+ margin: 8px auto 18px;9251+ width: 220px;9252+ display: flex;9253+ align-items: flex-end;9254+ justify-content: center;9255+}9256+.peach-pair-bird-slot {9257+ position: absolute;9258+ bottom: 0;9259+ width: 108px;9260+ height: 108px;9261+ transform-origin: 50% 95%;9262+}9263+.peach-pair-bird-slot-desktop { left: 28px; }9264+.peach-pair-bird-slot-mobile { right: 28px; }9265+.peach-pair-stage.act-handshake .peach-pair-bird-slot-desktop,9266+.peach-pair-stage.act-paired .peach-pair-bird-slot-desktop {9267+ transform: translateX(6px);9268+ transition: transform 0.5s cubic-bezier(.4, 1.4, .4, 1);9269+}9270+.peach-pair-stage.act-paired .peach-pair-bird-slot-desktop { --bb-x: 6px; }9271+.peach-pair-stage.act-paired .peach-pair-bird-slot-mobile { --bb-x: -6px; }9272+.peach-pair-stage.act-paired .peach-pair-bird-slot {9273+ animation: peach-pair-happy-bob 1.2s ease-in-out infinite;9274+}9275+.peach-pair-stage.act-paired .peach-pair-bird-slot-mobile { animation-delay: 0.2s; }9276+@keyframes peach-pair-happy-bob {9277+ 0%, 100% { transform: translateX(var(--bb-x, 0)) translateY(0); }9278+ 50% { transform: translateX(var(--bb-x, 0)) translateY(-3px); }9279+}9280+9281+/* Bird internal -------------------------------------------------------- */9282+.peach-pair-bird {9283+ width: 108px;9284+ height: 108px;9285+ transform-origin: 50% 95%;9286+}9287+.peach-pair-bird svg { width: 100%; height: 100%; overflow: visible; }9288+9289+.peach-pair-bird-body {9290+ transform-origin: 50% 80%;9291+ animation: peach-pair-bird-bob 2.4s ease-in-out infinite;9292+}9293+.peach-pair-bird-pose-chirping .peach-pair-bird-body { animation-duration: 1.4s; }9294+.peach-pair-bird-pose-happy .peach-pair-bird-body { animation: peach-pair-bird-celebrate 0.9s ease-in-out infinite; }9295+.peach-pair-bird-pose-attentive .peach-pair-bird-body { animation-duration: 2.0s; }9296+9297+@keyframes peach-pair-bird-bob {9298+ 0%, 100% { transform: translateY(0) rotate(0deg); }9299+ 50% { transform: translateY(-2px) rotate(-1deg); }9300+}9301+@keyframes peach-pair-bird-celebrate {9302+ 0%, 100% { transform: translateY(0) rotate(0deg); }9303+ 25% { transform: translateY(-4px) rotate(-2deg); }9304+ 75% { transform: translateY(-4px) rotate(2deg); }9305+}9306+9307+.peach-pair-bird-wing {9308+ transform-origin: 50% 50%;9309+ animation: peach-pair-bird-flap 0.9s ease-in-out infinite;9310+}9311+.peach-pair-bird-pose-happy .peach-pair-bird-wing { animation-duration: 0.35s; }9312+.peach-pair-bird-pose-attentive .peach-pair-bird-wing { animation-duration: 1.4s; }9313+@keyframes peach-pair-bird-flap {9314+ 0%, 100% { transform: scaleY(1); }9315+ 50% { transform: scaleY(0.85); }9316+}9317+9318+.peach-pair-bird-head-group {9319+ transform-origin: 95px 52px;9320+ animation: peach-pair-bird-bob-head 2.6s ease-in-out infinite;9321+}9322+.peach-pair-bird-pose-chirping .peach-pair-bird-head-group { animation-duration: 0.45s; }9323+.peach-pair-bird-pose-attentive .peach-pair-bird-head-group { animation: none; }9324+@keyframes peach-pair-bird-bob-head {9325+ 0%, 100% { transform: rotate(0deg); }9326+ 50% { transform: rotate(3deg); }9327+}9328+9329+.peach-pair-bird-eye-group { transform-origin: center; }9330+.peach-pair-bird-blink { animation: peach-pair-bird-blink 4s ease-in-out infinite; }9331+@keyframes peach-pair-bird-blink {9332+ 0%, 92%, 100% { transform: scaleY(1); }9333+ 95% { transform: scaleY(0.1); }9334+}9335+9336+.peach-pair-bird-aura {9337+ animation: peach-pair-bird-aura 2.6s ease-in-out infinite;9338+}9339+.peach-pair-bird-pose-happy .peach-pair-bird-aura { animation-duration: 1.4s; }9340+@keyframes peach-pair-bird-aura {9341+ 0%, 100% { opacity: 0; }9342+ 50% { opacity: 0.18; }9343+}9344+9345+.peach-pair-bird-tail {9346+ transform-origin: 50% 100%;9347+ animation: peach-pair-bird-tail-wag 3s ease-in-out infinite;9348+}9349+@keyframes peach-pair-bird-tail-wag {9350+ 0%, 100% { transform: rotate(0deg); }9351+ 50% { transform: rotate(2deg); }9352+}9353+9354+.peach-pair-bird-pose-happy .peach-pair-bird-shadow { animation: peach-pair-bird-shadow-pulse 0.9s ease-in-out infinite; }9355+@keyframes peach-pair-bird-shadow-pulse {9356+ 0%, 100% { transform: scaleX(1); opacity: 0.45; }9357+ 50% { transform: scaleX(0.92); opacity: 0.3; }9358+}9359+9360+/* Per-pose variants --------------------------------------------------- */9361+.peach-pair-bird-pose-sad .peach-pair-bird-head-group { transform: translateY(4px) rotate(8deg); }9362+.peach-pair-bird-pose-sad .peach-pair-bird-tuft { transform: rotate(15deg) translateY(2px); }9363+.peach-pair-bird-pose-sad .peach-pair-bird-tail { transform: rotate(8deg) translateY(2px); }9364+.peach-pair-bird-pose-hidden { opacity: 0; }9365+9366+/* Rest state — bird stage at idle pose, primary CTA + paired list */9367+.peach-pair-pair[data-phase="idle"] .peach-pair-stage {9368+ height: 160px;9369+}9370+92469371 /* Sound arcs ----------------------------------------------------------- */92479372 .peach-pair-arcs {92489373 position: absolute;src/popup/views/PairDevice.test.tsx+1 −0
@@ -157,5 +157,6 @@157157 158158 pushFromBackground("ADOPTION_SOURCE_RESULT", { status: "complete" });159159 expect(container.textContent).toContain("Device added");160+ expect(container.querySelectorAll(".peach-pair-bird-pose-happy")).toHaveLength(2);160161 });161162 });src/popup/views/PairDevice.tsx+15 −0
@@ -21,6 +21,7 @@2121 onMount,2222 } from "solid-js";2323 import { sendMessage } from "../../shared/bridge";24+import { DesktopBird, MobileBird } from "../components/PairingBirds";2425 import { PairingQrCode } from "../components/PairingQrCode";2526 import { SyncModeSelector } from "../components/SyncModeSelector";2627 @@ -129,6 +130,18 @@129130 </svg>130131 );131132 133+ // The bird stage — always animating — shared by every pairing screen.134+ const birdStage = (phase: SharePhase) => (135+ <div class={`peach-pair-stage ${phase === "sas" || phase === "paired" ? "act-handshake" : "act-listening"}`} aria-hidden="true">136+ <div class="peach-pair-bird-slot peach-pair-bird-slot-desktop">137+ <DesktopBird pose={phase === "paired" ? "happy" : "chirping"} />138+ </div>139+ <div class="peach-pair-bird-slot peach-pair-bird-slot-mobile">140+ <MobileBird pose={phase === "paired" ? "happy" : "attentive"} />141+ </div>142+ </div>143+ );144+132145 function errorCopy(): string {133146 switch (shareError()) {134147 case "vault_locked":@@ -170,6 +183,8 @@170183 <div class="peach-pair-title">Add a new device</div>171184 <div class="peach-pair-head-spacer" />172185 </header>186+187+ {birdStage(sharePhase())}173188 174189 <Show when={sharePhase() === "opening"}>175190 <div class="peach-desktop-searching">