Skip to content

Extract renderEditor from AppLayout (step 3c)#191

Merged
shellicar merged 2 commits intomainfrom
feature/render-editor
Apr 6, 2026
Merged

Extract renderEditor from AppLayout (step 3c)#191
shellicar merged 2 commits intomainfrom
feature/render-editor

Conversation

@shellicar
Copy link
Copy Markdown
Owner

What

The editor rendering loop moves out of AppLayout.render() into renderEditor(state: EditorState, cols: number): string[].

Changes

  • New src/renderEditor.ts — pure function, no I/O, no side effects
  • AppLayout replaces the 14-line loop with allContent.push(...renderEditor(this.#editorState, cols))
  • EDITOR_PROMPT constant removed from AppLayout (only used in the loop)

Why renderEditor does not include the divider

The divider (─── prompt ───) is structural chrome that AppLayout owns for every block type. The renderer is responsible for the text content only — prefixes, cursor highlighting, wrapping. Keeping the divider in AppLayout is consistent with how every other block is rendered.

Testing

11 unit tests in renderEditor.spec.ts cover: output shape, prompt/indent prefixes, cursor INVERSE_ON/OFF highlighting, EOL cursor space, wrapping, and the no-divider contract.

The editor rendering loop moves out of AppLayout.render() into a pure
function renderEditor(state, cols) in its own file. AppLayout replaces
the loop with a single push(...renderEditor(...)) call.

renderEditor does not include the section divider or the blank line
before the text — those are structural chrome that AppLayout owns for
every block type, editor included. The function is purely responsible
for the text content: prefixes, cursor highlighting, and wrapping.

The cursor character is wrapped in INVERSE_ON/INVERSE_OFF for a block
cursor without text displacement. At EOL a space is used as the target.

EDITOR_PROMPT was only used inside the rendering loop and is removed
from AppLayout. CONTENT_INDENT stays because renderBlockContent uses it.

11 tests cover output shape, prefix behaviour, cursor highlighting,
wrapping, and the no-divider contract.
@shellicar shellicar added this to the 1.0 milestone Apr 6, 2026
@shellicar shellicar added the enhancement New feature or request label Apr 6, 2026
@shellicar shellicar self-assigned this Apr 6, 2026
@shellicar shellicar added the enhancement New feature or request label Apr 6, 2026
@shellicar shellicar requested a review from bananabot9000 April 6, 2026 10:57
@shellicar shellicar enabled auto-merge (squash) April 6, 2026 10:57
Copy link
Copy Markdown
Collaborator

@bananabot9000 bananabot9000 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean extraction, textbook renderer 🍌

renderEditor is a pure function(state: EditorState, cols: number) => string[]. No this, no I/O, no mutation. Reads only through readonly getters. Exactly what the Renderer layer should be.

Boundary is correct: divider stays in AppLayout (structural chrome, not content rendering). "No-divider contract" test enforces this. EDITOR_PROMPT removed from AppLayout (only user was the extracted loop). CONTENT_INDENT stays (renderBlockContent still uses it).

11 tests covering output shape, prefix (emoji vs indent), cursor highlighting, wrapping, and the no-divider contract. makeState helper builds state through handleKey API rather than reaching into internals — nice.

Constants duplicated intentionally — PROMPT_PREFIX and INDENT in renderEditor.ts are the same values as AppLayout's originals but owned by the renderer now. If they need to diverge later, they can. YAGNI on shared constants.

AppLayout continues its journey toward pure compositor. State → Renderer → ScreenCoordinator pattern holding steady across all extractions.

Ship it 🚢🍌

@shellicar shellicar merged commit 0c6006e into main Apr 6, 2026
4 checks passed
@shellicar shellicar deleted the feature/render-editor branch April 6, 2026 11:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants