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.
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.
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.slnxThe 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.mdPublish a copy you can move anywhere
dotnet publish src\PaulTechGuy.MQ.App -c ReleaseRelease 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 -TestThis 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-test4The 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 -UnregisterThis 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.
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 Recentand on the start screen. Clear all on the start screen — andFile > 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-instanceopts 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+Ztakes 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 |
 |
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 |
|---|---|
 |
Marqora does not load content from the web |
<img src="https://example.com/shot.png"> |
The same, written as HTML |
 |
The file is real, but it is not beside the document |
 |
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.
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/*.mdIt 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.
| 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.
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.
dotnet test PaulTechGuy.MQ.slnxOne project per library, fifteen in all. Most are pure text in and assertions out. A few worth knowing:
PaulTechGuy.MQ.Editing.Testsexercises 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.Testsruns 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.Testspins 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.Testsruns marqora-cli both in-process and as the built executable, holdsdocs/CLI.mdto the command tree, and runs every example in it.PaulTechGuy.MQ.Exporting.Testsand 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.TestsIf 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.
"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.
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.
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.
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.0build/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 missingThe 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.