Omniscio documentation
Browse all documentation
  1. Getting Started13
  2. Sessions & Agents115
  3. Inbox & Notifications59
  4. Projects & Tasks95
  5. Automation & Scheduling75
  6. Knowledge & Memory26
  7. AI Features60
  8. Integrations100
  9. Plugins & Marketplace33
  10. Cloud & Teams56
  11. Settings & Customization58
  12. Account & Billing28
  13. Troubleshooting84
  14. CLI & API Reference22
  15. Legal & Policies4
  16. Uncategorised22

Flashcards

Spaced-repetition flashcards for learning and retention, shipped as a plugin with its own sidebar tile — create packs, review cards on an FSRS schedule, import from Anki, and watch streaks and stats build up, all stored locally.

What it is

Spaced-repetition flashcards for learning and retention. Flashcards is now a first-party builtin-source plugin in Omniscio: the UI ships as a self-contained webview under src/plugins/flashcards/ and reaches core services through a typed bridge (window.AgentMC.flashcards). Backend (FSRS scheduler, Anki .apkg importer, media store, task-system integration, migrations, tables, CLI routes) STAYS in core so the data model + queries are shared with any future in-core consumer (e.g. an LMS meeting-intel handshake).

The user opens the plugin from its sidebar tile ("Flashcards"), which shows the pack list, a stats dashboard, pack detail with per-card review, and an Anki import wizard. Each pack holds cards; each card has a front + back (+ optional pasted images), a scheduling blob, and provenance (manual, anki-imported, or ai-generated).

Where to find it

Flashcards has its own tile in the sidebar, Flashcards. It is a first-party plugin, so it installs and updates the way any other plugin does, and the panel itself is a self-contained view with a pack list, a stats dashboard, pack detail for reviewing individual cards, and an Anki import wizard.

Creating a pack in the UI

"New pack" (pack list) and "Create your first pack" (empty state) open the card editor with no pack selected; the editor then shows a Pack name field and creates the pack together with its first card on Save, landing on the new pack's detail view. This is the only place the UI creates a pack (the bridge's packCreate is otherwise reached only by tests and the CLI).

The stats dashboard

The pack list header carries a Stats button that sets subView: 'dashboard-tile'; DashboardTile renders retention %, a 30-day review heatmap, a 7-day forgetting-curve forecast, the streak and the due-today count, and its own header renders the way back to the pack list on every branch (loading / error / empty / full). Reachable since 2026-09-18 — until then App.tsx routed the subView and nothing ever set it, so the whole surface was dead UI. The same sweep removed 'onboarding-stage' from the union: it was routed to ReviewMode, which returns null for any subView other than 'review', so it could only ever have rendered a blank screen (ReviewMode already delegates to OnboardingStage on its own, from settings.sessionsCompleted < 3). Guard: tests/unit/lint/flashcards-subview-reachable.test.ts fails whenever a SubView member has no navigation target.

How it behaves

Reviews are scheduled by an FSRS spaced-repetition scheduler that runs inside the app, and everything lives in your local database — no account, no cloud, no cost. A pack's counts are worked out when the page is read rather than stored, so they are never stale; media attached to a card is held in a local store and survives a re-import.

For agents

