Extract renderEditor from AppLayout (step 3c)#191
Conversation
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.
bananabot9000
left a comment
There was a problem hiding this comment.
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 🚢🍌
What
The editor rendering loop moves out of
AppLayout.render()intorenderEditor(state: EditorState, cols: number): string[].Changes
src/renderEditor.ts— pure function, no I/O, no side effectsAppLayoutreplaces the 14-line loop withallContent.push(...renderEditor(this.#editorState, cols))EDITOR_PROMPTconstant removed fromAppLayout(only used in the loop)Why renderEditor does not include the divider
The divider (
─── prompt ───) is structural chrome thatAppLayoutowns for every block type. The renderer is responsible for the text content only — prefixes, cursor highlighting, wrapping. Keeping the divider inAppLayoutis consistent with how every other block is rendered.Testing
11 unit tests in
renderEditor.spec.tscover: output shape, prompt/indent prefixes, cursorINVERSE_ON/OFFhighlighting, EOL cursor space, wrapping, and the no-divider contract.