selahdocs

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 .

April 16, 2026

v0.5.0New

The companion now reaches out to you.

Heartbeat Companion Nudges + Nightly Learning Loop

  • The companion now reaches out to you.

    Every 15 minutes, a Vercel Cron evaluates your recent notes, prayers, and captures with Claude Haiku. If there's something worth surfacing — a stale note, a forgotten prayer, a connection to the week's devotional theme — it sends you a Telegram message. Feels like a thoughtful friend, not a notification bot.

  • Smart nudge timing.

    You control active hours (default 8am–10pm) in Settings. Nudges respect a daily cap of 2 so they stay meaningful. Overnight windows work correctly — set 10pm–6am if you want late-night prompts.

  • Nudges that don't repeat.

    The system reads the last 7 days of nudges before asking the LLM to generate a new one. If you've already been reminded about Romans 8 three times this week, it won't ask again.

  • Heartbeat settings in the web app.

    The Settings page now has a Proactive Check-ins card. Toggle nudges on/off, set your active hours, and write custom instructions for the companion (e.g., "Only nudge me about Study-stage notes" or "Focus on prayer when I haven't prayed in 3 days").

  • Nightly companion learning loop.

    Every night at 2am, the companion reviews the past 24 hours of your activity — notes edited, captures saved, prayers engaged — and updates its understanding of you. Five facts about your patterns and interests stay current. Your companion profile is regenerated from those facts so every conversation starts with fresh context.

  • Activity-aware learning.

    The learning loop uses Haiku to reason over your recent activity digest. It can add new facts, update existing ones, and remove stale ones — so the companion's knowledge of you actually evolves rather than accumulating noise.

  • Telegram formatting fixed.

    Companion responses now render bold, italic, inline code, and links correctly in Telegram (HTML mode). This was a regression introduced in the prior release and is now fully restored.

Added

  • `src/lib/heartbeat.

    ts — context assembly, LLM evaluation, parseHeartbeatResponse, buildHeartbeatSystemPrompt`

  • `src/app/api/cron/heartbeat/route.

    ts — 15-minute Vercel Cron (GET), isWithinActiveHours, isNudgeCapReached`

  • `src/app/api/cron/companion-learn/route.

    ts` — nightly learning cron (GET), activity digest, fact maintenance

  • `src/app/api/heartbeat-preferences/route.

    ts` — GET/PUT/PATCH heartbeat preferences

  • `src/app/settings/heartbeat-card.

    tsx` — Proactive Check-ins settings card

  • `src/lib/companion-learning.

    ts — buildActivityDigest, runLearningLoop`, fact CRUD helpers

  • `src/lib/telegram.

    ts — sendHeartbeatNudge, markdownToTelegramHtml` (restored)

  • `drizzle/0015_heartbeat.

    sql — heartbeat_nudges table + companion_preferences` heartbeat columns

  • `drizzle/0016_companion_learning.

    sql — topFacts column on companion_preferences`

  • 5 new test files: 432 tests total (up from ~370)

April 15, 2026

v0.3.0 → v0.4.0New

Interview notes now save as full, editable study documents.

