# Changelog

## 0.42.0 — 2026-03-22

Distillate is now an autonomous research platform. Describe a hypothesis, and an AI agent runs experiments for you — training models, tuning hyperparameters, and reporting results. A desktop GUI lets you watch the frontier improve in real time, steer the agent, and compare runs across projects.

### Highlights

- **Autonomous experiments**: describe what you want to test, an agent runs it. Time-budgeted sessions with live metric tracking, automatic frontier detection (`best`/`completed` runs), and structured insight extraction
- **Desktop IDE**: Electron app with four tabs — Control Panel (frontier chart + live session status), Session (terminal), Results (run grid + insights), Prompt (PROMPT.md editor). Context-aware chat with Nicolas throughout
- **Cloud & email** (optional): sign up with `--email` to get experiment reports with embedded charts, daily paper suggestions, and weekly digests. Papers and experiments sync to the cloud for backup
- **One-click onboarding**: install to live experiment in 60 seconds — new users see CTAs to launch a demo or connect their paper library
- **Paper-experiment integration**: link papers to experiments, discover relevant papers, credit papers via `inspired_by` on runs — what you read informs what you try
- **Desktop IDE**: four-tab Electron app (Control Panel, Session, Results, Prompt) with context-aware chat, stop generation, and full highlight display
- **No API key needed**: runs entirely through your Claude Code subscription (Max or Pro)

### New Features

- **Autonomous experiments**: describe a hypothesis, Nicolas spawns an agent to test it. Time-budgeted sessions, live metric tracking, and automatic insight extraction
- **Alchemy skill system**: 9 slash commands — The Laboratory (`/survey`, `/conjure`, `/steer`, `/assay`, `/distill`), The Library (`/brew`, `/forage`, `/tincture`), and `/transmute` to bridge papers and experiments
- **Onboarding flow**: sidebar CTA, welcome screen CTA, chat suggestion — all lead to a 3-step scaffold/GitHub/launch flow that gets you to a running experiment in under a minute
- **Library setup wizard**: "Connect your library" CTA in papers sidebar → Zotero credentials → reading surface choice (reMarkable / iPad / any device) → sync
- **Paper-experiment cross-references**: linked papers in experiment detail, linked experiments in paper detail, both clickable for navigation
- **`discover_relevant_papers` tool**: keyword-matches your paper library against experiment goals, returns candidates with relevance reasons
- **`inspired_by` on runs**: `conclude_run` accepts a paper reference, auto-links it to the project
- **Stop generation**: red stop button interrupts Claude via SDK, Nicolas says "Stopped. What should I do instead?"
- **Context-aware suggestions**: chat pills above the input update based on context — experiment (steer, analyze, compare) or paper (summarize, experiment ideas, similar papers)
- **Link handling**: external links open in system browser, arXiv/DOI links show popup with "Open in browser" or "Add to library queue"
- **Windows support**: `install.ps1` script, `npm run install:win`, homepage auto-detects OS via user agent
- **`purge_hook_runs` tool**: clean up accumulated noise from manual script executions
- **49 MCP tools**: paper library, experiments, bridge, and cleanup tools via the Distillate MCP server
- **Monorepo**: desktop app, MCP server, and Agent SDK in a single repository
- **GitHub integration**: public repos by default (`distillate-xp-` prefix), GitHub flare in control panel

### Desktop IDE

- **Control Panel**: metric chart with log scale toggle, export to PNG, session timer, goal chips
- **Session tab**: embedded xterm.js terminal attached to the running Claude Code agent
- **Results tab**: runs grid with research insights (key breakthrough, lessons learned, dead ends)
- **Prompt tab**: view and edit PROMPT.md with markdown rendering and syntax highlighting
- **Three experiment states**: running (green triangle), ready (purple circle), paused (gray square)
- **Chat UX**: purple presence dot on Nicolas messages, thinking spinner after tool completion, tool subtitles showing what's happening (search terms, project names, etc.)
- **Full highlights**: paper detail view shows complete highlights (no truncation)
- **Keyboard shortcuts**: Cmd+R refresh, Cmd+1-4 switch tabs, Cmd+E toggle sidebar, Cmd+K toggle chat

### CLI

- **Onboarding**: first-use welcome with two clear paths — conjure an experiment or connect Zotero via `/init`
- **Contextual suggestions**: hints adapt to what you have (papers, experiments, or both)
- **Rotating tips**: one random skill tip per session
- **`--report`**: reading insights dashboard — lifetime stats, weekly velocity, topic breakdown
- **`--export-state` / `--import-state`**: backup and restore tracked papers and reading history

### Bug Fixes

- **Scanner spurious runs**: hooks fired on all Bash commands, creating ghost runs. Now gated by `DISTILLATE_SESSION` env var — only fires inside Distillate-managed tmux sessions
- **`compare_projects` null values**: skipped non-"keep" runs and used wrong direction for loss metrics. Now includes all non-discarded runs with direction-aware best (min for loss, max for accuracy)
- **Unhashable list crash on delete**: run names in set comprehensions crashed with list-valued hyperparameters. Defensive `str()` wrapping
- **Highlights truncated at 3000 chars**: desktop paper detail used the MCP tool's truncation limit. Now reads full highlights directly
- **Links opened inside Electron app**: no navigation interception. Added `will-navigate` + `setWindowOpenHandler` guards
- **Control panel disappeared on select**: `isReady` variable removed during refactor but still referenced, crashing the render
- **Tab hijack on polling**: 15-second session poll re-rendered the detail pane, snapping back to Control Panel. Polling now only updates sidebar
- **Sync 501 errors in DevTools**: `triggerCloudSync()` called `.json()` on non-ok responses
- **Dock icon too large in dev mode**: now uses `.icns` with proper resolutions

### Cloud & Email

- **Cloud state sync**: papers and experiments sync to the Distillate cloud API via granular endpoints with incremental pull (`?since=` watermark) and additive merge
- **Run-level merge**: cross-device sync merges individual runs — decisions advance monotonically (best > completed > crash), metrics and metadata fill gaps
- **Email notifications**: experiment reports on session completion (with embedded chart), daily paper suggestions, and weekly digest — each toggled independently
- **Auto sync triggers**: background sync after first app load, automatic push after experiment sessions end
- **Supabase URL auto-derived**: no manual configuration needed — the Supabase project ref is extracted from the anon key JWT

### Experiment Decisions (best/completed)

- **Auto-detected decisions**: `conclude_run()` automatically marks runs as `best` (frontier-improving) or `completed` — agents no longer decide keep/discard
- **State migration v1→v2**: existing experiments re-evaluated chronologically to compute best/completed from the key metric frontier
- **Backward compatible**: old `keep`/`discard` in runs.jsonl parsed as `completed`; `best` determined from state migration
- **Commit format**: `[best]` prefix on frontier-improving runs — e.g. `[best] baseline CNN: f1=0.42`

### Desktop Improvements

- **Chart Y-axis**: scales to best runs only (discarded outliers no longer squish the frontier flat)
- **Log-scale ticks**: clean rounded values when data spans less than one decade
- **Sidebar stability**: experiments sorted by `added_at` instead of `last_scanned_at` (no more reordering on reload)
- **Faster sidebar**: removed auto-rescan from experiment list — state.json is authoritative, rescans only on session end and manual reload
- **Session status**: "Agent working — analyzing runs" instead of misleading "awaiting instructions"
- **Enrichment prompts**: cleaner output — one-sentence breakthroughs, no ALL CAPS, no Greek letters

### CLI Improvements

- **`--connectors`**: check status of all integrations (Zotero, email, Obsidian, reMarkable) at a glance
- **`--email`**: interactive email management with per-type toggles
- **`--sync-state`**: manual cloud sync trigger
- **Security**: CORS tightened to localhost only, auth token verification on sensitive endpoints

### Migration from 0.6.x

- **No breaking changes** — existing paper libraries carry over unchanged
- **`distillate` now requires Claude Code** — install from docs.anthropic.com/en/docs/claude-code
- **Desktop app is optional** — the CLI works exactly the same as before

## 0.6.0 — 2026-02-25

### New Features

