Skip to content

Latest commit

 

History

History
533 lines (398 loc) · 34.7 KB

File metadata and controls

533 lines (398 loc) · 34.7 KB

Markdown Viewer Features: Live Markdown Preview, Diagrams, Export, and Sharing

This page is the source-of-truth feature reference for Markdown Viewer. It describes what the app does, how each feature behaves for users, the implementation limits that matter in practice, and what happens to document data.

Product Summary

Markdown Viewer is a browser-based Markdown editor, viewer, reader, and previewer for opening .md and .markdown files, writing plain Markdown, and reading a live GitHub-style preview. It runs as a static web app, a Progressive Web App, a Docker-hosted static site, and a Neutralino desktop app. The editor is built around a plain textarea, a split-screen rendered preview pane with sync scrolling, document tabs, import/export tools, sharing tools, rich Markdown renderers, and optional Cloudflare endpoints for Share Snapshot and Live Share.

Most work happens in the browser or desktop webview. Markdown parsing, syntax highlighting, math rendering, diagram post-processing, PDF/PNG capture, tab storage, undo/redo, search, and formatting tools are client-side. The exceptions are explicit network features: GitHub import, emoji lookup, CDN library loading in the web build, remote diagram fallback services, large Share Snapshot storage, and Live Share relay rooms.

Main Workspace

The app opens with a header, document tab bar, formatting toolbar, editor pane, resize divider, and preview pane.

  • Editor mode shows only the textarea.
  • Split mode shows the editor and preview side by side.
  • Preview mode shows only the rendered document.
  • On small screens, the mobile menu exposes the same core actions and the layout avoids a cramped split view.
  • A draggable divider resizes editor and preview in split mode and keeps both panes above 20% width.
  • The divider also supports keyboard adjustment with left and right arrow keys while split view is active.
  • The GitHub link in the header opens the source repository.

The editor includes line numbers, wrapped-line height handling, a highlight layer for find results, live cursor overlays during Live Share, and skeleton placeholders during initial or heavy rendering. Line-number calculations are cached so large documents do not force a full layout measurement on every keystroke.

Document Tabs and Local Workspace Storage

Users can work with multiple documents at once.

  • New tabs can be created from the tab bar, mobile menu, imports, shared snapshots, and Live Share joins.
  • Tabs can be renamed, duplicated, deleted, and reordered by drag and drop.
  • The app enforces a practical tab limit of 20 tabs. Shared snapshots refuse to open when this limit has been reached.
  • Each normal tab stores a title, content, scroll position, view mode, local review threads, and creation time.
  • The active tab id and untitled-document counter are stored separately.
  • Temporary Share Snapshot and Live Share tabs are deliberately excluded from persistent tab storage.
  • The Reset button clears the current saved workspace and returns the app to a clean starting state.

Storage keys used by the current implementation include:

Key What It Stores
markdownViewerTabs Normal saved document tabs, including local comments and suggestions. Temporary shared/live tabs are stripped before saving.
markdownViewerActiveTab The active tab id.
markdownViewerUntitledCounter Counter used for new Untitled tab names.
markdownViewerGlobalState Theme, direction, view preferences, scroll sync, and similar global UI state.
app-lang Selected interface language.
find-replace-docked Whether the Find and Replace panel is docked.

On the web, these values live in browser localStorage. In the desktop app, the code mirrors selected localStorage values into Neutralino storage, so preferences and workspace state survive desktop restarts.

The About dialog includes two storage controls:

  • Private mode removes saved document/workspace state and prevents normal document-state keys from being written while it is enabled. The private-mode preference itself remains so the behavior survives a reload.
  • Clear local data removes saved document tabs, active-tab state, the untitled-tab counter, global workspace preferences, and their desktop storage mirrors. It does not revoke links that were already shared.

Comments and Suggestion Mode

