<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Pentine Pejay</title>
    <description>The latest articles on DEV Community by Pentine Pejay (@pentine_pejay).</description>
    <link>https://dev.to/pentine_pejay</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4080255%2F60701d24-daf1-4ced-b483-687b917b64f7.png</url>
      <title>DEV Community: Pentine Pejay</title>
      <link>https://dev.to/pentine_pejay</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pentine_pejay"/>
    <language>en</language>
    <item>
      <title>The Cold and Hard Log</title>
      <dc:creator>Pentine Pejay</dc:creator>
      <pubDate>Sat, 17 Oct 2026 17:26:06 +0000</pubDate>
      <link>https://dev.to/pentine_pejay/the-cold-and-hard-log-5dd4</link>
      <guid>https://dev.to/pentine_pejay/the-cold-and-hard-log-5dd4</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week2-2026-10-12"&gt;Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Dede Mensah sells cheese. One display fridge out front, one walk-in in the back,&lt;br&gt;
and a clipboard screwed to the wall next to each. Twice a day she writes down a&lt;br&gt;
temperature. The UK Food Standards Agency scheme she trades under (Safer Food&lt;br&gt;
Better Business, the retail HACCP flavour) says she has to: monitor the cold&lt;br&gt;
points, record them, act when something goes out of range. The clipboard does&lt;br&gt;
none of that between the two times she looks at it. If the display fridge drifts&lt;br&gt;
warm at 2pm and she checks at 9am and 5pm, the paper says everything was fine.&lt;/p&gt;

&lt;p&gt;Dede is a composite. I did not have a real cheesemonger to hand during the build&lt;br&gt;
window, so I built for a specific made-up one with a real, legally-driven&lt;br&gt;
problem. The HACCP obligation is not invented. The clipboard gap is not&lt;br&gt;
invented. That is the part worth solving.&lt;/p&gt;
&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;Baridi turns a fridge sensor stream into two things Dede's clipboard cannot give&lt;br&gt;
her: a breach timeline and an alert that does not lie.&lt;/p&gt;

&lt;p&gt;The name is Swahili. "Baridi" means cold, and it is the everyday word for the&lt;br&gt;
weather being chilly or a drink being cold, so it names exactly what the tool&lt;br&gt;
watches: is the cold still cold? That fits the Side Hustle theme too. This is&lt;br&gt;
built for the person running the whole shop alone, the cheesemonger who is also&lt;br&gt;
the buyer, the cleaner, and the compliance officer. Baridi is the extra pair of&lt;br&gt;
hands that watches the fridge overnight so the side hustle does not fail its&lt;br&gt;
inspection over a thing nobody was awake to see.&lt;/p&gt;

&lt;p&gt;Replay a day of readings and you get the timeline. Every stretch where the&lt;br&gt;
temperature stayed over 5C for at least ten minutes shows up as an excursion&lt;br&gt;
with its start, end, duration, and peak. A headline sums it: "2 excursions over&lt;br&gt;
5C, 43 min total." The alert side writes one record per distinct excursion, and&lt;br&gt;
it keeps writing correctly even if the process running it is killed halfway&lt;br&gt;
through.&lt;/p&gt;