- **Read on any device**: no longer requires a reMarkable tablet — read and highlight in the Zotero app on iPad, desktop, Android, or any device. Pick your reading surface during `--init` setup. reMarkable remains fully supported.

### Improvements

- Agent: dim magenta for verbose tool output, response truncation fix
- Email digest: trending section with top 3 papers, mobile-friendly layout
- Init wizard: WebDAV configuration step, reading surface choice
- Landing page: reMarkable now optional, "any device" messaging
- Windows: `--schedule` shows Task Scheduler instructions instead of crashing
- Lazy rmscene imports: Zotero-only users don't need rmscene/rmapi installed
- WebDAV fallback: catches all HTTP errors, visible retry output, manual upload detection

### Migration from 0.5.x

- **Newsletter signup** — the init wizard (`--init`) now offers an optional email signup at the end

## 0.5.2 — 2026-02-25

### Improvements

- **HuggingFace summary fallback**: papers get a real one-liner even without a Claude API key, using HF's AI-generated summaries
- **Email trending**: default limit tightened to 3 papers

## 0.5.1 — 2026-02-23

### Bug Fix

- **WebDAV PDF downloads broken since 0.4.4**: `get_pdf_attachment()` only matched `imported_file` and `imported_url` link modes, missing WebDAV's `linked_url` attachments. Papers got stuck as "Awaiting PDF" instead of downloading.

## 0.5.0 — 2026-02-24

Interactive agent mode — distillate becomes a research assistant.

### New Features

- **Nicolas — AI research assistant**: `distillate` now launches an interactive REPL ("Nicolas") powered by Claude. Search papers, compare findings, get reading suggestions — all in natural language.
- **Add papers from the REPL**: give an arXiv ID and it's added to Zotero, enriched with metadata, and synced on next run.
- **HuggingFace Daily Papers**: trending research with GitHub repo links, AI summaries, and community votes.
- **Cross-paper synthesis**: ask Nicolas to compare or synthesize across multiple papers in your library.
- **Refresh metadata**: agent can fix metadata gaps on existing papers via Semantic Scholar.
- **Conversation memory**: sessions persist locally for cross-session context.

### Migration from 0.4.x

- **`distillate` now opens the agent REPL** — use `distillate --sync` for the previous sync-only behavior
- **Optional extras removed** — `[ai]`, `[email]`, and `[all]` install extras are gone. `pip install distillate` (or `uv tool install distillate`) now includes everything

## 0.4.4 — 2026-02-21

Paper index numbers, terminal colors, and reliability fixes.

### Features

- **Paper index numbers**: every paper gets a stable `[index]` shown in `--status`, `--digest`, `--suggest`, `--list`, and emails — use it to target papers in commands
- **Paper lookup by index, citekey, or title**: `--reprocess 3`, `--remove kindel`, `--refresh-metadata "DynaSpec"` all work
- **Single-paper refresh**: `--refresh-metadata` now accepts an optional query to refresh just one paper
- **PDF subfolder**: annotated PDFs now saved to `Saved/pdf/` (configurable via `PDF_SUBFOLDER`), keeping notes and PDFs separate — auto-migrates existing files
- **S2 author backfill**: papers with unknown authors are enriched from Semantic Scholar, with automatic citekey regeneration
- **Terminal colors**: bold bright-white titles on dark terminals, dim gray metadata lines — TTY-aware with dark/light background detection

### Bug Fixes

- **Awaiting PDF retry**: re-checks Zotero children when the stored attachment returns 404 — fixes papers where the user adds a PDF after initial import
- **Missing years in citekeys**: extracts year from DOI patterns (e.g. `chemrxiv-2026-xxx`) when Zotero has no date; uses S2 `year` field as fallback when `publicationDate` is empty
- **Title cleaning**: strips `: JournalName` suffixes from Zotero web clipper titles (e.g. "Title: Neuron" → "Title")
- **Citekey for S2 authors**: handles "First Last" name format (not just "Last, First") when generating citekeys from Semantic Scholar data
- **Author preservation**: `--refresh-metadata` no longer overwrites S2-filled authors with empty Zotero creators
- **Refresh reporting**: citekey changes for non-processed papers (queue, awaiting) are now reported instead of showing "up to date"

