Run cumulus for real: the app core and its files (stage 8a)

The playground becomes app::App, and `cumulus` runs it: the main loop
with terminal setup and a panic hook, and the startup rules (./ideas.cum,
else ~/ideas.cum, made the first time; files and folders by name;
`cumulus new`; --paths).

The documents' files:
- autosave a second after the latest change (`:set noautosave` for
  vim-style :w), [+] in the document bar for unsaved changes; quitting
  saves first; without autosave :q refuses, :q! drops, ZZ saves first;
- a file changed outside cumulus is read again as an undo step; with
  unsaved changes here too, cumulus asks which to keep; a file with a
  mistake in it holds autosave until it's fixed or :w saves over it; a
  deleted one waits for :w; nothing is reloaded under an edit;
- the day's first save backs the file up to the data folder (30 days);
- a document open in another cumulus is read-only ([RO]) until that one
  lets go: OS file locks in the data folder, so a crash leaves none;
- where you left off (cursor, zoom, scroll), the recent documents (now
  in the documents picker) and the command history, in memory.ron;
- :e (alone: read it again; ! drops changes), :new, :bd, :w, :wa, :x,
  :qa, <Space>i for :in, and :set for every setting. glyphs=ascii now
  reaches the frames and bars too, not just the canvas and outline.