&lt;p&gt;Who pays for this? A one-person food business already pays, in time and in risk.&lt;br&gt;
The clipboard is unpaid labour that still leaves a hole an inspector can see.&lt;br&gt;
The commercial fix is a subscription HACCP-logging service, priced monthly,&lt;br&gt;
holding your readings on someone else's server. Baridi is the local version: the&lt;br&gt;
readings stay in the shop, there is no per-reading fee, and the breach math is&lt;br&gt;
inspectable. For a margin-thin cheese counter, local and $0-per-reading is the&lt;br&gt;
difference between doing it and not.&lt;/p&gt;
&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Live:&lt;/strong&gt; &lt;a href="https://baridi.tech" rel="noopener noreferrer"&gt;baridi.tech&lt;/a&gt;. The breach timeline runs there against&lt;br&gt;
a real TimescaleDB, served over HTTPS. The dashboard is at&lt;br&gt;
&lt;a href="https://baridi.tech/dashboard" rel="noopener noreferrer"&gt;baridi.tech/dashboard&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here is the timeline as Dede sees it. A FAIL status for the day, the two&lt;br&gt;
excursions each drawn against the 5C line, with the peak temperature and minutes&lt;br&gt;
over called out. You can switch sensors and days, and the footer reports the live&lt;br&gt;
dataset size and the measured query time:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5b7s1onywydbprh2rrg6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5b7s1onywydbprh2rrg6.png" alt="Baridi breach timeline: "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And the day she wants. Every sensor held at or below 5C, a PASS / all-clear state&lt;br&gt;
instead of a wall of green-wash:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fe6t1znb2umi6i2l6hzjs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fe6t1znb2umi6i2l6hzjs.png" alt="Baridi all-clear state: PASS status, "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The same data as raw API output (&lt;code&gt;curl https://baridi.tech/api/timeline&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"day"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-10-12"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"threshold_c"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;5.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sustain_minutes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"headline"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2 excursions over 5C, 43 min total"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"excursions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"sensor_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fridge-01"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-10-12T10:04:00+00:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"end"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-10-12T10:35:00+00:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"minutes_over"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"max_temp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;9.404&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"sensor_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fridge-01"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-10-12T17:04:00+00:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"end"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-10-12T17:14:00+00:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"minutes_over"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"max_temp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;7.11&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"disclaimer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"A logging and alert aid, not a certified compliance record."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The first excursion is a door left open: a 32-minute climb peaking at 9.4C mid&lt;br&gt;
morning. The second is a defrost cycle: 11 minutes peaking at 7.1C. A third&lt;br&gt;
event, a single-minute spike to 9.87C at 05:00, is in the data on purpose and&lt;br&gt;
does not appear here, because ten minutes of sustain is the rule and one minute&lt;br&gt;
is not a breach.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The kill-and-resume proof.&lt;/strong&gt; This is the one that matters for a logging tool,&lt;br&gt;
because a logger you cannot trust after a crash is worse than paper. The alert&lt;br&gt;
activity was given a 25-second delay so I could kill its worker after the breach&lt;br&gt;
was detected but before the alert was written, then restart a fresh worker.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;time (UTC)&lt;/th&gt;
&lt;th&gt;event&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;21:01:20.590&lt;/td&gt;
&lt;td&gt;worker A started (25s alert delay)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;21:01:24.595&lt;/td&gt;
&lt;td&gt;workflow submitted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;21:01:25.301&lt;/td&gt;
&lt;td&gt;worker A: alert started for the 10:04 excursion, not yet written&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;21:01:25.614&lt;/td&gt;
&lt;td&gt;worker A killed with &lt;code&gt;kill -9&lt;/code&gt; mid-alert; nothing is running&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;21:01:25.617&lt;/td&gt;
&lt;td&gt;alerts.log line count while down: 0 (the alert was not written before the crash)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;21:01:30.623&lt;/td&gt;
&lt;td&gt;worker B restarted, no delay&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;21:01:56.316&lt;/td&gt;
&lt;td&gt;Temporal redelivers the activity; the alert fires, about 31s late&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;21:01:56.544&lt;/td&gt;
&lt;td&gt;final alert count: 2, one per excursion, none lost, none doubled&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The alert arrived late, not lost, and it did not fire twice. Final log:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fridge-01:2026-10-12:2026-10-12T10:04:00+00:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"minutes_over"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"max_temp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;9.404&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fridge-01:2026-10-12:2026-10-12T17:04:00+00:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"minutes_over"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"max_temp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;7.11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This proof is on the site too, at &lt;a href="https://baridi.tech/durability" rel="noopener noreferrer"&gt;baridi.tech/durability&lt;/a&gt;,&lt;br&gt;
shown as a recorded test rather than a live action:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fiegfg826f0veeurkh8b1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fiegfg826f0veeurkh8b1.png" alt="Baridi durability page: the recorded kill-and-resume sequence, worker killed mid-alert, Temporal redelivering the activity about 31 seconds later, final alert count 2 with none lost and none doubled."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Naive row-scan vs continuous aggregate.&lt;/strong&gt; The breach math has a dumb version&lt;br&gt;
(pull every raw row, stitch in Python) and the TimescaleDB version (read a&lt;br&gt;
continuous aggregate, group in SQL). On one clean day they agree exactly and the&lt;br&gt;
naive one is a touch faster, because 1440 rows is nothing:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;naive row-scan:            best of 5 runs 4.08 ms
continuous-aggregate:      best of 5 runs 4.52 ms
paths agree on breaches:   True
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That is the honest toy-scale result. A compliance log is not one day, though. It&lt;br&gt;
is months across several fridges. Loaded to 1,038,240 readings, the same&lt;br&gt;
single-day query tells a different story in the plan:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NAIVE single-day:  Buffers: shared hit=5782   (hands 5760 raw rows to Python)
HEAVY single-day:  Buffers: shared hit=59      (returns 4 finished rows)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;About 98% fewer page reads, and the breach detection happens in the engine&lt;br&gt;
instead of in a Python loop. Wall-clock is 14.96 ms vs 19.63 ms, a 1.31x&lt;br&gt;
single-day speedup at that volume. Adding one new day to the million-row history&lt;br&gt;
refreshes only that day's buckets in 1.27s, not a full rebuild.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The &lt;code&gt;time_bucket&lt;/code&gt; SQL&lt;/strong&gt; that produces the timeline:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;MATERIALIZED&lt;/span&gt; &lt;span class="k"&gt;VIEW&lt;/span&gt; &lt;span class="n"&gt;readings_1min&lt;/span&gt;
&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timescaledb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;continuous&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;sensor_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;time_bucket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'1 minute'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;temp_c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;max_temp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;avg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;temp_c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;avg_temp&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;readings&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;sensor_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;time_bucket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'1 minute'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Excursions come from a gaps-and-islands query over that view: flag each minute&lt;br&gt;
over or under threshold, open a new island on each change of state, keep islands&lt;br&gt;
that last at least the sustain window. Full query and plans are in NOTES.md.&lt;/p&gt;
&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/kiprutopentine" rel="noopener noreferrer"&gt;
        kiprutopentine
      &lt;/a&gt; / &lt;a href="https://github.com/kiprutopentine/ColdLedger" rel="noopener noreferrer"&gt;
        ColdLedger
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Baridi&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;A cold-chain temperature stream becomes a breach timeline and a durable alert
log. Replay a day of fridge readings, see every excursion over threshold with
its start, end, duration, and peak temperature, and get an alert that survives a
worker crash without firing twice or losing a reading.&lt;/p&gt;
&lt;p&gt;Baridi is a logging and alert aid, not a certified compliance record.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What it does&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Ingests a time-series of temperature readings into TimescaleDB as a hypertable.&lt;/li&gt;
&lt;li&gt;Computes sustained excursions with a continuous aggregate (&lt;code&gt;time_bucket&lt;/code&gt;) and
an engine-side gaps-and-islands query, not a raw row scan.&lt;/li&gt;
&lt;li&gt;Runs a durable Temporal workflow that ingests, checks, and alerts, with an
idempotent alert keyed by &lt;code&gt;{sensor_id}:{day}:{excursion-start}&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Shows the day's breach timeline on a one-page UI.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Prerequisites&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Docker (for TimescaleDB)&lt;/li&gt;
&lt;li&gt;Python 3.12 with venv&lt;/li&gt;
&lt;li&gt;Temporal CLI (&lt;code&gt;temporal server start-dev&lt;/code&gt;) for the durable workflow. Not
needed for the offline durability proof.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Setup&lt;/h2&gt;

