Skip to content
paultechguyPublic

About

Local-first markdown editor for Windows 11: split-pane live preview, Monaco find-and-replace, Find All across every open tab, Mermaid diagrams, KaTeX math, spell check from your own dictionary, and self-contained HTML, PDF and Folio exports that open anywhere offline. No network calls, no telemetry, no account, ever. Free, open source, Apache-2.0.

Topics

Resources

Contributing

Stars

3 stars

Watchers

0 watching

Forks

Repository files navigation

Marqora logo

Marqora

A Markdown viewer and editor for Windows 11, built on .NET 10 and WinUI 3.

Source and preview live side by side with synchronized scrolling, mermaid diagrams render
inline, and everything runs locally: offline by default, no telemetry, no account.

No account gets created behind the scenes, no sync quietly turns itself on, and nothing you
write ends up sitting in a cloud you don't control — waiting for that server to be breached,
that company to be acquired, or that account to be phished. Nothing you write here goes
anywhere else.

Marqora in dark theme: markdown source on the left with spelling underlines, live preview on the right showing a rendered Mermaid flowchart, a table and typeset math.

paultechguy.github.io/Marqora


Download

Get the latest release — one zip, around 95 MB. Extract it into a folder of its own and double-click Install.cmd. It installs the app and marqora-cli, its command line, and asks whether to put the command line on your PATH; the answer is No unless you say yes.

Everything is per-user: no administrator, no UAC prompt, nothing written outside HKCU and your own profile. The .NET runtime and the Windows App SDK travel inside the zip, so the machine needs neither, and Marqora contacts nothing on its own at runtime. It is not code-signed, so Windows warns the first time; the release page says what you will see and why.

Staying current. Marqora does not check for updates, because checking would be a network call. What it keeps is a clock: every thirty days it puts a line in the status bar suggesting you look, and clicking it opens this page in your browser. Help > Check for Updates does the same thing whenever you want it, and the interval is a preference you can change or switch off. To hear about a release the moment it ships, watch the repository on GitHub — Watch > Custom > Releases — or subscribe to the releases feed in any feed reader.


Getting started

Requirements

  • Windows 10 1809 (17763) or later; Windows 11 recommended
  • .NET 10 SDK
  • WebView2 Runtime (pre-installed on Windows 11, and on Windows 10 via Windows Update)

First build

git clone <this repository>
cd PaulTechGuy.Marqora

# Third-party web assets are not in source control. Restore them once.
pwsh .\build\Get-WebAssets.ps1

dotnet build PaulTechGuy.MQ.slnx

The build fails with a clear message if the web assets are missing, so this step is hard to forget twice.

Run

dotnet run --project src\PaulTechGuy.MQ.App
# or open a document directly
.\src\PaulTechGuy.MQ.App\bin\Debug\net10.0-windows10.0.19041.0\win-x64\Marqora.exe README.md

Publish a copy you can move anywhere

dotnet publish src\PaulTechGuy.MQ.App -c Release

Release builds are self-contained and unpackaged: the output folder holds the .NET runtime, the Windows App SDK and the web assets, so it can be copied to another machine as-is. No MSIX, no signing certificate, no installer.

The output is about 256 MB. Three things Marqora cannot reach at runtime are deliberately left out — the Windows App SDK's machine-learning components, WinUI's non-English resource files, and Monaco's language services for TypeScript, CSS, HTML and JSON. That is roughly 68 MB. See docs/Architecture.md for how each is excluded and what not to trim further.

Build an installer for someone else's machine

pwsh .\build\New-Release.ps1

# gate the release on a green test run
pwsh .\build\New-Release.ps1 -Test

This is the one command that produces something shippable. It restores the web assets if they are missing, publishes, stages the app next to the installer scripts, and writes build\artifacts\Marqora-<version>-win-x64.zip with a .sha256 beside it. The zip is around 95 MB and has no wrapper folder — README.txt, Install.cmd, Uninstall.cmd, install\ and app\ sit at its root, so it extracts into a folder of the recipient's choosing rather than one of its own making.

The person on the other end extracts it and double-clicks Install.cmd. That installs to %LOCALAPPDATA%\Programs\Marqora, adds a Start menu and desktop shortcut, registers the markdown file types, and puts a real entry in Settings > Apps whose Uninstall button works. Everything is per-user: no elevation, no UAC prompt, nothing written outside HKCU and the user's own profile. Removing it is that Uninstall button, or Uninstall.cmd from the extracted folder, and either way settings and snippets under %LOCALAPPDATA%\PaulTechGuy\Marqora are kept unless -RemoveUserData is passed.

There is no code-signing certificate, so the installer strips the mark of the web from the installed copy. Without that, every launch of an unsigned executable that came out of a downloaded zip meets the full-screen SmartScreen panel; with it, the download is vetted once and the app then starts normally. The scripts live in build\installer\ and are Windows PowerShell 5.1 compatible on purpose — pwsh 7 is not on a stock Windows install, and needing to install a shell before you can install the app defeats the point.

docs/Installer.md records the design: what each script does, the constraints that are easy to break by accident, and what was deliberately left out.

Build a test copy for another machine

pwsh .\build\New-DevBuild.ps1 -Version 0.3.0-test4

The same zip and the same installer as above, built from the working tree as it stands and labeled with a version you choose rather than the one in Directory.Build.props. It lands in build\artifacts\dev\, a floor below the release artifacts so a test build is never picked up by hand in mistake for one the pipeline produced.

-Version is required, and a suffix is worth using. Without one the build inherits the repository's number, and what arrives on the far machine is an identically named zip reporting an identical version in Help > About — different bytes, no way to tell the two apart. The version you pass is stamped into the executable and written into the zip's README.txt alongside the branch and commit it came from.

Nothing else moves: no commit, no tag, no upload, and Directory.Build.props is left alone, so Publish-Release.ps1's version gate still means what it says. -Test runs the suite first, which is worth the wait for something another person is going to install.

One thing to know before installing one. It is the release installer, so it takes over the same install directory, shortcuts and file associations as a real Marqora on that machine — settings and snippets are kept, as across any upgrade, but the installed copy is replaced. The zip's README.txt leads with that warning.

Associate markdown files with Marqora

The installer above does this for you. Doing it by hand is for working in the source tree:

# after building or copying the app somewhere permanent
pwsh .\build\Register-FileAssociation.ps1 -ExePath 'C:\Program Files\Marqora\Marqora.exe'

# with no arguments it registers this repository's Release build
pwsh .\build\Register-FileAssociation.ps1

# and to undo it
pwsh .\build\Register-FileAssociation.ps1 -Unregister

This writes a ProgId and an Open With entry under HKCU, so no elevation is needed and nothing outside the current user changes. It also sets MultiSelectModel on the open verb, which is what lets Explorer hand a whole selection to one process instead of starting one process per file. Run it once per user; an installer should run it after copying the files, and pass -Quiet.

Windows does not allow an application to make itself the default handler — that choice is sealed with a per-user hash only the Settings UI can produce. The script makes Marqora available and keeps its command line correct; choosing it is a one-time manual step: right-click a .md file, Open with, Choose another app, Always.


Features

Everything below ships in the box, works offline, and is driven from the keyboard if you would rather not reach for the mouse.

Feature What you get
🔒 Yours alone Offline by default: no telemetry, no account, no sign-in, and nothing sent anywhere on its own. Your documents never leave the machine unless you send them
📑 Tabbed workspace Every document in its own tab, each with its own undo history, cursor and scroll position. Open a whole folder at once, drag tabs to reorder, and pick up exactly where you left off next launch
⚡ Live side-by-side preview Source and rendered output together, scrolling in lockstep — mapped through line numbers, so tall diagrams never throw the alignment off
✍️ Monaco editing The editor from VS Code: real find and replace, go-to-line, multi-level undo, line numbers, word wrap and per-pane zoom
🎨 A formatting bar that pays attention Bold, lists, headings and the rest, one click away — and the buttons light up for whatever the caret is sitting inside
🔍 Find All Every match in one window, across one tab or all of them, grouped by document. Walk the list with the arrow keys and the editor follows along
🔁 Replace All The same window with a replacement box, across every open tab at once. Regular-expression capture groups, a count to confirm against, and one undo per document
🧭 Outline panel Every heading beside the document, highlighting whichever section you are reading. Alt+4 shows it, Alt+Shift+4 puts the keyboard in it, and the arrow keys walk the document from there
🧹 One-key document formatting Sixteen independently switchable tidy-up rules, Shift+Alt+F to run them, Ctrl+Z to take the whole thing back. Never changes a single rendered word
📊 Mermaid 11 diagrams Flowcharts, sequence, class, state and the rest, rendered inline, re-themed with the app and cached so typing stays fast
∑ KaTeX math Inline and display math, laid out properly, exported the same way it looks
🌈 Rich markdown Tables, footnotes, task lists, definition lists, YAML front matter, auto-links, emoji, and syntax-highlighted code in both panes
🩺 Document problems Dead links, missing images and broken anchors underlined as you write — the preview renders a broken link exactly like a working one, so nothing else would tell you
🚫 Pictures that will not appear A picture on the web, or one kept in another folder, is marked and explained rather than left as a blank box — and Copy it in brings a stray one home beside the document in a click
🔤 Spell check Misspellings underlined as you type, corrections on Ctrl+. or a right-click, and a dictionary of your own that lives in a plain text file you can share. Windows' own words, so nothing is sent anywhere and nothing needs downloading
📤 Exports worth sending Self-contained HTML with fonts and images embedded, print-ready PDF with full page setup, a real Word .docx with proper styles and an updatable contents, and rich text on the clipboard for Outlook or Confluence
🧺 Folios Send a whole set of documents with the images they use. One .html anyone can read without Marqora — and drop it back in to get the Markdown out again
💻 Command line marqora-cli formats, checks links, exports and builds Folios for a whole folder, from a terminal or a script, through the app's own code. Its manual and recipes install with it, and Help > Command Line Manual opens them
💬 Review comments Comment on a document without changing it, diagrams included, then share one read-only page with your notes in the margin. Drop that page back in later to pick the review up again, pictures and all
🧩 Snippets and diagram starters A catalogue of ready-made blocks on the Insert menu, plus your own snippet files alongside them
📖 Cheatsheet at your elbow Ctrl+F1 opens a live markdown reference — real diagrams, real math — in a window you can leave open beside the editor
🌗 Light, dark or system Mica, an extended title bar and a theme that tracks Windows as it changes, diagrams included
🖌️ Color themes Palettes for headings, links, callouts, tables, code and diagrams, applied to the preview, to your exports, or to both, and previewed live from a gallery on the menu bar. Your files are never changed
🪟 One window, well behaved Single-instance by default, so "Open with" adds a tab instead of another copy. Drag files or folders straight onto it
👀 Files stay in sync Every open document is watched; change one outside the app and the tab reloads, or asks first if you have unsaved work
⌨️ Keyboard all the way down Alt drives the menu bar, every command has a shortcut, and Help > Keyboard Shortcuts... lists the lot with a button to copy them
🧳 Preferences that travel Every setting on six pages, applied as you change them and undone by Cancel — and exportable to a file you can import on another machine, whichever version of Marqora is on it

The rest of this section covers each of these in detail.

Opening documents

  • File menu, Ctrl+O, or drag files onto the window — each opens in its own tab. The Open dialog takes several files at once (Ctrl+click or Shift+click); they open sorted by name, leaving the first one active, and any that cannot be read are named afterwards
  • A Folio among the files you pick or drop is unpacked, and everything else in that pick or drop is ignored — the Folio is taken as the whole of what you meant. Open the other files separately, before or after. Only one Folio is unpacked at a time
  • File > Open Folder... (Ctrl+Shift+O) opens every markdown file in a folder, one per tab, sorted by name and leaving the first one active. Dropping a folder does the same
  • Dropping onto the preview works too, by a different route (see Architecture)
  • Recent files list with pinning, shown both in File > Open Recent and on the start screen. Clear all on the start screen — and File > Open Recent > Clear Recent Files... — empties it after confirming, offering to spare pinned entries when there are any
  • Command line: Marqora.exe path\to\file.md ..., so "Open with" works
  • Supported extensions: .md, .markdown, .mdown, .mkd, .mdx, .txt
  • Opening a file that is already open switches to its tab rather than duplicating it
  • One window. Marqora is single-instance: opening a document while it is already running adds a tab to the window you have, and brings it forward, rather than starting a second copy. Marqora.exe --new-instance opts out when you do want two

Opening a folder is deliberately not recursive — subfolders are ignored, so pointing it at a repository does not produce hundreds of tabs. It also skips .txt, which is fine to open by name but would sweep up unrelated files from a folder. Past 25 files it asks first.

Links in the preview