Review mode provides a local feedback layer over the rendered document without inserting or changing Markdown.

  • The Review button opens a dedicated read-only preview workspace and restores the previous Editor, Split, or Preview layout when closed.
  • Review pins are attached to rendered YAML frontmatter tables, headings, paragraphs, fenced code blocks, and supported diagram containers, including Mermaid and the other diagram engines.
  • Empty targets show a plus that opens and focuses the composer. Targets with feedback show two controls: the review icon and count opens saved feedback, while the adjacent plus opens the composer for another item. Selecting reviewed content also opens its saved feedback without opening the composer.
  • A reviewer can add either a comment or a suggestion. Both are feedback records; suggestions do not apply source changes automatically.
  • Threads show their opened and closed date/time and can be edited in the existing composer, resolved, reopened, or deleted individually and filtered by status. The panel also provides a detailed Markdown summary with lifecycle dates and counts, Resolve all, and confirmation-protected Delete all actions.
  • Open-thread counts appear inline in the desktop Review button and in the mobile menu.
  • Review controls support keyboard focus, screen-reader labels, dark mode, RTL layout, a compact desktop side panel, a tablet drawer, and a touch-friendly mobile bottom sheet.
  • Opening a new document tab closes Review mode and restores the prior document view before switching tabs.
  • Comments and suggestions use the existing app accent, button surfaces, borders, hover states, disabled states, and Bootstrap Icons; the feature does not introduce a separate color palette.

Anchoring and lifecycle:

  • Each thread stores a deterministic block signature, duplicate occurrence and count, neighboring block context for repeated blocks, block type, label, and source excerpt rather than relying on transient rendered element ids.
  • The app reapplies review anchors after both main-thread and worker preview renders.
  • If a reviewed block changes enough that its signature no longer matches, the thread stays visible as unanchored feedback instead of attaching to the wrong block.
  • Review threads are stored per normal tab inside markdownViewerTabs. Duplicating a document starts the copy with no review threads.
  • Private mode and Clear local data cover review threads because they use the same document storage as the Markdown tab.
  • Review pins, outlines, the review panel, and thread content are excluded from Markdown, HTML, PDF, PNG, and browser-print output.

Sharing behavior:

  • Review threads are not encoded into Share Snapshot URLs or stored snapshot payloads.
  • Live Share synchronizes review creation, resolve/reopen state, and deletion through a separate Yjs document. View-only participants can send Review updates without permission to edit Markdown.
  • A participant's temporary live tab is removed on leave; synchronized feedback remains in the host's normal tab and in the active room state.
  • Use Copy review summary to transfer feedback outside the app or after a Live Share session.

Editing and Formatting Tools

The formatting toolbar inserts or transforms Markdown at the current selection. It provides WYSIWYG-style helpers for plain Markdown with live preview, not full in-place rich-text editing.

  • Undo and redo use the app's custom per-tab history.
  • Clear document opens a confirmation modal.
  • Bold, italic, strikethrough, quote, inline code, code block, terminal block, horizontal rule, and headings H1-H6 insert standard Markdown.
  • Bulleted and numbered lists work on selected lines or the current line.
  • Pressing Enter inside a list continues the list; pressing Enter on an empty list item exits the list.
  • Tab inserts two spaces; Shift+Tab outdents selected lines.
  • Title case, uppercase, and lowercase transform selected text or the current line.
  • Alignment buttons insert left, center, or right aligned HTML blocks.
  • The direction toggle switches between left-to-right and right-to-left content direction.
  • Link, image, reference, table, emoji, symbol, alert, and diagram buttons open focused modals.
  • Date/time inserts a local timestamp.
  • Fullscreen uses the browser Fullscreen API when available.
  • Help and About buttons open informational modals.

View-only Share Snapshot tabs and view-only Live Share participant tabs block mutating tools and announce that the editor is read-only. Non-mutating actions such as fullscreen, find, help, and info remain available.

Custom Undo and Redo