Full Interview Notes + Cross-Surface Session Navigation

  • Interview notes now save as full, editable study documents.

    When you say "save this" on Telegram or in the web companion at the end of an interview, the companion captures everything: the theological arc of the conversation (as a pull quote), all your significant insights (no 3-6 limit, no truncation), scripture references with context, your most powerful verbatim quotes, and any open questions to explore later. The saved note opens as a rich, editable TipTap document — not a read-only summary. Old notes keep working through a backward-compat read path.

  • Tap "View Note" from Telegram.

    After saving an interview, prep, or general note from Telegram, the companion now includes an inline "View Note" button that deep-links straight to the editable note in the web app. No more "I can't edit notes" confusion.

  • `/sessions` on Telegram.

    See your recent conversations across both web and Telegram with one command. Tap any session to jump to it on the web. Same agent, multiple surfaces — the session is unified.

  • "History" button on the web companion.

    The clock icon now has a visible "History" label so you can actually find the session drawer. The drawer itself already listed topics + dates; now new users know it exists.

  • Deep-link into any past session.

    Navigate to /companion?sessionId=X and the companion loads that conversation directly. If the session doesn't exist (typo, deleted, stale Telegram link), you get a clear error banner — "Couldn't find that conversation. Started a new chat." — not a silent empty chat.

  • Notes link back to their source conversation.

    The "View companion interview" button on an interview note now routes through the unified session URL, so you can jump from the saved note back to the conversation that produced it, continue thinking, and save again.

  • Telegram companion loop crash fixed.

    markdownToTiptap(undefined) no longer throws — empty input returns a valid empty-paragraph doc. parseLLMJson now recovers when the LLM wraps JSON output in prose. Both fixed the "Something went wrong" errors that were locking users out of their own conversations.

  • System prompt truth-up.

    The companion no longer believes falsehoods about its own capabilities. It knows notes are editable at /notes/[id], it knows it can save verbatim quoted content, and it knows to capture all insights without truncating for a 3-6 bullet limit.

  • Data integrity: `openQuestions` field drop fixed.

    Interview notes stored questions under the wrong field name, which caused every open-question section to render blank. Fixed with a dual-field read in the notes editor and the embeddings backfill cron — old notes now display their questions correctly, and re-embeddings don't silently lose questions text.

Prayer-Aware Companion

  • `/prayers` — your prayer board in one tap.

    Send /prayers in Telegram to see all your active, ongoing, and waiting prayers grouped by status. Each prayer gets inline [Answered] / [Waiting] / [Ongoing] buttons. Tapping a button updates the status in-place — the message edits itself and the buttons disappear so you can't accidentally retap.

  • `/note [text]` — save a note directly from Telegram.

    Send /note Romans 5:3 — suffering produces perseverance and it goes straight to your notes library with an auto-title and a deep link back to the note in the app.

  • Companion knows your prayer board.

    Every conversation now includes your most recent non-answered prayers as context. When you say "God answered my prayer about the job offer," the companion understands what you mean and can call update_prayer_status to mark it answered. It won't guess UUIDs — if it's unsure which prayer you mean, it asks by title.

  • Three new companion tools:

    read_prayers (filtered board lookup), update_prayer_status (mark answered/waiting/ongoing with an optional note), create_note (save a theological insight or study reflection directly from conversation).

  • Bug fix:

    /start, /help, /recent, /profile, /prayers, and /note no longer send a redundant 👀 acknowledgment reaction — the reaction from the message handler is sufficient.

April 14, 2026

v0.2.0New

End-to-end devotional pipeline.