&lt;/div&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;python3&lt;/pre&gt;…
&lt;/div&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/kiprutopentine/ColdLedger" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  data/day.jsonl  (1440 readings, injected excursions)
        |
        v
  +----------------+        +---------------------------+
  |  Temporal      |        |  TimescaleDB              |
  |  workflow      | -----&amp;gt; |  readings (hypertable)    |
  |  (deterministic|ingest  |  readings_1min (cont. agg)|
  |   body)        |        +---------------------------+
  |     |          |                 |
  |     | check    | &amp;lt;---------------+  excursions(day) in SQL
  |     v          |
  |  activities    |        +---------------------------+
  |  (side effects)| -----&amp;gt; |  data/alerts.log          |
  |  idempotent    | alert  |  (one line per excursion) |
  |  alert         |        +---------------------------+
  +----------------+
        |
        v
  FastAPI /api/timeline  --&amp;gt;  breach timeline, served at baridi.tech
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The split is deliberate. The Temporal workflow body is deterministic: it reads&lt;br&gt;
no clock except &lt;code&gt;workflow.now()&lt;/code&gt;, touches no disk, and makes no network call&lt;br&gt;
itself. Everything with a side effect (writing readings, running the excursion&lt;br&gt;
query, firing an alert) is an activity. That is what lets Temporal replay the&lt;br&gt;
workflow history after a crash and land in the same place.&lt;/p&gt;

&lt;p&gt;The alert activity is the careful part. Its key is&lt;br&gt;
&lt;code&gt;{sensor_id}:{day}:{excursion-start}&lt;/code&gt;, it checks a persisted seen-set before&lt;br&gt;
firing, and it records the key with an atomic &lt;code&gt;os.replace&lt;/code&gt; before appending the&lt;br&gt;
human-readable line. A crash between the two still leaves the key marked seen,&lt;br&gt;
so a resume cannot double-fire.&lt;/p&gt;

&lt;p&gt;There is no AI model in the critical path, and that is on purpose. The breach&lt;br&gt;
decision is arithmetic: temperature over threshold, sustained past a window. A&lt;br&gt;
model surfaces nothing the code cannot verify, so the code owns the facts. I&lt;br&gt;
left a hook for an optional blip-vs-breach model and then did not wire it,&lt;br&gt;
because the sustain window already rejects the single-minute spike that a model&lt;br&gt;
would have been asked to catch. Shipping the model anyway would have been&lt;br&gt;
decoration.&lt;/p&gt;

&lt;p&gt;The live site runs on an open stack end to end: the FastAPI app behind Caddy for&lt;br&gt;
automatic HTTPS, TimescaleDB in a container, and the breach query in SQL. It is&lt;br&gt;
hosted on a .tech domain, &lt;a href="https://baridi.tech" rel="noopener noreferrer"&gt;baridi.tech&lt;/a&gt;, with the&lt;br&gt;
TimescaleDB bound to loopback and the public API rate-limited.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;Dede's readings are her own compliance liability. They are the record an&lt;br&gt;
inspector asks for and the record that protects her if a customer complains.&lt;br&gt;
That record should not live behind someone's subscription, on someone's server,&lt;br&gt;
billed per reading.&lt;/p&gt;

&lt;p&gt;Baridi runs on open parts end to end: TimescaleDB in a container, Temporal's open&lt;br&gt;
dev server, a Python stack. The readings never leave the machine. There is no&lt;br&gt;
per-reading cloud fee. The breach math is a SQL query she or her inspector can&lt;br&gt;
read, not a vendor's black box.&lt;/p&gt;

&lt;p&gt;Honest limits, stated plainly. Baridi is a logging and alert aid, not a certified&lt;br&gt;
compliance record, and the UI says so. The 1.31x database win is real but modest&lt;br&gt;
at one day; the stronger case is buffer reads and incremental refresh at real&lt;br&gt;
volume, and I would not claim otherwise. The data is synthetic, generated by a&lt;br&gt;
seeded script, and the write-up says where. The blip-vs-breach model is deferred,&lt;br&gt;
not hidden: the sustain window did the job, so the model stays out. A closed API&lt;br&gt;
could have done the arithmetic too, but it could not have kept the readings in&lt;br&gt;
the shop for free, which is the whole point.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Temporal:&lt;/strong&gt; the breach alert is one idempotent activity per
excursion, and a worker killed mid-alert resumes and fires it about 31 seconds
late without losing it or firing it twice, proven by a timestamped table and
an offline idempotency test.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Tiger Data:&lt;/strong&gt; the excursion timeline is computed by a
&lt;code&gt;time_bucket&lt;/code&gt; continuous aggregate and an in-engine gaps-and-islands query
that, at a million readings, reads about 98% fewer buffers than a raw scan and
returns finished excursions instead of raw rows.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of a .tech Domain:&lt;/strong&gt; the project is live at
&lt;a href="https://baridi.tech" rel="noopener noreferrer"&gt;baridi.tech&lt;/a&gt;, the breach timeline served over HTTPS from
the real TimescaleDB-backed app. The domain fits the product by meaning, not
just by availability. "Baridi" is Swahili for cold, the plain word for a chilly
day or a cold drink, so &lt;a href="https://baridi.tech" rel="noopener noreferrer"&gt;baridi.tech&lt;/a&gt; reads as "cold, built
in tech": a cold-chain tool that watches whether the cold is still cold. The
name carries the one question the product answers, and the .tech domain puts it
in the address bar.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>temporal</category>
      <category>tigerdata</category>
    </item>
    <item>
      <title>An 80-second browser freeze in Zulip's Manage channels, fixed by killing quadratic DOM work</title>
      <dc:creator>Pentine Pejay</dc:creator>
      <pubDate>Fri, 21 Aug 2026 19:06:01 +0000</pubDate>
      <link>https://dev.to/pentine_pejay/an-80-second-browser-freeze-in-zulips-manage-channels-fixed-by-killing-on-dom-work-1543</link>
      <guid>https://dev.to/pentine_pejay/an-80-second-browser-freeze-in-zulips-manage-channels-fixed-by-killing-on-dom-work-1543</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/bugsmash"&gt;DEV's Summer Bug Smash: Clear the Lineup&lt;/a&gt; powered by &lt;a href="https://sentry.io/" rel="noopener noreferrer"&gt;Sentry&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Overview
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/zulip/zulip" rel="noopener noreferrer"&gt;Zulip&lt;/a&gt; is an open-source team chat application: a Django backend and a TypeScript web client built on jQuery. The "Manage channels" overlay lists every channel you can see, with live search, sort, and filter. Each keystroke or filter change re-sorts the list in place.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4bcqlkks1udldcnekj88.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4bcqlkks1udldcnekj88.png" alt="Zulip" width="792" height="408"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Bug Fix or Performance Improvement
&lt;/h2&gt;

&lt;p&gt;I fixed &lt;a href="https://github.com/zulip/zulip/issues/39755" rel="noopener noreferrer"&gt;issue #39755&lt;/a&gt;. On a server with about 331 channels, opening Manage channels (or the &lt;code&gt;#channels/new&lt;/code&gt; view, or the All tab) makes the tab unresponsive for roughly 80 seconds. Chrome shows the "Page Unresponsive" dialog. The reporter's Chrome CPU profile pinned about 90% of the freeze on two DOM operations, &lt;code&gt;removeChild&lt;/code&gt; and &lt;code&gt;querySelectorAll&lt;/code&gt;, triggered through the bundled jQuery.&lt;/p&gt;

&lt;p&gt;The code is &lt;code&gt;redraw_left_panel()&lt;/code&gt; in &lt;code&gt;web/src/stream_settings_ui.ts&lt;/code&gt;, which sorts the list by moving DOM rows into their new order. The re-append loop looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stream_id&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;all_stream_ids&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;$widget&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;widgets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stream_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;scroll_util&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_content_element&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#channels_overlay_container .streams-list&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="c1"&gt;// re-queried every iteration&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$widget&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The problem is the &lt;code&gt;$("#channels_overlay_container .streams-list")&lt;/code&gt; inside the loop. That is a document-wide &lt;code&gt;querySelectorAll&lt;/code&gt;, run once per channel. A &lt;code&gt;querySelectorAll&lt;/code&gt; forces the browser to flush pending layout, so every one of the N appends triggers a full re-layout of the list. Detaching and re-appending N rows with a forced reflow each time is O(n²). With a few hundred rows, that is the 80-second freeze, and it matches the profile exactly: &lt;code&gt;removeChild&lt;/code&gt; from the per-row detach, &lt;code&gt;querySelectorAll&lt;/code&gt; from the per-row re-query.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;PR: &lt;a href="https://github.com/kiprutopentine/zulip/pull/1" rel="noopener noreferrer"&gt;https://github.com/kiprutopentine/zulip/pull/1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The fix, in &lt;code&gt;redraw_left_panel()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Query the content element once, before the loop.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;$content_element&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;scroll_util&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_content_element&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#channels_overlay_container .streams-list&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// ...scan .stream-row a single time, set classes, detach rows...&lt;/span&gt;

&lt;span class="c1"&gt;// Re-append each row into the CACHED element. The original re-queried the&lt;/span&gt;
&lt;span class="c1"&gt;// content element here on every iteration; that document-wide querySelectorAll&lt;/span&gt;
&lt;span class="c1"&gt;// forces a synchronous layout flush of the pending append, so each row paid a&lt;/span&gt;
&lt;span class="c1"&gt;// full reflow. Caching it removes the forced layout, and one append form keeps&lt;/span&gt;
&lt;span class="c1"&gt;// the change lint- and mock-compatible with the existing test.&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stream_id&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;all_stream_ids&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;$widget&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;widgets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stream_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$widget&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;$content_element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$widget&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three changes: query the content element once (this removes the per-row &lt;code&gt;querySelectorAll&lt;/code&gt; and the forced synchronous layout it caused), scan &lt;code&gt;.stream-row&lt;/code&gt; a single time instead of twice, and re-append each row into that cached element instead of re-looking it up every iteration. The ordering, the &lt;code&gt;notdisplayed&lt;/code&gt; classes, and the function's return value are all unchanged, so behavior is identical, only the DOM work drops from O(n²) to O(n).&lt;/p&gt;

&lt;h2&gt;
  
  
  My Improvements
&lt;/h2&gt;

&lt;p&gt;I kept the change inside the existing function and its jQuery idiom rather than rewriting the overlay. That was deliberate: Zulip's frontend tests run against a jQuery mock (&lt;code&gt;zjquery&lt;/code&gt;) whose elements are not real DOM nodes, so a raw &lt;code&gt;DocumentFragment&lt;/code&gt; reorder would pass in the browser but break the test suite. Staying on &lt;code&gt;.detach()&lt;/code&gt; / &lt;code&gt;.append()&lt;/code&gt; keeps the existing &lt;code&gt;redraw_left_panel&lt;/code&gt; test valid, which already asserts the sorted order across search, sort, and filter cases.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Removed the per-iteration content-element lookup, the O(n²) driver: a document-wide &lt;code&gt;querySelectorAll&lt;/code&gt; that forced a synchronous layout flush of the pending append on every row.&lt;/li&gt;
&lt;li&gt;Collapsed two &lt;code&gt;.stream-row&lt;/code&gt; scans into one.&lt;/li&gt;
&lt;li&gt;Kept the plain &lt;code&gt;.append($row)&lt;/code&gt; form, so the change is behavior- and lint-identical to the original and compatible with Zulip's &lt;code&gt;zjquery&lt;/code&gt; test mock (a raw &lt;code&gt;DocumentFragment&lt;/code&gt; reorder would pass in a browser but break the mock, whose elements are not real DOM nodes).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Verification: I provisioned a full Zulip dev environment on a throwaway cloud VM and ran Zulip's own tooling against the fix:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;./tools/test-js-with-node stream_settings_ui&lt;/code&gt; → &lt;code&gt;test: redraw_left_panel&lt;/code&gt; passes ("Test(s) passed. SUCCESS!").&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;./tools/run-tsc&lt;/code&gt; clean.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;./tools/lint --only=eslint,prettier web/src/stream_settings_ui.ts&lt;/code&gt; clean.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The change is behavior-preserving (same order, &lt;code&gt;notdisplayed&lt;/code&gt; classes, return value), so that existing test, which pins ordering across search, sort, and filter cases, passes unchanged.&lt;/p&gt;

