<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>simple-web-app, branch HEAD</title>
<subtitle>Unnamed repository; edit this file &#39;description&#39; to name the repository.
</subtitle>
<entry>
<id>7b32df42f3d0a5393ed63c6802ffb9ce9000fee7</id>
<published>2026-06-27T20:45:48Z</published>
<updated>2026-06-27T20:45:48Z</updated>
<title type="text">perf: use composite primary keys for vote and story_category</title>
<link rel="alternate" type="text/html" href="commit/7b32df42f3d0a5393ed63c6802ffb9ce9000fee7.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit 7b32df42f3d0a5393ed63c6802ffb9ce9000fee7
parent 2df5315bdbd11a0926e069605c4395c8a831bac6
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Sat, 27 Jun 2026 22:45:48 +0200

perf: use composite primary keys for vote and story_category

Replace surrogate integer PKs with structured composite PKs on tables
where the surrogate id was never referenced:

- vote: PRIMARY KEY (user_id, target_type, target_id), WITHOUT ROWID
  Eliminates the separate UNIQUE index — one B-tree instead of two.
- story_category: PRIMARY KEY (news_item_id, category_id), WITHOUT ROWID
  Also adds a uniqueness guarantee that was previously missing.

At 1M votes this halves DB size (59 → 26 MB) and improves vote write
throughput ~12-16% at steady state.

Also adds warmup phase and minimum request count to bench.sh to avoid
cold-cache measurement artifacts at low concurrency.

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>2df5315bdbd11a0926e069605c4395c8a831bac6</id>
<published>2026-06-14T13:41:48Z</published>
<updated>2026-06-14T13:41:48Z</updated>
<title type="text">fix: write seed SQL to temp file to avoid arg length limit</title>
<link rel="alternate" type="text/html" href="commit/2df5315bdbd11a0926e069605c4395c8a831bac6.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit 2df5315bdbd11a0926e069605c4395c8a831bac6
parent efcefa3f48e143404cc1111b4510f81b89d97535
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Sun, 14 Jun 2026 15:41:48 +0200

fix: write seed SQL to temp file to avoid arg length limit

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>efcefa3f48e143404cc1111b4510f81b89d97535</id>
<published>2026-06-14T13:19:40Z</published>
<updated>2026-06-14T13:19:40Z</updated>
<title type="text">fix: handle multiple nix output paths in bench.sh</title>
<link rel="alternate" type="text/html" href="commit/efcefa3f48e143404cc1111b4510f81b89d97535.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit efcefa3f48e143404cc1111b4510f81b89d97535
parent a95a107bb851785377fb65d884a21900f708f105
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Sun, 14 Jun 2026 15:19:40 +0200

fix: handle multiple nix output paths in bench.sh

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>a95a107bb851785377fb65d884a21900f708f105</id>
<published>2026-06-14T13:18:00Z</published>
<updated>2026-06-14T13:18:00Z</updated>
<title type="text">add bench.sh for repeatable read/write benchmarks</title>
<link rel="alternate" type="text/html" href="commit/a95a107bb851785377fb65d884a21900f708f105.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit a95a107bb851785377fb65d884a21900f708f105
parent 424a14337f392b3dedaf68c95051ab66d93ef8e7
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Sun, 14 Jun 2026 15:18:00 +0200

add bench.sh for repeatable read/write benchmarks

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>424a14337f392b3dedaf68c95051ab66d93ef8e7</id>
<published>2026-06-14T13:03:31Z</published>
<updated>2026-06-14T13:03:31Z</updated>
<title type="text">perf: use RETURNING * for create_story, create_comment, create_user</title>
<link rel="alternate" type="text/html" href="commit/424a14337f392b3dedaf68c95051ab66d93ef8e7.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit 424a14337f392b3dedaf68c95051ab66d93ef8e7
parent a2f3e8c23e385234e7173baef4e8fc443a107c4b
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Sun, 14 Jun 2026 15:03:31 +0200

perf: use RETURNING * for create_story, create_comment, create_user

Eliminates the separate read round-trip after each INSERT. The
created row is returned directly from the writer connection.

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>a2f3e8c23e385234e7173baef4e8fc443a107c4b</id>
<published>2026-06-14T12:50:11Z</published>
<updated>2026-06-14T12:50:11Z</updated>
<title type="text">fix: update score display when voting</title>
<link rel="alternate" type="text/html" href="commit/a2f3e8c23e385234e7173baef4e8fc443a107c4b.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit a2f3e8c23e385234e7173baef4e8fc443a107c4b
parent 4f6a4d78d497583e07040969a4e3b1cf47b92539
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Sun, 14 Jun 2026 14:50:11 +0200

fix: update score display when voting

Vote SSE response now sends two patch events — one for the vote
button and one for the score span. Previously only the button
updated, leaving the score unchanged.

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>4f6a4d78d497583e07040969a4e3b1cf47b92539</id>
<published>2026-06-14T12:35:04Z</published>
<updated>2026-06-14T12:35:04Z</updated>
<title type="text">perf: read vote score on writer connection, remove separate read round-trip</title>
<link rel="alternate" type="text/html" href="commit/4f6a4d78d497583e07040969a4e3b1cf47b92539.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit 4f6a4d78d497583e07040969a4e3b1cf47b92539
parent ae9fb1278f141a962d420dbfff1f37452ff77d52
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Sun, 14 Jun 2026 14:35:04 +0200

perf: read vote score on writer connection, remove separate read round-trip

toggle_vote now returns (voted, new_score) by reading the score
on the writer connection after the INSERT/DELETE. The trigger
updates the score on the same connection, so it&#39;s immediately
visible without a separate read pool round-trip.

Removes get_story_score and get_comment_score methods.

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>ae9fb1278f141a962d420dbfff1f37452ff77d52</id>
<published>2026-06-14T11:54:17Z</published>
<updated>2026-06-14T11:54:17Z</updated>
<title type="text">refactor: dedicated async DB thread pool, remove spawn_blocking</title>
<link rel="alternate" type="text/html" href="commit/ae9fb1278f141a962d420dbfff1f37452ff77d52.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit ae9fb1278f141a962d420dbfff1f37452ff77d52
parent 8fd1574e6cc9c3fcc24d0dd2b91c131795d01f9a
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Sun, 14 Jun 2026 13:54:17 +0200

refactor: dedicated async DB thread pool, remove spawn_blocking

Replace thread-local connections + spawn_blocking with dedicated
OS threads for reads (N=cores) and writes (1). Queries dispatch
via crossbeam channel, results via tokio oneshot. Fully async.

Structurally isolates DB from CPU-heavy work (argon2). No
semaphore, no max_blocking_threads cap, no thread-local RefCell.

Story page: 78k → 119k rps. Contention: 5.2k → 8.9k rps.

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>8fd1574e6cc9c3fcc24d0dd2b91c131795d01f9a</id>
<published>2026-06-14T10:18:07Z</published>
<updated>2026-06-14T10:18:07Z</updated>
<title type="text">perf: add semaphore on password hashing to prevent DB read starvation</title>
<link rel="alternate" type="text/html" href="commit/8fd1574e6cc9c3fcc24d0dd2b91c131795d01f9a.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit 8fd1574e6cc9c3fcc24d0dd2b91c131795d01f9a
parent d66f52ebd9f851442ffec2a1bc3a37088de1a5a4
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Sun, 14 Jun 2026 12:18:07 +0200

perf: add semaphore on password hashing to prevent DB read starvation

Argon2 and DB reads share the same blocking thread pool (capped
at core count). Under concurrent login bursts, hashes could fill
all slots and starve reads.

Semaphore limits concurrent hashes to cores/2, leaving slots
free for DB reads. Hashing is also separated from DB queries
into its own spawn_blocking call so it doesn&#39;t hold a read
connection while hashing.

Benchmark (100 concurrent logins + reads, Ryzen 7 7700):
  Before: reads drop to 5,192 rps (4.4x degradation)
  After:  reads stay at 21,358 rps (no degradation)

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>d66f52ebd9f851442ffec2a1bc3a37088de1a5a4</id>
<published>2026-06-14T07:08:20Z</published>
<updated>2026-06-14T07:08:20Z</updated>
<title type="text">fix: replace unsafe UnsafeCell with safe RefCell for thread-local connections</title>
<link rel="alternate" type="text/html" href="commit/d66f52ebd9f851442ffec2a1bc3a37088de1a5a4.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit d66f52ebd9f851442ffec2a1bc3a37088de1a5a4
parent 1d6a41c4f7aba3d1e8575a1c90eab67e7318c669
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Sun, 14 Jun 2026 09:08:20 +0200

fix: replace unsafe UnsafeCell with safe RefCell for thread-local connections

The previous read_conn() returned a &amp;Connection that escaped the
thread_local! with closure — unsound by the thread_local API
contract. Replaced with with_read(|conn| { ... }) which runs
the query inside the closure so the reference never escapes.

RefCell provides the same single-thread guarantee with a runtime
borrow check instead of unsafe. No measurable performance impact
(within noise at all concurrency levels).

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>1d6a41c4f7aba3d1e8575a1c90eab67e7318c669</id>
<published>2026-06-13T22:05:19Z</published>
<updated>2026-06-13T22:05:19Z</updated>
<title type="text">feat: reject non-selective search queries via FTS match count</title>
<link rel="alternate" type="text/html" href="commit/1d6a41c4f7aba3d1e8575a1c90eab67e7318c669.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit 1d6a41c4f7aba3d1e8575a1c90eab67e7318c669
parent dac53494b377b4768b4bd64106452561cc43c4c1
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Sun, 14 Jun 2026 00:05:19 +0200

feat: reject non-selective search queries via FTS match count

Queries where &gt;50% of documents match are rejected with empty
results. Uses COUNT(*) on FTS index (fast) to check before
running the expensive ranked query.

Before: broad query on 100k docs = 22 rps (450ms)
After: rejected in 5ms = 1,635 rps

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>dac53494b377b4768b4bd64106452561cc43c4c1</id>
<published>2026-06-13T13:59:27Z</published>
<updated>2026-06-13T13:59:27Z</updated>
<title type="text">fix: set created_at in all INSERT statements</title>
<link rel="alternate" type="text/html" href="commit/dac53494b377b4768b4bd64106452561cc43c4c1.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit dac53494b377b4768b4bd64106452561cc43c4c1
parent 0c1d430e4f5b840df5b392770cbfbb4feff875a7
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Sat, 13 Jun 2026 15:59:27 +0200