Adaptive Daily Devotional System

  • End-to-end devotional pipeline.

    Users receive a fresh scripture-based devotional every day at their chosen local hour, delivered via Telegram and/or surfaced in the companion chat. The system is week-aware: Monday opens a theme, Friday closes with reflection, mid-week entries develop it.

  • `devotional_weeks` + `devotional_entries` tables

    (migration 0011) — week records track theme, proposedNextTheme, and week-end reflection; entry records carry all rendered fields (dayRole, scriptureRef, scriptureText, opening, reflection, prayerPrompt, question) plus Telegram delivery metadata.

  • `src/lib/devotional.ts`

    — ensureTodaysDevotional is the core coordinator: claim a pending slot, call the role-specific LLM tool (theme_open / reflection_close / middle), write the result. Idempotent — concurrent callers compete for a pending row; only the first writer wins. LLM output truncated via v100()/txt() helpers before every DB write. tool_choice: { type: "tool", name } forces the correct role-specific tool to fire.

  • `POST /api/cron/devotional`

    — hourly Vercel cron that sweeps stale pending entries (> 10 min old), finds users at their delivery hour, generates entries, and sends to Telegram. Midnight hour normalized (% 24) for V8 Intl.DateTimeFormat quirk. Query capped at 500 users per run with a log warning when the limit is hit.

  • `POST /api/cron/devotional-extract`

    — nightly cron that extracts week-level reflection from completed entries and proposes next week's theme.

  • `GET /api/devotional`

    — returns today's devotional for the authenticated user (generating on first request).

  • `PATCH /api/devotional-preferences`

    — saves timezone, delivery hour, and devotional_enabled flag. IANA timezone validated via Intl.DateTimeFormat; isValidIanaZone exported for testability.

  • DevotionalMessage UI

    (src/components/companion/DevotionalMessage.tsx) — inline companion card rendering scripture reference, opening, reflection, prayer prompt, and reflection question with gold-tinted scripture styling per DESIGN.md.

  • DevotionalCard settings

    (src/components/settings/DevotionalCard.tsx) — timezone picker (20 common zones), hourly delivery selector, enable toggle, "Send now" preview button.

  • Daily Rhythm onboarding step

    — new step 4 in the onboarding flow introducing the devotional, with a scripted companion demo that walks through the companion philosophy using pre-scripted message pairs.

  • Notes page badge

    — devotional note type now renders a gold "Devotional" badge in the notes list.

  • `isDevotionalStructured` type guard

    — replaces a blind cast in the companion page; validates dayRole shape before rendering the devotional card inline.

  • Telegram delivery

    — sendDevotional renders devotionals as clean Markdown messages; atomic update sets deliveredTelegramAt only when IS NULL to prevent double-sends.

  • 15 new pure-function tests

    (269 total) — chunkMessage, verifyWebhookSecret, isValidIanaZone with full branch coverage.

  • Four adversarial-review fixes:

    midnight normalization, scale cap log warning, reflection_close idempotency guard, handleConnect single body parse.

New user discovers the bot → signup → back to Telegram.

Telegram Signup (Phase 1)

  • New user discovers the bot → signup → back to Telegram.

    Previously, a Telegram user with no Selah account hit a dead end: the bot sent them to /settings/telegram (7 steps to actually get connected). Now the bot creates a one-time signup invite, sends a single "Sign up to Selah" button, the website binds the chat on signup, and the user lands back in Telegram with the bot already greeting them.

  • `telegram_signup_invites` table

    (migration 0010) — 32-byte url-safe random token, 24h TTL, single-use, tracks chatId + Telegram username/first name. Inverse direction of the existing telegram_link_codes flow (website → bot).

  • `src/lib/telegram-signup.ts`

    — createSignupInvite, findOpenInviteForChat, findSignupInvite, claimSignupInvite, pushTelegramWelcomeIfEligible, buildWelcomeMessage. The claim function handles happy path, invalid/expired/consumed tokens, and cross-user guard with idempotent self-reclaim.

  • `POST /api/telegram/claim`

    — auth-required. Body { token }. Binds the chat to the authenticated user and returns botDeepLink from TELEGRAM_BOT_USERNAME. Maps claim errors to 400 (invalid/expired/consumed) and 409 (chat_linked_to_another_user).

  • Webhook unlinked-user branch

    (src/app/api/telegram/webhook/route.ts) — replaced the dead-end "go to settings" message with a personalized welcome + "Sign up to Selah" button. Reuses an existing open invite if the user re-messages instead of creating a fresh row each time.

  • Sign-up + sign-in pages

    (src/app/sign-up/page.tsx, src/app/sign-in/page.tsx) — read ?via=telegram&t=TOKEN on mount, stash the token in sessionStorage, show a "Signing up from Telegram" banner. After successful auth, call /api/telegram/claim immediately. Sign-up routes to /onboarding?via=telegram; sign-in (already-onboarded users) redirects straight to the bot via window.location. Wrapped in Suspense for useSearchParams() per Next.js 16.

  • Onboarding flow

    (src/app/onboarding/onboarding-flow.tsx) — detects via=telegram from the URL, hides the "How did you find Selah?" question, forces signup_source = "telegram" in the PATCH (including on Skip), and swaps the step 3 CTA from "Enter Selah" to "Open Telegram." On completion, reads telegramBotDeepLink from the response and redirects via window.location.

  • `/api/onboarding` PATCH

    now returns telegramBotDeepLink when signup_source === "telegram" and TELEGRAM_BOT_USERNAME is set. Also triggers pushTelegramWelcomeIfEligible on the first completion transition (gated behind !before.completed). Non-fatal if the push fails.

  • Welcome push

    — 3-tier context-aware greeting: anchors on first capture content if saved, else on current focus, else falls back to a generic "share a thought" prompt. All under 2 sentences per DESIGN.md's Telegram response rules. No emoji in message text (DESIGN.md rule).

  • Memory fact seeding

    remains unchanged: signup_source = "telegram" is analytics, not memory, so it isn't written to memory_facts.

  • Claim-immediately-after-signup architecture

    — the chat → user link is established before onboarding begins. If the user abandons mid-onboarding, they can return to Telegram and keep using the bot; onboarding resumes on their next web visit.

  • Tests (38 new, 218 total):

    20 helper tests (telegram-signup.test.ts), 11 claim-route tests (telegram-claim-route.test.ts), plus updates to the onboarding-route suite covering the welcome-push gate and the bot deep link response shape.