&lt;h2&gt;
  
  
  Best Use of Sentry
&lt;/h2&gt;

&lt;p&gt;This is a performance bug with zero exceptions, so error monitoring alone would never see it. I used Sentry's performance tooling to make the cost visible and to prove the fix.&lt;/p&gt;

&lt;p&gt;I built a benchmark harness (&lt;code&gt;@sentry/browser&lt;/code&gt; via the loader script) that rebuilds the exact redraw both ways against N real &lt;code&gt;.stream-row&lt;/code&gt; nodes and wraps each run in a Sentry span named &lt;code&gt;manage_channels.redraw_left_panel&lt;/code&gt; (&lt;code&gt;op: ui.render&lt;/code&gt;):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;OLD&lt;/strong&gt; detaches each row and re-appends one at a time, re-querying the content element every iteration (forcing a synchronous layout per row): O(n²).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NEW&lt;/strong&gt; queries the content element once and re-inserts in a single batch: O(n).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Measured in the harness, which is deliberately minimal (plain rows) so the numbers isolate the algorithm itself rather than Zulip's full per-row render cost:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;channels&lt;/th&gt;
&lt;th&gt;OLD (buggy)&lt;/th&gt;
&lt;th&gt;NEW (fixed)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;331&lt;/td&gt;
&lt;td&gt;115.8 ms&lt;/td&gt;
&lt;td&gt;3.7 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1000&lt;/td&gt;
&lt;td&gt;477.4 ms&lt;/td&gt;
&lt;td&gt;7.3 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2000&lt;/td&gt;
&lt;td&gt;2340.4 ms&lt;/td&gt;
&lt;td&gt;15.1 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;OLD grows super-linearly, close to quadratic at the larger sizes: doubling the list from 1000 to 2000 channels roughly quintuples the time. NEW scales linearly and stays in the low milliseconds. At 2000 channels the redraw drops from 2.3 seconds to 15 ms, about 155x. In the production overlay each row is far heavier (real templates, jQuery, the scroll container), so the same pattern is what turns "a few hundred channels" into the reporter's ~80-second freeze instead of a fraction of a second. The harness proves the mechanism and the fix; the 80 seconds is the real-world symptom of it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Performance / tracing:&lt;/strong&gt; the OLD run does not just take longer, it blocks the browser's main thread. Sentry's tracing recorded it as a &lt;code&gt;ui.long-animation-frame — Main UI thread blocked&lt;/code&gt; span (about 1.3 s in this trace), exactly the kind of long task that makes the tab unresponsive. The NEW run produces no such block.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc9jzl50amnk5ru44bxvr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc9jzl50amnk5ru44bxvr.png" alt="trace" width="799" height="410"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each run is tagged with its &lt;code&gt;channels&lt;/code&gt; count and its &lt;code&gt;kind&lt;/code&gt; (old/new), so the regression is visible &lt;em&gt;inside&lt;/em&gt; Sentry, not just in the console. Filtering the &lt;code&gt;manage_channels.redraw_left_panel&lt;/code&gt; transactions shows the fixed runs sitting in the low milliseconds (4–30 ms) while the buggy runs climb with the channel count, from ~100 ms up past 2 seconds, and beyond 17 seconds on a larger list. Sentry is not just recording one slow event; it shows the shape of the algorithm, and that the fix flattens it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb2b7p22p7n9metid26un.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb2b7p22p7n9metid26un.png" alt="old and new" width="799" height="372"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Session Replay:&lt;/strong&gt; the replay of the OLD run shows the list locked and unresponsive; the NEW run is instant.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fujuh6y6jp1606w8o64m0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fujuh6y6jp1606w8o64m0.png" alt="reply" width="799" height="372"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Three Sentry surfaces carried this fix: &lt;strong&gt;tracing&lt;/strong&gt; timed each redraw as a &lt;code&gt;ui.render&lt;/code&gt; transaction and plotted the cost curve as the channel count grew, its &lt;strong&gt;long-animation-frame&lt;/strong&gt; detection flagged the blocked main thread on its own, and &lt;strong&gt;Session Replay&lt;/strong&gt; captured the freeze the way a user feels it. Run it yourself, the harness is public at &lt;a href="https://github.com/kiprutopentine/sentry" rel="noopener noreferrer"&gt;https://github.com/kiprutopentine/sentry&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>bugsmash</category>
    </item>
    <item>
      <title>Dog Passport: a soulbound vaccination record your vet signs, on Solana</title>
      <dc:creator>Pentine Pejay</dc:creator>
      <pubDate>Sun, 16 Aug 2026 19:45:59 +0000</pubDate>
      <link>https://dev.to/pentine_pejay/dog-passport-a-soulbound-vaccination-record-your-vet-signs-on-solana-5ek9</link>
      <guid>https://dev.to/pentine_pejay/dog-passport-a-soulbound-vaccination-record-your-vet-signs-on-solana-5ek9</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/challenges/weekend-2026-08-13"&gt;Weekend Challenge: Dog Days Edition&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;Dog Passport turns a dog's vaccination and pedigree record into a&lt;br&gt;