The app keeps its own edit history for each tab so toolbar actions, typed edits, and programmatic changes can be undone consistently.

  • Ctrl+Z or Cmd+Z undoes the previous edit in the active editor.
  • Ctrl+Shift+Z, Cmd+Shift+Z, Ctrl+Y, or Cmd+Y redoes.
  • Undo and redo buttons are disabled when the current tab cannot be edited.
  • Cursor position is tracked so undo and redo feel close to native textarea behavior.

Find and Replace

Find and Replace is a floating or docked panel with editor and preview highlighting.

  • Open it with the toolbar, Ctrl+F, or Cmd+F.
  • Ctrl+H or Cmd+H opens the panel focused on replacement.
  • It supports case-sensitive matching, whole-word matching, regular expressions, selection-only search, and replace-all.
  • Regex replacements can use numbered capture groups like $1 and named groups like $<name>.
  • Preserve-case replacement adjusts replacement casing to match the matched text.
  • Search scope can be limited using a Marked lexer map, including headings, code blocks, Mermaid blocks, LaTeX blocks, or the entire document.
  • Diff preview shows the effect before bulk replacement.
  • The panel can be dragged, docked, undocked, and reset to a visible position. Its floating position is constrained to the viewport.
  • Find history and replace history are kept in memory for the current session, up to 10 entries each.

Limitations:

  • AST scoping depends on Marked token boundaries. Very unusual Markdown can be classified as plain text.
  • Scope validation protects LaTeX delimiters and Mermaid block starts, but it cannot prove every replacement is semantically correct.
  • Preview highlighting works on visible text nodes and may skip text produced inside complex third-party SVG renderers.

Live Markdown Preview and GitHub-Flavored Markdown

Markdown is parsed with Marked and highlighted with Highlight.js. Rendered HTML is sanitized with DOMPurify before it is inserted into the preview.

Supported Markdown behavior includes:

  • CommonMark-style headings, paragraphs, line breaks, emphasis, blockquotes, lists, code blocks, horizontal rules, links, images, and inline HTML.
  • GitHub-Flavored Markdown (GFM) features such as tables, task lists, strikethrough, and autolinks.
  • Heading ids generated from heading text for in-document anchor navigation.
  • Reference definitions and reference links.
  • Multi-paragraph footnotes with back references.
  • Definition lists using a term followed by : definition.
  • Superscript with ^text^.
  • Subscript with ~text~.
  • Highlight with ==text==.
  • GitHub-style alert blocks for NOTE, TIP, IMPORTANT, WARNING, and CAUTION.
  • Emoji shortcodes processed through JoyPixels when the emoji library is available.
  • Raw HTML is allowed only after sanitization. Scripts and unsafe event handlers are removed.

The worker and main renderer both preserve block math, custom diagram shells, footnote state, definition lists, superscript, subscript, and highlight syntax so advanced blocks do not collapse during live updates.

Web Worker and Preview Performance

Rendering is designed to keep typing responsive.

  • Small documents render on the main thread after a short debounce.
  • Very large documents can render in preview-worker.js.
  • The current worker threshold is 50,000 characters.
  • Render debounce is size-aware: 100 ms for typical documents, 160 ms for large documents, and 240 ms for huge documents.
  • Worker rendering has a 12 second timeout and falls back if worker rendering fails repeatedly.
  • When safe, the worker splits Markdown into blocks, hashes each block, and returns segmented HTML.
  • The main thread caches sanitized segments and patches only changed preview sections.
  • Segmented rendering is avoided when document constructs need global context, such as footnotes or reference-style definitions.
  • Preview sections use content-visibility: auto so off-screen content costs less to lay out.

Limitations:

  • Huge documents still depend on browser memory and DOM limits.
  • Advanced renderers such as Mermaid, MathJax, maps, STL, ABC, and remote diagrams run after the base Markdown pass, so they can appear slightly later than text.
  • The app retries advanced post-processing when shared or live content loads before renderer libraries are ready.

Math Rendering