## 0.4.3 — 2026-02-19

### Bug Fixes

- **Email stats missing pages/words**: the main sync loop now auto-pushes state to the Gist after processing papers, so GH Actions emails have fresh reading stats (page counts, highlight word counts)

## 0.4.2 — 2026-02-19

### Improvements

- **Suggestion compute-once**: `--suggest-email` now calls Claude at most once per day — subsequent runs reuse cached suggestions (from local state or Gist) and just re-send the email. Saves API cost on retries and manual re-runs

## 0.4.1 — 2026-02-19

### Bug Fixes

- **Traceback logging on crash**: unhandled exceptions now log the full traceback to the log file / CI output, instead of only showing the one-line message
- **CI uv cache warning**: disabled uv cache in the email workflow to silence spurious "no lockfile" warnings

## 0.4.0 — 2026-02-18

Handwriting OCR, personalized summaries, and Zotero collection filtering.

### Features

- **Handwriting OCR via Claude Vision**: handwritten margin notes are transcribed using Claude Haiku — renders ink onto the PDF page for context, then sends to the Vision API for accurate OCR
- **Personalized AI summaries**: reader's handwritten margin notes are fed to the summarizer, so AI-generated insights prioritize what *you* found interesting (not just what the paper says)
- **Zotero collection filtering**: scope Distillate to a specific Zotero collection (e.g. "To Read") via `ZOTERO_COLLECTION_KEY` — only papers in that collection get picked up
- **Collection picker in init wizard**: `--init` now lists your Zotero collections and lets you pick one to scope to

### Improvements

- **Landing page restructure**: separated core features ("Built in") from optional extensions ("Plug in what you need")

### Removals

- **Apple Vision / Pillow dependencies**: handwriting OCR now uses Claude Vision instead — removed `pyobjc-framework-Vision` and `Pillow` optional dependency group

## 0.3.3 — 2026-02-18

Handwritten notes on PDFs, Windows compatibility, and Zotero WebDAV support.

### Features

- **Ink layer on PDFs**: handwritten strokes from reMarkable are now rendered onto the annotated PDF, preserving your margin notes and annotations alongside highlights
- **reMarkable Paper Pro support**: auto-detects Paper Pro's 227 DPI coordinate system and maps ink/highlights accurately (classic RM1/RM2 also supported)
- **Typed notes from reMarkable**: keyboard-typed text on the reMarkable is extracted and included in Obsidian notes under "Notes from reMarkable"
- **Zotero WebDAV fallback**: users storing attachments via WebDAV can now download PDFs automatically — configure `ZOTERO_WEBDAV_URL`, `ZOTERO_WEBDAV_USERNAME`, `ZOTERO_WEBDAV_PASSWORD`
- **Auto-promote from suggestions**: papers suggested via `--suggest` that are later added to Zotero are auto-promoted out of the suggestion pool

### Bug Fixes

- **PDF highlights invisible in Obsidian**: fixed SYNC_HIGHLIGHTS overwriting annotated PDFs with originals, making highlights disappear
- **Windows rmapi path leak**: `rmapi put` on Windows could use the full temp path as the document name — now detected and renamed automatically
- **Windows UTF-8 encoding**: all file I/O now specifies `encoding="utf-8"` and `newline="\n"` to prevent corruption on Windows (thanks @davidlukacik!)
- **Tag pills in emails**: abbreviated long category names (e.g. "Computer Science" → "CS"), smaller pill size
- **Email footer overflow**: condensed stats labels for mobile readability
- **Suggestion emails include already-read papers**: `_sync_tags()` now marks papers as processed when Zotero tag is `read`
- **Year frontmatter garbled**: dates like "8 September 2024" were parsed as "8 Se" — now extracts 4-digit year correctly from any format

### Removals

- **`--dry-run`**: removed vestigial flag that added maintenance burden without clear user value

## 0.3.1 — 2026-02-17

UX fixes, CI hardening, and documentation improvements.

### Bug Fixes