Onboarding — Companion Context

  • Step 1 redesigned as a single dense screen

    — role (required-ish) plus four optional companion-context questions: Bible translation, what they're studying right now, theological tradition, and how they found Selah. All optional fields can be skipped with one button.

  • Translation preference

    — 9-option pill row (ESV, NIV, NLT, KJV, NASB, CSB, NRSV, MSG, Other), defaulted to ESV. Stored on user_onboarding.translation.

  • Tradition

    — 11-option pill row (Reformed, Wesleyan, Pentecostal, Baptist, Anglican, Lutheran, Catholic, Orthodox, Non-denominational, Still exploring, Other). Framed sensitively: "so we don't push contradictory stuff." Stored on user_onboarding.tradition.

  • Current focus

    — single-line text input. "What are you studying right now?" → free text up to 500 chars. The single biggest unlock for first-session value.

  • Signup attribution

    — "How did you find Selah?" pill row (Friend, Social, Search, Blog/podcast, Other). Stored on user_onboarding.signup_source. Analytics-only, NOT memory.

  • Memory fact seeding on completion

    — when a user finishes onboarding, role/translation/tradition/currentFocus are written into memory_facts (category: life_context / preference / theology, confidence 0.95, sourceType manual). The companion sees these from message #1 instead of cold-starting from zero. Gated so re-entry doesn't double-seed; non-fatal if seeding fails.

  • `PillRow` reusable component

    inside the onboarding flow with proper radiogroup/radio ARIA roles, 44px min touch targets, sage active state.

  • Migration `0009_user_onboarding_context.sql`

    — additive ALTER TABLE for the four new columns. Idempotent.

  • 30 new tests added (180 total passing)

    — helper coverage for the new fields, EAV mapping in seedMemoryFactsFromOnboarding, route validation for every new field's enum + bounds, the seeding gate (fires once, no re-fire on re-entry), and the non-fatal error path.

Signup + Onboarding

  • Dedicated `/sign-up` page

    — New optimized signup form replaces the inline toggle on /sign-in. Name is optional. Password has a show/hide toggle. Clear value prop, trust microcopy ("Free. No credit card. Takes 30 seconds."), and friendly error states.

  • Sign-in focused

    — /sign-in is now sign-in only with a clear link to /sign-up for new users. Matches signup-flow-cro best practices: one page, one job.

  • New user onboarding flow

    — /onboarding is a 3-step guided experience new users hit immediately after signup:

    1. Welcome + role — Pick how you use scripture (pastor, small group leader, student, personal). Skippable. Feeds companion personalization later.
    2. First capture (aha moment) — Guided textarea with an example placeholder. Saves a real stream capture so the user sees their Home populated immediately. Supports Cmd/Ctrl+Enter to submit.
    3. You're ready — Warm recap of what Selah can do (capture, companion, notes, prayer). Single CTA: "Enter Selah."
  • Onboarding state in DB

    — New user_onboarding table tracks role, first capture, and completed_at. Home page redirects any user with completed_at IS NULL to /onboarding, so returning users who bailed mid-flow resume where they left off.

  • `/api/onboarding` route

    — GET reads status (creates row lazily), PATCH updates role, saves first capture, or marks complete. All fields optional so the client can update progressively.

  • Proxy matcher update

    — /sign-up is now excluded from Neon Auth proxy alongside /sign-in so unauthenticated visitors can reach it.

April 13, 2026

v0.2.3

Upload a profile photo

Profile

  • Upload a profile photo

    — New photo section at the top of Settings → Public profile. Pick an image and it's resized, cropped to a square, and saved automatically. Change or remove anytime. JPG, PNG, and WebP welcome.

  • Branded default avatar

    — Accounts without a photo now show a warm soft-gold mark with a sage circle in the middle, matching the design system. Same mark on shared notes, the settings page, and anywhere the avatar appears.

  • Avatar on the settings page

    — Your avatar now sits beside your name and email in the Account card, and beside the Public profile link so it's obvious where to change it.

  • Consistent across surfaces

    — Shared note pages, settings, and the profile editor all use the same Avatar component, so the default and uploaded states look identical everywhere.

April 11, 2026

v0.2.0 → v0.2.2New

Tables in the editor

Knowledge Graph

  • Editable concept summaries

    — Click any summary on a concept's detail page to edit it inline. Click outside to auto-save. Write your own or hit the sparkle button to generate one with AI instantly (no waiting for background jobs).

  • "Generate Summary" button

    — One click to create a summary from your linked notes, captures, and teachings using AI. Available on concepts with no summary and inside the editor. 300 character limit keeps summaries tight.

  • Fixed stuck "updating" concepts

    — Concepts showing "updating" forever are now fixed. The background summarizer was being starved by the extraction pipeline. Summaries now process first, with a time budget that drains the full queue.

  • Smarter stale detection

    — The companion no longer mass-flags short-named concepts like "God" or "Sin" for summary refresh. Stale matching uses in-memory lookup instead of per-fact database queries.

  • Scripture parsing fix

    — "1 John 4:8", "2 John", and "3 John" references now parse correctly (trailing space in the regex key was eating the chapter separator).

Prayer Board

  • Inline-editable titles

    — Click any prayer title to edit it in place. Auto-sizing textarea preserves line wrapping. Enter saves, Escape cancels, blur saves. Optimistic updates with rollback.

  • Cleaner card layout

    — "Move to" buttons and delete icon collapse behind hover on desktop, always visible on mobile. Trash icon moved to top-right corner. Cards at rest show just the title and timestamp.

  • Loading skeleton

    — Card-shaped placeholders while prayers load instead of a blank board.

  • Keyboard shortcut hint

    — N badge next to "Add Prayer" button on desktop.

  • Serif page heading

    — "Prayer Board" now uses Cormorant Garamond per the design system.

  • API: title updates

    — PATCH endpoint now supports updating prayer titles alongside status changes.

Design System

  • Added **Keyboard Shortcut Hints** pattern to DESIGN.

    md (styling, placement, rules).

  • Added **Loading Skeletons** pattern to DESIGN.

    md (card skeletons, anti-spinner rule).

  • New decisions logged: hover-only card actions, shortcut hints, skeleton convention.

