aboutsummaryrefslogtreecommitdiff
Commit message (Collapse)AuthorAgeFilesLines
* refactor: delete commented-out function bodiesCraig Jennings2026-05-051-58/+0
| | | | | | | | | | | | | | Three large dead-code blocks removed: - 16-line commented-out org-drill-entry-due-p (replaced by current implementation that takes a session arg) - 17-line commented-out org-drill-hide-all-subheadings-except (the body was a placeholder docstring + commented body; the real function lives elsewhere now) - 25-line block of commented-out alternative org-drill-add-cloze-fontification + add-hook XXX commentary Pure deletion, no behavior change. Version control has the history.
* build: bump Org dep to 9.6 to match unguarded org-fold-* callsCraig Jennings2026-05-053-2/+46
| | | | | | | | | | | | | | | | | org-drill calls org-fold-show-entry and org-fold-show-subtree from seven sites without fboundp guards. Both functions arrived in Org 9.6. But the package declared org 9.3 (Package-Requires) / org 9.2 (Cask), so users on older Org would silently void-function at runtime instead of getting a clear install-time mismatch error. Bumped both declarations to org 9.6. Wrapping each of the seven call sites with fboundp would be the alternative, but Org 9.6 was released October 2022 — three-and-a-half years ago — and we already have a follow-up TODO to drop the legacy time-to-inactive fallback that this version bump unblocks. Two tests verify the declared dep and that the org-fold APIs are actually bound on the running Org version.
* fix: keep collection scan alive when one entry errors (upstream #53)Craig Jennings2026-05-053-24/+137
| | | | | | | | | | | | | | | | | | | | | | User reported that running org-drill on a buffer with a new (no-ID) entry threw 'Wrong Type Argument: hash-table-p, nil' and stopped the scan — every subsequent entry was silently skipped, so the user had to re-run org-drill once per item (10 items meant 10 invocations). The exact source of the hash-table error is environment-dependent (Emacs version, Org version, lazy org-id-locations init, Doom overrides), so this fix targets the user-visible failure mode instead of the underlying triggering condition. Wrapped the per-entry body of org-drill-map-entry-function in condition-case. An error on one entry now logs a 'skipping' message and the scan continues to the next entry. The session collects all the well-formed items, and the user can re-run drill once total to process them — no more once-per-item. Two regression tests: one verifies the resilience behavior directly (fail entry 1, scan continues to entry 2), the other documents the ID-creation-with-uninitialized-locations scenario as a smoke check.
* fix: keep cloze regex within a single line (upstream #38)Craig Jennings2026-05-052-2/+66
| | | | | | | | | | | | The inner match was [[:cntrl:][:graph:][:space:]]+?, which silently includes newline. A stray [ could match all the way to a ] several lines later, covering org headings in between with the visible-cloze face. Reporter saw lines 4 and 5 of test.org lose their org-level-N face and use default instead. Switched the inner class to [^\n]+?. Clozes now stay within a single line, which matches the design intent and stops the face bleed. Three new tests cover the regression.
* fix: skip LaTeX preview on TTY frames (upstream #44)Craig Jennings2026-05-052-10/+67
| | | | | | | | | | | | | | | | | Issue #44 (2021): running org-drill in a TTY emacsclient (the reporter mentioned tmux) raised "Window system frame should be used" because LaTeX preview helpers (org-latex-preview, org--latex-preview-region) require a window system and weren't guarded. Wrapped both call sites with (when (display-graphic-p) ...). - org-drill--show-latex-fragments: now a silent no-op on TTY - present-default-answer's clear-and-preview block: same guard LaTeX previews are inherently graphical. The right behavior on TTY is to skip the preview rather than crash the session — TTY users still see the underlying source text just fine.
* fix: clear stale end-pos on resume so final-report fires (upstream #33)Craig Jennings2026-05-052-0/+64
| | | | | | | | | | | | | | | | When a user interrupted a drill session to edit or capture, the session's end-pos slot got set to a marker (or :quit). The end-of- org-drill cond branched on end-pos: if set, show resume message and skip org-drill-final-report. That worked for the first interruption. But on org-drill-resume, the session was reused with end-pos still carrying the prior marker. Even when the resumed session completed normally, the same cond branch fired again — silently skipping final-report. Clear end-pos at the top of org-drill when resume-p is non-nil, per Markus's proposed patch on the upstream issue. The resumed session can now reach the final-report branch.
* fix: restore display state in the buffer setup ran inCraig Jennings2026-05-052-20/+93
| | | | | | | | | | | | | | | | | | org-drill--setup-display saved buffer-local state (mode-line, variable-pitch-mode) into global defvars and called setq-local on the current buffer. org-drill--restore-display read those globals and ran setq-local against whatever buffer happened to be current at restore time. If the user switched buffers mid-session, the restore wrote to the wrong buffer — leaving the original drill buffer's mode-line still hidden and trampling the destination buffer's mode-line with whatever was saved from elsewhere. Captured the buffer at setup in org-drill--saved-display-buffer. Restore now wraps mode-line and variable-pitch restoration in with-current-buffer against that saved buffer. Text-scale stays global (the underlying face attribute is process-wide).
* fix: recover from corrupted persist file at package load (upstream #45)Craig Jennings2026-05-052-3/+65
| | | | | | | | | | | | | Issue #45 (2021): persist-load raised End of file during parsing at persist.el:413 in some configurations, likely from a corrupted persist data file. Pre-fix, this propagated up through the top-level (persist-defvar org-drill-sm5-optimal-factor-matrix ...) form at file-load time and broke the entire package's load. Wrapped the persist-defvar form in condition-case. On failure, the matrix falls back to a fresh nil binding via plain defvar, and a message tells the user what happened. org-drill continues to load normally.
* fix: guard org-drill-again and org-drill-resume against nil last-sessionCraig Jennings2026-05-052-0/+43
| | | | | | | | | | | | Both functions bound session to org-drill-last-session and immediately called setf / org-drill-entries-pending-p on it without checking for nil. First-time invocation (or after Emacs restart with no active session) threw an obscure eieio-oset / nil-slot type error instead of a clear message. Added (unless session (user-error ...)) at the top of each function. A user running M-x org-drill-resume cold now sees a sensible message telling them to run org-drill first.
* fix: hide-drawers ignores drawers with no :END:Craig Jennings2026-05-052-3/+76
| | | | | | | | | | | | | drawer-end was captured as (save-excursion (re-search-forward ':END:' end t) (point)) which always returns a number — (point) is always defined. The subsequent (when drawer-end ...) guard was dead, so a malformed drawer (typo in :END:, mid-edit truncation) ended up with a junk overlay covering whatever range point happened to land in. Captured the search result itself and gate on it. Malformed drawers are now skipped silently; well-formed drawers still get their normal overlay.
* fix: default DRILL_LEITNER_BOX to 0 in leitner-reboxCraig Jennings2026-05-052-1/+49
| | | | | | | | | | | When the property is absent, org-entry-get returns nil and string-to-number errors with wrong-type-argument. Reachable when a user removes the property mid-session, or when a Leitner-tagged entry is rebox'd before its DRILL_LEITNER_BOX has been set. Wrapped the org-entry-get with (or ... "0"). Box 0 makes the rating semantics still sensible: a downgrade stays at 0, a promotion goes to 1.
* fix: drop dead translate_number entry from card-type alist (upstream #43)Craig Jennings2026-05-052-2/+48
| | | | | | | | | | | | | | | | | | | The card-type alist mapped translate_number to a function that no longer exists in the file. Cards with DRILL_CARD_TYPE: translate_number crashed with void-function during drill instead of being skipped. Reporter (issue #43, 2021) said they had old decks using the documented translate_number type and were getting the crash on restore. The function was apparently removed at some point without clearing the alist entry. Removed the alist entry so entry-f's no-presentation-fn branch fires and returns skip after messaging the user. Legacy decks now degrade gracefully instead of crashing the session. Tests in tests/test-org-drill-translate-number-regression.el lock the behavior in (entry-f returns skip on translate_number, alist no longer carries the entry).
* test: org-drill-entry-f per-card dispatcherCraig Jennings2026-05-051-0/+102
| | | | | | | | | | | 5 ERT tests for the per-entry dispatcher: - unknown DRILL_CARD_TYPE returns 'skip and doesn't call answer-fn - presenter returns nil (quit) → entry-f returns nil - presenter returns 'edit → propagates unchanged - presenter returns 'skip → propagates unchanged - presenter returns t (successful) → default answer presenter runs and the complete-func (reschedule) is invoked with the session
* test: org-drill-entries main loop queue-routing logicCraig Jennings2026-05-051-0/+130
| | | | | | | | | | | | | | 5 ERT tests for the session loop body: - quit return (nil) sets end-pos = :quit and exits the loop - edit return ('edit) sets end-pos to a marker and exits - passing rating (>failure-quality) routes marker to done-entries - failing rating (<= failure-quality) skips done-entries - skip return clears current-item without queueing Tests use a tempfile-backed buffer because pop-next-pending-entry calls org-drill-entry-p on each marker, which requires real org buffer state.
* test: replace-multi, map-entry-function, sm2/simple8 schedulersCraig Jennings2026-05-051-0/+167
| | | | | | | | | | | | | 9 ERT tests filling small gaps in coverage: - replace-entry-text-multi: N replacements → N overlays, each showing the matching string via display prop - map-entry-function: virgin entry → new-entries, future-scheduled entry → dormant-entry-count (not new), non-drill skipped - smart-reschedule with org-drill-spaced-repetition-algorithm bound to sm2 and simple8 (default tests covered sm5) - smart-reschedule with DRILL_CARD_WEIGHT - entries-pending-p: overdue queue alone keeps session pending
* test: prompt-for-string, leitner-capture, and resume coverageCraig Jennings2026-05-051-0/+143
| | | | | | | | | | | 7 ERT tests covering the last batch of testable smaller helpers: - presentation-prompt-for-string: stores typed answer in session->drill-answer, uses default prompt when arg is nil - map-leitner-capture: unboxed entry goes to unboxed list, box-3 entry goes to boxed list, box>5 (graduated) skipped, non-drill entry silently skipped - org-drill-resume: with pending entries, calls org-drill resume-p=t
* test: final-report message format and warning-branch zero-guardCraig Jennings2026-05-051-0/+162
| | | | | | | | | | | | 6 ERT tests covering org-drill-final-report: - Reviewed-count from done-entries appears in the message - Pending-queue line lists per-queue counts (1 new, 2 young, etc.) - 100% pass rate doesn't trigger the WARNING branch - Below forgetting-index pass rate triggers the warning prompt - Per-quality counts produce correct percentages (1/4 = 25%) - Warning-branch with zero dormant+due survives (locks in the zero-divisor guard fix)
* fix: guard zero-divisor in org-drill-final-report overdue percentageCraig Jennings2026-05-051-2/+7
| | | | | | | | | | | | The warning branch divided 100*overdue by (dormant+due) without guarding the denominator. When both counts are zero — degenerate scopes (cram with no items collected, pure-failure session on empty queues) — the call hit arith-error before the warning even rendered. Wrapped the divisor with (max 1 ...). In the zero case the percentage reads as 0% rather than crashing the session wrap-up. Resolves a long-standing pre-existing TODO entry.
* test: explain-text helpers and SM-or-Leitner dispatchCraig Jennings2026-05-051-0/+161
| | | | | | | | | | | | | 9 ERT tests covering: - get-explain-text: no :explain: parent returns existing-text, parent with :explain: tag adds its body, recursion stops at top-level outline depth - explain-answer-presenter: creates after-string overlay with Explanation: prefix, replaces prior overlay on second call - explain-cleaner: removes the overlay, no-op on missing overlay - sm-or-leitner: runs SM (via org-drill-again) when pending entries exceed leitner-completed, falls through to Leitner otherwise
* test: Spanish verb presenter and top-level drill commandsCraig Jennings2026-05-051-0/+151
| | | | | | | | | | | | | | 10 ERT tests: - present-spanish-verb (six-way cl-random branch): 0=present-translate, 2=past-translate, 4=future-perfect (mocked cl-random + presentation prompt) - org-drill-cram: passes cram=t to org-drill, accepts scope arg - org-drill-cram-tree: delegates to cram with scope=tree - org-drill-tree: passes scope=tree to org-drill - org-drill-directory: passes scope=directory - org-drill-again: resumes (resume-p=t) when prior session has pending entries, starts fresh otherwise
* test: language card presenters (verb conjugation, noun declension)Craig Jennings2026-05-051-0/+159
| | | | | | | | | | | | | 7 ERT tests covering the language-card presenters and answer-show helpers: - present-verb-conjugation: runs cleanly with all required VERB_ properties, formats tense+mood string when both set - show-answer-verb-conjugation: calls reschedule-fn - present-noun-declension: runs cleanly, includes definite/indefinite suffix when DECLINE_DEFINITE is set, skips suffix when neither extra property is present - show-answer-noun-declension: calls reschedule-fn
* test: card presenters with mocked presentation-promptCraig Jennings2026-05-051-0/+210
| | | | | | | | | | | | | 9 ERT tests covering the per-card-type presentation functions: - present-simple-card: clozes hidden during prompt, return value passthrough, overlays cleaned up after via with-hidden-cloze-text - present-default-answer: drill-answer slot path (overlay-displays the answer), unhide path (reveals body, calls reschedule-fn) - present-card-using-text: replaces body with question, sets drill-answer slot when ANSWER arg provided - present-two-sided-card and present-multi-sided-card: run cleanly on 2-side and 3-side cards
* test: add multicloze hide-n and hide-nth coverageCraig Jennings2026-05-051-0/+163
| | | | | | | | | | | | | 9 ERT tests with org-drill-presentation-prompt mocked to bypass interactive prompts: - hide-n with positive N: hides exactly N cloze overlays, no-op when buffer has no cloze - hide-n with negative N (show-mode): hides total-N pieces, leaving abs(N) visible - force-show-first + force-hide-first → user-visible error - hide-nth: hides only the Nth piece, no-op when out of range, negative N counts from the end
* test: presentation-prompt return values and dispatcherCraig Jennings2026-05-051-0/+125
| | | | | | | | | | | | 7 ERT tests covering the card-prompt return-value contract: - org-drill-presentation-prompt-in-mini-buffer with input-pending-p and read-key-sequence both mocked: quit → nil, edit → 'edit, skip → 'skip, any-other-key → t - explicit PROMPT arg appears in the formatted full-prompt - org-drill-presentation-prompt dispatcher routes by org-drill-presentation-prompt-with-typing (nil → mini-buffer variant, non-nil → in-buffer variant)
* test: add multicloze weighted-presenter dispatch coverageCraig Jennings2026-05-051-0/+143
| | | | | | | | | | | | | | | | | 8 ERT tests covering hide1-firstmore, show1-lastmore, show1-firstless. Each wraps a cond that selects between common and uncommon multicloze presenters based on org-drill-cloze-text-weight and the entry's total-repeats counter. Underlying presenter functions are mocked to no-op stubs that record which one was selected — the branch logic is what's under test, not the (interactive) cloze-prompt itself. Cases covered per function: - nil weight → fall back to non-weighted variant - invalid weight (non-positive int) → error - non-trigger rep → common path (hide-first / show-last / skip-first) - trigger rep → uncommon path (hide-n with appropriate force flags)
* test: add coverage for org-drill-reschedule rating loopCraig Jennings2026-05-051-0/+143
| | | | | | | | | | | | 10 ERT tests covering the rating function (read-key-sequence mocked): - Quality 0/3/5 each returns the integer rating - Quit key returns nil, edit key returns 'edit - Successful rating pushes quality onto session->qualities - Non-cram rating sets a SCHEDULED stamp via smart-reschedule - Cram mode skips the reschedule (no SCHEDULED set) - Failure with >= leech-failure-threshold tags entry :leech: - Failure under threshold doesn't tag :leech:
* fix: remove stray [debug] message in org-drill-entriesCraig Jennings2026-05-051-1/+0
| | | | | | | Every drilled card was logging "[debug] org-drill: at marker position N" to *Messages* and flashing it in the minibuffer. Pure noise — the print statement was clearly a leftover from diagnostic work that never got cleaned up. Delete it.
* test: add navigation, key-binding, push-end, and leitner-rebox coverageCraig Jennings2026-05-051-0/+191
| | | | | | | | | | | | | | 15 ERT tests covering: - org-drill-goto-entry: marker → buffer + position - org-drill-goto-drill-entry-heading: stays put on the drill heading, walks up from a child sub-heading, errors outside any drill entry - org-drill-command-keybinding-to-string: nil for unbound, string for bound commands - org-drill-push-end: appends to non-empty and empty lists - org-drill-leitner-rebox (interactive — read-key-sequence mocked): rating 0 resets to box 1, rating 1 decrements (with floor at 1), rating 2 stays, ratings 3-5 promote, quit-key returns 'quit
* test: add minibuffer prompt, relearn-item, and progress-message coverageCraig Jennings2026-05-051-0/+145
| | | | | | | | | | | 10 ERT tests covering: - org-drill--make-minibuffer-prompt: status char (N/Y/o/!/F), cram-mode shows C, done-entries count, prompt-text passthrough - org-drill-relearn-item: resets DRILL_LAST_INTERVAL to 0, unschedules the entry (days-ahead = 0 path through smart-reschedule) - org-drill-progress-message: emits on multiples of 50, silent otherwise, includes the COLLECTED count
* test: add coverage for org-drill-smart-rescheduleCraig Jennings2026-05-051-0/+119
| | | | | | | | | | | | | | 6 ERT tests covering all four days-ahead branches: - 0 → unschedule (treat as new again) - negative → schedule today (current-time) - positive → schedule N days ahead - nil → use the algorithm-computed next-interval (locks in the numberp guard fix) Plus property side-effects: writes DRILL_LAST_INTERVAL / EASE / TOTAL_REPEATS via store-item-data, and TOTAL_REPEATS increments on each call.
* fix: guard org-drill-smart-reschedule cond against nil days-aheadCraig Jennings2026-05-051-5/+10
| | | | | | | | | | | | | | | | The function takes `days-ahead' as &optional, but the schedule cond called `(= 0 days-ahead)' and `(cl-minusp days-ahead)' before any type-guard, so passing nil crashed with a wrong-type-argument error. Today's two callers (the rating-confirmation flow and the org-drill-relearn-item helper) always pass a number, so this was latent — but a third caller relying on the documented &optional shape would hit it immediately. Switched the cond to require numberp before the value comparisons, and the default branch now falls back to the algorithm-computed next-interval when days-ahead is nil. That matches the intent implied by the optional signature and the docstring.
* test: queue popping, fontification, ID creation, strip-all-dataCraig Jennings2026-05-051-0/+219
| | | | | | | | | | | | | | | | | 12 ERT tests covering: - org-drill-pop-next-pending-entry: empty session → nil, failed prioritized over new/old, again-entries fallback, max-item limit gates primary queues but again-entries bypasses - org-drill-card-tag-caller: dispatches per-tag hook fn from alist, unknown tag is silent no-op (falls through to ignore) - org-drill-id-get-create-with-warning: creates ID and flips warned-about-id-creation flag, doesn't re-warn (uses tempfile- backed buffer because org-id-get requires file-visiting) - org-drill-add-cloze-fontification: sets buffer-local cloze-regexp and cloze-keywords from current delimiters - org-drill-strip-all-data: yes-or-no-p gate (no-confirm = no-op, confirm = wipes scheduling props)
* test: add explain-entry-p, end-of-entry-pos, and language card info coverageCraig Jennings2026-05-051-0/+176
| | | | | | | | | | | | | 14 ERT tests covering: - org-drill-explain-entry-p: with/without :explain: tag, no-inherit flag rejects parent's tag - org-drill-end-of-entry-pos: single-heading and multi-heading subtree bounds - org-drill-get-verb-conjugation-info: full property read, tense-only (mood optional), missing-required errors, tense-color highlight face - org-drill-get-noun-info: full property read, missing-required errors, feminine-gender orchid color from alist, unknown-gender red fallback
* test: add list-utility, hide-comments/drawers, and Leitner promotion coverageCraig Jennings2026-05-051-0/+175
| | | | | | | | | | | | | 17 ERT tests covering: - org-drill-swap: distinct indices, same-index no-op, end-to-start - org-drill-shuffle: preserves element multiset, empty list, singleton - org-drill-pop-random: removes-one, nil-on-empty, empties singleton - org-drill-hide-comments: per-line overlay, no-op on comment-free buffer - org-drill-hide-drawers: PROPERTIES drawer, multiple drawers, no-op on drawer-free entry - org-drill-leitner-promote: box-N → box-(N+1), graduation at box 5 (with and without org-drill-leitner-promote-to-drill-p flag)
* test: add entry-status, days-since-creation, and overdue ordering coverageCraig Jennings2026-05-052-2/+219
| | | | | | | | | | | | | | | | | | | | | | Plus a docs fix to org-drill-order-overdue-entries' header comment. 16 ERT tests covering: - org-drill-entry-status: non-drill nil, empty entry nil, virgin :new, future :future, low-quality :failed, due+short-interval :young, due+long-interval :old, very-overdue :overdue, skipped-leech :unscheduled, three-element return shape - org-drill-entry-days-since-creation: with DATE_ADDED, missing without flag (nil), missing with use-last-interval-p flag (overdue+interval) - org-drill-order-overdue-entries: empty stays empty, non-lapsed sorted by DUE desc, lapsed split (by DUE crossing threshold, not AGE) appearing after sorted by AGE desc Fixed misleading header comment at line 2888 — it claimed the lapse split was by AGE, but the code uses DUE (cl-second). This matches the semantic gate in org-drill--entry-lapsed-p, so the code was right and the comment was stale. Updated the comment to state the actual three-step sort.
* test: add session-state predicate coverageCraig Jennings2026-05-051-0/+265
| | | | | | | | | | | | | | | 23 ERT tests covering the queue control flow: - org-drill-entries-pending-p: empty session, current-item slot, again-entries bypassing limits, item-count limit interaction - org-drill-pending-entry-count: empty, sums all queues, current-item marker check - org-drill-maximum-duration-reached-p: nil-duration disables, cram bypasses, fresh session under limit, old session over limit - org-drill-maximum-item-count-reached-p: nil disables, cram bypasses, under/at limit, includes-failed-items-p flag - org-drill--entry-lapsed-p: feature flag gate, threshold respected - org-drill-free-markers: explicit list, t-frees-everything
* test: add overlay coverage for cloze hide/show and entry text replacementCraig Jennings2026-05-051-0/+222
| | | | | | | | | | | | | | | | | 19 ERT tests covering the buffer-overlay machinery behind card presentation: - org-drill-hide-region / unhide-text: bounds, optional display text, no-op on clean buffers, leaves unrelated overlays alone - org-drill-hide-clozed-text / unhide-clozed-text: hides every cloze span with org-drill-cloze-overlay-defaults category, clean round-trip - org-drill-hide-cloze-hints: hides only the ||hint portion when present, no-op when absent (locks in the production fix) - org-drill-replace-entry-text / unreplace-entry-text: covers entry body with placeholder string overlay - org-drill-get-entry-text: returns body text, strips text-properties by default
* fix: don't create zero-width overlay for hint-less clozesCraig Jennings2026-05-051-2/+8
| | | | | | | | | | | | | | | | org-drill-hide-cloze-hints checked (null (match-beginning 2)) to detect "no hint present," but the cloze regex's hint group is an empty-allowed alternation — the group always participates in the match, so match-beginning is always a position, never nil. For a card like "[Paris]" (no hint), the function fell through to org-drill-hide-region with start = end and made a zero-width overlay. Cosmetically harmless but accumulates one stray overlay per hint-less cloze. On a buffer with many such cards the tracking cost is real. Switched the guard to (= (match-beginning 2) (match-end 2)) — empty match. Found while writing tests; locked in by tests/test-org-drill-hide-show.el's test-org-drill-hide-cloze-hints-no-hint-no-overlay.
* test: add tests for due/overdue predicates and scope translationCraig Jennings2026-05-051-0/+257
| | | | | | | | | | | | | | | 27 ERT tests covering the predicates that decide whether a card appears in today's drill session: - org-drill-days-since-last-review / hours-since-last-review with current-time mocked for determinism - org-drill-entry-days-overdue: normal mode (scheduled future/past/now, leech skip), cram mode (recent vs stale review windows) - org-drill-entry-due-p: scheduled in past/future, non-drill, virgin - org-drill-entry-overdue-p: factor-based threshold across last-interval and days-overdue - org-drill-current-scope: file → nil, file-no-restriction → file, symbol passthrough
* test: add tests for cloze regex, hypothetical scheduling, and entry strippingCraig Jennings2026-05-051-0/+185
| | | | | | | | | | | | | | 15 ERT tests covering: - org-drill--compute-cloze-regexp: match default and custom delimiters, hint separator, three-capture-group structure for fontification - org-drill--compute-cloze-keywords: font-lock spec shape - org-drill-hypothetical-next-review-date: virgin-card scheduling, quality-monotonic next-interval, DRILL_CARD_WEIGHT damping - org-drill-hypothetical-next-review-dates: 6-element non-decreasing list driving the rating-prompt preview - org-drill-strip-entry-data: scheduling-property cleanup, no-op on virgin entry
* test: add round-trip tests for item-data save/loadCraig Jennings2026-05-051-0/+177
| | | | | | | | | | | | | | | 11 ERT tests covering org-drill-get-item-data and store-item-data. The user-facing contract: rate a card → state persists across sessions. Three branches tested: virgin item (zero-list sentinel), modern DRILL_* properties (read all six fields, partial-set falls back to defaults), and legacy LEARN_DATA backward compat (precedence over modern, graceful fallthrough on malformed data). Round-trip tests document a deliberate type quirk: rounded fields (interval, meanq, ease) come back as floats because org-drill-round-float returns float; counters (repeats, failures, total-repeats) stay int. Numerically lossless and scheduler-safe.
* test: add unit tests for entry-property accessorsCraig Jennings2026-05-051-0/+214
| | | | | | | | | | | 30 ERT tests covering org-drill-entry-last-quality, entry-failure-count, entry-average-quality, entry-last-interval, entry-repeats-since-fail, entry-total-repeats, entry-ease, entry-leech-p, and entry-new-p. Documents each function's missing-property fallback behavior — three distinct shapes: nil-by-default (last-quality, average-quality, ease), hardcoded-zero-by-default (failure-count, last-interval, repeats-since- fail, total-repeats), or computed from other state (new-p, leech-p).
* test: add direct unit tests for SM2/SM5 scheduler helpersCraig Jennings2026-05-051-0/+222
| | | | | | | | | | | | 35 ERT tests covering org-drill-round-float, org-drill-modify-e-factor, org-drill-modify-of, org-drill-set-optimal-factor, org-drill-initial-optimal-factor-sm5, org-drill-get-optimal-factor-sm5, org-drill-inter-repetition-interval-sm5, org-drill-early-interval-factor, org-drill-random-dispersal-factor, and org-drill--safe-read-learn-data. These helpers were exercised transitively by the existing top-level scheduler tests but had no direct unit coverage. Direct tests give faster feedback when a helper breaks and pin each helper's contract.
* chore: remove .gitlab-ci.ymlCraig Jennings2026-05-051-6/+0
| | | | | | The fork's primary remotes are git.cjennings.net (origin) and github.com/cjennings/org-drill (mirror). GitLab is upstream's tracker, not ours, and I'm not running the old `.gitlab-ci.yml` anywhere. Replacement is a GitHub Actions workflow, which I captured as a follow-up with sketches for matrix versions, caching, and stages. That's a meaningful design decision rather than a file move, so I'm leaving it for its own session.
* fix: include child subtree in entry-empty-p search bound (upstream #13)Craig Jennings2026-05-052-2/+143
| | | | | | | | | | | | kqr (2019-07-22) reported that drill entries whose answer lives inside a child sub-heading were silently skipped. Their example: a question in the heading text and the answer under `** The Answer`. The function returned t (empty) for such entries, so they never got presented during drill sessions. The cause is `(outline-next-heading)` in `org-drill-entry-empty-p`. That primitive lands on the first heading at any level, including children. So the search range was metadata-end up to the child's heading line, which excluded the child's body. Bodies that lived in child sub-headings never got searched. I switched the bound to `(org-end-of-subtree t t)`, which covers the whole subtree of the current heading and degrades gracefully at the last heading in the buffer. The reporter suggested `outline-forward-same-level`, but that primitive errors at the last sibling, which would be its own regression. `org-end-of-subtree` is the canonical Emacs idiom for this kind of bound and handles end-of-buffer correctly. I added `tests/test-org-drill-entry-empty-p.el` with 6 ERT tests across Normal, Boundary (kqr's exact fixture), and edge categories. The two regression tests fail at HEAD before the fix and pass after. One semantic note worth flagging: any subtree content now counts as non-empty, including bare child headings with no body of their own. The bug report is silent on that case and I expect it to be rare in practice. If anyone reports the new behavior as a regression, the fix would be to filter heading lines out of the graphical-character search.
* fix: preserve default-input-method during key reads (upstream #52, #58)Craig Jennings2026-05-052-5/+125
| | | | | | | | | | Two reports from breadncup (issue #52 in 2023, issue #58 in 2024) said that running an org-drill session silently nulled out their `default-input-method`. The reproduction is exact: every rating prompt clears the user's persistent setting. The cause is `(set-input-method nil)` in `org-drill--read-key-sequence`. When `current-input-method` is nil, calling `set-input-method` with nil clears `default-input-method` as a documented side effect. The unwind-protect on the way back has the symmetric problem, since it passes the captured nil. The fix is to use the primitives that are scoped to current state. `deactivate-input-method` and `activate-input-method` don't touch `default-input-method`, and I wrap each call in a guard so the function is a no-op when no input method is active. The same pattern lives in `org-drill-response-get-buffer-create`, which propagates the caller's input method into the response buffer. When the caller has no input method active, the captured value is nil and `(set-input-method nil)` runs in the new buffer, clearing `default-input-method` again. I applied the same guard there. I added `tests/test-org-drill-read-key-sequence.el` with 6 ERT tests across Normal, Boundary (the bug case), and Error categories. The four regression tests fail at HEAD before the fix and pass after.
* build: add lint, compile, validate-parens, and :slow tag filterCraig Jennings2026-05-053-4/+75
| | | | | | | | | | I want a stronger maintainer-discipline baseline as I take over more of this project, so I added four targets that are common in Emacs-Lisp packages. `make lint` runs `checkdoc`, `package-lint`, and `elisp-lint` over `org-drill.el`. It's informational for now and doesn't fail on findings, because the existing source has known docstring and style debt to clear. I'll re-tighten to a hard gate after the docstring pass is done. `make compile` byte-compiles the source with `byte-compile-error-on-warn nil`, matching the existing `build` target's leniency. `make validate-parens` is a fast structural check that surfaces the line of the offending paren without needing a full byte-compile pass. I also added a `:slow` tag filter to the default ERT runners. `test-unit`, `test-integration`, `test-file`, and `coverage` now run with `'(not (tag :slow))`. Tests tagged `:slow` get skipped on the fast feedback path. `test-name` is left alone, since a pattern argument means the user wants those tests run whether or not they're tagged slow. Cask gets `package-lint` and `elisp-lint` as development deps. `.gitignore` gets `*-autoloads.el` so the Cask build artifact stays out.
* test: cover org-drill-time-to-inactive-org-timestamp (upstream #59)Craig Jennings2026-05-051-0/+90
| | | | | | | | I added a regression test for `org-drill-time-to-inactive-org-timestamp` to lock in the cherry-pick from commit 4c6e62a, which fixed upstream issue #59 on the GitLab tracker. Chipschap reported timestamps like `[Y-08-27 Wed 16:%]` getting written into DRILL_LAST_REVIEWED. The root cause is that Org 9.6+ dropped the angle brackets around `(cdr org-time-stamp-formats)`, so the original `(substring ... 1 -1)` started slicing off the leading `%` of `%Y` and the trailing `M` of `%M`. The fix took the Org 9.6+ branch via `(org-time-stamp-format t 'no-bracket)` instead. The new test file has 7 ERT tests across Normal, Boundary, and Error categories. The Error cases assert the output has no stray `%` characters and no literal `Y` in place of the year. I confirmed the same tests fail when I drop in the original buggy implementation, so they catch the bug shape from the report.
* build: add make coverage target via undercoverCraig Jennings2026-05-054-9/+95
| | | | | | | | | | I want to track test coverage as I work through the upstream issue backlog, so I added an undercover-based flow that mirrors how `make test-unit` already runs each file in its own Cask Emacs process. The Makefile gets `make coverage` and `make coverage-clean`. A new helper at `tests/run-coverage-file.el` instruments `org-drill.el` before the source is loaded. Undercover merges per-file results into a single simplecov JSON at `.coverage/simplecov.json`. I added `undercover` as a Cask development dep and `.coverage/` to `.gitignore` so the report stays local. I also renamed `make install` to `make setup`. The old name read like "deploy the package onto my system," but the target only installs Cask deps into the local `.cask/` directory. `setup` is closer to what it actually does, and all the internal `: install` prerequisites move with it. Baseline at this commit is 10.8% (208/1928 lines on org-drill.el).
* fix: increment totaln on Simple8 failure pathCraig Jennings2026-04-291-0/+1
| | | | | | | | The Simple8 failure branch was missing (cl-incf totaln) while SM2 and SM5 both increment total-repeats on failure. After this change, DRILL_TOTAL_REPEATS counts every review attempt regardless of which scheduling algorithm produced it, including failures. Going-forward only. Historical totaln values for Simple8 failures stay under-counted by one. Correct counting starts with the next failed review. Paired with the test commit 5c68f1e, which captured the new expected behavior first. Full suite at 214 of 214.