Stage 8 comes in four parts with a commit each (the user's choice); 8b
is the settings files. The example is now a playground running the app
on copies of the sample documents in a temporary folder, and the
end-to-end tests moved to src/app/tests.rs, with tests for the files.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
skkeyeandClaude Opus 5.5 committed 2026-10-01 23:18:21 -04:00
1 parent e6f3c06ecc
commit f6dc4564aa
47 files changed
+3985 -1024

No files matched your search

+64 -26
View File
@@ -122,10 +122,10 @@ lines.
- A title left empty comes back as it was, with a note saying so; a new idea
left empty goes away.
- Registers are shared by the whole app, as in vim: text yanked in one title
pastes into another. `"+` stays inside cumulus until stage 8 connects it to
pastes into another. `"+` stays inside cumulus until part 8c connects it to
the system clipboard.
- `.` inside the text repeats the last change there, typing included. `.` on
nodes comes with node yank and paste, in stage 8.
nodes comes with node yank and paste, in part 8c.
- As in vim: `cw` changes to the end of the word, `dw` stops at the end of the
line, `j` and `k` move by lines of text rather than wrapped rows, Esc steps
back onto the last letter typed.
@@ -189,15 +189,32 @@ lines.
keys and clicks go to them, a click outside a list closes it, and they stay
above the status bar.
**Files and saving**
**Files and saving** (settled in part 8a)
- Documents are RON files ending in `.cum`. Configs are TOML: `config.toml`,
`keys.toml` and `themes/<name>.toml`.
- Autosave about a second after a change (`autosave = false` for vim-style
`:w`). A file changed outside cumulus is reloaded; if you also had unsaved
changes, cumulus asks which to keep. Daily backups go to the data folder.
- Autosave a second after the latest change, so typing isn't saved letter
by letter (`autosave = false` for vim-style `:w`). Undoing back to what's
on disk counts as no change. The document bar marks unsaved changes `[+]`.
Quitting saves first; without autosave, `:q` with unsaved changes refuses
(`:q!` drops them), and `ZZ` saves, then closes, as in vim.
- Every second, cumulus looks at each file's time and size. A file changed
outside cumulus is read again as an undo step (`u` brings back what was on
screen); never under a text being edited or a node being moved, which it
waits for. If you also had unsaved changes, cumulus asks which to keep
(Esc: later; `:w` keeps yours, `:e!` takes the file's). A file with a
mistake in it (a hand edit half done) stops autosave until it's fixed or
`:w` saves over it; a deleted or moved file waits for `:w` to write it
again.
- The day's first save copies the file to the data folder's backups first
(`backups/<uid>/2026-10-01.cum`, the last 30 days kept).
- A document open in another cumulus opens read-only (`[RO]` in the bar)
until that one lets go of it. The lock is the operating system's lock on
a file in the data folder, so a crash never leaves one behind.
- Undo per document, while cumulus runs: one step per command, per
text-editing session, and per node in rapid entry.
- Each document reopens where you left it (cursor, zoom, scroll).
- Each document reopens where you left it: the cursor, and the canvas's
zoom and scroll. The documents picker (`<Space>b`) lists the documents
opened lately after the open ones.
**Screen**
- Vim-style splits; panes on the same document share one cursor.
@@ -419,9 +436,9 @@ document has a portal pointing here.
)
```
**Config** (TOML) lives in `%APPDATA%\cumulus` (`~/.config/cumulus` on Linux);
`cumulus --paths` prints the folders. `:config` writes these files with a
comment on every option, and cumulus reloads them when they change.
**Config** (TOML) lives in `%APPDATA%\cumulus\config` (`~/.config/cumulus` on
Linux); `cumulus --paths` prints the folders. `:config` writes these files with
a comment on every option, and cumulus reloads them when they change.
```toml
# config.toml
@@ -467,15 +484,20 @@ label = "blocks"
question = "question" # a #question node gets the "question" style
```
**Data folder:** where you left off in each document (keyed by `uid`), command
history, capital marks, recent documents, daily backups, and lock files. If a
second cumulus opens a document that's already open, it opens it read-only.
**Data folder** (`%APPDATA%\cumulus\data`, `~/.local/share/cumulus` on Linux):
`memory.ron` (where you left off in each document, keyed by `uid`, the command
history, recent documents; capital marks come in part 8c), `backups/` and
`locks/`. If a second cumulus opens a document that's already open, it opens
it read-only.
**Command line**
- `cumulus` opens `./ideas.cum`, or `~/ideas.cum` if the current folder doesn't
have one.
- `cumulus garden.cum` opens that file; `cumulus some/folder` opens that
folder's default document.
have one. When neither exists, it starts a new `~/ideas.cum` (written once
something is added): one ideas document for everywhere (the user's pick in
part 8a).
- `cumulus garden.cum` opens that file (a new document if there's no such
file yet, as `:e` does); `cumulus some/folder` opens that folder's default
document. Several can be named; the first one shows.
- `cumulus new` creates `./ideas.cum`, and `cumulus new "Garden plans"` creates
`./garden-plans.cum`. If the file already exists, cumulus opens it and says so.
- `cumulus --paths`, `--version` and `--help`.
@@ -486,7 +508,7 @@ second cumulus opens a document that's already open, it opens it read-only.
cumulus/
├─ Cargo.toml rust-toolchain.toml build.rs rustfmt.toml .cargo/config.toml
├─ README.md DESIGN.md .gitignore .gitattributes
├─ examples/ runnable checkpoints during the build (canvas.rs, …)
├─ examples/ the playground: cumulus on copies of two sample documents (canvas.rs)
└─ src/
├─ main.rs command-line arguments, then app::run
├─ lib.rs the module list
@@ -501,15 +523,23 @@ cumulus/
│ motion, object, layout), list (the popup list help, menus and pickers
│ share), card (the K card), status_bar, document_bar, which_key,
│ command_line, theme, glyphs
├─ config/ paths, settings, keys_file, theme_file (TOML)
├─ storage/ document_file (RON), atomic save, backups, lock, state, watch (change polling)
└─ app/ action, mode, buffer, commands (keys every view shares), calls (`:` commands),
text_edit (a node's text being edited, rapid entry), registers, panes (the
split tree), popup (help, menu, pickers, card); later App, jumps, portals
(cache, backlinks, renames), update, render, terminal (setup, teardown,
panic hook)
├─ config/ paths (the config and data folders), settings (and `:set`); later keys_file,
│ theme_file (TOML)
├─ storage/ document_file (RON), write_atomically, names (file names from titles), backup,
│ lock, stamp (noticing changes made outside), memory (memory.ron)
└─ app/ mod.rs (App), input (keys and the mouse, routed), update (carrying actions out),
render, terminal (the main loop, setup, teardown, panic hook), startup (what
`cumulus` opens), documents (the open documents and their files), files
(saving, reloading, locks, where you left off), action, mode, buffer, commands
(keys every view shares), calls (`:` commands), text_edit (a node's text being
edited, rapid entry), registers, panes (the split tree), popup (help, menu,
pickers, card, questions); later jumps, portals (cache, backlinks, renames)
```
The end-to-end tests are in `app/tests.rs` (and `app/tests/files.rs`): they
copy the sample documents to a temporary folder, type keys and click on a test
terminal, and check the screen, the documents and the files.
**Flow:** a terminal event comes in → the key engine turns keys into a typed
`Command` (with its count and register) → the focused view answers it with
`Action`s, and `app::commands::shared` answers the commands the view leaves
@@ -550,6 +580,9 @@ trait View { // settled in stage 5
// Added in stage 7, with defaults:
fn status(&self) -> String; // its name in the status bar
fn cursor_area(&self, ctx: &ViewContext) -> Option<Rect>; // where the K card goes beside
// Added in part 8a, with defaults (only the canvas has a camera):
fn camera(&self) -> Option<Camera>; // kept with the document
fn set_camera(&mut self, camera: Camera); // where you left off
}
// ViewContext: the document, the cursor, the mode, the theme and the view settings.
```
@@ -576,11 +609,16 @@ it.
| 5 | The shared `View` interface, `Action` and `Mode`, pulled out of stages 2–4 | the example runs through it |
| 6 | Text editor: typing, plus vim normal mode inside text | editing titles in the example |
| 7 | Outline and Note views; splits (the split tree); status bar, document bar, help, `K` card, right-click menu, pickers (documents, colors, borders, styles, tags) | the example with splits and two documents |
| 8 | App core: main loop, terminal setup and panic hook, buffers and panes (from the example into the app), visual and connect modes, jumps, marks, registers, files (load, autosave, reload, backups, locks, where you left off), config/theme/keys, portals, backlinks and renames, startup rules | `cumulus`: v1 |
| 8a | App core: the main loop, terminal setup and panic hook, buffers and panes (from the example into the app), files (load, autosave, reload, backups, locks, where you left off), the startup rules and `cumulus new`, `:set` | `cumulus` |
| 8b | Settings: config.toml, keys.toml, themes (named styles, styles by tag), `:theme`, `:config` | |
| 8c | The rest of vim: visual mode, node yank and paste with registers (`"+`: the clipboard), `.` on nodes, `/` `n` `N`, the jump list, capital marks, `gd` | |
| 8d | Connect mode, portals, backlinks, file names that follow the root's title | v1 |
| 9 | v1.1: Board, Search picker, `:tidy` and align, branch out, export/import | |
Each stage ends with `cargo build`, `cargo lint` and `cargo test` passing, and
one commit.
one commit. Stage 8, as big as stages 5 to 7 together, comes in four parts
with a commit each, and a pause after each for the user to try it (their
choice).
## Crates and tooling