Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
f1d4b07
fix(deps): update YAML and sanitizer security patches
Kechos23 Oct 11, 2026
fb28777
fix(storage): retain newer recovery journals after saves
Kechos23 Oct 11, 2026
4353a54
fix(review): recover comment metadata without losing newer replies
Kechos23 Oct 11, 2026
855d593
fix(editor): prevent reference chooser from stealing input focus
Kechos23 Oct 11, 2026
de4f5e8
fix(storage): coordinate deletions with queued document saves
Kechos23 Oct 11, 2026
07db99f
fix(storage): refuse destructive desktop document move collisions
Kechos23 Oct 11, 2026
d5d28ec
fix(storage): guard workspace replacement against pending operations
Kechos23 Oct 11, 2026
21905e6
docs(storage): explain recovery and workspace maintenance boundaries
Kechos23 Oct 11, 2026
bf0e404
test(storage): seed persisted document for stale writer move
Kechos23 Oct 11, 2026
3596f33
test(theme): compare final tokens after animated theme changes
Kechos23 Oct 11, 2026
54fcbae
test(trash): synchronize persistence and allow selection order
Kechos23 Oct 11, 2026
042c08f
fix(editor): prevent deferred media chooser focus from redirecting input
Kechos23 Oct 11, 2026
42d6c06
fix(editor): prevent deferred table chooser focus from changing row i…
Kechos23 Oct 11, 2026
f1b4041
feat(storage): isolate linked recovery hierarchy and detach imported …
Kechos23 Oct 11, 2026
8c3c1cf
feat(desktop): integrate reviewed linked workflows and draft safety g…
Kechos23 Oct 11, 2026
b3a1a72
test(desktop): cover linked safety and folder workflows without custo…
Kechos23 Oct 11, 2026
eee37bd
docs(desktop): explain linked workflows and filesystem safety boundaries
Kechos23 Oct 11, 2026
9e8b706
feat(i18n): complete reviewed Linked Workspace UI catalogs
Kechos23 Oct 11, 2026
d6762f2
fix(linked): persist new folder roots before scanning
Kechos23 Oct 12, 2026
578b987
fix(linked): require durable metadata before scan success
Kechos23 Oct 12, 2026
0598011
fix(linked): normalize Windows UNC source identities
Kechos23 Oct 12, 2026
9fce0a1
fix(i18n): preserve copy import labels across language updates
Kechos23 Oct 12, 2026
250a98e
fix(linked): serialize concurrent folder registration commits
Kechos23 Oct 12, 2026
637ae5f
test(linked): verify organization write failures through desktop backend
Kechos23 Oct 12, 2026
76f01e7
fix(linked): reject managed Vault source and export destinations
Kechos23 Oct 12, 2026
634c95d
fix(linked): persist scanned documents before reporting completion
Kechos23 Oct 12, 2026
6964424
fix(linked): distinguish conflict recovery from requested conversion …
Kechos23 Oct 12, 2026
afac215
test(ui): align broad menu assertions with copy import labels
Kechos23 Oct 12, 2026
c3f8f37
test(linked): verify managed path guards and IndexedDB session bounda…
Kechos23 Oct 12, 2026
50a5668
fix(i18n): translate incomplete linked scan notification
Kechos23 Oct 12, 2026
d6a2c50
fix(workspace): resolve linked workspace review findings
ThisIs-Developer Oct 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion assets/i18n/audit-ui-locales.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@ import { readFile, readdir } from 'node:fs/promises';

