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
7 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 −0
manifest.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": {

view file

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",

view file

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",

view file

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+};

view file

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;

view file

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 });

view file

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">

view file