What we’ve shipped
Every change to Selah, newest first, written for the people who use it rather than the people who built it. Last shipped .
The Prayer Timer.
Fixed
Building a custom reading plan no longer times out.
Longer plans — a full year, say — used to fail with an error before they finished generating. They now build reliably, however many days you ask for.
Some people weren't getting their daily devotional.
If you had no active reading plan and your week had no theme set, devotional generation errored out and skipped you — every day. It now passes over that day quietly and resumes the moment there's something to read.
The reading-plan picker now shows the full set of curated plans.
They were missing immediately after the 0.16.0 launch.
Added
The Prayer Timer.
Start a timer and pray. The clock holds when you pause; you can give blocks of time to specific prayers on your board, and jot a thought or a verse mid-session without losing focus. A focus mode clears the screen down to just the timer. Finished sessions are saved with a note (very short ones are discarded), and a Log and Stats view show where your prayer time has gone.
A reworked prayer board.
The board is now a kanban with a filter row and custom tags you create yourself — organize prayers however fits.
A new curated reading plan — "Proverbs, the Story & the Gospel."
A year in three short readings a day: a chapter of Proverbs keyed to the date, the Old Testament story of God's people in order, and a New Testament passage. At thirty turning points the Old Testament shadow and its New Testament fulfilment are set on the same day — Isaac bound on Moriah alongside the crucifixion, the bronze serpent alongside John 3, Jonah's three days alongside the sign of Jonah. Curated by Patrick B.
Two whole-Bible reading plans.
"Bible in a Year" reads straight through, Genesis to Revelation. "Old & New Testament Daily" pairs an Old Testament reading with a New Testament reading every day. They join the curated set — seven plans to choose from.
The Reader — a dedicated Scripture study workspace.
A new
/readersurface for deep study. Read two passages side by side in a multi-pane layout. Tap any word for Word Study: its Strong's number, lexicon definition, morphology, how often it occurs, and how the KJV renders it. Tag multi-word phrases as single study targets. Cross-references show inline with hover previews. Visualizations bring the text to life — a family tree you can zoom, pan, and click through person to person; a places map; a timeline. A Gospel harmony lines up parallel accounts. A Resources tab surfaces Old-Testament-in-New quotations and the Names of God. Keep a scratchpad open beside the passage to write as you study, or summon the Companion into the reader to talk a passage through. Per-pane full-text search, a ⌘K command palette, KJV by default. The lexical, morphology, and cross-reference data comes from STEPBible — the open scholarly Bible dataset curated by Tyndale House, Cambridge.Reading Plans — read through Scripture with a plan.
Pick a curated plan (the New Testament in 90 days, Psalms + Proverbs monthly, the Sermon on the Mount, the Gospel of John, the Pauline epistles, or a year-long Wisdom-Story-Gospel rotation) — or describe a goal and have a custom plan built for you. Each day's reading lands as a card. The plan is calendar-aligned: "today" is the day you're actually on, and days you miss collect in a catch-up list you can read in order or skip. Mark days read, pause and resume without losing your place, and switch plans anytime — picking a plan back up resumes where you left off. A reading-plan tab lives in the Reader, your daily devotional is built from the plan's passage, the home dashboard tracks your progress, and Telegram delivers each day's reading.
Public profile pages.
Your activity now has a shareable
/@usernamepage with a contribution-style heatmap, shown only when you turn on the activity-visible toggle.
Changed
The Companion shows its work.
While it thinks through a question, a calm line tells you where it is — checking cross-references, looking up a word, drafting the reply — instead of a silent thirty-second spinner. The answer still arrives the same way; the wait just stopped being a black box.
- The home and notes pages now use the same wider layout as the prayer page, so the surfaces line up.
Sign-up rollback no longer leaves users in a half-deleted state.
Fixed
Sign-up rollback no longer leaves users in a half-deleted state.
When a sign-up needs to be rolled back (bad invite code, transient Clerk outage), both the local account and the Clerk identity now come down together, or neither does. The previous path could quietly leave a "ghost" account that looked signed up but had no invite consumed. Two affected users on 2026-05-01 had their email verification restored manually.
Unauthenticated API requests return 401 instead of 500.
Hitting prayer or concept endpoints without a session now returns a clean "Unauthorized" response. Previously the unauth case escaped as a server error and showed up as a spike in error tracking, drowning out real bugs.
Already-signed-in visitors no longer hit an error on the sign-up page.
If you landed on the sign-up page while already signed in — for example, after an admin verified your email — you'd see a confusing error instead of getting into the app. You now go straight to onboarding instead.
No more "you started a companion session about an hour ago" message after using Telegram.
Fixed
No more "you started a companion session about an hour ago" message after using Telegram.
The bot was sending it after an hour of silence following any Telegram chat. Telegram chat doesn't have a session you "start" or "wrap up" — the message read as nonsense. Devotional sessions still get the same idle check, where it actually maps to something the user is doing.
Sign-up works again for invite-coded accounts.
Some sign-ups in the last day created a Clerk account, then had it deleted seconds later, bouncing the user back to the sign-in page. Two affected users can retry their original invite codes and finish creating their accounts.
Stream is a complete redesign.
Added
Stream is a complete redesign.
The Stream page now shows every entry type in one unified feed — captures, notes, prayers, devotionals, companion sessions, weekly arcs — instead of a flat capture list. Each card opens into a drawer for inline detail. Date grouping, filter tabs (All / Captures / Notes / Prayers / Devotionals / Companion), live-session banner, a capture input pinned to the bottom, and a "+ New" menu for jumping into anything from one place.
Folders + Library.
A new sidebar folder tree and a Library page give you one place to organize notes, captures, and prayers across every surface. Star a folder to pin it to the top. Drag items between folders. Open a folder to see breadcrumbs and sub-folders. Reorganizing never leaves items half-filed. The folder chip on each card lets you re-file in one click.
Weekly arc — your week, summarized in Telegram.
A Saturday-evening Telegram message lands with a one-paragraph lede, this-week stats (captures, sessions, notes, primary book studied), prayer summaries from your active board, and the scripture refs that came up most. Quiet weeks get gentler copy ("a quieter week — sometimes that's the point") instead of a stat dump.
Cross-surface notifications.
Things you do on the web — raise a prayer, save a note, end a session, confirm a theme, mark a prayer answered — now ping your Telegram chat with a short observational note. Events that started on Telegram don't echo back. If you do the same thing five times in two minutes, you get one ping, not five.
Telegram `/stream` command.
Browse the same unified feed from inside the bot, paginated. Now you have parity between web and chat for what you've been up to.
Smarter heartbeat scheduling.
Long active sessions get a single check-in ("still in 1 Cor 13?" after an hour). Prayer reminders and forgotten-prayer nudges look different from each other so you can tell at a glance which is which. Prayers and notes don't get nudged twice for the same thing under different wording.
Daily devotional card leads with scripture.
The Stream's daily devotional card shows the scripture ref + opening line first so you can decide whether to dive in without expanding.
Fixed
Stream no longer errors out when you're signed out.
Hitting
/streamwithout a session used to throw a server error; it now redirects you to sign-in like every other authed page.Untitled placeholder shows up on a fresh note.
New notes were saving with the literal title "Untitled" instead of leaving the title blank, so typing in the title field appended to it. Now the field is empty by default and the placeholder reads "Untitled" until you write something.
Devotional settings card no longer logs a stray error when you navigate away mid-load.
Switching surfaces while the card was fetching could leave a half-finished request that the page later complained about. It now cancels cleanly.
Filing an item into a brand-new folder is now all-or-nothing.
Creating a folder and dropping an item into it in one gesture used to leave the item half-filed if the second step failed. The flow either succeeds completely or leaves the item where it was.
Telegram welcome message now mentions everything you can do.
The /start linking flow undersold what was wired up; it now lists Stream browsing, prayer raising, note-saving from chat, and the Saturday weekly arc.
Changed
Public note URLs use a stable namespace.
The user-facing share URL stays
/@username/[slug]— that hasn't changed and your existing share links still work. Internally the route is now namespaced so adding new top-level pages won't collide with usernames in the future.Prompts tightened across devotional, prep, heartbeat, learning, summary, and knowledge graph.
Less restating things you already know, fewer hedge words, more direct observational voice. Quality re-checked against the eval suite for each surface.
Saturday-evening double-pinging fixed.
When the weekly arc Telegram lands at 6pm, the regular Saturday devotional now drops the parts that overlap so you don't read the same three sentences twice in five minutes.
Marking a prayer answered uses 🕊 (peace dove) instead of 🌅 (sunrise).
The dove reads tonally neutral whether the answer was joyful or somber — a sick parent who passed shouldn't get a sunrise emoji.
Help icons and loading states now consistent across pages.
The
?help button sits in the same spot on every authed page; loading skeletons match the layouts they stand in for; keyboard shortcuts no longer fire from inside form inputs.
Pages now show instant skeletons while loading.
Changed
Pages now show instant skeletons while loading.
Notes, Companion, Prayer Board, Knowledge, and Search all display page-matched placeholder layouts the moment you navigate to them — no blank screens or layout jumps while data fetches.
Button and Badge styles compose cleanly.
Tailwind class overrides passed via
classNamenow reliably win over defaults. Stacking multiple variants no longer produces unexpected merged styles.New `Page` component for consistent 5-state layout.
Any page that fetches data can now use
<Page loading error empty emptyState>to get loading skeletons, auto-dismissing error banners, and empty states without writing that logic from scratch each time.
Companion chat works across web and Telegram again.
Fixed
Companion chat works across web and Telegram again.
Switching between the web companion and your Telegram bot could trigger a crash. Both surfaces now stay in sync without errors.
Notes load reliably again.
Some users saw an error when opening their notes list. That's resolved.
Companion now remembers things about you across sessions.
Added
Companion now remembers things about you across sessions.
The memory system extracts atomic facts from session summaries (study style, theology, life context, preferences, observations) and stores them persistently. Facts are reinforced when they reappear, superseded when they change, and expired when they have a deadline. The companion and devotional engine pull the top active facts into context so each session builds on what came before.
Concept summaries now include personal context.
When the cron resynthesizes a concept summary, it includes memory facts that mention that concept by name — prefixed
[memory]in the snippet context passed to the LLM. "Grace" as a concept will now surface that you view it as empowerment rather than permission.Concept cards show "Updated X ago".
Every concept detail view now shows a relative timestamp ("Updated 3 hours ago") regardless of whether the concept has a summary yet. Previously the timestamp was only visible when a summary existed.
Notes track how many times they've been edited.
A new
edit_countcolumn on notes increments on every content save. The companion's activity digest now reflects the real edit depth rather than a hardcoded floor of 1.Cleaner public changelog page.
The
/changelogpage now groups releases by date instead of stacking version sections vertically. When several versions ship the same day, they collapse into one accordion bullet per release with the version number tagged inline. Less scroll, easier to find what shipped this week.
Fixed
Prompt injection in memory facts is now caught and flagged.
Fact text is screened against known injection patterns before storage. Suspicious facts are stored with
flagged_injection=trueand excluded from all LLM context rather than dropped — so "I want to disregard my doubts about grace" doesn't disappear, it just waits for review. Reinforcement updates also propagate the flag if a later extraction catches it.Concept staleness matching is more accurate.
The stale-concept flagging logic (which marks concepts for re-summarization after a session produces related facts) now uses a shared token-based matcher instead of a substring check. Short concept names (≤ 3 chars) no longer produce false positives. Same matcher used in both the pipeline and the cron so they can't diverge.
Profile regeneration is now concurrent-safe.
factsVersionwas set tofacts.lengthon every update, so two simultaneous regenerations could both read 5 facts and writefactsVersion: 5— making the second write look like a no-op when it wasn't. Now incremented atomically withCOALESCE(current, 0) + 1in SQL on both the INSERT and UPDATE branches.Database errors in Sentry now actually tell us what went wrong.
Postgres errors from the Neon serverless driver were arriving as "NeonDbError: null" with no SQLSTATE, no severity, no way to triage. The redactor strips message text for privacy (users write prayers); the categorical Postgres metadata never made it through Sentry's normalization. Two new tags now ride along on every NeonDbError event:
dbErrorCode(the 5-char SQLSTATE —42P01,23505, etc) anddbErrorSeverity(ERROR/FATAL/PANIC/ etc). When a database query fails in production, we'll know in seconds whether it's a missing table (migration drift), a duplicate key, a connection failure, or a timeout. Closes SELAH-SENTRY-D, unblocks triage on SELAH-SENTRY-C and SELAH-SENTRY-3 (same throw site, different captures viaerrorDigestmatching).Telegram companion back online after silent migration failure took down the sessions table.
relation "sessions" does not existwas killing every/api/telegram/webhookcall since the stream v2 deploy. Root cause: the migration runner's regex splitter (/;\s*(?:\n|$)/) shreddedDO $$PL/pgSQL blocks into invalid fragments. Neon's HTTP driver accepted them silently (returned 200, executed nothing), the runner marked all three migrations (0026, 0027, 0028) as applied, and thecompanion_sessions → sessionsrename never happened. Recovery migration0032_fix_sessions_schema.sqlapplies all the skipped DDL idempotently — rename, new columns, index,stream_entriestable,session_idforeign keys onstream_capturesandnotes.Companion warnings stop arriving as "<unlabeled>" events.
Sentry's
captureMessagecalls (the orphan-intent breadcrumb, the tool-loop hop budget exhaustion log, the final-response fallback warning) hand a static string toevent.message, which the redactor strips for privacy. Without a tag, every site looked identical on the dashboard. NewmessageOrigintag derives from a fixed prefix allowlist of literal strings our code emits —companion.orphan_intent,companion.hop_budget_exhausted,companion.final_response_fallback,ai.stop_reason,ai.schema.json_parse_failed,ai.schema.validation_failed,ai.job. Each captureMessage site now sorts to its own dashboard bucket. Closes SELAH-SENTRY-8.