MathJax renders LaTeX-style math.

  • Inline math uses $...$.
  • Display math uses $$...$$, \(...\), or \[...\].
  • Fenced math code blocks are converted into display math.
  • Additional MathJax packages are configured for richer notation.
  • MathJax loads only when math-like text is detected.
  • After typesetting, the app removes or hides MathJax assistive markup from export captures so duplicate text does not appear in PDFs or PNGs.

Limitations:

  • The first math render in the web build may require downloading MathJax unless it is already cached.
  • Invalid LaTeX is shown according to MathJax behavior and may produce warnings or unrendered source.
  • A literal dollar sign should be escaped as \$ when it is not intended to start math.

Insert Diagrams, Charts, Maps, Models, and Music

Markdown Viewer supports many fenced-code renderers, so it can work as a Markdown diagram editor, Mermaid editor, PlantUML editor, Graphviz/DOT editor, D2 diagram editor, Vega-Lite chart previewer, Markmap mind map viewer, WaveDrom timing diagram viewer, ABC notation viewer, map previewer, and 3D STL viewer.

Fence Language Renderer User Behavior Network Notes
mermaid Mermaid.js Renders diagrams as SVG with zoom, copy, PNG, and SVG actions. Client-side library. Diagram insertion previews may use mermaid.ink.
plantuml PlantUML/Kroki Renders SVG and provides zoom, copy, PNG, and SVG actions. Uses PlantUML server first, then Kroki fallback.
d2 Kroki Renders D2 SVG and provides zoom, copy, PNG, and SVG actions. Uses Kroki. Some source is normalized for common SQL-table cases.
graphviz / dot Kroki Renders Graphviz SVG and provides zoom, copy, PNG, and SVG actions. Uses Kroki.
vega-lite / vegalite Kroki Renders Vega-Lite charts. Uses Kroki.
wavedrom Kroki Renders WaveDrom timing diagrams. Uses Kroki.
markmap Markmap, D3 Renders a mind-map style SVG. Client-side libraries.
geojson Leaflet Renders an interactive map. Client-side library; map tiles may require network depending on tile source.
topojson Leaflet and TopoJSON Converts TopoJSON to GeoJSON and renders an interactive map. Client-side library; map tiles may require network.
stl Three.js Renders a 3D STL model with orbit controls, solid/surface-angle/wireframe modes, zoom modal, copy, and PNG export. Client-side libraries.
abc ABCJS Renders sheet music, supports playback, cursor sync, note highlighting, copy, PNG, and SVG export. Client-side library; browser audio support required for playback.

Every diagram shell keeps the original source in a data attribute so the app can rerender after theme changes, shared document loading, and export preparation. Diagram PNG exports add a solid background when needed so transparent SVGs stay visible.

Limitations:

  • Remote diagram engines send diagram source to third-party rendering endpoints. Do not use those fences for private diagram text unless you trust the endpoint or provide your own deployment.
  • Remote render requests have a 15 second timeout and retry twice.
  • Clipboard image writing requires a secure context and browser support for ClipboardItem.
  • WebGL STL rendering depends on GPU/browser support. The app disposes old STL views to reduce memory leaks.
  • ABC audio playback depends on browser audio APIs and may be unavailable in some environments.

Insert Diagram & More Modal

The Insert Diagram & More modal offers searchable templates grouped by engine. It shows source code and a live preview before insertion.

  • Categories include Mermaid, PlantUML, D2, Graphviz, Vega-Lite, ABC notation, WaveDrom, and Markmap.
  • Template code is cleaned before insertion.
  • Previews are cached in the browser Cache API under diagram-previews when possible.
  • Remote preview generation can use Kroki or mermaid.ink depending on the template.

Imports

Markdown Viewer can open .md and .markdown documents from local files, drag and drop, GitHub URLs, and desktop file arguments.

Local file import:

  • Accepts .md, .markdown, and text/markdown.
  • Extension checks are case-insensitive.
  • Dragging a file over the app shows a full-window drop overlay.
  • The first 8 KB of a file are scanned for null bytes to avoid loading binary files as text.
  • Imported local files open in the active tab or a new tab depending on the action.

