• v9.4.0 48c0048d50

    v9.4.0
    All checks were successful
    CI / Images — production build (push) Successful in 14s
    CI / App — typecheck, test & build (push) Successful in 2m1s
    CI / API — typecheck & tests (push) Successful in 4m28s
    Publish Images / Build & push images (push) Successful in 8m18s
    Stable

    claude-bot released this 2026-07-18 19:21:46 +00:00 | 35 commits to main since this release

    Composition Mode (#128)

    A motif is now an authored artifact rather than a reverse-chron dump. Its owner can manually order the motif's entries and interleave short prose/heading blocks between them, so the public page and Markdown export read as an essay with evidence — setup → evidence → counter-evidence → conclusion.

    • A new "Compose" mode on the motif page: drag-to-reorder (with ▲/▼ fallback for touch) plus add/edit/delete prose blocks.
    • Entries and prose blocks share one ordered sequence (migration 056: entry_motifs.position + a motif_blocks table; prose sanitized on write).
    • The public page, the motif Atom feed, and the Markdown export all render ORDER BY position NULLS LAST, created_at DESC and interleave blocks — so any motif you haven't curated renders exactly as before.

    API suite 52 files / 373 tests · app suite 14 files / 76 tests · builds green.

    Downloads
  • v9.3.0 c69897c55b

    v9.3.0
    All checks were successful
    CI / Images — production build (push) Successful in 17s
    Publish Images / Build & push images (push) Successful in 2m3s
    CI / App — typecheck, test & build (push) Successful in 2m13s
    CI / API — typecheck & tests (push) Successful in 4m24s
    Stable

    claude-bot released this 2026-07-18 17:11:27 +00:00 | 40 commits to main since this release

    Three self-contained v10-backlog research-workflow utilities.

    OCR — image text into searchable content (#118)

    On image upload the configured vision model also transcribes visible text (lyrics, captions, on-screen text). It's offered on the media card — "Show extracted text (OCR)" → "Add transcribed text to entry" — and only appended to the entry's content on explicit accept (sanitized on write), at which point it's full-text searchable. Nothing is applied automatically; no-ops when AI isn't configured. Migration 055 adds media.ocr_text.

    Mastodon + Instagram import adapters (#121)

    Two new archive parsers behind the existing adapter interface: Mastodon outbox.json (ActivityPub — original posts only, boosts skipped, HTML stripped) and Instagram posts JSON (caption + creation_timestamp), each mapped to entries with the right source/source_id dedup shape. New /api/import/{mastodon,instagram} endpoints + Import-page cards.

    Offline read cache (#122)

    Recently-viewed entries and motifs are now readable offline via an app-managed, user-scoped IndexedDB cache (separate from the capture queue). When a fetch fails offline the detail page renders the cached copy with a banner; the cache clears on logout. This does not relax the invariant that the service worker never caches /api/*.


    API suite 51 files / 368 tests · app suite 14 files / 76 tests · builds green.

    Downloads
  • v9.2.0 dad048a90d

    v9.2.0
    All checks were successful
    CI / Images — production build (push) Successful in 14s
    CI / App — typecheck, test & build (push) Successful in 2m4s
    Publish Images / Build & push images (push) Successful in 2m6s
    CI / API — typecheck & tests (push) Successful in 4m26s
    Stable

    claude-bot released this 2026-07-18 16:34:21 +00:00 | 47 commits to main since this release

    Two more v10-backlog research-workflow enhancements.

    Works Registry (#127)

    The studied media — a music video, film, or album — is now a first-class object, so an observation can be about a work, not merely from a source. A project-scoped, soft-deletable works registry (migration 054) with full CRUD in Settings → Works (title, artist, release date, canonical URL). Assign works to an entry inline on its detail page; filter Entries and Timeline by work to pull up every observation on a given video.

    Deferred follow-ups (tracked on #127): release-date pivot on Timeline, works-as-sections in Markdown export / public motif pages, and bootstrap-by-clustering existing source URLs.

    Path Finder (#129)

    "How are these two observations related?" Pick any two entries (or an entry and a motif) and get the shortest chain linking them — through typed connections and shared motifs — rendered as a graph on the Insights page, with typed edge labels and clickable nodes. A bounded bipartite BFS (GET /api/connections/path) treats motifs as nodes so a hub motif can't explode the search, is depth-capped, and is scoped to the active project. "No path found" is an explicit outcome.


    Full API suite: 50 files / 353 tests green. Also fixed a test-isolation bug (clearEntryData didn't truncate works/entry_works).

    Downloads
  • v7.3.0 876c161a44

    v7.3.0
    All checks were successful
    CI / App — typecheck, test & build (push) Successful in 2m16s
    CI / Images — production build (push) Successful in 4m4s
    CI / API — typecheck & tests (push) Successful in 4m16s
    Publish Images / Build & push images (push) Successful in 13m7s
    Stable

    claude-bot released this 2026-07-17 23:28:22 +00:00 | 92 commits to main since this release

    Tea Leaves v7.3.0 — UI, mobile & accessibility

    Released 2026-07-17

    The user-facing rough edges from the 2026-07-15 audit — silent failures, dead affordances, and accessibility gaps on the surfaces people actually touch. Twelve issues (#105–#116).

    Highlights

    • No more silent failures. Failed entry/motif deletes, removes, and connection deletes now surface errors (#106); a failing search shows a distinct "Search failed" state instead of the empty prompt (#107); and an offline capture the server rejects is flagged with its reason and offered Retry/Discard instead of retrying forever invisibly (#108).
    • Entries browse past 50 (#105). A "Load more" pager reaches a project's full history, not just the first page.
    • Public pages degrade gracefully (#109). A render error on a shared/embedded page shows the recoverable fallback instead of a blank screen.
    • QuickCapture image drop actually attaches (#110) — real upload, not placeholder text — and the PWA "New Entry" shortcut opens a working create form (#111).
    • Accessibility pass (#112–#114). ≥44px touch targets on capture and graph controls; role="dialog" + focus trap + aria-labels on the modals; and a WCAG-AA contrast sweep of muted text across the app.
    • Safer defederation (#115). The kill-switch now shows a non-mutating impact preview (editors/contributions/entries affected) in an in-app type-to-confirm dialog, replacing the stacked native confirm()/alert().

    Upgrading

    No migrations, no config changes. Hosts on the image-based stack update automatically — this tag publishes the :stable images that prod follows.

    Downloads
  • v7.2.1 7425a4c1a8

    v7.2.1
    All checks were successful
    CI / API — typecheck & tests (push) Successful in 3m38s
    CI / Images — production build (push) Successful in 3m41s
    CI / App — typecheck, test & build (push) Successful in 4m3s
    Publish Images / Build & push images (push) Successful in 1m29s
    Stable

    claude-bot released this 2026-07-17 21:38:32 +00:00 | 96 commits to main since this release

    Tea Leaves v7.2.1

    Released 2026-07-17

    A small patch — the first release cut through the new auto-deploy pipeline (#134).

    Changes

    • Image-dedup upload warning (#136 follow-up). After v7.2.0 gave the API real perceptual image dedup, the media-attachment UI now surfaces it: uploading a near-duplicate shows a dismissible "same file / looks similar to an image you already have" banner linking to the existing entry. Non-destructive — the upload still succeeds.
    • Auto-deploy (#134). CI now publishes channel-tagged images to the Forgejo registry (main:beta, vX.Y.Z:stable), and both environments run them under a per-stack, label-scoped Watchtower that auto-redeploys on a new image — dev follows :beta (tracks main), prod follows :stable (release tags). The API self-migrates on boot, so an image swap migrates itself. See deploy/.

    Upgrading: no migrations, no config changes. Hosts on the image-based stack update automatically; this is the release that produced the :stable images.

    Downloads
  • v7.2.0 c58ba559df

    v7.2.0
    All checks were successful
    CI / App — typecheck, test & build (push) Successful in 2m25s
    CI / API — typecheck & tests (push) Successful in 3m53s
    CI / Images — production build (push) Successful in 4m18s
    Stable

    claude-bot released this 2026-07-17 16:37:36 +00:00 | 100 commits to main since this release

    Tea Leaves v7.2.0 — Reliability, Data Integrity & Docs

    Released 2026-07-17

    The Medium/Low remainder of the 2026-07-15 codebase audit — the things that rot quietly rather than break loudly — plus two bonus features and a long-standing CI flake finally root-caused. All 13 milestone issues, no breaking changes. Migrations 044046 (additive; 046 renames one column).

    🛡️ Data integrity

    • Deleting a user no longer strips attachments off the entries it preserves (#98). media.user_id was still ON DELETE CASCADE while entries.user_id is SET NULL — so a departed collaborator's media was hard-deleted (and files orphaned on disk) even on entries the schema intends to keep. Migration 044 makes it SET NULL; entry_id remains the lifecycle anchor.
    • Soft-deleted content no longer leaks (#93, #94). The legacy motif share query served trashed motifs and entries; export leaked trashed rows across four queries (including one that embedded a trashed entry's content via a connection to a live one). CSV export cells starting = + - @ are now neutralised against spreadsheet formula injection.
    • Federation optimistic-lock TOCTOU (#96). The lock read happened on the pool before the write transaction, so a concurrent local edit could be silently clobbered by a stale remote update. Now SELECT … FOR UPDATE inside the transaction; regression-tested with a real concurrent-edit race.
    • Media access is project-scoped, not uploader-scoped (#135). Every media route keyed off media.user_id, so a collaborator's uploads were invisible to the project owner today, and (post-#98) a deleted uploader's preserved media was unreachable. Reads now scope to the entry's project; delete is stricter (uploader or project owner only). Multi-user tested.

    ⚙️ Reliability & ops

    • /health probes its dependencies (#97). It returned ok the moment the event loop was alive; now it runs SELECT 1 + Redis PING (each bounded) and returns 503 when either is down. Added a container HEALTHCHECK and gated app on service_healthy. (Note: Docker Compose doesn't restart on healthcheck failure — that's tracked in #137 → monitoring.)
    • CI builds the production images (#103) and asserts the migrations copy landed — a Dockerfile/peer-dep regression can no longer ship unverified.
    • Bounded Redis job retention (#100), top-level crash handlers (#101), a project-scoped entry-list index (#95), and the AP signature freshness window widened 30 s → 5 min (#102) so clock-skewed federated peers stop being rejected as invalid.

    Features

    • Semantic dedup Pass 2 is real (#99). It was documented as active but never existed — the duplicate-review queue could never fill. Now cosine similarity over text_embedding flags near-duplicate imports, with imports embedding inline (one AI call per post) and a dedicated import.dedupThreshold (default 0.95) so a themed archive doesn't flag itself.
    • Real perceptual image dedup (#136). media.phash held a SHA-256 of the bytes (matches only byte-identical files) under a misleading name. Renamed to content_sha256; added a 64-bit dHash so re-encoded/resized copies are caught. Threshold set from measurement — re-encodes 0–7 bits apart, distinct images ≥24, default 10 in the gap.

    📚 Docs

    • README documents v6.2 and v7 (#104) — it trailed the code by a major version; the federation trust model is now written down for a new operator. Categories/Insights added; two further stale claims fixed.

    🧪 Internal

    API suite grew to 268 tests (from 232). And the API — typecheck & tests CI flake is root-caused and fixed: with no AI configured the app defaults to ollama at localhost:11434, so every entry-write test fired a fire-and-forget embedding fetch that piled up inside CI's Docker-in-Docker and starved the pg pool, pushing analytics.test.ts past its 15 s timeout. test/setup.ts now takes the suite off the network for AI. It was never the runner.

    Upgrading: migrations 044046 apply on boot; no config changes. 046 renames media.phashmedia.content_sha256 and adds media.dhash.

    Downloads
  • v7.1.0 8843c7a2b5

    v7.1.0
    All checks were successful
    CI / App — typecheck, test & build (push) Successful in 2m4s
    CI / API — typecheck & tests (push) Successful in 3m8s
    Stable

    claude-bot released this 2026-07-17 01:00:38 +00:00 | 109 commits to main since this release

    Tea Leaves v7.1.0 — Core Hardening & Security

    Released 2026-07-16

    A pre-v8 hardening pass: fix the daily-use and recovery paths that were quietly broken, and lock down + test the v7 federation trust boundary before stacking v8's moderation layer on top of it. All 13 issues of the milestone, from the 2026-07-15 codebase audit. No new migrations; no breaking changes.

    🔒 Security

    AP inbox: signer bound to the claimed actor (#82)

    verifyInboxSignature only proved the request was signed by the keyId owner — for every activity type except the entry-write path it never asserted that owner == activity.actor. Anyone controlling a domain could sign a request while claiming to be an arbitrary third party: spoofed fediverse identities in public motif comments, forced Follow rows, a forged Delete/Undo. The binding is now hoisted to run for all activity types (Follow / Invite / Undo / Like / Announce / Create / Delete) immediately after signature verification → 401 on mismatch.

    AP inbox: SSRF guard on remote-controlled fetches (#81)

    The unauthenticated inbox fetched attacker-supplied actor URLs and delivered to attacker-supplied inbox URLs with no host filtering, following redirects — blind SSRF into the homelab. New lib/ssrfGuard.ts resolves the target and rejects loopback / RFC1918 / link-local / ULA / CGNAT, re-validating every redirect hop. Origins an admin has explicitly federated with bypass the denylist, so two homelab instances still federate over a private network (mirrors the #62 outbound allowlist).

    Backup routes are admin-only (#80)

    /api/backup/* sat behind only requireAuth, so any authenticated non-admin (e.g. a project collaborator) could read cloud-storage credentials, manage destinations and stage restores. Now requireAdmin, and GET /destinations returns only the names of extra_env keys — never the values (editing is write-only).

    🛟 Recovery & reliability

    Backup restore actually works (#84)

    applyRestoreEntry omitted project_id (NOT NULL → 23502) and defaulted certainty to 'possible', which isn't in the CHECK set (→ 23514). Every staged entry apply threw — at precisely the moment recovery is needed. Now sources project_id from the staged data (falling back to the active project) and validates certainty, covered end-to-end by an integration test.

    Graceful shutdown (#85)

    Nothing handled SIGTERM, so every deploy hard-killed in-flight import/media/backup jobs, left BullMQ locks lingering, and could strand a backup at status='running' forever. The API now drains workers, then closes the server, Redis and the pg pool, with a 30 s cap. The handler alone was inert: CMD ["npm", "start"] meant npm — not node — received the signal, so it never ran (exit 1, npm error signal SIGTERM). The image now execs node directly and compose runs tini as PID 1, which also reaps orphaned restic/pg_dump/ffmpeg children.

    Backups can't hang forever (#86)

    restic/pg_dump spawns had no timeout and no error handler — an unreachable repo or a missing binary left the promise pending forever, silently blocking every future scheduled backup while the dashboard cheerfully showed "running" (so no alert fired). Spawns now have a 2 h kill-timer and surface spawn errors; a reconciliation pass fails any job still running after 3 h and fires the configured alert.

    Migrations run before workers (#87)

    Workers and the backup scheduler started before runMigrations(), and the scheduler's error was swallowed — on a first-ever boot backup scheduling silently never registered. Boot order is now migrate → workers → scheduler → listen.

    📱 Mobile daily-use

    Quick Capture no longer throws away voice notes (#90)

    Worse than reported: the recorder's target entry was always null while the panel was open, so every Quick Capture voice note was silently discarded on Stop — the feature had no working path at all. Tapping Record now creates the entry up-front so audio always has somewhere to land, and Save updates that entry rather than duplicating it.

    Import, Suggestions & Trash reachable on a phone (#89)

    All three lived only in the desktop header — on <768px no on-screen control reached them. The bottom nav now ends in a More overflow sheet (Import, Suggestions, Trash, Settings — plus Admin/Federation for admins).

    A logged-out user clicking an /invites/:token link landed on /entries with the token gone and no message. The target path now round-trips through the OIDC flow, validated as a same-origin relative path on the way in and out (absolute, //evil.com and /\evil.com rejected) so it can't become an open redirect.

    🐛 Ops / UI

    • Uploads & imports over 1 MB (#83) — the bundled nginx set no client_max_body_size, so its 1 MB default 413'd essentially every image, video and archive import before it reached the API. Now 2g on the /api/ block.
    • PWA install & favicon (#88) — every icon referenced by index.html/manifest.json 404'd; the install prompt and tab icon were broken. Added favicon.svg, apple-touch-icon.png and the 192/512 maskable PNGs.

    🧪 Internal

    API suite grew to 238 tests (from 232): direct HTTP-signature verification tests (valid / replayed / tampered-digest / unsigned-digest / wrong-key / wrong-actor binding end-to-end), SSRF IP-classification tests, backup authorization + restore-apply coverage, and the open-redirect guard matrix — closing the audit's "the newest perimeter is the least tested" finding (#92).

    Upgrading: no migrations and no config changes required. Deploys pick up init: true / stop_grace_period from docker-compose.yml, which the graceful shutdown depends on — redeploy with the compose file from this tag rather than an older local copy.

    Downloads
  • v7.0.0 12342a49a3

    v7.0.0
    All checks were successful
    CI / App — typecheck, test & build (push) Successful in 1m28s
    CI / API — typecheck & tests (push) Successful in 2m40s
    Stable

    claude-bot released this 2026-06-30 15:19:31 +00:00 | 130 commits to main since this release

    Tea Leaves v7.0.0 — Cross-Instance Collaborative Editing

    Released 2026-06-30

    Extends the read-only federation layer into write-capable cross-instance collaboration: an invited researcher on another Tea Leaves instance can create and update entries in a host project over a signed ActivityPub channel, with a trust chain, conflict review, and an admin kill-switch. Migrations 041043 (additive).

    Features

    🤝 Cross-instance write path (#12 / #9 / #10 / #11)

    • Invite a remote editor by their ActivityPub actor URL (POST /api/projects/:id/invite-remote); their instance must be federated + enabled.
    • Signed write path — a Create/Update of a Note addressed to a project's AP context becomes an entry write. The signature is verified against the actor's cached key and bound to the activity's actor (no actor spoofing); only accepted remote editors may write (no open inbox). Remote-authored entries carry attribution snapshotted from the actor.
    • Conflict resolution — optimistic lock on updated_at vs the activity's published: non-conflicting writes apply automatically (last-write-wins), conflicts land in an owner review queue (apply / keep mine).
    • Schema: project_collaborators.remote_actor_url + remote_contributions (migration 041).

    ↔️ Editor-side round-trip (#62)

    The other half: an invited editor's instance learns of the invite via a signed AP Invite (a new Shared with me page), signed-fetches the host project's entries (GET /ap/projects/:id), and delivers signed Create/Update edits from a remote-project view — completing create and update end-to-end across instances. (migrations 042043)

    🛑 Admin defederation kill-switch (#61)

    Sever a malicious or compromised instance: a blocked state that hard-rejects its inbound activity before signature processing, revokes its remote editors, quarantines its pending/conflict contributions, and optionally purges the entries its actors authored.

    🔒 Security

    Two hardenings caught by automated review during the build:

    • Inbound signatures must now cover (request-target) / date / digest with the digest verified unconditionally — closes a replay / body-substitution actor-spoofing gap (also hardens the existing Follow/comment inbox).
    • Editor egress (Invite-supplied host URLs) is restricted to admin-federated origins — an SSRF guard.

    🧪 Internal

    API test suite grew to 218 tests (HTTP-signature harnesses for the signed read/write paths). No breaking changes; migrations 041043 apply on startup.

    Downloads
  • v6.2.0 04b40b24d7

    v6.2.0
    All checks were successful
    CI / App — typecheck, test & build (push) Successful in 1m32s
    CI / API — typecheck & tests (push) Successful in 2m30s
    Stable

    claude-bot released this 2026-06-29 17:56:19 +00:00 | 158 commits to main since this release

    Tea Leaves v6.2.0 — Core Improvements

    Released 2026-06-29

    The v6.2 milestone: a new project-scoped taxonomy, an analytics/visualization layer, and a clean split between workspace settings and instance administration. No breaking changes. One additive migration (040_categories).

    Features

    🏷️ Categories for entries & motifs (#6)

    A per-project taxonomy shared by entries and motifs — distinct from freeform tags: categories are project-scoped, curated, and carry an optional color. Full CRUD at /api/categories, assignment on entry/motif forms via a create-on-the-fly picker, colored contrast-aware badges throughout, and category_id filtering on the entry list, motif list, and timeline. Manage them in Settings → Categories. (migration 040_categories)

    📊 Insights — analytics & data visualization (#7)

    A new Insights page surfaces project-scoped analytics computed live from the active project: headline counts, monthly entry volume, certainty breakdown, motif-prevalence-over-time, and a force-directed motif co-occurrence web (motifs that share entries, weighted by overlap). Rendered with recharts. Three read-only endpoints under /api/analytics (overview, motif-prevalence, network).

    ⚙️ Settings / Admin redesign (#8)

    The single sprawling Settings page mixed per-workspace controls with server-wide instance configuration. They're now cleanly split by audience:

    • Admin (admin-only) gains AI, AI tools, Search, Media, Email (SMTP), Backup, and the Federation kill-switch, alongside Version / Storage quotas / Users / Audit Log.
    • Settings keeps the workspace controls every user needs: Projects, Project templates, Tags, Categories, Integrations, Export.
    • Security: /api/settings (and test-email) are now gated to admins — every key in the settings table is server-wide config.

    🐛 Fixes

    • Editing a Markdown entry no longer blanks the app (#52) — replaced the unmaintained tiptap-markdown with the official version-matched @tiptap/markdown, and added a top-level ErrorBoundary so a future render error degrades gracefully instead of whiting out the page.

    🧪 Internal

    Frontend at 63 tests, API at 175 tests — including a non-admin 403 regression test for the newly-gated settings routes.

    Downloads
  • v6.1.0 b82f44d1ae

    v6.1.0
    Some checks failed
    CI / App — typecheck, test & build (push) Successful in 2m9s
    CI / API — typecheck & tests (push) Failing after 3m12s
    Stable

    rbrooks released this 2026-06-25 23:31:33 +00:00 | 180 commits to main since this release

    Tea Leaves v6.1.0 — Core Polish & Fixes

    Released 2026-06-25

    A small, focused release of self-contained core-app fixes (milestone v6.1) to land before the larger v6.2 work — plus a substantial round of frontend test coverage. No migration, no breaking changes.

    Fixes & improvements

    🔗 Clickable motifs from an entry (#3)

    On an entry, the linked motifs in the "Motifs" section rendered each title as inert text — there was no way to jump from an entry to a motif. Each linked motif is now a proper link to its detail page, with a hover affordance.

    🌙 Readable motif graph in dark mode (#5)

    The motif graph used hardcoded light node backgrounds, borders, and background dots, and its chrome (controls, minimap, edges) had no dark theme — leaving it illegible on a dark page. The graph now follows the app theme: node and background colors are themed for dark mode, driven by a new reactive useIsDarkMode() hook.

    📄 Export all motifs as one Markdown document (#4)

    A new motifs.md export combines every motif into a single shareable Markdown document, with --- separators under a document header — reusing the existing per-motif formatter. Available in Settings → Export → All motifs, beside the existing motifs.json.

    🧪 Internal

    Frontend test coverage. The app went from a single smoke test to 59 tests, covering the API wrapper, the sanitize/shortcuts utilities, the Tiptap editor, the auth/theme/offline-queue hooks, and the entry list/detail pages — plus a regression test for each fix above.

    Downloads