commits
Splits crates/tala/src/editor.rs into focused submodules:
blank_wrap, draw_ops, format, image_paste, render, segments, sidecar_ops.
Fixes a data-loss bug in format_card_frag: when the user deletes the
opening `#blank[` but leaves the closing `]`, parse_card_structure finds
one balanced block ending at that orphaned `]`, leaving trailing content
(e.g. `ghi`) unaccounted for. The formatter then reconstructed the card
from only the parsed block, silently dropping the suffix. Fix: bail out
of formatting when non-whitespace content follows the last block's `]`.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Expands the selection bounds to cover any partially-overlapped equation
before wrapping, rather than rejecting with an error. #blank[text $eq$]
is valid Typst so no special restructuring is needed once the equation
delimiters are fully included.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
arboard is not Send on Linux so spawn_blocking is unavailable; use
std::thread + tokio oneshot to keep the async runtime unblocked during
the slow X11 selection transfer and image encode.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ArrowDown/Up at segment boundary now lands cursor on the correct line
and column of the destination card (first line for Down, last for Up)
instead of always placing at the end.
Uses a nav_cursor signal to pass (col, going_up) from on_keydown to
onmounted, avoiding the previous setTimeout race with Dioxus re-render.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
prevent_default() stops the browser moving the cursor before the async
JS check runs; cursor movement is now handled manually in JS so the
boundary detection sees the pre-keypress position.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- expand_math_selection: balance typst string literals (`"..."`) — an odd
quote count in the selection now advances end to include the closing `"`
- insert_blank_wrap_math: for display math (`$ ... $`), keep the blank
inside the single block (`$ left #blank[$sel$] right $`) instead of
splitting into two block-level math elements that render on separate lines
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Cards now carry an `id:` argument in the typst source, generated as a
13-character ATProto-compatible TID (microsecond timestamp + 10-bit
counter). `assign_ids` splices missing IDs into source on first open;
`ensure_card_ids` also remaps sidecar keys from positional ("0","1",...)
to the embedded IDs atomically. All card key lookups in the editor and
review queue now use the embedded ID, with positional fallback.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Appends one JSONL entry per grade in both GUI and CLI review paths.
Stats page gains a 30-day history bar chart and total-reviews count.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a color picker in Settings (persisted to ~/.config/tala/cloze_color)
that controls the SVG highlight color for cloze blanks and image rects in
the editor. Also collapses the duplicate active/inactive sidecar rect
rendering branches into a single loop with is_active gating interactivity.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Textarea height tracks scrollHeight+3px on mount and input
- Card list constrained to 55rem, centered
- Card previews centered via text-align on .card-row
- min-height reduced to 3rem
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
blank_boxes is now Vec<Vec<[f32; 4]>> where each inner vec has one merged
rect per rendered text line. cluster_into_lines groups glyphs by y-center
proximity (60% line-height threshold) so blanks wrapping across lines get
correct per-line highlight rectangles in the editor overlay.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Stats page (/stats):
- Counts total review items, new (no schedule), due today from sidecar
- 14-day forecast bar chart, today bar highlighted
- tala-srs: add pub date_offset(days) helper
Editor fixes:
- is_card_frag: recognize #card\n[ and #cloze\n[ (multiline formatted)
- strip_head_whitespace: normalize head\n[ -> head[ before typst render
so content blocks attach correctly; adjusts blank spans accordingly
- format_card_frag: no longer emits \n before first [, preventing
auto-format from producing the broken multiline form going forward
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Each cloze ReviewItem already had an independent sub_id ("b0", "b1", …).
The renderer was ignoring it and blanking everything. Now ReviewCloze
carries the target blank index and the Typst preamble uses a counter to
hide only that blank, showing all others.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Images: add Delete button per tile with confirm dialog; warns if N
cards reference the image via extract_img_names
- Editor: replace static "no cards" message with a live textarea so
new cards can be typed directly into the empty state
- Editor: fix ghost character reappearing after tab switch — on_input
was early-returning on empty value, leaving stale content in source
that onmounted would restore on next mount
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Dioxus does not clear inline style when set to empty string; use
explicit "display:block" so cards reappear when filter is cleared.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Editor: tag chips filter visible card segments (display:none preserves indices)
- Review: inline filter bar + Restart button available during active session
- Factored collect_all_tags_from_source for reactive derivation from live source signal
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Review setup screen with tag chip filter (OR/AND mode)
- Skip button and S key moves card to back of queue; terminates when all remaining are skipped
- CLAUDE.md rewritten to reflect current codebase (removed img_cloze/id field, added all GUI components, corrected Sidecar API and Preamble variants)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Both images stacked in a CSS grid cell; opacity transitions on click.
Answer fades in on reveal, click toggles back to obscured view.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously returned the full path string, causing src mismatch with sidecar
rects which store bare stems. Now strips "images/" prefix and extension for
both #img() and #image() forms.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Preserves rect geometry; clears blank schedules and resets rect
schedules to due-today. Requires confirm dialog before executing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Rects drawn before src was stored correctly have src="". Without this
fallback, img_idx was None and the dark overlay block was never entered,
so review showed the plain image with no rectangle obscured.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
build_review_queue now iterates sidecar rects (Vec<RectEntry>) in addition
to text blanks. Each due rect produces a ReviewItem with kind ImageRect {src, rect}.
Question render blacks out the rect region in the PNG by overwriting
pixels at the image box coordinates. Answer shows the card unmodified.
apply_review_schedule handles the rect-{id} sub_id by updating the
inline schedule on the matching RectEntry.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
card.span starts at the function name, not the # sigil. Without it,
the renderer sees plain text like cloze[...] instead of #cloze[...].
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
GlobalSignal reads require the Dioxus runtime; spawn_blocking runs on a
plain tokio thread. Capture the dir value in the async block (still on
the Dioxus runtime) before handing off to spawn_blocking.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Route /review with navbar link
- Builds due queue from cards.typ + sidecar (same logic as tala-cli)
- Renders question with ReviewFront/ReviewBack/ReviewCloze preamble
- Reveals answer with Authoring preamble on Space/Enter or button
- Grades 1-4 (Again/Hard/Good/Easy) via button or keyboard
- Saves sidecar after each card; no git commit from GUI
- Added Preamble::ReviewBack to tala-typst for bi-directional reverse review
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously only #img("stem") was rewritten; raw typst #image() calls
with full path+extension were left stale.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ImageTile rename now uses the original file extension instead of hardcoding .png
- tala-cli review: bi-directional cards enqueue a "reverse" item (back->front)
in addition to "forward"; apply_schedule and print_card_preview handle sub_id="reverse"
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Pasting an image (Ctrl+V) while in the editor saves it to
<deck>/images/Pasted-image-YYYYMMDD-HHmmss.png via arboard (reads the
OS clipboard directly, bypassing webkit2gtk's empty clipboardData) and
appends #img("stem") to the active card segment.
New /images route shows all images in the deck's images/ directory as
base64-encoded thumbnails. Each tile has Copy (#img snippet to
clipboard) and Rename (renames file + updates all references in
cards.typ). Click any thumbnail to open a full-size lightbox; click
outside to dismiss.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Load sidecar rects for all cards at render time; display them as static
(non-interactive) yellow rects on inactive cards, mirroring how text
blank highlights are shown for all cards regardless of active state.
- Click to select a sidecar image rect (blue border, grab cursor, × delete, edge handles)
- Click away (SVG background) to deselect
- Drag selected rect body to move it; drag edge handles to resize
- Newly drawn image rects auto-select after creation
- Selected rect always renders on top of intersecting rects
- Eat post-drag click via capture-phase one-shot listener to prevent spurious deselection on fast drags or when releasing over another rect
- Edge handle onclick stops propagation so drag-end clicks don't deselect
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove CardKind::ImgCloze as a separate card type. Images now live
inside regular #cloze card bodies via #img("name") or native #image().
Drawn rectangles over image elements are stored in the sidecar under
CardSchedule::Cloze { rects: Vec<RectEntry> } where RectEntry gains a
src field identifying which image the rect belongs to.
tala-typst: collect image element bounding boxes (image_boxes) from the
rendered frame tree and return them in RenderResult.
tala/main.rs: dispatch draw-mode mouseup to sidecar when the drawn rect
overlaps an image box rather than text; display saved rects as yellow
SVG overlays on the active card using image-local → page-space mapping.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Segmentation is now purely blank-line based (no typst AST spans), eliminating
the data-loss bug where unclosed brackets bloated spans to EOF
- Each card fragment compiled independently by typst — a broken card cannot
affect rendering of adjacent cards
- Removed active_card_start/end, active_seg_key, n_segs_sig signals and the
two-effect complexity they required; splice bounds computed inline from
make_segments (fast prefix scan, no typst calls)
- Typing \n\n in a textarea auto-splits into a new segment and moves focus there
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
paragraph-based segmentation (\n\n) of source text into cards, even assuming some of the card texts are malformed
- Move body flex layout to #main (Dioxus desktop mount point) so
#editor has a bounded height to flex against
- Add min-height:0 to .card-list and flex-shrink:0 to .card-row so
overflow-y:auto triggers instead of squishing cards
- Move textarea above preview within each card row so re-renders don't
shift the editing surface
- Focus textarea in onmounted JS so arrow-key boundary navigation
places the cursor in the adjacent card's textarea
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace single-textarea + split layout with a vertically stacked card
list. Each card row shows its rendered preview at all times; the active
card also shows a source textarea below its preview.
- Per-card rendering: extract each card fragment, render independently
via a single use_resource that re-runs on source change (300ms debounce)
- Active card state: click to focus; Up/Down at textarea boundary moves
to adjacent card (JS cursor-position check before nav)
- oninput splices the active card fragment back into the full source
string so save logic and parse positions stay consistent
- Draw mode: draw-capture overlay only on active card; blank insertion
operates on the card fragment then splices into full source
- PreviewData now derives Clone; DIVIDER_DRAG_JS removed
- CSS: #editor is now flex-column; card-list replaces preview-pane;
card-row / card-row.active / card-source / card-preview-wrap rules added
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- tala-format: collapse 4 nested if chains into && let chains
- tala-typst: loop+match -> while let; suppress too_many_arguments on
frame walkers (context struct adds indirection without clarity gain);
remove dead frag_end variable and its let _ = frag_end suppress hack
- tala: &PathBuf -> &Path on persist_dir and render_preview; add Path
import; 4 redundant || Vec::new() closures -> Vec::new fn pointers
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Save chosen dir to ~/.config/tala/dir on change; load it on startup
with priority: CLI arg > saved config > cwd.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace temp_dir() with OnceLock-backed CARD_DIR; CLI arg or cwd
- OnceLock later promoted to GlobalSignal for runtime mutability
- Home page: "Choose folder" button via rfd native GTK dialog
- Editor: load cards.typ on mount, autosave (1s debounce), save status
- img/img_cloze preambles drop hardcoded .png; World::file() probes
extensions when path has none (png, jpg, jpeg, svg, gif, webp)
- nav-dir, save-status, dir-path CSS added
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
In webkit2gtk, element_coordinates() (offsetX/Y) are in zoomed visual
pixels while offsetWidth/Height are in logical pixels. Dividing by zoom
before normalizing corrects the mismatch. Previous attempts using
clientX+BCR or offsetWidth alone both failed due to webkit2gtk's
inconsistent coordinate spaces.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Coordinate bug: `getBoundingClientRect().width/height` is multiplied by
the CSS zoom factor, but `element_coordinates()` (offsetX/Y) is not.
Dividing offset coords by BCR dims gave nx / zoom instead of nx. Fix:
fetch `offsetWidth` / `offsetHeight` from JS instead.
Refactored into `normalize_draw_coords()` for testability. Added 15
unit tests covering coordinate normalization (incl. zoom-invariant
invariant), rects_overlap, insert_blank_wrap, insert_blank_wrap_math,
and expand_math_selection edge cases.
Tagged v0.0.1 at prior HEAD before this fix.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Expose math_spans in RenderResult and PreviewData
- insert_blank_wrap_math: splits $A B C$ → $A$ #blank[$B$] $C$
- expand_math_selection: extends glyph-based range to token/paren boundaries,
fixing e^(-x^2) missing ) and sqrt(pi) capturing only 's'
- blank_boxes from glyph_map + frame shapes: vinculum shape fills in right
edge of sqrt that π glyph can't provide (no source span)
- Per-glyph font metrics (ttf_parser::glyph_bounding_box) replace 0.8*fs
cap-height approximation, fixing vertical position of sqrt blank box
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Card identity is now an app-layer concern. CardEntry.id removed from
tala-format; tala-cli uses position index as sidecar key. Preamble
#let definitions drop id: params. Card syntax is now:
#card[front][back] #cloze[body] #img_cloze(src: "x")
Also fix insert_blank_wrap to strip whitespace from the selected range
before inserting into #blank[], keeping spaces outside:
" is " -> " #blank[is] "
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Walk all fragment glyphs into RenderResult.glyph_map with per-character
source ranges (using glyph.span.1 byte offset, not the whole span node)
and an is_in_math flag derived from Equation node ranges in the syntax tree.
On mouseup in draw mode, find overlapping glyphs, reject math regions with
an inline error, otherwise splice #blank[text] directly into the source signal.
Error boxes (red) shown for failed draws; cleared on draw mode toggle.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove typst-rendered yellow highlight from #blank[]; blanks now render
as plain text in the authoring preamble
- Add frame walker in tala-typst: after compilation, recurse the layout
frame tree matching per-glyph source spans against blank content spans
to produce pixel-space bounding boxes (blank_boxes in RenderResult)
- tala-typst render() gains blank_spans parameter; update example and tests
- Add blank_boxes_located test: verifies 2 non-zero boxes found for 2-blank fixture
- Editor parses source via tala-format to extract blank content spans,
passes them to render(), normalizes boxes to [0,1] in PreviewData
- SVG overlay on preview image: yellow semi-transparent rects for existing
blanks, blue rects for user-drawn boxes, dashed live drag preview
- Draw Cloze toggle button; draw-capture div handles click-drag-release;
IoU matching identifies which existing blank a drawn box targets
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add Home/Editor/Settings routes under a persistent navbar shell.
Inject Ctrl+/-/0 zoom via sessionStorage-backed JS. Highlight
#blank[] content with yellow box in authoring preamble.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Split-pane editor: textarea on left, debounced typst render on right.
Draggable divider via JS CSS variable. Render errors shown inline.
Adds tala-typst, image, base64, tokio deps to tala crate.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Splits crates/tala/src/editor.rs into focused submodules:
blank_wrap, draw_ops, format, image_paste, render, segments, sidecar_ops.
Fixes a data-loss bug in format_card_frag: when the user deletes the
opening `#blank[` but leaves the closing `]`, parse_card_structure finds
one balanced block ending at that orphaned `]`, leaving trailing content
(e.g. `ghi`) unaccounted for. The formatter then reconstructed the card
from only the parsed block, silently dropping the suffix. Fix: bail out
of formatting when non-whitespace content follows the last block's `]`.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ArrowDown/Up at segment boundary now lands cursor on the correct line
and column of the destination card (first line for Down, last for Up)
instead of always placing at the end.
Uses a nav_cursor signal to pass (col, going_up) from on_keydown to
onmounted, avoiding the previous setTimeout race with Dioxus re-render.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- expand_math_selection: balance typst string literals (`"..."`) — an odd
quote count in the selection now advances end to include the closing `"`
- insert_blank_wrap_math: for display math (`$ ... $`), keep the blank
inside the single block (`$ left #blank[$sel$] right $`) instead of
splitting into two block-level math elements that render on separate lines
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Cards now carry an `id:` argument in the typst source, generated as a
13-character ATProto-compatible TID (microsecond timestamp + 10-bit
counter). `assign_ids` splices missing IDs into source on first open;
`ensure_card_ids` also remaps sidecar keys from positional ("0","1",...)
to the embedded IDs atomically. All card key lookups in the editor and
review queue now use the embedded ID, with positional fallback.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a color picker in Settings (persisted to ~/.config/tala/cloze_color)
that controls the SVG highlight color for cloze blanks and image rects in
the editor. Also collapses the duplicate active/inactive sidecar rect
rendering branches into a single loop with is_active gating interactivity.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
blank_boxes is now Vec<Vec<[f32; 4]>> where each inner vec has one merged
rect per rendered text line. cluster_into_lines groups glyphs by y-center
proximity (60% line-height threshold) so blanks wrapping across lines get
correct per-line highlight rectangles in the editor overlay.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Stats page (/stats):
- Counts total review items, new (no schedule), due today from sidecar
- 14-day forecast bar chart, today bar highlighted
- tala-srs: add pub date_offset(days) helper
Editor fixes:
- is_card_frag: recognize #card\n[ and #cloze\n[ (multiline formatted)
- strip_head_whitespace: normalize head\n[ -> head[ before typst render
so content blocks attach correctly; adjusts blank spans accordingly
- format_card_frag: no longer emits \n before first [, preventing
auto-format from producing the broken multiline form going forward
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Each cloze ReviewItem already had an independent sub_id ("b0", "b1", …).
The renderer was ignoring it and blanking everything. Now ReviewCloze
carries the target blank index and the Typst preamble uses a counter to
hide only that blank, showing all others.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Images: add Delete button per tile with confirm dialog; warns if N
cards reference the image via extract_img_names
- Editor: replace static "no cards" message with a live textarea so
new cards can be typed directly into the empty state
- Editor: fix ghost character reappearing after tab switch — on_input
was early-returning on empty value, leaving stale content in source
that onmounted would restore on next mount
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Editor: tag chips filter visible card segments (display:none preserves indices)
- Review: inline filter bar + Restart button available during active session
- Factored collect_all_tags_from_source for reactive derivation from live source signal
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Review setup screen with tag chip filter (OR/AND mode)
- Skip button and S key moves card to back of queue; terminates when all remaining are skipped
- CLAUDE.md rewritten to reflect current codebase (removed img_cloze/id field, added all GUI components, corrected Sidecar API and Preamble variants)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
build_review_queue now iterates sidecar rects (Vec<RectEntry>) in addition
to text blanks. Each due rect produces a ReviewItem with kind ImageRect {src, rect}.
Question render blacks out the rect region in the PNG by overwriting
pixels at the image box coordinates. Answer shows the card unmodified.
apply_review_schedule handles the rect-{id} sub_id by updating the
inline schedule on the matching RectEntry.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Route /review with navbar link
- Builds due queue from cards.typ + sidecar (same logic as tala-cli)
- Renders question with ReviewFront/ReviewBack/ReviewCloze preamble
- Reveals answer with Authoring preamble on Space/Enter or button
- Grades 1-4 (Again/Hard/Good/Easy) via button or keyboard
- Saves sidecar after each card; no git commit from GUI
- Added Preamble::ReviewBack to tala-typst for bi-directional reverse review
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ImageTile rename now uses the original file extension instead of hardcoding .png
- tala-cli review: bi-directional cards enqueue a "reverse" item (back->front)
in addition to "forward"; apply_schedule and print_card_preview handle sub_id="reverse"
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Pasting an image (Ctrl+V) while in the editor saves it to
<deck>/images/Pasted-image-YYYYMMDD-HHmmss.png via arboard (reads the
OS clipboard directly, bypassing webkit2gtk's empty clipboardData) and
appends #img("stem") to the active card segment.
New /images route shows all images in the deck's images/ directory as
base64-encoded thumbnails. Each tile has Copy (#img snippet to
clipboard) and Rename (renames file + updates all references in
cards.typ). Click any thumbnail to open a full-size lightbox; click
outside to dismiss.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Click to select a sidecar image rect (blue border, grab cursor, × delete, edge handles)
- Click away (SVG background) to deselect
- Drag selected rect body to move it; drag edge handles to resize
- Newly drawn image rects auto-select after creation
- Selected rect always renders on top of intersecting rects
- Eat post-drag click via capture-phase one-shot listener to prevent spurious deselection on fast drags or when releasing over another rect
- Edge handle onclick stops propagation so drag-end clicks don't deselect
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove CardKind::ImgCloze as a separate card type. Images now live
inside regular #cloze card bodies via #img("name") or native #image().
Drawn rectangles over image elements are stored in the sidecar under
CardSchedule::Cloze { rects: Vec<RectEntry> } where RectEntry gains a
src field identifying which image the rect belongs to.
tala-typst: collect image element bounding boxes (image_boxes) from the
rendered frame tree and return them in RenderResult.
tala/main.rs: dispatch draw-mode mouseup to sidecar when the drawn rect
overlaps an image box rather than text; display saved rects as yellow
SVG overlays on the active card using image-local → page-space mapping.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Segmentation is now purely blank-line based (no typst AST spans), eliminating
the data-loss bug where unclosed brackets bloated spans to EOF
- Each card fragment compiled independently by typst — a broken card cannot
affect rendering of adjacent cards
- Removed active_card_start/end, active_seg_key, n_segs_sig signals and the
two-effect complexity they required; splice bounds computed inline from
make_segments (fast prefix scan, no typst calls)
- Typing \n\n in a textarea auto-splits into a new segment and moves focus there
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Move body flex layout to #main (Dioxus desktop mount point) so
#editor has a bounded height to flex against
- Add min-height:0 to .card-list and flex-shrink:0 to .card-row so
overflow-y:auto triggers instead of squishing cards
- Move textarea above preview within each card row so re-renders don't
shift the editing surface
- Focus textarea in onmounted JS so arrow-key boundary navigation
places the cursor in the adjacent card's textarea
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace single-textarea + split layout with a vertically stacked card
list. Each card row shows its rendered preview at all times; the active
card also shows a source textarea below its preview.
- Per-card rendering: extract each card fragment, render independently
via a single use_resource that re-runs on source change (300ms debounce)
- Active card state: click to focus; Up/Down at textarea boundary moves
to adjacent card (JS cursor-position check before nav)
- oninput splices the active card fragment back into the full source
string so save logic and parse positions stay consistent
- Draw mode: draw-capture overlay only on active card; blank insertion
operates on the card fragment then splices into full source
- PreviewData now derives Clone; DIVIDER_DRAG_JS removed
- CSS: #editor is now flex-column; card-list replaces preview-pane;
card-row / card-row.active / card-source / card-preview-wrap rules added
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- tala-format: collapse 4 nested if chains into && let chains
- tala-typst: loop+match -> while let; suppress too_many_arguments on
frame walkers (context struct adds indirection without clarity gain);
remove dead frag_end variable and its let _ = frag_end suppress hack
- tala: &PathBuf -> &Path on persist_dir and render_preview; add Path
import; 4 redundant || Vec::new() closures -> Vec::new fn pointers
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace temp_dir() with OnceLock-backed CARD_DIR; CLI arg or cwd
- OnceLock later promoted to GlobalSignal for runtime mutability
- Home page: "Choose folder" button via rfd native GTK dialog
- Editor: load cards.typ on mount, autosave (1s debounce), save status
- img/img_cloze preambles drop hardcoded .png; World::file() probes
extensions when path has none (png, jpg, jpeg, svg, gif, webp)
- nav-dir, save-status, dir-path CSS added
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
In webkit2gtk, element_coordinates() (offsetX/Y) are in zoomed visual
pixels while offsetWidth/Height are in logical pixels. Dividing by zoom
before normalizing corrects the mismatch. Previous attempts using
clientX+BCR or offsetWidth alone both failed due to webkit2gtk's
inconsistent coordinate spaces.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Coordinate bug: `getBoundingClientRect().width/height` is multiplied by
the CSS zoom factor, but `element_coordinates()` (offsetX/Y) is not.
Dividing offset coords by BCR dims gave nx / zoom instead of nx. Fix:
fetch `offsetWidth` / `offsetHeight` from JS instead.
Refactored into `normalize_draw_coords()` for testability. Added 15
unit tests covering coordinate normalization (incl. zoom-invariant
invariant), rects_overlap, insert_blank_wrap, insert_blank_wrap_math,
and expand_math_selection edge cases.
Tagged v0.0.1 at prior HEAD before this fix.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Expose math_spans in RenderResult and PreviewData
- insert_blank_wrap_math: splits $A B C$ → $A$ #blank[$B$] $C$
- expand_math_selection: extends glyph-based range to token/paren boundaries,
fixing e^(-x^2) missing ) and sqrt(pi) capturing only 's'
- blank_boxes from glyph_map + frame shapes: vinculum shape fills in right
edge of sqrt that π glyph can't provide (no source span)
- Per-glyph font metrics (ttf_parser::glyph_bounding_box) replace 0.8*fs
cap-height approximation, fixing vertical position of sqrt blank box
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Card identity is now an app-layer concern. CardEntry.id removed from
tala-format; tala-cli uses position index as sidecar key. Preamble
#let definitions drop id: params. Card syntax is now:
#card[front][back] #cloze[body] #img_cloze(src: "x")
Also fix insert_blank_wrap to strip whitespace from the selected range
before inserting into #blank[], keeping spaces outside:
" is " -> " #blank[is] "
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Walk all fragment glyphs into RenderResult.glyph_map with per-character
source ranges (using glyph.span.1 byte offset, not the whole span node)
and an is_in_math flag derived from Equation node ranges in the syntax tree.
On mouseup in draw mode, find overlapping glyphs, reject math regions with
an inline error, otherwise splice #blank[text] directly into the source signal.
Error boxes (red) shown for failed draws; cleared on draw mode toggle.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove typst-rendered yellow highlight from #blank[]; blanks now render
as plain text in the authoring preamble
- Add frame walker in tala-typst: after compilation, recurse the layout
frame tree matching per-glyph source spans against blank content spans
to produce pixel-space bounding boxes (blank_boxes in RenderResult)
- tala-typst render() gains blank_spans parameter; update example and tests
- Add blank_boxes_located test: verifies 2 non-zero boxes found for 2-blank fixture
- Editor parses source via tala-format to extract blank content spans,
passes them to render(), normalizes boxes to [0,1] in PreviewData
- SVG overlay on preview image: yellow semi-transparent rects for existing
blanks, blue rects for user-drawn boxes, dashed live drag preview
- Draw Cloze toggle button; draw-capture div handles click-drag-release;
IoU matching identifies which existing blank a drawn box targets
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>