GitHub import:

  • Accepts github.com/owner/repo, github.com/owner/repo/tree/ref/path, github.com/owner/repo/blob/ref/path, and raw.githubusercontent.com file URLs.
  • Direct Markdown file URLs import immediately.
  • Repository or folder URLs query GitHub's public API to find Markdown files.
  • The modal shows a tree and supports selecting multiple files.
  • Only the first 30 Markdown files are shown if a repository contains more.
  • Requests are rate-limited by the app to avoid hammering GitHub.
  • Selected files are fetched as raw content and opened as separate tabs.

Limitations and privacy:

  • Local file content is read in the browser or desktop app and is not uploaded by local import.
  • GitHub import sends repository and path information to GitHub and downloads public file contents from GitHub.
  • Private GitHub repositories are not supported because the app does not ask for tokens.

Export Markdown to PDF, HTML, PNG, and MD

Export filenames use the active tab title when possible.

Markdown export:

  • Saves the raw Markdown text.
  • In the web app, it downloads through the browser.
  • In the desktop app, it uses a native save dialog and Neutralino filesystem writing.

HTML export:

  • Creates a standalone HTML document from the current Markdown.
  • Includes GitHub-style Markdown CSS, syntax highlighting styles, alert styles, footnote styles, math/diagram support hooks, and frontmatter rendering.
  • YAML frontmatter is parsed and shown as a table before the document body.
  • HTML export uses sanitized rendered content.
  • Exported HTML includes a restrictive document CSP and Subresource Integrity metadata for its external CSS/scripts where applicable.

PDF export:

  • Opens a modal with two modes.
  • Browser Print is recommended. It prepares the preview with a clean light print theme, hides app chrome and open modals, rerenders Mermaid with printable light SVG colors, refreshes theme-sensitive map and STL styling, then calls window.print() so the browser or OS can save or print the document. When the print preview closes, the app restores the user's previous light or dark UI theme.
  • Legacy Raster PDF uses html2canvas and jsPDF. It clones the preview into an off-screen A4 sandbox, renders Mermaid and ABC to SVG/image form, typesets math, waits for images/fonts, applies page-break rules, captures the document to canvas, and saves a PDF.
  • The raster exporter shows progress and has a cancel button.
  • Raster export uses allowTaint: false and useCORS: true to avoid unsafe cross-origin canvas capture.

PNG export:

  • Captures the rendered document into a PNG using html2canvas.
  • It uses a white/dark solid background based on theme and a high-resolution canvas.
  • It renders Mermaid, ABC, and MathJax in the off-screen capture before saving.

Limitations:

  • Browser Print output is controlled by the browser and print settings.
  • Browser Print removes app dark-mode styling from printed output, but it does not rewrite colors that a document author explicitly placed inside SVG, HTML, image files, or diagram source. A diagram that intentionally uses a dark background can still print dark.
  • Raster PDF and PNG are screenshots of rendered HTML, so very long documents can be memory-heavy.
  • Cross-origin images without CORS support may fail to appear in canvas-based PDF/PNG exports.
  • Advanced remote diagrams that have not rendered yet may need a moment before export.
  • Some complex CSS, wide tables, and large diagrams may be moved, scaled, or split differently from the live preview.

Share Markdown with Snapshot Links

Share Snapshot creates a link to a point-in-time copy of the current document.

Modes:

  • View only opens the shared content in preview mode with the editor hidden.
  • Editable opens the shared content in split mode so the recipient can edit their own copy.

Storage behavior:

  • Small documents are compressed with Pako, base64url-encoded, and placed directly in the URL hash as #share=....
  • Hash fragments are not sent to a web server as part of normal HTTP requests.
  • If the encoded legacy URL is too long, or if the Markdown is at least 3,000 bytes, the app stores the snapshot through /api/share.
  • Stored snapshots receive an id in #id=... form.
  • Stored snapshots are saved in Cloudflare KV for 90 days.
  • The server accepts up to 500,000 characters per stored snapshot.
  • Snapshot ids are random 10-character values using a reduced alphabet and must match the app's id pattern.
  • Stored snapshot responses use Cache-Control: no-store.
  • The Share API allows CORS only for the production app, null, and localhost/127.0.0.1 development origins; unsupported origins are rejected.
  • Creating a stored snapshot returns a creator-side deletion token. The token is hashed in KV and is required for DELETE /api/share/<id>; it is not part of the share URL.
  • Shared snapshot tabs are temporary and are not saved to the recipient's local workspace.

Privacy implications:

  • URL-hash snapshots keep document content inside the link itself.
  • Anyone with a snapshot link can read the snapshot.
  • Editable snapshot links are not collaborative; they only let the recipient edit their local opened copy.
  • Stored snapshots upload document content, mode, title, creation time, and size to the configured Cloudflare KV namespace until expiry.
  • The app prevents sharing a temporary shared snapshot again, and prevents Share Snapshot from a Live Share document, to avoid confusing copies of copies.

Live Share Rooms for Markdown Collaboration

Live Share creates a temporary real-time collaboration room.

User flow:

  • The host chooses a display name and an access mode.
  • Access can be Can edit or View only.
  • The app creates a random room id and a random secret.
  • The invite URL contains the room id, secret, and title. It does not embed the full Markdown body.
  • Participants open the link and join a temporary live tab.
  • Participants see presence avatars and live cursor indicators.
  • The host can end the session for everyone.
  • Participants can leave and return to their original tab state.
  • If a room has ended, expired, or has no active host, the participant sees an expired-room modal.

Implementation:

  • The client uses separate Yjs documents for Markdown/session state and Review threads.
  • Browser clients connect with WebSocket to /live-room/<room-id>?secret=<secret>.
  • The Pages Function and Durable Object reject unsupported WebSocket Origin values. The production app, HTTPS *.markdownviewer.pages.dev previews, null, and localhost development origins are allowed.
  • The host connection establishes separate host, edit, and view capabilities. The Durable Object stores these capabilities and authenticates each joining role server-side.
  • Cloudflare Pages routes the WebSocket to a Durable Object named LIVE_ROOMS.
  • The Durable Object relays only known message types and filters them by role: viewers cannot send Markdown updates or session-end messages, but they can request and send Review updates; editors can send Markdown and Review updates; only the host can publish full Review state or send every supported type.
  • The Durable Object does not write document state to KV or a database.
  • Room identity is derived from room id plus secret.

Limits:

  • A live message can be at most 1 MB.
  • A live room can have at most 64 WebSocket participants.
  • Participant presence is considered stale after 45 seconds without updates.
  • Join waits up to 8 seconds for initial room state before showing an expired/unavailable room message.
  • The room exists only while the Durable Object instance and connected sessions are alive.

Privacy implications:

  • Live Share document updates, display names, cursor positions, and presence are transmitted through the configured Cloudflare Durable Object.
  • Live room content is temporary relay state, not permanent document storage.
  • Anyone with the invite URL, including the secret, can join while the room is active.
  • View-only mode is enforced by the app and message handling; it is intended for normal use, not as a cryptographic access-control boundary against modified clients.

Clipboard and Copy Behavior

  • Copy Markdown copies the raw Markdown from the editor.
  • Ctrl+C or Cmd+C respects selected text in inputs/textareas and selected page text.
  • When no text selection is active, the app can copy the full Markdown document.
  • Diagram and ABC copy actions attempt to write PNG image data to the clipboard.
  • Clipboard APIs require browser permission and a secure context. The app falls back to a temporary textarea for text copying when needed.

Themes, Direction, and Localization

Theme behavior:

  • The app supports light and dark themes.
  • Initial theme follows saved preference, then system preference.
  • Theme choices are saved in global state.
  • Diagrams, maps, STL views, and rendered blocks are updated after theme changes when possible.

