Cosimo is split into a small reusable library and a wxdragon desktop application. The library owns the data model, deck parsing, review storage, and scheduling rules. The desktop application owns native controls, persistence policy, dialogue flow, localisation, and the current single-deck session state.
The guiding design is to keep user-authored learning content easy to inspect and recover, while keeping review history and scheduling state in a database. The GUI should remain screen-reader friendly by using native widgets and standard keyboard interaction wherever possible.
Runtime Data
Cosimo normally works with five runtime file groups:
- A
.cosimo-deckfile stores editable prompts and responses. - A neighbouring
.reviews.sqlite3file stores review events and card schedules. - A same-stem deck asset directory may hold optional WAV review audio.
cosimo.inistores application settings, the last opened deck, recent decks, and ready-list view state.backups/stores automatic deck backups and optional review-database backups.
Deck files use stable numeric card ids. Those ids are the long-term identity for scheduling, so editing a prompt or response does not disconnect the card from its review history. Older decks without numeric ids are migrated on open: Cosimo assigns unused ids, migrates any legacy review-store keys, backs up the deck according to the configured policy, and then rewrites the deck.
The asset directory is named from the deck stem. For example,
Indonesian.cosimo-deck uses Indonesian/ beside the deck for optional review
audio. Audio files are WAV only in the first implementation and are keyed by
stable card ID and side, such as 1.prompt.wav and 1.response.wav. Generated
reverse cards reuse the parent card's prompt and response assets with the sides
swapped, because the generated reverse direction is study state rather than
separate deck-authored content. Ordinary review and exam question prompts use
the same resolver; exam grading does not currently play response audio. Deck
backups do not currently include sidecar audio.
Library Modules
The public library surface is declared in src/lib.rs.
- src/model.rs defines cards, stable card keys, prompt guesses, recall feedback, and completed reviews. Rating constructors validate stored integer values before they become typed ratings.
- src/deck_format.rs parses and formats the plain-text deck format. It accepts legacy terminators and emits the current numbered-id format.
- src/review_store.rs wraps SQLite review storage. It performs integrity checks, schema setup, event insertion, schedule loading, key migration, review-pass recording, reversal persistence, sparse card-state persistence for suspension, flagging, and deferral, reverse-batch undo metadata, non-reversible card markers, higher-level deck consistency checks, and one-copy database backups.
- src/scheduler.rs contains FSRS-style card scheduling and review-queue selection. Due reviewed cards are prioritised by due time, equal priorities are randomised, new cards come before minimum-fill cards, and fill cards are randomised.
These modules do not depend on wxdragon. They are the preferred place for pure logic and data-loss-sensitive behaviour because they can be tested without the GUI toolkit.
Desktop Application Modules
The binary is organised around src/main.rs, which starts the wx application and wires XRC controls to Rust event handlers. Supporting modules keep most nontrivial logic out of the event handlers:
- src/app_config.rs reads, writes, and migrates
cosimo.ini; detects the system language; and stores recent decks and ready-list view preferences. - src/app_state.rs owns the loaded deck, review database, active review session, ready-list view state, and state mutations behind deck editing, scheduling, card states, reversal, database maintenance, and learning-dashboard data.
- src/deck_data.rs resolves default deck paths, derives review-database paths, and handles card-id enumeration.
- src/file_ops.rs handles durable file replacement, portable paths, deck backups, review-database backups, and opening local HTML docs.
- src/review_session.rs models the active review pass, including prompt/response phase, per-pass statistics, and failed-card repeats.
- src/exam_mode.rs models exam setup, typed prompt/answer collection, exact-match checking, end-of-exam grading, and report-only exam summaries.
- src/study_item.rs expands deck cards into study directions. Forward cards use the deck prompt and response; generated reverse cards swap them and use a child datum key so their scheduling and review history stay separate.
- src/ready_list.rs computes ready-list filtering, duplicate prompt/response detection, sorting, labels, counts, selection normalisation, and minimal list updates.
- src/ready_screen.rs renders and synchronises the main ready/review screen: schedule previews, status-bar text, ready-list labels, filter and sort controls, dynamic card-menu state, focus restoration after ready-screen changes, timed and F5 refresh handling, and due-notification baseline updates.
- src/review_dialogs.rs, src/exam_dialog.rs, src/options_dialog.rs, and src/dialog_support.rs isolate dialogue construction and modal behaviour.
- src/menu_model.rs centralises menu labels, mnemonics, shortcuts, and recent-deck menu layout.
- src/menu_ui.rs applies dynamic wx menu updates such as current labels and recent-deck menu insertion.
- src/menu_dispatch.rs owns the internal command ids
used to defer modal menu actions and tray menu requests back to the frame
event queue, plus the frame
on_menuregistration that turns command ids into typed dispatch decisions. - src/help_actions.rs handles Help menu actions that launch local documentation or project web pages and report browser-launch failures.
- src/keyboard.rs contains key predicates used by handlers.
- src/review_input.rs owns review-pass keyboard binding and the key-to-command dispatch policy for prompt and response phases.
- src/review_audio.rs resolves sidecar WAV files for the current study side and owns the wxSound play/stop state. Rendering calls it after each ordinary review-screen update so automatic audio follows prompt/response changes, and review or exam-ending paths stop audio before leaving the current prompt/response context.
- src/resident_tray.rs owns the minimise-to-system-tray icon, tray popup menu, tray polling timer, restore/quit command ids, and tray trace hook.
- src/card_details_model.rs formats the per-card details report.
- src/learning_dashboard_model.rs computes deck analytics, including separate standard, reverse, and total summaries when generated reverse cards exist.
- src/time_format.rs formats timestamps and speech-friendly durations.
- src/runtime.rs provides current time and deterministic shuffle helpers.
- src/localization.rs contains locale parsing, language
selection, placeholder formatting, and the
LocalizerAPI used by the rest of the application. Labels, message templates, and plural nouns are loaded through a generated gettext-style catalogue.
Gettext .po files in po/ are the editable source for catalogue-backed
translations. build.rs compiles them into a Rust lookup table during
the Cargo build, so the portable Windows package does not need gettext runtime
libraries or separate .mo files.
XRC files in ui/ define the native window, menus, and dialogues. The Rust code localises labels after loading the XRC resources.
Application State
AppState in src/app_state.rs is the central mutable state
for the GUI. It owns:
- the loaded deck path and review database path
- the loaded
Datumlist - the open
SqliteReviewStore - the current
ReviewSession - selected ready-list study item and ready-list filter/sort settings
- effective settings and localised status text
Event handlers borrow AppState, perform one user action, and then call the
rendering and synchronisation helpers in src/ready_screen.rs
to update controls from state. The ready-list stores study-item indices, not
visible row indices, so filtering and sorting can change without losing which
forward or reverse item is selected when that item is still visible. Actions
that edit the deck map the selected study item back to its parent deck card.
Review Flow
At startup, Cosimo resolves the portable default paths, migrates old config files if present, loads settings, creates the default deck if needed, opens the deck and review database, runs the database integrity check, and migrates deck card ids if needed.
The ready screen shows the current deck summary, ready-list filter, list of
prompts, and card management controls. Normal Start asks the scheduler for
the due queue using the configured minimum size, excluding cards that are
suspended or currently deferred. Force Start randomises every card that is
not suspended or deferred and marks the pass as unscheduled for statistics
text.
During a review pass the session phase is either prompt or response. Prompt
ratings can be skipped by using reveal directly. Once the response is visible,
recall feedback records an event in SQLite, updates the card schedule, advances
the session, and queues a single repeat at the end of the pass for cards rated
Again. When the pass finishes or is ended early, the statistics dialogue shows
the recorded pass summary and Continue returns to the ready screen.
Exam mode is an assessment flow rather than a scheduler-writing review flow.
Starting an exam asks for a question count or percentage of active cards, then
samples cards from the active deck in random order. Suspended and currently
deferred cards are excluded. While the modal exam flow is running, AppState
marks the session active so timed ready-screen refresh and notification code
do not interrupt it. The modal question dialogue collects typed answers without
recording review events or updating schedules. Exact answers are accepted
automatically. In the default mode, non-exact answers are adjudicated with a
native Yes/No prompt after all submitted exam answers have been collected. If
the setup dialogue's Exact checkbox is selected, non-exact answers are counted
incorrect without manual grading and are shown in a read-only answer review
flow with Next and Summary controls. Finishing the exam resets the session and
shows a report-only summary; it does not write review history, review-pass
rows, or card schedules.
Resident Tray Lifecycle
Minimise-to-tray is implemented in src/resident_tray.rs
with a TaskBarIcon stored separately from ReviewUi. The normal Windows
minimise action iconises the frame first; a size handler and a short polling
timer then convert that state into resident tray mode by installing the tray
icon and hiding the frame. Keeping this conversion outside the ordinary render
path avoids changing keyboard focus and ready-screen behaviour when the option
is disabled.
The tray polling timer and the ready-screen refresh timer must not share the
same wx event owner. wxdragon binds Timer::on_tick as a timer event on the
owner rather than filtering by a specific timer id, so two timers on the same
frame can both invoke each other's handlers. Cosimo keeps the tray polling
timer on the frame and owns the one-minute ready refresh timer from the root
panel. This prevents the 250 ms tray poll from causing repeated ready-screen
renders.
The same rule applies to transient menu work. Confirmation dialogues for destructive menu commands are deferred by posting command events to the frame, not by creating short-lived frame timers. A one-shot frame timer can leave a timer binding behind and be retriggered by the tray polling timer, which makes the confirmation dialogue appear to relaunch continuously.
Yes/No confirmations use the platform-native wxMessageDialog. The native
dialogue exposes its message text better to screen readers than a custom dialog
with a read-only text control. The important safety rule is not the dialogue
implementation, but where it is opened: destructive menu confirmations must be
opened only from the posted frame command path described above.
Menu commands that open modal dialogues follow the same event-queue pattern: the menu item consumes the native menu event and posts an internal command to the frame. src/menu_dispatch.rs maps those command ids to typed commands, including recent-deck slots and tray restore/quit requests. It also owns the frame menu-event hook: known internal command ids are consumed and dispatched, while unknown menu ids are skipped for normal wx processing. The frame consumes the internal command and only then opens the dialogue. This avoids relaunching a dialogue when Escape, Enter, or a button closes it while the original menu event is still unwinding. Direct state-change menu commands are also consumed before they mutate state.
The tray icon has a deliberately conservative lifetime. wxWidgets can assert if a window-like object is destroyed while one of its own event handlers, popup menu handlers, or pushed event handlers is still active. In practice this has affected the tray context-menu Quit path on Windows. For that reason Cosimo distinguishes hiding the tray icon from destroying the tray object:
- Restoring from the tray removes the visible icon and shows the frame again,
but keeps the
TaskBarIconobject available for later cleanup. - The tray context menu is installed with
TaskBarIcon::set_popup_menu()and the menu object is retained inResidentTray. This follows wxdragon's automatic taskbar-menu path instead of manually callingTaskBarIcon::popup_menu()from a right-click callback. wxdragon copies the retained menu when wxWidgets requests the tray menu, so the retained menu acts as the template for the current language. - Tray context-menu Restore and Quit do not run directly from the menu event. Cosimo posts the requested command to the frame, so the frame handles it on the next main-loop turn after the popup has unwound.
- Quitting from the tray context menu then removes the visible icon and asks the frame to close.
- Ordinary frame close stops and drops the ready-refresh and tray-minimise
timers and removes the visible tray icon if one has existed. It deliberately
does not call
TaskBarIcon::destroy()on the retained tray object; Windows testing showed tray destruction to be the fragile path. If a tray object had existed, Cosimo exits the process directly after saving view state and removing the visible icon, leaving the retained tray object to process teardown.
Future tray changes should preserve that staging. In particular, do not call
TaskBarIcon::destroy() from tray mouse callbacks, tray menu callbacks, or the
ordinary shutdown path unless the underlying wxdragon/wxWidgets lifetime
behaviour has been retested on Windows. Do not reintroduce manual tray
popup_menu() handling unless the automatic set_popup_menu() path has been
retested and found unsuitable.
For Windows tray regressions, Cosimo has an opt-in trace hook. If
COSIMO_TRAY_TRACE is set to a file path, tray minimise, restore, quit, and
frame-close lifecycle events are appended to that file. The
tools/windows-tray-repro-auto.bat and tools/windows-tray-repro-manual.bat
scripts use this hook to run automated minimise/close cycles, or to watch a
manual tray repro attempt and report whether cosimo.exe remains running after
the user has attempted to quit. The batch files are the preferred entry points
on Windows; the PowerShell script contains the lower-level automation.
Debug builds also write a lightweight lifecycle log to cosimo-debug.log in
the current working directory. Set COSIMO_DEBUG_LOG to a file path to choose a
different location. This log is intentionally broader than COSIMO_TRAY_TRACE:
it records startup, frame-close, tray lifecycle, and Rust panic-hook events.
Release builds do not write this log. When investigating a long-running
shutdown crash on Windows, run target\debug\cosimo.exe, reproduce the issue,
and keep cosimo-debug.log together with any Windows assertion or crash
message.
Persistence Policy
Deck modifications are written immediately because they are user-authored content. Before replacing an existing deck file, Cosimo applies the configured deck backup policy and then uses durable replacement.
Review events and schedules are written through SQLite. If enabled, a retained review-database backup is refreshed when a review pass starts and the database has opened successfully.
Review-database scrub is a ready-screen maintenance action. It derives the set
of active standard and generated reverse datum keys from the current deck and
card_reversals, backs up the database first when review-database backups are
enabled, then deletes dormant rows that are no longer reachable from those
active keys. The store runs SQLite VACUUM after the scrub transaction so the
database file can reclaim freed pages.
Settings changed in the options dialogue, including sound notifications and
minimise-to-tray behaviour, are saved when the user accepts the dialogue.
Opening a deck saves last_open_deck and recent decks when they change.
Lower-value ready-list view state is persisted on normal application exit and
only when it differs from the parsed cosimo.ini, avoiding unnecessary disk
writes.
Testing
The test suite covers both library logic and GUI-adjacent pure behaviour. The library tests exercise deck parsing, typed ratings, review storage, and scheduling. Binary tests cover localisation, config migration, backup behaviour, ready-list filtering and sorting, card state markers including deferral, sort persistence, shortcut/mnemonic rules, review-session flow, and source-level guards around fragile wx event bindings.
The Fossil pre-commit hook runs:
cargo test --lib --no-default-features
cargo test --bin cosimo
Use those commands before committing behaviour changes. For quick GUI compile verification, use:
cargo check --bin cosimo