- **Unknown CLI flags detected**: `distillate --foo` now prints an error instead of silently triggering a full sync
- **TTY output for `--reprocess`, `--dry-run`, `--backfill-s2`**: these commands now print to the terminal instead of silently logging to file
- **Tag pills in suggestion emails**: topic tag pills are now actually rendered (were silently skipped)
- **Queue health on empty queue**: no longer shows "oldest: 0 days" when no papers are waiting
- **`--send-digest` feedback**: prints "digest not sent" when no papers processed recently instead of returning silently
- **Digest email mobile links**: paper titles link to web URLs (works on any device) with Obsidian as a secondary link

### Improvements

- **CI matrix**: added Python 3.11 and 3.13 (matching pyproject.toml classifiers)
- **Publish safety gate**: PyPI publish workflow now runs tests before building
- **`.env.example`**: added `SYNC_HIGHLIGHTS` and uncommented `OBSIDIAN_VAULT_NAME`
- **README config table**: added `OBSIDIAN_VAULT_NAME` setting
- **`pyproject.toml`**: added Changelog URL to project URLs

## 0.3.0 — 2026-02-17

Smart metadata enrichment from Semantic Scholar.

### Features

- **Smart metadata from Semantic Scholar**: auto-completes missing publication dates, venues, and citation counts from Semantic Scholar when Zotero data is incomplete
- **Citekey regeneration**: when Semantic Scholar fills a missing date, the citekey is regenerated (e.g. `liu_embeddings` → `liu_embeddings_2024`) and all files are renamed automatically
- **`--refresh-metadata` with S2 enrichment**: re-queries Semantic Scholar for papers missing dates or S2 data, in addition to re-fetching from Zotero

### Improvements

- **Landing page copy refresh**: new ASCII flow ending, section subtitles ("Save. Read. Highlight. Distill."), redistributed copy

## 0.2.3 — 2026-02-17

Citekey rename, metadata refresh, and naming consistency fixes.

### Features

- **Citekey rename on metadata sync**: when a paper's citekey changes in Zotero (e.g. after adding a publication date), Distillate renames note + PDF files, updates reading log wikilinks, and PATCHes Zotero linked attachments — no orphaned files or broken links
- **`--refresh-metadata`**: new command to re-extract metadata from Zotero for all tracked papers, with verbose progress output — useful for one-time migrations and fixing stale citekeys
- **Title sync**: title changes in Zotero propagate to note frontmatter and reading log entries
- **Zotero PATCH helpers**: `update_obsidian_link()` and `update_linked_attachment_path()` update existing Zotero attachments in place (safer than delete + recreate)

### Bug Fixes

- **Citekey date parsing**: `_generate_citekey()` now extracts 4-digit years from any date format (`10/2024`, `12 February 2026`, `2024-10-15`) — previously only worked with `YYYY-*` format
- **Inbox PDFs use citekey**: `save_inbox_pdf()` and `delete_inbox_pdf()` now use citekey-based filenames, consistent with Saved folder naming
- **Frontmatter title updates**: `update_note_frontmatter()` now syncs the `title`, `citekey`, and `pdf` fields
- **Obsidian Bases template v4**: fixed sort syntax and added `file.ext == "md"` filter to exclude PDFs from Base view

## 0.2.2 — 2026-02-17

Bug fixes, safety improvements, and dead code cleanup.

### Bug Fixes

- **`distillate --version`**: was stuck at "0.1.7" — now reads version from package metadata
- **`date_read` frontmatter**: papers processed in the main sync pipeline now use the actual processing date instead of defaulting to today
- **`delete_attachment("")` crash**: papers fetched from arXiv (no Zotero PDF) no longer trigger an invalid API call when `KEEP_ZOTERO_PDF=false`
- **Highlight OCR crash**: `_recover_pdf_text` no longer crashes when a highlight normalizes to an empty string (e.g. pure hyphens)
- **Double-sleep on Zotero 429**: when Zotero sends a `Retry-After` header, we no longer also apply the exponential backoff delay
- **Citekey change detection**: Better BibTeX citekey changes are now picked up by metadata sync

### Safety Improvements

- **Annotation create-before-delete**: Zotero highlight annotations are now created before deleting old ones, preventing data loss if the POST fails mid-batch
- **Missing end marker protection**: if `<!-- distillate:end -->` is accidentally deleted from a note, Distillate warns and appends instead of silently deleting everything after the start marker

### Cleanup

- **Removed dead `_themes()` code**: removed `_themes()`, `generate_monthly_themes()`, `create_themes_note()`, and `send_themes_email()` — unreachable since v0.1.7
- **Public API functions**: `zotero_client.build_note_html()` and `remarkable_client.sanitize_filename()` are now public (were private but called cross-module)
- **Removed duplicate migration calls**: `ensure_dataview_note()`, `ensure_stats_note()`, `ensure_bases_note()` no longer run inside the per-paper loop (already run at sync start)
- **Module-level marker constants**: `MARKER_START` and `MARKER_END` promoted to module-level constants in `obsidian.py`

## 0.2.1 — 2026-02-17

### Bug Fixes

- **Awaiting PDF retry**: papers stuck in `awaiting_pdf` status are now correctly retried when a PDF attachment appears in Zotero

## 0.2.0 — 2026-02-16

Zotero Round-Trip: highlights flow back from reMarkable to Zotero, citekey-based naming, and Obsidian plugin compatibility.

### Features

- **Zotero highlight back-sync**: highlights made on reMarkable are written back to Zotero as searchable annotations — visible in Zotero's built-in PDF reader and compatible with the Zotero iOS/Android apps
- **Citekey-based file naming**: notes and annotated PDFs use Better BibTeX citekeys (e.g. `einstein_relativity_1905.md`) for compatibility with the Obsidian Zotero Integration plugin ecosystem
- **Note merge for plugin coexistence**: when a note already exists (e.g. from the Zotero Integration plugin), Distillate appends its sections between `<!-- distillate:start/end -->` markers instead of overwriting
- **Obsidian Bases support**: generates a `Papers.base` file for native table views in Obsidian 1.9+ (alongside existing Dataview template)
- **`--backfill-highlights [N]`**: back-propagate highlights to Zotero for already-processed papers (processes last N, default: all)
- **`--list` command**: list all tracked papers grouped by status

### Improvements

- **Tag sanitization**: Zotero tags like `Computer Science - Artificial Intelligence` become nested Obsidian tags (`computer-science/artificial-intelligence`) instead of appearing crossed out
- **Citekey fallback**: when Better BibTeX isn't installed, generates citekeys automatically from `surname_word_year`
- **Frontmatter additions**: notes now include `citekey`, `year`, and `aliases` fields for richer Obsidian integration
- **Saved/ folder rename**: processed papers output folder changed from `Read/` to `Saved/` for clarity
- **`SYNC_HIGHLIGHTS` config toggle**: control highlight back-propagation (default: on)
- **Duplicate prevention**: Zotero annotations tagged `distillate` are cleaned up before re-sync to prevent duplicates
- **Reading log citekey links**: reading log uses `[[citekey|title]]` wikilinks for stable references

## 0.1.7 — 2026-02-16

Friction reduction, power-user documentation, and tech debt cleanup.

### Features

- **`--status` shows Read/ folder**: papers waiting in Distillate/Read/ on your reMarkable are now listed, so you can confirm a paper is ready for processing
- **Power users guide**: new standalone page at distillate.dev/power-users.html documenting GitHub Actions automation, engagement scores, reprocessing, custom AI models, storage management, debug mode, and state sync

### Improvements

- **Better no-highlights guidance**: when no highlights are found, shows a numbered checklist (text recognition, highlighter tool, `--reprocess`) instead of a one-line warning
- **Awaiting PDF explanation**: `--status` and `--list` now explain why papers are stuck and what to do about it
- **Note overwrite on re-sync**: `create_paper_note` now overwrites existing notes instead of silently skipping — no more stale notes after re-sync
- **First-run `--status` onboarding**: shows "No papers tracked yet. Run `distillate --init` to get started." when state and config are empty
- **Unified suggestion title-matching**: extracted shared `match_suggestion_to_title()` helper, replacing three duplicate implementations across `digest.py` and `main.py`
- **`extract_insights` uses Haiku**: key learnings extraction now uses the fast model (Haiku) instead of Sonnet — equivalent quality at lower cost
- **GH Actions workflow fixes**: added `DISTILLATE_CONFIG_DIR` and `OBSIDIAN_VAULT_NAME` to workflow environment

### Removed

- **`--themes` entry point disabled**: monthly research themes synthesis is removed from `--help`, CLI routing, and GH Actions workflow. The underlying code is preserved for future use when users have enough papers to make it useful.

## 0.1.6 — 2026-02-16

First-impression hardening: make the first 5 minutes bulletproof.

### Security

- **`.env` file permissions**: config directory created with 0700, `.env` file set to 0600 after every write — API keys no longer world-readable
- **PDF delete guard**: Zotero PDF is no longer deleted when local save fails — prevents data loss when `KEEP_ZOTERO_PDF=false`

### Features

- **`--list` command**: list all tracked papers grouped by status (on_remarkable, processing, awaiting_pdf, processed)
- **`--remove "Title"` command**: remove a paper from tracking with substring match and confirmation prompt
- **`--status` queue contents**: shows individual paper titles with age in days (up to 10)

### Improvements

- **Clean terminal output**: TTY-aware logging — sync shows progress milestones (`Checking Zotero...`, `Uploading: "Title"`, `Extracting highlights... 14 found`, `Done: 2 sent, 1 synced`) instead of raw log lines; full logs go to `~/.config/distillate/distillate.log`
- **Claude data disclosure**: init wizard Step 5 now mentions that highlights and abstracts are sent to the Claude API
- **Text recognition prerequisite**: init wizard Step 2 and README mention enabling text recognition on reMarkable
- **Intermediate state save**: Step 2 saves `processing` status after Zotero tag change, resumes on restart — prevents papers stuck in limbo after crashes
- **"My Notes" section**: Obsidian/markdown notes now include a `## My Notes` section at the end
- **DOI link in notes**: papers with a DOI get an "Open paper" link at the top of the note
- **`_sync_state` timeout**: Gist sync now times out after 30 seconds instead of hanging indefinitely
- **PDF download logging**: failed arXiv/biorxiv downloads now log a warning instead of silently failing
- **Expanded `--help`**: commands grouped by category (core, management, advanced) with descriptions
- **Local-first positioning**: landing page and README now emphasize that notes stay on your machine
- **Troubleshooting guide**: README section covering common issues (rmapi not found, empty highlights, API errors)
- **Resend custom domain**: init wizard mentions free tier includes 1 custom domain
- **Config table**: README now documents `DIGEST_FROM`, `KEEP_ZOTERO_PDF`, `LOG_LEVEL`, `STATE_GIST_ID`

## 0.1.5 — 2026-02-16

### Features

- **`--suggest` polish**: structured terminal output matching `--digest` style — per-paper blocks with title, reason, days in queue, and citation count
- **First-run guidance**: helpful message on first sync explaining watermark and pointing to `--import`
- **Missing API key UX**: `--suggest` without Anthropic key now shows a clear message instead of silently failing

### Bug fixes

- **Suggest failure no longer demotes**: Claude API errors no longer remove previously promoted papers from your reMarkable
- **`--schedule` works for pip installs**: plist generation is now inline Python instead of shelling out to a bundled script
- **OG URL**: fixed `og:url` meta tag to point to distillate.dev instead of GitHub Pages

### Improvements

- **Empty highlights warning**: prints "Is text recognition enabled on your reMarkable?" when no highlights are found
- **Import progress**: shows per-paper progress (`[3/47] Uploading: Paper Title...`) and separates papers awaiting PDF in final count
- **`--status` config clarity**: missing optional features labeled as "Optional" instead of appearing as issues
- **`--status` empty queue hint**: suggests `--import` when queue is empty
- **Init Step 5 skip hint**: makes it clear optional features can be skipped and configured later via `--init`
- **Init Step 3 fix**: removed stale wiki-links claim, fixed Obsidian vault path navigation instructions
- **Init Step 5 DIGEST_FROM**: mentions custom sender domain option when Resend is configured
- **Register output formatting**: consistent indentation with the rest of the wizard
- **Top-level error handler**: unhandled exceptions show a clean message with a link to report issues
- **README model IDs**: synced config table with actual default model identifiers

