fuzzy find my records ken.waow.tech
embeddings pds search
6
fork

Configure Feed

Select the types of activity you want to include in your feed.

bring back the flavor carousel, fix iOS input zoom

carousel: 8 static lines describing what the backend is doing right now
(CAR walk, zat parsing, bge-small batching, cosine search cost, reuse by
(uri, cid), etc), rotating every 4.5s during the load phase. when
pollStatus sees prior_build_ms / prior_count come back, the pool is
extended in place with two calibrated lines that report the user's real
last-run numbers — they join the rotation alongside the static facts
instead of pinning. killed it during the branding pass on a "link, don't
explain" principle that overshot; the carousel does a different job from
the about modal (occupying the wait with something useful vs. answering
"what is this when asked").

ios zoom: safari auto-zooms into any text input rendered smaller than
16px on tap. the body inputs were at clamp(13px, 1.6vmin, 15px) which
tripped it on every phone. switched both the signin/search inputs and
the share-modal url input to font-size: max(16px, var(--text-*)) — 16
floor on mobile, clamp-scaled on desktop.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

+73 -19
+67 -17
backend/src/assets/main.js
··· 50 50 let lastFresh = 0; 51 51 let lastFreshAt = 0; 52 52 let smoothedRate = 0; 53 + let flavorTimer = null; 54 + let flavorIdx = 0; 55 + let flavorPool = []; 56 + 57 + // static educational lines describing what the backend is doing. mixed 58 + // with calibrated "last build was X" lines when prior stats are known, 59 + // rotating every few seconds during the load phase so the wait isn't empty. 60 + const STATIC_FLAVOR = [ 61 + "walking your repo as a single CAR file via com.atproto.sync.getRepo.", 62 + "parsing the CAR locally with zat — blocks + MST traversal, no network after the fetch.", 63 + "embedding with bge-small-en-v1.5 through llama.cpp. 16 records per forward pass.", 64 + "each record becomes a 384-dim float32 vector. cosine distance ≈ meaning.", 65 + "unchanged records are reused by (uri, cid). only new or changed ones are re-embedded.", 66 + "nothing leaves your PDS. model runs local, vectors live on your repo.", 67 + "saving writes a tech.waow.ken.pack record + blobs. delete is one click.", 68 + "brute force cosine across the whole pack takes ~5 ms for 17k vectors.", 69 + ]; 70 + 71 + function buildFlavorPool(priorMs, priorCount) { 72 + const pool = [...STATIC_FLAVOR]; 73 + if (priorMs > 0 && priorCount > 0) { 74 + const priorSec = (priorMs / 1000).toFixed(1); 75 + const rate = ((priorCount / priorMs) * 1000).toFixed(0); 76 + // inject calibrated lines at the front so the first thing the user 77 + // sees is specific to their repo, not generic 78 + pool.unshift( 79 + `last full build: ${priorCount.toLocaleString()} records in ${priorSec}s (${rate} rec/s).`, 80 + `your repo averaged ${rate} rec/s last time. expect similar on fresh records.`, 81 + ); 82 + } 83 + return pool; 84 + } 53 85 54 86 // ---------- helpers ---------- 55 87 ··· 110 142 progressFill.style.width = "0%"; 111 143 progressStage.textContent = "starting…"; 112 144 progressRate.textContent = ""; 113 - progressFlavor.textContent = ""; 145 + 146 + // seed the rotation with the static pool; pollStatus will rebuild it 147 + // later if prior-run stats come back from the backend and we want to 148 + // mix in calibrated lines. 149 + flavorPool = buildFlavorPool(0, 0); 150 + flavorIdx = Math.floor(Math.random() * flavorPool.length); 151 + progressFlavor.textContent = flavorPool[flavorIdx]; 114 152 progressEl.classList.remove("hidden"); 153 + 154 + if (flavorTimer) clearInterval(flavorTimer); 155 + flavorTimer = setInterval(() => { 156 + flavorIdx = (flavorIdx + 1) % flavorPool.length; 157 + progressFlavor.style.opacity = "0"; 158 + setTimeout(() => { 159 + progressFlavor.textContent = flavorPool[flavorIdx]; 160 + progressFlavor.style.opacity = "1"; 161 + }, 200); 162 + }, 4500); 115 163 } 116 164 117 - // Set the single-line progress caption — used for calibrated "last build 118 - // was X" messaging when we have prior-run metadata. No carousel; the about 119 - // modal handles any narrative. 120 - function setFlavor(msg) { 121 - if (progressFlavor.textContent === msg) return; 122 - progressFlavor.style.opacity = "0"; 123 - setTimeout(() => { 124 - progressFlavor.textContent = msg; 125 - progressFlavor.style.opacity = "1"; 126 - }, 150); 165 + // replace the static flavor pool with a pool that includes calibrated 166 + // lines from the prior pack. safe to call multiple times — if nothing 167 + // actually changed we no-op to avoid restarting the rotation. 168 + function updateFlavorPool(priorMs, priorCount) { 169 + const next = buildFlavorPool(priorMs, priorCount); 170 + if (next.length === flavorPool.length) return; 171 + flavorPool = next; 172 + // nudge index so the first calibrated line shows on the next tick 173 + flavorIdx = 0; 127 174 } 128 175 129 176 function stopProgress() { 177 + if (flavorTimer) { 178 + clearInterval(flavorTimer); 179 + flavorTimer = null; 180 + } 130 181 progressEl.classList.add("hidden"); 131 182 } 132 183 ··· 284 335 const reused = records_reused || 0; 285 336 const priorMs = prior_build_ms || 0; 286 337 const priorCount = prior_count || 0; 287 - // calibrated flavor line: if we have a prior pack's build stats, 288 - // show the user's real numbers. single line, no rotation. 338 + // calibrated flavor lines: if we have prior build stats, extend the 339 + // rotating pool with a couple lines specific to this repo. the 340 + // carousel keeps moving; the static educational lines still rotate 341 + // in alongside the calibrated ones. 289 342 if (priorMs > 0 && priorCount > 0) { 290 - const priorSec = (priorMs / 1000).toFixed(1); 291 - setFlavor( 292 - `last build: ${priorCount.toLocaleString()} records in ${priorSec}s. unchanged records are reused.`, 293 - ); 343 + updateFlavorPool(priorMs, priorCount); 294 344 } 295 345 296 346 if (status === "ready") {
+6 -2
backend/src/assets/style.css
··· 145 145 color: var(--fg); 146 146 padding: clamp(12px, 2vmin, 16px); 147 147 font: inherit; 148 - font-size: var(--text-body); 148 + /* 16px floor — iOS Safari auto-zooms into any text input rendered 149 + smaller than that, which is obnoxious on phones. max() keeps the 150 + desktop clamp behavior intact when --text-body ever exceeds 16. */ 151 + font-size: max(16px, var(--text-body)); 149 152 border-radius: var(--radius); 150 153 outline: none; 151 154 transition: border-color 0.15s; ··· 308 311 color: var(--fg); 309 312 padding: 10px 12px; 310 313 font: inherit; 311 - font-size: var(--text-small); 314 + /* same 16px floor — iOS zooms on anything smaller */ 315 + font-size: max(16px, var(--text-small)); 312 316 border-radius: var(--radius); 313 317 margin-top: clamp(10px, 2vmin, 14px); 314 318 outline: none;