fix: set created_at in all INSERT statements

The epoch timestamp migration removed DEFAULT (datetime(&#39;now&#39;))
from all tables but the INSERT statements weren&#39;t updated to
provide created_at explicitly. Fixes NOT NULL constraint failure
on session, comment, vote, and user creation.

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>0c1d430e4f5b840df5b392770cbfbb4feff875a7</id>
<published>2026-06-13T13:44:30Z</published>
<updated>2026-06-13T13:44:30Z</updated>
<title type="text">refactor: remove inline JS for tag checkboxes</title>
<link rel="alternate" type="text/html" href="commit/0c1d430e4f5b840df5b392770cbfbb4feff875a7.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit 0c1d430e4f5b840df5b392770cbfbb4feff875a7
parent 5be90e38ef14a2134b181bb9d8e12e8088e8719b
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Sat, 13 Jun 2026 15:44:30 +0200

refactor: remove inline JS for tag checkboxes

Use native HTML checkbox name attribute instead of JS collecting
checked values into a hidden input. Form now submits tags=3&amp;tags=7
which serde deserializes into Vec&lt;i64&gt; directly. Removed duplicate
updateTags script from submit.html and edit.html.

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>5be90e38ef14a2134b181bb9d8e12e8088e8719b</id>
<published>2026-06-13T13:31:12Z</published>
<updated>2026-06-13T13:31:12Z</updated>
<title type="text">feat: epoch integer timestamps, drop chrono</title>
<link rel="alternate" type="text/html" href="commit/5be90e38ef14a2134b181bb9d8e12e8088e8719b.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit 5be90e38ef14a2134b181bb9d8e12e8088e8719b
parent 4b95f59b077a512fa1f78c4d5e506666eb3259e7
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Sat, 13 Jun 2026 15:31:12 +0200

feat: epoch integer timestamps, drop chrono

All timestamps are now epoch seconds (i64) instead of text strings.
Unambiguous (always UTC), smaller (8 bytes vs 19), no parsing.

time_ago() and hot_score() use integer subtraction instead of
chrono date parsing. Removes chrono dependency (3 transitive deps).

Migration 0005 converts all existing text timestamps via
strftime(&#39;%s&#39;, column).

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>4b95f59b077a512fa1f78c4d5e506666eb3259e7</id>
<published>2026-06-13T13:15:47Z</published>
<updated>2026-06-13T13:15:47Z</updated>
<title type="text">chore: reduce static asset cache TTL to 1 day</title>
<link rel="alternate" type="text/html" href="commit/4b95f59b077a512fa1f78c4d5e506666eb3259e7.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit 4b95f59b077a512fa1f78c4d5e506666eb3259e7
parent c953ac2c7df3a4adff539fd7399bcdfcbd41329b
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Sat, 13 Jun 2026 15:15:47 +0200

chore: reduce static asset cache TTL to 1 day

A year-long cache for 3KB files is premature optimization that
makes CSS/JS changes invisible to users.

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>c953ac2c7df3a4adff539fd7399bcdfcbd41329b</id>
<published>2026-06-13T13:11:55Z</published>
<updated>2026-06-13T13:11:55Z</updated>
<title type="text">fix: HTML validation, meta description, clean up flake</title>
<link rel="alternate" type="text/html" href="commit/c953ac2c7df3a4adff539fd7399bcdfcbd41329b.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit c953ac2c7df3a4adff539fd7399bcdfcbd41329b
parent 7372fc6f6be891d593d6cec55f04c450406af26b
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Sat, 13 Jun 2026 15:11:55 +0200

fix: HTML validation, meta description, clean up flake

- Remove trailing slashes on void elements (W3C validator)
- Add meta description (PageSpeed SEO)
- Remove dead flake deps: quicktype, openssl, cargo-edit,
  rust-analyzer (redundant), wasm32-wasip2 target

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>7372fc6f6be891d593d6cec55f04c450406af26b</id>
<published>2026-06-13T13:08:02Z</published>
<updated>2026-06-13T13:08:02Z</updated>
<title type="text">docs: remove directory listing from README</title>
<link rel="alternate" type="text/html" href="commit/7372fc6f6be891d593d6cec55f04c450406af26b.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit 7372fc6f6be891d593d6cec55f04c450406af26b
parent f52439bb8de182f6348802994952ec99cf4fd6ca
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Sat, 13 Jun 2026 15:08:02 +0200

docs: remove directory listing from README

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>f52439bb8de182f6348802994952ec99cf4fd6ca</id>
<published>2026-06-13T12:56:02Z</published>
<updated>2026-06-13T12:56:02Z</updated>
<title type="text">docs: add migration guidelines to README</title>
<link rel="alternate" type="text/html" href="commit/f52439bb8de182f6348802994952ec99cf4fd6ca.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit f52439bb8de182f6348802994952ec99cf4fd6ca
parent e9293bae2480eb0a009d4342300d92a8990db357
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Sat, 13 Jun 2026 14:56:02 +0200

docs: add migration guidelines to README

Documents transaction semantics, version bumping, PRAGMA handling,
and testing. Also updates auth description (opaque sessions, not JWT).

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>e9293bae2480eb0a009d4342300d92a8990db357</id>
<published>2026-06-13T12:53:23Z</published>
<updated>2026-06-13T12:53:23Z</updated>
<title type="text">feat: replace JWTs with opaque session tokens</title>
<link rel="alternate" type="text/html" href="commit/e9293bae2480eb0a009d4342300d92a8990db357.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit e9293bae2480eb0a009d4342300d92a8990db357
parent 654245fbe3ef2deb0fe87b9a54646726a7cdfec2
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Sat, 13 Jun 2026 14:53:23 +0200

feat: replace JWTs with opaque session tokens

JWTs gave us statelessness we never used (secret rotated on every
restart anyway). Opaque sessions are simpler and support real
logout/revocation.

- New session table (token, user_id, created_at)
- Login creates a random 32-byte hex token, stores in DB
- Auth check is a primary key lookup (~4µs)
- Logout DELETEs the session row — actually invalidates it
- Removed jsonwebtoken dep (and its ring crypto transitive tree)
- Removed jwt_secret from Config
- src/auth.rs now only has password hashing

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>654245fbe3ef2deb0fe87b9a54646726a7cdfec2</id>
<published>2026-06-13T11:43:05Z</published>
<updated>2026-06-13T11:43:05Z</updated>
<title type="text">refactor: use ADDR and SMTP_ADDR env vars directly</title>
<link rel="alternate" type="text/html" href="commit/654245fbe3ef2deb0fe87b9a54646726a7cdfec2.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit 654245fbe3ef2deb0fe87b9a54646726a7cdfec2
parent df1a5a44600e1f0aa0820c59f32d2fc59a194734
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Sat, 13 Jun 2026 13:43:05 +0200

refactor: use ADDR and SMTP_ADDR env vars directly

No more PORT/SMTP_HOST/SMTP_PORT concatenation. Env vars match
what the code uses. Removed require_parse helper — only DEBUG
needed parsing and == &quot;true&quot; is clearer.

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>df1a5a44600e1f0aa0820c59f32d2fc59a194734</id>
<published>2026-06-13T11:35:40Z</published>
<updated>2026-06-13T11:35:40Z</updated>
<title type="text">refactor: use addr/smtp_addr instead of separate host+port fields</title>
<link rel="alternate" type="text/html" href="commit/df1a5a44600e1f0aa0820c59f32d2fc59a194734.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit df1a5a44600e1f0aa0820c59f32d2fc59a194734
parent cb0e3705551167533a63848c622300d72504375c
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Sat, 13 Jun 2026 13:35:40 +0200

refactor: use addr/smtp_addr instead of separate host+port fields

Config computes the full address once at startup. Handlers and
main.rs use the composed address directly.

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>cb0e3705551167533a63848c622300d72504375c</id>
<published>2026-06-13T11:34:20Z</published>
<updated>2026-06-13T11:34:20Z</updated>
<title type="text">refactor: separate Config from AppState</title>
<link rel="alternate" type="text/html" href="commit/cb0e3705551167533a63848c622300d72504375c.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit cb0e3705551167533a63848c622300d72504375c
parent a302bcc59be3eba6eb778914c481cbfd46d0611a
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Sat, 13 Jun 2026 13:34:20 +0200

refactor: separate Config from AppState

AppState now holds Arc&lt;Config&gt; instead of copying config fields.
Config is immutable after startup, AppState holds runtime state
(db, comment_tx). Clear separation of fixed vs ephemeral.

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>a302bcc59be3eba6eb778914c481cbfd46d0611a</id>
<published>2026-06-13T11:30:46Z</published>
<updated>2026-06-13T11:30:46Z</updated>
<title type="text">refactor: flatten db module into database.rs</title>
<link rel="alternate" type="text/html" href="commit/a302bcc59be3eba6eb778914c481cbfd46d0611a.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit a302bcc59be3eba6eb778914c481cbfd46d0611a
parent d3796200d8d6a96e32c6b83e50321db7ebfb7161
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Sat, 13 Jun 2026 13:30:46 +0200

refactor: flatten db module into database.rs

StoryWithMeta and CommentWithMeta now live in database.rs alongside
the types they wrap. Removed the unnecessary src/db/ directory.
One module for all database types and queries.

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>d3796200d8d6a96e32c6b83e50321db7ebfb7161</id>
<published>2026-06-13T11:27:58Z</published>
<updated>2026-06-13T11:27:58Z</updated>
<title type="text">refactor: move version bumps into migration SQL files</title>
<link rel="alternate" type="text/html" href="commit/d3796200d8d6a96e32c6b83e50321db7ebfb7161.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit d3796200d8d6a96e32c6b83e50321db7ebfb7161
parent b067e95c8802a34e87604a809eeb49b2cd2388ce
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Sat, 13 Jun 2026 13:27:58 +0200

refactor: move version bumps into migration SQL files

Each .sql file owns its own BEGIN IMMEDIATE, version bump, and
COMMIT. The runner just creates the migration_version table,
checks the current version, and runs pending SQL files. No
transaction management in Rust.

This makes the migration atomic: if the schema change succeeds
but the version bump fails, the whole transaction rolls back.

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>b067e95c8802a34e87604a809eeb49b2cd2388ce</id>
<published>2026-06-13T11:19:23Z</published>
<updated>2026-06-13T11:19:23Z</updated>
<title type="text">test: verify foreign_keys is ON after migrations</title>
<link rel="alternate" type="text/html" href="commit/b067e95c8802a34e87604a809eeb49b2cd2388ce.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit b067e95c8802a34e87604a809eeb49b2cd2388ce
parent 79dd01e19d78c56a1ceceb3024ae2c67fc7769c2
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Sat, 13 Jun 2026 13:19:23 +0200

test: verify foreign_keys is ON after migrations

Catches migrations that disable foreign_keys and forget to
re-enable them.

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>79dd01e19d78c56a1ceceb3024ae2c67fc7769c2</id>
<published>2026-06-13T11:18:42Z</published>
<updated>2026-06-13T11:18:42Z</updated>
<title type="text">fix: simplify migrations, remove tracing dep, fix dead code</title>
<link rel="alternate" type="text/html" href="commit/79dd01e19d78c56a1ceceb3024ae2c67fc7769c2.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit 79dd01e19d78c56a1ceceb3024ae2c67fc7769c2
parent f89ab41c2dfdceeeb8901b43d16a0dcb893942a1
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Sat, 13 Jun 2026 13:18:42 +0200

fix: simplify migrations, remove tracing dep, fix dead code

- PRAGMAs go in the .sql file, not in the runner. Removed the
  pre_sql tuple mess.
- Removed tracing dep (no subscriber configured, calls went
  nowhere). Replaced with eprintln.
- Reverted get_story_score/get_comment_score back to read_conn
  (WAL makes committed writes immediately visible to readers).

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>f89ab41c2dfdceeeb8901b43d16a0dcb893942a1</id>
<published>2026-06-13T11:14:31Z</published>
<updated>2026-06-13T11:14:31Z</updated>
<title type="text">chore: remove dead PRAGMA foreign_keys=ON from migration runner</title>
<link rel="alternate" type="text/html" href="commit/f89ab41c2dfdceeeb8901b43d16a0dcb893942a1.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit f89ab41c2dfdceeeb8901b43d16a0dcb893942a1
parent f4d4bd9aebbf468a96f4bfdffa6a2dd0667cd8e3
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Sat, 13 Jun 2026 13:14:31 +0200

chore: remove dead PRAGMA foreign_keys=ON from migration runner

Only ran on the migration connection which is dropped immediately after.
Read/write connections set their own PRAGMAs via open_conn().

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>f4d4bd9aebbf468a96f4bfdffa6a2dd0667cd8e3</id>
<published>2026-06-13T11:12:28Z</published>
<updated>2026-06-13T11:12:28Z</updated>
<title type="text">test: add migration version bump tests</title>
<link rel="alternate" type="text/html" href="commit/f4d4bd9aebbf468a96f4bfdffa6a2dd0667cd8e3.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit f4d4bd9aebbf468a96f4bfdffa6a2dd0667cd8e3
parent 3020f927bc3b71cbcf06399a6d29e50b0d958225
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Sat, 13 Jun 2026 13:12:28 +0200

test: add migration version bump tests

Three tests against in-memory SQLite:
- each_migration_bumps_version: version equals migration count
- migrations_are_idempotent: running twice is a no-op
- incremental_migration_bumps_version: version &gt;= N after migration N

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>3020f927bc3b71cbcf06399a6d29e50b0d958225</id>
<published>2026-06-13T11:11:10Z</published>
<updated>2026-06-13T11:11:10Z</updated>
<title type="text">fix: bump migration version inside the transaction</title>
<link rel="alternate" type="text/html" href="commit/3020f927bc3b71cbcf06399a6d29e50b0d958225.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit 3020f927bc3b71cbcf06399a6d29e50b0d958225
parent d06bc806010b43254f0596de9c8b9d8963138827
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Sat, 13 Jun 2026 13:11:10 +0200

fix: bump migration version inside the transaction

Version update now happens inside the same BEGIN IMMEDIATE
transaction as the migration SQL. If either fails, both roll back.
Prevents the case where a migration succeeds but the version bump
crashes, causing a non-idempotent migration to re-run.

Pre-transaction SQL (like PRAGMA foreign_keys=OFF) is specified
per-migration in the runner.

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>d06bc806010b43254f0596de9c8b9d8963138827</id>
<published>2026-06-13T11:07:00Z</published>
<updated>2026-06-13T11:07:00Z</updated>
<title type="text">refactor: let migrations manage their own transactions</title>
<link rel="alternate" type="text/html" href="commit/d06bc806010b43254f0596de9c8b9d8963138827.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit d06bc806010b43254f0596de9c8b9d8963138827
parent f479a5849d37f1b821cab77ef2af73893a118276
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Sat, 13 Jun 2026 13:07:00 +0200

refactor: let migrations manage their own transactions

Each migration now controls its own BEGIN/COMMIT. The runner
applies migrations one at a time and updates the version after
each success. This allows migrations to use PRAGMA statements
and CREATE VIRTUAL TABLE which don&#39;t work inside transactions.

Migration 3 now uses BEGIN IMMEDIATE for exclusive write access
during the STRICT table recreation.

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>f479a5849d37f1b821cab77ef2af73893a118276</id>
<published>2026-06-13T11:04:15Z</published>
<updated>2026-06-13T11:04:15Z</updated>
<title type="text">feat: migrate all tables to STRICT mode</title>
<link rel="alternate" type="text/html" href="commit/f479a5849d37f1b821cab77ef2af73893a118276.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit f479a5849d37f1b821cab77ef2af73893a118276
parent d1a959863e0625d2968db158bf0dd23381c1c35c
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Sat, 13 Jun 2026 13:04:15 +0200

feat: migrate all tables to STRICT mode

STRICT tables enforce column types at the SQLite level, preventing
silent type coercion. Migration drops all triggers/indices first,
recreates tables in dependency order with STRICT, copies data,
then rebuilds indices, FTS, and triggers.

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>d1a959863e0625d2968db158bf0dd23381c1c35c</id>
<published>2026-06-13T10:43:10Z</published>
<updated>2026-06-13T10:43:10Z</updated>
<title type="text">refactor: generate JWT secret randomly on startup</title>
<link rel="alternate" type="text/html" href="commit/d1a959863e0625d2968db158bf0dd23381c1c35c.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit d1a959863e0625d2968db158bf0dd23381c1c35c
parent 7c1046bcf16f2596604dc4f27b66d3897d03018e
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Sat, 13 Jun 2026 12:43:10 +0200

refactor: generate JWT secret randomly on startup

No reason to configure or persist the JWT secret. Restarts log
everyone out, which is fine. Removes JWT_SECRET from env vars,
sops, and NixOS module.

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>7c1046bcf16f2596604dc4f27b66d3897d03018e</id>
<published>2026-06-13T10:41:03Z</published>
<updated>2026-06-13T10:41:03Z</updated>
<title type="text">refactor: require all env vars, update .env.example and README</title>
<link rel="alternate" type="text/html" href="commit/7c1046bcf16f2596604dc4f27b66d3897d03018e.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit 7c1046bcf16f2596604dc4f27b66d3897d03018e
parent 52dfc51052fd43c9e08fc1977a9f76bca3f6fc7c
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Sat, 13 Jun 2026 12:41:03 +0200

refactor: require all env vars, update .env.example and README

Config now panics on startup if any env var is missing. No silent
defaults. .env.example has the complete list.

README rewritten with principles (mechanical sympathy, simplicity,
crash early, minimal deps), architecture notes, performance
numbers, and migration docs.

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>52dfc51052fd43c9e08fc1977a9f76bca3f6fc7c</id>
<published>2026-06-13T10:05:02Z</published>
<updated>2026-06-13T10:05:02Z</updated>
<title type="text">perf: use prepare_cached for all queries, HashMap for enrichment</title>
<link rel="alternate" type="text/html" href="commit/52dfc51052fd43c9e08fc1977a9f76bca3f6fc7c.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit 52dfc51052fd43c9e08fc1977a9f76bca3f6fc7c
parent 8dd22711e7f8d2332bfc74cbac6d59efdc541f8c
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Sat, 13 Jun 2026 12:05:02 +0200

perf: use prepare_cached for all queries, HashMap for enrichment

prepare_cached reuses compiled SQL statements across requests on
the same thread-local connection, avoiding re-parsing 19 queries
per request. HashMap/HashSet for story enrichment replaces O(n²)
linear scans with O(1) lookups.

Story page: 60k → 95k rps (+57%)
Homepage: 21k → 24k rps (+16%)

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>8dd22711e7f8d2332bfc74cbac6d59efdc541f8c</id>
<published>2026-06-13T10:00:06Z</published>
<updated>2026-06-13T10:00:06Z</updated>
<title type="text">perf: thread-local reader connections, cap blocking threads to core count</title>
<link rel="alternate" type="text/html" href="commit/8dd22711e7f8d2332bfc74cbac6d59efdc541f8c.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit 8dd22711e7f8d2332bfc74cbac6d59efdc541f8c
parent 569eb827d6e7b45e8a482bacc42d1e03c4122b6d
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Sat, 13 Jun 2026 12:00:06 +0200

perf: thread-local reader connections, cap blocking threads to core count

Replace mutex-based connection pool with thread-local SQLite
connections — zero synchronization overhead for reads. Each
spawn_blocking thread gets its own connection on first use.

Cap tokio&#39;s blocking thread pool to available_parallelism()
(core count) since more threads than cores just adds context
switching. Results on Ryzen 7 7700:

- c=500: 5,033 → 31,426 rps (6.2x improvement)
- Throughput now increases with concurrency instead of degrading
- p99 latency at c=500: 36ms

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>569eb827d6e7b45e8a482bacc42d1e03c4122b6d</id>
<published>2026-06-12T21:28:32Z</published>
<updated>2026-06-12T21:28:32Z</updated>
<title type="text">perf: separate reader pool and writer connection</title>
<link rel="alternate" type="text/html" href="commit/569eb827d6e7b45e8a482bacc42d1e03c4122b6d.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit 569eb827d6e7b45e8a482bacc42d1e03c4122b6d
parent c91d4f26eb82f291337bcf6c896e864e41d62799
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Fri, 12 Jun 2026 23:28:32 +0200

perf: separate reader pool and writer connection

Reads (20 methods) use a pool of 10 connections that never
contend with writes. Writes (9 methods) use a single dedicated
connection behind its own Mutex. In WAL mode, readers never block
on the writer and vice versa.

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>c91d4f26eb82f291337bcf6c896e864e41d62799</id>
<published>2026-06-12T21:20:37Z</published>
<updated>2026-06-12T21:20:37Z</updated>
<title type="text">perf: add SQLite performance PRAGMAs, merge feed db_query calls</title>
<link rel="alternate" type="text/html" href="commit/c91d4f26eb82f291337bcf6c896e864e41d62799.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit c91d4f26eb82f291337bcf6c896e864e41d62799
parent 638106b9a532703c50af06c3954d16679f056ba1
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Fri, 12 Jun 2026 23:20:37 +0200

perf: add SQLite performance PRAGMAs, merge feed db_query calls

Added synchronous=NORMAL (safe with WAL, skips unnecessary fsync),
mmap_size=256MB, temp_store=MEMORY.

Merged the two sequential spawn_blocking calls in the feed handler
into one, halving thread spawn + mutex lock overhead per request.

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>638106b9a532703c50af06c3954d16679f056ba1</id>
<published>2026-06-12T21:01:08Z</published>
<updated>2026-06-12T21:01:08Z</updated>
<title type="text">perf: replace lettre with hand-rolled SMTP client</title>
<link rel="alternate" type="text/html" href="commit/638106b9a532703c50af06c3954d16679f056ba1.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit 638106b9a532703c50af06c3954d16679f056ba1
parent db0f6ea16c551f15d6f3d103f77793648f1a1d3f
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Fri, 12 Jun 2026 23:01:08 +0200

perf: replace lettre with hand-rolled SMTP client

Lettre pulled 122 transitive deps (TLS, IDNA, ICU, email parsing)
for sending plain text emails to localhost. Replaced with ~50
lines of raw SMTP protocol over TcpStream. Binary 5.0MB -&gt; 4.4MB.

Also removed openssl from flake buildInputs (only needed by lettre).

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>db0f6ea16c551f15d6f3d103f77793648f1a1d3f</id>
<published>2026-06-12T20:24:24Z</published>
<updated>2026-06-12T20:24:24Z</updated>
<title type="text">perf: replace r2d2 with hand-rolled connection pool</title>
<link rel="alternate" type="text/html" href="commit/db0f6ea16c551f15d6f3d103f77793648f1a1d3f.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit db0f6ea16c551f15d6f3d103f77793648f1a1d3f
parent ee0630ef97fbdb420e046c67853e7581048b389e
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Fri, 12 Jun 2026 22:24:24 +0200

perf: replace r2d2 with hand-rolled connection pool

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>ee0630ef97fbdb420e046c67853e7581048b389e</id>
<published>2026-06-12T20:18:29Z</published>
<updated>2026-06-12T20:18:29Z</updated>
<title type="text">chore: remove thiserror, hand-impl Display + Error</title>
<link rel="alternate" type="text/html" href="commit/ee0630ef97fbdb420e046c67853e7581048b389e.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit ee0630ef97fbdb420e046c67853e7581048b389e
parent 54df83be67cd35880ed62541dace5b4ae3568ac8
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Fri, 12 Jun 2026 22:18:29 +0200

chore: remove thiserror, hand-impl Display + Error

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>54df83be67cd35880ed62541dace5b4ae3568ac8</id>
<published>2026-06-12T20:17:37Z</published>
<updated>2026-06-12T20:17:37Z</updated>
<title type="text">chore: remove unused deps (serde_json, uuid)</title>
<link rel="alternate" type="text/html" href="commit/54df83be67cd35880ed62541dace5b4ae3568ac8.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit 54df83be67cd35880ed62541dace5b4ae3568ac8
parent 91b0e076f22bbcd74c2624224384a916b9356ef3
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Fri, 12 Jun 2026 22:17:37 +0200

chore: remove unused deps (serde_json, uuid)

serde_json had zero uses (leftover from TrailBase).
uuid replaced with rand hex string (rand already a dep).

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>91b0e076f22bbcd74c2624224384a916b9356ef3</id>
<published>2026-06-12T20:00:23Z</published>
<updated>2026-06-12T20:00:23Z</updated>
<title type="text">chore: remove manual gzip, let nginx handle compression</title>
<link rel="alternate" type="text/html" href="commit/91b0e076f22bbcd74c2624224384a916b9356ef3.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit 91b0e076f22bbcd74c2624224384a916b9356ef3
parent 453924cdaee738b755894eb7d4a01792892b66d6
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Fri, 12 Jun 2026 22:00:23 +0200

chore: remove manual gzip, let nginx handle compression

Nginx already has recommendedGzipSettings enabled globally.
Removed the pre-compressed style.css.gz and the gzip negotiation
logic from the app. All static files now served uncompressed and
nginx compresses on the fly.

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>453924cdaee738b755894eb7d4a01792892b66d6</id>
<published>2026-06-12T19:51:05Z</published>
<updated>2026-06-12T19:51:05Z</updated>
<title type="text">perf: use system SQLite instead of bundled</title>
<link rel="alternate" type="text/html" href="commit/453924cdaee738b755894eb7d4a01792892b66d6.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit 453924cdaee738b755894eb7d4a01792892b66d6
parent bf3b224f88c40039b3b6c70acc2851b2cc02e71d
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Fri, 12 Jun 2026 21:51:05 +0200

perf: use system SQLite instead of bundled

On NixOS, system SQLite is always available and kept up to date.
Bundled added 2MB to the binary and ~10s to compile for no benefit.

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>bf3b224f88c40039b3b6c70acc2851b2cc02e71d</id>
<published>2026-06-12T19:26:49Z</published>
<updated>2026-06-12T19:26:49Z</updated>
<title type="text">refactor: move migrations to SQL files, update README</title>
<link rel="alternate" type="text/html" href="commit/bf3b224f88c40039b3b6c70acc2851b2cc02e71d.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit bf3b224f88c40039b3b6c70acc2851b2cc02e71d
parent 613fd402b13f2bc33c6597f04818120ed1efa503
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Fri, 12 Jun 2026 21:26:49 +0200

refactor: move migrations to SQL files, update README

Migrations now live as separate .sql files in migrations/ and are
embedded at compile time via include_str!. Readable SQL, testable
with sqlite3, compile-time guarantees.

Updated README to reflect current architecture (SQLite, no
TrailBase) with environment variables and migration docs.

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>613fd402b13f2bc33c6597f04818120ed1efa503</id>
<published>2026-06-12T19:22:51Z</published>
<updated>2026-06-12T19:22:51Z</updated>
<title type="text">chore: remove old seeds, scripts, guests, and schemas</title>
<link rel="alternate" type="text/html" href="commit/613fd402b13f2bc33c6597f04818120ed1efa503.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit 613fd402b13f2bc33c6597f04818120ed1efa503
parent 278aaf6e9fe33219de6bd3088ebf2c379492d570
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Fri, 12 Jun 2026 21:22:51 +0200

chore: remove old seeds, scripts, guests, and schemas

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>278aaf6e9fe33219de6bd3088ebf2c379492d570</id>
<published>2026-06-12T19:21:38Z</published>
<updated>2026-06-12T19:21:38Z</updated>
<title type="text">chore: remove leftover TrailBase files</title>
<link rel="alternate" type="text/html" href="commit/278aaf6e9fe33219de6bd3088ebf2c379492d570.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit 278aaf6e9fe33219de6bd3088ebf2c379492d570
parent ca8ca659c1781c72163f5651586556fcd9c86168
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Fri, 12 Jun 2026 21:21:38 +0200

chore: remove leftover TrailBase files

Removes trailbase.nix (nix package definition) and traildepot/
(TrailBase config, migrations, data) which are no longer used.

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>ca8ca659c1781c72163f5651586556fcd9c86168</id>
<published>2026-06-12T18:51:09Z</published>
<updated>2026-06-12T18:51:09Z</updated>
<title type="text">feat: add migration system, email verification, and data migration script</title>
<link rel="alternate" type="text/html" href="commit/ca8ca659c1781c72163f5651586556fcd9c86168.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit ca8ca659c1781c72163f5651586556fcd9c86168
parent 8140a43dac4c21f11f0849a6ad593bc717078889
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Fri, 12 Jun 2026 20:51:09 +0200

feat: add migration system, email verification, and data migration script

Migration system (BurntSushi-style):
- Numbered migrations in an array, version tracked in migration_version table
- All pending migrations run in a single transaction
- PRAGMAs (WAL, foreign_keys, etc.) run on every pool connection
- Schema extracted from hardcoded init into migration 0

Email verification:
- Registration sends verification email via SMTP (lettre)
- Users must verify email before login
- /verify-email?token=... endpoint
- Resend verification functionality
- Configurable via SMTP_HOST, SMTP_PORT, SMTP_FROM, SITE_URL env vars

Data migration:
- scripts/migrate-from-trailbase.sql migrates old TrailBase data
- Maps UUID user IDs to integer IDs
- Copies stories, comments, votes, categories
- Users need to re-register (passwords can&#39;t be migrated)

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>8140a43dac4c21f11f0849a6ad593bc717078889</id>
<published>2026-06-12T18:22:04Z</published>
<updated>2026-06-12T18:22:04Z</updated>
<title type="text">feat: replace TrailBase with direct SQLite access</title>
<link rel="alternate" type="text/html" href="commit/8140a43dac4c21f11f0849a6ad593bc717078889.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit 8140a43dac4c21f11f0849a6ad593bc717078889
parent e337c44c1a8928d8072b34cfd4c0b9e4299e6afe
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Fri, 12 Jun 2026 20:22:04 +0200

feat: replace TrailBase with direct SQLite access

Removes the TrailBase HTTP API dependency and queries SQLite
directly via rusqlite with r2d2 connection pooling. This
eliminates the ~5x overhead of HTTP serialization between the
app and database.

Changes:
- New src/database.rs: r2d2 SQLite pool with all queries,
  schema init (tables, indices, FTS5, triggers)
- New src/auth.rs: local auth with argon2id password hashing
  and JWT tokens (replaces TrailBase auth)
- SSE comment notifications via tokio::sync::broadcast channel
  (replaces TrailBase SSE subscription)
- Removed: trailbase.rs, trailbase_types.rs, trailbase-client
  dep, reqwest dep, /api/* proxy route
- Updated all handlers to use spawn_blocking + direct SQL
- Updated NixOS module: removed TrailBase service
- Updated flake: removed openssl dependency

No template changes required.

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>e337c44c1a8928d8072b34cfd4c0b9e4299e6afe</id>
<published>2026-06-12T17:49:16Z</published>
<updated>2026-06-12T17:49:16Z</updated>
<title type="text">perf: share reqwest connection pool across all TrailBase clients</title>
<link rel="alternate" type="text/html" href="commit/e337c44c1a8928d8072b34cfd4c0b9e4299e6afe.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit e337c44c1a8928d8072b34cfd4c0b9e4299e6afe
parent 5017be83a04bca5a146a3d5ddf4640661748e1f7
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Fri, 12 Jun 2026 19:49:16 +0200

perf: share reqwest connection pool across all TrailBase clients

Previously, every request created a new trailbase_client::Client
which internally created a new reqwest::Client, meaning no TCP
connection reuse to TrailBase. Under load, this caused hundreds of
simultaneous TCP connection setups to localhost, capping throughput
at ~220 rps despite TrailBase handling 1400+ rps.

Implements a SharedTransport that wraps a single reqwest::Client
(shared via Arc internally) and passes it to all TrailBase client
instances via ClientOptions.transport. Also bumps reqwest to 0.13
to match trailbase-client&#39;s version.

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>5017be83a04bca5a146a3d5ddf4640661748e1f7</id>
<published>2026-06-11T17:43:37Z</published>
<updated>2026-06-11T17:43:37Z</updated>
<title type="text">perf: add timing instrumentation to story and feed handlers</title>
<link rel="alternate" type="text/html" href="commit/5017be83a04bca5a146a3d5ddf4640661748e1f7.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit 5017be83a04bca5a146a3d5ddf4640661748e1f7
parent de9c24459a1e2f9d348f3bb56270473444b27f52
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Thu, 11 Jun 2026 19:43:37 +0200

perf: add timing instrumentation to story and feed handlers

Logs per-call timing breakdown for each TrailBase API call to
identify performance bottlenecks.

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>de9c24459a1e2f9d348f3bb56270473444b27f52</id>
<published>2026-06-11T13:24:33Z</published>
<updated>2026-06-11T13:24:33Z</updated>
<title type="text">fix: use data-init instead of data-on:load for SSE connection</title>
<link rel="alternate" type="text/html" href="commit/de9c24459a1e2f9d348f3bb56270473444b27f52.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit de9c24459a1e2f9d348f3bb56270473444b27f52
parent d4a353f8d326e863fede66c19b068dbe222bce18
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Thu, 11 Jun 2026 15:24:33 +0200

fix: use data-init instead of data-on:load for SSE connection

data-on:load only fires the native DOM load event, which doesn&#39;t
fire on &lt;section&gt; elements. Datastar&#39;s data-init is the correct
way to run expressions (including @get) when an element is first
processed.

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>d4a353f8d326e863fede66c19b068dbe222bce18</id>
<published>2026-06-11T12:55:03Z</published>
<updated>2026-06-11T12:55:03Z</updated>
<title type="text">fix: add X-Accel-Buffering header to SSE responses for nginx</title>
<link rel="alternate" type="text/html" href="commit/d4a353f8d326e863fede66c19b068dbe222bce18.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit d4a353f8d326e863fede66c19b068dbe222bce18
parent 151a4d25eb0556ee8495868e0ff5abd529c7b565
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Thu, 11 Jun 2026 14:55:03 +0200

fix: add X-Accel-Buffering header to SSE responses for nginx

Nginx buffers proxy responses by default, which prevents SSE
events from reaching the client in real-time. The /api/ location
had explicit proxy_buffering=off but the / location (serving the
app) did not. Adding X-Accel-Buffering: no to all SSE responses
tells nginx to disable buffering per-response.

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>151a4d25eb0556ee8495868e0ff5abd529c7b565</id>
<published>2026-06-11T12:43:09Z</published>
<updated>2026-06-11T12:43:09Z</updated>
<title type="text">feat: re-implement comment SSE bridge with initial event diagnostic</title>
<link rel="alternate" type="text/html" href="commit/151a4d25eb0556ee8495868e0ff5abd529c7b565.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit 151a4d25eb0556ee8495868e0ff5abd529c7b565
parent f82ffb0051398af639e7cacc8753de2576687242
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Thu, 11 Jun 2026 14:43:09 +0200

feat: re-implement comment SSE bridge with initial event diagnostic

Pure Datastar approach: data-on:load opens a long-lived SSE stream
to /story/{id}/comments-stream. The bridge subscribes to TrailBase
SSE internally and forwards datastar-patch-elements events.

Key improvements over first attempt:
- Sends current comments immediately on connect (diagnostic)
- data-on:load attribute preserved in SSE response for morph survival
- requestCancellation: &#39;cleanup&#39; for proper SSE teardown
- Extracted render_comments_event helper to reduce duplication

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>f82ffb0051398af639e7cacc8753de2576687242</id>
<published>2026-06-11T11:43:36Z</published>
<updated>2026-06-11T11:43:36Z</updated>
<title type="text">fix: replace broken SSE bridge with EventSource + Datastar @get</title>
<link rel="alternate" type="text/html" href="commit/f82ffb0051398af639e7cacc8753de2576687242.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit f82ffb0051398af639e7cacc8753de2576687242
parent a5c79d2e7b71b6f896cb054cfaf89f8f1c874ee8
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Thu, 11 Jun 2026 13:43:36 +0200

fix: replace broken SSE bridge with EventSource + Datastar @get

The server-side SSE bridge for comments was returning the full
story page instead of just comments. Replaced with a simpler
approach: EventSource subscribes to TrailBase SSE for notifications,
then dispatches a custom event that triggers Datastar&#39;s @get to
fetch a properly-formatted datastar-patch-elements comments fragment.
This gives us Datastar morphing without the complexity of bridging
two SSE systems server-side.

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>a5c79d2e7b71b6f896cb054cfaf89f8f1c874ee8</id>
<published>2026-06-11T10:38:17Z</published>
<updated>2026-06-11T10:38:17Z</updated>
<title type="text">feat: add Datastar loading indicators to interactive elements</title>
<link rel="alternate" type="text/html" href="commit/a5c79d2e7b71b6f896cb054cfaf89f8f1c874ee8.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit a5c79d2e7b71b6f896cb054cfaf89f8f1c874ee8
parent 2d4964582bb427d63881a6f6cd5b3b09f35264a9
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Thu, 11 Jun 2026 12:38:17 +0200

feat: add Datastar loading indicators to interactive elements

Adds data-indicator attributes to vote buttons, search form, and
settings tabs per Datastar Tao guidance. Vote buttons are disabled
during requests, search shows a &quot;Searching...&quot; indicator, and
settings tabs show &quot;Loading...&quot; while fetching tab content.

Also cleans up unused patch_elements_with_selector function.

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>2d4964582bb427d63881a6f6cd5b3b09f35264a9</id>
<published>2026-06-11T10:36:12Z</published>
<updated>2026-06-11T10:36:12Z</updated>
<title type="text">feat: replace vanilla JS comment updates with Datastar SSE stream</title>
<link rel="alternate" type="text/html" href="commit/2d4964582bb427d63881a6f6cd5b3b09f35264a9.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit 2d4964582bb427d63881a6f6cd5b3b09f35264a9
parent c4e901da35b7a269e129daa060171f4ee0131356
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Thu, 11 Jun 2026 12:36:12 +0200

feat: replace vanilla JS comment updates with Datastar SSE stream

Replaces the manual EventSource + fetch + innerHTML approach with
a proper Datastar SSE integration. A new /story/{id}/comments-stream
endpoint bridges TrailBase SSE subscriptions to datastar-patch-elements
events, letting Datastar morph the comments section instead of
destroying DOM state with innerHTML.

Removes the now-unused ?fragment=comments endpoint.

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>c4e901da35b7a269e129daa060171f4ee0131356</id>
<published>2026-06-11T10:33:08Z</published>
<updated>2026-06-11T10:33:08Z</updated>
<title type="text">fix: use proper SSE format for settings tabs, remove manual history.pushState</title>
<link rel="alternate" type="text/html" href="commit/c4e901da35b7a269e129daa060171f4ee0131356.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit c4e901da35b7a269e129daa060171f4ee0131356
parent 430dacd9cafd13bb66cc3fff2852f31573301d71
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Thu, 11 Jun 2026 12:33:08 +0200

fix: use proper SSE format for settings tabs, remove manual history.pushState

Settings tab handler now returns datastar-patch-elements SSE
events with outer morph. Removed manual history.pushState from
tab links per Datastar Tao guidance to let browsers handle
history naturally.

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>430dacd9cafd13bb66cc3fff2852f31573301d71</id>
<published>2026-06-11T10:32:06Z</published>
<updated>2026-06-11T10:32:06Z</updated>
<title type="text">fix: use proper SSE format and outer morph for search responses</title>
<link rel="alternate" type="text/html" href="commit/430dacd9cafd13bb66cc3fff2852f31573301d71.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit 430dacd9cafd13bb66cc3fff2852f31573301d71
parent a2b1c2eb8ad11a752fea1fa75f5b233ec2043d61
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Thu, 11 Jun 2026 12:32:06 +0200

fix: use proper SSE format and outer morph for search responses

Search handler now returns datastar-patch-elements SSE events.
Search results template includes wrapper div with ID for outer
morph matching.

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>a2b1c2eb8ad11a752fea1fa75f5b233ec2043d61</id>
<published>2026-06-11T10:31:36Z</published>
<updated>2026-06-11T10:31:36Z</updated>
<title type="text">fix: use proper SSE format and outer morph for vote responses</title>
<link rel="alternate" type="text/html" href="commit/a2b1c2eb8ad11a752fea1fa75f5b233ec2043d61.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit a2b1c2eb8ad11a752fea1fa75f5b233ec2043d61
parent d824539220c6ce5105a4937b7b4c3c0c04ce423c
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Thu, 11 Jun 2026 12:31:36 +0200

fix: use proper SSE format and outer morph for vote responses

Vote handler now returns text/event-stream with
datastar-patch-elements events instead of text/html with
non-standard custom headers. Vote button template includes
wrapper span with ID for proper outer morph matching.

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>d824539220c6ce5105a4937b7b4c3c0c04ce423c</id>
<published>2026-06-11T10:30:33Z</published>
<updated>2026-06-11T10:30:33Z</updated>
<title type="text">feat: add SSE helper module for Datastar-compliant responses</title>
<link rel="alternate" type="text/html" href="commit/d824539220c6ce5105a4937b7b4c3c0c04ce423c.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit d824539220c6ce5105a4937b7b4c3c0c04ce423c
parent b29468f52db172037b861e7c6a907d2d66dfaf11
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Thu, 11 Jun 2026 12:30:33 +0200

feat: add SSE helper module for Datastar-compliant responses

Adds src/sse.rs with helpers that format proper SSE
datastar-patch-elements events per the Datastar SSE spec,
replacing the non-standard custom header approach.

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>b29468f52db172037b861e7c6a907d2d66dfaf11</id>
<published>2026-06-10T20:52:50Z</published>
<updated>2026-06-10T20:52:50Z</updated>
<title type="text">feat: add validation-enhancer web component for inline form validation</title>
<link rel="alternate" type="text/html" href="commit/b29468f52db172037b861e7c6a907d2d66dfaf11.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit b29468f52db172037b861e7c6a907d2d66dfaf11
parent 5d1c5a978a2a638cb40796798d8053d9a3ea934e
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Wed, 10 Jun 2026 22:52:50 +0200

feat: add validation-enhancer web component for inline form validation

Vendor validation-enhancer v1.0.3 and wire it into login, register,
submit, and edit forms with aria-errormessage targets and custom
validation messages. Adds .field-error, .invalid, and .valid CSS.

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>5d1c5a978a2a638cb40796798d8053d9a3ea934e</id>
<published>2026-06-10T20:49:17Z</published>
<updated>2026-06-10T20:49:17Z</updated>
<title type="text">refactor: use enum for story fragment query parameter</title>
<link rel="alternate" type="text/html" href="commit/5d1c5a978a2a638cb40796798d8053d9a3ea934e.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit 5d1c5a978a2a638cb40796798d8053d9a3ea934e
parent 7b5699384059ded1d87419a226c3ab577988bc30
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Wed, 10 Jun 2026 22:49:17 +0200

refactor: use enum for story fragment query parameter

Type-safe deserialization instead of matching on raw strings.
Invalid fragment values now return 400 instead of silently
falling through to the full page.

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>7b5699384059ded1d87419a226c3ab577988bc30</id>
<published>2026-06-10T20:40:30Z</published>
<updated>2026-06-10T20:40:30Z</updated>
<title type="text">refactor: consolidate comments fragment into story handler</title>
<link rel="alternate" type="text/html" href="commit/7b5699384059ded1d87419a226c3ab577988bc30.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit 7b5699384059ded1d87419a226c3ab577988bc30
parent e0e3d681a922eb0b6d85130a144e150eae226f34
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Wed, 10 Jun 2026 22:40:30 +0200

refactor: consolidate comments fragment into story handler

Sub-route via ?fragment=comments query param instead of a separate
/story/{id}/comments endpoint, keeping the story page as one logical unit.

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>e0e3d681a922eb0b6d85130a144e150eae226f34</id>
<published>2026-06-09T18:07:43Z</published>
<updated>2026-06-09T18:07:43Z</updated>
<title type="text">feat: add /api/* reverse proxy for local development</title>
<link rel="alternate" type="text/html" href="commit/e0e3d681a922eb0b6d85130a144e150eae226f34.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit e0e3d681a922eb0b6d85130a144e150eae226f34
parent e09ad3174c4e02ee4d203556cf4b90fc4821acb6
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Tue,  9 Jun 2026 20:07:43 +0200

feat: add /api/* reverse proxy for local development

Proxy TrailBase API requests through the Axum app so SSE subscriptions
and other /api/* calls work locally without a separate reverse proxy.

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>e09ad3174c4e02ee4d203556cf4b90fc4821acb6</id>
<published>2026-06-09T18:07:38Z</published>
<updated>2026-06-09T18:07:38Z</updated>
<title type="text">fix: prevent vote race condition with try-then-catch pattern</title>
<link rel="alternate" type="text/html" href="commit/e09ad3174c4e02ee4d203556cf4b90fc4821acb6.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit e09ad3174c4e02ee4d203556cf4b90fc4821acb6
parent deca07de1c8456a7d84102db279ec4c07ba4945b
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Tue,  9 Jun 2026 20:07:38 +0200

fix: prevent vote race condition with try-then-catch pattern

Instead of check-then-act (read votes, then create/delete), try the
insert first and handle the unique constraint violation as &quot;already
voted, unvote instead&quot;. Also deduplicates story/comment vote handlers.

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>deca07de1c8456a7d84102db279ec4c07ba4945b</id>
<published>2026-06-09T18:07:33Z</published>
<updated>2026-06-09T18:07:33Z</updated>
<title type="text">feat: replace full page reload with targeted comment refresh on SSE</title>
<link rel="alternate" type="text/html" href="commit/deca07de1c8456a7d84102db279ec4c07ba4945b.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit deca07de1c8456a7d84102db279ec4c07ba4945b
parent f4bedce994cdcaf00e89b6cd7bfb68b50c3412ba
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Tue,  9 Jun 2026 20:07:33 +0200

feat: replace full page reload with targeted comment refresh on SSE

Fetch just the comments HTML fragment instead of reloading the entire
page when a new comment arrives via SSE. Preserves scroll position.

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>f4bedce994cdcaf00e89b6cd7bfb68b50c3412ba</id>
<published>2026-06-09T18:07:28Z</published>
<updated>2026-06-09T18:07:28Z</updated>
<title type="text">fix: batch-fetch comment authors and tag queries to eliminate N+1</title>
<link rel="alternate" type="text/html" href="commit/f4bedce994cdcaf00e89b6cd7bfb68b50c3412ba.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit f4bedce994cdcaf00e89b6cd7bfb68b50c3412ba
parent cb91fbe80c07ac11a0792795961e322a1cbecce9
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Tue,  9 Jun 2026 20:07:28 +0200

fix: batch-fetch comment authors and tag queries to eliminate N+1

- Add /enrich_comments WASM endpoint for batch user profile lookup
- Add /stories_by_tag WASM endpoint with proper JOIN/LIMIT/OFFSET
- Replace per-comment author fetch loop with single batch call
- Replace unbounded junction table scan in get_stories_by_tag
- Fix N+1 in tag handler using enrich_stories batch endpoint

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>cb91fbe80c07ac11a0792795961e322a1cbecce9</id>
<published>2026-06-09T18:07:21Z</published>
<updated>2026-06-09T18:07:21Z</updated>
<title type="text">chore: remove dead HTMX code</title>
<link rel="alternate" type="text/html" href="commit/cb91fbe80c07ac11a0792795961e322a1cbecce9.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit cb91fbe80c07ac11a0792795961e322a1cbecce9
parent 53b6a479a96fc3707fdd08c402109e2ee5a60b66
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Tue,  9 Jun 2026 20:07:21 +0200

chore: remove dead HTMX code

Remove unused HtmxRequest extractor and HX-Preloaded cache control
middleware left over from the HTMX-to-Datastar migration.

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>53b6a479a96fc3707fdd08c402109e2ee5a60b66</id>
<published>2026-06-08T20:36:42Z</published>
<updated>2026-06-08T20:36:42Z</updated>
<title type="text">revert: go back to page reload for SSE comments</title>
<link rel="alternate" type="text/html" href="commit/53b6a479a96fc3707fdd08c402109e2ee5a60b66.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit 53b6a479a96fc3707fdd08c402109e2ee5a60b66
parent fb434d9dc0869e369758fd5a54dd6127bca91c44
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Mon,  8 Jun 2026 22:36:42 +0200

revert: go back to page reload for SSE comments

Remove the redundant fetch-on-event approach. Keep simple
window.location.reload() on SSE comment events.

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>fb434d9dc0869e369758fd5a54dd6127bca91c44</id>
<published>2026-06-08T20:33:20Z</published>
<updated>2026-06-08T20:33:20Z</updated>
<title type="text">feat: swap comments via fetch instead of full page reload</title>
<link rel="alternate" type="text/html" href="commit/fb434d9dc0869e369758fd5a54dd6127bca91c44.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit fb434d9dc0869e369758fd5a54dd6127bca91c44
parent dd198be281d7cff5b02d8995ccaa9f0aa010f7be
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Mon,  8 Jun 2026 22:33:20 +0200

feat: swap comments via fetch instead of full page reload

On SSE event, fetch /story/{id}/comments HTML fragment and swap
the comments section innerHTML instead of reloading the page.

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>dd198be281d7cff5b02d8995ccaa9f0aa010f7be</id>
<published>2026-06-08T20:27:13Z</published>
<updated>2026-06-08T20:27:13Z</updated>
<title type="text">feat: replace HTMX with Datastar</title>
<link rel="alternate" type="text/html" href="commit/dd198be281d7cff5b02d8995ccaa9f0aa010f7be.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit dd198be281d7cff5b02d8995ccaa9f0aa010f7be
parent aba7c76f4a75c43744a962479110df5f5fdad38a
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Mon,  8 Jun 2026 22:27:13 +0200

feat: replace HTMX with Datastar

- Remove htmx.min.js and preload.min.js
- Add datastar.js, served locally
- Convert all hx-* attributes to data-on:* Datastar equivalents
- Vote, search, and settings handlers return Datastar-compatible
  responses with datastar-selector and datastar-mode headers

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>aba7c76f4a75c43744a962479110df5f5fdad38a</id>
<published>2026-06-08T16:41:17Z</published>
<updated>2026-06-08T16:41:17Z</updated>
<title type="text">fix: enable subscriptions on comment record API</title>
<link rel="alternate" type="text/html" href="commit/aba7c76f4a75c43744a962479110df5f5fdad38a.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit aba7c76f4a75c43744a962479110df5f5fdad38a
parent 1504a1336b33a4e051a0752f8c34df3729765e5a
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Mon,  8 Jun 2026 18:41:17 +0200

fix: enable subscriptions on comment record API

Subscribe returns 403 without enable_subscriptions: true.

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>1504a1336b33a4e051a0752f8c34df3729765e5a</id>
<published>2026-06-08T16:38:19Z</published>
<updated>2026-06-08T16:38:19Z</updated>
<title type="text">fix: filter SSE subscription by story_id</title>
<link rel="alternate" type="text/html" href="commit/1504a1336b33a4e051a0752f8c34df3729765e5a.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit 1504a1336b33a4e051a0752f8c34df3729765e5a
parent c6c96d2d8b32daa1e36c9b787f85b50bd166c751
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Mon,  8 Jun 2026 18:38:19 +0200

fix: filter SSE subscription by story_id

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>c6c96d2d8b32daa1e36c9b787f85b50bd166c751</id>
<published>2026-06-08T15:50:34Z</published>
<updated>2026-06-08T15:50:34Z</updated>
<title type="text">feat: add realtime comments via SSE subscription</title>
<link rel="alternate" type="text/html" href="commit/c6c96d2d8b32daa1e36c9b787f85b50bd166c751.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit c6c96d2d8b32daa1e36c9b787f85b50bd166c751
parent 492f110322442a94b16500601576bd274ded2fcf
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Mon,  8 Jun 2026 17:50:34 +0200

feat: add realtime comments via SSE subscription

Subscribe to trailbase&#39;s table-level SSE endpoint for comments.
When a new comment is inserted for the current story, reload the
page to show it. Nginx configured with proxy_buffering off for SSE.

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>492f110322442a94b16500601576bd274ded2fcf</id>
<published>2026-06-07T21:09:59Z</published>
<updated>2026-06-07T21:09:59Z</updated>
<title type="text">perf: batch story enrichment via WASM guest /enrich endpoint</title>
<link rel="alternate" type="text/html" href="commit/492f110322442a94b16500601576bd274ded2fcf.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit 492f110322442a94b16500601576bd274ded2fcf
parent 62abf675b44c0508a225bcd243e72231f6ba8695
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Sun,  7 Jun 2026 23:09:59 +0200

perf: batch story enrichment via WASM guest /enrich endpoint

Replace N+1 tag/author lookups (2 HTTP calls per story) with a
single /enrich?ids=... call that returns tags and author usernames
for all stories in 2 SQL queries.

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>62abf675b44c0508a225bcd243e72231f6ba8695</id>
<published>2026-06-07T21:07:39Z</published>
<updated>2026-06-07T21:07:39Z</updated>
<title type="text">fix: use IF NOT EXISTS in index migration</title>
<link rel="alternate" type="text/html" href="commit/62abf675b44c0508a225bcd243e72231f6ba8695.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit 62abf675b44c0508a225bcd243e72231f6ba8695
parent 98c1f18bd96da593e9a543d114d5a5ab6a87f8ee
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Sun,  7 Jun 2026 23:07:39 +0200

fix: use IF NOT EXISTS in index migration

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>98c1f18bd96da593e9a543d114d5a5ab6a87f8ee</id>
<published>2026-06-07T21:06:43Z</published>
<updated>2026-06-07T21:06:43Z</updated>
<title type="text">perf: add indices on story.published and story.score</title>
<link rel="alternate" type="text/html" href="commit/98c1f18bd96da593e9a543d114d5a5ab6a87f8ee.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit 98c1f18bd96da593e9a543d114d5a5ab6a87f8ee
parent c10f666365815cb5f4bb33e3de3ee34e8930c29b
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Sun,  7 Jun 2026 23:06:43 +0200

perf: add indices on story.published and story.score

At 1M rows, feed queries did a full table scan + temp sort.
Index on (published DESC) and (score DESC, published DESC)
brings feed from ~140ms to ~35ms at 1M rows.

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>c10f666365815cb5f4bb33e3de3ee34e8930c29b</id>
<published>2026-06-07T21:03:47Z</published>
<updated>2026-06-07T21:03:47Z</updated>
<title type="text">perf: drop ORDER BY rank from FTS5 search</title>
<link rel="alternate" type="text/html" href="commit/c10f666365815cb5f4bb33e3de3ee34e8930c29b.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit c10f666365815cb5f4bb33e3de3ee34e8930c29b
parent 9dec5975bee49ac67ccfa3a66d7c8d3d80121ca3
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Sun,  7 Jun 2026 23:03:47 +0200

perf: drop ORDER BY rank from FTS5 search

ORDER BY rank requires scoring all matching rows before limiting.
With 1M rows matching a common term, this takes ~1.3s vs 4ms
without ranking. FTS5&#39;s default match order is sufficient.

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>9dec5975bee49ac67ccfa3a66d7c8d3d80121ca3</id>
<published>2026-06-07T20:48:38Z</published>
<updated>2026-06-07T20:48:38Z</updated>
<title type="text">docs: trim README to stable content only</title>
<link rel="alternate" type="text/html" href="commit/9dec5975bee49ac67ccfa3a66d7c8d3d80121ca3.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit 9dec5975bee49ac67ccfa3a66d7c8d3d80121ca3
parent 90486b5dd24369cd615a0505e916662ae4f4162d
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Sun,  7 Jun 2026 22:48:38 +0200

docs: trim README to stable content only

Remove project structure listing and tech stack details that
go stale. Keep principles, dev setup, and build commands.

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>90486b5dd24369cd615a0505e916662ae4f4162d</id>
<published>2026-06-07T20:47:45Z</published>
<updated>2026-06-07T20:47:45Z</updated>
<title type="text">docs: update README with current stack, principles, and structure</title>
<link rel="alternate" type="text/html" href="commit/90486b5dd24369cd615a0505e916662ae4f4162d.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit 90486b5dd24369cd615a0505e916662ae4f4162d
parent 0cd96c2e93ae77a6642c362847dd98dde150907f
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Sun,  7 Jun 2026 22:47:45 +0200

docs: update README with current stack, principles, and structure

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>0cd96c2e93ae77a6642c362847dd98dde150907f</id>
<published>2026-06-07T20:03:50Z</published>
<updated>2026-06-07T20:03:50Z</updated>
<title type="text">chore: remove dead code and stale files</title>
<link rel="alternate" type="text/html" href="commit/0cd96c2e93ae77a6642c362847dd98dde150907f.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit 0cd96c2e93ae77a6642c362847dd98dde150907f
parent f07178234252bb5e961a6306b6419bd2bcb0e0ae
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Sun,  7 Jun 2026 22:03:50 +0200

chore: remove dead code and stale files

- Remove unused search_stories (replaced by WASM guest FTS5)
- Remove unused get_comments endpoint and CommentsTemplate
- Remove stale news_item schema files
- Add traildepot/data/, traildepot/secrets/, guests/rust/target/
  to .gitignore

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>f07178234252bb5e961a6306b6419bd2bcb0e0ae</id>
<published>2026-06-07T19:55:05Z</published>
<updated>2026-06-07T19:55:05Z</updated>
<title type="text">fix: revert realtime polling, add error logging for API failures</title>
<link rel="alternate" type="text/html" href="commit/f07178234252bb5e961a6306b6419bd2bcb0e0ae.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit f07178234252bb5e961a6306b6419bd2bcb0e0ae
parent d925b61c660e64e8445d519c5ea3fa4ea6e4f044
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Sun,  7 Jun 2026 21:55:05 +0200

fix: revert realtime polling, add error logging for API failures

- Remove HTMX polling on comments section (trailbase doesn&#39;t
  support table-level subscriptions)
- Add tracing::error! to all silent API failures (votes, tags,
  profile lookups) so errors show in logs instead of being swallowed

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>d925b61c660e64e8445d519c5ea3fa4ea6e4f044</id>
<published>2026-06-07T19:50:05Z</published>
<updated>2026-06-07T19:50:05Z</updated>
<title type="text">fix: replace unwrap() panic in reply form with proper error</title>
<link rel="alternate" type="text/html" href="commit/d925b61c660e64e8445d519c5ea3fa4ea6e4f044.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit d925b61c660e64e8445d519c5ea3fa4ea6e4f044
parent 9e1ceeba2fb27204e0970de395a5b3bc1d7de9e5
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Sun,  7 Jun 2026 21:50:05 +0200

fix: replace unwrap() panic in reply form with proper error

The show_reply_form handler called unwrap() on cookie tokens
which would panic if the user wasn&#39;t logged in. Now returns
a proper Unauthorized error instead.

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>9e1ceeba2fb27204e0970de395a5b3bc1d7de9e5</id>
<published>2026-06-07T19:48:52Z</published>
<updated>2026-06-07T19:48:52Z</updated>
<title type="text">feat: add realtime comment polling via HTMX</title>
<link rel="alternate" type="text/html" href="commit/9e1ceeba2fb27204e0970de395a5b3bc1d7de9e5.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit 9e1ceeba2fb27204e0970de395a5b3bc1d7de9e5
parent cd4097632d1927ba9a83fb9762f49b8e33c2ad4a
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Sun,  7 Jun 2026 21:48:52 +0200

feat: add realtime comment polling via HTMX

Comments section polls /story/{id}/comments every 10s via HTMX,
so new comments from other users appear without page refresh.
Extracted comments into a reusable template partial.

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>cd4097632d1927ba9a83fb9762f49b8e33c2ad4a</id>
<published>2026-06-07T19:43:52Z</published>
<updated>2026-06-07T19:43:52Z</updated>
<title type="text">git: update gitignore</title>
<link rel="alternate" type="text/html" href="commit/cd4097632d1927ba9a83fb9762f49b8e33c2ad4a.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit cd4097632d1927ba9a83fb9762f49b8e33c2ad4a
parent 7dab23b73245ecfc4adc4280e64c0c02ca44cbc7
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Sun,  7 Jun 2026 21:43:52 +0200

git: update gitignore

</content>
</entry>
<entry>
<id>7dab23b73245ecfc4adc4280e64c0c02ca44cbc7</id>
<published>2026-06-07T19:42:35Z</published>
<updated>2026-06-07T19:42:35Z</updated>
<title type="text">fix: remove accidentally committed guest build artifacts</title>
<link rel="alternate" type="text/html" href="commit/7dab23b73245ecfc4adc4280e64c0c02ca44cbc7.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit 7dab23b73245ecfc4adc4280e64c0c02ca44cbc7
parent be120e8f666fee6dea7483782a2fb24509f0ebdb
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Sun,  7 Jun 2026 21:42:35 +0200

fix: remove accidentally committed guest build artifacts

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>be120e8f666fee6dea7483782a2fb24509f0ebdb</id>
<published>2026-06-07T19:42:17Z</published>
<updated>2026-06-07T19:42:17Z</updated>
<title type="text">feat: replace rusqlite with WASM guest for FTS5 search</title>
<link rel="alternate" type="text/html" href="commit/be120e8f666fee6dea7483782a2fb24509f0ebdb.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit be120e8f666fee6dea7483782a2fb24509f0ebdb
parent e42a8b9e1c04ccbd1f3f983457156326e1fb2d73
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Sun,  7 Jun 2026 21:42:17 +0200

feat: replace rusqlite with WASM guest for FTS5 search

Add a trailbase WASM guest component (guests/rust/) that exposes
a /search endpoint with direct DB access for FTS5 MATCH queries.

Remove rusqlite, db_path, and BindReadOnlyPaths — search now goes
through trailbase&#39;s WASM runtime instead of direct file access.

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>e42a8b9e1c04ccbd1f3f983457156326e1fb2d73</id>
<published>2026-06-07T19:29:18Z</published>
<updated>2026-06-07T19:29:18Z</updated>
<title type="text">chore: regenerate types from trailbase schema via quicktype</title>
<link rel="alternate" type="text/html" href="commit/e42a8b9e1c04ccbd1f3f983457156326e1fb2d73.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit e42a8b9e1c04ccbd1f3f983457156326e1fb2d73
parent ff1226bdec4947327494488f825b5c8e7cb13e54
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Sun,  7 Jun 2026 21:29:18 +0200

chore: regenerate types from trailbase schema via quicktype

No manual edits needed — quicktype correctly handles nullable
fields as Option&lt;T&gt; from the JSON schema.

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>ff1226bdec4947327494488f825b5c8e7cb13e54</id>
<published>2026-06-07T19:03:31Z</published>
<updated>2026-06-07T19:03:31Z</updated>
<title type="text">feat: add porter stemming to FTS5 search</title>
<link rel="alternate" type="text/html" href="commit/ff1226bdec4947327494488f825b5c8e7cb13e54.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit ff1226bdec4947327494488f825b5c8e7cb13e54
parent 6e4f4c1284eadf94fdf64dc33a290dcaf701f24d
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Sun,  7 Jun 2026 21:03:31 +0200

feat: add porter stemming to FTS5 search

Recreate story_fts with &#39;porter unicode61&#39; tokenizer so searches
like &quot;running&quot; match &quot;run&quot;, &quot;runs&quot;, etc.

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>6e4f4c1284eadf94fdf64dc33a290dcaf701f24d</id>
<published>2026-06-07T18:48:52Z</published>
<updated>2026-06-07T18:48:52Z</updated>
<title type="text">chore: update generate-types.sh for current schema</title>
<link rel="alternate" type="text/html" href="commit/6e4f4c1284eadf94fdf64dc33a290dcaf701f24d.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit 6e4f4c1284eadf94fdf64dc33a290dcaf701f24d
parent 9df59880a76b927ba04c0b36ec5c1f5aee7ca43d
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Sun,  7 Jun 2026 20:48:52 +0200

chore: update generate-types.sh for current schema

Update table list from old news_item names to current story,
comment, vote, user_profile, category, story_category.

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>9df59880a76b927ba04c0b36ec5c1f5aee7ca43d</id>
<published>2026-06-07T18:35:39Z</published>
<updated>2026-06-07T18:35:39Z</updated>
<title type="text">chore: fix source URL, add seed script</title>
<link rel="alternate" type="text/html" href="commit/9df59880a76b927ba04c0b36ec5c1f5aee7ca43d.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit 9df59880a76b927ba04c0b36ec5c1f5aee7ca43d
parent aaafbad6381ecd892a85003397a8d10df77d104a
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Sun,  7 Jun 2026 20:35:39 +0200

chore: fix source URL, add seed script

- Point footer source link to Forgejo
- Add scripts/seed.sh for generating sample posts

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>aaafbad6381ecd892a85003397a8d10df77d104a</id>
<published>2026-06-07T18:29:23Z</published>
<updated>2026-06-07T18:29:23Z</updated>
<title type="text">feat: clean up design and remove duplicate sort links</title>
<link rel="alternate" type="text/html" href="commit/aaafbad6381ecd892a85003397a8d10df77d104a.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit aaafbad6381ecd892a85003397a8d10df77d104a
parent 0f7fa4aba0acc426160684ba14f6cabd92ac18c2
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Sun,  7 Jun 2026 20:29:23 +0200

feat: clean up design and remove duplicate sort links

- Remove hot/new/top from feed (already in header nav)
- Replace HN orange with a cleaner blue accent
- System font stack, better spacing, subtle borders
- Sticky header, pill-style nav links and tags
- Dark mode support
- Focus states on inputs

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>0f7fa4aba0acc426160684ba14f6cabd92ac18c2</id>
<published>2026-06-07T18:21:56Z</published>
<updated>2026-06-07T18:21:56Z</updated>
<title type="text">feat: serve htmx and preload.js locally instead of CDN</title>
<link rel="alternate" type="text/html" href="commit/0f7fa4aba0acc426160684ba14f6cabd92ac18c2.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit 0f7fa4aba0acc426160684ba14f6cabd92ac18c2
parent 987637b987400da22300df5f6982589eace41e99
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Sun,  7 Jun 2026 20:21:56 +0200

feat: serve htmx and preload.js locally instead of CDN

Embed htmx.min.js and preload.min.js at compile time and serve
from /static/ to avoid external CDN dependency.

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>987637b987400da22300df5f6982589eace41e99</id>
<published>2026-06-07T18:14:28Z</published>
<updated>2026-06-07T18:14:28Z</updated>
<title type="text">fix: add missing slash in resend verification URL</title>
<link rel="alternate" type="text/html" href="commit/987637b987400da22300df5f6982589eace41e99.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit 987637b987400da22300df5f6982589eace41e99
parent 6bdc76dd9494c13a72291f5969c9831c5498f7b1
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Sun,  7 Jun 2026 20:14:28 +0200

fix: add missing slash in resend verification URL

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>6bdc76dd9494c13a72291f5969c9831c5498f7b1</id>
<published>2026-06-07T18:11:17Z</published>
<updated>2026-06-07T18:11:17Z</updated>
<title type="text">feat: add resend verification email page</title>
<link rel="alternate" type="text/html" href="commit/6bdc76dd9494c13a72291f5969c9831c5498f7b1.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit 6bdc76dd9494c13a72291f5969c9831c5498f7b1
parent 3e9d8a750032c40e26bfa1c91925311c251944fb
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Sun,  7 Jun 2026 20:11:17 +0200

feat: add resend verification email page

Users who registered but can&#39;t login can request a new verification
email via /resend-verification. Link added to the login page.

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>3e9d8a750032c40e26bfa1c91925311c251944fb</id>
<published>2026-06-07T17:30:00Z</published>
<updated>2026-06-07T17:30:00Z</updated>
<title type="text">chore: upgrade trailbase from v0.23.0 to v0.28.3</title>
<link rel="alternate" type="text/html" href="commit/3e9d8a750032c40e26bfa1c91925311c251944fb.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit 3e9d8a750032c40e26bfa1c91925311c251944fb
parent f1647057e14243a9e3d5b0e0151fd2ec6ee92842
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Sun,  7 Jun 2026 19:30:00 +0200

chore: upgrade trailbase from v0.23.0 to v0.28.3

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>f1647057e14243a9e3d5b0e0151fd2ec6ee92842</id>
<published>2026-06-07T17:09:13Z</published>
<updated>2026-06-07T17:09:13Z</updated>
<title type="text">feat: use SQLite FTS5 for search instead of LIKE</title>
<link rel="alternate" type="text/html" href="commit/f1647057e14243a9e3d5b0e0151fd2ec6ee92842.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit f1647057e14243a9e3d5b0e0151fd2ec6ee92842
parent e57ad4bcdeca11e8c57bf6e874ea0d8adb511c09
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Sun,  7 Jun 2026 19:09:13 +0200

feat: use SQLite FTS5 for search instead of LIKE

Query the story_fts table directly via rusqlite for proper
full-text search with ranking. Searches both title and text.

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>e57ad4bcdeca11e8c57bf6e874ea0d8adb511c09</id>
<published>2026-06-07T16:23:58Z</published>
<updated>2026-06-07T16:23:58Z</updated>
<title type="text">feat: restore tag selection using hidden field with JS</title>
<link rel="alternate" type="text/html" href="commit/e57ad4bcdeca11e8c57bf6e874ea0d8adb511c09.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit e57ad4bcdeca11e8c57bf6e874ea0d8adb511c09
parent cfcd136f9f62f5e20df384f95af056ed25f7e8c4
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Sun,  7 Jun 2026 18:23:58 +0200

feat: restore tag selection using hidden field with JS

Use a hidden input populated by JS on checkbox change to send tags
as a comma-separated string, avoiding serde_urlencoded&#39;s inability
to handle repeated form keys.

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>cfcd136f9f62f5e20df384f95af056ed25f7e8c4</id>
<published>2026-06-07T16:17:34Z</published>
<updated>2026-06-07T16:17:34Z</updated>
<title type="text">fix: add URL validation error message and log profile creation failures</title>
<link rel="alternate" type="text/html" href="commit/cfcd136f9f62f5e20df384f95af056ed25f7e8c4.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit cfcd136f9f62f5e20df384f95af056ed25f7e8c4
parent ac265c3704e81bc53637c26003f253252535a01a
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Sun,  7 Jun 2026 18:17:34 +0200

fix: add URL validation error message and log profile creation failures

- Show clear error when URL is invalid instead of silent failure
- Log errors from profile creation on login instead of ignoring them

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>ac265c3704e81bc53637c26003f253252535a01a</id>
<published>2026-06-07T16:15:55Z</published>
<updated>2026-06-07T16:15:55Z</updated>
<title type="text">fix: change URL field from type=url to type=text</title>
<link rel="alternate" type="text/html" href="commit/ac265c3704e81bc53637c26003f253252535a01a.html" />
<author>
<name>Silas Brack</name>
<email>silasbrack@gmail.com</email>
</author>
<content type="text">commit ac265c3704e81bc53637c26003f253252535a01a
parent c95f71b10ab0695df09604242ded31bdaa4acd04
Author: Silas Brack &lt;silasbrack@gmail.com&gt;
Date:   Sun,  7 Jun 2026 18:15:55 +0200

fix: change URL field from type=url to type=text

type=url silently blocks form submission if the value isn&#39;t a
valid URL, with no visible error message to the user.

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
</feed>