## 0.1.4 — 2026-02-15

### Features

- **`--import` command**: import existing papers from your Zotero library (interactive selection or `--import all`)
- **`--schedule` command**: set up, check, or remove automatic syncing (launchd on macOS, cron instructions on Linux)
- **Init seed**: setup wizard now offers to import existing papers at the end

### Improvements

- **`_upload_paper()` helper**: extracted reusable per-paper upload logic from sync loop
- **Command order**: commands now follow workflow lifecycle across `--help`, landing page, and README
- **ASCII flow**: concrete outputs ("Notes + highlights + annotated PDF") instead of vague ending

## 0.1.3 — 2026-02-15

### Bug fixes

- **Title propagation**: changing a paper title in Zotero now updates `--status` and promoted papers list
- **`stat_document()` false negatives**: papers stuck in promoted list because empty rmapi stat output was treated as failure
- **Corrupt state recovery**: corrupted `state.json` is backed up and reset instead of crashing
- **rmapi timeout handling**: network timeouts now show a clean error instead of an unhandled exception

### Improvements

- **`--status` promoted list**: show last 3 promoted papers, one per line (was all on one unreadable line)
- **ASCII flow refresh**: `$ distillate   # turn papers into notes!` shell-comment style

## 0.1.2 — 2026-02-15

### Features

- **Cross-page highlight merging**: highlights that span page breaks are now joined into a single passage
- **Citation data surfacing**: Semantic Scholar citation counts shown in digest emails, suggestion prompts, and `--status` output
- **Richer `--status`**: config warnings, awaiting PDF titles, pending promotions
- **Smart `--init` re-run**: detects existing config and offers shortcut to optional features; shows existing values as defaults

### Reliability

- **Zotero API retry logic**: exponential backoff on 5xx, 429, and connection errors
- **Friendly error messages**: connection failures and auth errors show human-readable messages instead of stack traces
- **Config validation**: warnings for missing directories, malformed API keys
- **Duplicate detection**: skip papers already tracked by DOI or title
- **Item type filtering**: skip non-paper items (books, webpages, patents, etc.)

### Landing page

- Palatino serif typography, new tagline, FileHeart favicon
- Integration wordmarks with brand colors, Semantic Scholar card
- Open Graph and Twitter Card meta tags, colophon

## 0.1.1 — 2026-02-14

### Features

- **`--status` command**: show queue health, reading stats, and config summary
- **`--promote` cleanup**: demote old picks before promoting new ones
- **rmapi auth detection**: detect expired tokens and prompt re-registration
- **Suggest-then-promote flow**: GitHub Actions picks papers, local `--sync` promotes them on reMarkable
- **Engagement scores**: quantify reading engagement from highlight density, coverage, and volume
- **Email redesign**: lead with content, stats as footer, unified styling

## 0.1.0 — 2026-02-14

Initial public release.

### Features

- **Zotero to reMarkable sync**: automatically upload new papers from Zotero to reMarkable
- **Highlight extraction**: parse highlighted text from reMarkable `.rm` files via rmscene
- **Annotated PDFs**: overlay highlights on the original PDF using PyMuPDF text search
- **Markdown notes**: generate structured notes with metadata, highlights grouped by page, and optional AI summary
- **Reading log**: auto-updated log of all read papers, sorted by date
- **AI summaries**: one-liner, paragraph summary, and key learnings via Claude (optional, requires Anthropic API key)
- **Paper suggestions**: AI-powered daily reading suggestions from your queue
- **Email digest**: weekly reading digest and monthly research themes via Resend (optional)
- **Engagement scores**: quantify reading engagement from highlight density, coverage, and volume
- **Semantic Scholar enrichment**: citation counts and metadata via S2 API
- **Obsidian integration**: wiki-links, Dataview templates, reading stats
- **Plain folder mode**: alternative to Obsidian — just markdown notes and PDFs
- **Setup wizard**: interactive `distillate init` for first-time setup
- **Scheduling**: launchd (macOS) and cron (Linux) support for automatic syncing