A link in the preview to something on your own disk — [Merge strategy](MERGE-STRATEGY.md#incident-history), ../specs/plan.md, budget.xlsx — is opened, not handed to a browser as an address that goes nowhere.

A link to What happens
A markdown document It opens in a tab, or comes forward if already open, at the heading the link names
Any other file — a PDF, a spreadsheet, a picture It opens in the app Windows uses for it
A folder It opens in Explorer
A file that is not there A message says so, with the path it was looking for
A program — an .exe, a script, a shortcut Always asks first, because a link's words can say anything

View > Preferences > Preview > Links can show other files in their folder rather than opening them. Markdown documents always open in Marqora, whichever way that is set. Links to the web open in your browser as before.

Tabs

Documents open as tabs in the title bar. Each tab keeps its own undo history, cursor and scroll position, so switching between them is instant and loses nothing.

Action How
New empty document Ctrl+N or Ctrl+T, or the + button
The tab's own menu Right-click a tab
Close a tab The tab's ×, middle-click it, Ctrl+W or Ctrl+F4
Close all Ctrl+Shift+W
Close others Right-click the tab, or File > Close Other Tabs
Reorder Drag a tab along the strip
Select tab 1–8 Ctrl+1 … Ctrl+8
Select last tab Ctrl+9
Next / previous Ctrl+Tab / Ctrl+Shift+Tab

A tab with unsaved changes shows a dot before its name and prompts before closing. New documents are named Untitled 1, Untitled 2, and so on until the first save.

Right-clicking a tab selects it and offers what the File menu offers for that one document — Save, Save As, Reload from Disk, the three Close commands, Open in File Explorer and Copy Full Path. It is the quickest way to ask where a file lives when several tabs come from different folders. Right-clicking the empty part of the strip is right-clicking the title bar, so Windows puts up the window menu there instead.

Your open tabs are reopened next time you start, along with which one was active. Untitled documents are not restored, because they have no file to restore from.

Menus

The menu bar is text only. The four file commands worth a click rather than a walk through a menu — Open, Open Folder, Save, Save All — sit first on the formatting bar below it, ruled off from the formatting proper. Save and Save All are lit only when there is something to write: Save when this tab has unsaved changes, Save All when any tab does.

Alt on its own puts the keyboard on the menu bar. From there the arrow keys walk along it and Enter opens a menu, so nothing has to be memorized to drive the app without a mouse.

Each menu also opens directly: Alt+F File, Alt+E Edit, Alt+O Format, Alt+V View, Alt+T Tools, Alt+H Help. Format takes O because File has F, as Windows menus have always split those two.

Help > Keyboard Shortcuts... lists every shortcut in the app, with a button that copies the lot — as a table for anything that keeps formatting, and as aligned columns for anything that does not.

Views

View Shortcut
Source only Alt+1
Split Alt+2
Preview only Alt+3

View mode, zoom and word wrap are application-wide rather than per tab, matching how editors and browsers behave. Ctrl+1–Ctrl+9 belong to tab selection.

In split view the panes scroll together, mapped through source line numbers rather than scroll percentage, so tall diagrams and images do not throw the alignment off. Drag the divider to resize. Double-clicking evens the split up again — either on the divider itself or on the Split button on the menu bar, which is a much larger target than a six-pixel rule.

The menu bar row thins out as the window narrows rather than clipping: below about 900px the view switcher tightens and the scroll-sync toggle steps aside, and below about 720px the switcher and the zoom readout go too. Nothing becomes unreachable — view modes stay on the View menu and on Alt+1–Alt+3, and Ctrl+0 still resets the zoom. The formatting bar below sheds on its own schedule, at about 990px and 780px.

Outline

View > Outline lists the document's headings in a panel down the left-hand side, indented by level. It is off until you ask for it, and remembered after that.

Action How
Show or hide the panel Alt+4, or View > Outline. Also the button at the end of the filter row to hide it, and the strip left at the window's edge to bring it back; double-clicking the empty space below the headings, or anywhere on the strip, does the same
Go to the panel, and back again Alt+Shift+4
Jump to a heading, staying in the panel ↑ / ↓, or a single click
Jump to a heading and start editing there Enter, or a double-click
Narrow the list Type in the filter box
Leave the panel for the document Escape
Copy a heading Ctrl+C with the panel focused

Visibility and the keyboard are two separate questions, so they get two separate keys. Alt+4 shows and hides. Alt+Shift+4 takes you to the panel and brings you back, opening it first if it was closed — which is what you want when the outline is already on screen and you are typing, where a visibility toggle could only close the thing you were reaching for. It is the same split VS Code makes between showing the sidebar and focusing it.

Opening the panel puts the keyboard in it, on whichever section you are currently reading, on the grounds that asking for the outline is usually asking to use it. Escape hands the keyboard back and leaves the panel open. This is the only View menu item that moves the focus; the rest leave you where you were.

The highlight follows what you are reading — the caret in the source pane, the top of the viewport in preview — so the panel says where you are as well as where you can go. Clicking a heading moves both panes, since the jump is by source line like everything else here.

View > Preferences > Preview limits how deep the list goes if a document's fourth-level headings are more than you want to see. The panel is chrome rather than part of the preview, so it never appears in a printed page or an exported PDF.

Because the outline is a place to work rather than a menu you pass through, the commands that edit at the caret — the whole Format menu and formatting bar, plus Cut, Paste and Select All — gray out while the keyboard is in it. There is no caret on screen to apply them to, and applying them to one you cannot see is how documents get changed by accident. Everything else keeps working: Save, Undo, the Find family, view modes and zoom all behave exactly as usual, and zoom still targets the last pane you were in.

Heading numbers

View > Preferences > Preview numbers your headings — 1, 1.1, 1.1.1 — in the preview and in the outline panel together, starting from whichever level you choose. Nothing is written into your markdown; the numbers live in the rendered copy.

Someone else's document often numbers its own headings in the text, though, and then you are reading two sets at once that stop agreeing the moment the author skips a section. View > Heading Numbers settles it for that document alone, and every other tab carries on as it was with the preference untouched. Alt+5 drops Marqora's numbers and leaves the author's — and turns them back on again, which is how you compare the two.

Often the author has simply started counting somewhere else: their numbers begin at ## while yours begin at #, so the two are a component apart on every heading and dropping yours is more than you wanted. Alt+Shift+1, Alt+Shift+2 and Alt+Shift+3 say where this document's count starts, the same three levels the preference offers. Having named one, Alt+5 toggles against it, so you can flick the numbers off to read the author's and back on to where you had them.

It lasts as long as the tab does, because it is a reading convenience rather than a setting. What you see is what you export: a Word document or a Folio carries whatever the tab was showing, settled per document, so one self-numbered chapter in a twelve-document Folio does not unnumber the other eleven.

Zoom

Action Shortcut
Zoom the active pane Ctrl + / - / 0, or Ctrl+wheel over it
Zoom both panes together Ctrl+Shift + / - / 0, or Ctrl+Shift+wheel

Each pane remembers its own level between sessions.

Editing

Full Monaco editing with find, replace, go-to-line, undo and redo, all reachable from the Edit menu with the usual shortcuts. Ctrl+S saves, and saving a new document asks where to put it. File > Save All (Ctrl+Shift+S) writes every tab that has unsaved changes in one go, asking where to put each untitled one. Both go dim when there is nothing to write. Closing anything with unsaved work prompts first.

Cut, copy and paste are handled by the app rather than by the editor. A browser only permits clipboard access during a genuine user gesture, and a click on a native menu is not one, so the menu items would silently do nothing otherwise.

Find All

Edit > Find All... (Ctrl+Shift+F) opens a window listing every match at once, in the active tab or across every open one, grouped under the document each came from. Ctrl+F is still the editor's own find bar for stepping through one match at a time; this is for the question "where does this appear?".

Options Match case, whole word, regular expression — the same three the find bar offers
Scope Active tab, or all open tabs
Run it Enter in the search box, the Find All button, or F5 to run it again
Step the results F3 and Shift+F3 move to the next and previous match, skipping the document headings and wrapping at either end
Go to a match Select a row. The source pane switches tabs if it has to, scrolls the line into view and selects the matched text
Start again Clear, or the search box's own clear button — either empties the term and the results together
Dismiss Esc. The window hides rather than closing, so the results are still there next time

Selecting a row shows the match without taking the keyboard, so the arrow keys walk down the list with the editor following along. Enter or a double-click goes there properly and hands the keyboard to the text.

Results are a snapshot. Editing a document that was searched puts an amber Documents have changed notice on the status line — search again to update it — rather than reshuffling the rows while you are reading them; closing a document grays its results out. Going to a match that has since moved still lands on the right text: the match is looked for again before anything is selected, so a row can never quietly select the wrong thing.

Regular expressions are .NET's, matched a line at a time — a pattern cannot span a line break, and ^ and $ anchor to the line. A pattern that will not compile is reported in place of the results, and one that runs away is cut off rather than hanging the window.

Replace All

Edit > Replace All... (Ctrl+Shift+H) is the same window with a Replace with box folded out — the chevron beside the search box opens and closes it, so you can switch between finding and replacing without reopening anything. Ctrl+H is still the editor's own replace, for one document at a time; this one covers every open tab.

Run it Fill both boxes and press Replace All. It runs the search itself, so there is no need to find first
Confirm It asks before changing anything, naming how many matches and which document — or how many documents
Capture groups In regular-expression mode the replacement can use $1, $2, ${name} or $&. The icon beside the box lists them. In plain text mode the replacement is inserted exactly as typed
Deleting An empty Replace with box removes every match, and the confirmation says so
Undo One Ctrl+Z per document, whether or not that tab is the one on screen

Nothing is written to disk: each document it touches simply becomes unsaved. A search matching more than 5,000 times is refused rather than confirmed, since it stops scanning part-way and would rewrite an arbitrary part of the workspace.

Writing markdown

The Format menu applies markdown constructs so you do not have to type the punctuation.

Command Shortcut
Bold / italic / link Ctrl+B / Ctrl+I / Ctrl+K
Link to a heading in this document Ctrl+R
Inline code Ctrl+`
Strikethrough Ctrl+Shift+X
Code block Ctrl+Shift+K
Blockquote Ctrl+Shift+.
Bullet / numbered list Ctrl+Shift+8 / Ctrl+Shift+7
Heading level up / down Ctrl+Shift+] / Ctrl+Shift+[
List indent in / out Tab / Shift+Tab in a list, or Ctrl+] / Ctrl+[
Heading 1–6, task list, table, horizontal rule Menu only

Everything toggles except the two indent commands, which change a list item's depth rather than marking it. Ctrl+B with nothing selected wraps the word under the cursor, and pressing it again takes the markers off. Applied to several lines at once, a list or quote marks every line the selection touches, and clears them only when all of them already carry it — a part-marked selection gets finished rather than emptied. Switching between bullets, numbers and headings replaces the marker rather than stacking a second one in front of it.

Ctrl+K uses whatever is selected for the half it can work out: selected text becomes the label and leaves url selected to type over; a selected URL becomes the destination and puts the cursor between the brackets.

A link to a heading in the same document needs no anchor typed by hand. Press Ctrl+R, choose Insert > Reference to Heading..., or right-click in the source pane and choose Insert Reference...: a list of the document's headings opens at the caret, or where you clicked, narrowed as you type, and picking one writes the link at the caret — Name gives [Installing the app](#installing-the-app), Number gives [2.3](#installing-the-app). With text selected, that text stays and becomes the link, [the setup steps](#installing-the-app). A number is written as it reads at that moment: section numbers belong to the preview rather than the file, so it does not follow the heading if sections are added above it later. The link itself still lands on the right heading, and one whose heading is renamed or removed is underlined like any other dead anchor.

Pressing Enter inside a list carries the list on to the next line, numbering as it goes. Pressing it on an item you have not written anything in ends the list instead.

Tab and Shift+Tab move a list item in and out a level. The marker lands on the column its parent's text starts in, which is what actually nests it — two spaces under a -, four under a 10. — and everything underneath the item comes with it, so a branch keeps its shape. Numbered lists renumber on both sides of the move. Select a nested set and Shift+Tab peels a level off it each press — an item already at the margin stays there rather than holding the rest back — so flattening is repeated presses on one selection, not one selection per level. The first item of a list has nothing to nest under, so it stays put; anywhere that is not a list, a fenced code block included, Tab is the Tab key.

Explicit heading levels are on the menu without shortcuts. Ctrl+digit belongs to tab selection, and Ctrl+Alt+digit is indistinguishable from AltGr+digit on European keyboards, where it types a character.

The same commands sit on the formatting bar under the menu bar, which is live: the buttons light up for whatever the caret is inside, and the heading control reads H2 on an H2. Each says what it would do rather than what the text is, so a lit button always turns itself off. The two indent buttons are the exception and never light: depth is not a state a line is in. The bar runs Open · Open Folder · Save · Save All | undo · redo | bold · italic · strikethrough · code · link · code block · rule | lists · blockquote · outdent · indent | Heading ▾ | Snippet ▾, where Snippet holds the catalogue in name order, broken once under a Your snippets heading where your own files start. It is the one dropdown left on the bar, and it is here because snippets are the one thing on it you extend yourself — everything else the bar can do it does in a single click. Tables and diagrams are on the Insert menu, which is where you go for the things you reach for occasionally. Blockquote stays a button rather than a menu entry because it toggles — like the three list commands, it marks every line the selection touches and lights up when the caret is inside one, which is worth knowing without opening anything. The formatting half is disabled in Preview-only view, where there is no source pane to edit — the Format menu and the shortcuts go quiet with it — but the four file commands stay live, because opening and saving are as reasonable while reading as while writing.

As the window narrows the bar hands whole groups to a » button rather than clipping: the snippet menu goes first, then the file commands, the lists with the indent pair, and the heading control. Undo, bold, italic, code, link, code block and rule never leave, the » appears only when something is actually hidden, and nothing becomes unreachable — the file commands have no » entry, because Open and Save don't belong under "More formatting", but the File menu and Ctrl+O / Ctrl+S still have them.

The menu bar splits the same commands a different way, along a line the bar does not draw: Format marks up text that is already there, and Insert (Alt+I) puts something new in at the caret — an image, a link, a link to a heading, a table, a code block, a rule, a diagram or a snippet. Nothing in Insert needs a selection to mean something and everything in Format does, which is the difference the two menus are named for. Two places in Insert will use one if it is there: Reference to Heading... makes the selected text the link, and the five callouts take what the section below describes. Within each, the order is a flat inventory rather than the bar's grouping: that is what you want from the surface you go to when you cannot find something on the bar.

Callouts take the text you are looking at

Insert > Callouts holds the five GitHub alerts — Note, Tip, Important, Warning and Caution. Reaching for one with text selected puts that text inside it, rather than dropping an empty callout above it for you to retype into. A few words out of the middle of a paragraph splits the paragraph: what was before and after them stays where it was, reading correctly on either side. With nothing selected, the paragraph the caret is in goes in whole, so turning a paragraph into a callout is a click with no selecting first. A heading does the same.

A caret in a list, a table or a code block captures nothing and inserts an empty callout as it always did — not because those cannot go inside one, but because clicking Note with the caret somewhere in a long table should not silently move the table. Selecting it first still wraps it, selection being the thing that says you meant it.

Your own snippet files can do this too. $SEL anywhere in the file is where the captured text goes, and $0 is where the caret ends up:

<details>
<summary>$0</summary>

$SEL

</details>

Without a $SEL a snippet behaves exactly as it always has, which is why none of the ones that ship with Marqora carry one except the callouts — a captured paragraph inside a Mermaid fence is not a diagram. Write $$SEL or $$0 for a literal one.

Formatting

Edit > Format Document (Shift+Alt+F) tidies the current file. With text selected it formats just those lines. Edit > Format Markdown... opens the rule list; Edit > Format All Open Documents does the whole set of tabs at once.

Sixteen rules, each independently switchable, because "tidy" is a matter of taste:

Heading space Blank lines Collapse blanks
Trailing whitespace List marker space Ordered numbering
Normalize markers Link syntax Blockquote space
Line endings EOF newline Table formatting
Code fences Underlined headings Emphasis markers
Re-wrap paragraphs

Two are off by default. Normalize markers rewrites every bullet to one character, and re-wrap paragraphs reflows prose to a column you choose (80 by default) — that one rewrites every line of every paragraph, which ruins one-sentence-per-line writing and turns a one-word edit into a diff covering the file. Re-wrapping skips lists, quotes, tables and code, where a wrong continuation indent would change the structure rather than just the layout.

Three guarantees hold whatever the rules say:

  • Formatting never changes what a document renders to. It moves whitespace and punctuation, never words.
  • The inside of a fenced code block and YAML front matter are never touched, including their trailing spaces and blank lines.
  • Ctrl+Z takes a whole reformat back in one step. The editor is edited rather than reloaded, so the undo history survives.

Format automatically when saving in the rules dialog runs the formatter before every Ctrl+S. Off by default: it is a real convenience, but it does mean saving can change lines you did not edit.

Exporting

Export What you get
Edit > Copy as Rich Text (Ctrl+Shift+C) The preview on the clipboard, formatting intact, for pasting into Word, Outlook or Confluence
Tools > Export to PDF... The document laid out into pages at true size: page numbers, footnotes at the foot of each page, and optionally a title page and contents shared with Word. The dialog also offers paper size, orientation, margins, and whether to shade code, tables and callouts
Tools > Export to HTML... One self-contained .html file
Tools > Export to Word... A real .docx: Word's own heading styles, numbering, tables, footnotes and equations, with page setup of your choosing
Tools > Share as Folio... Every open document, and every image they use, as one thing to send
Tools > Open Folio... A Folio unpacked back into its documents and images
Review > Share Review... The document with your comments in its margin, one read-only page to send back
Review > Resume Shared Review... A page you shared, taken back up: the reviewed text, its pictures, and every comment where it was

All four exports are enabled whenever a document is open, and the three file exports default the filename to the document's own name with the new extension.

Copy as Rich Text takes whatever is selected in the preview, or the whole document when nothing is. It puts two things on the clipboard at once: the formatted version for anything that understands formatting, and the markdown source for anything that does not, so pasting into a terminal or a code editor still gives you markdown. Colors are resolved to fixed values on the way out, because the stylesheet is built on CSS custom properties and Word has never supported them.

Exports come from the live preview, not from a fresh render, so diagrams are already inline SVG, math is already laid out and code is already highlighted — what you export is what you were looking at. The HTML export inlines the stylesheets, embeds local images as data URIs and embeds the KaTeX fonts, so the file survives being emailed on its own. An image too large to embed is named rather than quietly left as a link. Exports are always light-themed: a dark background is rarely wanted in something printed or pasted into someone else's document. Their width follows Maximum width on the Appearance page — the same setting the preview uses, and unset by default, so an exported page fills the window.

Word

Tools > Export to Word... is the one export that does not carry the preview across, because a .docx is not a web page. The document is read again and written into Word's own constructs: Heading 1 to 6 rather than large bold text, so the navigation pane finds your sections and a contents field can collect them; tables it can resize; footnotes at the foot of the page; and equations you can click into and edit. The colors are your color theme's, the one exports use, in its light palette. The document theme carries them as well, so Word's own galleries, such as a new table or a chart, start from the same colors.

Numbering is Word's own — for lists, and for numbered headings when you have those switched on. Section numbers are attached to the heading styles the way Word's own Multilevel List → link to Heading styles does it, not typed in front of the text, so adding a section in Word later renumbers everything after it. The one consequence is that Word counts for itself from then on: a document that skips a heading level — a level-three heading directly under a level-one — can come out numbered differently from the preview, because Marqora drops the missing level and Word does not.

Only the three things a browser alone can produce come from a browser — the diagrams mermaid drew, the layout KaTeX gave the equations, and the colors highlight.js put on the code — and it is one of the export's own, never on screen, drawing exactly the text being exported, so what the preview happens to be doing has no part in the file. Everything else comes from the Markdown. Which is why this export never refuses: if that browser cannot draw something, you get the document without it rather than an error. Anything that could not be carried across — a picture that is not on this machine, a diagram that would not draw — is named when the export finishes rather than left to be discovered by whoever you sent it to.

The dialog asks for paper, orientation and margins, and for three things only Word has: a header and page numbers, a table of contents, and a title page. The title page is a template rather than a statement: title, subtitle, date, version and author, each filled in from your front matter where it has something to say and left as a gray word to type over where it does not. The last two are off by default; both put content into the document that your Markdown did not contain.

With both switched on the file is cut into three Word sections, which is how a report is built: the title page carries neither header nor page number, the contents number themselves i, ii, iii, and the body begins again at 1. The footer is the number by itself - i on the first contents page, 1 on the first page of the body.

The margin presets are Word's own — Normal, Narrow, Moderate, Wide and None, with Word's measurements, and the PDF export uses the same five. They did not always: Normal meant half an inch on one dialog and a whole one on the other, so the same document exported both ways came out on two different measures under one word.

The margin is still the one page setting not carried over from your PDF choices, because a document meant to be edited and one meant to be printed want different margins often enough that inheriting the answer is a worse guess than starting from Word's default. Paper and orientation are carried over, so somebody who works in A4 does not have to say so twice.

Folios

A Markdown file is not self-contained, and a folder of them is worse: the text goes in the email and the pictures do not. Tools > Share as Folio... takes the documents you have open, collects every image they reference — including ones stored somewhere else entirely — and repoints the paths so the copy still resolves on somebody else's machine.

Form For
One file others can read without Marqora A single .html: every document in one page with a contents list, images inlined, diagrams and math already drawn, and no script in it at all. Opens by double-click in any browser, offline
A folder of markdown and images Someone who is going to keep editing, or a copy that has to survive being moved
One zip of that folder The same, as one file to send

A preflight window shows what will travel before anything is written: how many documents and images, what it weighs, which images are missing, and which links point outside the set. Tick what goes in, and drag the rows to set the order the documents appear in — Alt+Up and Alt+Down do it from the keyboard. The window resizes and remembers its size.

It also says when the shape is wrong. A single page carrying hundreds of pictures, or one past what mail will take, offers the zip instead; images far wider than any screen can be sent smaller, which is off by default and names every picture it would touch before touching one.

The single file carries its own sources. They ride in a block browsers neither run nor display, so a reader sees an ordinary web page — but Tools > Open Folio..., or dropping that same file onto the window, unpacks it back into the documents and images it was made from. One artifact for both audiences: it does not matter which of the people you send it to have Marqora. A Folio is unpacked on its own: picked or dropped together with other files, the Folio is unpacked and the other files are ignored.

None of this goes near the network. A Folio is a file, and sharing it is whatever you already do with files.

Markdown cheatsheet

Help > Markdown Cheatsheet (Ctrl+F1) opens a small floating reference showing every construct Marqora renders, with the syntax beside its result — live mermaid diagrams and KaTeX included. Choosing it again hides it; if it is open but buried behind the editor, it comes forward instead. The menu item is ticked while it is on screen, and unticks if you dismiss the window with its own close button.

It is a real window rather than a dialog, so you can leave it beside the editor and keep typing. Its size, position and scroll offset are remembered between sessions, though it always starts closed. It wears the color theme you chose, so its examples look the way your own documents do, and it changes with the theme while it is open. The content is webshell/cheatsheet.md, rendered through the same pipeline as the preview — edit that file to change what it says.

Welcome document

The first launch of each new release opens Welcome to Marqora — a stock document covering what the app does and how it is driven — in preview view, as the tab in front. Tabs from the previous session are restored as usual and are left where they were; only the focus differs. A file named on the command line still wins: it opens last, keeps the focus and keeps your view mode, and the welcome document waits in a tab beside it.

The master ships as src/PaulTechGuy.MQ.App/Assets/Welcome to Marqora.md and is copied to %LOCALAPPDATA%\PaulTechGuy\Marqora\Welcome to Marqora.md before it opens, so the tab points at a file you can edit, save, or close and forget. The name never changes and each release overwrites the copy, which is what keeps the document a description of the version actually running.

Preview view here is for that document only and is not written to settings — the view mode you chose is still yours, and comes back the next time you start.

Hold Shift while starting Marqora to open it any time, whether or not this version has already shown it. That also replaces your copy with the shipped one, so it is the way back from having scribbled on it. A Shift launch outranks a file on the command line: the welcome document opens last and takes the focus, because holding Shift is asking for it in as many words. It applies to the instance that is starting — if Marqora is already running, the launch is handed to that window and the gesture is only noted in the log.

What has been shown is recorded as lastWelcomeVersion in settings.json; deleting that key brings the document back on the next launch, the same as holding Shift.

Help

Help > About Marqora shows the version, runtime, where the data and logs live, and the bundled third-party components. Its Copy details button puts all of that on the clipboard, which is what a bug report needs. Help > Open Log Folder goes straight to the logs.

Markdown support

Tables, footnotes, task lists, definition lists, YAML front matter, auto-links, emoji, and math via KaTeX. Fenced code is syntax-highlighted in both panes. Relative image and link paths resolve against the document's own folder.

Mermaid diagrams

Fenced ```mermaid blocks render inline. Flowcharts, class diagrams, sequence diagrams and the rest of mermaid 11 are supported, and diagrams re-render on theme change. Rendered SVG is cached by diagram source, so typing elsewhere in a diagram-heavy document does not re-render the diagrams.

A diagram too wide for the page can be laid out the other way. With the cursor in its fence, choose Format > Diagram Layout > Top to Bottom or Left to Right, or right-click inside the fence in the source pane for the same submenu. Mermaid draws it again in that direction, text upright, and every print, PDF, Word export and copy follows. It rewrites the direction the diagram's definition gives — flowchart LR becomes flowchart TB — and nothing else, so Ctrl+Z takes it back. Flowcharts, state, class, ER and requirement diagrams, git graphs and timelines have a direction; on a sequence diagram, a Gantt chart or another type without one, the item is grayed.

Checking a document

View > Show Problems underlines things that are wrong, in the source pane. It is on by default, because none of it shows up any other way: the preview renders a dead link exactly like a live one.

Underlined Why
[text](./gone.md) Nothing at that path, relative to the document's own folder
![alt](missing.png) Same, for an image
[text](#no-such-heading) No heading in this document produces that anchor
##Heading, -item, >quote, trailing spaces, [text] (url) Syntax the formatter would tidy up

The first three are warnings; the style rules are hints, because Edit > Format Document fixes all of them on request. A document that has never been saved has no folder for a relative path to resolve against, so its file links are left alone, though its anchors are still checked.

Nothing inside a fenced code block or YAML front matter is ever flagged.

Pictures that will not appear

View > Show Blocked Images underlines a picture Marqora cannot show you, and the hover says which of the two reasons it is. On by default, because nothing else in the app explains a blank box.

Underlined Why
![alt](https://example.com/shot.png) Marqora does not load content from the web
<img src="https://example.com/shot.png"> The same, written as HTML
![alt](C:\Users\you\Pictures\shot.png) The file is real, but it is not beside the document
![alt](../shared/logo.png) The same — it resolves to a file, just outside the document's folder

Right-click one for what can be done about it. A picture on the web can be opened in your browser, copied as an address, or written as the link it effectively is; one sitting elsewhere on the disk offers Copy it in, which copies the file in beside the document, points the reference at it, and is the one repair that fixes the problem outright. Either can be replaced from a file, pasted over from the clipboard, or removed.

A link is never underlined by this, however far away it points. Nothing is fetched until you click one, and clicking works — Marqora hands it to your browser. The rule is about what the preview would have to go and load by itself.

Nor is a web address ever checked. That would mean going to the network, which Marqora does not do; the mark says the picture will not appear here, and says nothing about whether it works anywhere else. Usually it does — a README full of build badges is not wrong, it simply renders somewhere Marqora is not.

This has its own switch precisely because of those badges. Quieting a row of eight should not cost you the dead links and the broken anchors, so Show Problems keeps those.

Spell check

View > Spell Check (F7) underlines words that are not in the dictionary. It is on by default. Right-click one — or press Ctrl+. — for corrections, to add the word to your dictionary, or to delete a word you have typed twice in a row.

The words come from the spelling dictionary Windows already has, so there is nothing to download and nothing leaves the machine. If Windows has no dictionary for your language, spell check stays quiet and the setting grays out to say why.

A document about software is mostly not prose, so most of what is on screen is never checked:

Never checked Checked
Fenced, indented and inline code The text of a link, and a reference's title
Link and image targets, autolinks, bare URLs The alt text of an image
HTML tags and attributes, entities Everything else
Math, footnote markers, emoji shortcodes
YAML front matter
SDK, win-x64, MainViewModel — acronyms, anything with a digit, camelCase names

Your own dictionary is a plain text file, one word per line, at %LOCALAPPDATA%\PaulTechGuy\Marqora\user-dictionary.txt. Lines beginning # are comments. Open it in Marqora and correct it like any other document — it is re-read the moment you save. Keep it in a project beside the documents it belongs to, review it in a diff, and export or import it from the Advanced page of Preferences; an import adds words and never removes any.

A word in your dictionary stays known when you make it possessive, so adding Marqora covers Marqora's too.

Appearance

System, Light or Dark, under View > Theme. System follows Windows and tracks changes made while the app is running. The window uses Mica and an extended title bar.

Color themes

A color theme decides how Marqora colors markdown: headings, links, callouts, tables, code and its syntax, lists, quotes, highlights and mermaid diagrams. They ship in the box: Default, Ember, Forest, Nordic, Ocean, Orchid, Pastel, Slate, Sunset, Sweetheart and Vintage Press, each with a light and a dark palette. One theme is in force for every document, and your files are never changed.

The palette button at the right-hand end of the menu bar, next to the zoom buttons, opens the gallery. Point at a theme, or reach it with the arrow keys, and the preview shows it at once; scroll the document to see more of it, click to keep it, or close the gallery to go back. The chevrons move along the row. The same choice is on View > Color Theme and under Preferences > Appearance.

Everything that leaves the app uses the theme too, in its light colors: print, PDF, HTML, Folio, Word, a shared review and the clipboard. To keep a colorful screen and send plainer files, choose another theme under Preferences > Appearance > For exports. A diagram that names a mermaid theme of its own keeps it.

New themes are one JSON file each; see docs/ColorThemes-Authoring.md.

Other View options

Word wrap (Alt+Z), line numbers, show whitespace, and markers showing where a wrapped line continues. Window size, position, split position, view mode and every toggle persist.

Status bar

Along the bottom: the last thing that happened on the left, and on the right the cursor's line and column, the word count and the character count, all of which appear only when a document is open.

Files on disk

Every open document is watched independently. If one changes externally and you have no unsaved edits in it, that tab reloads silently; if you do, Marqora asks first.

Preferences

File > Preferences... gathers every setting in the app onto six pages — Appearance, Editor, Preview, Files, Export & Print and Advanced. Changes apply as you make them, so you can see a font or a theme before committing to it, and Cancel puts all of them back. Five settings wait for OK instead: the recent-files limit, autosave and its delay, log retention, and the update reminder. The first four act on your disk rather than only describing how things look, so Cancel could not undo them after the fact; the last has nothing to show until its clock runs out.

Carrying preferences to another machine

On the Advanced page, Export preferences... writes every preference to a JSON file, stamped inside with the version of Marqora that wrote it and the machine it came from. Import preferences... reads one back.

The name offered carries the date and time it was taken — Marqora-preferences-2026-09-01-143022.json — so exporting again leaves a second file rather than an overwrite prompt, and a folder of them sorts into the order you took them.

Your open documents, window position, splitter, recent files and search history are not included. They describe the machine rather than your preferences, and an import never disturbs the ones on the machine you are importing to.

The two machines do not have to be on the same version. Import applies everything the running build understands and then says what it could not use — a setting the file's Marqora had and this one does not, a setting this one has that the file predates, a value outside the range Marqora allows. A settings.json copied straight off the other machine is accepted too.

Like every other change in the dialog, an import is undone by Cancel.

The update reminder

Marqora never asks GitHub whether a newer release exists, so it cannot tell you — not because a version number is precious, but because nobody asked it to, and the app does nothing on its own. What it has instead is a clock, set on the Advanced page: after this many days a line appears in the status bar suggesting you look, and clicking it hands the releases page to your browser. Zero never reminds you.

It is a live reminder rather than a check at startup, because Marqora is the kind of program people leave open for weeks. Two dates are compared on a slow tick rather than a countdown being run, which is what makes it survive sleep, hibernation and a session left open across a month — a timer set for the interval does not fire while the machine is suspended, so a laptop shut for a fortnight would come back a fortnight late.

It waits for a pause in your typing before it appears, it gives way to the changed-on-disk notice when both have something to say, and appearing is what resets the clock — ignoring it does not bring it back tomorrow. Installing a new version resets it too. Help > Check for Updates opens the same page at any time and resets it the same way, and About Marqora says when the next reminder is due.


Command line

marqora-cli.exe ships beside Marqora.exe and does what the Format, Find, Export and Share menus do, without a window, through the same code, so a file formatted from a terminal is formatted exactly as Format Document would have formatted it, and a PDF is the one File > Export > PDF would have made. The installer offers to put it on your PATH.

# Is every document formatted, and does every link still lead somewhere?
marqora-cli format --check "docs/**/*.md"
marqora-cli check "docs/**/*.md"

# A PDF beside every document, on A4
marqora-cli export pdf --paper a4 "docs/**/*.md"

# Share a handbook as a Folio, its pictures reduced to 1600 pixels wide
marqora-cli folio -o Handbook.zip --shrink-images 1600 docs/handbook/*.md

It is offline by default, like the app: folio --fetch-web-images is the only option that uses the network, and --dry-run names the sites first. Without --settings, it uses the built-in defaults rather than your Preferences, so the same command gives the same result on every machine. Every command takes --json, and the exit codes are fixed, for scripts and Git hooks. docs/CLI.md is the manual, with a five-minute tour, and docs/CLI-Recipes.md has recipes for everyday jobs in PowerShell, cmd and bash. Both are installed with it, in the docs folder beside marqora-cli.exe; Help > Command Line Manual opens the manual in Marqora, and every command's --help ends by saying where it is.


Where things are kept

Path Contents
%LOCALAPPDATA%\PaulTechGuy\Marqora\settings.json Preferences and window placement
%LOCALAPPDATA%\PaulTechGuy\Marqora\recent-files.json Recent and pinned files
%LOCALAPPDATA%\PaulTechGuy\Marqora\snippets\ Your own snippets, one per file
%LOCALAPPDATA%\PaulTechGuy\Marqora\logs\ Rolling logs, 14 days
%LOCALAPPDATA%\PaulTechGuy\Marqora\WebView2\ WebView2 cache
%LOCALAPPDATA%\PaulTechGuy\Marqora\Welcome to Marqora.md Your copy of the welcome document, replaced by each release

Deleting any of these is safe; the app recreates them. A corrupt state file is moved aside rather than blocking startup.


Project layout

PaulTechGuy.MQ.slnx
├── build/Get-WebAssets.ps1              Restores Monaco, mermaid, KaTeX, highlight.js
├── build/Register-FileAssociation.ps1   Makes Windows offer Marqora for markdown files
├── docs/Architecture.md         How it fits together, and why
├── docs/BlockedContent.md       Pictures that will not appear: the whole pipeline
├── docs/WordExport.md           The .docx exporter: design, traps, how to test it
├── docs/WebViewDebugging.md     Getting a DevTools console onto the preview
├── webshell/                    The preview shell: HTML, CSS, JS, vendor bundle
├── src/
│   ├── PaulTechGuy.MQ.Domain          Models and enums. No dependencies at all.
│   ├── PaulTechGuy.MQ.Abstractions    Interfaces. Everything else talks through these.
│   ├── PaulTechGuy.MQ.Repositories    JSON persistence
│   ├── PaulTechGuy.MQ.Markdown        Pure functions over markdown source text
│   ├── PaulTechGuy.MQ.Rendering       Markdig pipeline, and markdown to HTML
│   ├── PaulTechGuy.MQ.Docx            Markdown to Word, walking the same parse
│   ├── PaulTechGuy.MQ.Formatting      The 16 tidy-up rules
│   ├── PaulTechGuy.MQ.Editing         The Format menu's markdown commands
│   ├── PaulTechGuy.MQ.Analysis        Link, image and style checks
│   ├── PaulTechGuy.MQ.Spelling        The spelling analyzer and its skip rules
│   ├── PaulTechGuy.MQ.Finding         Find All's search engine
│   ├── PaulTechGuy.MQ.Folio           Gathering documents and their images
│   ├── PaulTechGuy.MQ.Exporting       The export jobs: HTML, PDF, Word and the Folio page, for app and CLI
│   ├── PaulTechGuy.MQ.Imaging         Resizing pictures through Windows' own codecs
│   ├── PaulTechGuy.MQ.WebRender       The preview shell in a browser with no window, and the PDF engine
│   ├── PaulTechGuy.MQ.Services        Document lifetime, settings, recent files, watching
│   ├── PaulTechGuy.MQ.App             WinUI 3 shell and view models
│   └── PaulTechGuy.MQ.Cli             marqora-cli: the same commands in a terminal
└── tests/                       One xUnit v3 project per library above

No concrete layer references another, with one documented exception: Docx references Rendering for the Markdig pipeline, so that the Word export reads a document exactly as the preview does. Everything else meets at the two composition roots, src/PaulTechGuy.MQ.App/Program.cs and src/PaulTechGuy.MQ.Cli/CliApp.cs. The command line never references the app, nor the app the command line, and a test holds both to it.


Tests

dotnet test PaulTechGuy.MQ.slnx

One project per library, fifteen in all. Most are pure text in and assertions out. A few worth knowing:

  • PaulTechGuy.MQ.Editing.Tests exercises every Format-menu command through a small stand-in for the editor, so the edits are proven to compose rather than merely to look right one at a time.
  • PaulTechGuy.MQ.Analysis.Tests runs documents through the real renderer and checks them against real files in a temporary folder — the link checks are about whether a path is there, so substituting the filesystem would test nothing.
  • PaulTechGuy.MQ.Finding.Tests pins Find All's line and column numbers, which is the whole of what makes a result clickable: mixed line endings, whole-word boundaries, regular expressions and the ceiling on how many matches come back.
  • PaulTechGuy.MQ.Cli.Tests runs marqora-cli both in-process and as the built executable, holds docs/CLI.md to the command tree, and runs every example in it.
  • PaulTechGuy.MQ.Exporting.Tests and the CLI's export tests draw real documents in a headless browser, so they need the WebView2 runtime and a signed-in desktop, as the exports themselves do. They run one at a time.

Adding another needs no wiring. A project under tests\ is picked up as a test project by Directory.Build.props automatically, and package versions come from Directory.Packages.props, which pins xUnit v3, NSubstitute and Shouldly.

dotnet new xunit3 -o tests\PaulTechGuy.MQ.Services.Tests
dotnet sln PaulTechGuy.MQ.slnx add tests\PaulTechGuy.MQ.Services.Tests

If the xunit3 template is not installed, copy an existing test .csproj instead: xUnit v3 projects are executables, which is the only setting that is not inherited.

The layers below the UI were written to make this straightforward: MarkdigMarkdownRenderer is synchronous and takes only a logger, AppPaths accepts a temp directory through its second constructor, and every service depends on interfaces that substitute cleanly.


Troubleshooting

"Web assets are missing" at build time, or an empty preview Run pwsh .\build\Get-WebAssets.ps1. The vendor bundle is git-ignored.

Something rendered oddly %LOCALAPPDATA%\PaulTechGuy\Marqora\logs\ has the answer. Errors raised by the preview's JavaScript are forwarded into the same log, so a failure in the shell is visible there rather than only in a developer-tools console.

Reset everything Close Marqora and delete %LOCALAPPDATA%\PaulTechGuy\Marqora.


Third-party components

Restored by build/Get-WebAssets.ps1, served locally, never fetched at runtime.

Component Version License
Monaco Editor 0.56.0 MIT
Mermaid 11.17.0 MIT
KaTeX 0.18.4 MIT
highlight.js 11.12.0 BSD-3-Clause
Paged.js 0.4.3 MIT

NuGet: Markdig, CommunityToolkit.Mvvm, Serilog, Windows App SDK, WebView2.


Support the project

Marqora is free and open source, and stays that way. Nothing is held back, and nothing here depends on paying for it.

If you find it useful, you can support continued development through GitHub Sponsors. Starring the repository helps too, and costs nothing. ⭐

The same links are in the app under Help → Support the Project.


License

Copyright (c) 2026 Paul Carver.

Marqora is released under the Apache License 2.0. You are free to use, modify and redistribute it, commercially or otherwise, provided you keep the copyright and license notices, state what you changed, and accept that it comes with no warranty.

Every source file carries the license as a two-line SPDX header:

// Copyright (c) 2026 Paul Carver
// SPDX-License-Identifier: Apache-2.0

build/Add-FileHeaders.ps1 applies it across the tree and is idempotent, so re-running it after adding files is safe. -Check reports anything missing without writing, which is the form to call from CI:

pwsh ./build/Add-FileHeaders.ps1          # apply
pwsh ./build/Add-FileHeaders.ps1 -Check   # verify, non-zero exit if any are missing

The header is also declared as file_header_template in .editorconfig with IDE0073 set to warning level, so a new file without one is flagged at build time.

The third-party components listed above keep their own licenses, which are separate from this one.

About

Local-first markdown editor for Windows 11: split-pane live preview, Monaco find-and-replace, Find All across every open tab, Mermaid diagrams, KaTeX math, spell check from your own dictionary, and self-contained HTML, PDF and Folio exports that open anywhere offline. No network calls, no telemetry, no account, ever. Free, open source, Apache-2.0.

Topics

Resources

Contributing

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages