Repository navigation
Release 1.14.0: work that can't be lost, files kept as they were, a locked-down preview - #21
Merged
Merged
Conversation
…g code Copying from the reading view carried the theme: Chromium's native copy inlined every computed style into text/html (navy background, near-white text, gold border), so Word, Outlook, Teams and mail pasted dark boxes or white text on white. And the TeX "repair" pass ran over the whole document, so a tab before `auth`/`ext…` became a literal `\t` and `\ ` became `\\ ` inside code blocks and inline code, which is what a copy then put on the clipboard. - Code blocks get a Copy button: exact source, no trailing newline, CRLF on Windows. Selections inside code copy plain text only; other selections copy clean, unstyled HTML; KaTeX copies as TeX source. - TeX repairs run only inside maths (output byte-identical on 16 maths cases); a leading BOM no longer hides the first heading. - Right-click menu (Cut/Copy/Paste/Select All, Copy Link Address); Ctrl+A in the reading view selects the document, not the window. - Memoize dangerouslySetInnerHTML objects: React 19 compares them by identity and rewrote every preview on each re-render, wiping Mermaid diagrams and the user's selection on window focus. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The API surface the 1.14.0 work builds against, fixed up front so the main-process, renderer and preview changes can land in parallel: - preload: file formats (encoding/BOM/EOL owned by main), onFileDeleted, reopenWithEncoding, hot-exit backups, openExternal, showItemInFolder, showTabContextMenu, onMenuCommand/updateMenu, setSpellcheck. - CodeEditor exposes getView(); MarkdownEditView moves to its own module and passes the last-saved text (savedContent) as the editor baseline. - MarkdownView receives isVisible (active tab or split pane). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Three Electron-free modules for the main process, each with node --test
coverage in test/ (run: node --test test/):
- fileFormat.cjs: decode/encode with the file's format
{ encoding: utf8|utf16le|utf16be|windows1252, bom, eol }. BOM sniffing,
BOM-less UTF-16 by its NUL pattern (conservative), strict UTF-8, else
Windows-1252; line breaks normalised to \n with the dominant EOL
recorded. The binary sniff now runs after UTF-16 detection, so
PowerShell's UTF-16 output is no longer refused as binary. Round trips
are byte-exact (unpaired surrogates and the five undefined 1252 bytes
included); Windows-1252 refuses unencodable text with UNENCODABLE,
naming the character and line. (H1, H4)
- textFiles.cjs: async readTextFile (size cap, NOT_FILE/TOO_LARGE/BINARY)
and writeFileAtomic: temp file in the same folder, fsync, rename over
the target. Symlinks resolved (the link stays a link), mode bits and
owner kept, the Windows mark of the web carried over, read-only files
still refused; in-place fallback for hard links, refused renames and
folders that won't take a temp file; never leaves the temp file. (H5)
- fileWatch.cjs: per-path watchers that coalesce bursts (one reload per
atomic save by another editor), follow replaced files, retry deleted
ones with backoff and report them once if they stay gone, survive
'error' events, deliver only the newest read, and hold reads while
FATE's own save is writing. (M10, low: three change events)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… errors
- backups.cjs: userData/backups/<id>.json written atomically (0700
folder, 0600 files). Ids validated against /^[A-Za-z0-9_-]{1,64}$/,
fields type-checked, sizes and count capped, calls per id serialised so
a remove can't be overtaken by an in-flight write. list() skips corrupt
or foreign files instead of failing; only remove() and clear() delete.
- rendererSettings.cjs: the keys store-get/store-set accept from the
renderer (today's settings plus spellcheck and remoteImages), each
with a cheap type check and a size cap. Main-only keys such as
recentFiles and registrationStamp are out of reach. (M14)
- fsAccess.cjs: plain wording for common errors (disk full, read-only
drive, file in use, ...) so save failures never show Node's raw text
or an atomic save's temp path; every result also carries a short
`reason` and the snap `advice`, for one-dialog summaries.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eload
Wires the new modules into main.cjs's file I/O regions:
- Open, reload and reopen decode via fileFormat: the renderer gets \n
text without a BOM plus meta.format; fileFormats (per watchKey) is
recorded on open, save and reload and dropped on close-file. A CRLF
file rewritten with the same text no longer sends file-changed (the
watcher compares a digest of normalised text). (H1, H4)
- save-file / save-file-as take (..., content, format?): the passed
format, else the recorded one, else the platform default; they resolve
with the format written, or { ok: false, code, error } in plain words
(UNENCODABLE for Windows-1252). Saves are atomic and async. (H5)
- New IPC reopen-with-encoding(path, encoding); the choice sticks for
the tab's later reloads.
- Live reload via fileWatch: one event per atomic save, deleted files
retried then reported with file-deleted, re-armed after our own saves,
held while saving so a racing reload can't echo. (M10)
- No sync file access on hot paths: opens, reloads, saves and the
recents existence checks are async (parallel, 1.5 s cap). Opens are
delivered in request order so restored tabs keep their order, with a
2 s cap so one dead network share can't hold the rest back. (M12)
- save-file only writes paths FATE has open (any open route, or chosen
in Save As); store-get/store-set only take renderer settings keys. (M14)
- backup-write/remove/list/clear IPC over backups.cjs (hot exit).
- Recents: app.emit('fate-recents-changed', list) on every change, Jump
List entries on Windows (cleared with the list), malformed stored
entries ignored.
- Session restore failures are collected into one summary dialog,
keeping the snap guidance, instead of one modal box per file.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keeps main.cjs free of a module-level `crypto` binding, which would collide with a `const crypto` added by another change to the same file. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… sniffing
Command palette
- New src/paletteModes.js: createPaletteModes(ctx) returns the ":" (go to
line[:col], clamped), "@" (symbols from the Lezer tree, or Markdown
headings scanned from the text in a reading view) and "#" (search every
open tab, 20 hits per tab, 200 in all) modes. Pure helpers are exported
and unit-tested against the real bundled grammars.
- CommandPalette takes `modes` and `initialQuery`, shows the active mode as
a badge and a quiet ": go to line · @ symbol · # search open tabs" footer
on an empty query, and understands disabled/error rows.
- Dialog semantics (aria-modal), combobox + listbox with
aria-activedescendant/aria-selected, focus trap and focus return.
Settings
- initialSection prop; a new Markdown page with the spell check and remote
images switches (with the Linux dictionary note and the privacy note).
- Live bindings everywhere ("New files (Ctrl+T)", not a hard-coded Ctrl+N).
- Sidebar width commits on blur/Enter only, clamped to 200-600; Escape
reverts; junk input is ignored.
- Real modal: role/aria-modal/aria-labelledby, focus in on open, Tab
trapped, focus back to the editor on close, Escape handled by the dialog
(it no longer closes a diff behind Settings first), background inert.
Every switch, select, slider and field is named by its label.
- The shortcut recorder stops when Settings closes; Escape cancels it.
Shared
- src/dialogFocus.js: the modal focus hook both dialogs use.
- settingsMeta.js: parseShortcut, normalizeBinding, bindingFromEvent,
matchesShortcut, isAllowedShortcut, shortcutParts, formatShortcutLabel,
toElectronAccelerator, clampSidebarWidth. The + key is stored as "Plus"
(legacy "Control++" parses and is migrated by resolveShortcuts, which
also drops bindings that would fire while typing).
- languageDetect.js: logs starting "[2026-10-04 …]" or "[INFO]" are no
longer sniffed as JSON; isJsonWithComments(fileName) for the linter;
JSONC names the registry doesn't know open as JSON.
DiffView and Starfield
- DiffView: leftFileName/rightFileName pick each side's language. It also
called EditorState.appendConfig (undefined), so no diff was ever
highlighted; now StateEffect.appendConfig.
- Starfield pauses on window blur as well as when hidden, draws at most
30 fps with time-based motion, and builds its gradients and the meteor
sprite once per build instead of every frame.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A "#" hit's detail is a file name and line ("notes.md:3"); the section
labels' small caps turned it into "NOTES.MD:3", which misreads a
case-sensitive name. Only an error row's icon turns red now.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…reload, backups) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d, shortcut helpers) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…, spellcheck privacy Links and navigation (C1, low: target=_blank and mailto:) - will-navigate allows only the exact entry document (dev: the Vite origin); a relative link such as [x](CONTRIBUTING.md) no longer replaces the app. - One link policy (electron/linkSecurity.cjs) for navigations, window.open, the print preview and the new 'open-external' IPC: http(s) after the existing confirmation, mailto: directly, everything else refused. - The print preview gets a navigation guard and its own menu (Ctrl+W). Application menu (C2) - electron/appMenu.cjs: File, Edit, View, Help with Open Recent; no Reload or Force Reload; DevTools only when unpackaged. Renderer items send 'menu-command' and show the user's bindings from 'update-menu' as display-only labels. Rebuilt on 'update-menu' and 'fate-recents-changed'. - will-prevent-unload asks before a blocked reload or close; after the save-or-discard walk the close goes ahead without asking twice. - Check for Updates shares the IPC's logic and reports why nothing happens. fate-local (C5): network, relative and non-image paths are refused before any file system access; the scheme drops supportFetchAPI, bypassCSP and stream. Single instance (M11): lock taken right after the userData override; the losing instance does no whenReady work and creates no window. Every argument opens, relative to the sender's working directory, file: URIs included, and a handoff that arrives while the page loads is queued. Spellcheck: off by default outside Windows. Measured on Electron 42, only an empty language list stops the Hunspell download from Google's servers; setSpellCheckerEnabled(false) and webPreferences.spellcheck do not. New 'set-spellcheck' IPC; suggestions and Add to Dictionary in the context menu. Also: updater promises caught (M13), stale temp files removed at launch, one in-flight font enumeration, show-item-in-folder and show-tab-context-menu IPC, safeFileStem regex without control-character literals. Unit tests: node --test (60 tests for the five new pure modules). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… files CodeEditor measures "dirty" against a baseline, the text last known to be on disk, instead of whatever it was mounted with: - `savedContent` prop: the baseline when it differs from the initial text. A Markdown tab coming back into Edit mode with unsaved text now starts dirty and says so at mount (C4), so isDirty(), getSavedContent(), the close guards and the quit walk agree. - getDocSnapshot()/markSaved(snapshot): a save marks exactly the document it wrote as clean; keys typed while the write is in flight stay dirty (M4). - replaceContent(text, baseline?) replaces only the span that changed (src/textSpan.js), and nothing at all for identical text, so a reload keeps the caret, folds and undo history (M5). setBaseline() and scrollToEnd() serve the changed-on-disk choices and log following. - `largeFile` drops lint, bracket matching, autocompletion and selection-match highlighting; `plainText` never loads a language. - The syntax-error linter really stops at 200 diagnostics, and only runs for languages chosen by file name, not sniffed from content (M8). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…dary - fileKinds: .txt is no longer Markdown; it opens in the editor. Markdown over 2 MB opens as plain text (kindForOpen) and large-file mode starts at 5 MB. couldBeMarkdown() says when "Render as Markdown" is offered. - renderSafely(): rendering that never throws; a failure degrades to the escaped text in a <pre> (H3: a 10 MB log overflowed marked's stack inside a state update and blanked the window). - A root error boundary shows a recovery screen instead of a blank window. As it catches, it writes every unsaved buffer to the hot-exit backups (hotExit.flushForCrash), so Reload loses nothing, and it lets the window close without the unmounted app's quit walk. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
App.jsx (renderer-docs workstream, 1.14.0):
- C3: a dirty tab keeps its text when its file changes on disk or is
opened again, in Reading view too, and shows a changed-on-disk bar:
Reload, Keep mine, Compare (diff with file names for highlighting).
A deleted file gets "Keep editing" / "Close tab"; Save recreates it.
- C4: Markdown tabs track savedSource (the text on disk) and hand it to
the editor as its baseline; leaving Edit mode keeps the dirty state.
- H3: no rendering inside state updaters; failures fall back to plain
text with a status message. "Render as Markdown" for .txt,
extensionless and oversized .md tabs; Follow (status bar) for logs.
- H6: docsRef/activeIdRef/splitIdRef update synchronously with state,
so two opens of one file before a commit make one tab; session paths
are de-duplicated when saved and when restored.
- Hot exit: each dirty tab is backed up about a second after it changes
and the backup removed on save, close, discard or becoming clean;
startup restores backups (even with session restore off), file ones
as each file's tab opens, Untitled ones as tabs. An unload guard
blocks reloads over unsaved work; the quit walk stands it down.
- Line endings and encodings: tabs carry `format` (from meta.format, or
the default for Untitled), pass it on every save and adopt the result.
Status bar: EOL toggle, encoding menu (Save with / Reopen with
encoding), UNENCODABLE errors; a format-only disk change is followed.
- M3: file drags are handled in the capture phase, so CodeMirror no
longer pastes a dropped file into the editor. M4 save snapshots.
- L14: Save As onto a path open in a dirty tab asks first; keeping keeps
that tab as an unsaved copy. L15: closing a dirty tab offers Save /
Don't save / Cancel. L17: Edit mode prints the rendered document.
- Application menu commands, updateMenu({ shortcuts }), and the tab
right-click menu (copy path, reveal, close, close others/to the right).
- saveFile's NOT_OPEN (a restored tab whose file was gone) falls back to
Save As; Save As onto another tab's path never sends closeFile for it.
New: components/DocBar.jsx, components/FormatStatus.jsx, docFormat.js,
and node --test coverage in test/rendererDocs.test.mjs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…, hot exit, disk-change bar, formats, large files) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…urity, single instance, spellcheck privacy) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… find, scroll sync
Links (C1): every click on a link in a preview is handled by FATE instead of
navigating the window. #fragments scroll within the preview (GitHub-style
heading slugs, or the user-content- ids the sanitiser now makes); relative
and absolute paths open as tabs, resolved against the document
(data-doc-path), and other.md#frag lands once that tab shows; http(s) and
mailto go to openExternal; anything else, network paths included, is
ignored. Middle and modified clicks no longer open windows.
Sanitiser (H2, H7): SVG profile on, so KaTeX's radicals, vector arrows,
accents, braces and tall delimiters render. No style tags, forms or
controls (inert task-list checkboxes excepted), dialogs, popovers, invoker
commands, frames, objects, link/meta/base or SVG image. style and class
survive only on KaTeX's own output (stamped per run, so raw HTML can't
claim the exemption), class also on highlighted code; ids and names become
user-content-*. Mermaid's SVG goes through DOMPurify too, keeps only CSS
scoped to the diagram, and can't fetch anything while it renders.
Images (C5, M6): //host and UNC paths are never local; remote images are
placeholders (alt text + host) until loaded from the new bar ("Load
images" / "Always load"), and renderMarkdown returns remoteImageCount.
Post-processing runs in an inert document, so rendering fetches nothing.
Local paths are decoded once with ?query/#hash dropped; Windows drive paths
work.
CSP: a <meta> injected into the built index.html (not in dev). Mermaid
needs no unsafe-eval, and Electron's insecure-CSP warning is gone.
Maths: the `\ ` repair applies only to formulas with other corruption (M9);
"costs $5 and $10" stays prose (L7). PowerShell, batch and Dockerfile
fences are highlighted.
Layout: Mermaid renders in the split pane and the edit preview (one shared
hook, cached per source, debounced in edit mode) and leaves nothing behind
when a diagram fails (L1); the contents resizer measures from its own pane
(L2); the active heading is worked out from a real layout when a pane shows
(L6); contents entries are keyboard-reachable buttons.
Find in the reading view: Ctrl+F or the fate:find event; "3 of 12",
Enter/Shift+Enter, Escape closes the bar without reaching App's Escape
binding; CSS Custom Highlight API; hidden in print.
Edit mode: the preview follows the editor's scrolling block by block
(data-line annotations), interpolated within blocks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Open Recent goes through openRecentFile (which prunes a missing entry), Clear Recently Opened through clearRecentFiles (the Windows Jump List too), and Open Containing Folder only reveals files FATE has open. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t-in, find, scroll sync) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every renderer package sat in "dependencies", so electron-builder copied each one into app.asar as node_modules although Vite had already bundled it into dist/. app.asar was 211.7 MB; it is 13.4 MB now. The main process requires only electron-store and electron-updater (grep over electron/*.cjs); everything else moves to devDependencies. The lockfile's root entry and dev flags follow (npm ls --package-lock-only is clean, and its production tree is exactly those two packages and their own deps). Measured with electron-builder --linux dir on both dependency sets, and the packaged app was started on a Markdown file: it renders, KaTeX and Mermaid included, with nothing on stderr but electron-updater's AppImage notice. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Linux (H9, M15) - deb.afterRemove / rpm.afterRemove replace electron-builder's default after-remove template, so they repeat it (update-alternatives removal, AppArmor unload and delete) and add FATE's part. The .deb's postrm deletes /etc/apt/sources.list.d/fate.sources on remove and purge when it carries a FATE header (the package's own, or the one the README has people download): its Signed-By keyring goes with the package, and apt update failed after every uninstall. The .rpm's postun does nothing during an upgrade ($1 >= 1), which is what deleted /usr/bin/FATE on dnf upgrade; on erase it removes the alternative and the fate.repo the package wrote. - A posttrans scriptlet (--rpm-posttrans) puts /usr/bin/FATE back after the unguarded postun of 1.13.x and older, so the upgrade to this version keeps the command too. - The public signing keyring is committed (build/linux/fate-archive-keyring .asc/.gpg, identical to the assets on the apt release). The packages ship it from there, so a fresh clone builds them without downloading anything. Windows (M16, M19) - customUnInstall removes the per-user registration the app's self-heal writes, on a real uninstall only: the HKCU FATE.<ext> ProgIds and Open-with values, the HKCU "Markdown Document" open command when it still points into $INSTDIR, and the per-user install key. - preInit is empty in the uninstaller-building pass, which electron-builder runs on the build machine. - scripts/appx-manifest.cjs (replacing escape-appx-manifest.cjs) gives the Store package .markdown, .txt and every code type installer.nsh registers, read from installer.nsh at build time; never .bat or .cmd. NSIS is untouched: it still takes the default for .md only. npm test (node --test) runs the new tests: the maintainer scripts executed against a scratch root with stubbed tools, the Store manifest transform, and the code-type lists across main.cjs, fileKinds.js and installer.nsh. installer.nsh also compiles with makensis 3.0.4 in both passes. The AppStream entry for 1.13.2 gets its real date (the tag, 2026-09-14). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… PRs Build Linux (C6, H8, M18) - build (npm ci, lint, test, electron-builder; no secrets), snap (snapcore action-build; no secrets), publish (the only job with the signing key: downloads the artifacts, signs, attaches, writes the apt/dnf indexes; runs no npm and no third-party action), snap-store (snapcore action-publish with the store token only), test-repos, flatpak. - Permissions default to contents: read; only the publish jobs get write. Checkouts use persist-credentials: false. Actions are pinned by commit SHA, except actions/setup-node and actions/upload-artifact, which are still on @v4 (marked TODO(pin): their SHAs could not be resolved here). - Guards: tag == v + package.json version before building; a recovery run checks out the tag; inputs reach scripts only through env. apt, dnf and the Snap Store are updated only for a published, non-prerelease release that is the repository's latest; a semver prerelease must be a GitHub prerelease. A draft gets its files, then the job stops with an error. - The signing secret must hold exactly the keys named in SIGNING_KEYS and each must be in the committed keyring (M21); otherwise nothing is signed. Several keys are supported for a rotation (all sign the indexes, the first signs the rpm). - Assets already on a release are kept unless the force input is set. The AppImage, .deb, .rpm and latest-linux.yml go up as one unit, binaries first and latest-linux.yml last; the indexes are built from the packages on the release. latest-linux.yml restates the signed rpm's sha512/size. - One fixed concurrency group for publishing, timeouts on every job, artifact names without "/", and the Flatpak lint fails on any error but the four every non-Flathub build reports (listed with reasons). Build Windows: the same guards and split (build on Windows read-only, attach from an ubuntu job that runs nothing but gh), the .exe.blockmap is attached for differential updates, latest.yml goes last, the PR paths cover the lockfile, electron/**, fileKinds.js, the icon scripts and brand/, and the build checks the Store manifest's file types. Checks (new, M17): npm ci, lint, npm test, vite build, an unpacked Linux package whose app.asar must hold the main process's packages and none of the renderer's, and a start under xvfb. ESLint now covers electron/*.cjs and scripts/ (2 errors in electron/main.cjs at base, left for the owners). Scripts: setup-signing-key.sh captures `gh secret list` instead of piping it into grep -q, stops when gh fails, never writes over a key backup, and gains --new-key-only for rotations. setup-msstore-credentials.ps1 runs on Windows PowerShell 5.1 and no longer claims a workflow submits to the Store. Docs: the release flow and guards (README), the signing key, its fingerprint and the rotation procedure (SECURITY.md), the snap's limits with hidden and system files (M20), npm test and the checks (CONTRIBUTING). flathub.json enables automerge for the bot's PRs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… a draft The snapbuild artifact is read only by the snap job, but one day was shorter than a "re-run failed jobs" may come. A publish run that stopped at a draft can be finished by publishing the release and re-running that job, which the error message and the README now say. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The publish jobs run no npm and no third-party action, but they do use GitHub's own checkout and download-artifact (pinned); the header comments now say so instead of "only gh". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…age scripts, smaller packages, PR checks) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…SONC
- indentDetect.js (pure, unit-tested): tabs or spaces and the width, by the
majority of indented lines and the most common indent step; Makefiles and
Go always use tabs; otherwise the "Indent size" setting (M7).
- CodeEditor takes the document's indentation (indentUnit and tabSize, live
through a compartment), and spell checking from EditorPrefsContext behind
its own compartment, so toggling needs no remount. JSON-with-comments files
(tsconfig.json, .jsonc, VS Code settings) are no longer syntax-checked.
- StatusMenu: FormatStatus's status-bar popover (portal, keyboard, Escape)
as a shared component; IndentStatus ("Spaces: 4" / "Tab size: 4") uses it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…mbing - M1: a bare Escape only acts as a shortcut when focus is on nothing in particular; editors, fields, buttons, menus, find bars and dialogs keep it. - M2/L9: the shortcut recorder only listens while Settings is open, takes bindingFromEvent/isAllowedShortcut, and the shared matchesShortcut and label helpers replace App's own parser (the + key works as "Plus"). - M7: each document's indentation is detected on open and shown and changeable in the status bar; Save As to a Makefile or Go file uses tabs. - L3: the focused split pane's document is the one commands act on (save, close, Edit/View, print/export, find, undo, the header, the status bar and Ln/Col) without changing the active tab; activating the split pane's document swaps the panes instead of leaving a one-pane split. - L5: a diff closes when the tab changes, and with its documents. - Palette ":" "@" "#" modes with revealLine (editor caret, or the reading view scrolled to the block's data-line); one openPalette/openSettings mechanism for every entry point. - Settings: spellcheck and remoteImages at boot, setSpellcheck at boot and on change, spell check on the Markdown editor only, remote images through every render and MarkdownView's remoteImagesAllowed/onAllowRemoteImages. - L18: the boot effect runs once per page, and updateSetting's updater is pure. - Tab strip follows the WAI-ARIA tabs pattern (roving tabindex, arrows, Home/End, Enter/Space, Delete), with the close button outside role="tab". - showItemInFolder errors reach the status bar; the home screen's recent files refresh when the window regains focus. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Since 1.14.0 the renderer opens .txt as plain text, but moving its Windows registration to another ProgID would orphan the default users already gave FATE for .txt. Document that, and let the consistency test expect exactly this difference. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…it-view focus, palette modes, settings plumbing) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ocked-down preview Version 1.14.0, its changelog entry, AppStream release and the README shortcut table (Find in the reading view, Escape's new scope, palette modes, the menu bar). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
QA found Ctrl+S on an unedited file with mixed line endings normalised it to its majority ending: saving a clean tab rewrote the file. A save with nothing to write now writes nothing (deleted or changed-on-disk files still save). A document's invalid Mermaid diagram is the document's mistake, so it logs a warning rather than an error. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- checks.yml: Actions runs steps with `bash -e`, so the expected 124 from `timeout` ended the step before its status was read. Capture it with `|| status=$?`. - Release workflows no longer cache npm: a Run workflow recovery checks out a tag the dispatcher chose, and its cache would land in the default branch's scope (CodeQL actions/cache-poisoning). - useMermaid: escape the diagram id fully before building the scope regex (js/incomplete-sanitization). - paletteModes: strip heading tags until none are left, and an unclosed one at the end (js/incomplete-multi-character-sanitization). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Code scanning (actions/cache-poisoning) flagged the build jobs: a Run workflow recovery checked out a ref named in the `release_tag` input and ran its code in a job whose caches land in the default branch's scope. A recovery run is now started from the tag itself (Use workflow from → Tags) and builds what it was started from; a `release_tag` that names another tag is refused. Tag pushes, the release path, are unchanged. README, comments and error messages describe the new way. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes everything in the 1.13.4 code review (6 critical, 10 high, 21 medium, 25 low findings) and adds the features it proposed. The full change list is the new
CHANGELOG.mdentry.Highlights
openExternalpolicy.fate-localserves local images only (no UNC/SMB, so no NTLM leak).:,@and#.app.asardrops from 211 MB to 13 MB.checks.ymlruns on PRs.Testing
npm run lint: clean, now includingelectron/**/*.cjsandscripts/**.npm test: 269/269node --testcases. They cover file formats, atomic saves, live reload, backups, the settings whitelist, link security, the app menu, spellcheck, launch arguments, palette modes, language detection, shortcuts, indentation, preview paths, maths, deb/rpm scripts, the signing keyring, the appx manifest and the Windows registration lists.vite buildpasses.Only a real release can prove
Windows-only paths (UNC refusal, the native spellchecker, Explorer arguments) are unit-tested but not run.
Not done
actions/setup-nodeandactions/upload-artifactare still referenced by@v4(markedTODO(pin)). Resolving their SHAs was blocked in this environment.🤖 Generated with Claude Code