Notes

  • Tables in the editor

    — Type /table to insert a 3x3 table with a header row. Columns are resizable by dragging. Tables styled to match the warm journal aesthetic (sage selection highlights, warm-white headers).

  • Paste markdown, get formatted content

    — Paste a markdown table, heading, or list into the editor and it auto-converts to rich text. No more raw | pipes or asterisks.

  • Selection-based cleanup

    — Select text in a note and click the sparkle (✦) in the floating toolbar to format just that section with AI. No selection = format the whole document (as before). Long notes no longer lose content during cleanup.

  • Slash commands fixed

    — The / menu now appears reliably when you type / at the start of a line.

Companion

  • Formatted interview transcripts

    — AI and user messages in companion interviews now render bold, italic, lists, headings, blockquotes, and tables instead of showing raw markdown like this.

Fixes

  • Reformat no longer truncates notes over 8K characters (raised to 25K with clear error for very long notes).
  • Reformat errors now show in the UI instead of silently failing.
  • Table CSS aligned to design system (4px grid spacing, `tabular-nums`, accessible `scope="col"` headers).
  • Defensive HTML escaping in companion transcript rendering.

April 10, 2026

v0.1.0New

`/prep` command

Companion

  • `/prep` command

    — Type /prep [topic] to start a Socratic interview for sermon, Bible study, or devotional prep. The AI interviews you (8+ exchanges before it lets you build), then formats your words into a structured teaching note with inline enrichment tags ([Historical], [Greek/Hebrew], [Cross-ref], [Cultural], [Illustration]). Your voice stays central. Saved as a note you can edit in the Tiptap editor.

  • Backend-driven session mode

    — The companion tracks what mode you're in (prep, interview, pray) server-side. No more frontend guessing.

  • Structural tool gating

    — The AI can't skip the interview. The save_prep tool is withheld until you've had enough exchanges. When you say "done," the AI is forced to call the tool instead of just saying "Building now."

  • Reading level matching

    — Prep output matches your vocabulary and tone. If you write simply, the note reads simply.

  • Full-page build animation

    — When the AI builds your prep note, a loading overlay shows progressive status messages instead of a generic spinner.

  • Socratic interview mode

    — Deep-dive conversations with pull-quote transcript UI.

  • Capture and note attachments

    — Attach your stream captures and notes to companion messages as context.

  • Session persistence

    — Leave and come back. Your conversation picks up where you left off.

  • `/pray`, `/interview`, `/help` commands

    — Quick-access slash commands for common actions.

Stream

  • Scripture auto-detection with expandable pills.
  • Chronological feed with timestamps.
  • Promote stream entries to notes (new or append to existing).

Notes

  • Tiptap rich text editor with slash commands (`/` menu: headings, lists, quote, divider).
  • Selection toolbar (floating: bold, italic, highlight, headings, list, quote).
  • Soft-gold text highlighting.
  • Properties drawer with stage stepper (Capture → Study → Finished) and type tags.
  • Auto-save with 800ms debounce.
  • Filter tabs: All, Capture, Studying, Finished, Threads, Teaching, Prep.
  • Scripture block extension (insertable via `/` menu).

Prayer Board

  • Kanban columns: Active, Waiting, Answered, Ongoing.
  • Drag-and-drop between columns.

Knowledge Graph

  • Auto-extraction pipeline: concepts, edges, and relationships from your notes and captures.
  • Knowledge dashboard (`/knowledge`) with type filters and manual sync.
  • Concept detail pages with delete and merge UI.
  • Auto-generated concept summaries.

Search

  • Postgres full-text search with GIN indexes.
  • Global search bar in sidebar.

Import

  • Import from Markdown, TXT, CSV, DOCX, and PDF files.
  • Proper markdown-to-Tiptap conversion (headings, lists, bold, etc.

    ).

Auth

  • Neon Auth (managed Better Auth) with middleware route protection.
  • Sign in/sign up page.