on master help sign in
hide the reading pane by default: the outline fills the screen, zp or the ◨ button or Tab shows the pane, the editor still opens in it
commit65307b5a22c9bd92bd86feb2ecb4d0dfcd119b32 authoriko <ilyakooo0@gmail.com> dateSat 26 Sep 2026, 06:20 (1 month ago) parent9c6b745
9 files changed+82−12
README.md+2−1 SPEC.md+5−1 crates/fold-tui/src/app.rs+25−3 crates/fold-tui/src/app/action.rs+6 crates/fold-tui/src/app/ui.rs+10−4 crates/fold-tui/tests/conflict_watch.rs+1 crates/fold-tui/tests/mouse.rs+27−1 crates/fold-tui/tests/tui.rs+3−1 crates/fold-tui/tests/wrap.rs+3−1
README.md+2−1
@@ -43,7 +43,8 @@ starts a new vault with an `# Inbox`.
4343
4444The TUI is mouse-first, and every action also has a key.
4545
46−- **Click** a row to select it; the reading pane on the right shows it. Click `▸`/`▾` to
46+- **Click** a row to select it. Click `◨` in the top bar (or press `zp`) to show the
47+ reading pane beside the outline, which shows the selected node. Click `▸`/`▾` to
4748 fold, `☐` to check a task off, a breadcrumb in the top bar to zoom out.
4849- **Double-click** a row to zoom into it. Double-click a line of text to edit it there.
4950- **Right-click** a row (or click its `⋯`) for everything you can do to a node: edit, new
SPEC.md+5−1
@@ -852,7 +852,10 @@ Four regions, each of them live under the pointer:
852852 (click to resolve), *done hidden* when `zd` is on (click to show), the file, and the save
853853 state.
854854
855−Split layout, outline on the left at a third of the width (minimum 30 columns); the border
855+The outline fills the screen. The reading pane is hidden until asked for: `zp` or the top
856+bar's `◨` (*Reading pane*) shows it, `Tab` shows it and moves focus there, and the editor
857+(§10.6) always opens in it, hiding it again when done if it was hidden before. Shown, the
858+layout splits: outline on the left at a third of the width (minimum 30 columns); the border
856859between the panes is a handle — drag it to resize. Below 80 columns the panes stack.
857860
858861**Long lines wrap** in the reading pane and the editor. Prose breaks after a space, and a
@@ -922,6 +925,7 @@ the popup — closes the topmost thing.
922925| `zd` | toggle hiding done nodes (both panes) |
923926| `zr` | toggle raw mode: exact source in the reading pane, frontmatter included |
924927| `zw` | toggle wrapping of long lines in the reading pane and the editor (§10.1) |
928+| `zp` | show / hide the reading pane (§10.1) |
925929| `za` | archive: refile subtree under `Archive` (§6.5) |
926930| `-` | go to parent |
927931| `{` / `}` | previous / next sibling |
crates/fold-tui/src/app.rs+25−3
@@ -75,6 +75,9 @@ pub struct App {
7575 raw_mode: bool,
7676 /// Long lines wrap in the reading pane and the editor (§10.1); `zw`.
7777 wrap: bool,
78+ /// The reading pane is shown beside the outline (§10.1); `zp`. Hidden,
79+ /// the outline takes the screen and the pane appears only to edit.
80+ pub show_reading: bool,
7881 register: String,
7982 status: String,
8083 status_time: Instant,
@@ -164,6 +167,7 @@ impl App {
164167 hide_done: false,
165168 raw_mode: false,
166169 wrap: true,
170+ show_reading: false,
167171 register: String::new(),
168172 status: "click to select · double-click to zoom · right-click for actions · drag to move · ? help".into(),
169173 status_time: Instant::now(),
@@ -658,6 +662,11 @@ impl App {
658662 self.open_editor_on(target);
659663 }
660664
665+ /// Whether the reading pane is on screen: shown, or holding the editor.
666+ pub fn reading_visible(&self) -> bool {
667+ self.show_reading || self.mode == Mode::Edit
668+ }
669+
661670 /// Open the built-in editor over a node's subtree (§10.6).
662671 fn open_editor_on(&mut self, target: NRef) {
663672 let buf = fold_core::edit::open_editor(&self.vault, target);
@@ -1099,6 +1108,7 @@ impl App {
10991108 }
11001109 ('z', KeyCode::Char('a')) => self.act_archive(),
11011110 ('z', KeyCode::Char('w')) => self.run_action(Action::Wrap),
1111+ ('z', KeyCode::Char('p')) => self.run_action(Action::ReadingPane),
11021112 ('g', KeyCode::Char('g')) => match self.focus {
11031113 Focus::Outline => self.cursor = 0,
11041114 Focus::Reading => self.read_cursor = 0,
@@ -1185,7 +1195,10 @@ impl App {
11851195 }
11861196 _ if ctrl => {}
11871197 KeyCode::Char('q') => self.quit = true,
1188− KeyCode::Tab => self.focus = Focus::Reading,
1198+ KeyCode::Tab => {
1199+ self.show_reading = true;
1200+ self.focus = Focus::Reading;
1201+ }
11891202 KeyCode::Char('j') | KeyCode::Down => {
11901203 if self.cursor + 1 < len {
11911204 self.cursor += 1;
@@ -1788,6 +1801,13 @@ impl App {
17881801 self.clamp_cursor();
17891802 self.say(if self.hide_done { "done hidden" } else { "done shown" });
17901803 }
1804+ Action::ReadingPane => {
1805+ self.show_reading = !self.show_reading;
1806+ if !self.show_reading && self.focus == Focus::Reading {
1807+ self.focus = Focus::Outline;
1808+ }
1809+ self.say(if self.show_reading { "reading pane shown" } else { "reading pane hidden" });
1810+ }
17911811 Action::Wrap => {
17921812 self.wrap = !self.wrap;
17931813 self.say(if self.wrap { "long lines wrap" } else { "long lines are cut at the edge" });
@@ -1862,7 +1882,9 @@ impl App {
18621882 self.cursor = 0;
18631883 self.read_cursor = 0;
18641884 self.scroll_reading = 0;
1865− self.focus = Focus::Reading;
1885+ if self.reading_visible() {
1886+ self.focus = Focus::Reading;
1887+ }
18661888 }
18671889 }
18681890
@@ -1924,7 +1946,7 @@ pub fn help_text() -> Vec<Line<'static>> {
19241946 ("n/N x t", "new sibling / child · done · task on/off"),
19251947 ("J/K > < ~", "move · indent · outdent · heading ↔ bullet"),
19261948 ("r y d p/P", "move to… · copy · delete · paste after / before"),
1927− ("s za zd zr zw", "make block · archive · hide done · raw source · wrap lines"),
1949+ ("s za zd zr zw zp", "make block · archive · hide done · raw source · wrap lines · reading pane"),
19281950 ("/ : ?", "filter · command palette · this help"),
19291951 ("u/U q", "undo / redo · quit (everything is always saved)"),
19301952 ("Tab", "switch panes · in the text: [[ ]] headings, / search, o link"),
crates/fold-tui/src/app/action.rs+6
@@ -37,6 +37,7 @@ pub enum Action {
3737 HideDone,
3838 RawMode,
3939 Wrap,
40+ ReadingPane,
4041 GoTo,
4142 ClearDone,
4243 Canonicalize,
@@ -95,6 +96,7 @@ impl Action {
9596 HideDone => "Hide / show done",
9697 RawMode => "Raw / styled",
9798 Wrap => "Wrap lines",
99+ ReadingPane => "Reading pane",
98100 GoTo => "Go to…",
99101 ClearDone => "Clear done",
100102 Canonicalize => "Canonicalize",
@@ -123,6 +125,7 @@ impl Action {
123125 Filter => "⌕",
124126 Palette => "☰",
125127 Help => "?",
128+ ReadingPane => "◨",
126129 Undo => "↶",
127130 Redo => "↷",
128131 Capture => "+",
@@ -173,6 +176,7 @@ impl Action {
173176 HideDone => "zd",
174177 RawMode => "zr",
175178 Wrap => "zw",
179+ ReadingPane => "zp",
176180 EditDone => "Esc",
177181 EditRevert => ":q!",
178182 Close => "Esc",
@@ -223,6 +227,7 @@ impl Action {
223227 HideDone => "hide or show finished tasks",
224228 RawMode => "show the exact source",
225229 Wrap => "wrap long lines, or cut them at the edge",
230+ ReadingPane => "show or hide the pane beside the outline",
226231 GoTo => "jump to a node",
227232 ClearDone => "trash finished tasks under the zoom",
228233 Canonicalize => "rewrite the vault in canonical form",
@@ -302,6 +307,7 @@ pub const PALETTE: &[Action] = &[
302307 Action::HideDone,
303308 Action::RawMode,
304309 Action::Wrap,
310+ Action::ReadingPane,
305311 Action::EditorKeys,
306312 Action::ClearDone,
307313 Action::Canonicalize,
crates/fold-tui/src/app/ui.rs+10−4
@@ -260,7 +260,13 @@ impl App {
260260 self.draw_conflict(f, body);
261261 } else {
262262 let narrow = area.width < 80;
263− let (outline, reading) = if narrow {
263+ let shown = self.reading_visible();
264+ if !shown && self.focus == Focus::Reading {
265+ self.focus = Focus::Outline;
266+ }
267+ let (outline, reading) = if !shown {
268+ (body, Rect { x: body.x + body.width, width: 0, ..body })
269+ } else if narrow {
264270 let h = body.height / 2;
265271 (Rect { height: h, ..body }, Rect { y: body.y + h, height: body.height - h, ..body })
266272 } else {
@@ -275,10 +281,10 @@ impl App {
275281 self.draw_outline(f, outline);
276282 if self.mode == Mode::Edit {
277283 self.draw_editor(f, reading);
278− } else {
284+ } else if shown {
279285 self.draw_reading(f, reading);
280286 }
281− if !narrow {
287+ if shown && !narrow {
282288 // the outline's right border is the handle
283289 let handle = Rect { x: outline.x + outline.width - 1, y: outline.y + 1, width: 1, height: outline.height.saturating_sub(2) };
284290 self.ui.push(handle, Hit::Divider);
@@ -403,7 +409,7 @@ impl App {
403409 fn draw_topbar(&mut self, f: &mut Frame, area: Rect) {
404410 let buf = f.buffer_mut();
405411 buf.set_style(area, Style::default().bg(theme::BAR));
406− let actions = [Action::Filter, Action::Capture, Action::Undo, Action::Redo, Action::Palette, Action::Help];
412+ let actions = [Action::Filter, Action::Capture, Action::ReadingPane, Action::Undo, Action::Redo, Action::Palette, Action::Help];
407413 let crumbs = self.crumbs();
408414 let crumb_w: u16 = crumbs.iter().map(|&c| self.vault.tree.node(c).title.width() as u16 + 3).sum::<u16>() + 6;
409415 let room = area.width.saturating_sub(crumb_w.min(area.width / 2));
crates/fold-tui/tests/conflict_watch.rs+1
@@ -103,6 +103,7 @@ fn reading_search_jumps_to_match() {
103103#[test]
104104fn reading_heading_jump() {
105105 let (_d, mut app) = app_with("# A\n\ntext\n\n## B\n\nmore\n\n## C\n");
106+ app.show_reading = true;
106107 app.key_normal(key(KeyCode::Enter));
107108 // a single `]` does nothing (§10.4: `]]`)
108109 app.handle_key(key(KeyCode::Char(']')));
crates/fold-tui/tests/mouse.rs+27−1
@@ -12,7 +12,9 @@ const H: u16 = 32;
1212fn app_with(text: &str) -> (tempfile::TempDir, App) {
1313 let dir = tempfile::tempdir().unwrap();
1414 std::fs::write(dir.path().join("root.md"), text).unwrap();
15− let app = App::new(dir.path()).unwrap();
15+ let mut app = App::new(dir.path()).unwrap();
16+ // these tests are about the reading pane, hidden by default
17+ app.show_reading = true;
1618 (dir, app)
1719}
1820
@@ -274,3 +276,27 @@ fn capture_button_prompts_and_saves() {
274276 assert!(root(&d).contains("- milk"), "{}", root(&d));
275277 assert_eq!(title(&app), "milk");
276278}
279+
280+#[test]
281+fn the_reading_pane_is_hidden_until_asked_for_or_editing() {
282+ let (_d, mut app) = app_with("# A\n\ntext\n");
283+ app.show_reading = false;
284+ let s = draw(&mut app);
285+ assert!(app.hit_pos(Hit::ReadingPane).is_none() && app.hit_pos(Hit::Divider).is_none(), "{}", s);
286+ assert!(!s.contains("text"), "{}", s);
287+ // editing opens the pane for the editor, and closing it hides it again
288+ typing(&mut app, "e");
289+ assert_eq!(app.mode_pub(), "edit");
290+ assert!(draw(&mut app).contains("text"));
291+ app.handle_key(KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE));
292+ assert_eq!(app.mode_pub(), "normal");
293+ assert!(!draw(&mut app).contains("text"));
294+ assert!(app.hit_pos(Hit::ReadingPane).is_none());
295+ // the top bar's button shows it, and `zp` hides it
296+ button(&mut app, Action::ReadingPane);
297+ assert!(draw(&mut app).contains("text"));
298+ assert!(app.hit_pos(Hit::ReadingPane).is_some());
299+ typing(&mut app, "zp");
300+ draw(&mut app);
301+ assert!(app.hit_pos(Hit::ReadingPane).is_none());
302+}
crates/fold-tui/tests/tui.rs+3−1
@@ -10,7 +10,9 @@ fn key(code: KeyCode) -> KeyEvent {
1010fn app_with(text: &str) -> (tempfile::TempDir, App) {
1111 let dir = tempfile::tempdir().unwrap();
1212 std::fs::write(dir.path().join("root.md"), text).unwrap();
13− let app = App::new(dir.path()).unwrap();
13+ let mut app = App::new(dir.path()).unwrap();
14+ // these tests are about the reading pane, hidden by default
15+ app.show_reading = true;
1416 (dir, app)
1517}
1618
crates/fold-tui/tests/wrap.rs+3−1
@@ -11,7 +11,9 @@ const H: u16 = 20;
1111fn app_with(text: &str) -> (tempfile::TempDir, App) {
1212 let d = tempfile::tempdir().unwrap();
1313 std::fs::write(d.path().join("root.md"), text).unwrap();
14− let app = App::new(d.path()).unwrap();
14+ let mut app = App::new(d.path()).unwrap();
15+ // these tests are about the reading pane, hidden by default
16+ app.show_reading = true;
1517 (d, app)
1618}
1719