Direction behavior:

  • Users can switch content direction between LTR and RTL.
  • Direction affects editor and preview layout and is saved in global state.

Localization:

  • The UI includes English, Simplified Chinese, Japanese, Korean, Brazilian Portuguese, Spanish, French, German, Russian, Italian, Turkish, Polish, Traditional Chinese, and Ukrainian.
  • Language is selected in this order: URL ?lang=, hash query ?lang=, saved app-lang, browser language, then English.
  • Selecting a language updates the URL query and saves app-lang.
  • Translations are static in I18N_DICTS inside script.js.
  • Some generated renderer messages and third-party output remain English.

Statistics

The header and mobile menu show:

  • Estimated reading time.
  • Word count.
  • Character count.

Reading time is based on a simple words-per-minute estimate. Counts update as the active document changes. Character count is a practical UI metric, not a byte-level file-size guarantee.

Keyboard and Accessibility

Common shortcuts:

Action Shortcut
Save/export Markdown Ctrl+S / Cmd+S
Find Ctrl+F / Cmd+F
Replace Ctrl+H / Cmd+H
Toggle scroll sync Ctrl+Shift+S / Cmd+Shift+S in split mode
Undo Ctrl+Z / Cmd+Z
Redo Ctrl+Shift+Z, Cmd+Shift+Z, Ctrl+Y, or Cmd+Y
New tab Desktop: Ctrl+T / Cmd+T; web and desktop: Alt+Shift+T
Close tab Desktop: Ctrl+W / Cmd+W; web and desktop: Alt+Shift+W
Indent Tab in the editor
Outdent Shift+Tab in the editor
Close modals/panels Escape

Accessibility behavior:

  • Tabs use ARIA tablist semantics and roving keyboard focus.
  • Tab bar supports ArrowLeft, ArrowRight, Home, End, Enter, and Space.
  • Modals trap focus and close with Escape or cancel buttons.
  • The resize divider is keyboard focusable.
  • Screen-reader announcements are used for imports, Live Share, read-only states, and other dynamic actions.
  • Touch targets were enlarged in earlier accessibility passes.

Offline, PWA, and Caching

The web app registers sw.js when service workers are supported.

  • The service worker cache name is versioned in sw.js so stale caches can be retired safely.
  • Critical local assets include /, index.html, styles.css, script.js, preview-worker.js, manifest.json, and assets/icon.jpg.
  • Local shell assets use a network-first strategy for update-sensitive paths, falling back to cache when offline.
  • CDN assets from cdnjs and jsDelivr use cache-first behavior after first successful load.
  • The app manifest allows standalone PWA installation.

Limitations:

  • Service workers require HTTPS or localhost.
  • First use of CDN-based renderers requires network access unless already cached.
  • Clearing site data removes the cached app shell and local documents.
  • Opening index.html through file:// can break workers and service workers because of browser security rules.

Desktop App

The desktop build wraps the same app in Neutralino.

Desktop-specific behavior:

  • Uses a native window with minimum size 400 x 200 and default size 1280 x 720.
  • Uses one-time token security.
  • Logging is disabled in the current config.
  • Native APIs are allowlisted instead of fully open.
  • Allowed APIs include app exit, open/save dialogs, message boxes, external URL opening, tray setup, file read/write, and Neutralino storage get/set. os.execCommand is intentionally not in the default allowlist.
  • Local imports and exports use native open/save dialogs.
  • External Markdown file paths passed at launch can be loaded into the editor.
  • Closing the desktop window asks for confirmation before exiting.
  • Desktop resources are built by desktop-app/prepare.js.
  • prepare.js copies root assets, rewrites paths for /resources/, strips web-only SEO metadata, and downloads/bundles external libraries into /resources/libs/.
  • Downloaded desktop dependencies are checked against SHA-384 integrity values when SRI is available.
  • The desktop build points dynamic libraries to local /libs/... paths, so prepared desktop resources do not need CDN-hosted renderer libraries after setup.

