Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Leafdown uses lightweight [Keep a Changelog](https://keepachangelog.com/en/1.1.0

### Added

- Keep named folders with their own tabs and unsaved drafts. Switch from the navigator, welcome, File menu, or command palette; an active context opens in its existing window. Named folders remain available when session restoration or recent history is off, and closing their windows parks their drafts without writing document files.
- Search the keyboard shortcuts. `Help > Keyboard shortcuts` opens on a search field that narrows the list by command, menu, or key name, such as `heading` or `ctrl shift f`, and `Record keys` finds what a combination does when you press it.
- Add a File icons setting to Preferences > Appearance. Colored draws folders in amber and gives articles and each other type of file a color of its own, the same under every accent color. Neutral, the default, keeps the icons gray.

Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ This suite is separate from `pnpm check` and runs as its own CI job on pull requ
pnpm test:e2e:desktop:run -- --workers 2
```

The runner accepts one to four workers on consecutive ports starting at 4445. Each worker gets its own application identity, persisted data, WebView2 root, fixtures, and artifacts. Independent scenarios can overlap; the `persistence` scenario keeps its write and restart sequence ordered on one worker.
The runner accepts one to four workers on consecutive ports starting at 4445. Each worker gets its own application identity, persisted data, WebView2 root, fixtures, and artifacts. Independent scenarios can overlap; the `persistence` and `named-folders` scenarios keep their write and restart sequences ordered on one worker.

To run one scenario against an already built binary:

Expand Down
11 changes: 8 additions & 3 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ Session and editor workflows request yes/no decisions through the shared confirm
- **Session:** the window's open documents, each in a tab with one of them active, plus an optional folder context.
- **Tab:** one open document in a window, with the editor that holds it and that editor's state.
- **Source projection:** a temporary editable source representation of a supported Markdown object while the editor retains its canonical document model.
- **Workspace:** not an application domain term; Leafdown creates neither a workspace model nor workspace metadata.
- **Named folder:** an optional application-managed context containing one ordinary root, a display name, ordered tabs, and document snapshots. It may be active in one window or parked, with no metadata in its root.
- **Workspace:** not an application domain term; named folders require no workspace initialization or folder-local metadata.

## Runtime Model

Expand Down Expand Up @@ -231,9 +232,13 @@ The listener is registered before the first take, so a document routed during st

### Kept Session Workflow

A window's frontend starts after its persisted stores -> With `Restore session` on, it takes its kept state from the backend, the first window opening the other kept windows -> Session claims each saved document, reads clean ones through the open path and builds dirty ones from their snapshots and kept file version, adds the tabs, checks each dirty document's file as the External Change workflow does, and loads the folder context -> The window then reports its state every 5 seconds after a change, reading each edited dirty document's settled Markdown as HTML export does -> Launch documents open after the restored tabs. With `Restore session` off, the first window clears the kept session instead.
A window's frontend starts after its persisted stores -> With `Restore session` on, it takes its kept state from the backend, the first window opening the other kept windows -> Session claims each saved document, reads clean ones through the open path and builds dirty ones from their snapshots and kept file version, adds the tabs, checks each dirty document's file as the External Change workflow does, and loads the folder context -> The window then reports its state every 5 seconds after a change, reading each edited dirty document's settled Markdown as HTML export does -> Launch documents open after the restored tabs. With restoration off, the first window clears automatic restart state and unnamed snapshots while preserving named contexts. A new window explicitly assigned a named context restores it independently of that setting.

Each report rewrites the session record from the process's own windows and removes the snapshots they no longer refer to, so the record assumes one process. Development runs therefore keep their session in a directory of their own: they share the installed application's identifier and can run beside it, since they leave out the single-instance plugin.
Session owns named-folder orchestration, presentation, and its revision-ordered shared list. Rust's `kept_session` owns context claims, parked state, restart eligibility, and snapshot files under one lock. Its version-2 record stores each named context's document state once and references it for restart; version-1 unnamed windows and snapshots remain readable. A report carries its expected context ID, so a delayed report cannot overwrite a newly selected context. Snapshot revisions use new IDs, and atomic record replacement precedes removal of unreferenced snapshots. A failed record write rolls back the in-memory context change.

Switching waits for pending saves, takes a final snapshot, claims the destination, and only then replaces the frontend tabs. Editing and application command dispatch pause during that transition. The backend checks and reserves the destination's saved paths while holding the context lock; document claims consult parked named contexts before the active-window registry. The lock order is kept-session state followed by window-document state. Low-level document open, folder options, and session task queues remain separate from context orchestration to preserve dependency direction. A context restored in the same window shares the existing kept-tab recovery path and never runs ordinary folder index opening.

Each report rewrites the session record from the process's active and parked contexts and removes snapshots they no longer refer to, so the record assumes one process. Development runs therefore keep their session in a directory of their own: they share the installed application's identifier and can run beside it, since they leave out the single-instance plugin.

### Drop Workflow

Expand Down
7 changes: 4 additions & 3 deletions docs/decisions.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@

### No vault or workspace model

**Decision:** Opening a folder does not create a vault, workspace, import process, database, or metadata files in that folder.
**Decision:** Opening a folder does not create a vault, workspace, import process, database, or metadata files in that folder. A writer may optionally keep one ordinary folder as a named working context, with its display name, tabs, and unsaved drafts stored entirely in application data.

**Rationale:** Leafdown is designed as a document-centric editor rather than a personal knowledge management system with custom workspace setups.

Expand All @@ -99,6 +99,7 @@
- Existing folder structures remain unmodified.
- Application metadata is stored externally from the opened folders.
- Folder workflows execute without initialization steps.
- Named folders remain separate from recent history. A context can be active in one window, and a saved file has one owner across active and parked contexts. Switching parks the current named context before restoring another; a failed park retains the current work.

### Wiki links use document-relative paths

Expand Down Expand Up @@ -137,7 +138,7 @@

### Keep the session and its unsaved text across restarts

**Decision:** While `Restore session` is on, Leafdown keeps each window's folder context and tabs, and the unsaved text of every document they hold, from one run to the next. It snapshots each document whose unsaved text changed, untitled documents included, into its application data every few seconds. Quitting takes a final snapshot instead of asking about unsaved changes, and the next launch reopens the windows with that text still unsaved. Only Save writes a document's file. The model follows the session snapshot and periodic backup of Notepad++.
**Decision:** Leafdown snapshots each named folder's tabs and unsaved text in application data independently of `Restore session`. That setting controls automatic reopening of windows left at quit or crash and keeps the existing unnamed-window session behavior. Switching or closing a named context takes a final snapshot and parks it; failed persistence stops the transition. Restart assignments reference the same context state and snapshots rather than a second copy. Only Save writes a document's file. The snapshot and periodic backup model follows Notepad++.

**Rationale:** Asking on quit treats an unsaved document as something to be disposed of, when the author usually means to return to it. Keeping it lets quitting, restarting for an update, or a crash cost at most a few seconds of typing, and it covers untitled documents, which have no file to write. Saving to the file automatically was the alternative ([issue #730](https://github.com/Azganoth/leafdown/issues/730)). It would write half-finished text into files that version control, sync, and other tools read, and it still could not keep an untitled document. Snapshots outside the file keep each file as the author last chose to write it, as [Markdown files remain the source of truth](#markdown-files-remain-the-source-of-truth) requires.

Expand All @@ -147,7 +148,7 @@
- Snapshots and the session record live in Leafdown's application data, never in opened folders. A snapshot holds only unsaved text: saving or discarding a document deletes it. Snapshots are not revisions and are not kept as history.
- Closing a tab, the folder, or a window while Leafdown keeps running still asks, since what it closes leaves the session.
- A file that changed or went missing between runs restores its snapshot as an unsaved document with that change recorded, so Save warns before overwriting.
- Turning `Restore session` off deletes stored snapshots and the session record, and quitting asks as before.
- Turning `Restore session` off clears automatic restart state and unnamed snapshots, while named contexts and their drafts remain available. Unnamed windows then ask about unsaved changes before closing; named windows still park.
- Recovery after a crash reaches back only to the last snapshot.

### Use one hybrid document surface
Expand Down
14 changes: 13 additions & 1 deletion docs/help/file-and-folder-workflows.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,18 @@ Browse notes without losing your place, keep the documents you need, and search

On Windows, Leafdown is available in File Explorer's **Open with** menu and **Settings > Apps > Default apps** after installation. Installing does not change your default app. Opening an associated file starts Leafdown or brings it forward; starting Leafdown again without a file opens a new window.

## Keep named folders

Open **File > Named folders…**, enter a display name for the current folder, and choose **Keep this folder**. Use **Choose folder…** first if you want another folder. The folder remains ordinary: its name on disk and its files stay unchanged, and Leafdown keeps the entry and unsaved drafts in application data.

Choose a named folder above the navigator, on welcome, in **File > Named folders…**, or by searching its name in the command palette. Switching parks the current named folder and restores the selected folder's own tabs and drafts, including untitled notes and documents outside its root. Restoring starts a fresh undo history. If keeping the latest text fails, the current work stays open. Entering from an unnamed session asks about its unsaved changes before replacing its tabs.

**Open in new window** opens a parked context separately; choosing one already active brings its window forward. Opening a document owned by another context brings that context forward too, so there is only one editable copy. **Park this folder**, **Close folder**, and opening an ordinary folder leave a named context's drafts available for later.

Use **Rename** to change the display name. Park the context before using **Locate folder…** to find a moved root, or **Remove** to forget the entry. Locating updates document paths inside the old root and retains outside tabs. A missing or unreadable root still leaves its entry and recoverable drafts available. Removing an entry does not delete its directory or saved files, and disposing of its unsaved drafts requires confirmation.

Named folders are separate from recent history and survive clearing it. They also survive turning **Restore session** off: that setting controls which windows reopen automatically. Closing a named context's window parks it, even while other windows remain open or restoration is off. Only **Save** writes a Markdown file.

## Work with tabs

Opening a file through Open, recent items, a drop, or the operating system gives it an ordinary tab after the active one. Opening an already open file switches to its tab.
Expand Down Expand Up @@ -40,7 +52,7 @@ A file is open in only one window at a time. Opening it again brings its existin

Renaming a file or folder moves its open tabs in every window and preserves edits. Deleting closes its tabs in the deleting window; other windows keep their text and mark the file missing. Folder replacement skips a file with unsaved edits in another window and reports it.

**File > Close window** (`Alt+F4`) closes one window. **File > Quit** (`Mod+Q`) closes them all. With **Restore session** on, quitting or closing the last window keeps the windows, tabs, and unsaved edits for next launch without asking, unless saving that session fails. Closing a window while others remain open asks about its unsaved changes as usual. With restoration off, unsaved changes prompt before closing. Canceling a quit stops there, leaving that window and those not yet asked open.
**File > Close window** (`Alt+F4`) closes one window. **File > Quit** (`Mod+Q`) closes them all. Named contexts always park their tabs and drafts; failed persistence keeps the window open. For unnamed sessions, **Restore session** keeps windows and unsaved edits at quit or when closing the last window, and closing a window while others remain asks as usual. With restoration off, unnamed unsaved changes prompt before closing. Canceling a quit stops there, leaving that window and those not yet asked open.

## Work in the article navigator

Expand Down
4 changes: 3 additions & 1 deletion docs/help/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,12 @@ The tab's dot disappears when its changes have been saved. Session restoration k

## Leave and come back

Close one tab with **File > Close document** (`Mod+W`). Leafdown asks before discarding that tab's unsaved changes. Closing a folder or a window while other Leafdown windows remain open also asks about unsaved work. Closing the last window keeps its session instead when restoration is on.
Close one tab with **File > Close document** (`Mod+W`). Leafdown asks before discarding that tab's unsaved changes. A named folder parks its tabs and drafts when you leave it or close its window. An unnamed folder or a window closed while others remain asks about unsaved work. Closing the last window keeps its session instead when restoration is on.

**File > Quit** (`Mod+Q`) keeps your windows, tabs, and unsaved edits for the next launch when **Preferences > Files > Restore session** is on, as it is by default. Turn it off to have quitting ask about unsaved changes instead.

To keep distinct sets of tabs and drafts, use **File > Named folders…**. Named folders remain available with restoration off, and closing their windows parks them. See [File and folder workflows](file-and-folder-workflows.md).

If another program changed a file, Leafdown warns before overwriting it. If the file went missing, Save offers Save as.

## Choose your next step
Expand Down
2 changes: 1 addition & 1 deletion docs/help/settings-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ Two related settings live in the workspace itself: **View > Always on top** keep

### Return to unfinished work

Leave **Restore session** on to reopen windows, tabs, and unsaved text after quitting. It is on by default. Turn it off to have quitting ask about unsaved changes and the next launch start at the welcome screen.
Leave **Restore session** on to reopen the windows left open at quit or crash, with their assigned contexts, tabs, and unsaved text. It is on by default. Turning it off clears automatic restart state and unnamed sessions, so the next launch starts at welcome and unnamed windows ask about unsaved changes before closing. Named folders and their drafts remain available; their windows still park on close. Parked contexts stay parked until you select them.

> **Tip:** Restoring a session does not save your Markdown files. Use **File > Save** (`Mod+S`) when you want the file on disk to contain your changes.

Expand Down
5 changes: 3 additions & 2 deletions docs/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ Global settings persist across application launches unless specified otherwise.
- **Ignored directories for folder scans:** directory name list. Default: `.git`, `.hg`, `.svn`, `node_modules`, `target`, `dist`, `build`, `.cache`.
- Directory name matching is case-sensitive on Unix-like systems and case-insensitive on Windows. Matching directories and their contents are recursively skipped.
- **Restore session:** On or Off. Default: On.
- Keeps each window's folder context and tabs, and the unsaved text of the documents in them, for the next launch, so quitting does not ask about unsaved changes. See [Kept Session](./specification.md#kept-session).
- Automatically reopens windows left at quit or crash, including their assigned named contexts. Turning it off clears automatic restart state and unnamed sessions; named folders and their unsaved drafts remain available. See [Kept Session](./specification.md#kept-session).
- **When dropping a folder:** Open or Insert folder link. Default: Open.
- **When dropping a Markdown file:** Open or Insert file link. Default: Open.

Expand Down Expand Up @@ -88,6 +88,7 @@ Shortcuts use `Mod` as the primary platform modifier (`Ctrl` on Windows/Linux, `
- **New window** (`Mod+Shift+N`)
- **Open...** (`Mod+O`)
- **Open folder...** (`Mod+Shift+O`)
- **Named folders…**: select, keep, rename, park, locate, or remove a named folder, or open it in a new window. Also available above the navigator and on welcome; individual named folders appear in the command palette. No default shortcut.
- **Open recent**
- **Open last closed** (Deferred)
- **Recent files**
Expand Down Expand Up @@ -390,7 +391,7 @@ A submenu trigger carries the same state as the commands behind it: it is disabl

- `Open file location` requires an active saved document path. If the native file reveal fails because the path is missing or inaccessible, Leafdown shows an error.
- `Reveal in sidebar` requires a folder context and an active saved article in the article navigator.
- `Close folder` requires a folder context.
- `Close folder` requires a folder context. In a named context it parks that context, retaining its tabs and drafts.
- `Open last closed` requires a last-closed item.
- `Clear recent items` requires at least one recent file or folder.
- `Toggle sidebar`, `Sort articles by`, `Show all files`, `Collapse all folders`, and `Expand all folders` require a folder context and an available article navigator.
Expand Down
Loading