const CATALOG_DIR = new URL('./', import.meta.url);
const EXPECTED_LOCALES = ['bg', 'de', 'en', 'es', 'fr', 'it', 'ja', 'ko', 'pl', 'pt', 'ru', 'tr', 'tw', 'uk', 'zh'];
// Keyboard key names and bare capacity values may legitimately remain identical.
// Keep the exception finite: ordinary untranslated UI labels must still fail.
const SHARED_TECHNICAL_LITERALS = ['Alt', 'Tab', 'Cmd', 'F11', 'F3', '10 MB.', '20 MiB.'];
const REVIEWED_IDENTICAL_TRANSLATIONS = {
de: ['Option'],
it: ['$$ formula $$', ...Array.from({ length: 6 }, (_, index) => `Desktop: Ctrl/Cmd+${index + 1}`)]
};
const ALLOWED_IDENTICAL_TRANSLATIONS = {
bg: ['{{0}}m', '{{0}}m {{1}}s', '{{0}}s', 'Ab', 'Ctrl', 'Git Graph', 'GitHub', 'GitHub URL', 'h1, h2, h3, h4, h5, h6', 'HTML', 'JavaScript', 'Markdown (.md)', 'Markdown Viewer', 'MD', 'PDF', 'PNG', 'Shift', 'SVG', 'ThisIs-Developer'],
de: ['{{0}}m', '{{0}}m {{1}}s', '{{0}}s', 'Ab', 'Explorer', 'FAQ', 'h1, h2, h3, h4, h5, h6', 'HTML', 'JavaScript', 'Link', 'Markdown (.md)', 'Markdown Viewer', 'MD', 'Name', 'PDF', 'PNG', 'SVG', 'ThisIs-Developer', 'Version', 'Wrap'],
Expand Down Expand Up @@ -54,6 +61,7 @@ addFailures(failures, 'en', 'non-English source pollution', englishKeys.filter(s
addFailures(failures, 'en', 'non-interface source key', englishKeys.filter(source =>
/^\d+\.\d+\.\d+(?:-[\w.-]+)?$/i.test(source)
|| /^bi(?:\s+bi-[\w-]+)+$/i.test(source)
|| /\[[\w-]+[~|^$*]?=[^\]]*\]/.test(source)
|| /(?:^|\s)(?:style|class|id|aria-[\w-]+|data-[\w-]+|role|tabindex)=/i.test(source)
));

Expand All @@ -75,7 +83,7 @@ for (const locale of files) {
|| (key.includes('Markdown') && !catalog[key].includes('Markdown'))
));
if (locale !== 'en') {
const allowed = new Set(ALLOWED_IDENTICAL_TRANSLATIONS[locale] || []);
const allowed = new Set([...SHARED_TECHNICAL_LITERALS, ...(ALLOWED_IDENTICAL_TRANSLATIONS[locale] || []), ...(REVIEWED_IDENTICAL_TRANSLATIONS[locale] || [])]);
addFailures(failures, locale, 'unexpected English fallback', keys.filter(key =>
catalog[key] === key && !allowed.has(key)
));
Expand Down
485 changes: 424 additions & 61 deletions assets/i18n/bg.json

Large diffs are not rendered by default.

485 changes: 424 additions & 61 deletions assets/i18n/de.json

Large diffs are not rendered by default.

547 changes: 455 additions & 92 deletions assets/i18n/en.json

Large diffs are not rendered by default.

485 changes: 424 additions & 61 deletions assets/i18n/es.json

Large diffs are not rendered by default.

485 changes: 424 additions & 61 deletions assets/i18n/fr.json

Large diffs are not rendered by default.

485 changes: 424 additions & 61 deletions assets/i18n/it.json

Large diffs are not rendered by default.

485 changes: 424 additions & 61 deletions assets/i18n/ja.json

Large diffs are not rendered by default.

485 changes: 424 additions & 61 deletions assets/i18n/ko.json

Large diffs are not rendered by default.

485 changes: 424 additions & 61 deletions assets/i18n/pl.json

Large diffs are not rendered by default.

485 changes: 424 additions & 61 deletions assets/i18n/pt.json

Large diffs are not rendered by default.

485 changes: 424 additions & 61 deletions assets/i18n/ru.json

Large diffs are not rendered by default.

485 changes: 424 additions & 61 deletions assets/i18n/tr.json

Large diffs are not rendered by default.

485 changes: 424 additions & 61 deletions assets/i18n/tw.json

Large diffs are not rendered by default.

485 changes: 424 additions & 61 deletions assets/i18n/uk.json

Large diffs are not rendered by default.

485 changes: 424 additions & 61 deletions assets/i18n/zh.json

Large diffs are not rendered by default.

63 changes: 63 additions & 0 deletions assets/linked-workspace-guide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Welcome to Linked Workspace

Linked Workspace lets you edit Markdown files where they already live on your computer.

## Link a file or folder

Use **Link Markdown files** or **Link Markdown folder** in the Explorer toolbar or the Linked Workspace menu. A linked folder includes Markdown files in its subfolders.

Right-click a linked folder or subfolder to add existing file or folder links inside it. This only groups links in the sidebar: originals stay in their current locations. Grouping survives reopening the app and does not change which source a folder scans. Removing or converting the parent source folder keeps independently grouped links available outside that group.

Dropping an external Markdown file imports an independent Vault copy. Use the explicit linking actions when you want to keep an original file linked.

Linked documents use a file-with-arrow icon; linked folders use a folder-with-arrow icon. Hover over a linked document to see its original path.

## Drafts and saving

Editing automatically stores a recovery draft inside the application's **Linked Workspace** storage folder. It does **not** automatically overwrite your original file.

Linked-folder recovery drafts mirror the source subfolders under a separate recovery root. ID suffixes distinguish roots and documents with identical names. These are internal recovery copies, not the original files; do not manually reorganize this storage.

- **Ctrl+S** or **Save** updates the original linked file.
- **Save As** creates a new external file and a separate link. The previous document and original remain unchanged.
- Saving a Vault document externally keeps its independent Vault copy and activates the new linked document.

The Save shortcut works across keyboard layouts, including Greek. Ordinary Save updates the original directly and reports success. Conflict and missing-original warnings still require confirmation.

## Independent Vault copies

**Convert to Workspace copy** turns a linked draft into an independent Vault document without changing the original. You can also drag a linked document into the Vault or one of its folders.

Linked documents cannot move directly into Secret Workspace. Convert to an independent Vault copy first, then move that copy into Secret Workspace.

Reopening the original creates or activates its link; it never replaces the independent Vault copy.

You can also **Convert to Workspace copy** on a linked folder or subfolder, or drag it into the Vault or one of its folders. This converts the currently linked drafts and preserves their subfolder structure. Excluded files are not included. A complete root conversion removes the folder link; a subfolder conversion keeps the root linked.

If a conversion stops partway through, completed copies remain in the Vault and the remaining drafts stay linked. The notification reports partial completion. Retrying uses the remembered destination when it is still available. Existing destination folders are not silently merged; a separate copy name is used when needed.

## Save a Vault folder externally

On a normal Vault folder, **Save As linked folder…** lets you choose an external parent directory. It creates a new folder inside that directory, preserves the subfolder structure, adds the new linked folder, and activates a linked copy. The Vault folder and documents remain independent and unchanged.

Existing destination names get a copy suffix rather than being overwritten. This exports Markdown documents, not a complete archive of images or other assets. If writing or linking stops partway through, the notification identifies the destination; partial external copies are kept, and Vault drafts remain available.

## External changes and rescanning

The application checks linked sources for external changes. If your local draft conflicts with a changed original, it keeps the draft and shows a warning.

Hover over the warning icon to see the source status. **Reload from disk** replaces your draft with the original and asks before discarding unsaved changes. Saving over a conflicting original also requires confirmation. If the original is missing or unreadable, your draft is retained; do not assume the source was deleted just because access failed.

**Rescan folder** discovers new files and restores links previously removed or converted, if the originals still exist. Re-linking the same folder does the same. Automatic background refreshes respect removed links instead of restoring them.

## Closing and removing

Closing a tab keeps its document in the workspace. **Remove linked file** or **Remove linked folder** removes links from the application, not the originals from disk.

## Backups and safety

Workspace backups include internal recovery drafts, not copies taken from external originals. Restoring a backup keeps linked drafts as recovered documents without reconnecting their original paths or enabling writes to those originals. Link originals explicitly when you want to reconnect them.

Multi-file conversion and external folder saving are not all-or-nothing operations. The application validates drafts and destinations and verifies writes, but the desktop filesystem APIs cannot make a whole folder write atomic or eliminate every race with another program. Keep backups of important originals, and review any incomplete-operation message before retrying.

This guide is an internal, editable document, not a link to an external file. You can delete it without deleting any source file; it will not reappear on every launch.
11 changes: 11 additions & 0 deletions assets/lucide-icons.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions desktop-app/neutralino.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"nativeAllowList": [
"app.exit",
"os.showOpenDialog",
"os.showFolderDialog",
"os.showSaveDialog",
"os.showMessageBox",
"os.getPath",
Expand All @@ -35,6 +36,9 @@
"filesystem.copy",
"filesystem.move",
"filesystem.remove",
"filesystem.createWatcher",
"filesystem.removeWatcher",
"filesystem.getWatchers",
"storage.setData",
"storage.getData",
"storage.getKeys",
Expand Down
3 changes: 3 additions & 0 deletions desktop-app/prepare.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ fs.copyFileSync(path.join(ROOT_DIR, "script.js"), path.join(jsDest, "script.js")
console.log("✓ Copied script.js → resources/js/script.js");

fs.copyFileSync(path.join(ROOT_DIR, "workspace-storage.js"), path.join(jsDest, "workspace-storage.js"));
fs.copyFileSync(path.join(ROOT_DIR, "native-markdown-source.js"), path.join(jsDest, "native-markdown-source.js"));
console.log("✓ Copied workspace-storage.js → resources/js/workspace-storage.js");

fs.copyFileSync(path.join(ROOT_DIR, "preview-worker.js"), path.join(jsDest, "preview-worker.js"));
Expand Down Expand Up @@ -331,6 +332,8 @@ async function prepareOfflineDependencies() {
html = html.replace(/<script\b[^>]*type="application\/ld\+json"[^>]*>[\s\S]*?<\/script>/i, '');
html = html.replace(/\s*<!-- Browser fallback for hosts that do not run the Cloudflare Pages SEO middleware\. -->\s*<script type="module" src="assets\/seo-metadata\.mjs"><\/script>/i, '');

html = html.replace(/<script src="native-markdown-source\.js" defer><\/script>/g, '<script src="/js/native-markdown-source.js"></script>');

// Inject Neutralino script tags
html = html.replace(
/<script\s+src="workspace-storage\.js"[^>]*><\/script>\s*<script\s+src="script\.js"[^>]*><\/script>/i,
Expand Down
Loading
Loading