edamame 0.1.2

A fast TUI Markdown editor and viewer
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
# In-app documentation

> Part of the edamame contributor deep-dives. Index and project-wide conventions: [`AGENTS.md`](../../AGENTS.md). Sibling docs live in [`docs/dev/`](.).


`docs/*.md` is compiled into the binary (`src/docs/registry.rs`) and opened as a pathless, read-only document.

- **`open_doc` and `file_path` are mutually exclusive, and pathless is what makes a page safe.** Both load paths maintain it — `load_doc_into_editor` sets one and clears the other, `load_file_into_editor` the reverse. `handle_file_changed` / `handle_file_removed` return early unless `file_path` names the changed path, so an external write landing while the manual is open is dropped rather than diffed against it; the watcher deliberately stays armed on the file the reader came from. Forgetting the clear also leaves `follow_link` resolving a real document's relative links against the embedded manual.
- **A relative link resolves against the embedded set only while a page is open.** The gate is `open_doc.is_some()` at the `follow_link` call site, never inside `LinkTarget::parse`, which is pure and must keep answering the same way for a document linking to a file of its own named `security.md`. A path with any directory component (`dev/theming.md`, `../SECURITY.md`) is not embedded and becomes a GitHub URL.
- **Read-only rests in `Mode::Preview` and is enforced by two backstops, not per-site guards.** Preview is already the browse-only view (no cursor, no raw reveal, `mouse_ops::apply_preview_action` declining the checkbox toggle and table handles), so `edit_ops::enter_edit_if_preview` refusing the transition out of it makes all twenty-six of its call sites — `Action::Paste` among them — no-ops at once, and `EditorState::apply_delta` refuses the write. `ToggleRawMode` / `EnterEditMode` no-op in `edit_ops` too, since they *request* the transition rather than incidentally causing it. The App-level `actions::readonly_safe_action` is a courtesy on top, and a rule over `ActionCaps` rather than a list: it denies `mutates_buffer || needs_path` (five actions) so a palette pick of `Save` doesn't open a Save-as prompt for a document nobody can own, allows `navigates_away` emphatically, and refuses **silently**. Keyed on `editor.readonly`, not `open_doc`.
- **A context-dependent action is normalized *before* any gate sees it.** `Alt+Left` / `Alt+Right` bind to `TableMoveColumnLeft` / `TableMoveColumnRight` — "reorder a column" inside a table, "navigate back / forward" outside one. Behind the gates the redirect left readers a dead chord, so `App::normalize_context_action` runs ahead of all three. `cursor_in_table` (both copies — `app::actions` and `ui::bottom_region`) answers `false` while `readonly`, so a `GoToSection` jump into one of `keybindings.md`'s tables can't kill `Alt+Left` and the hint row keeps the Back chord unconditionally; that row otherwise differs by one field, dropping only the `Press any key to edit` prelude.
- **`ALL_DOCS` derives the index page and link resolution; it does not derive the palette** — `ui::command_palette::actions::ALL_ACTIONS` is hand-written, so a new page needs a line there too (`the_palette_lists_every_embedded_page_exactly_once`). Fragments match exactly, like every other deep link, so a renamed heading breaks sibling pages' links silently and only in the app (`tests/docs.rs::every_cross_page_fragment_names_a_real_heading`).