non-transferable Token-2022 on Solana that the issuing vet signs with their own&lt;br&gt;
keypair.&lt;/p&gt;

&lt;p&gt;A boarding kennel, groomer, border agent, or a new owner can open the passport,read it straight from the chain, and see two things at once: the dog's details,and whether the record was really signed by a known vet. No app account, no central server to trust, no company that can shut down and take the records with it. The token is soulbound, so it stays bound to the dog and cannot be sold or swapped like an NFT.&lt;/p&gt;

&lt;p&gt;The problem is small and real. Paper vaccination cards get lost right when you need them for boarding or travel. Pet-record apps die with the company that made them. Breeders hand over pedigrees you have no way to check. A vet-signed on-chain record fixes the parts that hurt: it is portable, tamper-evident, and outlives any single vendor.&lt;/p&gt;

&lt;p&gt;The passport carries a photo, passport number, owner, breed, date of birth, colour, microchip, the vaccination history, and the issuing vet's key. Every one of those fields, including the photo, is covered by the vet's signature.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyiyi4afb90zfipttfjpr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyiyi4afb90zfipttfjpr.png" alt="passport" width="799" height="566"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;Live (Solana devnet, no wallet or setup needed):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;App: &lt;a href="https://bosco-eta.vercel.app" rel="noopener noreferrer"&gt;https://bosco-eta.vercel.app&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
      &lt;div class="c-embed__body flex items-center justify-between"&gt;
        &lt;a href="https://bosco-eta.vercel.app" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;bosco-eta.vercel.app&lt;/span&gt;
          

        &lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;Open a passport directly: &lt;a href="https://bosco-eta.vercel.app/?mint=52SP5ACC7m8BwnWge1CX3DyidhfBXWLxAz9yNdS2sf3G" rel="noopener noreferrer"&gt;https://bosco-eta.vercel.app/?mint=52SP5ACC7m8BwnWge1CX3DyidhfBXWLxAz9yNdS2sf3G&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The landing page has a gallery of five real dogs plus two test states, so you can click through every verification outcome:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyqkf7y10j53fa00u7ny4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyqkf7y10j53fa00u7ny4.png" alt="Viewer" width="800" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Authentic&lt;/strong&gt; — signed by a trusted vet. Green "VERIFIED" stamp, all checks pass.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Untrusted issuer&lt;/strong&gt; — a valid signature from a vet who is not in the trusted list. Amber stamp.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tampered&lt;/strong&gt; — a field was altered on-chain after signing, so the signature nolonger matches. Red "INVALID" stamp.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4utaaez0x9awkgzfv6kc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4utaaez0x9awkgzfv6kc.png" alt="verification-untrasted" width="575" height="587"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk2y7p1kaegyxmd39tsn8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk2y7p1kaegyxmd39tsn8.png" alt="tampered" width="575" height="587"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The viewer renders a proper passport data page: photo, fields, a machine-readable zone, per-vaccine validity status (up to date, due soon, overdue), a verification checklist, and a QR code of the shareable link. It can also scan a printed passport QR with the device camera.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F139ujtxs8mv1p2h762l8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F139ujtxs8mv1p2h762l8.png" alt="overdue" width="575" height="587"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Open the live link above and click the gallery chips to see each outcome for yourself, straight from devnet.&lt;/p&gt;