Where the code lives

  • src/plugins/flashcards/manifest.json - plugin id flashcards, category productivity, sidebar title "Flashcards", entry point ui/index.html.
  • src/plugins/flashcards/bridge/flashcards-bridge.ts - 28-method switch dispatching to services (single-object args pattern; emits IPC.FLASHCARDS_PUSH after each mutation).
  • src/plugins/flashcards/web/src/ - the React webview: App.tsx (subView router), components/ (PackListView, PackDetailView, CardEditor, ReviewMode, GradeButtons, OnboardingStage, MediaRefStrip, PackStatsPanel, DashboardTile, ImportWizard, FlashcardsErrorBoundary, ReviewComplete, FlashcardsEmptyState), store/ (Zustand pack + review slices), lib/ (flashcards-api bridge SDK, i18n shim, toast bus, telemetry stub, parse-cloze, is-fresh helpers), ui/ (self-contained primitives: Button, AsyncButton, Modal, DialogShell, Toaster, Pill, EmptyState, Spinner, CenteredSpinner, ListItem, IconButton, ImageCanvas, Kbd, FormField, SegmentedControl, SearchInput, LoadingPanel, MenuShell, ConfirmDialog).
  • src/main/services/flashcards/ - core services: flashcards-service.ts (CRUD + review + spawn context builder + CLI-server-mediated spawn), fsrs-scheduler.ts (ts-fsrs 5.4.1 wrapping), media-store.ts (content-addressed image blob store), importer/ (Anki .apkg parse + commit via yauzl + fzstd), pack-group-task.ts (task-system integration), pick-apkg-file.ts (OS-native file picker).
  • src/main/db/queries-flashcards.ts + queries-flashcards-session.ts - SQL surface for pack + card + review + trickle-queue + settings + pack-session tables.
  • src/main/db/migrations/ - four additive migrations: 20260824-flashcards-tables.ts, 20260825-flashcards-trickle-queue.ts, 20260826-flashcards-media-table.ts, 20260826-flashcards-retrievability.ts.
  • src/main/services/cli/cli-server-flashcards-routes.ts - 28 CLI routes under /flashcards/* (route-list identical to the bridge methods).
  • src/shared/flashcards/ - types + Zod schemas + parse-cloze + telemetry-event catalog shared by bridge, service, tests.
  • src/shared/push-event-schemas/flashcards.ts - 9-kind discriminated union for IPC.FLASHCARDS_PUSH (pack.list.changed, pack.detail.changed, card.list.changed, review.session.updated, card.review.recorded, import.started, import.progress, import.completed, import.failed).
  • src/shared/ipc-channels/flashcards.ts - reduced to the ONE push channel; the 28 request/response channels are retired (bridge is the transport).
  • tests/unit/plugins/flashcards/bridge/ - bridge dispatch, media round-trip, session-spawn cost preservation tests.

Bridge shape (window.AgentMC.flashcards)

Every method resolves with UNWRAPPED data and rejects with an Error when the main side reports a failure. Arguments are single-object payloads (matching the Wave 1 Zod schemas). Full list (28 methods):

  • Pack CRUD - packList(), packGet({packId}), packCreate({name, source?}), packRename({packId, name}), packDelete({packId}).
  • Card CRUD - cardList({packId?, limit?, offset?}), cardCreate({packId, front, back, cardType, frontMediaRefs?, backMediaRefs?}), cardUpdate({cardId, ...}), cardDelete({cardId}).
  • Review - reviewStart({packId: string | null}), gradeCard({sessionId, cardId, grade, latencyMs, reviewMode, interleaved}), reviewEnd({sessionId}).
  • Settings + stats - settingsGet(), settingsUpdate({...}), stats().
  • Import (Anki .apkg) - pickApkgFile(), importParse({apkgFilePath}), importCommit({importId, packName}), importCancel({importId}).
  • Sessions (spawn) - sessionStartOnCard({cardId, reason?, ...}), sessionStartOnPack({packId}), packSessionAttribution({packId}), trickleEnqueue({...}), trickleDismiss({...}).
  • Media - mediaUpload({bytes: Uint8Array, originalName?}), mediaGet({mediaId}), mediaDelete({mediaId}), mediaList().

CLI parity

CLI routes at /flashcards/* are preserved; the plugin bridge and CLI parity share the same 28-method surface. Read routes gate through cliReadPreamble; mutations gate through cliMutationPreamble (bearer auth + rate limit). The flashcards-panel unreleased-feature flag was retired with the plugin conversion - plugin install + enable is now the user-facing gate, and CLI routes are always registered so plugin-external agents can drive Flashcards data. pickApkgFile retains its CLI-parity exemption (OS-native dialog is not headless-callable).

Cost preservation invariant

Session-spawn methods (sessionStartOnCard, sessionStartOnPack) MUST route through the local CLI server's /project/:name/new with bearer auth + the X-AMC-Source-Session-Id provenance header. The bridge dispatches these to spawnClaudeSessionForCard / startSessionOnPack in src/main/services/flashcards/; it does NOT reimplement the spawn logic. A AMC_TEST_MOCK_CLI_POST=1 escape hatch in spawnClaudeSessionForCard returns a canned response so integration tests never fire a real (billable) session-spawn. Automated tests MUST use that escape hatch - invoking these methods live burns real Claude tokens.

Media Buffer round-trip

mediaUpload({bytes, originalName}) stores content-addressed image blobs under <userData>/flashcards-media/, returns {mediaId, contentHash, sizeBytes, mime}. mediaGet({mediaId}) returns {bytes, mime, sizeBytes} - the bytes may arrive as a Node Buffer or a {type:'Buffer', data:[...]}-shaped structured-clone; the plugin store's normalizeToUint8Array() helper accepts either shape so a new Blob([bytes]) render path stays safe. Verified by tests/unit/plugins/flashcards/bridge/flashcards-bridge.media.test.ts.

Push events

Server-side emits emitPush(IPC.FLASHCARDS_PUSH, {kind: '...', ...}) from the bridge (after each mutation) and from import-service.ts (import.started / progress / completed / failed). The host forwards that channel onto the plugin's OWN event bus as flashcards:push (src/main/app/startup/register-push-routing.ts → dispatchPluginEvent('flashcards', …), plugin-scoped), because a sandboxed plugin webview only hears window.AgentMC.events.on, never the renderer push channels. Island side, lib/plugin-events.ts is the one subscription chokepoint: subscribeToFlashcardsPush (store) drops the pack-list TTL guard and refetches on any list-changing kind (pack/card changes, a recorded review, a completed import), and usePushListener (used by ImportWizard for progress) subscribes a component with optional debounce. Wired 2026-09-15 — before that both were no-op stubs, so a pack created through the bridge or the CLI never appeared without a reload.

Pack counts are computed on read (not stored)

flashcard_packs.card_count / due_count are legacy columns: every writer (createPack and the Anki importer) inserts 0 and nothing maintains them — due_count cannot be maintained at all, since it changes as the clock passes with no write. PACK_READ_COLUMNS (queries-flashcards.ts) computes both from the card rows with the same predicate the review queue uses (dueAt <= now, not soft-deleted), so pack.dueCount > 0 means exactly "Start review can start a session now". A lint guard (tests/unit/lint/flashcards-pack-counts-computed.test.ts) bans any raw SELECT of the stored columns. Fixed 2026-09-15: until then the raw zeros were served and "Start review" was disabled forever on every pack (imports only worked because the wizard calls startReview directly).

Deps

ts-fsrs@5.4.1 (FSRS scheduling), yauzl (.apkg zip reading), fzstd (Zstd decompression), @types/yauzl (typings) live in the ROOT package.json and are used by main-process code only. The plugin webview never imports these - its own package.json under src/plugins/flashcards/web/ pulls react, zustand, lucide-react, zod, tailwindcss.

Related

Flashcards keeps its own data and does not read your sessions; for the notes-and-memory side of Omniscio see Global memory.

Last verified 2026-09-23