Plain (non-formatted) paragraphs and simple list items in rendered mode use RenderedEditSession for buffer ownership, one-click cross-block switching, and commit-on-switch — the same model as headings.
PRD: Rendered Edit Session
Foundation: Core types, Widget identity
| Action | Result |
|---|---|
| Edit paragraph P, click list item L | P commits to source, L activates with one click |
| Edit heading H, click paragraph P | H commits, P activates (shared session) |
| Focus leaves active block (no other session block) | close_active_ui(SaveIfDirty) commits buffer |
| Click outside active widget (empty space, label, formatted display, culled gap) | End-of-frame dismiss when active block did not receive response.clicked() |
| Click formatted display while session block active | Formatted display click goes through switch_to_ui, which commits the previous active block automatically (no separate bridge) |
Raw-mode edit bumps source_epoch |
Session buffers cleared on next rendered frame |
Formatted paragraphs/list items and table cells share this model — see rendered-edit-session-formatted.md and rendered-edit-session-tables.md.
| Block | BlockRef |
Widget id key |
|---|---|---|
| Plain paragraph | Paragraph { line } |
para_text + line |
| Simple list item | ListItem { line, item } |
list_item_text + line (item is HashMap key only) |
Cold buffer init:
- Paragraph:
extract_paragraph_content(source, start_line, end_line)— preserves trailing spaces vs AST round-trip. - List item:
extract_list_item_content(source, start_line)— content without marker; newlines stripped on edit/commit.
| Concern | Location |
|---|---|
| Shared TextEdit + session wiring | render_session_plain_text_block in src/markdown/editor.rs |
| Paragraph render | render_paragraph, render_paragraph_with_structural_keys (simple branch only) |
| List item render | render_list_item, render_list_item_with_structural_keys (simple branch only) |
| Session load/save | load_for_epoch / save_for_epoch in rendered_session.rs |
| Commit | commit_session_block, update_source_range — list markers preserved via extract_line_prefix |
| Click-away dismiss | session_dismiss_if_clicked_outside — end of frame if active block did not receive response.clicked() |
| Formatted display click | Handled natively by switch_to_ui (see rendered-edit-session-formatted.md) |
Session state is stored in egui temp memory keyed by editor id. A companion key rendered_edit_session_epoch records the last seen Tab::source_epoch.
When load_for_epoch sees a mismatched epoch (raw edit, reload, external mutation), it calls invalidate_buffers() so all block buffers reload from source on next interaction.
Rendered commits do not bump source_epoch — only external/raw edits do.
- Temp egui buffers:
para_edit_buffer,list_item_edit_buffer, tracking ids rendered_focus::after_text_editfor these pathsrendered_focus::focus_loss_should_commitfor these paths- Per-frame immediate source writes on
TextEdit::changed()(non-structural paragraph path)
- Document:
# Title, plain paragraph, bullet list with one simple item. - Edit each block; single-click switch Title → paragraph → list item → Title; confirm edits persist at leave time.
- Switch to raw mode, change a line, return to rendered; confirm session buffers match updated source.
cargo test rendered_session::— includesload_for_epoch_invalidates_when_epoch_changes, widget id parity for paragraph/list- Existing list commit tests:
test_update_source_range_preserves_bullet_listineditor.rs