&lt;p&gt;The verification is honest. Change any field in the record and the signature no longer matches:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;original verifies  : true
tampered breed     : false
tampered vaccine   : false
wrong issuer key   : false
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/kiprutopentine" rel="noopener noreferrer"&gt;
        kiprutopentine
      &lt;/a&gt; / &lt;a href="https://github.com/kiprutopentine/dog-passport" rel="noopener noreferrer"&gt;
        dog-passport
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Dog Passport&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;A dog's vaccination and pedigree record issued as a non-transferable Token-2022
on Solana. A vet signs each record with their keypair, so a boarding kennel
groomer, border agent, or new owner can verify the record is authentic and came
from a specific vet, without trusting any central server or app.&lt;/p&gt;
&lt;p&gt;Runs on Solana devnet.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Why on-chain&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Paper vaccination records get lost. App-based pet records die when the app or
company shuts down. A breeder can hand you a pedigree with no way to check it.
Putting the record on-chain as a soulbound token makes it portable,
tamper-evident, and independent of any single vendor. The passport is bound to
the dog, not tradeable, so it cannot be sold or swapped like an NFT.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;How it works&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;Each passport is one Token-2022 mint with three extensions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;NonTransferable&lt;/strong&gt; — the token is soulbound. Once minted to the owner it
cannot be…&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/kiprutopentine/dog-passport" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Stack:&lt;/strong&gt; plain &lt;code&gt;@solana/web3.js&lt;/code&gt; + &lt;code&gt;@solana/spl-token&lt;/code&gt;, no Anchor program. A handful of Node scripts plus a static viewer, small enough to build and verify in a weekend.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Solana part that matters.&lt;/strong&gt; Each passport is one Token-2022 mint that combines three SPL Token Extensions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;NonTransferable&lt;/strong&gt; makes the token soulbound. I de-risked this first with a   proof-of-concept that mints a token and then tries to transfer it. The program rejects the transfer, which is exactly what a passport needs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MetadataPointer&lt;/strong&gt; points the mint at itself for metadata, so there is no separate metadata account.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TokenMetadata&lt;/strong&gt; stores the whole record on-chain in &lt;code&gt;additionalMetadata&lt;/code&gt;:passport number, name, breed, dob, sex, colour, microchip, owner, photo URL andits SHA-256 hash, the vaccine list, the issuing authority, the vet's public key, and the vet's signature.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkbz6lpgelntl67wtdetf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkbz6lpgelntl67wtdetf.png" alt="solana" width="800" height="322"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd78yrd2f92xblkm5j1qq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd78yrd2f92xblkm5j1qq.png" alt="metadata" width="800" height="346"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Issuer-signed records.&lt;/strong&gt; The value is not "data on a chain," it is &lt;em&gt;who signed it&lt;/em&gt;. The vet holds an Ed25519 keypair. Before minting, the vet signs a canonical serialization of the record (stable key order, reproducible byte-for-byte) with &lt;code&gt;tweetnacl&lt;/code&gt;. That detached signature goes on-chain. Verification recomputes the canonical record from the on-chain fields, checks the signature against the vet's&lt;br&gt;
public key, then checks that key against a trusted-vet list. Tampering fails because the bytes being verified no longer match the bytes that were signed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Photo integrity.&lt;/strong&gt; The image is bound to the record by hashing its bytes and signing the hash. The viewer re-hashes the photo it displays and compares, so the picture cannot be swapped without breaking verification.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The viewer and hosting.&lt;/strong&gt; The on-chain read and the verification run in one &lt;code&gt;loadAndVerifyPassport&lt;/code&gt; function shared by the CLI and the web. On Vercel it runs as a serverless function at &lt;code&gt;/api/passport&lt;/code&gt;; locally the same function runs behind a small Node server. The function is host-agnostic: it reads the photo and the trusted-vet list over HTTP from its own deployment, so nothing from the local&lt;br&gt;
filesystem has to be bundled.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Two things that fought back.&lt;/strong&gt; The public devnet faucet was rate-limited to death, so funding falls back across endpoints (an Alchemy demo endpoint saved the day). And on Vercel, &lt;code&gt;@solana/web3.js&lt;/code&gt; pulls in &lt;code&gt;rpc-websockets&lt;/code&gt;, which bundles an ESM-only &lt;code&gt;uuid&lt;/code&gt; and then &lt;code&gt;require()&lt;/code&gt;s it, crashing the function with &lt;code&gt;ERR_REQUIRE_ESM&lt;/code&gt;; pinning &lt;code&gt;uuid&lt;/code&gt; to its CommonJS build via an npm &lt;code&gt;overrides&lt;/code&gt; fixed it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Best Use of Solana.&lt;/strong&gt; - Used Token-2022's NonTransferable extension to make the passport soulbound, on-chain TokenMetadata to carry the record, and the issuer-signature model to make verification about the vet rather than a server.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Real vet identity: a registry or attestations instead of a local trusted list.&lt;/li&gt;
&lt;li&gt;An owner-transfer flow for rehoming and adoption, without making the token freely tradeable.&lt;/li&gt;
&lt;li&gt;Mainnet custody and a kennel/border scanner app.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
    </item>
  </channel>
</rss>