Privacy:

  • Desktop documents are stored on the local machine through localStorage and Neutralino storage.
  • Native file reads/writes happen only through user actions or explicit file arguments.
  • Share Snapshot, Live Share, GitHub import, remote diagram fallbacks, and external links still use the network when used.

Security Model

Important protections:

  • DOMPurify sanitizes preview HTML before insertion.
  • Preview sanitization allows needed render attributes and safe URI patterns while blocking scripts and inline event handlers.
  • CDN scripts and styles in index.html use Subresource Integrity where checked in.
  • Desktop preparation verifies downloaded assets against SHA-384 integrity values when available.
  • Cloudflare Pages supplies CSP, clickjacking, referrer, permissions, cross-origin, HSTS, and MIME-sniffing protections through _headers; sensitive deployment files are redirected to 404 through _redirects.
  • Canvas exports use allowTaint: false.
  • STL rendering validates source size, finite vertex coordinates, and geometry vertex count before creating a WebGL view.
  • The desktop native API allowlist follows least privilege for the app's current features.
  • Private mode and Clear local data provide explicit controls over local document persistence.
  • Share and live endpoints return no-store responses for dynamic content.
  • The app does not include analytics, telemetry scripts, ad pixels, accounts, cookies, or subscription code.

Security limitations:

  • Sanitization reduces XSS risk but cannot make every third-party renderer or browser bug impossible.
  • Remote diagram services receive diagram source for supported remote engines.
  • Links and images in Markdown can request external resources when rendered or clicked.
  • View-only Live Share relies on cooperative client enforcement and relay filtering, not end-to-end encryption.
  • Share Snapshot links are bearer links: possession of the URL grants access.
  • Security headers and CSP depend on the deployment surface; self-hosters should preserve the policies in _headers and review the Docker/Nginx policy when customizing it.

Data Handling Summary

Feature Leaves Device? Stored Where Notes
Typing and local preview No Browser memory and saved tabs Sanitized before preview insertion.
Normal tab autosave No localStorage or desktop storage mirror Cleared with site/app data or Reset.
Comments and suggestions Only during Live Share Normal saved tabs plus temporary Live Share relay state Excluded from document exports and Share Snapshot; synchronized between active Live Share participants.
Private mode No No document-state persistence Clears existing document state when enabled and prevents normal document-state writes until disabled.
Local file import No Current tab/workspace Reads selected files only.
Markdown/HTML/PDF/PNG export No, except remote assets already referenced User download location Browser may request external images/fonts used by content.
GitHub import Yes GitHub API/raw URLs Public repos only; no token flow.
Emoji lookup Yes GitHub emoji API response in memory Used for shortcode picker/lookup.
CDN library loading Yes Browser/service-worker cache Web build only, first use unless cached.
Remote diagram engines Yes Third-party renderer response/cache Source is sent to PlantUML, Kroki, or mermaid.ink depending on renderer/preview.
Share Snapshot hash link Only when user sends the link Inside URL hash Small documents are not uploaded by generation.
Stored Share Snapshot Yes Cloudflare KV for 90 days Content, mode, title, createdAt, and size.
Live Share Yes Cloudflare Durable Object relay memory Temporary while active; no KV/database write.
Desktop native storage No Local app storage Mirrors app state for restart persistence.

Known Technical Limits

  • Browser storage quotas can reject very large saved workspaces.
  • The GitHub importer shows a maximum of 30 Markdown files.
  • Stored Share Snapshot content is limited to 500,000 characters.
  • STL source is limited to 2 MiB and parsed geometry to 300,000 vertices.
  • Legacy raster PDF and PNG exports can fail on extremely tall documents because canvas size and memory are browser-limited.
  • Remote renderer availability depends on third-party services and network conditions.
  • The service worker cannot cache assets that have never been successfully fetched.
  • The desktop app depends on the platform webview and Neutralino runtime behavior.