What’s new
Generated from the project's own CHANGELOG.md each time this site rebuilds, so it's only as current as the last push, with no separate page to remember to update.
8 releases since June 2026, roughly one every 1 to 4 weeks.
Every dot is work that landed; hover or tap one. Newest on the right.
Jun 16 · 13 changes
- Plain text: # card front at column 0, indented answer lines, ! notes (multiple ! lines form one multi-line note), % comments, \ to escape a leading markup character…
- Cloze cards: a #? front with {holes} in the answer expands into one sub-card per hole; sibling holes are masked and spaced apart in the queue.
- Directives (% key: value): mode, order, scheduler set per-deck defaults; read from the requested deck(s) only, overridden by CLI flags.
- Dependencies (% requires: <deck>): prerequisite decks are pulled in transitively and ordered foundations-first; cycles and missing prerequisites are reported…
- Reference links (% link: <url>) are offered to the ask-Claude feature.
- Answer modes: flip (default, self-graded), typing (char-by-char), fuzzy (whole-line, typo-tolerant), choice (multiple choice with distractors sampled from the session)…
- Schedulers: Leitner (the original 6-stage boxes, compatibility-verified) and SM-2 (per-card ease factors), interchangeable.
- Session controls: --new (new-card cap), --limit, --cram, --order sequential, restart from the summary screen, failed cards requeued within the run.
- Notes render as a quoted block, split into sentences, with fenced code shown verbatim (indentation preserved).
- Press ? on an answered card to ask the Claude Code CLI about it without leaving the session; one conversation spans the run. Ctrl-N condenses the exchange into note…
- Startup deck picker (recent decks + the decks directory) when run with no arguments.
- Card identity is a stable XxHash64 over the deck file name plus the back lines (a test pins the value so upgrades never orphan progress). Progress is stored at…
- assets/install-desktop.sh installs an icon, launcher, and .desktop entry.
Jun 16 · 1 change
- Note rendering moved into a frontend-independent render module that emits a structured model (NoteUnit: sentence-split prose or verbatim code blocks); the TUI now only…
Jun 16 · 1 change
- Mark a card for removal during review or browse with the new remove key (default Ctrl-X in review, x in browse, x/Remove button in the web UI). It is dropped without…
Jun 16 · 1 change
- Local web frontend: add --serve to review or browse to run it in the browser instead of the terminal, reusing the same session logic and writing to the same progress…
Jun 17 · 5 changes
- The answer mode is now resolved per card instead of once per session: CLI --mode > the card's % mode: > the deck's % mode: > the built-in default. --mode still forces…
- Deck-level directives (% mode/order/scheduler) must now sit in the deck header, before the first card; a % key: value after a card front is treated as a per-card…
- Web review now shows the expected answer whenever a typed line differed — including a fuzzy pass within tolerance — matching the TUI, so typos aren't reinforced.
- Per-card answer mode: a % mode: directive placed after a card's front overrides the deck's % mode: for that card only, so one deck can mix modes (e.g. a line lyrics card…
- A mode badge at the top of the answer section on every card, in both the TUI and the web app — flip, typing exact, typing fuzzy, choice, line by line — so typing vs…
Jun 17 · 1 change
- Breaking — cloze hole syntax is now {{ }} (was { }). A lone { or } is literal inside #? cards, so code with braces needs no escaping. Cloze identity is now hashed from…
Jun 17 · 1 change
- Dual-direction cards: a % direction: directive (per card or deck-wide, forward/reverse/both) reviews a card both ways — both generates the card and its swap (e.g…
Jun 17 · 1 change
- Image cards: % img: (question side) and % img-back: (answer side, revealed with the back) attach an image to a card; a deck-level % img-dir: sets the folder filenames…
Jun 18 · 3 changes
- Typing mode grades multi-line answers order-independently: a card whose answer is several items can be typed in any order, each completed line matched to whichever…
- Typing feedback now keeps the typed text on screen and, on a wrong line, shows the correct answer underneath with a check mark (the TUI previously discarded the input…
- "New session" on the summary is disabled when nothing is due: the TUI omits the hint and makes the key inert, the web disables the button and shows a "nothing due" note…
Jun 18 · 2 changes
- The TUI's remaining-card count moved from the header to the bottom-right of the footer, shown as N↓ after the pass/fail tally — matching the web frontend's score line…
- Repeated TAB in typing mode progressively reveals the answer: each press uncovers two more characters until the line is fully shown (still counts the card as failed)…
Jun 18 · 1 change
- Deck completion states and unlocks. Each deck has a state derived from its cards' stages — not started / started / finished (all cards at the top stage) — shown in the…
Jun 18 · 1 change
- A card that reaches the top Leitner stage now retires (rests, no longer scheduled until alix reset) instead of recurring at the stage-5 weekly cooldown, so a *finished*…
Jun 18 · 1 change
- Ask-Claude in the web frontend (--serve): an "Ask" button / the ? key on an answered card opens a chat panel (Send / Save note / Close), mirroring the TUI feature. The…
Jun 19 · 1 change
- % mode: explain — understanding cards. The front is an open prompt and the back lines are the *key points* a good answer should cover (not a string to reproduce). You…
Jun 19 · 1 change
- Workspaces — a folder of decks reviewed together with shared directives. A folder is a workspace when it has an alix.toml manifest (a scoped config.toml) setting a title…
Jun 21 · 1 change
- Trace source snapshots — creating a workspace by exploring a source (alix explore --into <dir> --build) now freezes the cited excerpts into the workspace as its final…
Jun 21 · 1 change
- Per-workspace progress store — a deck inside a workspace (a folder with a alix.toml) now tracks its progress in a progress.json inside that workspace, not the one global…
Jun 21 · 2 changes
- Picker disables decks with nothing to review (terminal) — a deck with no card due right now (fully drilled, or all on cooldown) is dimmed and badged with a 🕒 clock…
- Reworked deck picker + trace walking from the picker (terminal) — the no-argument picker is a clean, single-launch list (no checkboxes): Enter opens the focused row. Its…
Jun 21 · 2 changes
- Confirm before abandoning a review; commit the picker filter with Esc (terminal) — quitting a review mid-session now asks to confirm (Enter leaves, any other key stays)…
- The TUI reflows immediately on a terminal resize. The event loops redrew from a size query that could be momentarily stale right after a resize, so the screen sometimes…
Jun 21 · 1 change
- The web deck-selection screen now mirrors the terminal picker. It is single-launch (no checkboxes): click a deck to start it, or open a Workspace / Folder to drill into…
Jun 22 · 1 change
- The web ask-Claude panel now shows only the current card's exchanges. It was rendering the whole session's conversation, so every former card's Q&A piled up on screen…
Jun 22 · 1 change
- You can start the AI exam on a deck inside a workspace from the browser. POST /api/exam/start only resolved top-level deck names, so the "Take exam" action silently…
Jun 22 · 1 change
- You can sit the AI exam early to test out of the drilling. The exam no longer requires every card drilled to the top stage first — it's available as soon as a deck has a…
Jun 22 · 1 change
- The ask-Claude tutor can read the card's source to verify its answer (opt-in). A new [ask] source_access flag (off by default) lets the tutor run with Read/Glob/Grep and…
Jun 22 · 1 change
- A % source: that names several files no longer breaks the exam. The deck generator sometimes writes a multi-file source as <root>/README.md + src/lib.rs (first a full…
Jun 22 · 4 changes
- A note saved from the ask tutor shows on the card right away. Saving a note appended it to the deck file but left the in-memory card unchanged, so the new note only…
- The web ask panel shows the card above the conversation, matching the terminal. The card under discussion (its front + answer) now sits at the top as the reference, with…
- The grounded tutor no longer breaks the conversation with "No conversation found with session ID". Claude scopes its conversation history by working directory, but the…
- Exam remediation is faster, can't fail silently, and shows progress. Three problems when "Add remediation cards" was slow or produced nothing: (1) the remediation call…
Jun 22 · 1 change
- The progress store is now version-checked. A progress.json written by a newer alix is refused on open with a clear "upgrade alix" message instead of being silently…
Jun 23 · 1 change
- Fact cards can cite their source (% at:), shown on reveal. A plain fact card may now carry a % at: file:lines locator into its deck's % source: (the same form a trace…
Jun 22 · 1 change
- Browse a deck from the session-end summary (terminal). When a deck turns *exam due* at the end of a review, the summary now offers b to browse it (a read-only walk…
Jun 23 · 1 change
- % unlock-stage: N — unlock a deck before its cards retire. A % source: deck becomes *exam due* (its exam opens), and a source-less deck *finished* (its dependents…
Jun 23 · 18 changes
- Renamed the project flash → alix. The binary, the crate, the workspace manifest (flash.toml → alix.toml), and the data directory (~/.local/share/flash →…
- alix check no longer fails on warnings: duplicate-answer warnings are advisory, so it exits non-zero only when a deck won't parse, and prints a N error(s), M warning(s)…
- alix explore --title shapes the scaffolded workspace; the goal becomes its description. alix explore --into <dir> now takes an optional --title for the workspace's…
- alix import <file.tsv> — import an Anki "Notes in Plain Text" export (tab-separated front<TAB>back) into an alix deck, no Claude needed. It skips Anki's #-prefixed…
- alix check now validates trace % at: locators. A trace deck is linted like any other: check resolves each checkpoint's locator against its % source: and warns (advisory…
- alix deck <source> (renamed from alix generate, which no longer exists as an alias) — generates a facts deck with Claude from a web page URL or a local file/directory…
- Traces (alix trace, experimental) — a guided predict-and-verify walk along a *path* through a % source:, drilling the connections between facts (the edges) rather than…
- alix explore <source> (experimental) — goal-driven exploration: prints an ordered learning plan toward a --goal (default "understand the whole source"), the fact decks…
- % title: deck directive (also usable in a workspace.alix manifest): a display name shown in the picker, session header, alix list and alix stats instead of the file…
- alix exam <deck> — the AI exam, which *verifies understanding* and gates progression (rung 3 of the AI-exam direction). A deck declares its ground truth with % source…
- In-browser deck selection: alix --serve (and alix browse --serve) with no deck files now opens a deck-selection screen in the browser instead of the terminal picker — a…
- alix reset clears stored progress: for one or more decks, a single card (--card <id-or-front-text>), or everything (--all). With no decks it opens the same checkbox…
- Generated decks now put a blank line between cards. alix deck's output cleaner (generate::clean_output) inserts a blank line before each card front (#) after the first…
- alix generate <url> builds a deck from a web page via Claude (WebFetch), with a prompt that spreads cards across four layers of understanding, uses cloze and notes, and…
- alix browse — read-only walk through cards (no grading, no writes).
- alix deps (alias require) — edit a deck's prerequisites with a checkbox picker.
- alix stats, alix list, alix check, alix config.
- ~/.config/alix/config.toml with [keys], [browse], [ask], [generate] sections and decks_dir. alix config --init writes a self-documenting template (every option commented…
v0.1.0 · 2026-06-23
Tagged release. Full notes below.
Jun 24 · 3 changes
- alix deck augment — deliberate AI deck augmentation. A new command that enriches an existing deck with Claude and caches the result beside your progress (augment.json…
- alix deck is now a command group: alix deck generate + alix deck augment. Breaking: alix deck <source> is now alix deck generate <source>.
- Choice-mode offline distractors are shape-aware. Number-like answers now only compete with the same shape (a 4-digit year vs other years, not a 1,5 ratio or a 2-digit…
Jun 24 · 1 change
- Opening a deck with nothing due no longer bumps it to the top of the recent list. A review now records the deck as "recent" only when the session actually has cards to…
Jun 24 · 1 change
- Ask-Claude (web): Enter now inserts a newline and Shift+Enter sends. The ask box is a multi-line textarea, so plain Enter composes freely and a deliberate Shift+Enter…
Jun 24 · 1 change
- alix check rejects a cloze whose entire answer is one hole. A #? card whose only hole spans the whole answer (e.g. {{IdentStr}} , with nothing but formatting around it)…
Jun 24 · 1 change
- Web exam: Shift+Enter advances to the next question (or submits, on the last), matching the ask box — Enter still inserts a newline so multi-line answers compose freely…
Jun 24 · 1 change
- % requires: now gates the exam, not drilling. You can review/drill any deck at any time, in any order — a prerequisite-locked deck is no longer blocked in the picker (it…
Jun 25 · 1 change
- Reviewing a deck no longer pulls in its prerequisites' cards. A review (in the TUI/CLI) now holds exactly the deck(s) you picked — % requires: decks are not auto-added…
Jun 25 · 1 change
- Web exam: leaving mid-answer asks to confirm. Pressing Esc (or Quit) while answering now shows a "Quit the exam? Your answers won't be graded" prompt — Enter abandons…
Jun 25 · 1 change
- Web trace walk: the leave button reads "Leave" and confirms an unfinished walk. The hosted walk's return chip was "Decks"; it's now "Leave" (matching a fact-deck…
Jun 25 · 1 change
- Web picker draws the dependency tree like the TUI. A workspace's members now show ├─/└─/│ branch lines (muted) instead of plain indentation, and the 🕒 "nothing due"…
Jun 25 · 1 change
- The Mastered window shows when a deck was mastered and how much is left to drill. A mastered deck's badge now reads e.g. mastered 🎉 · 3w ago · 8 to drill — the time…
Jun 25 · 1 change
- Browse a deck straight from the web picker. A deck row's primary action is now Review (Enter), with a new Browse button (the go-right key, l/→) that opens a read-only…
Jun 25 · 1 change
- Ask-Claude (web): the input re-focuses when a reply lands, so you can type a follow-up immediately instead of clicking back into the box.
Jun 25 · 8 changes
- Ask Claude during a trace walk. The web walk now has an Ask button on each reveal (and the ? key) — the same tutor a card review offers, scoped to the current checkpoint…
- A "⌵ N more" marker when a source excerpt overflows the card. A reveal whose excerpt is taller than the card shows a small ⌵ N more lines pill at the cut edge (counting…
- A trace's exam is its compression — AI-graded. A trace's % trace: is a question ("how X becomes Y"); its exam is to answer it — retrace the whole path in a sentence or…
- The review header no longer shows the stage ladder. The always-on new|s1|s2|… stage histogram is gone from the review header (TUI and web) — it was noise; the per-stage…
- Returning to the picker keeps your place. After a review/browse/walk/exam, the deck picker re-lands the cursor on the deck you just launched (rather than jumping to the…
- Breaking — a trace masters by passing its exam, not by finishing the walk. Walking a trace is now the *drill*: completing the walk no longer masters it (the earlier…
- A % at: locator written relative to a project root above % source: now resolves. When a deck scopes % source: to a subdirectory or file (e.g. …/crate/src/executor) but…
- A long (hand-crafted) deck title no longer reflows the header. The review/browse/walk headers truncate an over-long title with an ellipsis instead of wrapping to a…
Jun 28 · 1 change
- Topology-ordered review (experimental). alix deck augment <deck> --target topology derives a graph of how a deck's cards relate — labeled edges, a suggested walk, and…
Jun 28 · 1 change
- Breaking — one deck per session. alix review and alix browse now take exactly one deck *file*: merging several loose decks into a combined session is gone, and a whole…
Jun 25 · 3 changes
- alix explore --into --build now actually freezes its assets/. The generated % source: paths were silently doubled: when --source is a subdirectory (a crate) but the plan…
- A multi-file % source: (a.rs + b.rs) now freezes every cited file. Snapshotting treated the whole + -joined line as one literal path, so a multi-file source froze…
- A cited deck that can't be frozen is reported, not swallowed. alix explore --build now warns which deck's source couldn't be read instead of silently leaving an empty…
Jun 25 · 1 change
- alix explore generates short, title-cased deck/trace titles. The plan prompt asks for a terse title, but the model ignored it and appended the deck's contents after a…
Jun 28 · 4 changes
- The ask-Claude tutor grounds a frozen card in its live source. For a card in a frozen workspace (alix explore --into --build), the tutor now reads the original crate for…
- % origin: — the live source root a frozen deck's snapshots came from. Written into a workspace's alix.toml [defaults] at build time and cascading workspace → deck → card…
- alix check flags drifted frozen cards. When a frozen card's snapshot no longer appears in its live source — the lines changed, or the file is gone — it warns (card at…
- Breaking — the freeze format records provenance on the % at: line, not a note. Freezing a workspace now writes % origin: (the live crate root) and appends each card's…
Jun 29 · 1 change
- Web picker keys. Clicking a deck now selects it (opening its focus drawer when it has a topology) rather than launching outright — Review or Enter launches. Browse moved…
Jun 29 · 2 changes
- Explain-mode key points — a checklist that derives the grade. A new augmentation, alix deck augment <deck> --target keypoints, has Claude break each card's answer into…
- alix deck augment says what it's doing. It now prints which augmentation it's generating, for which deck, and with which model before the (foreground, possibly slow)…
Jun 30 · 1 change
- New cards are introduced as an *attempt*, not a cold quiz (acquire). A never-seen card no longer drops you into a quiz you can't pass — its first encounter is a…
Jun 30 · 1 change
- Web picker header. The deck filter moved into the header — a compact box centered on the list — and a burger menu (☰) there holds keyboard shortcuts, refresh decks…
Jun 30 · 1 change
- Augment decks from the web picker — no CLI needed. Press a on a deck (or its new Augment button) to open a screen of what its augmentation cache holds: one row per…
Jun 30 · 1 change
- Breaking: card identity is now whitespace-insensitive — an answer's id no longer depends on line breaks, indentation, or repeated spaces (only its words). Cards whose…
Jun 30 · 1 change
- alix deck generate now shapes cards better: it splits enumerations into one-idea cards (or uses % mode: line for ordered lists) and structures answers and notes instead…
Jun 30 · 1 change
- alix deck augment --target format — a non-destructive pass that reshapes a badly-shaped card (e.g. a list crammed into one prose answer) into clean display lines, a…
Jun 30 · 1 change
- Browse now shows the same display augmentations as review — the format reshape and notes trivia — so the two views render a card the same way instead of browse falling…
Jun 30 · 1 change
- Web picker: ←/→ (and h/l) now step the focus drawer's regions, and going back is Esc/Backspace only. The drawer needs left/right to move between regions, so those keys…
Jun 30 · 1 change
- Web picker: browser-style back + refresh buttons in the header, for people who reach for the mouse/touch over keybindings. The ← button goes back a view (disabled at the…
Jun 30 · 1 change
- Bigger cards in the web review and browse views. The card was capped small (≤820/720px wide), so it sat in a sea of empty space on a normal screen at 100% zoom and long…
Jun 30 · 1 change
- Browse is now an in-page mode of the web app — no separate /browse page. Hitting Browse in the web picker (or alix browse <deck> --serve) opens a read-only overlay right…
Jun 30 · 3 changes
- Web picker: the primary action is Learn, bound to Enter. The focused row's primary action — Learn a deck (review or walk), Open a workspace, or Take exam — is bound to…
- Breaking — review grades are now failed / partly / passed, replacing again / good / easy (shown in the UI as Missed it / Partly / Got it — an honest self-report of…
- No stray blinking caret across the web app. The caret is suppressed on card/slide prose everywhere — review, browse, and the trace walk — appearing only inside a real…
Jul 1 · 1 change
- Workspace icons draw fast, without timing out. The explore --build icon prompt now caps the emblem at a few compact primitive shapes instead of letting the model emit…
v0.2.0 · 2026-07-01
Tagged release. Full notes below.
Jul 1 · 1 change
- Web picker: cleaner dependency-tree lines. The workspace drill-in's tree connectors (├─ / └─ / │) are now drawn as subtle dotted CSS guides in the row border colour —…
Jul 1 · 1 change
- Browse left-aligns multi-line answers like review. The read-only browser decided the left-aligned-block layout from the reshaped-list flag alone, so an unaugmented…
Jul 2 · 1 change
- Draw input (web). Answer a flip/explain card by drawing or handwriting on a canvas, then self-grade — either authored per card/deck with % input: draw (for answers that…
Jul 2 · 1 change
- Gemini backend ([ask] backend = "gemini"). alix's AI calls can now run through the Google Gemini CLI (gemini -p, headless). Tool access maps to Gemini's read-only tools…
Jul 2 · 1 change
- Codex backend ([ask] backend = "codex"). alix's AI calls can now run through the OpenAI Codex CLI (codex exec, headless). Codex takes the prompt as a command-line…
Jul 3 · 1 change
- Backends degrade gracefully. An AI feature now checks the selected backend's capabilities *before* doing any work and refuses cleanly when they don't match — e.g. an…
Jul 3 · 2 changes
- alix backend check [--all] health probe. Sends a trivial tool-free request to the configured backend (or all four with --all) and reports whether each is installed…
- Breaking — alix check is now alix deck check. Deck validation moved under the deck noun-group for consistency with alix deck generate/alix deck augment. The command is…
Jul 3 · 1 change
- Multi-turn tutoring works on every backend. Claude keeps a running conversation with its session flags (--session-id/--resume); other CLIs don't have those, so alix…
Jul 3 · 2 changes
- [ask] backend selector. All AI calls now route through a pluggable backend. Set backend in [ask] to choose among claude (default), gemini, codex, or copilot. Auth is…
- Copilot backend ([ask] backend = "copilot"). alix's AI calls can now run through the GitHub Copilot CLI, authenticated via gh auth login.
Jul 3 · 1 change
- The tutor is now backend-agnostic ("Ask Tutor" / "Tutor"). The in-session tutor was labelled "Ask Claude" in the UI and docs. It works with every supported backend…
Jul 3 · 1 change
- Pre-flight source-size guard. Before deck generate, trace --build, trace --suggest, and explore read a large source, alix measures the estimated size and asks for…
Jul 3 · 1 change
- Pairing token for alix serve --lan. Serving to the network now auto-generates a token (printed at startup) and requires it on /api/*, so the LAN endpoint is no longer…
Jul 3 · 1 change
- Breaking: alix trace --serve removed. Trace walking in the browser now goes through alix serve's deck picker (pick the trace) — the standalone single-trace web server is…
Jul 4 · 6 changes
- [review] config section — FSRS pacing. retention (target recall probability, 0.70–0.99, default 0.9; higher = shorter intervals) and retire_after (a duration "1y" / "6m"…
- Per-workspace pacing via alix.local.toml. A workspace can override the global [review] retention / retire_after in a personal alix.local.toml beside its alix.toml — kept…
- alix deck check warns on a non-gating prerequisite — when a sourced deck % requires: a source-less deck, that edge can't gate its exam; the lint names it and suggests…
- Breaking: FSRS is now the only scheduler. alix schedules with FSRS-5 (via the rs-fsrs crate) for every review; the Leitner and SM-2 schedulers are gone, along with the %…
- Breaking: % unlock-stage: removed (the directive, the --unlock-stage flag, and the [defaults] unlock-stage key). A deck's exam is now gated on graduation: it turns *exam…
- --cram refreshes without rewarding. A correct answer under --cram now re-anchors the card's due date by its current interval — no FSRS update, no reward — so cramming…
Jul 4 · 2 changes
- Breaking: a review session scores each card once per appearance. A missed card is no longer re-drilled immediately in the same sitting; it keeps its short spaced step…
- Breaking: a card graduates only after two spaced correct recalls. A single Good after a miss no longer promotes a card to the long-term review phase faster than two…
Jul 5 · 1 change
- A just-seen card starts drilling in the same session. Acquiring a new card (the ungraded "Seen" first exposure) now settles for ~1 minute (was ~5) and the card stays in…
Jul 5 · 3 changes
- Remediation cards are now virtual cards in the store. A failed source exam's remediation cards live in alix's store instead of being written into your deck file. They…
- Promote a virtual card into its deck. A review-time action appends a remediation card to the deck file and drops the virtual copy — "Promote to deck" in the web review…
- Breaking: a failed exam no longer appends remediation cards to your deck file. Remediation cards are now created as virtual cards in alix's store, so the deck .txt stays…
Jul 5 · 1 change
- A virtual (remediation) card's retirement is now fully derived, matching a deck card: purely its FSRS interval vs. retire_after, no stored archive flag. Raising…
Jul 5 · 1 change
- Exam-fail remediation count, and a "remediation card" review label. The post-remediation exam screen now reports how many remediation cards the failure created or…
Jul 5 · 1 change
- Breaking: % mode:, the #? cloze marker, and the --mode flag are removed — replaced by % reveal: and the session depths above. A cloze card is now % reveal: cloze (was…
Jul 6 · 1 change
- Breaking: the terminal frontend is removed — alix is web-first. Bare alix (or alix <deck>) now opens the local web app and prints its URL, instead of a ratatui TUI…
Jul 6 · 1 change
- Breaking (store): dropped the legacy Leitner stage/stage_entered_ms fields now that FSRS is the sole scheduler; stage_entered_ms is renamed acquired_ms. Pre-FSRS cards…
v0.3.0 · 2026-07-07
Tagged release. Full notes below.
Jul 7 · 3 changes
- Two quiet overrides give the learner the final say. A typed Reconstruct check normalizes both sides (case, whitespace, trailing punctuation) and compares exactly, then…
- Per-deck badges for Recognize/Recall/Reconstruct, shown in the picker. A deck earns a depth's badge once every card is currently solid at it (recognized; or at/past 21…
- Breaking: --max-typos and the fuzzy check mode are gone. A typed check now normalizes (case, whitespace, trailing punctuation) and compares exactly, then shows the diff…
Jul 7 · 1 change
- A full Reconstruct pass credits a due Recall schedule — downward only, pass-only. Getting a card fully right at Reconstruct (outside cram) now counts for its Recall…
Jul 7 · 1 change
- % reveal: — the authored presentation axis. How a card is *presented* is now its own directive (deck or card, default flip): flip, cloze ({{spans}}), or line — while how…
Jul 7 · 1 change
- The web app can no longer be served stale from the browser cache. alix sent no cache headers, so after an upgrade the browser could keep showing the previous version's…
Jul 7 · 3 changes
- Session depths: Recognize, Recall, Reconstruct — replacing the retired % mode: checks. Every review now happens at one of three independent depths, chosen per session…
- alix list/alix stats report per depth. list now shows each card's Recall and Reconstruct schedule state plus a ✓ once it's recognized; stats adds a per-depth due count.
- Breaking (store): per-depth schedules replace the single fsrs field. A card's progress is now stored per depth (recall/reconstruct), plus a recognized flag, instead of…
Jul 8 · 6 changes
- Scoped roots: alix <dir> serves that folder as a self-contained instance — its own catalog plus its own progress.json and recent.json kept inside the folder, so several…
- alix doctor — one health command. Checks the config parses, the progress store is readable, the decks folder scans (broken decks point at alix deck check), and the…
- A scannable pairing QR in the --lan startup output, alongside the pairing URL — which now shows the machine's actual IP instead of a placeholder. A phone or tablet pairs…
- [review] max_new and limit config keys for session pacing, with per-instance --new/--limit overrides on bare alix (precedence: flag > config > built-in 10 / no cap).
- A taken port now errors immediately with a try --port hint — the server binds before printing its URL, so a clash no longer shows a success-looking line first.
- Breaking: the CLI collapses to alix [dir] plus task subcommands — every review starts from the picker. Removed outright (pre-1.0, no aliases): alix <deck> direct-deck…
Jul 8 · 3 changes
- alix share <path> and alix receive <code> — send decks to someone over magic-wormhole (shells out to the wormhole binary; install it separately). Share takes a deck…
- stats, list, and reset take a deck, a folder, or a workspace. A folder or workspace expands to its member decks against the store that serving uses; reset on a workspace…
- Breaking: one generate verb for all AI authoring — explore, trace, deck generate, and deck check are removed. alix generate <source> routes by the source: a URL/file…
Jul 8 · 1 change
- alix workspace init <dir> scaffolds an empty workspace — an alix.toml, a personal alix.local.toml, and an assets/ folder, no decks. Both TOML files are written fully…
Jul 8 · 1 change
- Cram is back — as a tick-box in the picker's Learn ▾ menu (key c, rebindable as [keys.picker] cram), combining with any depth; plain Learn never crams. Its semantics got…
Jul 8 · 2 changes
- docs/API.md — the web JSON API is now a written, tested contract. Endpoints, DTO field tables with nullability, the flows (select→state→grade, walk, exam, augment, ask)…
- Breaking (API): three contract shapes normalized before the freeze. WalkDto.verdict sends passed/partly/failed machine tokens instead of English display labels; POST…
Jul 8 · 1 change
- alix generate <dir> (workspace build) no longer blocks on, or touches, a populated destination. The build always stages into a scratch dir first, then merges the new…
Jul 9 · 1 change
- Web picker self-sufficiency: the ☰ menu gains Add deck… (generate from a URL, import .tsv/.txt, receive a wormhole code or .zip), Share… (wormhole code or .zip…
Jul 9 · 1 change
- The tutor's "couldn't find the source" reply, for a frozen card whose live source root is gone, now comes back immediately instead of round-tripping through the model to…
Jul 9 · 1 change
- The acquire view's badge no longer names a check — a brand-new card shows just NEW (the attempt-first reveal is ungraded).
Jul 9 · 4 changes
- The web exam launch pre-flights the backend's ability to reach the deck's source, failing at start instead of mid-exam.
- A just-finished card can no longer come straight back: its re-serve clock now floors at the card transition, so time spent on the feedback screen or the next card never…
- The picker's ⟳ now re-reads the config, so a changed decks_dir takes effect without a restart (scoped alix <dir> instances stay pinned to their folder).
- Breaking: an ambiguous bare deck name is rejected instead of silently resolving. The same file name occurring in two containers now fails with 400 from every name-taking…
Jul 9 · 1 change
- A sequence card (% reveal: line) at Recognize is now quizzed as one whole answer among the cached distractors, instead of a meaningless pick-one-step choice built from…
Jul 9 · 2 changes
- The same-card floor now covers Recognize too: a failed pick used to resurface instantly (a deliberate exclusion at the time); with one card left, that meant an instant…
- Multiple-choice options now reshuffle on each appearance of a card, instead of sitting in the same positions every time — a retry could otherwise be solved by position…
Jul 9 · 1 change
- A wrong Recognize pick now shows which option was right before moving on (Continue grades it failed) — the silent instant-demote skipped the corrective moment.
Jul 9 · 1 change
- Redesigned the web UI (IBM Plex typography, borderless/hairline dark theme).
Jul 9 · 1 change
- docs/API.md documented /api/walk/grade's delta keys as "g"|"p"|"m"; the server and web client have always used "n"|"p"|"f". The doc now matches the wire (caught by the…
Jul 9 · 1 change
- A live Codecov badge on the README, backed by a real-server HTTP round-trip test suite (tests/api.rs) that drives /api/* over the wire rather than calling handlers…
Jul 10 · 2 changes
- A kids web client (touch-first, aimed at roughly age 10): a calm, consumption-focused frontend over the same engine, served at / when [serve] audience = "kids". An adult…
- [serve] audience config key ("adult" default, or "kids") — which frontend / serves, and which voice the tutor uses.
Jul 10 · 1 change
- docs/API.md described DeckItemDto.name as a key you can always send to /api/select. Only deck rows are selectable: a workspace or folder row is a container and…
Jul 10 · 1 change
- An end-to-end smoke suite for the alix web clients — both adult and kids (make e2e, Playwright): a click must produce the expected request, response, and screen, with no…
Jul 11 · 2 changes
- On a first-seen (acquire) card, h (or a tap on the answer) hides / un-hides the revealed answer in place, so you can self-test the fresh encoding (conceal it, try to…
- A multi-line front now renders as centred lines instead of one run-on line, so a dual-direction card's reverse side (its several alternatives, shown on the question…
Jul 11 · 1 change
- Ask tutor on Recognize. The tutor button now appears on a Recognize (multiple-choice) card's feedback, the same as Recall and Reconstruct. It's most useful after a wrong…
Jul 11 · 1 change
- The trace walk screen now shares the session chrome. It still rendered pre-re-skin chrome: no ☰ menu in the header, and a footer that packed Missed it/Partly/Got…
Jul 11 · 1 change
- A keypoint click on an Explain-mode card that also carries a % at: citation could silently swap the whole answer region to the cited source excerpt instead of marking…
v0.4.0 · 2026-07-11
Tagged release. Full notes below.
Jul 11 · 1 change
- The adult theme gallery's Kids group now offers Sunrise, Ocean, and Berry — the same three themes the kids app ships, re-derived as full adult palettes (every token the…
Jul 11 · 1 change
- Tutor: make this a card. In a review exchange, "Make this a card" asks the tutor to distill the conversation into a draft front/back; you edit it, then Add lands it as a…
Jul 12 · 3 changes
- The tutor's Save note (and the new Make this a card) now stay disabled until the tutor has actually answered, instead of looking active and silently doing nothing on an…
- The placeholder "Fun" kids theme, superseded by the three real kids themes above.
- The review tutor is now offered during a card's first encounter (acquire), once you reveal the answer. It stays hidden during the blind attempt, matching the…
Jul 12 · 1 change
- Breaking: the progress store now lives with your decks (<decks_dir>/progress.json) instead of the platform data dir (~/.local/share/alix/progress.json); bare alix and…
Jul 13 · 2 changes
- The Augment screen redesign: one card per target, not a row. Each of the six targets (choices, notes, questions, key points, format, topology) shows a plain description…
- Breaking: the /api/augment/generate request body now takes a targets list of {target, with?} entries (each with its own optional guidance) instead of a single target…
Jul 13 · 1 change
- The topology augmentation now defaults to a pedagogical (foundations-first) order when you give no guidance, named pedagogical order rather than auto; a guidance steer…
Jul 13 · 1 change
- The formatting augmentation no longer strands already-clean cards as a permanent gap: a card the formatter checks and leaves as-is is now recorded as done, so coverage…
Jul 13 · 1 change
- alix generate and its review pass now keep each card's answer to exactly what its front asks, moving extra context into the note instead of over-answering the question.
Jul 13 · 1 change
- The exam overlay now hides its scrollbar, matching the augment and review surfaces, instead of showing one and reserving a gutter for it.
Jul 13 · 1 change
- The review screen's up/down navigation is now rebindable. The multiple-choice and key-point lists move with k/j by default (the arrow keys always work too); rebind them…
Jul 13 · 2 changes
- Opening the web app in the first moments after a server start could paint a blank page (the page booted before the server was ready to answer); the boot now retries…
- For library consumers: a full cargo feature (on by default) now gates the AI backends and the web server. Depending on alix with default-features = false compiles just…
Jul 13 · 1 change
- Lenient exam grading no longer downgrades an incomplete-but-correct answer to partial: the grading criteria now say outright that covering only some key points still…
Jul 13 · 1 change
- Every augment card carries its own guidance input. Instead of one shared guidance box in the footer, each target card has a compact steer field with a kind-specific…
Jul 13 · 1 change
- alix doctor --grading: is your model good enough to grade exams? An opt-in spot-check (three real, costed calls) that runs six hand-labeled grading probes against the…
Jul 14 · 1 change
- Breaking: POST /api/check no longer reads the client-sent ordered flag; whether typed lines pair by position (typeline) or match in any order is derived server-side from…
Jul 14 · 2 changes
- The Augment screen now opens on a workspace (or folder). The same six target cards run across all member decks at once: Generate fills a target's gaps in every member…
- A Select all button on the Augment screen ticks every target that can run, so a full batch is two clicks.
Jul 14 · 2 changes
- Leaving the tutor now asks first when the conversation is unsaved, the same pause as leaving a session: the transcript survives on the current card, but moving on to the…
- While the tutor is thinking, the panel shows the looping alix logo next to "Thinking…" (the header logo already looped; this one sits where you're looking), and the…
Jul 14 · 1 change
- The tutor's "Save note" is now "Make this a note", matching "Make this a card", and both distill actions are rebindable: Breaking: the [keys.review] key save_note is…
Jul 14 · 1 change
- alix generate and its review pass now turn a mapping of pairs into one cloze card (one line per pair, the recalled half blanked) instead of a "match each X to its Y"…
Jul 14 · 1 change
- The site gains an Impressum, a privacy note, a contact address and a sponsor link; personal details are injected at deploy time, not stored in the repo.
Jul 14 · 2 changes
- The legal notice and privacy pages are now in English (headings kept as "Legal notice (Impressum)" and "Privacy (Datenschutz)" for recognizability).
- The landing page counts visits with GoatCounter, a cookie-less, privacy-friendly analytics service; the privacy page explains what it does and does not collect.
Jul 14 · 1 change
- The review header shows a dim "N left" count: how many cards the session still holds, updated after every card. It can honestly tick up when a card you missed cools back…
Jul 14 · 1 change
- The acquire cooldown is configurable and defaults to 5 minutes (was a fixed 1 minute): [review] acquire_cooldown ("90s", "10m", "1h"; a bare number is minutes, "0"…
Jul 14 · 1 change
- A "What's new" page on the site: an interactive timeline of releases and landed changes (dots with popovers, drawn from this changelog and the git history at build time)…
Jul 15 · 2 changes
- Multi-device roaming guards in the store. Every save stamps which device wrote progress.json (the device name is a plaintext file in the data dir, rename it freely), and…
- The library exposes its version as alix::VERSION (the mobile About screen shows it next to the app's own).
Jul 15 · 1 change
- A Recognize card with no buildable multiple-choice question no longer strands the review. A deck too small for distractors (or without cached AI ones) reported the…
v0.5.0 · 2026-07-15
Tagged release. Full notes below.
Jul 14 · 1 change
- Augment batches on the Claude backend now share one conversation: the first target sends the cards once, each later target runs as a short follow-up that references them…
Jul 15 · 3 changes
- alix doctor <dir> now actually runs its workspace lints (the missing-icon warning, the deadline-key check) for a directory target; a routing gap meant they never printed.
- Workspaces can carry a personal deadline. Set deadline = "YYYY-MM-DD" and deadline_ramp in a workspace's alix.local.toml (CLI: alix workspace deadline <dir> <date>; also…
- Picker UX pass: quieter refresh, a footer Back chip, a clearer depth button. The window-focus re-scan now repaints only when the catalog actually changed, so alt-tabbing…
Jul 15 · 1 change
- A never-drilled deck now defaults to Recognize when it already has AI distractors. Plain Learn used to always start a fresh deck at Recall; now it starts at Recognize if…
Jul 15 · 1 change
- An experimental native app now lives in apps/mobile: a Flutter shell embedding the lean Rust core to review decks offline on Android (and as a Linux desktop window). It…
Jul 16 · 1 change
- The web app names your configured AI backend instead of assuming Claude. The tutor header and the "working…" progress lines during augment and the exam now show the…
Jul 16 · 1 change
- A tutorial deck on first run. A brand-new decks directory is seeded with "The alix tutorial": ten cards that teach alix by being reviewed — honest grading, spacing…
Jul 16 · 1 change
- The session summary no longer reads all zeros after a first pass. A fresh deck's first sitting is acquire-only (attempt-first exposure, no grades), but the summary said…
Jul 17 · 1 change
- A paired phone can borrow the desktop's AI backend for the tutor and the exam, over /api/remote/*, including a trace deck's compression exam. The client re-sends its own…
Jul 17 · 1 change
- A paired phone can also generate a deck from a URL through the desktop's AI backend, over POST /api/remote/generate. The server returns the full deck text and a…
Jul 17 · 1 change
- A paired phone can also condense its tutor conversation into note lines, over POST /api/remote/ask/note. The server condenses up to three lines the same way the web's…
Jul 18 · 1 change
- A quiet Support line in the About dialog, on both the web and mobile clients. Leads with the free alternative (telling someone who studies), a sponsors link second…
Jul 18 · 1 change
- Breaking: a multiple-choice pick now requires a cached AI augmentation; options are never sampled from other cards. Distractors were previously topped up by sampling the…
Jul 18 · 1 change
- Breaking: the Recognize depth is now pick-only. A Recognize session schedules only *recognizable* cards (ones a cached pick can be built for); an un-augmented card is no…
Jul 19 · 2 changes
- alix doctor now lints a decks folder for identity problems, and alix reset --orphans clears the leftovers it finds. Over a folder, doctor reports duplicate deck and card…
- A copied deck no longer silently shares one card's progress across two files. When two decks in a folder claim the same identity token (a copied file, or a card copied…
Jul 19 · 1 change
- A cloze card's blanks now keep their progress when you edit its gaps. Inserting, deleting, or reordering \blank{…} gaps, or rewording the text around one, no longer…
Jul 19 · 1 change
- Breaking: deck-level strictness removed: grading strictness is a learner setting (config or workspace defaults); a deck cannot ship grading rigor. A strictness: key in a…
Jul 19 · 2 changes
- Breaking: decks are now Markdown files (.md), and a card's identity is a minted token, not a content hash. A card front is ## , its answer lines follow plainly, a note…
- The CLI --help text is modernized to the Markdown deck wording: a trace stub now declares trace: in its frontmatter (no % trace:), replacing the stale old-format…
Jul 19 · 1 change
- Grading no longer rewrites the progress store on every answer: in-session progress stays in memory and is written once, when the session ends (leaving the deck…
Jul 19 · 1 change
- Breaking: the alix deck augment --target value topology is renamed to order (matches the web app's "Order" card); pre-1.0, no alias. This is a user-facing rename only…
Jul 20 · 1 change
- Breaking (web API): a card's images are now lists, not single fields. The CardDto wire shape drops the scalar img / img_back strings and replaces them with images /…
Jul 20 · 1 change
- Breaking: media embedding is now standard Markdown !alt; the \image/\audio/\video markers and the <!-- img: -->/ <!-- img-back: --> directives are removed. Write a plain…
v0.6.0 · 2026-07-20
Tagged release. Full notes below.
Jul 25 · 1 change
- Breaking (pre-1.0): grounded tutor filesystem access now requires an explicitly declared deck or workspace source; its root is the deck's first local-path source…
Jul 25 · 1 change
- Visible adult-client scrollbars now use slim, theme-aware tracks and thumbs instead of the browser's bright native chrome.
Jul 25 · 1 change
- A private vulnerability-reporting policy and a tracked threat model covering local files, LAN pairing, AI providers, sharing, persistence, mobile, and release boundaries.
Jul 25 · 1 change
- Production CI and release workflows now select exact Rust, Flutter, Java, Node, Android NDK, FRB codegen, mdBook, and coverage-tool versions. Every directly referenced…
Jul 25 · 1 change
- Breaking (pre-1.0): a hand-authored Markdown file must be explicitly initialized with alix deck init <file> before it appears in the picker or can be reviewed or…
Jul 25 · 1 change
- The adult tutor's unsaved-conversation prompt no longer binds <kbd>Enter</kbd>, which remains available for composing a newline; <kbd>Escape</kbd> stays in the tutor…
Jul 25 · 1 change
- Adult review notes now use the same content width and text size as the answer or choice column instead of shrinking into a narrower, smaller box.
Jul 26 · 1 change
- Breaking (pre-1.0): progress and AI augmentation now live in independently versioned documents per initialized deck: progress/deck-<token>.json and…
Jul 26 · 1 change
- Breaking (pre-1.0): workspace member decks now live only under direct decks/*.md children. Manifests, assets, and augmentation remain at the workspace root; private…
Jul 26 · 2 changes
- State, deck, and manifest writes now sync the file's data before the atomic rename and the directory entry after it, so a power loss right after a save can no longer…
- A review session whose progress document was replaced by another writer (for example a synced device) now reports it: the review state carries a save_error and the adult…
Jul 27 · 5 changes
- Plain fact cards can carry multiple <!-- at: ... --> citations. The adult source view resolves every locator and stacks the editor-style excerpts in authored order…
- alix doctor --repair-source-locators explicitly fingerprints reviewed source citations and rebases a uniquely relocated exact excerpt while preserving deck and card IDs…
- Breaking (web/mobile APIs): CardDto and the shared mobile CardView replace the single at citation with ordered citations; web citation entries carry their resolved…
- Breaking (pre-1.0): every complete <!-- at: ... --> citation now carries a named fingerprint: xxh64-... field. Review, trace, tutor grounding, and grading fail closed…
- Hard-wrapped Markdown answer prose no longer renders each source line as a separate, widely spaced answer line. Ordinary flip and acquire views join soft wraps; line…
Jul 27 · 5 changes
- alix workspace update <dir> reconciles frozen source-backed members with their live local sources. It stages an exact sibling workspace for review, then --apply…
- Dependency changes now pass a reviewed duplicate-family gate through make deps-check, preventing an avoidable second compiled version from entering the graph unnoticed.
- Breaking (pre-1.0): initializing a source-backed workspace member now freezes each cited source excerpt and every local card image before success. A citation's frozen…
- Single-deck sharing now carries and validates the complete deck-owned asset directory plus matching augmentation while continuing to exclude progress. Generated…
- Breaking (pre-1.0): typed WorkspaceFiles and UserFiles owners now separate shareable deck material from private learning state. Assets and per-deck augmentation stay…
Jul 27 · 1 change
- alix deck copy <deck> <workspace> and alix deck move <deck> <workspace> transfer one initialized workspace member with its owned frozen assets and augmentation. Both…
Jul 27 · 2 changes
- Review progress now persists as it happens: every grade, acquire, exam flag, badge, and card mutation writes the deck's progress document before the response returns, so…
- The server drains its workers, flushes any unsaved state, and exits cleanly on Ctrl-C or SIGTERM instead of dying mid-request.
Jul 27 · 1 change
- Saving a document that first has to create its progress/ or augment/ directory now flushes the new directory entry to disk, closing a window where a power loss right…
Jul 27 · 2 changes
- Progress and augmentation documents now reject an unrecognized field instead of ignoring it. Before, renaming or removing a field in a future build would let old…
- A malformed virtual card in a progress document no longer vanishes silently (it was decoded best-effort and dropped on failure); the whole document now fails to load…
Jul 28 · 1 change
- Breaking (pre-1.0): deck ids are now self-describing and prefixed. A deck declares id: "deck-<token>" (the id: frontmatter key replaces alix-id:), and every card marker…
Jul 28 · 1 change
- Tutor and exam grounding now combines frozen evidence with the live source: values, deck and workspace layered apart (deck sources are the primary grounding, the…
Jul 28 · 1 change
- The picker's focus-drawer heatmap no longer renders a card met in an acquire pass (seen, not yet graded) the same as a card never touched. Such cards now show a dim…
Jul 28 · 27 changes
- The empty "Nothing due." session screen now shows one quiet line saying when the next card is due (for example "Next due in 4 min." during an acquire cooldown), so an…
- alix profile: define and launch a named alix instance per person (its own decks, port, and adult/kids frontend), reachable on your LAN with a stable token so phones can…
- Multiple-choice cards you author directly: write the answer as a GitHub task list (- [x] correct, - [ ] distractors). It renders as a checklist in any Markdown previewer…
- card text now renders inline Markdown: bold, *italic*/_italic_, and code .
- LaTeX math in cards: $...$ renders inline and a whole-line $$...$$ renders as centered display math in adult web, kids web, and mobile. Formula clozes support…
- Committed manual-QA examples for graphical math rendering and a self-contained workspace with a frozen Rust ownership trace.
- the picker's focus drawer now shows a deck's preamble (the prose written under its # title), which was parsed but never surfaced before
- alix doctor flags a dangling requires: (one naming a deck that does not exist), so a renamed or deleted prerequisite is reported instead of silently dropping the gating…
- alix doctor warns when a card's <!-- id: --> marker is not the card's closing line (the position stamping mints at), so a hand-placed marker drifts back to the canonical…
- Trace source excerpts now highlight exact, case-sensitive terms that the checkpoint author marked as inline code in its key points.
- Additive (web API): card display projection now comes from the shared Rust core. InlineRun gains optional math, CardDto gains context_runs, and StateDto gains…
- Additive (web/mobile APIs): trace walk state now carries inline-run projections for its description, checkpoint prompt, givens, key points, and note alongside the…
- Mobile review now consumes the core's shared inline runs for bold, italic, code, and LaTeX math instead of rendering raw card strings separately.
- Android release builds now size-optimize the embedded Rust core with fat LTO, one codegen unit, and stripped symbols. make aab produces the Android App Bundle for Google…
- Inline code ( like this ) now renders with a distinct, theme-aware color for readability.
- Breaking (pre-1.0): inline */_/ in existing card text now renders as emphasis; a deck that used them literally (e.g. 2*3*4) will render/grade with the markers stripped…
- Breaking (web API): CardDto.front and CardDto.back now contain inline-marker-stripped content, while the new front_runs and back_runs fields carry display formatting…
- the picker's focus drawer now opens for every deck, not only decks with a topology augmentation, and shows a per-card retrievability heatmap: a single whole-deck bar for…
- Breaking (web API): the drawer no longer shows a raw due count. POST /api/deck-topology is renamed POST /api/deck-drawer; its response DeckTopologyDto becomes…
- Short fact-card source excerpts now keep the answer region's centered vertical alignment; excerpts still top-align when they overflow.
- Fact-card citations and trace walks now share the same editor-style, path-labelled source excerpt instead of using two visually inconsistent renderers.
- Trace walks now render authored inline Markdown in their description, checkpoint prompt, givens, key points, and note on adult web and mobile instead of showing raw…
- Multiple-choice options now receive a fresh shuffle seed for each study session, so a card's correct answer does not return to the same memorized position every time the…
- alix doctor now reports malformed recognized LaTeX with its deck, card line, source snippet, and renderer error. CLI, desktop-server, and paired-mobile generation reject…
- Authored checkbox answers and distractors now retain their inline formatting source for display while duplicate detection and typed grading continue to use…
- A formula that is the only inline run on its logical line now renders larger across adult web, kids web, and mobile, while math embedded in prose keeps its previous…
- Editing a card's content now invalidates its cached AI augmentations (distractors, note, questions, key points, and the reshaped answer). Previously a cached output…
Jul 28 · 33 changes
- alix doctor no longer tells you to "restore from a backup" that Alix does not make; it now points at moving the file aside or restoring the folder from your own backup…
- promoting a remediation card to a real one no longer risks a duplicate: the promotion wrote the card into the deck file but the matching removal from the in-session…
- the web listing no longer re-parses unchanged decks on every request: the server keeps a per-file cache keyed on (mtime, size) and re-reads only files that actually…
- starting a review or walk no longer re-parses the whole collection to resolve the deck name: name resolution (/api/select, /api/browse, /api/generate, share/receive…
- the picker no longer shows an empty page on the first (cold) load, needing a reload to appear: static assets (the page shell, fonts, css, js, and the key endpoints) are…
- the deck listing was quadratic in collection size (each loose deck probed its parent folder as a workspace, and that probe read every sibling deck); a 325-deck folder…
- Your decks folder is self-contained: drop it in a cloud drive (Dropbox, iCloud, Syncthing) for roaming multi-device (one device at a time), no accounts.
- /api/decks rows now carry selectable — whether the row's name can be sent to /api/select (decks: yes; workspace/folder rows: no). Clients no longer have to infer it from…
- Group rows in /api/decks no longer report reviewable unconditionally: a workspace/folder row now aggregates its members, and a deck that fails to parse reports nothing…
- An examples/gh-review-prep.rs showing how to compose the library into an ephemeral, goal-scoped workspace for understanding a change you must read closely (a GitHub PR…
- Web picker: a workspace's goal shows in its drill-in. Opening a workspace now shows its goal (the one-line description) under the title eyebrow, the same goal the…
- Web picker: a drawer indicator. A quiet ▾ at the bottom-centre of a picker row marks a deck that has a focus drawer (a cached topology), so you can see at a glance which…
- Retirement is now interval-based. A card retires (rests until alix reset) once its FSRS interval reaches retire_after (default 1 year, configurable, never to disable) —…
- alix serve --lan now requires the pairing token on /api/* (auto-generated unless you set one). The HTML shell, theme assets, and images stay open — only the JSON API is…
- Breaking — config keybindings are namespaced under [keys]. Every key table is now a [keys.*] subtable: [keys] → [keys.review], [picker] → [keys.picker], and [browse] →…
- Review cards settle a short answer below the midline. The answer region now grows to fill the space between the question and the note and centers its content when it…
- Multi-line review answers left-align by default. An answer with more than one line (a list, or several sentences) now renders as a left-aligned block, centered as a…
- The web UI header shows an animated alix wordmark. The lightning-bolt mark in the review/picker and trace-walk headers is now a self-contained <alix-logo> web component…
- Trace walk is now an in-page mode of the web review UI. Picking a trace from the deck-selection screen no longer navigates to a separate /walk page — the walk runs…
- Web: Backspace leaves the augment view. The augment screen accepted only Esc to return to the picker, while browse and the picker also honour Backspace — so Backspace…
- Web picker: the header buttons are legible on light themes. The ☰ menu and the ← / ⟳ nav buttons used the muted --dim colour, too low-contrast on some light themes (e.g…
- Web picker: clicking empty space keeps keyboard focus. A click anywhere in the picker area that isn't a row or control — including the margins around the centered list…
- alix explore --build freezes cited excerpts more reliably. When a generated % at: locator dropped (or added) a leading subdirectory — e.g. chapter.md when the file is at…
- Workspace icons in the web picker. A workspace can show a small emblem next to it in the picker for quick recognition. Generated as an abstract SVG by alix explore…
- Web UI theme gallery — alix's own themes plus popular editor/slide palettes. The web frontend (--serve) ships a gallery of colour themes: the alix Dark/Light originals…
- Reshaped list answers show as a left-aligned bullet list. When the format augment turns a crammed prose answer into a multi-item list, the web review and browse views…
- Leitner stage 1 now has a ~5-minute relearn/settle cooldown (was 0). A newly acquired or freshly failed card becomes due ~5 minutes out for the *next* session, so…
- The picker labels a trace by its description, not its filename. A trace row in the picker (web tree and TUI drill-in) showed the raw file stem — a clipped kebab slug…
- A trace --grade reply that isn't a real verdict now errors instead of being scored as a miss. The per-hop grader expects the model to answer NAILED/PARTLY/FAILED; an…
- A missing or stale % source: base fails with a clear message. A directory % source: that no longer exists used to have the locator joined onto it, yielding a baffling…
- Frozen-snapshot excerpts show the original file and line numbers. A walk or fact card whose % source: is a frozen assets/ snapshot showed the asset (30.rs, lines 1-N)…
- A trace/fact citation against a single-file % source: no longer doubles the path. When % source: is one file, every % at: reads *that* file; a locator that repeats the…
- A fact card's % at: citation resolves against a multi-file % source:. A deck whose % source: joins several files with + (the generator's format, e.g. <crate>/README.md…
Jul 28 · 1 change
- The multiple-choice quiz no longer highlights a hovered option like the keyboard-focused one; keyboard focus is the single highlight and the mouse gives no visual state.
Jul 28 · 2 changes
- A Recognize session now paces first contact like every other depth: at most max_new never-met cards enter per session, and an explicit --new is honored there (it was…
- A cached review order (alix deck augment --target order) now applies at Recognize; the walk sorts the session before any limit truncation, so a limit keeps the…
Jul 28 · 2 changes
- Picker rows (decks and workspaces alike) no longer light their frame on mouse hover; keyboard selection is the single frame highlight.
- A multiple-choice card starts with its first option focused, so Enter or the nav keys act immediately instead of needing a first keypress to focus.
Jul 28 · 2 changes
- The picker's focus drawer shows a nested progress funnel pinned to its top-right: N cards always, then s seen, k learned, and r retired appended as each count becomes…
- A picker row no longer prints a right-aligned status counter for a new or started deck: "new" duplicated the NEW chip, and a started deck's k/N graduated counter was…
Jul 29 · 2 changes
- "Seen" now means the card was shown to you at least once, right or wrong: the first time a card becomes the displayed card in any session, the store records a one-time…
- The drawer and breadcrumb heatmaps paint five tiers instead of a retrievability gradient: neutral untouched, grey seen, white acquired, a learned (graduated) card…
Jul 29 · 1 change
- Session pacing is now two [review] keys: max_session (cards a single sitting serves, default 10) and new_cards_percent (the new-card share of that cap, default 30). The…
Jul 29 · 2 changes
- The kids client and the mobile app now show the persistent "progress isn't saving" banner the adult web client already had. On mobile a failed per-grade save no longer…
- The server no longer dies when the consumer of its stdout goes away (for example alix ~/decks | head, or a supervisor closing the pipe after the URL line): the startup…
Jul 29 · 1 change
- An unreadable decks folder (deleted, renamed, or pointing at a plain file) no longer masquerades as an empty catalog: GET /api/decks now answers 500 and logs the cause…
Jul 29 · 1 change
- GET /api/doctor no longer holds the application state lock while it probes the backend and wormhole binaries for their versions: the lock is held only to snapshot the…
Jul 29 · 1 change
- A progress save that keeps failing no longer lets a deck switch silently discard the unsaved session: select, browse, deselect, reset, exam start and close, walk leave…
Jul 29 · 1 change
- A passed or failed exam's progress write no longer saves silently outside the save-error accounting: a transient failure now shows the "progress isn't saving" state and…
Jul 29 · 1 change
- Two imports of the same deck name no longer race each other (or a concurrent receive or generate landing) for the destination: every destination write now runs on one…
Jul 29 · 1 change
- A tutor exchange that finishes after the card has already advanced is now discarded: the late answer no longer appears under the next card's transcript, and a late note…
Jul 30 · 1 change
- Breaking: every card-relative review POST (/api/grade, /api/skip, /api/acquire, /api/check, /api/choose, /api/remove, /api/promote, /api/restart, and the four tutor…
Jul 30 · 4 changes
- A rejected exam start, deck selection, or browse no longer swaps in the progress store it validated against: validation runs on a candidate and the store is installed…
- The picker no longer reopens progress from disk for the workspace that is actively being studied: the listing reads the study owner's own view, so a deck mid-review…
- Shutting the server down now cancels an in-flight tutor call and reaps its subprocess (advancing past the card or replacing the question does the same), so a Ctrl-C…
- A catalog listing can no longer be served from a build whose inputs went stale while it ran: a refresh carrying newer progress leads its own build instead of joining the…
Jul 30 · 1 change
- alix generate now accepts --language, --audience, and --card-style (mixed, plain, cloze, or authored-choices), and applies --goal to single decks as well as directory…
Jul 30 · 4 changes
- The picker no longer reopens an inactive workspace's progress from disk once that workspace has been studied this run: the progress owner retains a projection of every…
- A rejected augment open (for example over a duplicate card token) no longer swaps in the progress store it validated against: like exam start, select, and browse before…
- Cancelling an AI call now kills the backend's whole process tree, not just the CLI itself: the child starts as its own process-group leader and cancel signals the group…
- Shutting down while a paired client's tutor request is in flight now cancels that subprocess too: the remote ask path kept no cancellation handle, so the server could…
Jul 30 · 1 change
- A standalone orchestrate research CLI runs Claude Code and Codex against one frozen spec in isolated worktrees. Symmetric differential review accepts only mechanically…
Jul 30 · 3 changes
- alix reset --orphans <folder> now finds orphans when the folder holds exactly one live deck. It had opened only that deck's own progress document, so a leftover…
- alix reset --orphans now aborts when a deck-like file in the target cannot be parsed, instead of judging that deck's live progress orphaned and deleting it. Decks still…
- alix reset --orphans names a target that is neither a deck file nor a folder instead of reporting it as unlistable.
Jul 30 · 1 change
- alix generate now reports calm, live progress from structured Claude and Codex events while keeping partial deck text private until validation succeeds. Deck-drafting…
Jul 31 · 1 change
- alix generate now checks the output destination before calling the AI backend. Pointing it at a workspace that does not exist, or at a deck name already taken without…
Jul 31 · 2 changes
- alix profile list now shows each profile's config file path, so an unexpected decks directory or port can be traced to the file that set it.
- Reading session state no longer moves you to a different card. Sitting on one card for longer than the five-minute acquire cooldown (asking the tutor, for example) let…
Jul 31 · 1 change
- CardDto now carries the card's id on the wire (card-<token>, or the -N / -r sub-id for a cloze hole or reversed twin), the same spelling CreateCardResp returns. Clients…
Jul 31 · 3 changes
- Decks carry a format-version: in frontmatter, written above id:. alix deck init writes it, it stays 1 before 1.0, and alix refuses any other number with a message…
- Frontmatter now reads authors, license, tags, and created-at. authors and tags accept one value or a list, license and created-at are single strings (an SPDX identifier…
- Breaking: an initialized deck (one with an id:) must now also declare format-version: 1. A deck without it fails to load and alix doctor names the conversion tool. Decks…
Jul 31 · 1 change
- The tutor panel names the model that actually answered instead of showing model: default. When [ask] model is unset the backend CLI chooses, and alix could not name it…
Jul 31 · 2 changes
- The picker keeps keyboard focus when you click outside the deck list, so the row-navigation keys keep responding instead of going silently dead. Only clicks inside the…
- A submenu's Cancel now sits where Back does, on the left, instead of trailing the depth chips on the right. It is the same "leave this level" action Esc performs, so it…
Jul 31 · 1 change
- alix deck init no longer writes a second format-version: into a deck that already declares one. The duplicate is an invalid YAML mapping key, so the deck stopped loading…
Jul 31 · 1 change
- Cloze cards read as fill-in-the-blank. The gap you are answering is a chip with a single rule on the baseline instead of four separated underscores, the other holes are…
Aug 1 · 1 change
- Jumping to the last deck with G/End now reveals its drawer. The drawer is fetched after the jump, so it opened below the fold and looked like it had not opened at all.
Aug 1 · 1 change
- The picker no longer stays blank forever when a start-up request stalls. Boot already retried a *failed* request, but a request that never answers is not a failure…
Aug 1 · 1 change
- Breaking: POST /api/choose now takes {index, card}, where card is the card.id of the card the pick was made on. A pick naming any other card is refused with 409 and…
Aug 1 · 1 change
- An exhausted Recognize sitting now says what it was hiding instead of "Nothing due — come back later": a deck whose pick-capable cards are all recognized reports how…
Aug 1 · 1 change
- Revealing a new card's answer now counts as the encounter: leave the session right there, without pressing Seen, and the card still will not re-introduce as new next…
Aug 1 · 1 change
- The blank first load. A connection burst against a fresh or idle server (typically a page reload right after the server starts) could leave one accepted connection's…
Aug 1 · 1 change
- Desktop release downloads are now verifiable: every release asset uploads with a .sha256 checksum beside it, and releases are gated on a clean RustSec advisory scan of…
Aug 1 · 1 change
- alix --port 0 (OS-assigned port) now announces the port the kernel actually bound instead of printing an unreachable http://127.0.0.1:0 URL. Explicitly requested ports…
Aug 1 · 1 change
- Resetting a workspace deck over the API now reaches the deck listing immediately. Previously the listing could keep serving the pre-reset progress (deck "started"…
Aug 2 · 1 change
- Deck lifecycle completes with removal and restore. alix deck remove <deck> deletes a deck and everything that is its alone (file, review history, frozen assets…
Aug 2 · 1 change
- POST /api/choose read its request body straight off the socket with no size cap, the one JSON route that bypassed the central 256 KiB body cap; a client could grow the…
v0.7.0 · 2026-08-02
Tagged release. Full notes below.
Aug 2 · 1 change
- Old deck formats are no longer recognized anywhere. The dedicated retired-key errors (alix-id:, origin:), the doctor's "un-converted" classification of decks and state…
Aug 2 · 2 changes
- Multiple-choice option text was near-invisible on the gruvbox-light and catppuccin-latte themes: ten themes omitted the text/faint/accent-ink/ brand-text tokens and…
- The augment dialog's footer no longer places the destructive "Remove all" between "Generate selected" and "Close"; it now sits first, so a reach for Close cannot land on…
Aug 2 · 1 change
- Saving a tutor note onto a stamped card appended it after the card's closing <!-- id --> marker, tripping doctor's misplaced-marker warning on every noted card. Notes…
Aug 2 · 1 change
- generate's max_cards is now a soft ceiling with a default of 100 (was a hard-worded 30): the prompt aims for the configured count, and a generation that comes back…
Aug 2 · 2 changes
- The kids app behaves on touch screens: a long-press no longer opens the browser's context menu, page text cannot be selected or grow a blinking insertion cursor, taps…
- A kids card with a picture asks the question above it, matching the authored front order and the adult app, instead of hoisting the image over the question.
Aug 3 · 1 change
- The retired reveal = "cloze" value is rejected in a workspace manifest's [defaults] too, the one scope that still accepted it, and the workspace init template stops…
Aug 3 · 1 change
- The adult web picker can remove a focused loose deck, workspace member, or whole workspace from its secondary menu. A type-the-name sheet previews the irreversible…
Aug 4 · 1 change
- Card tables: a GitHub-flavored pipe table in a deck is a compact card source, one card per row (front | back | optional note). The header row names the columns and is…
Aug 5 · 1 change
- alix deck init writes the prepended frontmatter block closed directly after the id: line, with the blank line following the block instead of sitting inside it.
Aug 6 · 2 changes
- Table cards draw their Recognize options from their own column, so a vocabulary table needs no AI augmentation and no authored options (both still take precedence where…
- Tables accept the card directives (direction, reveal, input, sampling) between the table and its id line. Anything the format cannot hold (a fourth column, cloze markers…
Aug 6 · 1 change
- A filesystem error after an atomic rename no longer strands progress or augmentation behind a stale in-memory revision: the error still surfaces, while retry bookkeeping…
Aug 6 · 1 change
- A new card whose answer was revealed before the "Seen" acknowledgment no longer returns as a new card for the rest of the sitting. It came back after every introduction…
Aug 6 · 3 changes
- The session summary places the sitting against the whole deck: the "introduced" row now also reads how many of the deck's cards have ever been met, out of how many it…
- The session summary no longer starts the next card by itself. When a settle gap passes while the summary is open it says it is ready and arms Continue, leaving the…
- The summary's "Next due in N min" counts down while the page stays open instead of freezing at the value it had when the sitting ended.
Aug 6 · 1 change
- A new card's badge now names its interaction too, not just "new": new · choice when it offers options, new · draw on a sketch card, new · reveal otherwise. It still…
Aug 7 · 2 changes
- Each running web-server instance now keeps an always-on local diagnostic log outside the decks directory. The current and one rolled file are capped at 5 MiB each by…
- make web-debug now runs the ordinary server with --log http, which keeps verbose request timings in the local log and mirrors them to stderr.
Aug 7 · 2 changes
- Deck authoring has a written rule for which card shape suits which material, and one worked example per shape. docs/include/card-shapes.md marks each row structural (the…
- Generated decks now pick a card shape from the material rather than choosing between plain and cloze. alix deck generate can produce card tables, line-by-line reveals…
Aug 7 · 1 change
- The example images now show the card as the reader should read it. A choice example is photographed answered, because the cursor rests on option 1 and a picture of that…
Aug 8 · 4 changes
- Personal notes and cards now live in a file of their own beside the deck (spanish.md gets spanish.personal.md), leaving the authored deck byte-identical. Tutor notes…
- Breaking: remediation and tutor cards are no longer stored inside the progress file. They are Markdown blocks in the deck's personal file, so you can read, edit, and…
- Breaking: the "Promote to deck" review action is gone, with its /api/promote endpoint, its StateDto.promotable field, and its [keys.review] promote binding. A minted…
- alix reset clears a personal card's schedule and leaves the personal file alone, the same treatment the authored deck gets.
Aug 8 · 1 change
- alix doctor now reports a frozen source excerpt whose lines moved, and --repair-source-locators rebases it. Previously a moved excerpt was found anywhere in the file and…
Aug 8 · 1 change
- Stamping a deck that ends in a card table with no final newline put the row's stamp on a line of its own instead of in the row. The row then read as unstamped, so every…
Aug 8 · 1 change
- alix deck init refuses a deck whose code fence never closes, instead of writing an id line inside the fence. The card then still read as unstamped, so every later stamp…
Aug 8 · 1 change
- Stamping never leaves a deck alix cannot read. The result is parsed before anything is written, and a stamp that would produce an unreadable file is refused with the…
Aug 9 · 1 change
- alix doctor reports a cloze block whose > note spells out one hole's answer. Every hole of a block shows the same note, so reviewing a later hole reveals an earlier…
Aug 9 · 1 change
- Two blanks of one card that share a name are drilled as a single card asking both spans, so a fact split across two blanks is no longer answered in halves. They need not…
Aug 11 · 1 change
- alix bug-report writes a local, reviewable diagnostics archive, and the adult web app offers the same download from About. It includes bounded current and rolled logs…
Aug 17 · 1 change
- A typed card with several expected answers can no longer be passed by answering only some of them. Grading returned one result per submitted line, so an unanswered line…
Aug 18 · 1 change
- Recognize is now an ordinary scheduled depth (ADR 0033). A recognition answer creates and extends its own FSRS schedule at a laxer desired retention ([review]…
Aug 18 · 1 change
- A card's tier now reflects what the learner did (ADR 0035). The published seven-value vocabulary becomes unseen | seen | learning | learned-weak | learned-fading |…
Aug 19 · 1 change
- Image regions (ADR 0034): a deck can hide a region of a picture and ask what is under it. blank:, cover:, and crop: directive comments carry a closed named-field grammar…
Aug 19 · 1 change
- An image now owns its line: prose or a cloze hole sharing a line with an image is a parse error. The display model always tore such lines apart silently (the sentence…
Aug 19 · 1 change
- The mask markers are now reserved codepoints: an asked blank shows as ⍰ and a hidden one as ⬚ (matching the boxed-? a masked formula renders), and authored deck text may…
Aug 20 · 1 change
- An unterminated \verb in a formula (\verb|abc with no closing delimiter) is now a loud math error on every rendered surface instead of silently rendering with the last…
Aug 20 · 1 change
- The kids web app handed its error toast and its tutor unbound setTimeout/setInterval references, which browsers reject with Illegal invocation when called as plain…
Aug 20 · 1 change
- Mermaid diagrams render into decks: a mermaid fence in a workspace member freezes at alix deck init time into a deck-owned PNG plus a label-geometry file (rendering…
Aug 21 · 1 change
- alix reset <target> opened the configured decks folder's progress store before looking at the target, so one unreadable file there blocked resetting an unrelated deck or…
Aug 21 · 1 change
- A full-target alix reset no longer parses the progress it is about to discard: each target deck's document is opened alone (sibling documents in the same store are never…
Aug 21 · 1 change
- A deck whose own progress document cannot be read now shows as a red error row in the picker (web, and a red error marker on mobile) with a generic explanation line…
Aug 21 · 1 change
- Resetting one workspace deck's progress now reaches the picker immediately even when a workspace-wide augment view had been opened in the same run: the listing…
Aug 21 · 1 change
- A deck whose cards are all inside the introduction cooldown can be crammed again from the web picker: the depth control stayed disabled once nothing was due, so the deck…
Aug 22 · 1 change
- Headings are study structure, not deck metadata (breaking). A # heading is now the SECTION CONTEXT for the cards beneath it, ### and #### are SUB-CARDS of the card…
Aug 22 · 1 change
- The advertised curl -sSf https://alix.study/install.sh | sh install has been failing since 2026-08-03. GitHub's /releases/latest is repo-wide, and the mobile-v0.3.0 APK…
Aug 22 · 1 change
- Heatmap, topology and crumb cells are objects now (breaking wire change). A cell was a bare tier string; it is {tier, locked}, where tier keeps the same seven values and…
Aug 21 · 1 change
- The prebuilt installer now resolves one immutable release tag and verifies the downloaded archive against that release's SHA-256 record before extracting or installing…
Aug 22 · 1 change
- Every shipped deck now declares its name in frontmatter. The tutorial, the four decks bundled with the mobile app, and the nine committed examples carried an # heading…
Aug 22 · 1 change
- A deck body starts with a heading (breaking). A line that sits before the first # or ##, outside any card, is now a parse error rather than silently becoming the section…
Aug 22 · 1 change
- A Recognize sitting no longer ends in "you reviewed 0 cards": a Recognize grade is an ordinary FSRS review and lands in reviews/passed/failed with every other depth…
Aug 22 · 1 change
- Adult review now draws a visible note-coloured divider between a revealed answer or source excerpt and its authored note. The generic hairline blended into the card…
Aug 22 · 1 change
- Adult review no longer repeats that a newly introduced card will be quizzed in about a minute after its answer is revealed. The NEW mode badge and Seen action already…
Aug 22 · 1 change
- Adult deck filtering now closes the focus drawer when its selected row no longer matches. Previously an empty result could still show the hidden deck's description and…
Aug 22 · 1 change
- Deck lists now order numeric runs by value, so numbered titles appear as 10, 11, 100 instead of 10, 100, 11. The same comparison governs root entries and dependency-tree…
Aug 22 · 1 change
- A never-seen ordered card in the adult app now reveals one authored line at a time. Reveal next remains available until the sequence is complete, and only then changes…
Aug 22 · 1 change
- Adult ordered-card reveals now keep earlier lines fixed while later lines appear below them, instead of recentering the growing answer upward after every step. The…
Aug 22 · 1 change
- Adult draw cards now place the learner's frozen sketch beside the expected answer in labeled comparison panes. Narrow screens stack the same panes so neither side…
Aug 22 · 1 change
- Choice-card notes can no longer silently describe the wrong option after a shuffle. generate and AI note augmentation reject position-dependent notes, while doctor…
Aug 22 · 1 change
- Keyboard navigation through a long adult multiple-choice list now keeps the focused option clear of the answer region's overflow fades and hints at both edges.
Aug 22 · 1 change
- Long notes in adult review now show edge fades and more below or more above pills, so hidden parts of an authored explanation remain discoverable and reachable while…
Aug 22 · 1 change
- Adult review cards now use the height available between the header and footer on tall windows, keeping the mode and question near the deck title and the authored note…
Aug 22 · 1 change
- format-version is now a reserved key; a deck that does not declare it is format version 1 (breaking). alix deck init and the personal sidecar writer no longer write the…
Aug 22 · 1 change
- The adult picker's action footer no longer shifts when focus moves between decks whose early exam is available and decks whose exam is still locked.
Aug 22 · 2 changes
- alix doctor --repair-frontmatter-order rewrites each checked deck's frontmatter into the canonical key order. Opt-in only; frontmatter it cannot safely permute (a blank…
- Frontmatter alix writes follows one canonical key order: authored keys first (title, description, directives, source/link), machine lines last, so a minted deck id now…
Aug 22 · 1 change
- The adult picker now shows loose decks that have never been opened alongside workspace groups. Previously they existed in the search results but were invisible in the…
Aug 23 · 1 change
- Bare content shapes render literally (breaking). A task list or pipe table without an invocation (or deck default) no longer becomes a choice card or card table: it…
Aug 23 · 1 change
- Long section context no longer replaces the adult review question and pushes the card's answer choices down the page. A compact control below the divider now swaps…
Aug 23 · 1 change
- Frontmatter holding more than one YAML document is rejected loudly. A mid-line carriage return could smuggle a --- document separator past the line scanner; the stamper…
Aug 23 · 1 change
- Code fences follow the CommonMark closing-length rule: a fence closes only on a delimiter of its own character at least as long as its opener. A four-backtick fence can…
Aug 23 · 1 change
- Inline backslash escapes follow the CommonMark punctuation rule: a backslash before any ASCII punctuation character yields the literal character (it previously worked…
Aug 23 · 1 change
- ~~strikethrough~~ renders struck through on every client, as GitHub renders it: pure presentation with no study semantics, and grading keeps comparing the unstruck…
Aug 23 · 1 change
- Sub-cards now stack to ######: heading depths 5 and 6 are sub-card depths 3 and 4 under the same one-level-parent rule, so every ATX depth has a meaning. Seven or more…
Aug 23 · 1 change
- Bare pipe tables render as real aligned tables on every card surface (adult web, kids fronts, mobile): alignment colons in the delimiter row set column alignment, short…
Aug 23 · 1 change
- A fence-shaped line carrying an info string (like a nested rust opener quoted inside a longer outer fence) no longer closes the fence during review rendering. The…
Aug 23 · 1 change
- Markup-rejection parse errors are onboarding surfaces: every message that fires on ordinary pasted Markdown (an answerless heading, a deep heading without its parent…
Aug 23 · 1 change
- Two consumers of the card-depth rule missed the depths 5/6 widening, found by Codex during reciprocal review: the fast duplicate scan at review open ignored copied…
Aug 23 · 1 change
- Four reserved Markdown shapes are now line-numbered errors with a suggested rewrite instead of silently showing their markers as card content: setext === underlines…
Aug 23 · 2 changes
- The kids reviewer now shows a bare-table note after reveal, found by Codex during reciprocal review: the table renderer existed but was never handed to the kids study…
- A card table whose delimiter row is narrower than its header gets its own diagnosis naming the valid rewrite (a --- cell per header column), found by Codex during…
Aug 23 · 1 change
- The math spellings aligned with GitHub (decision 11). Display math gains its two block spellings: a bare $$ line opens a block that the next bare $$ line closes, and a…
Aug 23 · 1 change
- HTML tag shapes are a loud parse error (decision 10, first slice): a < directly followed by a letter, or </, on any deck surface (content, sections, fronts, notes, table…
Aug 24 · 1 change
- Autolinks render as inert links (decision 10, second slice): <https://...> and <user@host> display as the bracket-free URL, styled as a link on all three clients with no…
Aug 24 · 1 change
- The typing-mode character reveal is removed (ruled 2026-08-24 with the aid naming: revealing answer characters piecemeal only slow-fails the card, and partial recall…
Aug 24 · 1 change
- The styled HTML subset renders (decision 10, third slice): <sub>, <sup>, and <ins> pairs display as subscript, superscript, and underline on all three clients, with the…
Aug 24 · 1 change
- A doubled backslash before an image marker no longer produces a silent third state, found by Codex during reciprocal review: the tag-shape carve-out judged \\!d a live…
Aug 24 · 2 changes
- Multi-backtick code spans keep their body as code in the display projection, found by Codex during reciprocal review: the glyph scan paired a backtick to the next single…
- Mobile renders subscript below and superscript above the baseline, found by Codex during reciprocal review: both had been drawn as small text on the normal baseline, so…
Aug 24 · 1 change
- HTML entities decode on display (decision 10, fourth slice): the full HTML5 named set (&, €, all ~2,100 names) plus the numeric A and hex A forms…
Aug 24 · 1 change
- Links display as their labels (decision 8, first slice): label renders the label styled as an inert link on every card surface, with the brackets, parentheses, and…
Aug 24 · 1 change
- Link-definition lines ([label]: destination) are consumed as deck-wide metadata instead of being answer content: they no longer display, never join typed grading, and a…
Aug 24 · 1 change
- A span blank no longer crashes a deck whose answer block contains an HTML entity, found by Codex during reciprocal review: the masking source map had assumed every…
Aug 24 · 1 change
- BREAKING: comment machinery trails its block (everything-trails). A mapping invocation (<!-- cards -->, <!-- plain -->, <!-- choices: single -->, <!-- choices: multiple…
Aug 24 · 1 change
- alix doctor --repair-comment-order rewrites each checked deck's trailing comment machinery into the canonical order: invocation, directives, region comments, locator, id…
Aug 24 · 1 change
- Link display follows the GFM inline-link grammar at its edges: a nested bracket pair stays inside the label ([array[index]](url) links) while a nested link beats the…
Aug 24 · 1 change
- A deck or store file reached through a symlink survives being saved: the replacement lands on the link's target instead of renaming over the link and forking the two…
Aug 24 · 1 change
- BREAKING: a note is a badged blockquote, and every other blockquote is a quote. A blockquote whose first line is exactly one of GitHub's five alert badges ([!NOTE]…
Aug 25 · 1 change
- A cloze card keeps its block's at: source citation, found by Codex during reciprocal review: the expansion built one card per hole and copied every other field but the…
Aug 25 · 1 change
- Emphasis follows CommonMark's flanking rule on both sides of a run, so * and _ next to punctuation no longer style text GitHub leaves literal. a.tail, a*.tail*, and a(x)…
Aug 25 · 1 change
- A card's note carries its GitHub alert badge to every client. CardDto.note is now a list of note objects ({badge, units}) rather than a flat list of display units, so a…
Aug 25 · 1 change
- A card carries several notes rather than one. A second badged blockquote used to be swallowed into the first note's body, with the last badge silently winning; each…
Aug 25 · 1 change
- A display reshape is keyed on the notes it was generated from. Adding a second alert to an already reshaped card used to leave the older reshape in place, which after…
Aug 25 · 1 change
- A bare blockquote in an answer is a quotation, and every client renders it as one. back_units gains a quote unit carrying its own units, so a quotation can hold prose…
Aug 25 · 1 change
- A reveal: line card at Reconstruct asks for every line, not just the first. The check returned a result for every answer line however few the learner had submitted, and…
Aug 25 · 1 change
- A card whose answer an AI reshape replaced can be typed again. Every client shows display_back, while the check graded the authored answer, so a learner typing exactly…
Aug 26 · 2 changes
- A card carries answer_steps: the steps a client walks its answer in, one per gradeable line and one per quotation run. Reveal counts every step and typing counts only…
- POST /api/check takes one line per gradeable step, not one per answer line. A quotation owns no field, so a client that sent a blank for it is now one field too many…
Aug 26 · 1 change
- A format reshape changes what a card SHOWS, never what a typed check expects, which is what the manual has always promised. A card is now served in one answer space: a…
Aug 26 · 2 changes
- TypeLine on a phone asks for one answer line at a time, as it always has in a browser. Mobile opened every gradeable field at once and submitted them all on the first…
- A second fenced block after a line-reveal boundary renders its own diagram rather than repeating the first one. The answer was rendered in two passes and the second pass…
Aug 26 · 1 change
- A one-line quotation renders as quoted content in the kids client too. The render path was chosen from the raw answer line count, so a card whose whole answer is a…
Aug 26 · 1 change
- The tutor's reference card shows a quotation as quoted content rather than as its > markers. The reference walked raw answer lines, so it kept the markup the review…
Aug 26 · 1 change
- Badged notes and quotations are styled. Each of the five badges opens its own callout with a chip naming it, tinted from the theme you are using rather than from…
Aug 26 · 1 change
- A supporting quotation is no longer graded as a claim the learner owes. Three surfaces still derived their gradeable items from the card's raw answer lines: the Explain…
Aug 26 · 1 change
- Every adult surface that shows a WHOLE answer renders a quotation as a quote block. Only the progressive line reveal walked the answer's steps; the Explain reveal (with…
Aug 27 · 1 change
- Breaking (deck format): the section terminator is gone; a bare # resets the context instead. A section used to end early at a --- alone between blank lines. That…
Aug 27 · 3 changes
- Breaking: AI walk grading is removed, including its [trace] auto_grade config key. A trace walk is self-judged on every client, as the phone already was. The feature was…
- Removing a card no longer overwrites edits made to the deck since the sitting opened. A sitting caches the deck text at open and replays its removals over that text, so…
- Opening a deck can no longer re-mint another deck's card token on the word of the review-open line scan. That scan trades full parses for speed and cannot know a file…
Aug 27 · 3 changes
- Copying a card inside one deck no longer detaches the original from its review history. The duplicate resolver addressed the losing card by token alone, and the first id…
- A region- or topology-scoped sitting's crumb no longer reports a locked card as unlocked. The crumb paints every region of its topology, including cards the sitting was…
- Continuing at Recall from the summary keeps the sitting's scope. A session opened on one review order, or on one region of it, was re-selected without either, so the…
Aug 27 · 1 change
- Two shipped documentation examples that did not parse are corrected. The README's headline deck wrote A Vec<u8>, its bytes on the heap., which the HTML doctrine refuses…
Aug 27 · 2 changes
- A card carrying two id: directives is refused instead of half repaired. The parser took the last one as the card's identity while a rewrite targeted the first, so a…
- Opening a deck resolves every duplicated card in it, not just the first. A card pasted twice inside one deck left the second copy sharing an identity until some later…
Aug 27 · 2 changes
- A deck that changed while alix was scanning is no longer written on the strength of that scan. The directory scan that names a duplicate's loser takes over a second on a…
- A duplicated cloze card or card table is repaired instead of reported as already resolved. Duplicate detection compares the composed id a review unit answers to, which…
Aug 27 · 1 change
- alix doctor's repair flags reach every deck the check walked. Pointed at a folder that holds workspaces rather than decks, the check descends into each one and reports…
Aug 27 · 1 change
- A thematic break is one lexical class: three or more of one marker, -, *, or _, with spaces or tabs allowed between them and an indent under four columns. ----, -----…
Aug 28 · 2 changes
- Breaking (deck format): ---, *, and ___ are interchangeable, and a break only divides a front. Position decides what a break means, never which of the three you typed…
- A break line has exactly one legal shape (breaking). A front divider sits directly above its answer with a blank line or the card's own heading above it; any other break…
Aug 28 · 1 change
- A removal whose file replacement completed no longer wedges the rest of the sitting. A deck is written by renaming a finished temporary over it, and the directory sync…
Aug 28 · 1 change
- A break line after a card table is read by the break grammar rather than reported as trailing prose. While a table was active the scanner handed the outer grammar only…
Aug 28 · 1 change
- alix doctor no longer certifies a decks folder it cannot read as healthy and empty. A folder whose permissions changed (a shared drive, a removable disk, a restored…
Aug 28 · 2 changes
- Duplicate card identities are resolved by the id: line a deck file actually holds, not by the review-unit ids it expands into. Two consequences, both found by Codex…
- A duplicate repair rewrites the identity the parser read, never a matching token inside a fenced example. A card teaching deck syntax can show a complete card source…
Aug 28 · 1 change
- The backslash escape reaches every break spelling. \--- was literal text while \* and \___ kept and displayed their backslash, which split a class the same release had…
Aug 28 · 1 change
- alix doctor counts one physical deck folder once, however many symlinks reach it. Exposing a synced or externally located workspace under a deck collection through an…
Aug 28 · 1 change
- Sharing a folder never carries a file out of it. Staging asked is_dir, which follows a link, so a directory link inside the shared folder was recursed into and its…
Aug 28 · 2 changes
- A folder listing offers one physical workspace once. Linking a workspace into a deck collection is supported, and when the original and the alias were both under the…
- alix doctor --remove-backup-files no longer counts one backup twice or fails after deleting it. The scan followed a directory alias a second time and a backup reached…
Aug 28 · 1 change
- Two rows of the directives reference told authors their valid decks were invalid. It named ###/#### and said nothing goes deeper, while ##### and ###### have been…
Aug 28 · 1 change
- The mobile app's sync-conflict list counts one conflict once. The walk behind the bridge's sync_conflicts visited every spelling of a workspace under the root, so a…
Aug 28 · 1 change
- The same boundary holds on every path that copies material into an outgoing tree, not just the folder walk. Sharing a single deck refuses a linked augmentation file or a…
Aug 28 · 1 change
- A shared archive can no longer decide which directory alix deletes files from. A .zip can carry a symbolic link and the extractor recreates it, so an archive whose…
Aug 28 · 1 change
- One physical deck is one row, and one physical sync-conflict document is one thing to resolve, however many names reach it. A workspace or folder holding a deck and a…
Aug 28 · 1 change
- alix doctor checks a workspace member's frozen excerpts even when the deck still holds an unfrozen at: citation. One live citation used to switch off the whole…
Aug 28 · 1 change
- Both ends of the frontmatter fence now accept the same spellings. --- (with a trailing space) closed frontmatter but did not open it, because the opener was an exact…
Aug 28 · 1 change
- A pipe table in an answer is one block, like a quotation. Under <!-- reveal: line --> it takes a single reveal instead of arriving a pipe line at a time, and it is…
Aug 29 · 1 change
- A fence marker inside a display-math block is math source, not the start of a code block. $$, a line of math, a line, then $$ parses as one closed block, but the…
Aug 29 · 1 change
- An AI reply that carries anything after its JSON answer is rejected by name instead of being cut to the last }. The exam and the augment passes both sliced from the…
Aug 29 · 1 change
- A quoted option on an authored choice card keeps its own text. The choice builders ran the checked options through the answer-DISPLAY helpers, which exist for an answer…
Aug 29 · 1 change
- A diagram theme's base fill is read as a colour only when it is one. hex_color split the digits and handed each pair to from_str_radix, which accepts a leading +, so…
Aug 29 · 1 change
- alix workspace update blames the member you wrote, not a staged path you never saw. When a backend reply is not a deck, the run failed with cannot load proposed deck…
Aug 29 · 1 change
- alix workspace update no longer aborts the whole run when one member's proposal is unusable. The member is reported by name with its reason, its staged deck, assets, and…
Aug 29 · 1 change
- A deck's <!-- --> comments are alix machinery, and alix doctor now says so uniformly. The finding used to depend on the comment's length: a one-word comment that named…
Aug 29 · 2 changes
- alix doctor --normalize rewrites a deck that drifted out of canonical bytes after it was initialized, for when an editor put a byte-order mark, CRLF endings, or trailing…
- alix normalizes a deck's bytes whenever it writes one. A leading byte-order mark is dropped, CRLF line endings become LF, and trailing spaces and tabs are removed. A…
Aug 30 · 1 change
- alix workspace augment <dir> --target <...> warms a whole workspace in one go. The card targets (choices, notes, questions, keypoints, format) run as a single batched…
Aug 30 · 3 changes
- Receiving a shared deck, transferring one between workspaces, and merging explored material now write the deck in canonical bytes, as the deck format chapter already…
- Normalization now drops a whole run of carriage returns at end of line, not just one. A deck written with \r\r\n endings kept a carriage return per line through…
- Repairing a duplicate card id when a deck is opened for review writes the deck in canonical bytes. It rewrote only the id token, leaving the rest of the file as it found…
Aug 30 · 1 change
- An AI call on the Claude backend now runs with the tool set alix grants it, and nothing else. Alix passed the grant as --allowedTools, which pre-approves the tools it…
Aug 30 · 1 change
- An AI call now runs without your own instructions for the CLI it shells out to. Every backend reads an operator instruction file (CLAUDE.md, AGENTS.md, GEMINI.md), and…
Aug 30 · 1 change
- An exam labels each source: file by the path the deck declared rather than by the file's basename, so two sources that share a name are no longer indistinguishable to…
Aug 31 · 1 change
- A span blank bound inside a formula reveals its answer as rendered math, as the displayed answer regains its $ delimiters; grading still compares the plain hidden text…
Aug 31 · 1 change
- Breaking: the inline \blank{...} cloze marker is retired. Span and rect blanks (<!-- blank: span hidden="..." --> and its rect sibling) are the only cloze; \blank{...}…
Aug 31 · 1 change
- alix deck init no longer fails on a card whose answer ends in a table: the stamper placed the card's id above the table, which the parser then rejected. A display table…
Aug 31 · 1 change
- A blank span can be named, <!-- blank: span [base] hidden="Unit" -->, and a note line can be written to that name: > base: text gives that blank a note of its own…
Sep 1 · 3 changes
- alix doctor flags a pipe table that declares no mapping: a bare table still renders plain, and the new lint asks for an explicit <!-- cards --> or <!-- plain --> so a…
- Images accept the GFM title (!alt, single-quote and paren spellings too). The title is parsed and ignored for now; previously a titled image failed as malformed and…
- Breaking: a GFM footnote definition line ([^label]: at line start, outside code) is now a hard parse error naming footnotes as unsupported. It previously parsed…
Sep 1 · 1 change
- Deck normalization drops invisible bytes with no rendered role from prose: form feed, DEL, an interior BOM, an interior carriage return, and the two bidi reversal…
Sep 1 · 1 change
- Typed grading draws the ink line: a character that paints no horizontal ink (soft hyphen, zero-width space, joiners, word joiner, variation selectors, bidi isolates and…
Sep 1 · 1 change
- alix doctor reports the invisible characters a deck keeps: one calm per-deck note with counts by class, emoji-aware so bytes inside a well-formed emoji stay silent. It…
Sep 1 · 3 changes
- Named mapping invocations: <!-- choices: single --> or <!-- choices: multiple --> below a task list makes it a choice card (one correct answer, or…
- The select-all wire: a choices: multiple card serves every authored option with choices_multiple: true in the review state, and POST /api/choose {indices, card} grades…
- Breaking: the scheduling key renames from order: to review: scheduled|sequential, in deck frontmatter and workspace [defaults]; order: now fails as an ordinary unknown…
Sep 2 · 15 changes
- The example decks gained one for a blank inside a formula, showing that a <!-- blank: span hidden="..." --> directive works on $...$ and $$...$$ formula lines.
- alix doctor warns when a formula span pinned to input: type hides a LaTeX command, because the span's hidden text is the expected answer and typing \pm asks for the…
- Breaking: generation moved under the noun it produces. The top-level alix generate command is gone. alix deck generate takes a URL, a file, or a directory taken whole…
- Text-span blank cards are Recognize-eligible again, preserving the depth that the inline cloze cards they replace had before retirement. They enter choices augmentation…
- A formula span whose hidden text sits directly against the next token renders again. In a derivative ending nx^{...}, a blank: span hiding the n produced the undefined…
- A formula span now reveals as that formula's piece rather than as its source: hiding \pm with <!-- blank: span hidden="\pm" --> reveals a typeset ± where the source…
- The alix deck generate --review and alix deck augment help texts name the AI backend neutrally instead of hardcoding Claude, the augment progress line names the…
- A malformed or edited alix workspace update proposal can no longer name a Windows root-relative or drive-relative member that escapes the workspace on apply. Every…
- Ask Tutor on adult web now opens for a card whose answer contains a pipe table. The tutor receives the table renderer it calls, so the reference renders as a table…
- alix deck generate and alix doctor --repair-source-locators now stamp every citation when card-table rows share one at: line or cards arrive out of file order. Rewrites…
- Web catalog revalidation now counts directory entries, so adding or removing a workspace member in the same filesystem timestamp tick invalidates the snapshot on every…
- [keys.review] context can now be rebound as the manual promised. Setting it no longer rejects the whole config and takes the decks directory, backends, and every other…
- alix deck augment --help now lets each valid --target describe itself, including keypoints and format, instead of repeating a stale four-target list above the complete…
- Relative and absolute spellings of the same deck or member directory now resolve the same workspace and review store. Running alix stats d.md inside a workspace's decks/…
- alix deck init d.md from a workspace's decks/ directory now freezes source excerpts like the absolute spelling. It no longer stamps card ids while leaving live citations…
Sep 2 · 3 changes
- alix doctor reads a bare token in requires: as an ordinary dangling filename prerequisite. The dedicated note that singled the shape out and prescribed a rewrite is…
- alix workspace init's manifest template advertises review: as the commented [defaults] scheduling sample. The sample previously named a key the current format does not…
- The warning printed when assembled decks disagree on their scheduling setting names the review: key. It previously pointed the reader at a key the current format does…
Sep 2 · 1 change
- The codex backend forwards a configured [ask] effort to the CLI as its reasoning-effort config instead of silently dropping it. Nothing changes when effort is unset.
Sep 2 · 1 change
- The desktop entry written by install-desktop.sh no longer describes alix as a trainer "for the terminal"; bare alix opens the local web app.
Sep 2 · 1 change
- A cloze hole cut out of a formula now defaults to the sketch input, because the hole's content is the expected answer and a formula's piece has no keyboard spelling. The…
Sep 2 · 1 change
- A display formula on the question side of an adult web card scales to the room the question region leaves it, and the region shows the answer region's edge fade when…
v0.8.0 · 2026-09-02
Tagged release. Full notes below.
Sep 2 · 1 change
- The semantic documentation audit (make docs-audit) did not reach a clean sample for this release. Fifteen rounds ran on the candidate; every round reported findings…
Sep 2 · 1 change
- Adult web review keeps several formulas and question images visible together instead of clipping a later formula or shrinking an image to a speck.
Sep 3 · 1 change
- A bare <!-- ignore --> among a card's trailing comments keeps the card in the file, with its id and history, but takes it out of review, the exam, and every count; an…
Sep 3 · 1 change
- --lan refuses a --token or [serve] token value shorter than 16 characters before the server binds; minted tokens are unaffected.
Sep 3 · 1 change
- alix doctor warns when a cloze card's front contains a blank's answer, the check that already covers a block note.
Sep 5 · 1 change
- alix profile add refuses a decks folder that equals, contains, or sits inside another profile's folder; bare alix doctor reports every existing overlap without rewriting…
Sep 6 · 1 change
- A deck where only some cards have buildable choice options now opens its first sitting at Recall; Recognize is the default only when every card has options.
Sep 7 · 2 changes
- Machine-managed progress and recent history now live under .alix/ beside every deck: at the workspace root for members and in the containing folder for loose decks. CLI…
- A deck's personal-file twin is now <deck>.local.md instead of <deck>.personal.md, matching the private *.local.* naming rule.
Sep 7 · 2 changes
- Paired phones can list and pull complete picker entries, including their private progress and local sidecars, then push revision-checked progress by deck id. Each served…
- alix deck restore can swap the progress-only backup left by an accepted paired-phone push. Bare alix doctor reports malformed or duplicate profile root identities and…
Sep 7 · 1 change
- Paired sync reports a member it cannot canonicalize or fully parse through the entry's left_out paths instead of disabling every sync route. Pulls omit that member and…
Sep 7 · 1 change
- The phone's shared-folder decks root and its MANAGE_EXTERNAL_STORAGE permission, and the review screen's "another device wrote the store" notice; decks reach the phone…
Sep 7 · 1 change
- The mobile app now stores pairings keyed by the paired desktop's root identity, keeping one entry per root instead of a single overwritten config, and refuses a desktop…
Sep 8 · 1 change
- A paired pull larger than 32 KiB no longer arrives chunked without a Content-Length; the phone's sync client refused every real-sized entry with an absurd free-space…
Sep 8 · 1 change
- alix doctor no longer reports alix's own .bak backups under a workspace's private state (the progress document an accepted paired-phone push or a deck overwrite…
Sep 9 · 1 change
- Opening a workspace on the phone, and listing one anywhere, parses each member deck once instead of two or three times, classifies the folder once, and no longer…
Sep 11 · 2 changes
- Listing a folder of table decks no longer takes time quadratic in the rows. Deciding whether a table card can be shown as a multiple-choice question rebuilt that card's…
- A table row whose column holds answers that differ only in inline markup is offered as a multiple-choice question again. Such a pair collided when the question was…
Sep 11 · 1 change
- A workspace deck's drawer counts the cards a session actually drills. It loaded the member deck without the workspace manifest's [defaults], so under direction = both it…
Sep 11 · 1 change
- Inline code on the phone is painted in the theme's code colour, so a code span is distinguishable where the surrounding text is already monospace (the answer and every…
Sep 11 · 1 change
- A note no longer breaks into two paragraphs in the middle of a sentence. Note bodies are split one paragraph per sentence, and the splitter ended a sentence at any full…
Sep 12 · 1 change
- A picker row now reads its workspace manifest's [defaults]. A member of a direction: both workspace greyed out its Recall launcher and dropped its "new" badge while an…
Sep 12 · 2 changes
- The phone keeps parsed decks in memory across picker listings. Every listing used to parse every deck again; a repeat listing over unchanged decks now parses none, so…
- Listing the picker no longer freezes the phone. The bridge ran the listing on the UI thread, so a large workspace held the app for the whole parse; it now runs on a…
Sep 12 · 1 change
- A workspace listing resolves each member's physical path once, when the workspace lists its members, instead of three times per listing (the member dedup, the…
Sep 12 · 1 change
- A paired phone's sync cycle pulls only the entries whose files changed since its last pull: GET /api/sync/entries carries a per-entry digest over the pull manifest's…
Sep 12 · 2 changes
- The phone refuses to pair with a desktop whose root id is not root- plus 26 lowercase Crockford base32 characters, and it never joins an unchecked root id into its…
- An https pairing URL without a port now dials 443; it dialed 80.
Sep 13 · 1 change
- The phone's sync report labels a loose deck by reading that one deck file instead of listing the whole paired root first.
Sep 13 · 1 change
- An assets/ directory that no deck in the workspace owns is left out of alix share and of a paired phone's pull instead of failing the whole workspace; alix doctor warns…
Sep 13 · 1 change
- A sync route that fails answers with the failure's message and prints it on the server's stderr; GET /api/sync/entries sent an empty 500 and discarded the failure. The…
Sep 14 · 1 change
- The web section context dims the whole page at once and slides a sheet up from the bottom of the page to just under the question, as wide as the card, so the question…
Sep 16 · 1 change
- A paired-sync request that fails with a 500 answers with a class, a message that names alix's own files by their fixed names and a deck by its minted id, never a host…
Sep 16 · 1 change
- Every published desktop archive and the Android APK now carry a build-provenance attestation, signed during the release run against the workflow identity that produced…
Sep 17 · 1 change
- The "more below" and "more above" markers on an overflowing answer or note are now buttons. Pressing one scrolls that region by most of its own height, so a long answer…
Sep 17 · 1 change
- The phone's review card follows the same design: a revealed answer reads as left-aligned prose, a badged note drops its tinted ground and keeps its accent in the badge…
Sep 17 · 1 change
- A review or walk on the phone no longer shows the alix wordmark in its bar, the way a chat screen names the person rather than the app. The review's check tag (NEW…
Sep 18 · 2 changes
- The phone can now sync with a paired desktop: the Sync button beside the desktop's heading in the picker runs a cycle over everything beneath it (list, push every…
- The phone's picker names the folder you opened in its bar, where the root screen shows the wordmark, and drops the heading that used to repeat it in the list. The…
Sep 18 · 1 change
- Every row in the phone's picker now opens with a disc: a workspace's own icon, or its initial when it declares none, and the card glyph for a deck. Nothing sits on the…
Sep 18 · 1 change
- GET /api/version now also reports profile, the launch profile serving that root, or null when alix was started any other way. The phone's picker heads the paired…
Sep 18 · 1 change
- A generated or installed workspace emblem is written as a standalone SVG document, carrying the SVG namespace declaration; a wrong or empty one in the source is…
Sep 20 · 3 changes
- A revealed answer is set in the card's proportional face and justified, on the web and on the phone alike. Monospace stays where it carries meaning: a code span, a…
- The phone review card now wraps the question, every answer unit and every note at one measure instead of insetting the answer and the note inside the question. A…
- The web review card reads as one column. The question, the answer and the note wrap at one shared measure: the question is centred in it, and every answer unit and note…
Sep 21 · 1 change
- The phone can cram. The sheet a long-press on a deck row opens now starts with a Cram switch above the three depths: switch it on, then pick a depth. Cram belongs to…
Sep 21 · 1 change
- Review state now marks the first card ever introduced from a section (a section with any progress stays quiet in later sittings), so clients can present its section by…
Up next
0.9 is the sync release. Put any workspace or deck from your desktop on your paired phone, review it offline with the progress you already earned, and hand the progress back. The first cut is on main.
Up next
Sync from anywhere over your own Tailscale mesh: alix prints a second pairing address for the mesh, and the phone syncs wherever it is while the desktop runs alix. No engine, no accounts, nothing leaves your devices.
Up next
Prompt hygiene under the hood. A hard boundary between alix's own prompt text and deck text in every AI call, the exam's source fence, and context that keeps quotes, tables, and checklists as structure instead of flattening them to text.
Up next
In the works
Merged on the main branch, not yet part of a tagged release.
Added
- The phone can cram. The sheet a long-press on a deck row opens now starts with a Cram switch above the three depths: switch it on, then pick a depth. Cram belongs to that one launch and is never saved, so a plain tap on the deck never crams. A crammed session also serves cards that are not due, under the same rules as the web app's cram: a due card grades as a normal review, an early pass only re-anchors the due date and records nothing, a miss lapses normally, and retired cards stay out. Useful for loading material shortly before it is needed rather than when the schedule says so.
- Every published desktop archive and the Android APK now carry a build-provenance attestation, signed during the release run against the workflow identity that produced the file. Verify one with
gh attestation verify <file> --repo Alex6323/alix. The.sha256records stay as they were; a checksum shows a download was not corrupted, while an attestation also covers an archive and checksum replaced together. The install script checks the checksum only. - Paired phones can list and pull complete picker entries, including their private progress and local sidecars, then push revision-checked progress by deck id. Each served folder gets a stable
.alix/sync.tomlroot identity; pulls are streamed from bounded temporary archives and pushes have a 64 MiB cap. GET /api/versionnow also reportsprofile, the launch profile serving that root, ornullwhen alix was started any other way. The phone's picker heads the paired desktop's section with that name instead of the address it dialled, and falls back to the address when there is no profile to name.alix deck restorecan swap the progress-only backup left by an accepted paired-phone push. Barealix doctorreports malformed or duplicate profile root identities and nested.alix/sync.tomlfiles.alix profile addrefuses a decks folder that equals, contains, or sits inside another profile's folder; barealix doctorreports every existing overlap without rewriting profiles.- A bare
<!-- ignore -->among a card's trailing comments keeps the card in the file, with its id and history, but takes it out of review, the exam, and every count; an ignored card may lack an answer.alix statsreports the count andalix listmarks the cards. alix doctorwarns when a cloze card's front contains a blank's answer, the check that already covers a block note.- The phone can now sync with a paired desktop: the Sync button beside the desktop's heading in the picker runs a cycle over everything beneath it (list, push every changed deck, pull, tidy renamed entries), app open runs one in the background once the desktop answers with the same root, and a review session's summary silently pushes that deck's progress. A one-line status shows under that heading while a cycle runs or its report is unread; tapping it opens the report (landed, pushed, kept, phone-only, removed, renamed, left out, not on this phone, orphaned, refused), and a push or pull conflict surfaces a choice naming what each side discards. A desktop entry the phone has never pulled now shows as its own row below the picker's entries, name and size only, tap to pull it for the first time. Settings gains a "Paired desktop" row to switch between saved pairings.
Removed
- The phone's shared-folder decks root and its
MANAGE_EXTERNAL_STORAGEpermission, and the review screen's "another device wrote the store" notice; decks reach the phone through the paired desktop instead.
Changed
- A revealed answer is set in the card's proportional face and justified, on the web and on the phone alike. Monospace stays where it carries meaning: a code span, a fenced block, a typed line's expected text. Both clients had set the whole answer in monospace, which was TUI heritage rather than a ruling. The note is justified with it, and the last line of a paragraph stays unstretched, as newspapers set it.
- The phone review card now wraps the question, every answer unit and every note at one measure instead of insetting the answer and the note inside the question. A one-line answer unit no longer centres itself as a block while a wrapping one sits left, and a line-by-line reveal aligns with the rest of the answer rather than centring each line.
- The web review card reads as one column. The question, the answer and the note wrap at one shared measure: the question is centred in it, and every answer unit and note aligns to its left edge, including a short one that used to centre itself as a block. The question, the type pill and the section title stay centred. A note no longer sits in a tinted box: the divider above it, its dimmer face, and its badge carry the separation, and the badge keeps each kind's accent in its border so severity stays legible without reading the word. That badge and the card's type pill are now the same object in two places, matching in size, border, tracking and padding. The section-context key moved into the same right-hand column as the region toggles, as a glyph and keycap of the same shape, and the gap under the type pill grew so it no longer crowds the section title.
- The "more below" and "more above" markers on an overflowing answer or note are now buttons. Pressing one scrolls that region by most of its own height, so a long answer can be walked from the marker itself.
- The phone's picker names the folder you opened in its bar, where the root screen shows the wordmark, and drops the heading that used to repeat it in the list. The wordmark itself now reads "Alix"; the lowercase spelling is the command's name, not the app's.
- Every row in the phone's picker now opens with a disc: a workspace's own icon, or its initial when it declares none, and the card glyph for a deck. Nothing sits on the right any more: no due dot, no overflow menu, no chevron. Dropping the menu also drops the height it forced on a paired row, which stood 24 logical pixels taller than a local one for it, and the list starts off the bar by the same 6 pixels that separate its rows.
- A review or walk on the phone no longer shows the alix wordmark in its bar, the way a chat screen names the person rather than the app. The review's check tag (NEW, FLIP, SELECT ALL and the rest) moves into the space that frees, so the card itself starts where the tag used to sit and gains that height for its question and answer.
- The phone's review card follows the same design: a revealed answer reads as left-aligned prose, a badged note drops its tinted ground and keeps its accent in the badge border, and that badge and the card's type pill are one shape in two places. The question, the answer and an unboxed note wrap at one measure, the card's own; a framed note puts that measure on its box and keeps its text inside the frame.
- Review state now marks the first card ever introduced from a section (a section with any progress stays quiet in later sittings), so clients can present its section by themselves once and offer it on demand thereafter. The web review shows the section heading as a dim title line on every sectioned card, never renders the section inline, and opens the full, paragraph-preserving section sheet by itself once on the first introduction. The title,
ckey, and Context menu entry open it on demand. The phone shows the heading as a tappable pill above the question that opens the section in a bottom sheet, and opens that sheet by itself under the same rule. - An
assets/directory that no deck in the workspace owns is left out ofalix shareand of a paired phone's pull instead of failing the whole workspace;alix doctorwarns about it, and reports a link or an unreadableassets/root, which share and sync refuse, as an error. - The phone's sync report labels a loose deck by reading that one deck file instead of listing the whole paired root first.
- A paired phone's sync cycle pulls only the entries whose files changed since its last pull:
GET /api/sync/entriescarries a per-entrydigestover the pull manifest's file rows, and the phone compares it with the manifest it holds. A pull that lands keeps the modification time of every file whose bytes did not change, so the picker re-parses only what the pull changed. - A workspace listing resolves each member's physical path once, when the workspace lists its members, instead of three times per listing (the member dedup, the prerequisite table, and the dependency forest each resolved every member again).
- The phone keeps parsed decks in memory across picker listings. Every listing used to parse every deck again; a repeat listing over unchanged decks now parses none, so returning from a review to the picker costs the row derivation only. Entries are validated by the file's modification time and size, as the web server's cache already did, and the cache drops whole once it holds more than 1024 paths.
- Machine-managed progress and recent history now live under
.alix/beside every deck: at the workspace root for members and in the containing folder for loose decks. CLI and server resolution are identical, workspacealix.tomlno longer acceptsstore, and the--storeoverride and global deck-state fallback have been removed. - A deck's personal-file twin is now
<deck>.local.mdinstead of<deck>.personal.md, matching the private*.local.*naming rule. --lanrefuses a--tokenor[serve] tokenvalue shorter than 16 characters before the server binds; minted tokens are unaffected.- The mobile app now stores pairings keyed by the paired desktop's root identity, keeping one entry per root instead of a single overwritten config, and refuses a desktop whose
/api/versionpredates that identity. Pre-1.0: re-pair once after upgrading.
Fixed
- A generated or installed workspace emblem is written as a standalone SVG document, carrying the SVG namespace declaration; a wrong or empty one in the source is replaced, and a root tag spelled other than lowercase
<svg>is refused, since element names are case-sensitive there, as is input whose closing tag does not follow its root, which used to panic. Without the declaration a browser parses the file as markup that is not SVG and paints nothing, so an emblem the phone drew correctly was invisible in the web picker, which also lost the chevron the row shows when there is no emblem. Existing emblem files are not rewritten; regenerate one to repair it. - A paired-sync request that fails with a 500 answers with a class, a message that names alix's own files by their fixed names and a deck by its minted id, never a host path or a user-authored file name, and one remedy sentence; the class and message reach the server log and the bug-report archive, and the phone shows the message and remedy instead of a bare status number.
- The web section context dims the whole page at once and slides a sheet up from the bottom of the page to just under the question, as wide as the card, so the question stays legible above it; the title shows its key beside it, a click anywhere on the dimmed page,
c, or Escape closes the sheet, and closing returns focus to the title without a ring. Before, a card-bounded drawer hid the question, its only visible close route was the dark ring around the panel, and the title took a focus ring after every close. - A sync route that fails answers with the failure's message and prints it on the server's stderr;
GET /api/sync/entriessent an empty 500 and discarded the failure. The log record carries no host path. - The phone refuses to pair with a desktop whose root id is not
root-plus 26 lowercase Crockford base32 characters, and it never joins an unchecked root id into its paired directory path, so a malformed or path-shaped id cannot place the paired tree outsidepaired/. - An
httpspairing URL without a port now dials 443; it dialed 80. - Listing the picker no longer freezes the phone. The bridge ran the listing on the UI thread, so a large workspace held the app for the whole parse; it now runs on a worker while the screen stays responsive, the local decks appear as soon as they are listed, and the paired desktop's follow. Until the first listing answers the screen shows nothing in place of the list rather than the empty-folder hint.
- A picker row now reads its workspace manifest's
[defaults]. A member of adirection: bothworkspace greyed out its Recall launcher and dropped its "new" badge while an ungraded card waited, because the picker's loader applied the built-in defaults instead of the workspace's. Editing a manifest's defaults also refreshes an already-listed member. - A note no longer breaks into two paragraphs in the middle of a sentence. Note bodies are split one paragraph per sentence, and the splitter ended a sentence at any full stop followed by a space, so "structure (e.g. a Merkle path) was considered" rendered as two paragraphs; it also never broke on
?or!. Sentence bounds now come from Unicode UAX#29, which keeps abbreviations and decimals whole and does break on question and exclamation marks. - A workspace deck's drawer counts the cards a session actually drills. It loaded the member deck without the workspace manifest's
[defaults], so underdirection = bothit described half the deck: the card total, the seen/learned/retired funnel, and every heatmap cell. - Listing a folder of table decks no longer takes time quadratic in the rows. Deciding whether a table card can be shown as a multiple-choice question rebuilt that card's distractor pool by scanning every card in the deck, once per card, and ranked the whole pool to pick three; the deck's columns are now indexed once and each card answers from that index. A folder holding one 800-row table deck served a warm listing in 4.667 s and now serves it in 0.016 s; a 1600-row deck's recognizability scan alone fell from 9.19 s to 0.012 s.
- A table row whose column holds answers that differ only in inline markup is offered as a multiple-choice question again. Such a pair collided when the question was assembled, after the three distractors had been picked, which dropped the card out of Recognize entirely; candidates are now compared before the pick, so a colliding one is passed over instead.
- Inline code on the phone is painted in the theme's code colour, so a code span is distinguishable where the surrounding text is already monospace (the answer and every progressive reveal step); it was distinguished by font family alone, which those regions had already spent.
- Opening a workspace on the phone, and listing one anywhere, parses each member deck once instead of two or three times, classifies the folder once, and no longer re-parses a prerequisite chain for every member that depends on it;
has_examreads the workspace manifest at most once per loaded deck instead of on every call. The picker's drill-in is one bridge call. alix doctorno longer reports alix's own.bakbackups under a workspace's private state (the progress document an accepted paired-phone push or a deck overwrite preserves) as synchronization conflicts needing deliberate recovery. They count as backups instead: the folder run's backup total includes them and--remove-backup-filesdeletes them.- A paired pull larger than 32 KiB no longer arrives chunked without a
Content-Length; the phone's sync client refused every real-sized entry with an absurd free-space figure while tiny fixtures passed. Downloads now always state their length. - Paired sync reports a member it cannot canonicalize or fully parse through the entry's
left_outpaths instead of disabling every sync route. Pulls omit that member and its owned bundle files, percent-encoded entry names resolve for pull and share, and a served-root identity failure prints before the success banner. - A deck where only some cards have buildable choice options now opens its first sitting at Recall; Recognize is the default only when every card has options.
- Adult web review keeps several formulas and question images visible together instead of clipping a later formula or shrinking an image to a speck.
v0.8.0
Added
- Deck normalization drops invisible bytes with no rendered role from prose: form feed, DEL, an interior BOM, an interior carriage return, and the two bidi reversal overrides (LRO/RLO). Fenced code keeps every byte; every invisible character with a rendered role (zero-width joiners, word joiner, soft hyphen, variation selectors, bidi isolates and embeddings, and the rest) is untouched.
- Typed grading draws the ink line: a character that paints no horizontal ink (soft hyphen, zero-width space, joiners, word joiner, variation selectors, bidi isolates and embeddings, tag characters) no longer grades, on either side of the comparison, so an answer is judged on what the screen shows rather than on what a device's keyboard or emoji picker happens to emit. Spacing characters still grade.
alix doctorreports the invisible characters a deck keeps: one calm per-deck note with counts by class, emoji-aware so bytes inside a well-formed emoji stay silent. It warns only where a byte has no legitimate reading: tag characters outside a flag emoji (they encode invisible text) and bidi override characters inside a code fence (rendered order may differ from stored order).alix doctorflags a pipe table that declares no mapping: a bare table still renders plain, and the new lint asks for an explicit<!-- cards -->or<!-- plain -->so a forgotten mapping cannot silently turn fifty vocabulary rows into one card.- Images accept the GFM title (
, single-quote and paren spellings too). The title is parsed and ignored for now; previously a titled image failed as malformed and degraded to literal text, losing the picture on paste. alix workspace augment <dir> --target <...>warms a whole workspace in one go. The card targets (choices, notes, questions, keypoints, format) run as a single batched call over every member's cards rather than one call per deck, and each result is filed back under the deck that owns the card.iconjoins them as the target only a workspace has, giving the workspace emblem its first path outside the web app.--target orderstays onalix deck augment, since a review order is built per deck and a workspace has no single answer for it.alix doctor --normalizerewrites a deck that drifted out of canonical bytes after it was initialized, for when an editor put a byte-order mark, CRLF endings, or trailing blanks back. It refuses to write anything that would stop parsing.- Badged notes and quotations are styled. Each of the five badges opens its own callout with a chip naming it, tinted from the theme you are using rather than from GitHub's palette, and several notes stack in authored order instead of sharing one box. A quotation carries a left rule and keeps the answer's own face, so its boundary is visible without a
>. Kids gets the same five badges on a calmer palette; mobile matches.[!NOTE]and[!IMPORTANT]share a hue because alix palettes carry four semantic colours, not GitHub's five; the word and a heavier chip border tell them apart. The accent paints the wash and the borders only, never small text, so a chip's label and its note's body both keep the theme's own ink. - A bare blockquote in an answer is a quotation, and every client renders it as one.
back_unitsgains aquoteunit carrying its own units, so a quotation can hold prose, code, or a list, and the>markers no longer render as text. A quotation is excluded from the typed target: typing one back tests transcription, not understanding, so the learner types the answer's own prose while the quote stands as supporting content. A>line inside a fence or a display-math block is that block's source and is graded like any other answer line, and a card whose whole answer is a quotation has nothing to type, so Reconstruct explains it rather than asking for text. - A card carries
answer_steps: the steps a client walks its answer in, one per gradeable line and one per quotation run. Reveal counts every step and typing counts only the gradeable ones, so a two-line quotation now reveals as ONE block underreveal: line(adult, kids, and mobile) instead of a marker line at a time, and no typed field is drawn for it on any client. The>markers no longer reach the line-reveal surfaces either. - Links display as their labels (decision 8, first slice):
[label](destination)renders the label styled as an inert link on every card surface, with the brackets, parentheses, and destination dropped from display; the deck file keeps the authored syntax untouched. Anchor and relative destinations render the same way (anchor navigation stays a documented non-feature). Emphasis works inside and across labels, code spans keep the syntax literal, and grading compares the label text. Reference links resolve against the deck's own definition lines and render the same way, in all three GFM forms ([text][label],[text][], bare[text]); labels match by Unicode case folding with interior whitespace collapsed (so[STRASSE]defines[straße]), on the authored spelling, so an escape or entity resolves against the same spelling and not across spellings. Only the grammar's six whitespace characters collapse, so a no-break space pasted from a web page or a PDF stays label content and never silently unifies two different labels. Matching never crosses deck boundaries, an undefined reference stays ordinary prose, and a protected[(escaped, code, math, entity) never opens a reference, keeping its text visible. A reverse or both-directions card carries its deck's definitions like the forward half. - The math spellings aligned with GitHub (decision 11). Display math gains its two block spellings: a bare
$$line opens a block that the next bare$$line closes, and a ````math``fence carries its body the same way; both render as one display formula, exactly like a$$...$$line, with every interior line as verbatim math source (blank lines,>lines, and other card-markup shapes included). The fence spelling and a closed bare block render on card, section, and context surfaces alike. A card or note that opens a$$block without closing it fails loud at the opener's line instead of swallowing the rest of the card. An unmatched section or context opener stays literal and does not consume the lines after it. Inline math gains the backtick-anchored spelling: a dollar, a backtick-quoted body, a dollar (as in$x^2$) renders exactly like$x^2$`. Code-fence interiors, escaped markers, and empty bodies stay literal; kids and mobile inherit everything through the existing math-run wire. - Bare pipe tables render as real aligned tables on every card surface (adult web, kids fronts, mobile): alignment colons in the delimiter row set column alignment, short rows pad, long rows truncate to the header width. Mapped card tables keep their strict column rules; kids answers show bare tables as literal lines for now.
- Sub-cards now stack to
######: heading depths 5 and 6 are sub-card depths 3 and 4 under the same one-level-parent rule, so every ATX depth has a meaning. Seven or more hashes are ordinary answer text per CommonMark instead of an error. ~~strikethrough~~renders struck through on every client, as GitHub renders it: pure presentation with no study semantics, and grading keeps comparing the unstruck content. Exactly the double-tilde pair strikes; a single or triple tilde run stays ordinary text, and line-start tilde fences are unaffected.- HTML entities decode on display (decision 10, fourth slice): the full HTML5 named set (
&,€, all ~2,100 names) plus the numericAand hexAforms render as their characters on every card surface, per CommonMark. Anything that is not a complete, valid entity stays literal, and a decoded character is content, not markup:*x*shows*x*without italics and<div>shows<div>without the reserved-tag error. Code spans and math keep entities as written. Emphasis next to an entity is judged on the spelling in the file,&on its left and;on its right, so𝔸_x_italicizes the variable rather than showing the underscores, and_ x_emphasizes even though the entity decodes to a space. Grading compares the decoded text, so typingTom & JerryanswersTom & Jerry; the deck file keeps the authored entity untouched. - The styled HTML subset renders (decision 10, third slice):
<sub>,<sup>, and<ins>pairs display as subscript, superscript, and underline on all three clients, with the tags dropped from the shown text. Grading compares the tag-free content, so typingH2OanswersH<sub>2</sub>O. Pair interiors keep normal inline scanning (emphasis and code inside a pair still render), while code spans and escapes keep the tags literal. A dropped tag stays a boundary for the markup around it:x<sup>2</sup>_i_italicizes rather than reading as one word, and a superscript citation such as[array]<sup>(1)</sup>keeps its brackets instead of taking the superscript's parentheses as a link destination. - Autolinks render as inert links (decision 10, second slice):
<https://...>and<user@host>display as the bracket-free URL, styled as a link on all three clients with no navigation attached. The URL text is protected from emphasis (underscores in a URL never italicize), and code spans and escapes still show the brackets literally. Only ASCII whitespace and control characters end the URL, so a no-break space inside one is ordinary URL content. - HTML tag shapes are a loud parse error (decision 10, first slice): a
<directly followed by a letter, or</, on any deck surface (content, sections, fronts, notes, table cells) fails with the line, the column, and the two outs (backticks for literal markup,\<for a lone bracket). alix does not render HTML, so the shape is reserved instead of silently shown as text. Code spans, fence interiors, verbatim math, whole-line comments (the channel), complete image destinations (), non-tag brackets (a < b,<3), autolink shapes (<https://...>,<user@host>), and the<sub>/<sup>/<ins>pairs stay legal. The boundaries are exact: subset pairs close in order on the same line (nested, cross-nested, mismatched, doubled, and unclosed forms all error), an image destination is exempt only as the completeform sealed by its paren (a link destination in angles is not), and a comment protects nothing mid-line: a tag insidetext <!-- <div> -->errors like any other. - Named mapping invocations:
<!-- choices: single -->or<!-- choices: multiple -->below a task list makes it a choice card (one correct answer, or select-all-that-apply),<!-- cards -->below a pipe table makes it a card table, and thechoices:frontmatter key declares a deck-wide task-list default with<!-- plain -->as the per-block escape. A comment naming no known invocation or directive is analix doctorfinding; achoices: multiplelist that checks exactly one option is legal and silent. - The select-all wire: a
choices: multiplecard serves every authored option withchoices_multiple: truein the review state, andPOST /api/choose {indices, card}grades the pick by exact set equality (MultiChooseFeedbackDto). Select-all builds only from the authored option set, so these cards are never AI-distractor gaps, and a cross-shape submission fails loudly. The adult web reviewer toggles these options independently, sends no request before its explicit Submit action, supports Space, digit, and Enter controls with focus reset for every new card, and marks chosen, missed-correct, and chosen-wrong options from the reply. The kids reviewer gets the same select-all flow: taps toggle marks and one Done action submits the set. The mobile reviewer completes the three clients: options toggle, a Submit chip grades the set through the embedded core, and the feedback marks the same three option states. alix doctor --repair-frontmatter-orderrewrites each checked deck's frontmatter into the canonical key order. Opt-in only; frontmatter it cannot safely permute (a blank line or comment inside the block) is left as-is with a note.alix doctor --repair-comment-orderrewrites each checked deck's trailing comment machinery into the canonical order: invocation, directives, region comments, locator, id last. Opt-in only; any order still parses, and content or an editorial comment bounds what may move.- Mermaid diagrams render into decks: a ```
`mermaid``fence in a workspace member freezes atalix deck inittime into a deck-owned PNG plus a label-geometry file (rendering shells out to the optionalsekienCLI; without it the deck still initializes and warns). At review the rendered diagram appears in the fence's own position on every client, and an unfrozen or stale fence falls back to its source in place. The diagram's authored theme (mermaid init directives and frontmatter, including the dark themes) decides its baked colors and compositing ground.alix doctordiagnoses missing, stale, orphaned, or corrupt frozen diagrams, andalix doctor --repair-diagramsremoves orphaned stamps and re-freezes what needs it. A frozen diagram also renders inside a cloze card's context (the sentence above the hole), where the fence previously showed as raw source lines; a context fence that cannot render as a diagram now displays as a code block. Ablank:orcover:` span over a diagram label becomes a region mask ON the rendered diagram (the asked label's box lifts when the answer shows, sibling and cover boxes stay), with accessible text that names only the visible labels; a span must cover one complete label (a bare node, whose label is its id, masks like any other), and a span that does not bind keeps the card on its masked-source fallback. A deck whose stamped diagram cannot serve says so once when a session opens (web and mobile) instead of silently showing source. - Image regions (ADR 0034): a deck can hide a region of a picture and ask what is under it.
blank:,cover:, andcrop:directive comments carry a closed named-field grammar with per-region minted stamps; a stamped blank is a reviewable card, a named group is one card asking every member (its id derived from the member stamps), and a cover masks without ever asking. A block carrying ablank:is a template, exactly like cloze: it produces only its region cards, and the block's note and prose ride each one, the prose as context with the card's own span shown as the blank marker and every sibling or cover span hidden (styling survives around the masks), whilecover:/crop:alone keep the ordinary card; removing the lastblank:re-exposes that card with its review history intact, and text holes or a task-list answer beside ablank:are a parse error. The review contract'sImageDtogrowsregionsandcrop(docs/API.md), classifying each region as asked, a sibling card's mask, or a cover. All three clients draw the masks: flat overlays carrying the same reserved-codepoint vocabulary as text blanks (the asked region shows the blank glyph, a sibling card's mask the hidden glyph, and a cover stays a plain glyphless fill), positioned over the image's painted rectangle so they track every resize, lifting on reveal exactly perreveal_on_answer; a cropped image renders as the crop viewport with regions kept in full-source space, shrinking to fit its bounded region with the crop's aspect kept instead of scrolling. A region reaching past the source edge is clipped at it, and an asked region that clips to nothing raises the client's error surface instead of silently showing the unmasked question. Each region also carriesreveal_on_answer: true for an asked blank and for a cover on a card whose block poses no sibling questions (a cover exists to stop a question-side leak), false for a sibling mask and for a cover on a region or cloze card, whose content could give a sibling's answer away; clients never infer reveal behavior from the role. Image-region cards take no multiple-choice questions until the MC-family design settles; text-span blank cards can take them when their choices can be built. Spans bind into math source under a structural-unit law: a hidden text must be a complete structural unit of its formula (no cut control words, no split brace groups, no script cut from its base, no command cut from its argument groups, no contained column or row separators; a standalone symbol command such as\pmor a Greek letter qualifies, a base with its whole script such asb^2qualifies, and a whole-formula match always qualifies), the authored formula and every masked variant must re-parse under the product's renderer, and violations are loud binding errors naming the offending token. Masked math renders the blank as a boxed form instead of the text markers, the same forms cloze holes in formulas already use. A math span whose hidden text carries a LaTeX command while the block pinsinput: typegets the untypable-answer lint, exactly like cloze holes. Opening a deck mints aposition:anchor into each span directive, the 1-based grapheme index of the bound occurrence in the block's visible text: never read for binding, it is the drift signal that letsalix doctorsay exactly where a span used to point after the text moves. The stamper only ever mints a missing anchor; a divergent one is left untouched, restamping an untouched file never rewrites a byte, andalix doctorreports each divergence with its two concrete edits (keep the authored occurrence, or keep the old target viaoccurrence=when the anchor still starts one).alix doctor --repair-positionsapplies the keep-authored edit on demand, rewriting each diverged anchor to where the span binds today. alix bug-reportwrites a local, reviewable diagnostics archive, and the adult web app offers the same download from About. It includes bounded current and rolled logs, platform and version details, a token-free config, and only hashed deck identities with aggregate counts. By default it includes no deck text;--include-deck <path>deliberately adds exactly that deck verbatim, including card text and authored notes. Personal sidecars, prompts, and AI responses are always excluded. It never uploads anything, and removes configured tokens and home-directory identity from every text file. Panic locations, failed AI calls, parse-error classes, and HTTP-error classes now join card selection in the default bounded log, without request paths or learning content. Archives use the frozen versioned name and user-reviewable entry order; copied logs admit only known-safe fields, and copied config drops AI prompt overrides and extra guidance.- A blank span can be named,
<!-- blank: span [base] hidden="Unit" -->, and a note line can be written to that name:> base: textgives that blank a note of its own instead of the shared one, and> base+: textadds to the shared note. A>note belongs to the whole card, so until now a note that spelled out one blank's answer gave it away on every sibling card. A line is only an address when the card names a blank and the name before the:is one of them, so ordinary notes are untouched;alix doctorreports an address naming no blank of its card and shows the line as a note. The name is an address inside its own card, never an id: a lone named span keeps its stamp identity, so naming a blank you have already drilled keeps its history, and two cards may each have abase. A group only exists at two or more members. - Two blanks of one card that share a name are drilled as a single card asking both spans, so a fact split across two blanks is no longer answered in halves. They need not be adjacent or on the same line, both show as blank markers, and the answer is typed as a list with one line per span. Grouping starts the merged card's history over, because recalling two spans together is a harder question than either alone and an inherited schedule would overstate what you know; the blanks you did not group keep theirs, and a name on a single span is not yet a group, so the reset happens only when the second member joins.
- Personal notes and cards now live in a file of their own beside the deck (
spanish.mdgetsspanish.personal.md), leaving the authored deck byte-identical. Tutor notes, tutor-made cards, and exam remediation cards all land there; afor:frontmatter key names the deck the file belongs to, a<!-- note: <card-id> -->marker followed by>lines adds notes to one of its cards, and an ordinary card block adds a card of your own. A card closes with its<!-- id: -->line as it does in a deck, while a note's marker opens its block, because a note has to name the card it belongs to before anything below it means anything. Nothing in the file copies the deck, so nothing in it can go stale. A personal file is never listed as a deck,alix deck initrefuses one rather than stamping it with anid:of its own, andalix shareleaves it at home in both directions: it is never bundled, and an arriving bundle cannot overwrite yours. - The example decks gained one for a blank inside a formula, showing that a
<!-- blank: span hidden="..." -->directive works on$...$and$$...$$formula lines. alix doctorwarns when a formula span pinned toinput: typehides a LaTeX command, because the span's hidden text is the expected answer and typing\pmasks for the spelling of\pm. Without an authoredinput:, a formula span is sketched rather than typed on mobile; the desktop web app still serves the typed check.- Deck authoring has a written rule for which card shape suits which material, and one worked example per shape.
docs/include/card-shapes.mdmarks each row structural (the material has a property the shape exploits, so any other shape wastes it) or judgement (more than one shape is defensible), the manual includes it, anddocs/examples/shapes/demonstrates every row. Each example is held to the shape it advertises, so a deck that stops producing what it documents fails the suite. - Each running web-server instance now keeps an always-on local diagnostic log outside the decks directory. The current and one rolled file are capped at 5 MiB each by default, contain operational timings and minted IDs but no learning content, names, titles, or paths, and are never uploaded or read back by Alix.
alix doctorand the web Doctor sheet name the profile's readable, collision-safe log path;[log]configures its cap and verbosity. - The session summary places the sitting against the whole deck: the "introduced" row now also reads how many of the deck's cards have ever been met, out of how many it holds.
met_totalanddeck_totalcarry it on the JSON API. - Card tables: a GitHub-flavored pipe table in a deck is a compact card source, one card per row (front | back | optional note). The header row names the columns and is never displayed; a
##heading directly above the table titles it and is the row card's context. Each row carries a minted six-character stamp at its end and the table one container id line, so sorting, inserting, and editing rows preserves review history. - Table cards draw their Recognize options from their own column, so a vocabulary table needs no AI augmentation and no authored options (both still take precedence where present). The new
sampling: on|offkey turns that off or on again, in the frontmatter for a whole deck, in a workspace's defaults, or in one table's directive comments, which win over the deck in either direction.alix doctorreports asampling:key that can affect nothing. - Tables accept the card directives (
direction,reveal,input,sampling) between the table and its id line. Anything the format cannot hold (a fourth column, cloze markers or images in cells, stray content after the table) is a loud parse error rather than a guess. - The adult web picker can remove a focused loose deck, workspace member, or whole workspace from its secondary menu. A type-the-name sheet previews the irreversible stakes and Alix-owned artifacts first; source files Alix does not own remain in place, active sessions block removal, recent and retained state are invalidated, and a partial failure names completed and failed artifacts with an
alix doctorrecovery step. The JSON API exposes the same preview and removal contract for other clients. - Deck lifecycle completes with removal and restore.
alix deck remove <deck>deletes a deck and everything that is its alone (file, review history, frozen assets, augmentations, and any.bakbackups) after one confirmation that states the stakes: cards with progress, the reviewed-since date, the exact file list. Total by design: nothing is backed up and it cannot be undone.alix deck restore <deck>is the counterpart for overwrites: it swaps a deck with its.bakbackups and is its own inverse, so restoring again swaps back. Overwrites (a forced import, trace or workspace regeneration) now back up the full trio (deck file, review history, augmentations), where they previously kept only the deck text, so a restore brings the history back too.alix doctorcounts accumulated backups and--remove-backup-filesdeletes them after a confirmation.
Changed
- Breaking: the scheduling key renames from
order:toreview: scheduled|sequential, in deck frontmatter and workspace[defaults];order:now fails as an ordinary unknown key. In the same ruling the (unreleased) mapping grammar went key: value on every surface: the invocations are<!-- choices: single -->and<!-- choices: multiple -->, the deck-wide default is thechoices:frontmatter key, and notable:key exists, so a pipe table becomes cards only through<!-- cards -->on the line below it. Single-word instructions without a key are not allowed in frontmatter; the bare<!-- cards -->and<!-- plain -->invocations stay per-block only. The pre-rename spellings (tasklist:,table:,choices-single,choices-multiple) fail as ordinary unknown vocabulary. - Breaking: a GFM footnote definition line (
[^label]:at line start, outside code) is now a hard parse error naming footnotes as unsupported. It previously parsed silently, as prose or worse as a link definition with a caret label, misrepresenting pasted content. Inline[^1]and regex classes like[^abc]stay ordinary prose. - Breaking: the inline
\blank{...}cloze marker is retired. Span and rect blanks (<!-- blank: span hidden="..." -->and its rect sibling) are the only cloze;\blank{...}in a deck now parses as ordinary literal answer text. The positional sub-ids the marker minted (card-<token>-N) are no longer composed or parsed, the store's hole fingerprint records are gone (an old progress document fails loud on its unknown field), and the formula rule carries over: a span inside a formula is sketched unless the block pinsinput: type, and a block'sinput:directive now reaches its span cards. Per-hole addressed notes survive addressed to region names, grouped or lone:> g: textreplaces the shared block note for the card named g,> g+: textappends to it. - alix normalizes a deck's bytes whenever it writes one. A leading byte-order mark is dropped, CRLF line endings become LF, and trailing spaces and tabs are removed. A hard line break survives as exactly two spaces, and a code fence keeps the trailing blanks on every line it owns, its opening and closing lines included: they are code, and a fence opener is fingerprinted byte for byte, so trimming one would silently drop a card's cached augmentations. Editors add these bytes without telling you and no deck needs them, so alix settles on one form instead of carrying whatever arrived. Initializing, stamping, augmenting, receiving, and every doctor repair write canonical bytes; a file alix never writes is left alone.
- Breaking: generation moved under the noun it produces. The top-level
alix generatecommand is gone.alix deck generatetakes a URL, a file, or a directory taken whole and always writes one deck;--tracestill makes that deck a trace, and atrace:stub still builds in place.alix workspace generatetakes a directory, explores it, and always builds a workspace, a one-item plan included; another source shape is refused with a pointer to the deck command. The requested noun fixes the result before the model call instead of letting the source type or plan size decide it.--deckis gone becausealix deck generateis what it meant, and--workspace <dir>is now--into <dir>on both subcommands: fordeck, an existing workspace receives the deck; forworkspace, that folder is built and created when absent. Every other command already readsalix <noun> <verb>, andalix deck --helpnow lists the complete deck command set. - A deck's
<!-- -->comments are alix machinery, andalix doctornow says so uniformly. The finding used to depend on the comment's length: a one-word comment that named no invocation was reported, while a longer one was ignored, so<!-- Transfer -->was flagged and<!-- ## About Interfaces -->passed unnoticed. Recognition decides it now. Anything that is not a directive, a locator, an id, or an invocation is reported, and a directive-shaped comment with an unknown key is reported before the first card as well as inside one. Decks carrying commented-out outlines or prose will report more than before; a deck's own prose belongs indescription:and its origin insource:. alix workspace updateno longer aborts the whole run when one member's proposal is unusable. The member is reported by name with its reason, its staged deck, assets, and augmentation are left as the copy found them, and the other members still stage. The command exits non-zero, records the failures in the proposal so--applyreprints them, and applies only the members that updated. If every member fails there is no proposal to apply. The frozen-evidence precondition moved ahead of the first model call, so an unfrozen member now stops the command before any proposal is paid for.- A pipe table in an answer is one block, like a quotation. Under
<!-- reveal: line -->it takes a single reveal instead of arriving a pipe line at a time, and it is excluded from the typed target: typing pipe syntax back tests transcription, not understanding.answer_stepsgains atablestep carrying the table unit, so adult, kids, and mobile all draw the aligned table rather than raw|lines. The consequence to expect: a card whose whole answer is a table now has nothing to type, so Reconstruct explains it instead of asking for text. Typing the cells themselves is a separate feature and is not in this release. - A thematic break is one lexical class: three or more of one marker,
-,*, or_, with spaces or tabs allowed between them and an indent under four columns.----,-----, and- - -used to be ordinary content while____was already a break, which is a distinction no author could predict and one the manual could not justify. GFM reads all of them as breaks, and alix has no reason to differ. A line like----sitting in an answer is now a loud stray rather than text, so write\----or trail it with<!-- plain -->; measured exposure across every deck collection and committed example available to us was one occurrence, in a product note that alix never parses. A link definition now also stops at a---line, as it already stopped at***. Setext headings stay unsupported, deliberately: a===underline is still refused with a message naming the#-prefixed form, and a dash underline is simply read by the break rule above. Setext offers two heading levels and alix cards run to six, so it could never express the card grammar. - Breaking: AI walk grading is removed, including its
[trace] auto_gradeconfig key. A trace walk is self-judged on every client, as the phone already was. The feature was config-and-plumbing only: no client ever read its wire fields, so nothing on screen changes.WalkDtolosesauto_grade,thinking,verdict,feedback, andgrade_error. A config still carryingauto_gradenow fails to parse as an unknown key rather than silently ignoring it. Walk grading will return as a delivered feature rather than a half-wired one. - Breaking (deck format): the section terminator is gone; a bare
#resets the context instead. A section used to end early at a---alone between blank lines. That overloaded---, which already served as the frontmatter fence and the front divider, and it meant***and___could never be its equals. Now a#with no title opens an *empty* section context, so the cards after it carry none until the next titled#, and prose under a bare#joins that empty section like prose under any other heading. Attached directly under a card's own lines a bare#is an error, where a reset would silently truncate the card. - The backslash escape reaches every break spelling.
\---was literal text while\***and\___kept and displayed their backslash, which split a class the same release had just unified: all of them carry the front-divider meaning, so all of them need the escape. A whole line of break markers is escapable now, spaced spellings included, and the deck importer escapes the same shapes when it writes an imported cell. - Breaking (deck format):
---, `*, and___are interchangeable, and a break only divides a front.** Position decides what a break means, never which of the three you typed, and dividing a card's front from the answer attached below it is the only meaning a break has: anywhere else it is a line-numbered error, blank-surrounded between cards included. alix has no standalone horizontal rule, and the shape is reserved: section context is a short orienting line authored under a#, and a break there occurs zero times across every real collection, the shared study workspace, the committed examples, and the end-to-end fixtures. Keep the characters with<!-- plain -->on the line below, or\---for a dash spelling.***and___gain the front-divider role and the<!-- plain -->escape, which previously only---had; a break attached under a heading with no card open is stray for all three, as---already was. Decks using a---` terminator between cards fail loudly. - A
formatreshape changes what a card SHOWS, never what a typed check expects, which is what the manual has always promised. A card is now served in one answer space: a reveal mode gets the reshape, a typed mode gets the deck's authored answer, andback,back_units,answer_steps, andreshapedall describe the same text. Nothing validates the model's reshape and the prompt explicitly permits relabelling, so an accepted reshape could otherwise become the exact text a learner had to reproduce. A typing surface shows blank fields, so the learner could not see the substitution before submitting and would simply fail on the deck's own correct wording. Found by Codex with a deterministic red test against the shipped commit. POST /api/checktakes one line per gradeable step, not one per answer line. A quotation owns no field, so a client that sent a blank for it is now one field too many rather than a position that silently passes. Clients in this repository are updated together; anything else reading the API needs the same change.- A display reshape is keyed on the notes it was generated from. Adding a second alert to an already reshaped card used to leave the older reshape in place, which after the multi-note change meant the new note was silently omitted from review and browse while the deck file still held it. The format augmentation now has its own input fingerprint over the question, the answer, and the ordered note stack, so a note-only edit makes the reshape stale and it regenerates. Scheduling identity is untouched, so no other augmentation is invalidated, and a reshape cached before this release simply misses and regenerates on demand. Codex found this.
- A card carries several notes rather than one. A second badged blockquote used to be swallowed into the first note's body, with the last badge silently winning; each badged run is now its own note, in authored order, and every client renders one block per note. A badge line only opens a note at the start of a blockquote run, so a
> [!WARNING]inside an open note stays body text, matching how GitHub reads alerts. A note alix adds rather than the author (an AI augmentation, a personal sidecar note) now stands beside the authored note as its own unbadged block instead of joining the author's, where it would have read as part of whatever their badge was saying. An AI-reshaped note replaces the whole stack, keeping the badge only when there was exactly one note to reshape. - A card's note carries its GitHub alert badge to every client.
CardDto.noteis now a list of note objects ({badge, units}) rather than a flat list of display units, so a> [!WARNING]and a> [!NOTE]are finally distinguishable on the wire.badgeis lowercase (note,tip,important,warning,caution) and absent for a note no blockquote opened: a card table's note column, an AI augmentation, or a personal note. Adult, kids, and mobile read the new shape; badge colours and the multi-note stack come next. A client written against the old flat list must unwrapunits. - Emphasis follows CommonMark's flanking rule on both sides of a run, so
*and_next to punctuation no longer style text GitHub leaves literal.a**.tail**,a*.tail*, anda**(x) y**render as written now; before, alix bolded or italicized them._also gets the spec's stricter rule, so an underscore between two word characters stays a literal underscore in more places. A deck relying on the old behaviour will show the markers instead of the styling. One known deviation, named in the code: CommonMark's punctuation class is Unicode category P or S, and alix approximates it as neither alphanumeric nor whitespace, so an accent typed as a separate character, or an emoji's invisible presentation selector, reads as punctuation when it sits immediately before a marker. The accent case is a regression:*cafe+ combining acute +*sused to italicize by luck and now renders literally. Only that position is affected; the same accent anywhere else in the run, and the ordinary precomposed spelling, are unchanged. - BREAKING: a note is a badged blockquote, and every other blockquote is a quote. A blockquote whose first line is exactly one of GitHub's five alert badges (
[!NOTE],[!TIP],[!IMPORTANT],[!WARNING],[!CAUTION], that spelling and that casing) is the post-answer note, and the>lines under it are its body. Any other blockquote is content: it belongs to the answer and reveals with it, which is how a deck carries an actual quotation for the first time. Existing decks are affected without failing: a bare>note becomes a quote in the answer, so add a badge line above each one. A badge alix does not know, a badge in the wrong case, and a badge with text after it on the same line are all quotes, matching GitHub, and each draws a doctor warning naming the shift; a badge whose body is empty or only blank>lines draws one too and shows nothing. A note is trailing card machinery like a directive comment, so a directive may stand above it and answer content may not follow it: prose or a quotation below a note fails loud naming the badge line, rather than being silently reordered below the note. A card that resolves no note carries no badge either, so an addressed cloze note never leaves its badge on a sibling hole's card. Sidecar notes in a personal file are unaffected: their<!-- note: card-... -->marker already declares them. Every prompt that writes a deck emits the badge: deck and workspace generation, the deck reviser, the exam's card shapes, the trace generator, and the shared card-shape guide, so generated material carries notes as notes rather than as quotations in the answer. - BREAKING: comment machinery trails its block (everything-trails). A mapping invocation (
<!-- cards -->,<!-- plain -->,<!-- choices: single -->,<!-- choices: multiple -->) now sits on the line directly below the table, task list, or---it maps, never above; a recognized invocation above its block fails loud with the line, the why, and the fix. One invocation maps exactly one block and consumes it, so adjacent tables each take their own and a second invocation below the first fails loud instead of silently overriding it, for a table as for a task list. Only the recognized machinery run stands between a block and its invocation: a note, a fence, a table, an editorial comment, or an unknown directive key in between is a block of its own, so the older one no longer binds. The run reads the same downward from a---divider and from a table as from a task list. Frontmatter stays the leading exception, and a table row's inline<!-- r:... -->stamp stays the one sanctioned inline position. Existing decks with leading invocations fail as ordinary invalid input; move each invocation below its block. Card directive comments (reveal:,direction:,input:,at:locators, the id) gather at the card end the same way: a recognized directive on the heading line or with card content below it fails loud with the line, the why, and the fix. Adiagram:stamp keeps its place on the line after its fence. - Link-definition lines (
[label]: destination) are consumed as deck-wide metadata instead of being answer content: they no longer display, never join typed grading, and a card whose entire answer was definitions fails loud as answerless. Fences keep them literal, an escaped bracket or a hole marker keeps the line prose, and the deck file is untouched. The collected labels are what reference links resolve against. Recognition follows the GFM link-reference-definition block grammar, not a line shape: a destination is an<angle>form or a bare run with no raw whitespace and balanced parentheses, a title may sit whitespace- separated on the same line or continue on following lines, and definitions in section prose join the same deck-wide table. An invalid shape (raw-space destination, trailing prose, unclosed title) consumes nothing and stays visible answer content. - The typing-mode character reveal is removed (ruled 2026-08-24 with the aid naming: revealing answer characters piecemeal only slow-fails the card, and partial recall belongs to the future grade-capped hint). The
review.hintconfig key and itstab/ctrl-h/ctrl-backspacebindings are gone; a config still namingreview.hintfails as an unknown key. - Four reserved Markdown shapes are now line-numbered errors with a suggested rewrite instead of silently showing their markers as card content: setext
===underlines, indented code blocks, nested> >quotes, and a thematic break of any spelling anywhere but the front divider (paragraph and task-list continuations, fence interiors, and standalone===lines stay legal). Trailing-space hard breaks are a non-spelling: content lines shed trailing whitespace. - Markup-rejection parse errors are onboarding surfaces: every message that fires on ordinary pasted Markdown (an answerless heading, a deep heading without its parent, prose before the first heading, empty headings, malformed or mis-sized card-table lines) now names the line, why the shape is rejected, and a concrete supported rewrite.
- Bare content shapes render literally (breaking). A task list or pipe table without an invocation (or deck default) no longer becomes a choice card or card table: it stays literal content, so a pasted GFM document loads without acquiring study semantics. Existing decks that relied on auto-recognition must opt in via the new invocations or the deck-wide
choices:default; generated decks declarechoices: singlethemselves. Under an invocation, shape violations that used to be lints (mixed content, a wrong[x]count, a missing distractor) fail loudly. - A break line has exactly one legal shape (breaking). A front divider sits directly above its answer with a blank line or the card's own heading above it; any other break is now a loud parse error instead of silently joining the answer as content. A literal break line is written
\---for a dash spelling, or kept as content by<!-- plain -->on the line below. A bare##with no text after the hashes is now an empty heading, an error, rather than prose. - Frontmatter alix writes follows one canonical key order: authored keys first (
title,description, directives,source/link), machine lines last, so a minted deckidnow lands at the bottom of the block instead of the top. An author's own order still never matters and is never diagnosed. format-versionis now a reserved key; a deck that does not declare it is format version 1 (breaking).alix deck initand the personal sidecar writer no longer write the key, and a deck with anidbut noformat-versionparses instead of failing. A hand-writtenformat-version: 1is still accepted, and any other number is still refused.- A deck body starts with a heading (breaking). A line that sits before the first
#or##, outside any card, is now a parse error rather than silently becoming the section context of the cards above the first heading. A deck of plain cards still needs no section: opening with##is fine and those cards have no context. What a deck is about belongs intitle:anddescription:. - Every shipped deck now declares its name in frontmatter. The tutorial, the four decks bundled with the mobile app, and the nine committed examples carried an
#heading from the era when that heading was the deck's title; under the current rule it is section context, so those decks were showing as their filename stems. Each now hastitle:(and, where the intro prose was doing that job,description:), and the example screenshots were retaken against the checkout. - Heatmap, topology and crumb cells are objects now (breaking wire change). A cell was a bare tier string; it is
{tier, locked}, wheretierkeeps the same seven values andlockedsays the card is a sub-card whose parent has not graduated. The two are orthogonal: a locked card is usually stillunseen. A card served in a session also carries itssection_contextwith per-line runs and fence units, so a client can show the section a card sits under. Both are documented indocs/API.md. - The prebuilt installer now resolves one immutable release tag and verifies the downloaded archive against that release's SHA-256 record before extracting or installing it. Missing, malformed, mismatched, and corrupt checksum paths fail closed on both Linux and macOS.
- Headings are study structure, not deck metadata (breaking). A
#heading is now the SECTION CONTEXT for the cards beneath it,###and####are SUB-CARDS of the card above them, and a deck's name and description move into the frontmatter astitle:anddescription:. A deck whose first line was an#title now reads that title as a section, so every deck needs converting;tags:is no longer parsed. A sub-card is withheld from selection until every review unit its parent expands to has graduated at Recall (a two-hole cloze needs both holes, a titled table every row), and analix reset --cardof the parent closes the gate again. A withheld card counts toward a deck's totals but toward no due, new, or reviewable claim, so the picker, the queue, the backlog counts and the done summary agree. The gate is read when a sitting is assembled: a parent graduating mid-sitting frees its children for the next one, never under the card in front of you. - The mask markers are now reserved codepoints: an asked blank shows as
⍰and a hidden one as⬚(matching the boxed-?a masked formula renders), and authored deck text may not contain either character, the same way control characters are rejected. In exchange, literal____and[…]are ordinary prose everywhere, including beside blanks: a mask can no longer be counterfeited by text, so nothing needs rejecting. - An image now owns its line: prose or a cloze hole sharing a line with an image is a parse error. The display model always tore such lines apart silently (the sentence kept a hole where the image syntax sat, the image moved to the card's media list); the format now says so loudly. Inline images in prose are a named roadmap item.
- A card's tier now reflects what the learner did (ADR 0035). The published seven-value vocabulary becomes
unseen|seen|learning|learned-weak|learned-fading|learned-strong|retired, andlearningmeans "correct at least once, not yet graduated" instead of the oldacquired, which a merely acknowledged or peeked card could reach without ever answering. Revealing an answer no longer records anything:POST /api/revealis removed, and revealing then leaving meets the card as new next sitting, reversing the earlier reveal-counts-as-encounter behaviour on Alex's ruling that only the Seen acknowledgment introduces a card. The whole "acquire" vocabulary is renamed to "introduction" (/api/acquire→/api/introduce,StateDtoacquire/acquired→introducing/introduced, configacquire_cooldown→introduction_cooldown). This is a breaking, unversioned wire and config change (pre-1.0, no migration). - Recognize is now an ordinary scheduled depth (ADR 0033). A recognition answer creates and extends its own FSRS schedule at a laxer desired retention (
[review] recognize_retention, default 0.85), so recognition fades and returns instead of being asked exactly once forever. Recognize grades count inreviews/passed/failed; the per-depthrecognized/recognize_partly/recognize_missedtallies are replaced by onepartialcounter for all depths. A pass now propagates credit to every shallower depth with an existing schedule.alix listshows three per-depth cells (state plus due, shallow to deep) instead of the✓mark and the Recall-only due column, andalix statsgains adue now (recognize)line. The recognize deck badge now means mature-at-recognize, the same bar the other depths always had. Existing progress files fail to open (pre-1.0, no migration); recognize progress was one boolean per card and is recoverable by one pass. alix doctornow reports a frozen source excerpt whose lines moved, and--repair-source-locatorsrebases it. Previously a moved excerpt was found anywhere in the file and passed silently, so a card could point a reader at unrelated code while doctor said nothing, which was true of dozens of citations in a real workspace. A rebase corrects theat:line numbers only: the frozen bytes and their fingerprint are untouched, so no card gains a claim it did not already have. An excerpt whose content actually changed is still reported and never repaired automatically.- Breaking: remediation and tutor cards are no longer stored inside the progress file. They are Markdown blocks in the deck's personal file, so you can read, edit, and delete them yourself. A progress file written by an earlier version fails to load; there is no conversion (pre-1.0).
- Breaking: the "Promote to deck" review action is gone, with its
/api/promoteendpoint, itsStateDto.promotablefield, and its[keys.review] promotebinding. A minted card is already a Markdown block in a file you own, so there is nothing left to promote it into. The review badge no longer carries aremediation ·prefix. alix resetclears a personal card's schedule and leaves the personal file alone, the same treatment the authored deck gets.- Generated decks now pick a card shape from the material rather than choosing between plain and cloze.
alix deck generatecan produce card tables, line-by-line reveals, both-direction cards, and draw cards, which it previously had no way to emit, so a vocabulary source becomes a table and a procedure becomes an ordered reveal. Its prompt is built from the same rule the manual publishes, so the two cannot drift. Settingcard_stylestill pins one shape for a whole run. make web-debugnow runs the ordinary server with--log http, which keeps verbose request timings in the local log and mirrors them to stderr.- A new card's badge now names its interaction too, not just "new":
new · choicewhen it offers options,new · drawon a sketch card,new · revealotherwise. It still never names the graded check the card's schedule will use once the card has been met. - The session summary no longer starts the next card by itself. When a settle gap passes while the summary is open it says it is ready and arms Continue, leaving the choice to the learner instead of dropping them into a graded card.
alix deck initwrites the prepended frontmatter block closed directly after theid:line, with the blank line following the block instead of sitting inside it.- Old deck formats are no longer recognized anywhere. The dedicated retired-key errors (
alix-id:,origin:), the doctor's "un-converted" classification of decks and state documents, and the workspace manifest'soriginrejection are gone, along with every "deck conversion tool" suggestion (no such tool ships). An old artifact now fails like any other invalid input: unknown keys lint, ids and locators fail the current grammar, unreadable documents error, and nothing suggests a remedy. The dedicated errors for a" + "-joinedsource:value (deck frontmatter and workspace manifest) and the stray-aggregate-state-file warning are gone on the same ruling; a joined source is an ordinary path that fails to resolve. The retired%-prefixed directive spelling is likewise gone from the config templatealix config --initwrites and from the backend source-reachability errors, which now say plainsource:. The deadReveal::Clozeenum variant is deleted outright, and the cloze image hash preimage now uses the currentspelling. - The retired
reveal = "cloze"value is rejected in a workspace manifest's[defaults]too, the one scope that still accepted it, and theworkspace inittemplate stops advertising it. Cards declare blanks on their blocks with<!-- blank: span ... -->or<!-- blank: rect ... -->directive comments; no workspace default creates one. generate'smax_cardsis now a soft ceiling with a default of 100 (was a hard-worded 30): the prompt aims for the configured count, and a generation that comes back larger is kept in full with a warning instead of being constrained.- Text-span blank cards are Recognize-eligible again, preserving the depth that the inline cloze cards they replace had before retirement. They enter choices augmentation, and authored, cached, or sampled distractors build their choice questions when they supply a usable option set; an image-region card, or a named group containing one, stays excluded.
alix doctorreads a bare token inrequires:as an ordinary dangling filename prerequisite. The dedicated note that singled the shape out and prescribed a rewrite is removed.
Fixed
- A display formula on the question side of an adult web card scales to the room the question region leaves it, and the region shows the answer region's edge fade when content still overflows. The region is capped at a share of the card and hides its scrollbar, so a tall masked formula at a short window lost its denominator with nothing to say so; the shipped formula-cloze example image showed exactly that.
- The desktop entry written by
install-desktop.shno longer describes alix as a trainer "for the terminal"; barealixopens the local web app. - The codex backend forwards a configured
[ask] effortto the CLI as its reasoning-effort config instead of silently dropping it. Nothing changes wheneffortis unset. alix deck initno longer fails on a card whose answer ends in a table: the stamper placed the card's id above the table, which the parser then rejected. A display table (one without acardsinvocation) is answer content, and the id now lands after it, trailing blank directives included.- A span blank bound inside a formula reveals its answer as rendered math, as the displayed answer regains its
$delimiters; grading still compares the plain hidden text. Previously the revealed answer of a math-classed span showed raw LaTeX source. - An exam labels each
source:file by the path the deck declared rather than by the file's basename, so two sources that share a name are no longer indistinguishable to the examiner. A deck declaringa/notes.mdandb/notes.mdpreviously produced two prompt sections both headednotes.md, and any question or rubric point mentioningnotes.mdcould belong to either. A source declared by a rooted path still reaches the prompt as its basename, so the exam never sends your directory layout to the model. On Windows that covers a root-relative declaration such as\Users\you\notes.md, which names a rooted location without being an absolute path. - An AI call now runs without your own instructions for the CLI it shells out to. Every backend reads an operator instruction file (
CLAUDE.md,AGENTS.md,GEMINI.md), and those instructions shaped replies alix parses strictly, so a rule as ordinary as "end every answer with a timestamp" made exam grading, distractors, notes and generation fail to parse. Claude now runs with--safe-mode, Codex with-c project_doc_max_bytes=0, and Copilot with--no-custom-instructions. Gemini offers no such switch, so that backend is unchanged and the gap is written down in the security notes. This also keeps your hooks, skills and MCP servers out of the subprocess alix hands untrusted deck text to. - An AI call on the Claude backend now runs with the tool set alix grants it, and nothing else. Alix passed the grant as
--allowedTools, which pre-approves the tools it names without removing any others, so whatever your own Claude Code settings allow was reachable from every alix AI call, including the ones alix treats as tool-free: augmentation, exam grading, and the workspace emblem. Deck text is untrusted input, so this mattered most for a deck you received from someone else. The grant is now also passed as--tools, which bounds the set, and the empty grant bounds it to nothing. Verified by running the subprocess and watching the filesystem rather than asking the model what it did. - Receiving a shared deck, transferring one between workspaces, and merging explored material now write the deck in canonical bytes, as the deck format chapter already promised. A received folder is normalized all the way down, including decks in an ordinary subfolder. All three landed the sender's bytes unchanged, so a received deck could arrive with a byte-order mark, CRLF endings, and trailing blanks, and stayed that way until some later edit rewrote it. Found by Codex.
- Normalization now drops a whole run of carriage returns at end of line, not just one. A deck written with
\r\r\nendings kept a carriage return per line through normalization, and stamping it then wrote a file mixing both terminators. Found by Codex. - Repairing a duplicate card id when a deck is opened for review writes the deck in canonical bytes. It rewrote only the id token, leaving the rest of the file as it found it. Found by Codex.
alix workspace updateblames the member you wrote, not a staged path you never saw. When a backend reply is not a deck, the run failed withcannot load proposed deck <staging>/decks/<name>.mdand a bare parse error, naming a file inside the staging directory and never showing what came back. The failure now readsdecks/<name>.md: the reply is not a deck (<reason>); it began: <the reply>. The quoted reply is untrusted backend output, so its control characters are escaped rather than sent to your terminal.- A diagram theme's base fill is read as a colour only when it is one.
hex_colorsplit the digits and handed each pair tofrom_str_radix, which accepts a leading+, sofill:#+1+2+3parsed as the colour(1, 2, 3)and picked a ground from it instead of being rejected. Every byte after the#must now be an ASCII hex digit, so a malformed fill fails with the message that says so. - A quoted option on an authored choice card keeps its own text. The choice builders ran the checked options through the answer-DISPLAY helpers, which exist for an answer that carries supporting blocks:
readable_answer_linesdrops a>marker andgradeable_answer_linesremoves a quoted line outright. So achoices: singlecard whose correct option was quoted could normalize onto an unquoted distractor and silently stop being a choice card, a quoted correct option and a quoted distractor projected differently (the odd one out gave the answer away), and achoices: multiplecard lost every quoted correct option from its select-all set, which changed what the card asserts rather than how it looks. An authored option is exactly one task-list line and an invoked choice card may carry no other answer content, so an option is now offered as it was written. - An AI reply that carries anything after its JSON answer is rejected by name instead of being cut to the last
}. The exam and the augment passes both sliced from the first{to the last}, so a trailing sentence was silently discarded when it held no brace and reported as "the model did not return valid JSON" when it did. A reply now parses exactly one JSON answer and allows only whitespace or a closing fence after it; anything else says the backend returned unexpected content after its JSON answer, so a model that retracts a grade in a second object can no longer have its first grade taken. Both copies of the parser are now one. - A fence marker inside a display-math block is math source, not the start of a code block.
$$, a line of math, a ``line, then$$parses as one closed block, but the renderer opened a code fence on the`and let it swallow the closing$$, so the card arrived as two code blocks instead of one rendered formula. Whichever block opened first now owns the other's marker until it closes, which is what the parser already did: a$$` inside a fence stays code, and this is its missing other half. - Both ends of the frontmatter fence now accept the same spellings.
---(with a trailing space) closed frontmatter but did not open it, because the opener was an exact compare while the closer trimmed, so a deck whose first line carried an invisible trailing space failed with a message about front dividers that never mentioned frontmatter. One predicate now decides both ends at all five sites that tested for a fence: a line starting with exactly---, spaces or tabs allowed after it, no indentation before it. The fifth wasgenerate's cleanup of a model reply, which found the deck's start by an exact compare: a reply whose opening fence carried a trailing space lost its whole frontmatter block, so the saved deck droppedsource,link,choices, its title, and its identity metadata. alix doctorchecks a workspace member's frozen excerpts even when the deck still holds an unfrozenat:citation. One live citation used to switch off the whole per-citation inspection, so a fingerprinted excerpt that had moved, changed, or gone ambiguous was never reported and the deck got a single line about freezing instead. The gate now covers only what it was for, an invalid frozen asset, and the live citations stay one counted error rather than becoming a warning each.- Duplicate card identities are resolved by the
id:line a deck file actually holds, not by the review-unit ids it expands into. Two consequences, both found by Codex. Copying a reviewed fact card and turning the copy into a cloze left the shared identity in place, because a plain card and a one-hole cloze claim different review-unit ids; the per-deck progress documents then both claimed the same hole-remap record and aggregate progress refused to open, blocking the library and session views until the identity was repaired by hand. And a block could be elected keeper of one of its holes and loser of another in the same scan, a verdict oneid:line cannot satisfy, which severed the surviving block from the other hole's history. - A duplicate repair rewrites the identity the parser read, never a matching token inside a fenced example. A card teaching deck syntax can show a complete card source,
id:line included, and an example copied from that card's own source carries its token exactly. Resolution rewrote the example, corrupting what the learner reads, and left the real duplicate in place. Found by Codex. - Two rows of the directives reference told authors their valid decks were invalid. It named
###/####and said nothing goes deeper, while#####and######have been sub-cards all along (chapter 3 says so), and it gaveplaina card-only scope while a break plus<!-- plain -->is legal in section context and its own description says so. Found by Codex. - A shared archive can no longer decide which directory alix deletes files from. A
.zipcan carry a symbolic link and the extractor recreates it, so an archive whose payload was a link to a directory sent the receive-side sanitizer into that directory, where it deleted every entry matching its personal-file patterns (dotfiles,recent.json,progress/,-bakand.json.tmpnames), and then planted the link among your decks. The rest depended on your filesystem layout: the link landed after a rename, or the fallback copy refused it with a message about sharing and a temporary path you never chose. An archive that carries a link is now refused, naming the entry by its place in the archive, and the message asks the sender for one that carries the file itself. The refusal comes before any path the link points at is read or written and before anything lands, so no file of yours is touched; entries the sanitizer already removed inside the throwaway extraction copy are the only deletions that can precede it. - The same boundary holds on every path that copies material into an outgoing tree, not just the folder walk. Sharing a single deck refuses a linked augmentation file or a linked owned-assets folder, and refuses it before the bundle directory exists, so no partial copy is left behind; the workspace walk's augmentation and owned-assets copies refuse a link the same way. A link inside a deck's owned assets was already refused a step earlier, since an object whose name is not its content address is invalid material to bundle. The single-deck half was found by Codex.
- Sharing a folder never carries a file out of it. Staging asked
is_dir, which follows a link, so a directory link inside the shared folder was recursed into and its contents copied into the outgoing archive or wormhole payload: a workspace that linked a notes, source, or media folder rather than duplicating it sent files the user never selected. A link is now refused by name before anything is staged, since the archive carries files rather than links, and the message says to replace it with what it points to. Found by Codex. - The mobile app's sync-conflict list counts one conflict once. The walk behind the bridge's
sync_conflictsvisited every spelling of a workspace under the root, so a linked workspace made one conflict file look like two to resolve. Same physical-identity rule as the folder listings. - One physical deck is one row, and one physical sync-conflict document is one thing to resolve, however many names reach it. A workspace or folder holding a deck and a link to that deck offered two study rows backed by one progress document, and a workspace deadline counted that deck twice; two workspaces sharing one
storereported the same conflict document twice, under the two lexical spellings that reach it. Member discovery and the conflict list now apply the same physical-identity rule as the folder listings, after eligibility rather than before, so an ignored alias can neither add a row nor take one away. Found by Codex. - A folder listing offers one physical workspace once. Linking a workspace into a deck collection is supported, and when the original and the alias were both under the configured root, the library listing and the picker catalog showed two entries backed by the same decks and the same progress: two choices that look independent and are not. Both routes now share one physical-identity rule, and in the picker it spans the recent entries and the scanned children, so the spelling you last used is the one you are offered. Found by Codex.
alix doctor --remove-backup-filesno longer counts one backup twice or fails after deleting it. The scan followed a directory alias a second time and a backup reached under two names was listed twice; the confirmed cleanup then deleted the file under the first name and errored on the second, after the destructive step, so the result no longer told the user whether cleanup had completed. Found by Codex.alix doctorcounts one physical deck folder once, however many symlinks reach it. Exposing a synced or externally located workspace under a deck collection through an alias is ordinary, and the deck summary walked every spelling: one workspace with a permission problem reported two unreadable paths, and a readable one had its decks and its folder counted twice. The findings traversal already resolved aliases to one physical directory; the summary now does too. Found by Codex.alix doctorno longer certifies a decks folder it cannot read as healthy and empty. A folder whose permissions changed (a shared drive, a removable disk, a restored backup, a synced tree) produced a greendecksrow with zero decks, which is exactly the wrong answer to the command people run when their decks disappear. The target folder now fails with the path and the I/O error, and a subfolder that cannot be read is named in the row instead of counting as zero. A single deck file alix cannot read is named the same way: a readable folder holding one locked deck reported the deck as absent rather than as unreachable, so the same green zero-deck row survived one level down. Found by Codex.- A break line after a card table is read by the break grammar rather than reported as trailing prose. While a table was active the scanner handed the outer grammar only headings, so a rule between a mapped table and the next card was refused with a message about directive comments, and
<!-- plain -->below it, the sanctioned way to keep the characters, failed to load the deck instead of becoming section content. Found by Codex. - A removal whose file replacement completed no longer wedges the rest of the sitting. A deck is written by renaming a finished temporary over it, and the directory sync after that rename can still fail (a network, FUSE, or sync-mounted deck folder, or a transient storage error). The rename had already happened, but the sitting recorded nothing, so the next Remove compared the deck against the text it held from session open, saw its own completed write, and refused as "changed on disk". Removal stayed refused until the deck was closed and reopened. The sitting now records what it wrote whenever the replacement committed, while the durability error still reaches the learner. Found by Codex.
alix doctor's repair flags reach every deck the check walked. Pointed at a folder that holds workspaces rather than decks, the check descends into each one and reports its findings, but all five repairs looked only at the top level, and even there only at decks that had been initialized, while the check also diagnoses uninitialized drafts. Since a repair is only ever offered by the warning that found the problem, the remedy the warning named did nothing at the scope people run it in,alix doctorwith no argument included. Checking and repairing share one traversal now, which visits each physical directory once: a symlink pointing back into the tree used to print a single finding dozens of times and schedule one deck's repair as often. The initialized-only set and the symlink cycle were found by Codex.- A card carrying two
id:directives is refused instead of half repaired. The parser took the last one as the card's identity while a rewrite targeted the first, so a duplicate that alix appeared to resolve on open was still shared afterwards, silently and permanently. One card holds one identity, and a secondid:is now a line-numbered error. Found by Codex. - Opening a deck resolves every duplicated card in it, not just the first. A card pasted twice inside one deck left the second copy sharing an identity until some later open happened to catch it. Found by Codex.
- A deck that changed while alix was scanning is no longer written on the strength of that scan. The directory scan that names a duplicate's loser takes over a second on a real deck folder, and an editor save or a file sync inside that window can resolve the duplicate, reorder the blocks, or both, which leaves every address and every claim in the verdict describing text that no longer exists. Applying it then either severed a card from its progress or moved that progress onto the pasted copy. Each deck is digested as it is scanned, and a repair refuses unless every participating file still hashes to what the scan read. One deck's repairs land in a single write, so no repair can invalidate its siblings' evidence, and the deck about to be written is the last one read, so a save landing during the other reads is not overwritten by text from before it. Found by Codex.
- A duplicated cloze card or card table is repaired instead of reported as already resolved. Duplicate detection compares the composed id a review unit answers to, which suffixes the authored
id:per cloze hole, table row, reversed half, and region, but a deck file only ever holds the authored value itself. Every repair therefore searched for a spelling no file contains and gave up. Copying a cloze card or a card table into another deck left both copies sharing scheduling identities, so grading one could move the other's progress. Repairs now address the authored value and the authored block: one block is one rewrite, however many review units it expands to. Found by Codex. - Removing a card no longer overwrites edits made to the deck since the sitting opened. A sitting caches the deck text at open and replays its removals over that text, so a deck edited in an editor (or touched by a sync tool) while the review screen was open lost those edits on the next Remove, silently, with the exposure lasting the whole sitting. Remove now checks the file first and refuses when it moved: nothing is written, the card keeps its place and its progress, and the reason is reported where a failed save is reported. Found by Codex.
- Opening a deck can no longer re-mint another deck's card token on the word of the review-open line scan. That scan trades full parses for speed and cannot know a file was refused, so a neighbour the parser rejects outright still claimed its tokens; a valid deck sharing one was then named the loser and rewritten on open, which mints a fresh token and severs that card's review history. The line scan still gates the cost, but it now gates on a repeated raw token, counted before deck-level exclusion can hide one, and a rewrite requires the full parse to agree. Reported by Codex from a fence-law divergence between
dedupand the parser, and reproduced end to end here: a no-break space on a closing---is enough. - Copying a card inside one deck no longer detaches the original from its review history. The duplicate resolver addressed the losing card by token alone, and the first id comment carrying that token in document order is the card that KEPT it, so opening the deck re-minted the original and left the pasted copy holding its identity. The loser is now addressed by its own front line. Found by Codex.
- Two shipped documentation examples that did not parse are corrected. The README's headline deck wrote
A Vec<u8>, its bytes on the heap., which the HTML doctrine refuses because<uis a tag shape, so the first deck a reader copies failed to load; it is now backticked, matching the book's own spelling of the same line. The manual's front-image example put a---directly under a content line, which has been aStrayDividererror since dividers took their meaning from position, and the paragraph above it described the old behavior. Found by extracting every deck-shaped fenced block from the manual and the root guides and parsing each one; the committed example decks underdocs/examples/were swept the same way and all parse. - A region- or topology-scoped sitting's crumb no longer reports a locked card as unlocked. The crumb paints every region of its topology, including cards the sitting was scoped away from, but it read the lock axis from the sitting's own slice, and that answer covers only the cards in it. Any locked card outside the scope therefore came back unlocked. The lock graph was always the complete deck's, so the fix reads the deck-wide answer (
Locks::locked_ids_everywhere) for a surface that paints beyond its slice. - Continuing at Recall from the summary keeps the sitting's scope. A session opened on one review order, or on one region of it, was re-selected without either, so the next sitting silently widened to the whole deck. The review state now carries
topologyandregion, and the summary's Continue action echoes them back. - A supporting quotation is no longer graded as a claim the learner owes. Three surfaces still derived their gradeable items from the card's raw answer lines: the Explain checklist's fallback (an un-augmented card asked the learner to reproduce the quoted source, and counted it against them), an authored select-all (each quotation line became a correct option to pick), and a trace checkpoint's points (the walk presented the source as a point to predict, and the compression exam's rubric required it). All four grading paths, typing included, now read one shared projection of the answer's own claims. An atomic choice keeps the whole answer as its option, quotation included, but shows it without the
>marker. - Every adult surface that shows a WHOLE answer renders a quotation as a quote block. Only the progressive line reveal walked the answer's steps; the Explain reveal (with and without cached key points), a reshaped flip, a reshaped introduction, and deck browse each still walked raw answer lines, so the learner read
>markers on exactly the flows that show everything at once. All of them now walk the same steps the progressive reveal does. - The tutor's reference card shows a quotation as quoted content rather than as its
>markers. The reference walked raw answer lines, so it kept the markup the review surfaces had already stopped showing. - A one-line quotation renders as quoted content in the kids client too. The render path was chosen from the raw answer line count, so a card whose whole answer is a single
>line took the plain-text branch and the child read the marker. The shortest quotation is the common one (a definition, a warning, an aphorism), and multi-line quotes happened to work, so nothing caught it. Found by Codex. - TypeLine on a phone asks for one answer line at a time, as it always has in a browser. Mobile opened every gradeable field at once and submitted them all on the first Check, so an ordered answer could be read ahead and answered in one shot, which is the mode's whole point gone. The checked prefix now stands above the one open field, and the field is emptied only when the server accepts a longer prefix, so a miss stays visible. Found by Codex; the defect predates the
answer_stepsslice. - A second fenced block after a line-reveal boundary renders its own diagram rather than repeating the first one. The answer was rendered in two passes and the second pass restarted fence pairing at zero.
- A card whose answer an AI reshape replaced can be typed again. Every client shows
display_back, while the check graded the authored answer, so a learner typing exactly what was on screen could not pass a reshaped card at Reconstruct, and the mode was chosen from a line count nobody saw. The check and the mode now both follow the displayed answer. The Explain rubric still falls back to the authored lines, which is what keeps it truthful. Found by Codex during reciprocal review. - A
reveal: linecard at Reconstruct asks for every line, not just the first. The check returned a result for every answer line however few the learner had submitted, and the client reads that count to decide whether another field is owed, so the first line closed the card and marked every remaining line wrong without ever offering its field. The reply now covers the submitted prefix, and a card passes only once the number of lines sent matches the answer. A line sent beyond that comes back failing instead of being discarded, so a client one field out of step sees the mismatch rather than a clean pass. Found by Codex during reciprocal review. - A cloze card keeps its block's
at:source citation, found by Codex during reciprocal review: the expansion built one card per hole and copied every other field but the citations, so a generated or hand-written cloze block with a locator produced review cards that could not be flipped to their source, silently, while the authored Markdown still carried the line.alix doctoralso reports one authored locator once now, however many cards its block produced, instead of repeating the same finding per row or per hole, and it checks each of a card's locators separately, so a healthy one no longer hides a stale sibling on the same card. - A deck or store file reached through a symlink survives being saved: the replacement lands on the link's target instead of renaming over the link and forking the two paths, and the file's own permissions carry onto the replacement rather than the process umask widening a restricted deck. A link whose target does not resolve, such as one pointing at storage that is not mounted, fails the save with the link untouched.
- Link display follows the GFM inline-link grammar at its edges: a nested bracket pair stays inside the label (
[array[index]](url)links) while a nested link beats the outer one ([foo [bar](/inner)](/outer)links onlybar, since links may not contain links) while a nested IMAGE does not, so[](/uri)stays one link around its image, a parenthesized aside with a raw space is no destination (the prose stays visible and graded), and an escaped\(never opens a destination. Angle destinations and quoted or parenthesized titles render as links, with a title always separated from its destination by whitespace, an explicit empty<>destination included; unbalanced or whitespace-carrying bare destinations stay literal text, backticks included, so a code span cannot hide an unbalanced parenthesis from the destination grammar. - A span blank no longer crashes a deck whose answer block contains an HTML entity, found by Codex during reciprocal review: the masking source map had assumed every escaped glyph was preceded by a backslash, so an entity at the start of a line killed the whole parse and a masked span next to an entity spliced the wrong bytes. Splices now cover an entity's whole authored footprint.
- Multi-backtick code spans keep their body as code in the display projection, found by Codex during reciprocal review: the glyph scan paired a backtick to the next single backtick instead of matching the opening run's exact length, so ``
<https://alix.study>`lost its brackets and rendered as a link instead of literal code. The scan now claims code spans with the same exact-run grammar as the parse classifier, and an unmatched run stays literal text. Fence-shaped lines inside per-line projections (a section's```rust``` line) stay verbatim instead of losing backticks to phantom empty spans. - Mobile renders subscript below and superscript above the baseline, found by Codex during reciprocal review: both had been drawn as small text on the normal baseline, so
H<sub>2</sub>Oandx<sup>2</sup>were indistinguishable on the phone while web rendered them natively. - A doubled backslash before an image marker no longer produces a silent third state, found by Codex during reciprocal review: the tag-shape carve-out judged
\\a live image by escape parity while the card scanner treated any backslash run as escaping the marker, so the deck loaded with neither the image nor an error. All three marker consumers (the card scanner,image_references, and the carve-out) now share CommonMark parity: pairs of backslashes are literal text, an odd run escapes the marker, and a live image on a line with leftover text fails loud as a mixed image line. - The kids reviewer now shows a bare-table note after reveal, found by Codex during reciprocal review: the table renderer existed but was never handed to the kids study view, so a note authored as a comparison table left the explanation bubble empty.
- A card table whose delimiter row is narrower than its header gets its own diagnosis naming the valid rewrite (a
---cell per header column), found by Codex during reciprocal review: the previous message recommended padding with empty cells, which is invalid in a delimiter row and led from one parse failure into another. - Two consumers of the card-depth rule missed the depths 5/6 widening, found by Codex during reciprocal review: the fast duplicate scan at review open ignored copied depth-5/6 cards (two cards could keep one identity and alias their review history), and a reader-authored depth-5/6 note label leaked into a personal card's answer. Card depth is now one named predicate every consumer asks, so the next depth change cannot strand a range literal.
- A fence-shaped line carrying an info string (like a nested ```
`rust``` opener quoted inside a longer outer fence) no longer closes the fence during review rendering. The parser already kept it inside the block; the web and review projections, the section and context unit walks, and the diagram capture now all apply the parser's closing rule, so the rendered code block matches the authored one instead of splitting into stray prose or math. Found by Codex during reciprocal review. - Inline backslash escapes follow the CommonMark punctuation rule: a backslash before any ASCII punctuation character yields the literal character (it previously worked for only five characters), and a backslash before anything else stays literal, so
\blank{...}is untouched. Grading keeps comparing the unescaped content. - Code fences follow the CommonMark closing-length rule: a fence closes only on a delimiter of its own character at least as long as its opener. A four-backtick fence can now wrap a three-backtick sample the way GitHub renders it, instead of splitting into two blocks and leaking the sample as prose; a shorter delimiter leaves the fence open, reported by the unclosed-fence lint at the opener line.
- Frontmatter holding more than one YAML document is rejected loudly. A mid-line carriage return could smuggle a
---document separator past the line scanner; the stamper then spliced the deck id into the invisible second document and a second stamp failed on the duplicate key (found by the weekly stamper fuzzing). - Long section context no longer replaces the adult review question and pushes the card's answer choices down the page. A compact control below the divider now swaps context into the answer area while the question stays fixed.
- The adult picker now shows loose decks that have never been opened alongside workspace groups. Previously they existed in the search results but were invisible in the ordinary picker, leaving mixed deck roots looking empty.
- The adult picker's action footer no longer shifts when focus moves between decks whose early exam is available and decks whose exam is still locked.
- Adult review cards now use the height available between the header and footer on tall windows, keeping the mode and question near the deck title and the authored note near the review controls.
- Long notes in adult review now show edge fades and
more belowormore abovepills, so hidden parts of an authored explanation remain discoverable and reachable while scrolling. - Keyboard navigation through a long adult multiple-choice list now keeps the focused option clear of the answer region's overflow fades and hints at both edges.
- Choice-card notes can no longer silently describe the wrong option after a shuffle.
generateand AI note augmentation reject position-dependent notes, whiledoctordiagnoses authored notes that name an option by number, letter, or screen position. - Adult review now draws a visible note-coloured divider between a revealed answer or source excerpt and its authored note. The generic hairline blended into the card background, making the two sections appear joined.
- Adult review no longer repeats that a newly introduced card will be quizzed in about a minute after its answer is revealed. The
NEWmode badge andSeenaction already communicate that state, so the extra sentence made the card busier without giving the learner another choice. - Adult deck filtering now closes the focus drawer when its selected row no longer matches. Previously an empty result could still show the hidden deck's description and heatmap above the
No decks match.message. - Deck lists now order numeric runs by value, so numbered titles appear as
10,11,100instead of10,100,11. The same comparison governs root entries and dependency-tree siblings while preserving recent-first, prerequisite, and blocked-last ordering. - A never-seen ordered card in the adult app now reveals one authored line at a time.
Reveal nextremains available until the sequence is complete, and only then changes toSeen; the same transition works from the Space key. - Adult ordered-card reveals now keep earlier lines fixed while later lines appear below them, instead of recentering the growing answer upward after every step. The remaining-content indicator is now a compact pill.
- Adult draw cards now place the learner's frozen sketch beside the expected answer in labeled comparison panes. Narrow screens stack the same panes so neither side becomes unreadably small.
- The advertised
curl -sSf https://alix.study/install.sh | shinstall has been failing since 2026-08-03. GitHub's/releases/latestis repo-wide, and themobile-v0.3.0APK release took that slot, so the installer resolved to a release carrying no desktop archive and got a 404. The APK release now publishes withmake_latest: false, leaving the slot to the desktopv*release, andmake toolchain-checkfails if that pin is ever dropped. - A deck whose cards are all inside the introduction cooldown can be crammed again from the web picker: the depth control stayed disabled once nothing was due, so the deck could not be reopened at all until the cooldown passed. The control now opens whenever a cram is possible (counting the deck's personal cards, which a session serves alongside the authored ones) and offers the cram tick-box; each depth still gates on its own due-ness unless cram is on, and the plain Learn button is unchanged. The mobile client has no cram control and is unaffected.
- Resetting one workspace deck's progress now reaches the picker immediately even when a workspace-wide augment view had been opened in the same run: the listing previously kept serving the pre-reset progress from a retained in-memory snapshot until the server restarted, so a successful reset looked like it had done nothing.
- A deck whose own progress document cannot be read now shows as a red
errorrow in the picker (web, and a rederrormarker on mobile) with a generic explanation line pointing atalix doctor, makes no progress claims, and refuses to start a review, exam, or browse: a fresh session would write new progress over the unreadable document. The kids app shows such a deck as "needs a grown-up" instead of a false "New", with no practise buttons. The damage is scoped to that one document: sibling decks in the same store keep their real progress, listings and the web server no longer fail wholesale over one damaged file (the server boots and lists over it), and a multi-deck or workspace session still refuses when one of the decks it is actually opening is the damaged one. Previously one unreadable document could empty the whole listing's progress or block the server outright. - A full-target
alix resetno longer parses the progress it is about to discard: each target deck's document is opened alone (sibling documents in the same store are never read), a readable document still resets surgically with orphaned schedules preserved, and a target document that does not parse as a progress document is removed after the confirm prompt discloses it (removal happens first, so a removal failure aborts before anything else is erased; I/O and other non-parse failures still stop the command). Previously one corrupt document blocked the exact command that removes it. alix reset <target>opened the configured decks folder's progress store before looking at the target, so one unreadable file there blocked resetting an unrelated deck or workspace. The store is now opened only by the branches that use it (--alland exact--card); a target reset touches only the target's own store.- The kids web app handed its error toast and its tutor unbound
setTimeout/setIntervalreferences, which browsers reject withIllegal invocationwhen called as plain object methods: the error toast never armed its auto-dismiss and the tutor's answer polling never started. Both timer sets are now bound towindow. - An unterminated
\verbin a formula (\verb|abcwith no closing delimiter) is now a loud math error on every rendered surface instead of silently rendering with the last authored character dropped, which is how the pinned renderer accepts it. Bind-time span validation rejects it the same way. - A typed card with several expected answers can no longer be passed by answering only some of them. Grading returned one result per submitted line, so an unanswered line produced no result and the card passed when every returned result passed. Both the unordered and the ordered check now return a result for every expected line, failing the ones nobody answered. The adult web app was unaffected, because it submits one field per expected line including empty ones, but any client that sends only filled fields was told a partly answered card had passed.
alix doctorreports a cloze block whose>note spells out one hole's answer. Every hole of a block shows the same note, so reviewing a later hole reveals an earlier one's answer before the learner has met it. The warning states the fact rather than a judgment: "the note contains the text of hole 1's answer (Unit); every other hole shows this note". It cannot see a paraphrase, only the answer's own text.- Stamping never leaves a deck alix cannot read. The result is parsed before anything is written, and a stamp that would produce an unreadable file is refused with the file untouched. Tab-indented frontmatter did this: it parsed as written, and splicing the
id:in made it invalid YAML. alix deck initrefuses a deck whose code fence never closes, instead of writing an id line inside the fence. The card then still read as unstamped, so every later stamp appended another id and the file grew one each time.alix doctoralready named the unclosed fence and its line.- Stamping a deck that ends in a card table with no final newline put the row's stamp on a line of its own instead of in the row. The row then read as unstamped, so every later stamp minted it a new one and the row's card id changed with it, detaching that row's review history. Found by the new stamper fuzzing.
- A cloze hole cut out of a formula now defaults to the sketch input, because the hole's content is the expected answer and a formula's piece has no keyboard spelling. The mobile client honors the default; the desktop web app still serves the typed check. An
input:written on the card or the deck wins over the default on mobile, since the rule only fills in where nothing was authored; the desktop web app currently ignoresinput: drawon a cloze card. - A formula span whose hidden text sits directly against the next token renders again. In a derivative ending
nx^{...}, ablank: spanhiding thenproduced the undefined\cdotsxcontrol sequence and failed the whole formula; the mask now terminates before the following token. - A formula span now reveals as that formula's piece rather than as its source: hiding
\pmwith<!-- blank: span hidden="\pm" -->reveals a typeset ± where the source characters used to appear. The plain hidden text remains the answer, while its display projection carries rendered math. - The example images now show the card as the reader should read it. A choice example is photographed answered, because the cursor rests on option 1 and a picture of that reads as if option 1 were the answer: the table example appeared to pair "to advocate" with "widerlegen". The draw example is sketched on, since an empty canvas photographs as an empty box. Every image also waited on a fixed pause that expired before the header logo finished forming, so all of them were captured mid-animation.
- The summary's "Next due in N min" counts down while the page stays open instead of freezing at the value it had when the sitting ended.
- A new card whose answer was revealed before the "Seen" acknowledgment no longer returns as a new card for the rest of the sitting. It came back after every introduction cooldown offering only "Seen" again, so it could never reach a graded review. Cards whose Recognize options need no AI (table rows, authored option lists) hit this on every introduction, because picking an option is what reveals the answer.
- A filesystem error after an atomic rename no longer strands progress or augmentation behind a stale in-memory revision: the error still surfaces, while retry bookkeeping follows the replacement that already committed. Retrying a virtual-card promotion after its deck write also recognizes the stable card ID instead of appending a duplicate, preserving its review history across the deck/progress failure boundary.
- The kids app behaves on touch screens: a long-press no longer opens the browser's context menu, page text cannot be selected or grow a blinking insertion cursor, taps skip the double-tap-zoom delay, and the answer and rating buttons grow to a child's finger size on coarse-pointer devices (the tutor's text box keeps selection and its menu).
- A kids card with a picture asks the question above it, matching the authored front order and the adult app, instead of hoisting the image over the question.
- A Recognize sitting no longer ends in "you reviewed 0 cards": a Recognize grade is an ordinary FSRS review and lands in
reviews/passed/failedwith every other depth, with onepartialcounter onStateDtofor the middle grade, and both web summaries show every kind of work done (introduced, reviewed). A sitting with no work at all no longer celebrates. - The
alix deck generate --reviewandalix deck augmenthelp texts name the AI backend neutrally instead of hardcoding Claude, the augment progress line names the configured backend, andalix receive's help mentions workspaces alongside decks and folders. - Multiple-choice option text was near-invisible on the gruvbox-light and catppuccin-latte themes: ten themes omitted the text/faint/accent-ink/ brand-text tokens and silently inherited the dark default's light gray. Every theme now defines the full token set (dark themes keep their current rendering), pinned by a token-parity test.
- The augment dialog's footer no longer places the destructive "Remove all" between "Generate selected" and "Close"; it now sits first, so a reach for Close cannot land on it.
- Saving a tutor note onto a stamped card appended it after the card's closing
<!-- id -->marker, tripping doctor's misplaced-marker warning on every noted card. Notes now land before the card's trailing comment markers, keeping the id line last. - A malformed or edited
alix workspace updateproposal can no longer name a Windows root-relative or drive-relative member that escapes the workspace on apply. Every member path must contain only normal relative components before it can reach an atomic write. - Ask Tutor on adult web now opens for a card whose answer contains a pipe table. The tutor receives the table renderer it calls, so the reference renders as a table instead of throwing before the panel opens.
alix deck generateandalix doctor --repair-source-locatorsnow stamp every citation when card-table rows share oneat:line or cards arrive out of file order. Rewrites address citations by line and count distinct physical lines, so a valid deck no longer fails after it is written.- Web catalog revalidation now counts directory entries, so adding or removing a workspace member in the same filesystem timestamp tick invalidates the snapshot on every platform instead of remaining invisible.
[keys.review] contextcan now be rebound as the manual promised. Setting it no longer rejects the whole config and takes the decks directory, backends, and every other binding with it.alix deck augment --helpnow lets each valid--targetdescribe itself, includingkeypointsandformat, instead of repeating a stale four-target list above the complete set.- Relative and absolute spellings of the same deck or member directory now resolve the same workspace and review store. Running
alix stats d.mdinside a workspace'sdecks/directory no longer opens a different store and reports an in-progress deck as not started. alix deck init d.mdfrom a workspace'sdecks/directory now freezes source excerpts like the absolute spelling. It no longer stamps card ids while leaving live citations in a partially initialized deck that another relativeinitcannot finish.alix workspace init's manifest template advertisesreview:as the commented[defaults]scheduling sample. The sample previously named a key the current format does not define, so uncommenting it was silently ignored.- The warning printed when assembled decks disagree on their scheduling setting names the
review:key. It previously pointed the reader at a key the current format does not define.
Release gates
- The semantic documentation audit (
make docs-audit) did not reach a clean sample for this release. Fifteen rounds ran on the candidate; every round reported findings, every finding was verified against the code and fixed, and the round count was capped at fifteen instead of rerunning to PASS. Grader calibration (its matrix is stored underdocs/release-records/), the card-shape evaluation, and the old-format audit ran to PASS on the release candidate's production code. Every text surface edited after the last audit round went out unaudited: this changelog's rollover, the entries moved into the mobile changelog, the version pins in the README,docs/API.md, and the book's getting-started page,RELEASING.md, the calibration record underdocs/release-records/, and the web-app chapter's Doctor row list from the last round's own finding.
v0.7.0
Added
- Desktop release downloads are now verifiable: every release asset uploads with a
.sha256checksum beside it, and releases are gated on a clean RustSec advisory scan of both lockfiles (make audit, backstopped by a nightly advisory-drift workflow between releases). - Revealing a new card's answer now counts as the encounter: leave the session right there, without pressing Seen, and the card still will not re-introduce as new next time (it cools and returns as a regular review). Leaving before the reveal keeps the card new. Both web clients report the first reveal (and the first pick on a new choice card) via the new
POST /api/reveal, which records the engagement without advancing the session. The drained-Recognize summary's Augment chip also gained itsakey binding, matching the picker. - An exhausted Recognize sitting now says what it was hiding instead of "Nothing due — come back later": a deck whose pick-capable cards are all recognized reports how many cards wait at Recall and how many have no choices yet (
StateDto.recognize_gap), and the adult summary points at both exits, with "Continue at Recall" (Enter) and "Augment" actions. Previously a deck with a few authored choice cards defaulted to Recognize, served only those, and then looked permanently empty while the rest of the deck was untouched. The done summary also no longer prints zero-valued stat rows, an instant-empty select no longer announces "session complete" for a session that never happened, and "N still due" beside a disabled Continue now says the cards are cooling and when one opens (next_due_mscarries the acquire-floor instant, previously absent at Recognize). - Decks carry a
format-version:in frontmatter, written aboveid:.alix deck initwrites it, it stays1before 1.0, and alix refuses any other number with a message telling you to upgrade rather than guessing at a format it does not know. This is the same detect-and-refuse guard the progress documents already had, which decks lacked: a deck from a newer alix previously misparsed into confusing lint noise. - Frontmatter now reads
authors,license,tags, andcreated-at.authorsandtagsaccept one value or a list,licenseandcreated-atare single strings (an SPDX identifier and an ISO 8601 date by convention). They were accepted but discarded before. alix stores and never rewrites them. CardDtonow carries the card'sidon the wire (card-<token>, or the-N/-rsub-id for a cloze hole or reversed twin), the same spellingCreateCardRespreturns. Clients could not previously tell whether a served card had actually changed except by comparing rendered text. Null for a card with no id marker yet.alix generatenow reports calm, live progress from structured Claude and Codex events while keeping partial deck text private until validation succeeds. Deck-drafting calls have a one-hour absolute safety limit. Generation paths using structured events also have a separate five-minute inactivity limit that resets whenever the agent emits real activity and can be disabled withidle_timeout_secs = 0. For Gemini, Copilot, and unstructured wrappers, the same setting is a nonrenewing absolute fallback: by default they retain the prior five-minute ceiling rather than waiting the full hour when wedged. They report generic activity and forward bounded stderr diagnostics.alix generatenow accepts--language,--audience, and--card-style(mixed,plain,cloze, orauthored-choices), and applies--goalto single decks as well as directory plans. The same controls reach generated workspaces; card style governs facts-deck items while trace items retain their checkpoint shape. Explicit styles are parsed and checked before a deck is written, and the optional review pass preserves the requested contract.- A standalone
orchestrateresearch CLI runs Claude Code and Codex against one frozen spec in isolated worktrees. Symmetric differential review accepts only mechanically reproduced, user-relevant defects; asymmetric runs pair an implementation with an independent property suite. Atomic state, raw transcripts, live durable heartbeats, concurrent independent agent calls, bounded fixes, serialized mutation gates, correctness-gated scoring, and test-first landing make every run resumable and inspectable. - The kids client and the mobile app now show the persistent "progress isn't saving" banner the adult web client already had. On mobile a failed per-grade save no longer aborts the grade behind the scenes: the review or walk continues in memory, the banner names the failure, and the next successful save carries every earlier grade (the finished-review payload gains an additive
save_error). - The picker's focus drawer shows a nested progress funnel pinned to its top-right:
N cardsalways, thens seen,k learned, andr retiredappended as each count becomes non-zero, so a fresh deck reads as a plain "N cards" and a worked deck fills in. The/api/deck-drawerpayload gains the additivetotal,seen,graduated(labelled "learned" in the drawer), andretiredcounts, which nestretired <= graduated <= seen <= total; the per-cardheatmaplists only stamped cards and cannot stand in for them. - The empty "Nothing due." session screen now shows one quiet line saying when the next card is due (for example "Next due in 4 min." during an acquire cooldown), so an empty sitting explains itself instead of only reading "Nothing due." Adult web and mobile; the finished session payload (
StateDto/ReviewState) gains an additivenext_due_msinstant. alix deck copy <deck> <workspace>andalix deck move <deck> <workspace>transfer one initialized workspace member with its owned frozen assets and augmentation. Both reuse the same public bundle boundary as wormhole sharing; copy excludes progress, while confirmed move carries progress between distinct user roots and removes the source only after the destination is complete.alix workspace update <dir>reconciles frozen source-backed members with their live local sources. It stages an exact sibling workspace for review, then--applypublishes those same bytes without another model call or--discardremoves them. Retained IDs require unchanged learning content; changed and obsolete cards retire with their IDs, while replacements receive fresh IDs during staging.- Plain fact cards can carry multiple
<!-- at: ... -->citations. The adult source view resolves every locator and stacks the editor-style excerpts in authored order inside one scrollable answer region. alix doctor --repair-source-locatorsexplicitly fingerprints reviewed source citations and rebases a uniquely relocated exact excerpt while preserving deck and card IDs. Plain doctor remains read-only.- A private vulnerability-reporting policy and a tracked threat model covering local files, LAN pairing, AI providers, sharing, persistence, mobile, and release boundaries.
alix profile: define and launch a named alix instance per person (its own decks, port, and adult/kids frontend), reachable on your LAN with a stable token so phones can bookmark it.alix profile add/list/remove,alix profile <name>to launch,alix profile defaultto pick what barealixlaunches, andalix --launch-allto boot every profile at once.- Multiple-choice cards you author directly: write the answer as a GitHub task list (
- [x]correct,- [ ]distractors). It renders as a checklist in any Markdown previewer and drives the Recognize quiz from your own options, with no AI distractor pass needed. Task lists in notes and card fronts render as checkboxes too. - card text now renders inline Markdown:
**bold**,*italic*/_italic_, and `code`. - LaTeX math in cards:
$...$renders inline and a whole-line$$...$$renders as centered display math in adult web, kids web, and mobile. Formula clozes support\blank{...}, and RaTeX chemistry remains available through\ce{...}. The Rust core produces one self-contained SVG shared by every graphical client while decks, grading, fingerprints, and progress retain only the authored source. - Committed manual-QA examples for graphical math rendering and a self-contained workspace with a frozen Rust ownership trace.
- the picker's focus drawer now shows a deck's preamble (the prose written under its
#title), which was parsed but never surfaced before alix doctorflags a danglingrequires:(one naming a deck that does not exist), so a renamed or deleted prerequisite is reported instead of silently dropping the gating edge.alix doctorwarns when a card's<!-- id: -->marker is not the card's closing line (the position stamping mints at), so a hand-placed marker drifts back to the canonical shape instead of scattering through the deck.
Changed
- Breaking: an initialized deck (one with an
id:) must now also declareformat-version: 1. A deck without it fails to load andalix doctornames the conversion tool. Decks with noid:are unaffected: they are uninitialized, andalix deck initwrites both keys. Pre-1.0, existing decks are converted by a disposable external script rather than by a compatibility path in alix. - Breaking: every card-relative review POST (
/api/grade,/api/skip,/api/acquire,/api/check,/api/choose,/api/remove,/api/promote,/api/restart, and the four tutor POSTs) must echo theStateDto.study_revisionin anX-Alix-Study-Revisionheader. Missing or malformed is 400; stale is 409 and mutates nothing, so retrying a grade whose reply was lost cannot grade the next card. Both web clients echo it and refetch the state on a 409. - Breaking:
POST /api/choosenow takes{index, card}, wherecardis thecard.idof the card the pick was made on. A pick naming any other card is refused with 409 and grades nothing. The revision header proves the client saw a transition; the id proves it is answering the card it rendered, which the revision alone cannot. Both web clients send it. - Session pacing is now two
[review]keys:max_session(cards a single sitting serves, default 10) andnew_cards_percent(the new-card share of that cap, default 30). The oldmax_new/limitkeys and the--new/--limitlaunch flags are gone;--session Noverridesmax_sessionfor one launch. Each sitting first selects its capped set (new and due each get a share, whichever pool is short lets the other backfill to the cap) and only then orders that slice for serving, so a deep overdue card no longer starves behind shallow ones. Per-card cooldown floors now survive a restart, so a chained sitting skips a card that is still cooling. The/api/selectbody dropsmax_new/limitfor a singlesession?field, and the done-phaseStateDto/ReviewStategain additivedue_left/new_leftbacklog counts (the summary now says "N still due" or "Start N new"). Testers: delete any commentedmax_new/limitlines from your config andalix.local.toml(a stale key is now a loud error naming the replacement, andalix doctorflags it in a local manifest). - "Seen" now means the card was shown to you at least once, right or wrong: the first time a card becomes the displayed card in any session, the store records a one-time presentation stamp (
presented_ms), so a card you met and failed, or merely opened a session on, no longer looks untouched. The drawer funnel'ss seencount follows this meaning.acquired_msis now absent until a card is acknowledged or answered correctly at least once. - The drawer and breadcrumb heatmaps paint five tiers instead of a retrievability gradient: neutral untouched, grey seen, white acquired, a learned (graduated) card green/yellow/red banded by its current Recall retrievability (strong
>= 0.9, weak< 0.7, fading between), and purple retired. On the wire,DeckDrawerDto.heatmap, its topologycells, andCrumbDto.cellscarry tier names (strings) instead of numbers. - A picker row no longer prints a right-aligned status counter for a new or started deck: "new" duplicated the NEW chip, and a started deck's
k/Ngraduated counter was cryptic and redundant beside it. New and started rows now show the title and state chip only, so the chip is the row's single state signal; finished, mastered, and exam-due rows keep their status word. The graduated count moved to the focus drawer's progress funnel (as "learned"). - Breaking (pre-1.0): deck ids are now self-describing and prefixed. A deck declares
id: "deck-<token>"(theid:frontmatter key replacesalix-id:), and every card marker is<!-- id: card-<token> -->(card-<token>-Nfor a cloze hole,card-<token>-rfor a reversed twin). The same prefixed id names the deck's state documents (progress/deck-<token>.json,augment/deck-<token>.json) and asset directory (assets/deck-<token>/), andrequires:accepts adeck-<token>id so a prerequisite survives a rename. Source citations use named fields,<!-- at: <src>:<lines> fingerprint: xxh64-<hex> asset: sha256-<hex>.<ext> -->, replacing the old@ xxh64:form:at:is always the real source path, andasset:(present only on a frozen citation) holds the cited excerpt exactly. Freezing no longer rewritessource:and stamps nothing; a frozen deck keeps its real source. Theorigin:key in deck frontmatter and the workspace manifest merged into the multi-valuedsource:, and the card-levelorigin:directive was retired with no replacement; a workspace manifest may declare a top-levelsource(the material the workspace is about), which the tutor and examiner receive as layered supporting context under the deck's own sources and whichhas_examcounts. Asource:value is one expression (a URL, a file, or a directory); the" + "-joined form is retired and now fails to parse with a one-source-per-list-entry hint. Old-format decks fail to load loudly and the deck conversion tool rewrites them; there is no runtime reader for the old shape.alix doctorflags an un-converted bare-token state document or asource:that points intoassets/. - Progress and augmentation documents now reject an unrecognized field instead of ignoring it. Before, renaming or removing a field in a future build would let old documents load with that field silently dropped, losing that data on the next save; now such a document fails to load loudly and is left on disk for external conversion, with no format version bump.
- Review progress now persists as it happens: every grade, acquire, exam flag, badge, and card mutation writes the deck's progress document before the response returns, so closing the browser or killing the server mid-session no longer loses the sitting. The former session-batched flush (one write per transition) is gone; transition flushes remain as backstops.
- The server drains its workers, flushes any unsaved state, and exits cleanly on Ctrl-C or SIGTERM instead of dying mid-request.
- Dependency changes now pass a reviewed duplicate-family gate through
make deps-check, preventing an avoidable second compiled version from entering the graph unnoticed. - Breaking (pre-1.0): initializing a source-backed workspace member now freezes each cited source excerpt and every local card image before success. A citation's frozen object holds exactly its excerpt under
assets/deck-<token>/sha256-<digest>.<ext>(uncited lines never enter an asset), and a citation-less member initializes with no freezing at all;source:stays the deck's real material, and runtime source consumers fail closed on live, cross-deck, missing, or corrupted assets. A URL-valuedsource:grounds the exam and tutor but holds no freezable bytes; citations must land in a local source. - Single-deck sharing now carries and validates the complete deck-owned asset directory plus matching augmentation while continuing to exclude progress. Generated workspaces freeze in hidden staging before publication, and merges add immutable objects without replacing unrelated decks' assets.
- Breaking (pre-1.0): typed
WorkspaceFilesandUserFilesowners now separate shareable deck material from private learning state. Assets and per-deck augmentation stay with the workspace content;--storeand a workspacestoresetting relocate only progress and recent history. Sharing carries matching assets and augmentation while excluding progress and local configuration. - Tutor and exam grounding now combines frozen evidence with the live
source:values, deck and workspace layered apart (deck sources are the primary grounding, the workspace source supporting context). URL sources are fetched only when the backend and tool grant permit it; local sources still require explicit source access. The tutor continues from frozen evidence with a visible warning when the full current source is unavailable.alix generate --source-url <URL>records a portable public URL as an additional deck source or the generated workspace'ssource. - Breaking (web/mobile APIs):
CardDtoand the shared mobileCardViewreplace the singleatcitation with orderedcitations; web citation entries carry their resolved excerpt or per-locator error. Both views also gainback_units, the core projection used to render ordinary answer prose independently of authored physical line wrapping. - Breaking (pre-1.0): every complete
<!-- at: ... -->citation now carries a namedfingerprint: xxh64-...field. Review, trace, tutor grounding, and grading fail closed when the addressed text does not match, showing a warning instead of unrelated source. Generated and frozen citations are stamped at creation; hand-authored citations remain incomplete until explicitly reviewed and stamped. - Breaking (pre-1.0): workspace member decks now live only under direct
decks/*.mdchildren. Manifests, assets, and augmentation remain at the workspace root; private progress is colocated there by default but may use a separate user-files root. Relative member sources and images anchor at the workspace. Workspace creation and every generation/import/receive surface write the new shape, whilealix doctorreports initialized root decks that are not discovered. Existing workspaces must move their deck files intodecks/without changing itsid:or card ids. - Breaking (pre-1.0): progress and AI augmentation now live in independently versioned documents per initialized deck:
progress/deck-<token>.jsonandaugment/deck-<token>.json. Renaming a deck keeps its state, reviewing different decks on different synced devices no longer rewrites one shared file, and stale local revisions fail instead of silently replacing a newer document. This is a clean pre-1.0 format break: production reads only version-1 per-deck documents and contains no runtime converter for preceding layouts. Same-deck concurrent offline review is still unsupported; usealix doctor <folder>to surface incompatible or orphaned documents and synchronization conflicts. Sharing carries matching per-deck augmentation and recursively excludes all progress, temporary, backup, and conflict material. - Breaking (pre-1.0): a hand-authored Markdown file must be explicitly initialized with
alix deck init <file>before it appears in the picker or can be reviewed or augmented.alix deck initstamps a freshid: "deck-<token>"; a frontmatterid:whose value is not adeck-<token>id is rejected rather than adopted. Opening an initialized deck still assigns ids to newly added cards, while ordinary.mdprose with##headings is ignored and never modified. - Production CI and release workflows now select exact Rust, Flutter, Java, Node, Android NDK, FRB codegen, mdBook, and coverage-tool versions. Every directly referenced GitHub Action uses an immutable commit SHA, a blocking check prevents movable pins from returning,
make installexplicitly uses the repository's exact Rust pin and lockfile, and scheduled drift jobs remain the explicit non-publishing path for testing current upstream toolchains. - Breaking (pre-1.0): grounded tutor filesystem access now requires an explicitly declared deck or workspace
source; its root is the deck's first local-path source (workspace source as fallback). A citation still supplies the card's evidence, but alix no longer guesses a wider project root fromCargo.toml,.git, or other markers. A public URL source can supply current context whenWebFetchis available. - Trace source excerpts now highlight exact, case-sensitive terms that the checkpoint author marked as inline code in its key points.
- Additive (web API): card display projection now comes from the shared Rust core.
InlineRungains optionalmath,CardDtogainscontext_runs, andStateDtogainschoice_runsandkeypoint_runs; every run list stays in index lockstep with its existing text field.CardDtocontinues to expose text fallback for clients that ignore the new fields. - Additive (web/mobile APIs): trace walk state now carries inline-run projections for its description, checkpoint prompt, givens, key points, and note alongside the existing raw strings.
- Mobile review now consumes the core's shared inline runs for bold, italic, code, and LaTeX math instead of rendering raw card strings separately.
- Android release builds now size-optimize the embedded Rust core with fat LTO, one codegen unit, and stripped symbols.
make aabproduces the Android App Bundle for Google Play whilemake apkremains the GitHub-release and phone-smoke artifact. - Inline code (`
like this`) now renders with a distinct, theme-aware color for readability. - Breaking (pre-1.0): inline
*/_/**in existing card text now renders as emphasis; a deck that used them literally (e.g.2*3*4) will render/grade with the markers stripped. Escape with a backslash (\*) or wrap in inline code (`2*3*4) to keep them literal. Runalix doctor <deck>` to find affected cards. - Breaking (web API):
CardDto.frontandCardDto.backnow contain inline-marker-stripped content, while the newfront_runsandback_runsfields carry display formatting. Sentence-shapedNoteUnitvalues also gainruns. - the picker's focus drawer now opens for every deck, not only decks with a topology augmentation, and shows a per-card retrievability heatmap: a single whole-deck bar for a plain deck, split into named regions when the deck has a topology. Cards you have never reviewed render as a neutral cell rather than red, so a fresh deck reads as unlearned instead of failing
- Breaking (web API): the drawer no longer shows a raw due count.
POST /api/deck-topologyis renamedPOST /api/deck-drawer; its responseDeckTopologyDtobecomesDeckDrawerDto(gainspreambleand a flatheatmap, dropsdeck_due), andRegionInfoDtodrops itsduefield alix profile listnow shows each profile's config file path, so an unexpected decks directory or port can be traced to the file that set it.
Fixed
POST /api/chooseread its request body straight off the socket with no size cap, the one JSON route that bypassed the central 256 KiB body cap; a client could grow the server's memory without bound. It now reads through the same cap as every other JSON body.- Resetting a workspace deck over the API now reaches the deck listing immediately. Previously the listing could keep serving the pre-reset progress (deck "started", nothing startable) from a retained store snapshot until the workspace was next opened, leaving the picker's row actions disabled for a deck that had just been cleared.
alix --port 0(OS-assigned port) now announces the port the kernel actually bound instead of printing an unreachablehttp://127.0.0.1:0URL. Explicitly requested ports are unaffected.- The blank first load. A connection burst against a fresh or idle server (typically a page reload right after the server starts) could leave one accepted connection's request unread inside the HTTP layer's task queue, stalling a single asset or API call for about two minutes while its siblings completed instantly; the page shell rendered but the app never booted, and the focus drawer could silently fail the same way. The server now pumps its own listener once a second, which releases any stranded connection within about a second. Measured before/after: a restart-and-reload loop in a real browser stalled on the first cycle without the pump and completed 30 consecutive cycles with it.
alix deck initno longer writes a secondformat-version:into a deck that already declares one. The duplicate is an invalid YAML mapping key, so the deck stopped loading entirely: a file copied from the manual's frontmatter example hit this on its first initialization.- The picker no longer stays blank forever when a start-up request stalls. Boot already retried a *failed* request, but a request that never answers is not a failure: nothing settled, so nothing retried, and only a manual reload recovered. Start-up now gives up on a stalled request after four seconds and retries, which is the recovery the existing retry was meant to provide. This bounds the symptom; it is not a fix for whatever strands the request.
- Cloze cards read as fill-in-the-blank. The gap you are answering is a chip with a single rule on the baseline instead of four separated underscores, the other holes are quiet chips instead of
[…], and the gapped sentence now leads the card while the front line above it steps back to a topic label. The sentence is the question, but it was set smaller than the topic. Plain cards are untouched: the topic styling applies only to a card that has context lines. - The picker keeps keyboard focus when you click outside the deck list, so the row-navigation keys keep responding instead of going silently dead. Only clicks inside the stage were handled before; a click anywhere else stranded focus on the page body.
- Jumping to the last deck with
G/Endnow reveals its drawer. The drawer is fetched after the jump, so it opened below the fold and looked like it had not opened at all. - A submenu's Cancel now sits where Back does, on the left, instead of trailing the depth chips on the right. It is the same "leave this level" action Esc performs, so it no longer moves depending on which menu is open.
- The tutor panel names the model that actually answered instead of showing
model: default. When[ask] modelis unset the backend CLI chooses, and alix could not name it; it now reads the model out of the backend's own startup event and reports that. Unset stays unset: alix does not pin a model on your behalf, it just stops pretending not to know once the backend has said. Still showsdefaultbefore the first answer of a session, which is the only point at which nothing has been reported yet. - Reading session state no longer moves you to a different card. Sitting on one card for longer than the five-minute acquire cooldown (asking the tutor, for example) let an earlier failed card come off its floor, and because the server re-picked the first servable card in roster order on every state read, closing the tutor landed you on the card before the one you were working on. Polling now reports the session rather than reshuffling it: the card you are on is kept while it remains servable. Grading, skipping, acquiring, and removing still move on exactly as before.
alix generatenow checks the output destination before calling the AI backend. Pointing it at a workspace that does not exist, or at a deck name already taken without--force, spent a full generation (minutes, and a paid call) before reporting a failure that was knowable up front. Both the deck and the--tracewalk paths resolve the destination first.alix reset --orphans <folder>now finds orphans when the folder holds exactly one live deck. It had opened only that deck's own progress document, so a leftoverprogress/<id>.jsonstayed invisible: the command reported "No orphaned progress to reset." whilealix doctorkept reporting the orphan. A folder target now scans its store root's whole aggregate, the same documentsdoctorreads; a deck-file target judges only its own document, so it cannot reach a sibling's progress. A folder whose last deck was deleted is a valid target.alix reset --orphansnow aborts when a deck-like file in the target cannot be parsed, instead of judging that deck's live progress orphaned and deleting it. Decks still awaiting anid:line count as live.alix reset --orphansnames a target that is neither a deck file nor a folder instead of reporting it as unlistable.- The picker no longer reopens an inactive workspace's progress from disk once that workspace has been studied this run: the progress owner retains a projection of every document it has owned, so a progress file briefly parked or replaced by an editor or sync tool cannot resurrect a mid-study deck as "new" in the listing.
- A rejected augment open (for example over a duplicate card token) no longer swaps in the progress store it validated against: like exam start, select, and browse before it, validation runs on a candidate and the store installs only when the augment session actually opens, so subsequent grades keep saving through the still-active document.
- Cancelling an AI call now kills the backend's whole process tree, not just the CLI itself: the child starts as its own process-group leader and cancel signals the group, so helpers the backend spawns (node, browsers, git) die with it instead of surviving with API quota and source access. On Windows only the direct child is killed, as before.
- Shutting down while a paired client's tutor request is in flight now cancels that subprocess too: the remote ask path kept no cancellation handle, so the server could report shutdown complete while the AI process kept running.
- A rejected exam start, deck selection, or browse no longer swaps in the progress store it validated against: validation runs on a candidate and the store is installed only when the transition actually happens, so an accepted grade after a refusal keeps saving through the still-active document instead of silently writing progress into the wrong one.
- The picker no longer reopens progress from disk for the workspace that is actively being studied: the listing reads the study owner's own view, so a deck mid-review cannot resurface as "new" while an editor or sync tool briefly parks or replaces its progress file.
- Shutting the server down now cancels an in-flight tutor call and reaps its subprocess (advancing past the card or replacing the question does the same), so a Ctrl-C while the AI is thinking no longer leaves an orphaned process burning quota with source access.
- A catalog listing can no longer be served from a build whose inputs went stale while it ran: a refresh carrying newer progress leads its own build instead of joining the in-flight one, and changing the decks folder mid-build discards that build instead of publishing a listing of the old root.
- A tutor exchange that finishes after the card has already advanced is now discarded: the late answer no longer appears under the next card's transcript, and a late note or draft is no longer applied (previously the note was written to the earlier card's file even though the screen had moved on).
- Two imports of the same deck name no longer race each other (or a concurrent receive or generate landing) for the destination: every destination write now runs on one owner, so exactly one same-name import lands and the landed file is intact.
- A passed or failed exam's progress write no longer saves silently outside the save-error accounting: a transient failure now shows the "progress isn't saving" state and the result is retried by the next flush instead of being lost when the session closes.
- A progress save that keeps failing no longer lets a deck switch silently discard the unsaved session: select, browse, deselect, reset, exam start and close, walk leave, and augment open and close now answer 500 and keep the current session active while the store cannot be flushed. Repairing the disk and repeating the same request retries the flush; there is no force-discard path.
GET /api/doctorno longer holds the application state lock while it probes the backend and wormhole binaries for their versions: the lock is held only to snapshot the store path and decks root, so a slow or hung version probe cannot freeze every other request (grades, pickers, state polls) for its duration.- An unreadable decks folder (deleted, renamed, or pointing at a plain file) no longer masquerades as an empty catalog:
GET /api/decksnow answers 500 and logs the cause instead of returning "no decks". The adult picker shows a calm loading line while fetching and, on failure, a quiet "Couldn't read the decks folder." notice with a Retry button; the kids client's existing "couldn't find your boxes" notice now actually appears in this case (the empty-success response used to show "No boxes yet" instead). Selecting by name is unaffected: an unknown name still answers 400. - The server no longer dies when the consumer of its stdout goes away (for example
alix ~/decks | head, or a supervisor closing the pipe after the URL line): the startup announcement lines now tolerate a closed stream instead of panicking mid-serve. - Picker rows (decks and workspaces alike) no longer light their frame on mouse hover; keyboard selection is the single frame highlight.
- A multiple-choice card starts with its first option focused, so Enter or the nav keys act immediately instead of needing a first keypress to focus.
- A Recognize session now paces first contact like every other depth: at most
max_newnever-met cards enter per session, and an explicit--newis honored there (it was silently ignored). Already-met cards still awaiting recognition all enter, uncapped, so the recognition sweep keeps its contract. Previously a fresh authored-choice deck, which defaults to Recognize, opened its whole deck in one session. - A cached review order (
alix deck augment --target order) now applies at Recognize; the walk sorts the session before anylimittruncation, so a limit keeps the topologically first cards. - The multiple-choice quiz no longer highlights a hovered option like the keyboard-focused one; keyboard focus is the single highlight and the mouse gives no visual state.
- The picker's focus-drawer heatmap no longer renders a card met in an acquire pass (seen, not yet graded) the same as a card never touched. Such cards now show a dim "seen" cell instead of the neutral no-data cell, so first-pass work is visible. The
DeckDrawerDtoheatmap gains a-2value for it (the mobile crumb heatmap matches). - A malformed virtual card in a progress document no longer vanishes silently (it was decoded best-effort and dropped on failure); the whole document now fails to load loudly instead, so a corrupt or out-of-shape card is surfaced rather than quietly discarding that card and its progress.
- Saving a document that first has to create its
progress/oraugment/directory now flushes the new directory entry to disk, closing a window where a power loss right after the save could drop the freshly created directory and the document inside it even though the save reported success. alix doctorno longer tells you to "restore from a backup" that Alix does not make; it now points at moving the file aside or restoring the folder from your own backup (see the manual's Backing up section).- Hard-wrapped Markdown answer prose no longer renders each source line as a separate, widely spaced answer line. Ordinary flip and acquire views join soft wraps; line reveal, typing, fenced code, and generated lists retain their line structure.
- State, deck, and manifest writes now sync the file's data before the atomic rename and the directory entry after it, so a power loss right after a save can no longer leave the only copy of a document empty; previously the bytes could still sit unflushed in the OS cache when the rename was already durable.
- A review session whose progress document was replaced by another writer (for example a synced device) now reports it: the review state carries a
save_errorand the adult web client shows a persistent banner advising to reopen the deck, instead of failing every save silently into the server log. - Adult review notes now use the same content width and text size as the answer or choice column instead of shrinking into a narrower, smaller box.
- The adult tutor's unsaved-conversation prompt no longer binds <kbd>Enter</kbd>, which remains available for composing a newline; <kbd>Escape</kbd> stays in the tutor, and leaving returns to the card that opened it without an unnecessary session-state refresh.
- Visible adult-client scrollbars now use slim, theme-aware tracks and thumbs instead of the browser's bright native chrome.
- Short fact-card source excerpts now keep the answer region's centered vertical alignment; excerpts still top-align when they overflow.
- Fact-card citations and trace walks now share the same editor-style, path-labelled source excerpt instead of using two visually inconsistent renderers.
- Trace walks now render authored inline Markdown in their description, checkpoint prompt, givens, key points, and note on adult web and mobile instead of showing raw markers such as backticks.
- Multiple-choice options now receive a fresh shuffle seed for each study session, so a card's correct answer does not return to the same memorized position every time the app is reopened; repeated state polls still keep the current question stable.
alix doctornow reports malformed recognized LaTeX with its deck, card line, source snippet, and renderer error. CLI, desktop-server, and paired-mobile generation reject malformed math before placement without damaging an existing deck; generated text that does not parse as a deck keeps the previous lenient saved-draft behavior.- Authored checkbox answers and distractors now retain their inline formatting source for display while duplicate detection and typed grading continue to use delimiter-free content.
- A formula that is the only inline run on its logical line now renders larger across adult web, kids web, and mobile, while math embedded in prose keeps its previous text-sized scale.
- Editing a card's content now invalidates its cached AI augmentations (distractors, note, questions, key points, and the reshaped answer). Previously a cached output generated from the old content was served until you cleared or regenerated it.
v0.6.0
Fixed
- promoting a remediation card to a real one no longer risks a duplicate: the promotion wrote the card into the deck file but the matching removal from the in-session store was not persisted, so the card could reappear as both a real and a virtual card; the store change is now saved with the rest of the session
- the web listing no longer re-parses unchanged decks on every request: the server keeps a per-file cache keyed on (mtime, size) and re-reads only files that actually changed, so a warm
/api/decksover a large collection stops re-reading every deck - starting a review or walk no longer re-parses the whole collection to resolve the deck name: name resolution (
/api/select,/api/browse,/api/generate, share/receive, exam start, augment open, and more) now reuses the same warm deck cache the listing does, instead of rebuilding the catalog from scratch on every call - the picker no longer shows an empty page on the first (cold) load, needing a reload to appear: static assets (the page shell, fonts, css, js, and the key endpoints) are now served without waiting for the shared server state lock, so a slow cold deck listing can no longer stall the fonts the picker text is drawn with. The web server also handles connections with a worker pool now, so no single slow request blocks the accept loop
- the deck listing was quadratic in collection size (each loose deck probed its parent folder as a workspace, and that probe read every sibling deck); a 325-deck folder took seconds per listing, now milliseconds
- The session summary no longer reads all zeros after a first pass. A fresh deck's first sitting is acquire-only (attempt-first exposure, no grades), but the summary said "Nothing due." with 0 reviews right after every card was introduced. The wire state now carries
acquired(StateDto), and the summary leads with "New cards planted." and an "introduced" count, hiding the grade rows when nothing was graded.
Added
- A cloze card's blanks now keep their progress when you edit its gaps. Inserting, deleting, or reordering
\blank{…}gaps, or rewording the text around one, no longer shuffles or resets the review schedules across a card's blanks: each schedule follows its hidden word, matching first by word and surrounding context, then by word alone. A gap whose word *and* context both change starts fresh (it can't be told from a new gap), and a deleted gap's progress is discarded rather than inherited by a different word. Any cached choice-mode distractors and notes move with their gap. alix doctornow lints a decks folder for identity problems, andalix reset --orphansclears the leftovers it finds. Over a folder, doctor reports duplicate deck and card tokens (naming which copy keeps the earned progress), store keys matching no live card or deck (orphans, including pre-1.0 numeric ids), a non-canonical token, an unspliceable frontmatter that can't be stamped, the entries that are card content still without an id, and any stray.txt-era file that no longer parses. Orphans are never auto-pruned (they are evidence);alix reset --orphansis the explicit opt-in that clears them, scoped to a folder/workspace or the decks root.- A quiet Support line in the About dialog, on both the web and mobile clients. Leads with the free alternative (telling someone who studies), a sponsors link second; About only, never on a study surface.
- A paired phone can borrow the desktop's AI backend for the tutor and the exam, over
/api/remote/*, including a trace deck's compression exam. The client re-sends its own card, transcript, and answers with every call and keeps its own progress; the server only computes replies, it never writes its own store, session, decks, or recent list. A remote trace sitting checks no re-sit cooldown either, since that state is the browser's own store, not the phone's (RemoteExamDto.is_tracetells a trace sitting apart from a fact deck's). This is the server side only: the phone app's own pairing screen ships in a later mobile release. - A paired phone can also generate a deck from a URL through the desktop's AI backend, over
POST /api/remote/generate. The server returns the full deck text and a suggested file name; placing the file, and any collision handling, is the client's job, same iron rule as the tutor and exam. Server half only. - A paired phone can also condense its tutor conversation into note lines, over
POST /api/remote/ask/note. The server condenses up to three lines the same way the web's own note-save does; appending them to the deck is the client's job, same iron rule as the rest of the remote surface. Server half only. - A tutorial deck on first run. A brand-new decks directory is seeded with "The alix tutorial": ten cards that teach alix by being reviewed — honest grading, spacing, depths, where decks come from, the AI features and what they send, and that your decks are plain files you own. Its last card says to delete it, and a deleted tutorial never comes back (seeding happens only when the decks directory itself is first created). The mobile app seeds the same deck into a fresh app-private folder.
Changed
- Breaking: media embedding is now standard Markdown
; the\image/\audio/\videomarkers and the<!-- img: -->/<!-- img-back: -->directives are removed. Write a plain Markdown image where you want one to appear: position decides the side (the question region for a front image, the answer region for a back image), a card can carry more than one per side, and thesrcis a standard Markdown path resolved relative to the deck file (theimage-dir:/img-dir:frontmatter key is removed). The payoff over the old markers: a deck's images now render in any Markdown viewer that opens the file directly (GitHub, Obsidian, a plain preview pane), not just alix's own web app. The retiredmath:directive is also removed (it never had any effect).\blank{…}text occlusion is unchanged. - Breaking (web API): a card's images are now lists, not single fields. The
CardDtowire shape drops the scalarimg/img_backstrings and replaces them withimages/images_back, each an ordered list of{ src, alt }(srcthe same/img/<key>URL as before,alttheembed's alt text or null). This lets a card carry several images per side, in source order, with alt text. Empty list when a side has no image. Both web clients render every image in the list. - Grading no longer rewrites the progress store on every answer: in-session progress stays in memory and is written once, when the session ends (leaving the deck, switching decks, or opening an exam or the augmenter). Administrative actions (reset, deadlines) still write immediately. If the server process dies mid-session, that session's unwritten grades are lost.
- Breaking: deck-level
strictnessremoved: grading strictness is a learner setting (config or workspace defaults); a deck cannot ship grading rigor. Astrictness:key in a deck's frontmatter is now an ordinary unknown-key lint, not a recognized directive; only the global config default and a workspacealix.toml's[defaults]feedexam_strictness. - Breaking: decks are now Markdown files (
.md), and a card's identity is a minted token, not a content hash. A card front is##, its answer lines follow plainly, a note is>, deck metadata (source:,requires:,link:,trace:,reveal:,direction:, …) lives in a---YAML frontmatter block, and a cloze gap is\blank{…}. The old.txtformat (#fronts, tab-indented answers,!notes,% key:directives,{{…}}clozes) is removed:.txtfiles no longer enumerate or parse. Every card and deck carries a minted identity token, written into the file the first time it is opened (a per-card<!-- id: … -->, a deckid:in frontmatter); a card's id is that token verbatim (token,token-Nfor cloze hole *N*,token-rfor the reversed half), so editing a card's front, note, or answer no longer changes its id (only re-stamping does). On the wire an id stays a JSON string (it always was); its value is now the token, never a decimal number, so clients must treat it as opaque (docs/API.md). Consequences (pre-1.0, no migration): progress stores reset, since their old content-hash keys are unreachable under the new token ids; anaugment.jsoncache regenerates, because a topology-carrying cache fails the stricter load and is rebuilt while a topology-free one loads with now-unreachable keys (harmless); the internal% requires:rewriter (set_requires) is gone; and a lone whole-answer cloze (\blank{…}with no surrounding text) now parses, where the old format errored. There is no bundled converter: existing decks must be regenerated or hand-converted before opening. When alix writes an id it now goes on its own line at the end of the card's block (after its last answer or note line, before the next card or end of file), and every frontmatter block alix emits carries a blank line before its closing---; the old inline id, below-the-front id, and blank-less closer all stay valid input. - A copied deck no longer silently shares one card's progress across two files. When two decks in a folder claim the same identity token (a copied file, or a card copied with its
<!-- id: … -->comment), the undecorated original keeps the progress and the copy is re-minted the next time it is opened (deck.mdbeatsdeck (1).md/deck copy.md); unrelated same-token files fall back to scan order. File-sync and backup copies (*.sync-conflict-*,* (conflicted copy…),*.bak/*.orig/*~) are excluded from every deck scan, so they never list, stamp, or error. - Breaking: a multiple-choice pick now requires a cached AI augmentation; options are never sampled from other cards. Distractors were previously topped up by sampling the rest of the session's answers, which produced junk options (unrelated ones that gave the answer away, or near-duplicates). A pick now renders only from a deck's cached distractors (
alix deck augment --target choices); without them there is no pick. Run the augment to keep multiple-choice on a deck that relied on sampling. - Breaking: the Recognize depth is now pick-only. A Recognize session schedules only *recognizable* cards (ones a cached pick can be built for); an un-augmented card is no longer served there as a plain flip, which had blurred Recognize into Recall. A deck with no recognizable card can't be drilled at Recognize at all, so the picker greys the depth out (even under cram) — run
alix deck augment --target choicesto enable it. The deck-list API gainscan_recognize(per row; group rows aggregate their members) for the gate. - The web app names your configured AI backend instead of assuming Claude. The tutor header and the "working…" progress lines during augment and the exam now show the
[ask] backendyou actually use, so a Gemini, Codex, or Copilot user no longer reads "Claude is working…" (AskInfoDtogained abackendfield). - The CLI
--helptext is modernized to the Markdown deck wording: a trace stub now declarestrace:in its frontmatter (no% trace:), replacing the stale old-format references. - Breaking: the
alix deck augment --targetvaluetopologyis renamed toorder(matches the web app's "Order" card); pre-1.0, no alias. This is a user-facing rename only: the internal type/field names and the/api/deck- topologyendpoint are unchanged.
v0.5.0
Fixed
- A Recognize card with no buildable multiple-choice question no longer strands the review. A deck too small for distractors (or without cached AI ones) reported the choice mode with no options to show, leaving the card with no way forward; it now falls back to a plain reveal-and-grade, so the reported mode is honest for every client.
alix doctor <dir>now actually runs its workspace lints (the missing-icon warning, the deadline-key check) for a directory target; a routing gap meant they never printed.- The tutor's Save note (and the new Make this a card) now stay disabled until the tutor has actually answered, instead of looking active and silently doing nothing on an empty conversation.
- The formatting augmentation no longer strands already-clean cards as a permanent gap: a card the formatter checks and leaves as-is is now recorded as done, so coverage completes instead of a Generate that appears to do nothing.
- The exam overlay now hides its scrollbar, matching the augment and review surfaces, instead of showing one and reserving a gutter for it.
- Lenient exam grading no longer downgrades an incomplete-but-correct answer to partial: the grading criteria now say outright that covering only some key points still passes when what is said is right, reserving "partial" for an actual error (caught by the grader-calibration suite).
- The trace walk screen now shares the session chrome. It still rendered pre-re-skin chrome: no ☰ menu in the header, and a footer that packed Missed it/Partly/Got it/Ask/Leave into one centered row with a dead
0✓ 0◐ 0✗per-checkpoint counter (that readout was deliberately removed from review's footer, but the walk kept its own copy). The walk now gets the ☰ menu (Ask Tutor only — Remove/Promote don't apply to a checkpoint), a zoned footer (Leave left, grade actions center, Ask tutor right, matching review), and the counter is gone. - A keypoint click on an Explain-mode card that also carries a
% at:citation could silently swap the whole answer region to the cited source excerpt instead of marking the point — the click bubbled into the answer region's own answer/source toggle. The keypoint<li>now stops that propagation. - Opening the web app in the first moments after a server start could paint a blank page (the page booted before the server was ready to answer); the boot now retries briefly instead of giving up on the first failed fetch.
Added
- Multi-device roaming guards in the store. Every save stamps which device wrote
progress.json(the device name is a plaintext file in the data dir, rename it freely), and the library can report a recent *foreign* write plus any Syncthing conflict copies sitting next to a store. The mobile app surfaces both as banners; the web/CLI surfaces are a follow-up. The rule stays one device at a time; these make a slip visible instead of silent. - The library exposes its version as
alix::VERSION(the mobile About screen shows it next to the app's own). - Workspaces can carry a personal deadline. Set
deadline = "YYYY-MM-DD"anddeadline_rampin a workspace'salix.local.toml(CLI:alix workspace deadline <dir> <date>; also the API and the web picker's Ready by… action, keyd), and scheduling bends toward it: intervals cap at the days left, target retention ramps up in the final stretch, and nothing schedules past the date, releasing back to normal once it passes. The picker shows a chip (date, days left, ready percent) on the workspace row and its drawer;alix doctorwarns about a malformed deadline key. - The site gains an Impressum, a privacy note, a contact address and a sponsor link; personal details are injected at deploy time, not stored in the repo.
- A "What's new" page on the site: an interactive timeline of releases and landed changes (dots with popovers, drawn from this changelog and the git history at build time) over the full text record, plus a short teaser on the landing page, so it can't go stale on its own the way a hand-maintained page would.
- The legal notice and privacy pages are now in English (headings kept as "Legal notice (Impressum)" and "Privacy (Datenschutz)" for recognizability).
- The landing page counts visits with GoatCounter, a cookie-less, privacy-friendly analytics service; the privacy page explains what it does and does not collect.
- The review header shows a dim "N left" count: how many cards the session still holds, updated after every card. It can honestly tick up when a card you missed cools back in for its retry. (The card pile already hinted at this but flattens at 3, so a long backlog and a nearly-done session looked the same.)
- The adult theme gallery's Kids group now offers Sunrise, Ocean, and Berry — the same three themes the kids app ships, re-derived as full adult palettes (every token the picker needs, contrast-checked for the adult UI's denser text), so a kid transitioning to the grown-up app can keep the look they learned to love.
- Tutor: make this a card. In a review exchange, "Make this a card" asks the tutor to distill the conversation into a draft front/back; you edit it, then Add lands it as a free-standing card on the current deck, drilled like any other and promotable to the deck file. Adult review only; a non-parseable draft errors rather than inventing a card.
- Your decks folder is self-contained: drop it in a cloud drive (Dropbox, iCloud, Syncthing) for roaming multi-device (one device at a time), no accounts.
- The Augment screen redesign: one card per target, not a row. Each of the six targets (choices, notes, questions, key points, format, topology) shows a plain description of what it does and a small neutral before/after preview next to its coverage count and action. You can also tick several targets and press "Generate selected" to run them as one batch: a rough estimate of how many generations that will take shows up front, then each ticked card tracks its own status, queued, generating, done, or failed, as the batch runs, and one target failing doesn't stop the rest.
- The Augment screen now opens on a workspace (or folder). The same six target cards run across all member decks at once: Generate fills a target's gaps in every member, Remove clears it everywhere, and an Order generated here is one workspace-wide pedagogical path that a workspace review session picks up. A workspace also gets an Icon card that draws (or redraws) the emblem shown on its picker row, steered by the card's guidance input.
- A Select all button on the Augment screen ticks every target that can run, so a full batch is two clicks.
- Every augment card carries its own guidance input. Instead of one shared guidance box in the footer, each target card has a compact steer field with a kind-specific example as its placeholder (choices: "use common misconceptions", notes: "add a mnemonic", ...), so you can see per target what a steer is good for, and a batch sends each ticked card's own guidance.
alix doctor --grading: is your model good enough to grade exams? An opt-in spot-check (three real, costed calls) that runs six hand-labeled grading probes against the configured backend: wrong, empty, off-topic, and incomplete answers that must not pass, and correct answers that should. It reports the two directions with different weight, since a model that passes a wrong answer makes "mastered" overstate understanding, while one that misses a correct answer is only harsher than intended.- The review screen's up/down navigation is now rebindable. The multiple-choice and key-point lists move with
k/jby default (the arrow keys always work too); rebind them under[keys.review]asup/down, like any other review action. - An experimental native app now lives in
apps/mobile: a Flutter shell embedding the lean Rust core to review decks offline on Android (and as a Linux desktop window). It has its own release track (mobile-vX.Y.Ztags, a signed APK on GitHub Releases) and its own changelog (apps/mobile/CHANGELOG.md); it is not part of the crate's released binaries. - For library consumers: a
fullcargo feature (on by default) now gates the AI backends and the web server. Depending onalixwithdefault-features = falsecompiles just the lean core (decks, scheduling, sessions, store) with no behavior change for anyone using the defaults; this is the half the mobile app embeds.
Removed
- The placeholder "Fun" kids theme, superseded by the three real kids themes above.
Changed
- A never-drilled deck now defaults to Recognize when it already has AI distractors. Plain Learn used to always start a fresh deck at Recall; now it starts at Recognize if the deck's augment cache has cached choices for at least one card (a genuine multiple-choice pick is ready), else Recall as before. The stricter acquire-time bar already protected an unaugmented card from ever seeing a junk multiple-choice, so this only changes which depth a well-augmented deck opens at, never what it's allowed to ask.
- Picker UX pass: quieter refresh, a footer Back chip, a clearer depth button. The window-focus re-scan now repaints only when the catalog actually changed, so alt-tabbing back no longer visibly flickers; the header back arrow is replaced by a footer Back chip (
esc); the depth split button now reads Depth… instead of a bare triangle; and refreshing (r, or the header button) also re-fetches workspace icon images, so a regenerated emblem shows without a reload. - Augment batches on the Claude backend now share one conversation: the first target sends the cards once, each later target runs as a short follow-up that references them by index, cutting prompt cost and latency on multi-target (and workspace-wide) batches. Other backends and single-target runs keep their self-contained one-shot per call; a failed target starts a fresh conversation for the rest of the batch.
- The acquire cooldown is configurable and defaults to 5 minutes (was a fixed 1 minute):
[review] acquire_cooldown("90s","10m","1h"; a bare number is minutes,"0"disables it), also overridable per workspace inalix.local.toml. One knob paces both gaps it always governed: the settle before a new card's first graded quiz, and the floor before any just-seen card (a miss, a wrong pick) may return. With the longer default a short session can now end while a missed card is still cooling; it slots back in on its own (the summary keeps polling), or next session. - Breaking:
POST /api/checkno longer reads the client-sentorderedflag; whether typed lines pair by position (typeline) or match in any order is derived server-side from the card's mode. Send{lines}only; anorderedfield in the body is ignored. - The tutor's "Save note" is now "Make this a note", matching "Make this a card", and both distill actions are rebindable: Breaking: the
[keys.review]keysave_noteis renamedmake_note(stillctrl-n), and the newmake_card(defaultctrl-d) triggers "Make this a card" from the keyboard. - Leaving the tutor now asks first when the conversation is unsaved, the same pause as leaving a session: the transcript survives on the current card, but moving on to the next one would drop it before it became a note or a card. Enter leaves, Esc stays.
- While the tutor is thinking, the panel shows the looping alix logo next to "Thinking…" (the header logo already looped; this one sits where you're looking), and the transcript no longer rebuilds on every poll tick.
- The review tutor is now offered during a card's first encounter (acquire), once you reveal the answer. It stays hidden during the blind attempt, matching the after-reveal rule the rest of review follows, so you can ask about a brand-new card (and make a card from it) without waiting for its first graded review.
- Breaking: the progress store now lives with your decks (
<decks_dir>/progress.json) instead of the platform data dir (~/.local/share/alix/progress.json); barealixandalix <decks_dir>share one store. Move an existing store once:mv ~/.local/share/alix/progress.json ~/decks/progress.json. - Breaking: the
/api/augment/generaterequest body now takes atargetslist of{target, with?}entries (each with its own optional guidance) instead of a singletarget, and the augment poll response (AugmentDto) also reportsqueued,done, andfailedtargets for batch progress. - The topology augmentation now defaults to a pedagogical (foundations-first) order when you give no guidance, named
pedagogical orderrather thanauto; a guidance steer still overrides it. alix generateand its review pass now keep each card's answer to exactly what its front asks, moving extra context into the note instead of over-answering the question.alix generateand its review pass now turn a mapping of pairs into one cloze card (one line per pair, the recalled half blanked) instead of a "match each X to its Y" card that asks to recall the whole table at once.
v0.4.0
Added
- A kids web client (touch-first, aimed at roughly age 10): a calm, consumption-focused frontend over the same engine, served at
/when[serve] audience = "kids". An adult builds and augments a box (workspace) of decks in the regular web app, then a kid opens it here: pick a box, pick a deck, pick a depth — Tap the answer (Recognize) or Say it yourself (Recall) — and review, with a mascot's short "why" on reveal and a kid-safe Ask Alix tutor. v1 is consumption only: augmenting a deck, the AI exam, and traces stay adult-only for now. Self-hosts the Baloo 2 font (SIL OFL, seeNOTICE). No API or contract change — it's a second frontend over the same/api/*endpoints documented indocs/API.md. [serve] audienceconfig key ("adult"default, or"kids") — which frontend/serves, and which voice the tutor uses.- Ask tutor on Recognize. The tutor button now appears on a Recognize (multiple-choice) card's feedback, the same as Recall and Reconstruct. It's most useful after a wrong pick ("why is the highlighted option right, not the one I picked?"). The key already worked there; this makes it visible and tappable.
/api/decksrows now carryselectable— whether the row'snamecan be sent to/api/select(decks: yes; workspace/folder rows: no). Clients no longer have to infer it fromis_workspace.- On a first-seen (acquire) card,
h(or a tap on the answer) hides / un-hides the revealed answer in place, so you can self-test the fresh encoding (conceal it, try to recall, show it to check) before "Seen" moves on. It only flips the answer's visibility: the note, the footer, and the layout stay put, nothing reflows. Shown as a small corner cue like the source⟷answer swap on a cited card, not a separate button. A first-encounter aid only: an ordinary review drills a card by failing it, which brings it back spaced. - A multi-line front now renders as centred lines instead of one run-on line, so a dual-direction card's reverse side (its several alternatives, shown on the question side) reads clearly.
- An end-to-end smoke suite for the alix web clients — both adult and kids (
make e2e, Playwright): a click must produce the expected request, response, and screen, with no uncaught page errors, covering session select/grade, the picker, and a multi-line answer rendering as separate lines rather than one joined string. - A live Codecov badge on the README, backed by a real-server HTTP round-trip test suite (
tests/api.rs) that drives/api/*over the wire rather than calling handlers in-process — the deterministic half of contract hardening. Line coverage crossed 90%; a handful of functions a deterministic test can't meaningfully drive (a live OS route lookup, print-only QR output, a two-call AI workspace build) are marked#[cfg_attr(coverage_nightly, coverage(off))], each excluded one function at a time with a stated reason, so the number stays honest. - Web picker self-sufficiency: the ☰ menu gains Add deck… (generate from a URL, import .tsv/.txt, receive a wormhole code or .zip), Share… (wormhole code or .zip download), Reset… (typed-name confirm), Doctor, and Pair a device (QR) — all additive
/api/*endpoints, pinned in the contract suite and documented indocs/API.md. docs/API.md— the web JSON API is now a written, tested contract. Endpoints, DTO field tables with nullability, the flows (select→state→grade, walk, exam, augment, ask), auth, and the stability rules clients may rely on (unknown fields must be ignored; enum vocabularies are open sets unless marked closed). Every response shape is pinned by full-object snapshot tests (mod contract), which also emittests/contracts/*.json— canonical examples and a codegen corpus for client models.- Cram is back — as a tick-box in the picker's Learn ▾ menu (key
c, rebindable as[keys.picker] cram), combining with any depth; plain Learn never crams. Its semantics got honest and due-aware: cram only changes which cards are queued — a card that was genuinely due grades exactly like a normal review (full credit, recorded, Reconstruct→Recall propagation included), while a pass on a not-yet-due card only re-anchors its due date, so grinding can't inflate intervals; misses always count. At Recognize, cram serves every card — the repeatable quiz a badged deck otherwise wouldn't offer./api/selectaccordingly takescramplus optionalmax_new/limitoverrides, closing the thin-client pacing gap. A failed session start now also shows a brief notice instead of failing silently. - Scoped roots:
alix <dir>serves that folder as a self-contained instance — its own catalog plus its ownprogress.jsonandrecent.jsonkept inside the folder, so several instances run side by side without sharing state (one per family member:alix ~/decks-maria --lan --port 7781). A workspace folder opens the picker drilled into it, over its own store. alix doctor— one health command. Checks the config parses, the progress store is readable, the decks folder scans (broken decks point atalix deck check), and the backend CLI is installed — each problem with a one-line fix.--backendsadds a real end-to-end probe of the configured AI backend (--all-backends: all four).- A scannable pairing QR in the
--lanstartup output, alongside the pairing URL — which now shows the machine's actual IP instead of a placeholder. A phone or tablet pairs by pointing its camera at the terminal. [review] max_newandlimitconfig keys for session pacing, with per-instance--new/--limitoverrides on barealix(precedence: flag > config > built-in 10 / no cap).alix share <path>andalix receive <code>— send decks to someone over magic-wormhole (shells out to thewormholebinary; install it separately). Share takes a deck, folder, or workspace and stages a copy with the personal state left home (progress, recent list, local pacing); receive lands a deck in the decks dir (or--workspace <dir>) and a folder under its own name, stripping any leaked personal files. The code mnemonic and transfer progress come straight from wormhole. No wormhole installed?alix share <path> --zip [--output <path>]writes the same staged copy as a.zipinstead, andalix receive <file.zip>integrates one. The open picker re-scans its catalog when the browser tab regains focus, so a deck received (or generated) from the terminal shows up when you switch back — no manual refresh.alix workspace init <dir>scaffolds an empty workspace — analix.toml, a personalalix.local.toml, and anassets/folder, no decks. Both TOML files are written fully commented, every key explained inline, so they document themselves. Grow the workspace withalix generate … --workspace <dir>oralix deck import … --workspace <dir>, which write their deck into the workspace.stats,list, andresettake a deck, a folder, or a workspace. A folder or workspace expands to its member decks against the store that serving uses;reseton a workspace clears card progress, virtual cards, and mastered flags together, under one blast-radius confirmation.- The web exam launch pre-flights the backend's ability to reach the deck's source, failing at start instead of mid-exam.
Fixed
- Group rows in
/api/decksno longer reportreviewableunconditionally: a workspace/folder row now aggregates its members, and a deck that fails to parse reports nothing reviewable. (The kids app's box line and the adult picker's dim states are honest now.) docs/API.mddescribedDeckItemDto.nameas a key you can always send to/api/select. Only deck rows are selectable: a workspace or folder row is a container and/api/selectrejects it (400) — drill into itsmembers. A group row'sreviewable_*flags aggregate its members rather than inviting a select. (Found when the kids client believed the doc and shipped a button that did nothing.)docs/API.mddocumented/api/walk/grade'sdeltakeys as"g"|"p"|"m"; the server and web client have always used"n"|"p"|"f". The doc now matches the wire (caught by the new HTTP round-trip suite).- A wrong Recognize pick now shows which option was right before moving on (Continue grades it failed) — the silent instant-demote skipped the corrective moment.
- A just-finished card can no longer come straight back: its re-serve clock now floors at the card transition, so time spent on the feedback screen or the next card never eats the gap.
- The same-card floor now covers Recognize too: a failed pick used to resurface instantly (a deliberate exclusion at the time); with one card left, that meant an instant boomerang. It now re-queues but stays floored like every other depth.
- Multiple-choice options now reshuffle on each appearance of a card, instead of sitting in the same positions every time — a retry could otherwise be solved by position memory rather than actually recalling the answer.
- The picker's ⟳ now re-reads the config, so a changed
decks_dirtakes effect without a restart (scopedalix <dir>instances stay pinned to their folder). - A sequence card (
% reveal: line) at Recognize is now quizzed as one whole answer among the cached distractors, instead of a meaningless pick-one-step choice built from the card's own lines (falls back to the self-report chips when no distractors are cached). - The acquire view's badge no longer names a check — a brand-new card shows just
NEW(the attempt-first reveal is ungraded). - The tutor's "couldn't find the source" reply, for a frozen card whose live source root is gone, now comes back immediately instead of round-tripping through the model to have it echo the same fixed sentence.
alix generate <dir>(workspace build) no longer blocks on, or touches, a populated destination. The build always stages into a scratch dir first, then merges the new files in one by one: a name already present in the destination keeps your original untouched and reports the new version's location (in the kept-around staging dir) instead of failing the whole run or overwriting anything.--forceoverwrites collisions. A leftover staging dir from a previous conflicted build now asks for confirmation before it's wiped and rebuilt, and dot-prefixed folders are hidden from the picker's scan so a kept-around staging dir never shows up as a bogus workspace.- A taken port now errors immediately with a
try --porthint — the server binds before printing its URL, so a clash no longer shows a success-looking line first.
Changed
- Breaking: an ambiguous bare deck name is rejected instead of silently resolving. The same file name occurring in two containers now fails with 400 from every name-taking endpoint instead of silently resolving to one of them; use the qualified
<workspace>/<file>name. - Breaking (API): three contract shapes normalized before the freeze.
WalkDto.verdictsendspassed/partly/failedmachine tokens instead of English display labels;POST /api/walk/leavereturns the pickerStateDtolike every other closer (was a bare 204); a trace exam's re-sit cooldown is anExamDtoin a newcooldownphase withcooldown_msset (was an untagged{cooldown_ms}object). - Breaking: one
generateverb for all AI authoring —explore,trace,deck generate, anddeck checkare removed.alix generate <source>routes by the source: a URL/file becomes one deck; a directory is explored first and the plan's size decides (one item → a deck, more → a workspace, shown and confirmed before building;--planpreviews;--deckforces a single deck;--workspace <dir>names the destination);--traceauthors a trace over a source (--trace --plan= the suggestions menu); naming an existing% trace:stub builds its checkpoints in place. The terminal trace walk is gone — traces are walked in the web picker, and the old--gradeflag becomes the[trace] auto_gradeconfig key (opt-in AI grading per hop, now available in the browser walk too).alix doctor <deck>lints a single deck (wasdeck check);importmoves underdeck(alix deck import). - Breaking: the CLI collapses to
alix [dir]plus task subcommands — every review starts from the picker. Removed outright (pre-1.0, no aliases):alix <deck>direct-deck launch, thereviewandworkspacesubcommands (alix <dir>covers workspaces),alix backend check(usealix doctor --backends), the--serveflag (everything is served since the terminal UI was removed), and the per-session flags--cram/--depth/--topology/--region/--order— depth, topology, and region are picked in the web picker; order is the deck's% order:directive. Barealixkeeps only--lan,--port,--token,--config,--new,--limit. - Redesigned the web UI (IBM Plex typography, borderless/hairline dark theme).
v0.3.0
Added
- Session depths: Recognize, Recall, Reconstruct — replacing the retired
% mode:checks. Every review now happens at one of three independent depths, chosen per session (--depth, or the web picker's split Learn button and its ▾ menu — on the keyboard,vopens it and1/2/3pick a depth, rebindable in[keys.picker]) instead of authored per card or set in config; plain Learn reuses the deck's own last-used depth (first use: Recall). Recall is the familiar reveal-and-self-grade flashcard. Reconstruct keeps its own FSRS schedule per card, independent of Recall — no cross-crediting, two separate practices — and has you type a short answer or a cloze gap, type each line in turn (% reveal: line), or explain a longer one. Recognize is unscheduled and boolean (no FSRS state at all): a multiple-choice pick where there's material to build one, the same attempt-then-reveal a first encounter gets otherwise. A card no longer climbs or descends between depths on its own. - A full Reconstruct pass credits a due Recall schedule — downward only, pass-only. Getting a card fully right at Reconstruct (outside cram) now counts for its Recall schedule too: full credit if recall was due at that moment (recorded in the card's history, marked as propagated), only a pushed-out due date if it wasn't (memory untouched, nothing recorded). Partial and failed answers never propagate, and a card drilled only at Reconstruct never gains a recall schedule from this. Alongside it, any full pass at any depth — cram included — marks the card recognized if it wasn't yet.
- Two quiet overrides give the learner the final say. A typed Reconstruct check normalizes both sides (case, whitespace, trailing punctuation) and compares exactly, then shows the diff — but *you* grade it, so a typo you recognize as one can still be Got it (no edit-distance tolerance guessing at "close enough"). A correct Recognize pick can be quietly walked back with an "I guessed" link, which un-marks the card and re-queues it.
- Per-deck badges for Recognize/Recall/Reconstruct, shown in the picker. A deck earns a depth's badge once every card is currently solid at it (recognized; or at/past 21 days of FSRS stability) — solid while it still clears the bar, dotted once a card has lapsed below it (a badge keeps its earn date once won). Only the highest badged depth shows, and a deck that gains cards after being badged gets a small "new" chip. Informational only: badges never gate anything — passing the AI exam is still the only thing that unlocks a dependent deck.
alix list/alix statsreport per depth.listnow shows each card's Recall and Reconstruct schedule state plus a ✓ once it's recognized;statsadds a per-depth due count.% reveal:— the authored presentation axis. How a card is *presented* is now its own directive (deck or card, defaultflip):flip,cloze({{spans}}), orline— while how deeply it's *checked* is the session depth above, never authored into a shared deck. The web review UI shows a small badge naming the check (flip/line/typing/explain) so how you'll interact is clear up front. See the Changed note for the% mode:/#?break.- Remediation cards are now virtual cards in the store. A failed source exam's remediation cards live in alix's store instead of being written into your deck file. They drill like normal cards and count toward a deck's *due* total (not its card count), dedupe on regeneration, archive when their FSRS interval reaches
retire_after, and revive if the same gap fails again. See the Changed note below for the behavior break. - Promote a virtual card into its deck. A review-time action appends a remediation card to the deck file and drops the virtual copy — "Promote to deck" in the web review menu (rebindable
[keys.review]promote, defaultctrl-p). Offered only while reviewing a virtual card. The promoted card keeps its review schedule; it doesn't restart. - Exam-fail remediation count, and a "remediation card" review label. The post-remediation exam screen now reports how many remediation cards the failure created or revived. While drilling a still-virtual card, the review screen's existing mode badge reads "remediation card" in place of "new card" — it reverts once the card is promoted.
[review]config section — FSRS pacing.retention(target recall probability, 0.70–0.99, default 0.9; higher = shorter intervals) andretire_after(a duration"1y"/"6m"/"2w"/"30d", or"never"to disable retirement; default"1y").- Per-workspace pacing via
alix.local.toml. A workspace can override the global[review]retention / retire_after in a personalalix.local.tomlbeside itsalix.toml— kept separate from the shared manifest, so it never travels when you share the workspace. alix deck checkwarns on a non-gating prerequisite — when a sourced deck% requires:a source-less deck, that edge can't gate its exam; the lint names it and suggests adding a% source:.- Pairing token for
alix serve --lan. Serving to the network now auto-generates a token (printed at startup) and requires it on/api/*, so the LAN endpoint is no longer wide open. Pin your own with--tokenor[serve] token; the browser picks it up from the printed…/?token=…URL, and native clients send it as a bearer token. Opening the web UI without a valid token shows a prompt to paste it (then reloads) instead of a blank page. - An
examples/gh-review-prep.rsshowing how to compose the library into an ephemeral, goal-scoped workspace for understanding a change you must read closely (a GitHub PR or issue) before acting on it. Read-only; a demonstration of composability, not a GitHub feature. [ask] backendselector. All AI calls now route through a pluggable backend. Setbackendin[ask]to choose amongclaude(default),gemini,codex, orcopilot. Auth is each CLI's own login — alix stores no API keys.alix backend check [--all]health probe. Sends a trivial tool-free request to the configured backend (or all four with--all) and reports whether each is installed, signed in, and responding. The only reliable way to confirm the whole path works end-to-end. Errors are the same actionable messages the rest of alix shows (rate limit, not signed in, not installed).- Gemini backend (
[ask] backend = "gemini"). alix's AI calls can now run through the Google Gemini CLI (gemini -p, headless). Tool access maps to Gemini's read-only tools via an--allowed-toolsallowlist (the standard read-only file tools plus web fetch and search) — no write or shell tool is granted, and none is auto-approved. Trace building picks each backend's own strong model (Claude still defaults toopus; other backends inherit the CLI's default), so leaving[trace] modelunset does the right thing per backend. - Codex backend (
[ask] backend = "codex"). alix's AI calls can now run through the OpenAI Codex CLI (codex exec, headless). Codex takes the prompt as a command-line argument rather than on stdin, and its access is governed by a sandbox rather than a tool allowlist: it runs--sandbox read-onlywith--ask-for-approval never, which permits reading local source but blocks writes, shell escalation, and the network — so a fetch/search grant can't be honoured under this backend (source reading still works). - Copilot backend (
[ask] backend = "copilot"). alix's AI calls can now run through the GitHub Copilot CLI, authenticated viagh auth login. - Backends degrade gracefully. An AI feature now checks the selected backend's capabilities *before* doing any work and refuses cleanly when they don't match — e.g. an exam or
deck generateover a URL% source:under a backend that can't fetch the web says so and names the fix (point the source at a local file, or switch[ask] backend), instead of crashing or fabricating a result. Tracebuild/suggestandexploregate the same way. A failed AI CLI now also leads with actionable guidance: a rate-limit or quota error suggests waiting or switching backend; an unauthenticated error suggests running the CLI's login once (the raw detail is still shown). - Pre-flight source-size guard. Before
deck generate,trace --build,trace --suggest, andexploreread a large source, alix measures the estimated size and asks for confirmation. Pass--yesto skip the prompt in non-interactive scripts.examinstead truncates the source to 100 KB and prints a notice so the exam can still run unattended. - Web picker: a workspace's goal shows in its drill-in. Opening a workspace now shows its goal (the one-line description) under the title eyebrow, the same goal the top-level list shows on the workspace row — so the context stays visible while you pick a member deck.
- Web picker: a drawer indicator. A quiet
▾at the bottom-centre of a picker row marks a deck that has a focus drawer (a cached topology), so you can see at a glance which rows expand on focus instead of discovering it only after selecting. The marker lights to the accent colour on the focused row. - Draw input (web). Answer a
flip/explaincard by drawing or handwriting on a canvas, then self-grade — either authored per card/deck with% input: draw(for answers that can't be typed, e.g. diagrams) or via a per-device "Draw answers" toggle in the review menu. Web-only; the drawing is ephemeral (never persisted or sent to the server).
Changed
- Breaking (store): per-depth schedules replace the single
fsrsfield. A card's progress is now stored per depth (recall/reconstruct), plus arecognizedflag, instead of one shared FSRS state. Pre-1.0, no migration: an existing store loads fine, but every card starts with empty schedules at every depth — a one-off rewrite for anyone who wants to seed it otherwise. - Breaking:
--max-typosand thefuzzycheck mode are gone. A typed check now normalizes (case, whitespace, trailing punctuation) and compares exactly, then shows the diff and leaves the pass/fail call to you — no edit-distance tolerance guessing at "close enough" (it used to let "affect" pass for "effect" within tolerance). - Breaking (store): dropped the legacy Leitner
stage/stage_entered_msfields now that FSRS is the sole scheduler;stage_entered_msis renamedacquired_ms. Pre-FSRS cards lose their stage-derived interval carry-over. Pre-1.0 — no migration path.alix statsno longer prints a per-stage histogram andalix listshows the FSRS state instead of a Leitner stage. - Breaking: the terminal frontend is removed — alix is web-first. Bare
alix(oralix <deck>) now opens the local web app and prints its URL, instead of aratatuiTUI;ratatuiandcrosstermare dropped as dependencies (~129 transitive crates gone). The standalonealix examandalix browsecommands are removed — both are reached through the web picker instead (pick anexam duedeck to sit its exam; press "Browse" on a deck to read through it). The% frontend:directive (any/tui/web) is removed — every card just renders in the web app.alix resetand deck dependency editing (the oldalix deps/alix require) are now non-interactive: name a deck, or pass--card <id>/--alltoreset; edit% requires:lines by hand.alix trace's walk is unaffected — it still runs in the terminal (a plain stdin loop, never a TUI). - Breaking:
% mode:, the#?cloze marker, and the--modeflag are removed — replaced by% reveal:and the session depths above. A cloze card is now% reveal: cloze(was#?); a deck's presentation is% reveal: flip|cloze|line(was% mode:); and how deeply you drill is the session depth (--depthor the split Learn button), not a per-card mode or a CLI flag. Cards once authored% mode: typing/explainreview as reveal-and-self-grade in Recall sessions — start a Reconstruct session to get the producing (typing/explain) checks. Card ids are preserved: the retired markers were never part of a card's identity hash, so progress carries over. Upgrade an existing deck with a one-off textual rewrite (#?→ a% reveal: clozeline;% mode:→% reveal:where a reveal-method applies, droppingtyping/fuzzy/choice/explain), or re-generate it — either way ids and history survive. - Breaking: a failed exam no longer appends remediation cards to your deck file. Remediation cards are now created as virtual cards in alix's store, so the deck
.txtstays byte-for-byte unchanged. Drilling, due counts, and the exam re-sit are otherwise the same; use the new promote action to move a remediation card into the deck if you want it there permanently. - A virtual (remediation) card's retirement is now fully derived, matching a deck card: purely its FSRS interval vs.
retire_after, no stored archive flag. Raisingretire_afterlater un-retires a previously archived virtual card whose interval now sits below the new cap, the same as it would for a deck card. - Breaking: a review session scores each card once per appearance. A missed card is no longer re-drilled immediately in the same sitting; it keeps its short spaced step and re-appears once that step has elapsed, interleaved behind other due cards (so every scored review is genuinely time-separated). When nothing is due right now the session ends — a card still cooling is picked up the next session, or re-enters on its own if you keep the window open. Fixes cards graduating too early from same-session re-drills.
- Breaking: a card graduates only after two spaced correct recalls. A single Good after a miss no longer promotes a card to the long-term review phase faster than two clean passes would; two full Goods graduate it, a miss resets that progress, and a *partly* is neutral. (A lapsed card still re-graduates on one Good.)
- A just-seen card starts drilling in the same session. Acquiring a new card (the ungraded "Seen" first exposure) now settles for ~1 minute (was ~5) and the card stays in the sitting, so its first graded quiz comes back interleaved a minute later instead of waiting for a new session.
- Breaking: FSRS is now the only scheduler. alix schedules with FSRS-5 (via the
rs-fsrscrate) for every review; the Leitner and SM-2 schedulers are gone, along with the% scheduler:directive and the--schedulerflag that chose between them. Grades map to FSRS ratings (missed it / partly / got it → Again / Hard / Good) and the next interval comes from FSRS, not a fixed stage ladder. The old per-cardstageis kept only as a seed for a card's first FSRS review, so existing progress isn't reset. No compat shim, pre-1.0. - Breaking:
% unlock-stage:removed (the directive, the--unlock-stageflag, and the[defaults] unlock-stagekey). A deck's exam is now gated on graduation: it turns *exam due* once every card has reached FSRS's review phase (past the initial learning steps), rather than at a configurable stage bar. - Retirement is now interval-based. A card retires (rests until
alix reset) once its FSRS interval reachesretire_after(default 1 year, configurable,neverto disable) — previously it retired at the top Leitner stage. --cramrefreshes without rewarding. A correct answer under--cramnow re-anchors the card's due date by its current interval — no FSRS update, no reward — so cramming keeps cards fresh without inflating their schedule; a cram miss still lapses normally. Previously--cramfully rescheduled every answer.alix serve --lannow requires the pairing token on/api/*(auto-generated unless you set one). The HTML shell, theme assets, and images stay open — only the JSON API is guarded; localhost serving is unchanged (open).- Breaking:
alix trace --serveremoved. Trace walking in the browser now goes throughalix serve's deck picker (pick the trace) — the standalone single-trace web server is gone, so there's now exactly one web server.alix trace <deck>still walks in the terminal. - The tutor is now backend-agnostic ("Ask Tutor" / "Tutor"). The in-session tutor was labelled "Ask Claude" in the UI and docs. It works with every supported backend (Claude, Gemini, Codex, Copilot), so it is now called "Tutor" throughout — in the ☰-menu button, the hint text, the README, and the book. The
[ask]config section name is unchanged (it was already neutral). - Breaking —
alix checkis nowalix deck check. Deck validation moved under thedecknoun-group for consistency withalix deck generate/alix deck augment. The command is identical; only the path changed:alix check <deck>→alix deck check <deck>. No compat shim, pre-1.0. - Multi-turn tutoring works on every backend. Claude keeps a running conversation with its session flags (
--session-id/--resume); other CLIs don't have those, so alix drops them for a backend without a session mechanism. To restore cross-turn memory there, the tutor now re-inlines the accumulated Q&A transcript into each prompt — a follow-up on a non-Claude backend carries the prior questions and answers, so the tutor no longer forgets what you just asked. Claude's efficient--resumepath is unchanged. - Breaking — config keybindings are namespaced under
[keys]. Every key table is now a[keys.*]subtable:[keys]→[keys.review],[picker]→[keys.picker], and[browse]→[keys.browse]. This groups all bindings in one place and disambiguates the sharedremove/quitaction names per surface. Update your~/.config/alix/config.toml— the old top-level[picker]/[browse]and a bare[keys]section now error (no compat shim, pre-1.0).alix config --initwrites the new layout. - Review cards settle a short answer below the midline. The answer region now grows to fill the space between the question and the note and centers its content when it fits — so a short answer sits just below the card's middle instead of clustering under the question, and the lower half no longer reads as empty. Long answers and cited excerpts still top-align and scroll (using the whole card), and the question never shifts when the answer is revealed. Applies in browse too.
- Web picker: cleaner dependency-tree lines. The workspace drill-in's tree connectors (
├─/└─/│) are now drawn as subtle dotted CSS guides in the row border colour — aligned under each parent's label and stopping at each row's border rather than crossing the gaps between rows — instead of single-line box-drawing glyphs that broke into disconnected segments on the tall rows. - Multi-line review answers left-align by default. An answer with more than one line (a list, or several sentences) now renders as a left-aligned block, centered as a whole, instead of each line being independently centered (which read as ragged — especially for lists). Single-line answers stay centered, and reshaped-list bullets are unchanged.
- The web UI header shows an animated
alixwordmark. The lightning-bolt mark in the review/picker and trace-walk headers is now a self-contained<alix-logo>web component — a flat orange "mitosis" wordmark that plays a one-time reveal on load (and on reload /r) and loops as a calm loading indicator while a Claude/server call is in flight. The shared header chrome — the<head>boilerplate and the brand mark — is now single-sourced (_head.html/_brand.html, filled in by the server) so all pages stay consistent. - Trace walk is now an in-page mode of the web review UI. Picking a trace from the deck-selection screen no longer navigates to a separate
/walkpage — the walk runs insidereview.htmlwith no page reload, and trace cards match fact-card sizing and layout.
Fixed
- The web app can no longer be served stale from the browser cache. alix sent no cache headers, so after an upgrade the browser could keep showing the previous version's page on the same address. The app shell and its assets now demand revalidation (
no-cache) and live JSON state is never cached (no-store). - Browse left-aligns multi-line answers like review. The read-only browser decided the left-aligned-block layout from the reshaped-list flag alone, so an unaugmented multi-line answer rendered centered (ragged, each line on its own axis) instead of as a left-aligned block. It now uses the same rule as review — any multi-line answer is a left-aligned block centered as a whole; only reshaped lists get bullets.
- Web: Backspace leaves the augment view. The augment screen accepted only
Escto return to the picker, while browse and the picker also honourBackspace— soBackspacefelt inconsistent across views. It now leaves the augment view too, while the guidance box still edits its own text withBackspace. - Web picker: the header buttons are legible on light themes. The ☰ menu and the ← / ⟳ nav buttons used the muted
--dimcolour, too low-contrast on some light themes (e.g. Solarized Light); they now use the main text colour, so they read on every theme. - Web picker: clicking empty space keeps keyboard focus. A click anywhere in the picker area that isn't a row or control — including the margins around the centered list, not just inside it — no longer drops focus to
<body>(where the row-nav keys go dead); it re-homes to the current (or first) row so arrow-key navigation stays live. alix explore --buildfreezes cited excerpts more reliably. When a generated% at:locator dropped (or added) a leading subdirectory — e.g.chapter.mdwhen the file is atsrc/chapter.md— freezing couldn't find it and skipped it (cited file not found, not frozen), leaving a checkpoint without its source. Resolution now falls back to a basename search under the source root to recover the excerpt, and the fill prompt pins every locator to one consistent root so the mix is less likely to arise.- Workspace icons draw fast, without timing out. The
explore --buildicon prompt now caps the emblem at a few compact primitive shapes instead of letting the model emit long<path>coordinate data — the token-heavy part that made the draw slow enough to time out (could not draw a workspace icon: 'claude' timed out after 120s). The draw also retries once. (Supplying--icon, or dropping a conventionalassets/icon.*, still skips generation entirely.)
v0.2.0
Added
- Web picker: browser-style back + refresh buttons in the header, for people who reach for the mouse/touch over keybindings. The ← button goes back a view (disabled at the top level; the keyboard equivalent is
Esc/Backspace, since the←*key* steps the focus drawer's regions) and⟳re-scans the deck list (also bound to the newrkey). Refresh moved out of the burger menu, and the drill-in's footer "Back" chip is gone — the header ← replaces it. alix deck augment --target format— a non-destructive pass that reshapes a badly-shaped card (e.g. a list crammed into one prose answer) into clean display lines, a tidier front/note, and a suggested answer mode, applied at review without touching the deck file or card identity. Also available from the web Augment screen. The reshaped output drops noisy inline backticks and puts a code snippet in a fenced block, rendered as a monospace code box on the card.- Augment decks from the web picker — no CLI needed. Press
aon a deck (or its new Augment button) to open a screen of what its augmentation cache holds: one row per target (choices, notes, questions, key points) with a coverage bar, plus its topologies. Generate fills only the cards a target is still missing — a costed background call, with a live spinner — Remove clears one target, and the topology row adds or drops named topologies. A shared guidance box feeds the--withsteer. It writes the sameaugment.jsonthe CLI does, so review reads it unchanged. Decks only; workspaces don't show it. (The terminal surface comes later — the library and server logic are shared.) - New cards are introduced as an *attempt*, not a cold quiz (acquire). A never-seen card no longer drops you into a quiz you can't pass — its first encounter is a low-stakes try, then the answer, then one key ("Seen") files it on the ladder at stage 1, *ungraded*, with its first real quiz a later session. By default it's recall (the front shows first — try, then reveal); for a deck augmented with AI distractors (
--target choices), an atomic card instead greets you as a multiple-choice question (pick one, see which was right). A guess never promotes or punishes — stage 1 either way. Start another session to drill what you've met (the per-session--newcap is unchanged — 10 per session). Terminal and web. The acquire step of the acquire → explain → maintain card lifecycle (the explain step shipped below). - Explain-mode key points — a checklist that derives the grade. A new augmentation,
alix deck augment <deck> --target keypoints, has Claude break each card's answer into the few load-bearing claims a reconstruction must hit (cached beside your progress, like distractors/notes). In explain mode the reveal then becomes a checklist: you tick the points you covered and the grade is *derived* — all → passed, some → partly, none → failed — turning the self-grade from a vibe into a per-claim check (TUI and web). An *atomic* answer (a single fact/term/date) is left without key points and keeps its plain reveal, the same way choice mode skips cards with no usable distractor. Tune the maximum with[ai] keypoint_count(default 5). First step toward an acquire → explain → maintain card lifecycle. - Web picker header. The deck filter moved into the header — a compact box centered on the list — and a burger menu (☰) there holds keyboard shortcuts, refresh decks, about (the version, via a new
/api/versionendpoint), and Theme…. The Mastered jump moved to the header too. - Workspace icons in the web picker. A workspace can show a small emblem next to it in the picker for quick recognition. Generated as an abstract SVG by
alix explore --into <dir> --build(grounded in the workspace's topic), or supplied yourself with--icon <file>or anicon = "assets/<file>"key inalix.toml(else a conventionalassets/icon.*). SVGs are tinted to the active theme; rasters show as-is. - Topology-ordered review (experimental).
alix deck augment <deck> --target topologyderives a graph of how a deck's cards relate — labeled edges, a suggested walk, and coarse named regions — cached beside your progress (a deck can hold several, one per--withprinciple, keyed by it).alix review <deck> --topology <name>then serves the due cards in that walk's order instead of at random — SRS still decides *which* cards are due, the topology only reorders them — and review shows a thin region breadcrumb ("where am I", current emphasized) so the sequence reads as a path, not a shuffle. A single cached topology is picked automatically. Terminal and web; the edge labels (which would reveal answers) stay under the hood. The breadcrumb doubles as a strength heatmap — a per-card bar under each region, red (weak) → green (learned) — so a region greens up as you master it.alix review <deck> --region <name>drills one region (SRS still picks what's due within it). In the web picker, selecting a deck that has a topology opens an inline focus drawer (sliding open/closed): pick which topology orders the session and pick a region to scope the launch — by click or with ← / → — with the selection's due/new count shown at the right end, all before the session starts (the in-card breadcrumb stays read-only). - The ask-Claude tutor grounds a frozen card in its live source. For a card in a frozen workspace (
alix explore --into --build), the tutor now reads the original crate for context — explaining how the cited code fits the surrounding source — with the frozen snapshot excerpt as the anchor (what the learner sees stays the ground truth, so the tutor never reasons about a drifted copy). It no longer cites opaque asset names (01.rs). The live source is found via a new% origin:directive (below); if it's gone, the tutor replies *"I couldn't find the source material of this card to provide a grounded answer."* so you can update or drop the card. The trace-walk tutor, which had no grounding at all, gets the same treatment. Gated by the existing[ask] source_accessopt-in. % origin:— the live source root a frozen deck's snapshots came from. Written into a workspace'salix.toml [defaults]at build time and cascading workspace → deck → card like every other directive (a card may override it for a cross-repo source), it lets the tutor and drift detection find the real crate even though% source:points at the opaqueassets/.alix checkflags drifted frozen cards. When a frozen card's snapshot no longer appears in its live source — the lines changed, or the file is gone — it warns (card at line N — frozen excerpt no longer found in the source), so you can refresh or remove that card. A snippet that merely *moved* within the file is not flagged.- Ask Claude during a trace walk. The web walk now has an Ask button on each reveal (and the
?key) — the same tutor a card review offers, scoped to the current checkpoint (its question, key points and the live source excerpt). Send questions, Save note to append a!line to that checkpoint, Esc to close. The ask machinery is now a shared component used by both the review and the walk, so one CLI conversation spans the session. Hosted walks only (the picker → walk flow); the standalonealix trace --serveis unaffected. - A "⌵ N more" marker when a source excerpt overflows the card. A reveal whose excerpt is taller than the card shows a small
⌵ N more linespill at the cut edge (counting the hidden lines), in both the trace walk and a fact card's% at:citation — and it appears immediately on an overflowing excerpt, not only after the first scroll. The subtle edge-fade stays underneath it. - A trace's exam is its compression — AI-graded. A trace's
% trace:is a question ("how X becomes Y"); its exam is to answer it — retrace the whole path in a sentence or two from memory — and Claude grades that *holistically* against the path's checkpoints (no question generation, no source read: the checkpoints already paraphrase the source). Passing masters the trace (unlocking its dependents), exactly like a fact deck. Reached three ways:alix exam <trace>(which no longer refuses a trace), the capstone offered at the end of a walk (Take the exam?), or the picker's "Take exam" button (terminal and web) — and, like a fact deck, you can sit it early to test out, gated only by% requires:. A failed trace exam is re-walked (not remediated into cards — a trace is a path, not a card pile; its weak checkpoints already resurface through SRS), and after a fail it cools down before a re-sit so the graded feedback can't be pasted straight back into the one fixed question —[exam] retry_cooldown_secs(default 3600;0disables it). Built on the existing exam engine (Sitting::start_trace+grade_compression), so the TUIExamAppand the web exam overlay drive it unchanged. - Browse a deck straight from the web picker. A deck row's primary action is now Review (Enter), with a new Browse button (the go-right key,
l/→) that opens a read-only walk through its cards — the review server hosts the browse page at/browse, so you no longer need a separatealix browseserver. A workspace/folder still opens (drills in) onl/→; leaving a browse returns to the picker (and re-opens the launching workspace). Browse-from-the- picker is view-only (card removal stays a feature ofalix browse --serve). - Web UI theme gallery — alix's own themes plus popular editor/slide palettes. The web frontend (
--serve) ships a gallery of colour themes: the alix Dark/Light originals and a playful Kid theme, plus crowd-favourite editor palettes — GitHub, Dracula, Nord, Solarized, Gruvbox, Catppuccin, Tokyo Night, Monokai, One Dark, Ayu, Rosé Pine, Everforest (light + dark where they have both). Pick one from the Theme… popover (the ⋮ menu, or a bar button on the trace walk): a grid grouped Light/Dark that previews on a small sample card as you hover (the app re-themes only when you click one) and remembers your choice per browser. The palette lives in a shared, server-servedtheme.cssso every screen themes together; the default stays the original dark, so nothing changes unless you choose. alix deck augment— deliberate AI deck augmentation. A new command that enriches an existing deck with Claude and caches the result beside your progress (augment.json, keyed by card id); review reads the cache, so study stays instant and fully offline (Claude is never called mid-session). Three targets:--target choiceswrites plausible multiple-choice distractors (used automatically in choice mode, with the offline sampler as fallback — so choice now works even on a deck too thin to sample from);--target noteswrites a short trivia/mnemonic note per card, shown *alongside* the card's own deck note on reveal (the deck file is never modified); and--target questionswrites a pool of reworded phrasings of each question (same answer), a fresh one of which review rotates in each time a card comes up so it can't be passed by recognizing one fixed wording (plain, non-cloze cards).--with "<guidance>"steers how. Tuned under[ai](model,distractor_count,variant_count,timeout_secs).alix checkrejects a cloze whose entire answer is one hole. A#?card whose only hole spans the whole answer (e.g. `{{IdentStr}}, with nothing but formatting around it) is a plain front→back card in disguise — blanking the lone hole leaves no surrounding text to recall it from.checknow flags it (cloze answer is one hole with no surrounding text … use a plain '#' card), the sibling of the existing "cloze with no holes" error. Answers with literal context around the hole, or with two or more holes (each hole's siblings show as[…]`), are unaffected.
Changed
- Web picker: the primary action is Learn, bound to Enter. The focused row's primary action — Learn a deck (review or walk), Open a workspace, or Take exam — is bound to Enter, replacing the old Review/Walk split.
l/→no longer launch a deck (they step the focus drawer's regions and enter a workspace). The intro prose and the "select decks" label are gone, and the list fills the space. - Browse is now an in-page mode of the web app — no separate
/browsepage. Hitting Browse in the web picker (oralix browse <deck> --serve) opens a read-only overlay right in the main app — step through every card with Prev/Next/Leave, seeing the reshaped answers, notes, and images — instead of navigating to a separate page with its own older picker. The standalonebrowse.htmlpage and the/browseroute are gone; terminalalix browseis unchanged. Breaking: the web browse is read-only (card removal stays a terminalalix browsefeature). - Reshaped list answers show as a left-aligned bullet list. When the
formataugment turns a crammed prose answer into a multi-item list, the web review and browse views render each item with a•, left-aligned (the list block is centered as a whole). Single-line tidies and a card's own authored back lines (a poem, typing answers) are left as-is. - Bigger cards in the web review and browse views. The card was capped small (≤820/720px wide), so it sat in a sea of empty space on a normal screen at 100% zoom and long questions/answers wrapped early. It now caps at ~1200px wide (94vw) and ~780px tall, filling far more of the viewport.
- Web picker:
←/→(andh/l) now step the focus drawer's regions, and going back isEsc/Backspaceonly. The drawer needs left/right to move between regions, so those keys no longer double as "back out"; with no drawer open,→still enters a workspace / launches a deck and←is inert. - Browse now shows the same display augmentations as review — the
formatreshape andnotestrivia — so the two views render a card the same way instead of browse falling back to the raw deck. alix deck generatenow shapes cards better: it splits enumerations into one-idea cards (or uses% mode: linefor ordered lists) and structures answers and notes instead of producing prose blobs — the same shaping now applies toalix explore --builddecks.- Breaking: card identity is now whitespace-insensitive — an answer's id no longer depends on line breaks, indentation, or repeated spaces (only its words). Cards whose answers span multiple lines or use irregular spacing get a new id once and reset their review progress.
- Leitner stage 1 now has a ~5-minute relearn/settle cooldown (was 0). A newly acquired or freshly failed card becomes due ~5 minutes out for the *next* session, so starting another session right away no longer re-serves a card you just saw or just missed. In-session drilling is unchanged — a failed card still comes back the same run (the queue is served by position, not by due time).
- Web picker keys. Clicking a deck now selects it (opening its focus drawer when it has a topology) rather than launching outright — Review or Enter launches. Browse moved to
b, freeing ← / →: they step the focus drawer's region selection when one is open, and otherwise enter / leave a workspace. Up / down still move between decks. (The drawer is new this release, so only the Browse-key and click-to-select changes affect existing muscle memory.) alix deck augmentsays what it's doing. It now prints which augmentation it's generating, for which deck, and with which model before the (foreground, possibly slow) Claude call, instead of hanging silently until the result.- Breaking — one deck per session.
alix reviewandalix browsenow take exactly one deck *file*: merging several loose decks into a combined session is gone, and a whole workspace is no longer reviewed at once. Workspaces stay an organizing layer — review their members one at a time (the picker drills in;alix workspace <dir>opens that picker), and a member still inherits the workspace's directives and store.stats/list/resetstill take multiple decks (they're per-deck operations, not a merged session). - Breaking — review grades are now
failed/partly/passed, replacingagain/good/easy(shown in the UI as Missed it / Partly / Got it — an honest self-report of understanding, not a pass/fail verdict; the real pass/fail is the AI exam). Fact-deck review and the trace walk now share one three-outcome grade: failed resets the card to stage 1, partly drops it *one* stage (a soft miss — it returns sooner but you keep most of your progress), and passed advances one stage. The oldeasy(+2 stage jump) is gone, andpartlyis a genuinely new middle — previously the trace walk's "partial" scheduled identically to a miss (full reset); now it is a distinct, gentler outcome on both surfaces. Apartlydoes not advance the streak (it can't retire a card). The[keys]config keys renamed —again/good/easy→failed/partly/passed(defaults1/f,2/p,3/n); an existing config with the old keys is rejected with an error naming the valid keys (alix config --initshows the new template). Pre-1.0, no shim. Progress files are unaffected — grades were never stored by name. - Breaking — the freeze format records provenance on the
% at:line, not a note. Freezing a workspace now writes% origin:(the live crate root) and appends each card's original location to its locator (% at: 29.rs from src/caching.rs:46-66), instead of smuggling it into a hidden! from …note that the display then stripped back out. Notes are the learner's again. Existing frozen workspaces keep working for review and the exam, but the tutor can't ground them until re-frozen (re-runalix explore … --build). Pre-1.0, so no compatibility shim. Card identities are unaffected (% at:/% origin:/notes are not hashed). - The review header no longer shows the stage ladder. The always-on
new|s1|s2|…stage histogram is gone from the review header (TUI and web) — it was noise; the per-stage breakdown stays in the end-of-session summary. - Returning to the picker keeps your place. After a review/browse/walk/exam, the deck picker re-lands the cursor on the deck you just launched (rather than jumping to the top), so you can step straight to the next — often dependent — deck. Both the terminal picker and the browser picker (the top list and a workspace drill-in).
- The Mastered window shows when a deck was mastered and how much is left to drill. A mastered deck's badge now reads e.g.
mastered 🎉 · 3w ago · 8 to drill— the time since it passed (thedeck_masteredtimestamp was already stored) and how many of its cards aren't yet retired (so a deck you *tested out* of without drilling shows the work remaining). Both TUI and web. - Web picker draws the dependency tree like the TUI. A workspace's members now show
├─/└─/│branch lines (muted) instead of plain indentation, and the 🕒 "nothing due" glyph moved from the start of the row to the end (with the status), so the left gutter is just tree + title. (The server already computed the prefix fordepth; it's now sent to the browser.) alix exploregenerates short, title-cased deck/trace titles. The plan prompt asks for a terse title, but the model ignored it and appended the deck's contents after a colon — so the title is now condensed deterministically in code rather than left to the prompt: the enumeration is cut (at the first:/;/dash, or by a word cap when there's no separator), and the result is title-cased with code spans (`grpc,snake_case,CamelCase,ACRONYMs) left intact. Workspace decks read asThe Crate Surface, notthe crate surface: three-part Store/Execute/Inspect model, the three feature flags …`, and stop truncating in the picker. The condensed title also drives the file name, so slugs no longer trail a stray word from the cut enumeration.- Web trace walk: the leave button reads "Leave" and confirms an unfinished walk. The hosted walk's return chip was "Decks"; it's now "Leave" (matching a fact-deck session), and leaving before the last checkpoint shows a "Leave the walk before finishing the path?" prompt (Enter leaves, Esc stays) — the same guard as review and exam. A finished walk still leaves immediately.
- Web exam: leaving mid-answer asks to confirm. Pressing Esc (or Quit) while answering now shows a "Quit the exam? Your answers won't be graded" prompt — Enter abandons it, Esc keeps going — so a stray Esc no longer throws away an in-progress exam, matching the review-session leave guard. (Other phases close immediately; the typed answer is preserved if you keep going.)
- Reviewing a deck no longer pulls in its prerequisites' cards. A review (in the TUI/CLI) now holds exactly the deck(s) you picked —
% requires:decks are not auto-added "foundations-first" — matching what the web already did. Dependencies are about *order and gating* (the picker tree + the exam gate), not what a session contains. (Removed theresolve_deck_order/dep_ranksmachinery; book + README updated.) - Breaking — a trace masters by passing its exam, not by finishing the walk. Walking a trace is now the *drill*: completing the walk no longer masters it (the earlier "mastered once every checkpoint retires" behavior is gone). A fully-walked trace becomes exam due; passing the new trace exam — the compression (see Added) — is what masters it and unlocks its dependents, just like a fact deck. The ungraded walk-end "compress" step is removed (and its
/api/walk/compressendpoint), and the progress store bumps to v2 (an older alix now cleanly refuses a v2 store with an "upgrade alix" message rather than mis-reading the new deck-progress shape). % requires:now gates the exam, not drilling. You can review/drill any deck at any time, in any order — a prerequisite-locked deck is no longer blocked in the picker (it stays bright and startable; the lock is named explicitly when it's focused — the TUI footer says "🔒 Exam locked", the web shows its "Take exam" button disabled with a 🔒 — rather than a per-row lock glyph that read as "the deck is locked"). The dependency order applies to exams: to sit a sourced deck's exam you must have passed each *sourced* prerequisite's exam. A source-less prerequisite has no exam, so it never gates — its edge is informational in the dependency tree, seen *through* to the nearest sourced ancestor. (is_lockedcounts only sourced prereqs; both pickers and the exam-due review shortcut respect the new gate.)alix deckis now a command group:alix deck generate+alix deck augment. Breaking:alix deck <source>is nowalix deck generate <source>.- Choice-mode offline distractors are shape-aware. Number-like answers now only compete with the same shape (a 4-digit year vs other years, not a
1,5ratio or a 2-digit count), so an obviously-wrong option no longer slips in. - Ask-Claude (web): Enter now inserts a newline and Shift+Enter sends. The ask box is a multi-line textarea, so plain Enter composes freely and a deliberate Shift+Enter submits the question (the Send chip and placeholder show the hint). Previously Enter sent and Shift+Enter made the newline.
- Web exam: Shift+Enter advances to the next question (or submits, on the last), matching the ask box — Enter still inserts a newline so multi-line answers compose freely, and the Next/Submit button now shows the binding.
Fixed
- The picker labels a trace by its description, not its filename. A trace row in the picker (web tree and TUI drill-in) showed the raw file stem — a clipped kebab slug like
08-how-a-workout-starts-logs-a— even though the trace already carries a readable name in% trace:. It now labels the row from that description (How a Workout Starts, Logs a Set, and Advances to the Next), condensed to a label-sized head so a long--build/hand-written path-question doesn't overrun the row. Plain decks (a% title:or neither) are unaffected. - A trace
--gradereply that isn't a real verdict now errors instead of being scored as a miss. The per-hop grader expects the model to answerNAILED/PARTLY/FAILED; an unrecognized reply (a weaker model ignoring the instruction) used to silently fall through to a failing grade — fabricating a verdict the model never gave. It now surfaces an error and falls back to self-grading, so a correct prediction is never quietly marked wrong. alix explore --into --buildnow actually freezes itsassets/. The generated% source:paths were silently doubled: when--sourceis a subdirectory (a crate) but the plan writes a scope relative to the project root above it (crates/x/src/lib.rs), the write-time join produced…/crates/x/crates/x/src/lib.rs— a path that doesn't exist. Every citation read failed, so the freeze step copied nothing and the workspace was left with noassets/and no warning. Generation now anchors the scope overlap-aware (the write-time twin of the% at:read fix), so the citations resolve and the excerpts freeze.- A multi-file
% source:(a.rs + b.rs) now freezes every cited file. Snapshotting treated the whole+-joined line as one literal path, so a multi-file source froze nothing; it now splits the source exactly as the review path does (sharedSourceBase), so freeze and review can't disagree. - A missing or stale
% source:base fails with a clear message. A directory% source:that no longer exists used to have the locator joined onto it, yielding a baffling…/README.md/src/lib.rs"no such file"; it now reports the real cause — the source base doesn't exist (the path is likely stale or wrong). - A cited deck that can't be frozen is reported, not swallowed.
alix explore --buildnow warns which deck's source couldn't be read instead of silently leaving an emptyassets/. - A
% at:locator written relative to a project root above% source:now resolves. When a deck scopes% source:to a subdirectory or file (e.g.…/crate/src/executor) but writes its% at:paths from the crate root (src/executor/local_vm.rs), joining them doubled the overlap (…/src/executor/src/executor/local_vm.rs, "no such file"). Resolution now walks up the base directory's ancestors until the cited file is found. - Frozen-snapshot excerpts show the original file and line numbers. A walk or fact card whose
% source:is a frozenassets/snapshot showed the asset (30.rs, lines 1-N) instead of the real source; the cited excerpt now relabels to the originalcaching.rs:106-120(from the location recorded on its% at:line) — in the walk, the fact-card citation and the terminal walk. - A long (hand-crafted) deck title no longer reflows the header. The review/browse/walk headers truncate an over-long title with an ellipsis instead of wrapping to a second line and growing the header's height.
- No stray blinking caret across the web app. The caret is suppressed on card/slide prose everywhere — review, browse, and the trace walk — appearing only inside a real text input or a source-code excerpt (e.g. with the browser's caret-browsing on).
- Ask-Claude (web): the input re-focuses when a reply lands, so you can type a follow-up immediately instead of clicking back into the box.
- A trace/fact citation against a single-file
% source:no longer doubles the path. When% source:is one file, every% at:reads *that* file; a locator that repeats the path relative to a different root (e.g. the crate root,% at: src/executor/env.rs:44-64against% source: …/src/executor/env.rs) was joined onto the file's own directory, yielding…/src/executor/src/executor/env.rs("no such file"). Both the walk reveal andalix checknow share onelocator_pathresolver, so they can't disagree. - Opening a deck with nothing due no longer bumps it to the top of the recent list. A review now records the deck as "recent" only when the session actually has cards to review (
!session.is_finished()), so merely entering a fully-drilled / all-on-cooldown deck leaves the recent order untouched. - A fact card's
% at:citation resolves against a multi-file% source:. A deck whose% source:joins several files with+(the generator's format, e.g.<crate>/README.md + src/lib.rs) now reads each card's cited excerpt from the right file. Previously the whole joined string was treated as one directory and the% at:file appended to it, so the reveal showedcannot read the source …/README.md + src/lib.rs/README.md.SourceBase::for_decknow bases on the first source file (matchingsource_paths); with several files a bare-line locator is rejected (ambiguous) rather than silently reading the first.
v0.1.0
Changed
- Renamed the project
flash→alix. The binary, the crate, the workspace manifest (flash.toml→alix.toml), and the data directory (~/.local/share/flash→~/.local/share/alix) all move to the new name. Existing progress is auto-adopted on first run: if the legacyflashdata dir exists and the new one doesn't, it's moved across, so your history carries over untouched. (The cards are still "flashcards" — only the tool's name changed.) - You can sit the AI exam early to test out of the drilling. The exam no longer requires every card drilled to the top stage first — it's available as soon as a deck has a
% source:and its% requires:are satisfied (drilled or not). Passing it masters the deck regardless of card progress, which unlocks its dependents — so a learner who already knows a topic isn't forced to grind its cards. Exams still flow in dependency order: a locked deck stays un-examable until its prerequisites are mastered (pass *their* exams first). In the browser picker, a focused examable deck gets a "Take exam" button (and thexkey);alix exam <deck>does the same from the terminal. - The web deck-selection screen now mirrors the terminal picker. It is single-launch (no checkboxes): click a deck to start it, or open a Workspace / Folder to drill into its unlock dependency tree (each deck nested under the prerequisite that gates it). Rows are grouped into Workspaces (each with its last-progress time) · Recent loose decks · Folders, and the filter searches *every* loose deck. A deck you can't start is dimmed — 🔒 locked (
% requires:), 🕒 nothing due — and mastered/done/locked decks are kept out of Recent, with amastered 🎉deck tucked into a Mastered window (m); navigation honors the[picker]config keys (served to the page at/api/picker-keys). A locked deck can no longer be *started* for review (was advisory), but stays fully browsable (alix browseignores locking) and resettable; thealix reset/alix depspickers keep their plain multi-select. The shared badge / lock / dependency-tree logic now lives in the library (picker::deck_statusand the exposed dependency-forest helpers), consumed by both frontends. A trace picked from the in-browser picker now walks (predict → verify, just like the terminal), hosted by the review server at/walk; a Back to decks (orEsc) returns to the picker. - A card that reaches the top Leitner stage now retires (rests, no longer scheduled until
alix reset) instead of recurring at the stage-5 weekly cooldown, so a *finished* deck stays finished. - The TUI's remaining-card count moved from the header to the bottom-right of the footer, shown as
N↓after the pass/fail tally — matching the web frontend's score line (the header now carries only the stage histogram). - Typing mode grades multi-line answers order-independently: a card whose answer is several items can be typed in any order, each completed line matched to whichever expected line it best fits (TUI and web). Single-line answers are unchanged.
- Typing feedback now keeps the typed text on screen and, on a wrong line, shows the correct answer underneath with a check mark (the TUI previously discarded the input and repainted only the answer; the web already did this).
- "New session" on the summary is disabled when nothing is due: the TUI omits the hint and makes the key inert, the web disables the button and shows a "nothing due" note — instead of only reacting after the key is pressed.
- Breaking — cloze hole syntax is now
{{ }}(was{ }). A lone{or}is literal inside#?cards, so code with braces needs no escaping. Cloze identity is now hashed from the parsed structure (delimiters removed) rather than the raw braced text, so existing cloze cards' progress is reset once — but future markup changes won't cost progress again. Existing#?decks must be rewritten{x}→{{x}}or they fail to load (they'd have no holes). - Note rendering moved into a frontend-independent
rendermodule that emits a structured model (NoteUnit: sentence-split prose or verbatim code blocks); the TUI now only paints it. No change to how notes look — this lets a future frontend reuse the same note structuring instead of reimplementing it. - The answer mode is now resolved per card instead of once per session: CLI
--mode> the card's% mode:> the deck's% mode:> the built-in default.--modestill forces every card. - Deck-level directives (
% mode/order/scheduler) must now sit in the deck header, before the first card; a% key: valueafter a card front is treated as a per-card override. alix checkno longer fails on warnings: duplicate-answer warnings are advisory, so it exits non-zero only when a deck won't parse, and prints aN error(s), M warning(s)summary.- Web review now shows the expected answer whenever a typed line differed — including a fuzzy pass within tolerance — matching the TUI, so typos aren't reinforced.
Added
- Fact cards can cite their source (
% at:), shown on reveal. A plain fact card may now carry a% at: file:lineslocator into its deck's% source:(the same form a trace checkpoint uses —file:lines, or justlinesfor a single-file source). On reveal a</>marker appears on the answer; in the web you click the answer (or presss) to swap it for the line-numbered source excerpt and back, and in the terminal you presss— one view at a time, so the card stays compact. The excerpt is read live, so a moved/missing source shows "source unavailable" rather than a stale quote, and% at:is not part of the card's identity hash (adding it never resets progress). Reuses the trace walk's excerpt machinery via a sharedtrace::SourceBase/excerpt_at. The deck generator writes these citations for you —alix deckon a local source andalix explore --buildadd a% at:to each fact that maps to specific lines — andalix checkvalidates a fact deck's citations, warning about one that no longer resolves (a moved or shrunk file). A workspace built withalix explore --into --buildfreezes every cited deck's excerpts into itsassets/(fact decks now, not just traces), so the citations don't drift and the workspace travels without the upstream source; a frozen fact deck's% source:then points at the excerpts, so its exam grades against them. (Snippet names are workspace-unique now, so multiple frozen decks no longer collide inassets/.) % unlock-stage: N— unlock a deck before its cards retire. A% source:deck becomes *exam due* (its exam opens), and a source-less deck *finished* (its dependents unlock), once every card reaches Leitner stage N — without retiring them, so they keep drilling to the top stage; the directive only lowers the unlock bar. Default (unset) keeps the old gate: every card retired at the top stage. Settable per deck, in a workspacealix.toml[defaults], or viaalix explore --into --unlock-stage <1–5>. Generalizes the completion gate (Deck::state).- Browse a deck from the session-end summary (terminal). When a deck turns *exam due* at the end of a review, the summary now offers
bto browse it (a read-only walk through its cards) right next toxto sit the exam — useful for a last skim before the exam. Both the offer line and the footer show the keys. (Appreturns anAfterReview::{Exam,Browse}formainto launch.) - The progress store is now version-checked. A
progress.jsonwritten by a newer alix is refused on open with a clear "upgrade alix" message instead of being silently rewritten at the old version (which could drop data the newer format added); the file on disk is left untouched. A store with noversionfield still loads as the original format. This lays the groundwork for safe schema migrations. - The ask-Claude tutor can read the card's source to verify its answer (opt-in). A new
[ask] source_accessflag (off by default) lets the tutor run withRead/Glob/Grepand its working directory at the deck's% source:project root (resolved up to the nearestCargo.toml/.git/ …), and instructs it to check the real files before answering instead of relying on memory — so a question about a generated deck is grounded in the same source the deck was built from. Off by default because it grants the (possibly LAN-served) tutor file-read access. A workspace can override it per-folder withsource_accessin itsalix.toml(so you can enable it for one trusted crate without turning it on globally). The web ask panel also now shows which model and effort are answering (model: … · effort: …) — a reminder that the tutor uses the CLI default unless[ask]pins a stronger one. alix explore --titleshapes the scaffolded workspace; the goal becomes its description.alix explore --into <dir>now takes an optional--titlefor the workspace'salix.tomltitle(omitted, the folder name is used). It also writes the--goalas a newalix.tomldescriptionfield instead of an ignoredgoalkey; a workspace'sdescriptionshows dim under its row in both pickers (terminal and web).- Confirm before abandoning a review; commit the picker filter with Esc (terminal) — quitting a review mid-session now asks to confirm (
Enterleaves, any other key stays), so a strayEscno longer drops a queued session; a finished session or a hardCtrl-Cstill leaves at once (matching the web frontend). In the picker,Escin the filter box now keeps the filter and drops to the list focused on the first match (a secondEscclears it), instead of discarding what you typed. - Picker disables decks with nothing to review (terminal) — a deck with no card due right now (fully drilled, or all on cooldown) is dimmed and badged with a 🕒 clock, mirroring how a 🔒 locked (
% requires:) deck looks, andEnteron it is a no-op — no more starting an empty session that bounces you out to a "Nothing to review right now" message. Such decks also can't be ticked into a merged review, and (in a workspace drill-in) sink below the startable ones.--cram, which ignores cooldowns, turns the gating off; browse never gates (any deck is browsable). New lib helpersession::has_reviewable. - Reworked deck picker + trace walking from the picker (terminal) — the no-argument picker is a clean, single-launch list (no checkboxes):
Enteropens the focused row. Its header is justalix; rows are grouped into Workspaces (each showing when it last made progress, from its own store) · Recent (loose decks you reviewed lately) · Folders, a blank line between sections, with the filter searching *every* loose deck. A deck that lives inside a workspace is kept out of Recent — you reach it by opening its workspace. Rows that share a title (two workspaces named the same) get a path hint to tell them apart; over-long rows (a trace's% trace:sentence) are truncated with…. Rows you can't start now are dimmed andEnteris a no-op: 🔒 locked (% requires:unfinished), 🕒 nothing due (on cooldown); a mastered deck readsmastered 🎉. The focus is on the list by default with Vim-style keys, rebindable in a new[picker]config section (j/kor arrows move,l/Enteropen,h/Esc/Backspaceback,mopens the Mastered window,/orCtrl-Ffilters); jumping to the first/last row is fixed atg/G(or Home/End), like the[browse]pager. Mastered/done and locked decks are kept out of Recent (a quick launchpad);mopens a dedicated Mastered window of the exam-passed decks, or the filter reaches them. Long% title:/% trace:labels are capped so rows stay short. The picker and the review/walk/exam it launches now share one terminal: opening a deck and returning to the workspace no longer tears the TUI down and reopens it. Opening a workspace or folder drills into its members drawn as an unlock dependency tree — a deck nests under the% requires:prerequisite that gates it, foundations at the roots, siblings startable-first, each badged· trace ·/· deck ·. Opening a workspace, stepping back (Esc/Backspace), and returning after a review/walk/exam all happen within one live screen — no TUI teardown/reopen — so you can study a deck and land back in the picker (the workspace you came from, or the top list) to pick the next; only anEscat the picker itself quits. The big gap it closes: a trace opened from the picker now walks (predict → reveal) instead of being flattened into a card review — both in the top-level drill-in andalix workspace <dir>. An explicitalix review <trace.txt>still flattens it (honoring the literal command). The multi-select machinery is retained in the code but unused for now. The web picker follows in a later phase. - Per-workspace progress store — a deck inside a workspace (a folder with a
alix.toml) now tracks its progress in aprogress.jsoninside that workspace, not the one global~/.local/share/alix/progress.json. So a workspace is a self-contained, portable unit (decks +assets/snapshots + progress in one folder), its history is isolated, and same-named decks in different workspaces no longer collide in one store. Loose decks (and plain folders without a manifest) keep the global store;--store <path>overrides either; a workspace can redirect its store with astore = "..."line in thealix.toml. Resolution:--store> the single workspace all the session's decks share > global. Applies across the CLI/TUI (review,trace,exam,browse,stats/list,reset,alix workspace); the web frontend follows with the picker revamp. (No migration — workspace decks start fresh in the workspace store; existing global progress for them is left in place.) - Trace source snapshots — creating a workspace by exploring a source (
alix explore --into <dir> --build) now freezes the cited excerpts into the workspace as its final step: for each checkpoint it writes a small snippet file into the workspace'sassets/folder (assets/01.rs,02.rs, …) holding just the lines that checkpoint reveals, and repoints the% at:(and the trace's% source:) at it. This stops the line-number locators from drifting when the upstream source is later edited (the walk reads the source live, so a moved line silently shows the wrong excerpt), and makes the workspace self-contained — without copying whole (possibly huge) source files. A re-based snippet loses its original line numbers, so when those matter the originalfile:linesis preserved in the card's note (! from scheduler.rs:90-98). The source is plain text (any file, any topic — no version-control assumption). It's automatic for explored workspaces, not a command; a loose trace over a live% source:is left as-is. Rationale indocs/traces.md. alix import <file.tsv>— import an Anki "Notes in Plain Text" export (tab-separatedfront<TAB>back) into an alix deck, no Claude needed. It skips Anki's#-prefixed header lines, turns<br>tags into separate answer lines, decodes the common HTML entities, and backslash-escapes a back line that would otherwise read as a%comment or!note; rows missing a side are dropped. The result is validated and written to~/decks/(-o/--print/--force, likealix deck). Conversion lives in the lib (import::tsv_to_deck).alix checknow validates trace% at:locators. A trace deck is linted like any other:checkresolves each checkpoint's locator against its% source:and warns (advisory, non-fatal) about any that name a missing file, run past the end of the file, give bare line numbers without a single-file source, or are absent — a quick "does this excerpt still exist?" structural check that catches a moved or trimmed source before a walk hits it. (Frozen snapshots are validated the same way.) It also prints the deck's% trace:description. Logic in the lib (trace::Trace::lint_locators).alix deck <source>(renamed fromalix generate, which no longer exists as an alias) — generates a facts deck with Claude from a web page URL or a local file/directory path, mirroringalix trace. A URL is fetched with WebFetch and the deck starts with a% link:; a local source is explored read-only withRead/Glob/Grepat its root and the deck starts with a% source:(soalix examcan grade against it). This gives a facts-deck stub fromalix explore --intoa manual fill path (pointalix deckat its% source:).- Traces (
alix trace, experimental) — a guided predict-and-verify walk along a *path* through a% source:, drilling the connections between facts (the edges) rather than isolated facts. A trace deck declares a% trace:(a path description that marks it a trace) and a sequence ofexplain-style checkpoint cards, each with a% at:locator (file:lines, or justlinesfor a single-file source) into the real source, and optional% given:lines that name off-screen symbols the question leans on (shown as a list under the question, so a tight excerpt doesn't orphan the names it uses). Walking it goes hop by hop: you predict before anything reveals, the real excerpt is read live from the source and shown with the key points, you self-judge the gap (Got / Partial / Missed — a weak edge resets so it resurfaces sooner, via the normal per-checkpoint SRS), and after the last hop you compress the whole path into two sentences. Self-judged and offline (no model call) by default;alix trace --gradeinstead has Claude judge each typed prediction against the key points and return the verdict + one line of feedback (a model call per hop, run at the lightweight[ask]tier — not the heavy build defaults below).alix trace <deck> --servewalks it in the web frontend (the same frontend-agnosticWalkstate machine the terminal uses): a left path rail whose nodes color in by Got / Partial / Missed, each checkpoint's source shown in a line-numbered excerpt, and--serve --graderunning the live grade on a background thread while the page polls;--port/--lanwork as inreview.alix trace <deck> --mapprints the path without quizzing; the generic AI exam refuses a trace (its verification is the walk itself). Seedocs/examples/workspace-showcase/decks/ownership-move.md.alix trace --build <deck>discovers the path for you: declare just the% trace:and% source:, and Claude explores the source (read-onlyRead/Glob/Grep, with the source root as its working directory — no write or shell access), traces the single load-bearing path, and writes the checkpoints back into the deck. The build prompt encodes the chain rules fromdocs/traces.md, so generated traces are paths, not quizzes. Configurable via a new[trace]section (model, effort, timeout, extra guidance) — which, unlike the other AI features, defaults to a strong model (opus) and high effort (--effort high) because building is one-shot, correctness-critical and fails silently on a weak model. A neweffortknob also exists on[ask](off by default) and is plumbed through to the CLI's--effortflag.alix trace --suggest <source>recons a source (read-only, one pass) and prints a ranked menu of candidate traces to author — a path-question, a spine sketch, and a suggested scope each, no checkpoints — closing the "what's worth tracing?" gap before--build. alix explore <source>(experimental) — goal-driven exploration: prints an ordered learning plan toward a--goal(default "understand the whole source"), the fact decks and traces worth authoring. Each item is tagged[trace]/[deck](chosen by shape — edges become traces, node-shaped fact tables become decks), carries its% requires:prerequisites (the list is a valid topological order, foundations first), and a% source:scope. The goal scopes coverage — a broad goal spans every subsystem, a narrow one collapses to its slice (and traces it deeper). By default read-only (prints the plan);--into <dir>materializes it into a workspace folder — analix.toml(the goal) plus a stub deck/trace file per item,% requires:-wired in dependency order with absolute% source:paths, ready toalix trace --build/ author (refuses a non-empty dir unless--force). Add--buildto fill them:alix explore … --into <dir> --buildexplores the source once, then resumes that same CLI session to write the full content of every item — predict-verify checkpoints for traces, fact cards for decks — so the workspace is review-ready in one command, with the items coherent (written from one understanding) and facts decks filled too.--walkinstead builds an explore walk — a predict-verify trace over the source's *shape* (what it is → its domain nouns → entry point → spine → the first paths worth tracing), each hop revealing real structural evidence (the manifest, the module list, the entry enum). It's written to a file (-o, defaultexplore.txt) and walked immediately, reusing thealix tracewalk; re-walk later withalix trace <file>.- Workspaces — a folder of decks reviewed together with shared directives. A folder is a workspace when it has an
alix.tomlmanifest (a scopedconfig.toml) setting atitleand a[defaults]table of directives that fill in what each deck leaves unset (precedence CLI > card > deck > workspace > default); a folder of decks *without* a manifest is a plain folder — still reviewable, but not a workspace. Both appear as their own rows in the picker (terminal and web, labeled "workspace" vs "folder") and drill into their decks (review all, or tick a subset);alix review/browse <folder>reviews the whole cluster.alix workspace <dir>opens a workspace into its own picker and routes each member to the right thing — a facts deck → review, a trace deck → predict-verify walk — returning to the picker when done. Great for clusters like a vocabulary set that should all bedirection = "both"without repeating it per file. % title:deck directive (also usable in aworkspace.alixmanifest): a display name shown in the picker, session header,alix listandalix statsinstead of the file name. Display-only and never part of card identity.alix exam <deck>— the AI exam, which *verifies understanding* and gates progression (rung 3 of the AI-exam direction). A deck declares its ground truth with% source: <url-or-file>(repeatable); the exam asks Claude for fresh open questions generated from that source (never from the cards, which would be circular), reads your typed answers, and grades them Pass/Partial/Fail against per-question rubric points. Passing marks the deck mastered, which is what now unlocks dependent decks — drilling a% source:deck to the top stage leaves it *exam due* (a new deck state, shown in the picker andalix stats) rather than finished; source-less decks keep the mechanical "finished = drilled" unlock. On a fail, the missed concepts can be turned into remediation cards appended to the deck — the card type is chosen per gap (cloze/plain for a missed fact,% mode: explainfor a missed concept), and overlapping gaps are consolidated into a single card — then re-drill, re-sit. Grading strictness is per deck —% strictness: strict | balanced | lenient(oralix exam --strictness, or the[exam]default) — because some material needs every point recalled while other is about grasping the idea:stricttreats an omitted rubric point as a gap,balanced(default) judges understanding and forgives terse phrasing,lenientonly flags clearly wrong answers (orthogonal topass_threshold, which sets how many answers must pass). New[exam]config section (model,timeout_secs,num_questions,pass_threshold,strictness,extra); reuses the[ask]command/permission/tools (WebFetch reads a source URL).alix resetof a deck also clears its mastered state. A URL% source:also doubles as an ask-Claude reference link (no duplicate% link:needed); a% link:never becomes an exam source. The exam is fully interactive in both frontends (rung 3b): answer one question at a time (Back/Next), then see a per-question breakdown —alix examandalix serveshare one engine (exam::Sitting) that runs Claude on a background thread and polls, so neither blocks. You reach it by picking anexam duedeck (it launches the exam instead of an empty review) or from the session-end summary when a deck you were drilling just became exam-due. Exam-due decks aren't tickable into a merged review (they have no due cards).% mode: explain— understanding cards. The front is an open prompt and the back lines are the *key points* a good answer should cover (not a string to reproduce). You optionally type your explanation, reveal the points, and self-grade (Again/Good/Easy) on whether you covered them — for cards aimed at understanding over recall. The typing is optional and unchecked (a self-graded mode can't verify it); the web shows your answer beside the points. Works in both frontends and pairs with ask-Claude. (Daily tier of the planned AI exam.)- Ask-Claude in the web frontend (
--serve): an "Ask" button / the?key on an answered card opens a chat panel (Send / Save note / Close), mirroring the TUI feature. The server runsclaude -pon a background thread and the page polls for the reply, so the single-threaded server stays responsive; one conversation spans the session (--session-id/--resume), and Save note appends a condensed note to the deck file. Reachable wherever you serve, including--lan. - Deck completion states and unlocks. Each deck has a state derived from its cards' stages — not started / started / finished (all cards at the top stage) — shown in the deck picker (terminal and web) and
alix stats. A deck is locked while any of its% requires:prerequisites isn't finished (finishing a foundation unlocks what builds on it); locked decks are dimmed with a 🔒 but stay selectable (advisory). Derived live from progress, with no new directive or storage. - Repeated
TABin typing mode progressively reveals the answer: each press uncovers two more characters until the line is fully shown (still counts the card as failed); typing or deleting resets the reveal. - In-browser deck selection:
alix --serve(andalix browse --serve) with no deck files now opens a deck-selection screen in the browser instead of the terminal picker — a checklist of the same decks (recent first), with a Start button that builds the session in place. Passing decks on the CLI still skips it. A running web session can return to the picker via "Choose other decks" (on the summary or the menu) to study a different deck without restarting. Selection only accepts deck names from the live catalog, so no path is built from request input. - Mark a card for removal during review or browse with the new
removekey (defaultCtrl-Xin review,xin browse,x/Remove button in the web UI). It is dropped without being asked again (cloze siblings too); the marked cards are deleted from their deck files and their progress is pruned — at the end of the session in the TUI, immediately in the web UI (which has no end-of-session). - Local web frontend: add
--servetorevieworbrowseto run it in the browser instead of the terminal, reusing the same session logic and writing to the same progress store, so browser and CLI share one history. All answer modes work (flip, line-by-line, typing, fuzzy, multiple choice); controls are touch-friendly and mirror the configured[keys]bindings. Binds to localhost by default;--lanexposes it to the network (no auth),--port/[serve]set the port (both--portand--lanrequire--serve). Built ontiny_http. - Per-card answer mode: a
% mode:directive placed after a card's front overrides the deck's% mode:for that card only, so one deck can mix modes (e.g. alinelyrics card amongflipcards). Cloze sub-cards inherit their source card's mode. - A mode badge at the top of the answer section on every card, in both the TUI and the web app —
flip,typing exact,typing fuzzy,choice,line by line— so typing vs fuzzy (otherwise identical input prompts) is clear at a glance. - Dual-direction cards: a
% direction:directive (per card or deck-wide,forward/reverse/both) reviews a card both ways —bothgenerates the card and its swap (e.g.purported → angeblichand back). The two get distinct progress, are kept apart in the queue, and are removed together; cloze cards are unaffected. - Image cards:
% img:(question side) and% img-back:(answer side, revealed with the back) attach an image to a card; a deck-level% img-dir:sets the folder filenames resolve against (else the deck file's folder; absolute card paths are used as-is). Images render in the web frontend only, so an image card is automatically web-only — the TUI skips such cards (and refuses, pointing at--serve, if a whole deck is web-only). A general% frontend: any|tui|webdirective (per card or deck-wide) controls this explicitly;alix checkwarns about missing image files./img/<key>URLs are opaque hashes of registered deck paths, so the server never joins request input to a filesystem path. alix resetclears stored progress: for one or more decks, a single card (--card <id-or-front-text>), or everything (--all). With no decks it opens the same checkbox picker asreview/browseto choose them;--cardsopens a picker over a deck's cards (those with progress). Confirms first unless-y/--yes, and refuses to act without a terminal rather than wiping silently.
Fixed
- Generated decks now put a blank line between cards.
alix deck's output cleaner (generate::clean_output) inserts a blank line before each card front (#) after the first, so a generated/--reviewed deck is readable instead of cards running together. The first card stays attached to its%header, and an already-separated deck is left untouched. - A note saved from the ask tutor shows on the card right away. Saving a note appended it to the deck file but left the in-memory card unchanged, so the new note only appeared after the deck was re-read (a later session). The just- saved lines are now mirrored onto the in-memory card (
Card::append_note) — no deck re-read — so returning to the card shows the note immediately, on both the web and the terminal (the web previously never reflected it; the terminal only updated the ask view's recap, not the card on return). On the web, closing the ask panel now re-pulls the card state (keeping the reveal position) so the saved note appears on return instead of only after a manual page reload. - The web ask panel shows the card above the conversation, matching the terminal. The card under discussion (its front + answer) now sits at the top as the reference, with Claude's conversation flowing below it (answer under question), instead of the card being tucked beneath the conversation — so the question you're studying reads above the answer, the same order the TUI already used. The card and conversation now share one scroll region and the card sticks to the top, staying in view as a long conversation scrolls under it.
- The grounded tutor no longer breaks the conversation with "No conversation found with session ID". Claude scopes its conversation history by working directory, but the grounded tutor (
[ask] source_access) runs each card's questions with the working directory set to that card's% source:root. A follow-up--resumethat ran in a *different* directory than the--session-idthat created the session — moving between cards grounded in different roots, switching a grounded and an ungrounded deck, or the "save note" condense (which ran ungrounded) after a grounded question — landed in the wrong project and failed. The CLI session is now cwd-aware (CliSession::args_in): a working-directory change starts a fresh conversation there (a clean first prompt) instead of a doomed resume, and a card's condense uses the same grounding as its questions so the directory stays stable. Same-directory follow-ups still resume as before. - Exam remediation is faster, can't fail silently, and shows progress. Three problems when "Add remediation cards" was slow or produced nothing: (1) the remediation call inherited the tutor's
WebFetch/WebSearchtools and could wander off researching the gaps — it now runs tool-free (it only needs the gap list), so it's a quick, deterministic text-generation call; (2) if the model replied in prose instead of cards, the prose was appended to the deck as a bogus "card" (so "no new cards" appeared on re-drill) — the reply must now contain at least one#card front or remediation fails with a clear message instead; (3) a failed call (timeout, empty/unparseable reply, write error) was easy to miss — both the web and terminal exam views now show a prominent error banner at the top (web offers "Try remediation again"; the terminal scrolls it back into view), and every in-flight Claude call (generating, grading, remediating) shows a live "Claude is working… Ns" counter so a long call no longer looks frozen. - A
% source:that names several files no longer breaks the exam. The deck generator sometimes writes a multi-file source as<root>/README.md + src/lib.rs(first a full path, the rest relative to it). The exam read the whole string as one path and failed with "cannot read source file …"; sources are now split on+, each part resolved (relative parts anchored to the first file's directory) and read, with unreadable parts skipped rather than aborting the exam. The grounded tutor's project-root resolution handles the same format. - You can start the AI exam on a deck inside a workspace from the browser.
POST /api/exam/startonly resolved top-level deck names, so the "Take exam" action silently failed (a 400) for a workspace member; it now resolves members by their qualified<workspace>/<file>name too, like/api/select. - The web ask-Claude panel now shows only the current card's exchanges. It was rendering the whole session's conversation, so every former card's Q&A piled up on screen. The display is now scoped to the card you're on (and so is the "save note" condense), while the CLI conversation still spans the session — Claude keeps the full context. The card's front + answer are pinned just above the input, for easy reference while you type a question. (The terminal ask view already scoped per card.)
- The TUI reflows immediately on a terminal resize. The event loops redrew from a size query that could be momentarily stale right after a resize, so the screen sometimes stayed unchanged until the next keypress refreshed it. They now resize with the dimensions the resize event itself carries — picker, review, exam, and browse.
flash 0.1.0
First release of alix: a terminal spaced-repetition flashcard trainer with a ratatui TUI, plain-text decks, two schedulers, several answer modes, cloze cards, deck dependencies, an ask-Claude helper, and AI deck generation.
Deck format
- Plain text:
#card front at column 0, indented answer lines,!notes (multiple!lines form one multi-line note),%comments,\to escape a leading markup character. Indented#lines are answer content, not new cards. - Cloze cards: a
#?front with{holes}in the answer expands into one sub-card per hole; sibling holes are masked and spaced apart in the queue. - Directives (
% key: value):mode,order,schedulerset per-deck defaults; read from the requested deck(s) only, overridden by CLI flags. - Dependencies (
% requires: <deck>): prerequisite decks are pulled in transitively and ordered foundations-first; cycles and missing prerequisites are reported. Prerequisites contribute cards only, not directives. - Reference links (
% link: <url>) are offered to the ask-Claude feature.
Review
- Answer modes: flip (default, self-graded), typing (char-by-char), fuzzy (whole-line, typo-tolerant), choice (multiple choice with distractors sampled from the session), and line (reveal the back one line at a time — for lyrics, poems, ordered lists).
- Schedulers: Leitner (the original 6-stage boxes, compatibility-verified) and SM-2 (per-card ease factors), interchangeable.
- Session controls:
--new(new-card cap),--limit,--cram,--order sequential, restart from the summary screen, failed cards requeued within the run. - Notes render as a quoted block, split into sentences, with fenced code shown verbatim (indentation preserved).
Ask Claude
- Press
?on an answered card to ask the Claude Code CLI about it without leaving the session; one conversation spans the run.Ctrl-Ncondenses the exchange into note lines appended to the deck. The input line supports cursor movement and editing. Runs headless with a safe permission model (dontAsk+ an exclusiveWebFetch/WebSearchallowlist).
AI deck generation
alix generate <url>builds a deck from a web page via Claude (WebFetch), with a prompt that spreads cards across four layers of understanding, uses cloze and notes, and self-reviews for redundancy;--reviewadds a second refinement pass. Output is validated and saved (or--printed);--cardsand[generate]config tune it. Claude only returns text — alix writes the file — so no extra tool permissions are needed.
Other commands
alix browse— read-only walk through cards (no grading, no writes).alix deps(aliasrequire) — edit a deck's prerequisites with a checkbox picker.alix stats,alix list,alix check,alix config.- Startup deck picker (recent decks + the decks directory) when run with no arguments.
Configuration
~/.config/alix/config.tomlwith[keys],[browse],[ask],[generate]sections anddecks_dir.alix config --initwrites a self-documenting template (every option commented at its default);alix configprints the active settings. Key bindings are rebindable.
Storage
- Card identity is a stable XxHash64 over the deck file name plus the back lines (a test pins the value so upgrades never orphan progress). Progress is stored at
~/.local/share/alix/progress.json, created on first use.
Desktop
assets/install-desktop.shinstalls an icon, launcher, and.desktopentry.