Cosimo

Architecture
Login

Architecture

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:

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.

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:

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:

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:

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