Skip to content
 
 

Latest commit

 

History

1,838 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

CardMirror

CardMirror

This is a fork (BlueCheeseburger/cardmirror) of ant981228/cardmirror. Changes added on top of upstream, newest first:

  • The update chip shows download progress (desktop only) — the status-bar pill fills in as the update downloads and its text names the percent, instead of only appearing once the download is done. (main)
  • Each pane in a multi-pane workspace now shows its own cloud-sync pill, in that pane's footer, instead of one shared pill that left it ambiguous which pane's document it applied to — compact, provider-logo-only while synced, no "Cloud" text label. (main)
  • Right-click anywhere on the ribbon to name (or rename) a window (desktop only) — a named window shows that name as its title instead of the document filename, so windows are easier to tell apart at a glance; persists across mode-switch reloads. (main)
  • A pane's speech-doc marking now shows through even while you're focused on it (amber wins over the focused-pane blue highlight instead of being hidden by it), Send to Speech explains itself when the cursor isn't somewhere sendable instead of silently doing nothing, and a filename with a macOS Finder-typed "/" now displays the same way it does in Finder instead of showing the raw on-disk ":" underneath it. (main)
  • Custom dash can convert "--" and "---" independently, at the same time instead of picking just one; opening a moved/deleted Recent file now offers to locate it instead of just forgetting it; a reading-speed test link was added to the readers setting; Option+backtick no longer captures as "Unidentified" when rebinding a shortcut on macOS. (main)
  • Rebinding a shortcut on macOS warns when it overlaps a system shortcut (Quit, Hide, Log Out, the screenshot keys, Mission Control, Full Screen — checked against the real Ctrl/Cmd keys you pressed, so a two-key combo like Control-Command-F isn't confused for a plain Cmd-F); settings/tooltips that name a shortcut now say "Cmd" instead of "Ctrl" on macOS wherever that's what actually works; and Send to Speech now tells you why it did nothing instead of silently no-opping when the speech doc is the only document open and you try to send from it to itself. (main)
  • New Document and opening a file from Finder/Dock now ask which pane or window in a three-pane workspace, instead of guessing (New always spawned a whole window even with an empty pane sitting right there; external-open always went to the last-focused window even when a different one had the room). (main)
  • Synced with upstream through its 1.8.0 release — pulls in the disk-conflict guard rework and cloud pill (Dropbox/OneDrive/Google Drive/iCloud folders show sync status, and a save that would overwrite a teammate's edit writes a conflicted copy instead of silently clobbering it), the single-owner flashcard store (fixes cards vanishing with two windows open), and every 1.7.0 co-editing fix. This fork's own multi-pane autosave now uses the same conflicted-copy path on a changed-on-disk write. (main)
  • Save (ribbon button or Mod-S) only saves the current document again, and in three-pane mode the Save and Autosave buttons move out of the ribbon into each pane's own title chip — each pane now has its own Save/Autosave controls acting only on that pane's doc, instead of one shared pair of ribbon buttons whose target depended on which pane happened to be focused. (main)
  • Ctrl/Cmd+K auto-fills the "Link to" field when the selected text already looks like a URL (has a scheme, a www. prefix, or a recognized domain ending) — no more retyping a pasted link into the hyperlink dialog after selecting it as the display text. (main)
  • Closing with unsaved changes in multiple panes now shows one named dialog per document, all at once, instead of a single unnamed "You have unsaved changes" prompt repeated one at a time with no indication which doc it was about. A doc whose saved file can no longer be found also gets a "Bind new filepath…" button in place of plain Save. (main)
  • The autosave-failure notice offers a "Save As…" button when the file has gone missing or its folder refused the write, instead of only telling you to do that yourself. Works for a background pane in three-pane mode too — it brings that doc into view first. (main)
  • Autosave is on by default, instead of off until turned on per file — turn it off per file from the ribbon toggle (or per-pane chip in three-pane mode) same as before. (main)
  • Three-pane workspace autosave now flashes the Save button on a successful save, matching single-doc mode and manual saves — it used to save silently there. (main)
  • Home screen suggests reopening a whole three-pane workspace, not just individual files — closing a multi-pane window with 2+ saved docs open remembers that set, so one click puts all of them back in the same pane layout. Desktop only. (main)
  • Ctrl/Cmd+K adds or removes a hyperlink — select text and press it for a small dialog (display text + URL); press it again on an existing link to remove it. (main)
  • Three-pane workspace toggle in the ribbon, independently per window — no more Settings-dialog-only switch. Opening a document also now offers "New window" alongside the three slots, so a full workspace never forces you to replace an open pane. (main)
  • CardMirror registers as a .docx handler on macOS/Windows — it now shows up (and can be set as default) in "Open With" for Word documents, not just its own native .cmir format. (main)
  • Paced auto-scroll — a hands-free teleprompter that scrolls the document at your actual reading pace, slowing for dense highlighted text and speeding through everything else.
  • Autosave for .docx files, not just .cmir — plus a time-boxed, logged fallback for the zip worker and a ribbon button that distinguishes "saving" from "paused" (e.g. a doc with a live view / linked copy Word can't hold).
  • Independent document background theme — the editor surface's light/dark appearance can now be set separately from the app chrome's theme.
  • Independent multi-window three-pane workspace — additional windows no longer have to mirror the main window's pane layout.
  • Google Gemini as a third AI provider, alongside Anthropic and OpenRouter.

CardMirror is a debate text editor focused on high school and college policy debate. It's a standalone replacement for the editor side of Verbatim, the Microsoft Word add-in most US debate teams use: same organizational structure, same F-key shortcuts, same send-to-speech workflow, same Word-compatible .docx files — but with no Word, no macros, and no add-in. That keeps it fast on the multi-megabyte evidence files debaters work in, and enables cross-platform feature parity.

Run it as a desktop app for tournament-day work, or as a web preview in any browser for trying it out or working from a Chromebook or locked-down school machine.

Keep backups. CardMirror is stable and used daily, but no editor is bug-free — keep a copy of anything critical in a second format.

Desktop builds are unsigned, so Windows and macOS warn you the first time you launch. The one-time workaround for each is in the install steps below.

See MANUAL.md for the full user manual; PRIVACY.md for how CardMirror handles your data; TERMS.md for the terms of use; CHANGELOG.md for release notes; ARCHITECTURE.md for the full design; PROJECT.md for project orientation.

You can find a video walkthrough of CardMirror's basic functions here.

Support Upstream Cardmirror

The core CardMirror editor is free, and built and maintained by one person. If it's useful to you, two ways to help keep it going:

  • Subscribe at Debate Decoded — recurring support that funds ongoing development, and the best way to keep the project moving.
  • Tip once on Ko-fi — a one-time contribution, no account needed. (Or use the Sponsor button at the top of the repo.)

Install

Desktop builds live on the Releases page. Pick the file for your operating system, run the installer, and launch CardMirror like any other app.

macOS

  1. Download CardMirror-x.x.x-universal.dmg — one download that runs natively on both Apple Silicon (M1 and later) and Intel Macs.

  2. Open the .dmg, drag CardMirror to your Applications folder.

  3. First launch only. Gatekeeper refuses to open unsigned apps. You'll see one of two messages depending on your macOS version:

    • "can't be opened because Apple cannot check it for malicious software" → Open Finder → Applications, right-click (or Control-click) CardMirrorOpen. Click Open in the confirmation dialog. From then on, normal double-click works.

    • "is damaged and can't be opened" → macOS has hard-quarantined the app and the right-click workaround doesn't suffice. Open a terminal (Spotlight → "Terminal") and run:

      sudo xattr -cr /Applications/CardMirror.app

      You'll be asked for your password. After that, normal double-click works.

Windows

  1. Download CardMirror Setup x.x.x.exe.
  2. Run the installer (Next → Install → Finish).
  3. First launch only. SmartScreen shows "Windows protected your PC." Click More infoRun anyway.

Linux

Two options for installation, depending upon distribution:

  • AppImage (works on every modern distro): download cardmirror-x.x.x.AppImage. In a terminal, in the download folder, run:

    chmod +x cardmirror-x.x.x.AppImage
    ./cardmirror-x.x.x.AppImage

    For most graphical desktop environments with modern file managers, you should be able to run the AppImage by double-clicking after the chmod +x command.

  • Arch and Arch-based distributions — use the AUR:

    yay -S cardmirror-bin
    # or with paru: paru -S cardmirror-bin

    Or grab cardmirror-x.x.x.pacman from the release directly and:

    sudo pacman -U cardmirror-x.x.x.pacman

Updates

Updates specifically pull from BlueCheeseburger/cardmirror, not upstream cardmirror.

Help → Check for Updates… checks manually: it tells you you're current, or announces an available update and downloads it in the background.

CardMirror also checks for updates automatically — silently at launch and once a day, speaking up only when a new version is actually ready. Turn this off (or pause it for a week — handy at a tournament) in Settings → General → "About this install" → Check for updates automatically.

Updates never interrupt you: when a new version has finished downloading, a small chip appears in the status bar ("Update x.y.z ready — restart to install"), and nothing installs until you click it. On Windows and Linux, quitting the app normally also applies a downloaded update on the way out. On macOS, clicking the chip restarts straight into the new version; if your install can't be updated in place (for example the app isn't in a writable folder), the chip opens the releases page instead so you can grab the new .dmg.

Going to a tournament? Pause update checks for 1 week (Settings → General → "About this install") stops all automatic checks and downloads until the shown resume date.

Linux users who installed via the AUR can update with yay -Syu instead.

(Optional) Set up AI features

A few features call out to an AI model:

  • AI-formatted citations from a pasted URL or freeform quote.
  • AI repair of OCR / PDF extraction errors in a selection (Mod-Shift-R).
  • AI image alt-text and table-from-image (right-click an image).
  • AI commenting / explain features in the comments column.

Two providers are supported; pick either one:

  • Anthropic talks directly to Anthropic's Claude API. Get an API key from console.anthropic.com (you'll need to top up a small amount of credit — Anthropic doesn't have a free tier for the API).
  • Gemini talks directly to Google's Gemini API. Get an API key from aistudio.google.com/api-keys (you'll need to top up a small amount of credit — Anthropic doesn't have a free tier for the API).
  • OpenRouter talks to openrouter.ai, which fronts models from many labs behind one key — use it if you already have OpenRouter credit or want a specific non-Claude model. You choose the model by its id (e.g. anthropic/claude-sonnet-4.6 or openai/gpt-4o) in the OpenRouter model field.

To enable them:

  1. Get an API key from the provider you picked.
  2. In CardMirror, click the ⚙ gear icon in the ribbon.
  3. Toggle AI features on, choose your provider under AI provider, and paste your key into that provider's key field (Anthropic API key, Gemini API key, or OpenRouter API key).

The key is stored locally on your machine and is sent directly to the provider you picked when you trigger an AI feature. It doesn't travel through a third-party server.

Translation (Mod-Shift-T on a selection → copied to the clipboard) also has an Anthropic backend, but it works without any AI setup: the default MyMemory backend needs no key. You can also plug in a Google Cloud Translation key. Configure it under Settings → Editing → Translation.

Run from source

You only need this if you want to build CardMirror yourself (contribute, run a development branch, or use the editor on a platform we don't publish binaries for). For day-to-day use, download a release above.

1. Install Node.js

CardMirror is built with JavaScript / TypeScript and needs Node.js to run. Node is a regular desktop installer.

  • macOS — open nodejs.org in your browser and click the blue "LTS" download button. Open the .pkg file from Downloads and click through the installer.
  • Windows — open nodejs.org and click the blue "LTS" download button. Open the .msi file from Downloads and click through the installer.
  • Linux — the easiest path is the official installer at nodejs.org/en/download — pick your distro and follow the few commands it shows.

You don't need to verify the install — if the next step works, Node is installed.

2. Download the source

  1. Open the CardMirror page on GitHub in your browser.
  2. Click the green <> Code button near the top of the file list.
  3. Click "Download ZIP" at the bottom of the dropdown.
  4. Unzip the download. You'll get a folder called cardmirror-main. Move it somewhere you can find later — your Desktop or Documents is fine.
  5. Open the cardmirror-main folder and look inside. Some unzippers double-wrap. You want the folder that directly contains package.json, README.md, index.html, and src/ — if you only see another cardmirror-main folder, that's the wrapper; open it.

3. Open a terminal inside that folder

A "terminal" is a window where you type commands. You're going to open one already pointing at the CardMirror folder.

  • macOS — enable Finder → right-click → Services → New Terminal at Folder once via System Settings → Keyboard → Keyboard Shortcuts → Services → Files and Folders, then right-click the folder.
  • Windows — open File Explorer in the folder, click the address bar, type cmd, press Enter.
  • Linux — right-click inside the folder and pick Open Terminal Here (Nautilus / Dolphin / Thunar all offer it), or open in terminal and cd to the filepath.

To make sure you're in the right directory, type ls (macOS / Linux) or dir (Windows) and press Enter. You should see package.json, README.md, src, apps. If you don't, your terminal is likely one folder too high up — verify your current folder with the pwd command (macOS / Linux / Windows PowerShell) or by typing echo %cd% (Windows, non-PowerShell).

4. Install dependencies

npm install

Downloads everything CardMirror needs. Takes 30 seconds to a couple of minutes. Deprecation warnings are normal; only red error lines indicate trouble.

5. Run the web edition

npm run dev

After a few seconds, open http://localhost:5173/ in your browser.

Run the desktop edition (from source)

npm run desktop:dev

This builds the Electron main process, starts the Vite dev server, and launches the desktop window. Same code, same renderer — but in a native window with file-system access.

Coming back later

Open a terminal in the same folder; run npm run dev (or npm run desktop:dev) again. To pick up newer code, download a fresh ZIP and rerun npm install in it.

Other commands

These all run inside the terminal pointed at the CardMirror folder (same setup as the install steps above):

npm test            # run all tests
npm run test:bench  # performance benchmarks
npm run typecheck   # strict TypeScript check

Testing round-trip against your own .docx files

The round-trip test suite and the round-trip benchmark both walk a folder of .docx fixtures and run universal preservation checks on each one (text length, heading IDs, mark counts, indent / spacing multisets, etc.). Point them at any folder by setting CARDMIRROR_DOCS_DIR:

CARDMIRROR_DOCS_DIR="/path/to/your/docx/files" npm test
CARDMIRROR_DOCS_DIR="/path/to/your/docx/files" npm run test:bench

When the variable isn't set, the suite looks under reference-docs/example docs/ (the project owner's local corpus). When that folder doesn't exist either — the default state on a fresh clone — the file-dependent tests skip cleanly and the rest of the suite still runs.

Round-trip a docx

The CLI imports a Verbatim/Advanced-Verbatim docx, normalizes it through our schema, and re-exports a fresh docx:

npm run round-trip path/to/input.docx [path/to/output.docx]

The output is fully native to Verbatim — same canonical style ids, same direct-formatting conventions. Stylepox and other non-Verbatim cruft is dropped on import (per ARCHITECTURE.md §3).

Public API

import {
  schema,        // the ProseMirror schema
  fromDocx,      // .docx bytes → ProseMirror doc
  toDocx,        // ProseMirror doc → .docx bytes
  exportDoc,     // schema doc → { documentXml, relsXml }
  importDoc,     // document.xml → schema doc
  newHeadingId,  // generate a fresh stable heading UUID
} from 'cardmirror';

Example: read a docx, modify, write it back

import { fromDocx, toDocx } from 'cardmirror';
import { readFile, writeFile } from 'node:fs/promises';

const buf = await readFile('input.docx');
const doc = await fromDocx(buf);

// `doc` is a ProseMirror Node — walk it, transform it, edit it...
console.log(`${doc.nodeSize} chars in tree`);

const out = await toDocx(doc);
await writeFile('output.docx', out);

Schema highlights

doc:        sequence of block-level kinds
pocket:     Heading 1 paragraph (with stable id)
hat:        Heading 2 paragraph (with stable id)
block:      Heading 3 paragraph (with stable id)
card:       structured: tag (card_body | undertag | cite_paragraph | analytic | table)*
tag:        Heading 4 (only inside card)
cite_paragraph, card_body: body paragraphs inside cards
analytic:   outline-4 paragraph (Analytic style; can be standalone or in-card)
undertag:   Undertag-styled paragraph
paragraph:  unstyled body text (first-class — can sit between any nodes)
table:      table_row+ (at doc level OR inside a card / analytic_unit)
table_row:  (table_cell | table_header)+
table_cell: paragraph+
image:      inline atom (base64 bytes + EMU dimensions + alt; round-trips through .docx)

Every paragraph-like textblock carries round-trip-only attrs indent (left indent in OOXML dxa) and spacing (verbatim <w:spacing> map). Tables carry rawTblPr (table-level borders / style / shading captured opaquely); cells carry rawTcPr (per-cell borders, shading, vAlign).

Marks: cite_mark, underline_mark, underline_direct, emphasis_mark, undertag_mark, analytic_mark, plus direct formatting bold, italic, strikethrough, superscript, subscript, link, highlight, font_color, font_size, shading, pilcrow_marker, font_family, comment_range (anchors a thread to a range of text).

See src/schema/ for full specs and ARCHITECTURE.md §4 for design rationale.

Plugin API (experimental)

The desktop app can load plugins — one GitHub repo per plugin, publishing a manifest + built bundle as release assets. A plugin registers commands that appear in the command palette, the keybindings editor, and the printed shortcut reference like native ones, and can extract structured card data from the current selection, jump back to a card's source, and exchange messages with companion apps (such as a flowing app) over the local cardmirror-bridge — a loopback HTTP handshake, never the network.

Two things to know before writing one:

  • Plugins are full-trust code. They run inside the editor with the same access the editor has, so installs are limited to a curated allowlist (served by the relay; self-hosted relay operators curate their own via RELAY_PLUGIN_ALLOWLIST). For development, use "Load plugin from file…" in Settings → Plugins, or unlock arbitrary repos with __plugins('community-on') in the developer console.
  • The plugin-facing API surface is a draft (a sandboxed v2 may change it); the bridge handshake and HTTP routes are frozen.

The full contract — manifest fields, install flow, the capability API, and the bridge protocol — lives in reference-docs/cardmirror-plugin-api.md.

Acknowledgements

CardMirror is built on ProseMirror, the modular rich-text editor framework created and maintained by Marijn Haverbeke. Nearly every editor primitive CardMirror leans on — the schema-validated transactions, the typed-tree document model, NodeViews, plugin state, the keymap and history modules — is ProseMirror's.

The idea to use ProseMirror, as well as credit for dozens of course-corrections along the way, belongs to text editor wizard Slim Lim.

If you're curious how ProseMirror works under the hood, Marijn's launch post is the best high-level introduction, and the ProseMirror docs cover the APIs in depth.

Thank you to Marijn and the ProseMirror community for the years of careful library design that made this project tractable. If ProseMirror has been useful to you too, Marijn's work is supported directly at https://marijnhaverbeke.nl/fund/.

CardMirror's voice control was shaped by ideas pioneered in the hands-free-editing community — Talon, Cursorless, and Pokey Rule.

Features and code have been contributed by Shreeram Modi, Q Cooper, cora, and Neo Cai. Each contribution is credited where it shipped in the changelog.

Special thanks to Q Cooper and Missouri State debate for beta testing.

The app's interface icons are from the Untitled UI free icons, used under their free license. See THIRD-PARTY-NOTICES.md for the full third-party attributions and license terms.

License

CardMirror is licensed under the PolyForm Noncommercial License 1.0.0. You can read, fork, modify, and share the source for any noncommercial purpose (personal use, hobby projects, debate-team and academic use, research, government use, charitable / public-interest organizations); commercial use requires a separate license. See LICENSE for the full terms.

Underlying dependencies (ProseMirror and friends) ship under their own permissive licenses, preserved in node_modules/.

About

ProseMirror-based editor for competitive policy/LD/PF debate. Lossless .docx round-trip against Advanced Verbatim. Better multi-pane view compared to upstream, and several other small fixes.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages