this repo has no description
1# CLAUDE.md
2
3Guidance for Claude Code when working in this repository.
4
5## Commands
6
7```bash
8cargo build # build everything
9cargo check # check without artifacts
10cargo test # all tests
11cargo test -p tala-format card_count # single test
12cargo run -p tala-cli -- check <deck-dir>
13cargo run -p tala-cli -- review <deck-dir>
14cargo run -p tala # GUI (uses cwd or ~/.config/tala/dir)
15```
16
17## Architecture
18
19Cargo workspace, edition 2024. All crates under `crates/`.
20
21```
22tala-format → tala-srs → tala-typst → tala (GUI)
23 ↑
24 tala-cli
25```
26
27---
28
29### `tala-format`
30
31Parses `.typ` card files using `typst-syntax`. Entry point: `parse_cards(source: &str) -> Vec<CardEntry>`.
32
33Uses `LinkedNode` (not `SyntaxNode`) — `LinkedNode::range()` returns `Range<usize>`; `SyntaxNode` only exposes an opaque `Span`. Walker matches `SyntaxKind::FuncCall`, dispatches on function name.
34
35**Card syntax:**
36```
37#card(tags: ("t1",), dir: "bi")[front][back] → CardKind::FrontBack
38#cloze(tags: ("t1",))[body #blank[hidden] ...] → CardKind::Cloze
39```
40
41No `#img_cloze` — removed. Image region overlays live in the sidecar as `RectEntry`.
42No `id:` field — cards are identified by **position index** in the file ("0", "1", ...).
43`dir` defaults to `Forward`; `"bi"` → `Bidirectional` (two review items: forward + reverse).
44
45**Key types:**
46```rust
47pub struct CardEntry {
48 pub tags: Vec<String>,
49 pub kind: CardKind,
50 pub span: Range<usize>, // byte range of the full #card(...)[...] call
51}
52
53pub enum CardKind {
54 FrontBack { front_span, back_span, dir: Direction },
55 Cloze { body_span, blanks: Vec<BlankEntry> },
56}
57
58pub struct BlankEntry { pub index: usize, pub span: Range<usize>, pub content_span: Range<usize> }
59pub enum Direction { Forward, Bidirectional }
60```
61
62**Span gotcha:** the `#` sigil is outside the `FuncCall` AST node. `card.span` points to `card(...)` not `#card(...)`. Use `span.start - 1` when byte-splicing to include the sigil.
63
64---
65
66### `tala-srs`
67
68Loads/saves `<cards>.srs.json` sidecar alongside `<cards>.typ`. All FSRS schedule data lives here, never in the typst source. Image rect overlays also live here.
69
70**Primary API:**
71```rust
72Sidecar::load_or_empty_for(typ_path: &Path) -> Result<Self>
73Sidecar::save_for(&self, typ_path: &Path) -> Result<()>
74Sidecar::path_for(typ_path: &Path) -> PathBuf // derives .srs.json path
75Sidecar::empty() -> Self
76sidecar.get(card_id: &str) -> Option<&CardSchedule>
77sidecar.orphaned(known_ids: &[String]) -> Vec<&str>
78sidecar.missing(known_ids: &[String]) -> Vec<&str>
79```
80
81`card_id` is always a stringified position index ("0", "1", ...).
82
83**Key types:**
84```rust
85// Tagged-union JSON: #[serde(tag = "type", rename_all = "snake_case")]
86pub enum CardSchedule {
87 FrontBack {
88 forward_schedule: Option<Schedule>,
89 reverse_schedule: Option<Schedule>,
90 },
91 Cloze {
92 blanks: HashMap<String, Schedule>, // keys: "b0", "b1", ...
93 rects: Vec<RectEntry>, // image region overlays
94 },
95}
96
97pub struct Schedule { pub due: String, pub stability: f32, pub difficulty: f32 }
98// due is "YYYY-MM-DD"
99
100pub struct RectEntry {
101 pub id: String, // "r0", "r1", ...
102 pub src: String, // bare image stem (no path, no extension)
103 pub rect: [f32; 4], // normalized [x, y, w, h] in [0.0, 1.0]
104 pub schedule: Schedule,
105}
106```
107
108**FSRS helpers:** `is_due(sched: &Schedule) -> bool`, `next_schedule(current, days_elapsed, grade) -> Schedule`, `today_str() -> String`.
109
110---
111
112### `tala-typst`
113
114Renders typst source strings to RGBA pixels via the `typst` crate. Implements `typst::World`. Font loading must provide at least one valid font family or typst panics.
115
116```rust
117pub fn render(deck_dir: &Path, fragment: &str, preamble: Preamble, blank_spans: &[Range<usize>]) -> Result<RenderResult, Error>
118
119pub enum Preamble {
120 Authoring, // full card, all content visible
121 ReviewFront, // FrontBack: show front content block only
122 ReviewBack, // FrontBack: show back content block only (for bidirectional reverse)
123 ReviewCloze, // Cloze: replace all blank[] with opaque boxes
124}
125
126pub struct RenderResult {
127 pub rgba: Vec<u8>, // premultiplied alpha, row-major
128 pub width: u32, pub height: u32,
129 pub blank_boxes: Vec<[f32; 4]>, // pixel [x,y,w,h] per blank (requires blank_spans)
130 pub glyph_map: Vec<([f32; 4], Range<usize>, bool)>, // (px rect, frag range, is_math)
131 pub math_spans: Vec<Range<usize>>, // fragment-relative Equation byte ranges
132 pub image_boxes: Vec<[f32; 4]>, // pixel [x,y,w,h] per image, document order
133}
134```
135
136The RGBA output is **premultiplied alpha**. Convert to straight alpha before encoding to PNG (divide RGB by alpha).
137
138---
139
140### `tala-cli`
141
142Two subcommands:
143
144- **`check <path>`** — parses all `.typ` files, reports card counts, sidecar orphans/missing entries.
145- **`review <path> [--tag TAG]`** — interactive terminal review session. Collects all due items across files, prompts grade (1-4), saves to sidecar, git-commits each modified sidecar with message `"review session YYYY-MM-DD"`.
146
147The CLI uses `git2` for commits; the GUI does not.
148
149---
150
151### `tala` (GUI)
152
153Dioxus desktop app. Single binary. Routes: `Home`, `Editor`, `Images`, `Review`, `Settings`.
154
155**Global state:**
156```rust
157static CARD_DIR: GlobalSignal<PathBuf>
158fn card_dir() -> PathBuf
159fn cards_path() -> PathBuf // card_dir().join("cards.typ")
160fn set_card_dir(path: PathBuf) // persists to ~/.config/tala/dir
161```
162
163Deck dir is loaded from: CLI arg → `~/.config/tala/dir` → cwd.
164
165**Editor** (`Editor` component):
166- Segments source file by blank lines: `SegKind::Card` or `SegKind::Text`, identified by prefix (`#card` / `#cloze`).
167- Each card segment rendered independently. Active card has an editable textarea; inactive cards show rendered preview.
168- Auto-saves to disk after 1s debounce. **No git commits from the GUI.**
169- Card identification: `active_card_key()` counts `Card` segments up to active index → stringified count → sidecar key.
170- **Draw mode**: drag to create rect overlays on images within a card. Saved to sidecar as `RectEntry`. Existing rects are draggable/resizable. Delete via button.
171- Image paste from clipboard (arboard, not web_sys) → saves to `<deck>/images/`.
172- Keyboard nav: Up/Down at textarea boundary switches active segment.
173
174**Images** (`Images` component):
175- Grid of all files in `<deck>/images/`.
176- Rename: updates filename on disk and rewrites matching `src` fields in all sidecar `RectEntry` records.
177- Copy-name button, lightbox preview.
178
179**Review** (`Review` component):
180- Setup screen: clickable tag chips (OR/AND toggle for 2+ tags), "Start review →" builds filtered queue.
181- Queue items: `ReviewKind::FrontBack`, `ReviewKind::Cloze`, `ReviewKind::ImageRect { src, rect }`.
182- Question → reveal → grade (1=Again 2=Hard 3=Good 4=Easy). Keyboard: Space/Enter=reveal, 1-4=grade, S=skip.
183- Skip: moves item to back of queue with `skipped: true`; terminates when all remaining are skipped.
184- Image rect review: renders full card via `Authoring`, then pixel-fills the rect region for the question image.
185- Crossfade between question/answer images via CSS grid overlap + `opacity` transition. Click image to toggle.
186- Saves sidecar to disk after each grade. No git commit.
187
188**Settings** (`Settings` component):
189- Change deck directory (file picker).
190- "Reset all schedules" — clears all schedule data from sidecar (preserves rect geometry), requires confirm dialog.
191
192---
193
194## Deck directory layout
195
196```
197<deck>/
198 cards.typ # human-authored typst source, git-tracked
199 cards.srs.json # machine-managed schedule + rect overlay data
200 images/ # referenced via #img("stem") or #image("images/stem.ext")
201```
202
203Card edits are byte-span splices — never reformat the whole file.
204`extract_img_names(source)` normalizes both `#img("stem")` and `#image("images/stem.ext")` to bare stems, matching sidecar `RectEntry.src`.