feat(text): a plain file joins the editing mode and can be saved - #878
Merged
Conversation
andiwand
force-pushed
the
feat/txt-editing
branch
from
September 10, 2026 14:29
6c9c68e to
77e0732
Compare
`text.js` was the one editor that answered to nobody: its own
`beforeinput` gate, its own undo, and no `odr.editing` on the page at all.
So a host that wired the mode once and opened a `.txt` found
`odr.generateDiff` missing - which decision 11 of `editing.md` said could
not happen, and which was true of every view except this one.
It attaches now. The view writes `editing.js` and the page-level state,
and `text.js` is an editor on the mode rather than beside it. Its undo
needed nothing new: it is the one editor that recorded an inverse per
change before any of this work started.
A plain file has no element tree, so none of the document operations
reach it. What it has instead is one operation, `setContent {text}`,
carrying the file's whole text. Not a line at a time: a line number is a
path, and a plain file has no registry to hang an id on, so per-line
operations would walk back into what decision 1 removed.
`TextFile::write_edited` is the write path, shaped like `PdfFile::annotate`
rather than `Document::save`, because a `.txt` is a `TextFile` and neither
`Document::edit` nor the element adapters reach it. `is_savable()` refuses
an encoding we cannot decode, since the view hands those bytes to the
browser as they are. What it writes is UTF-8 whatever the source was -
`encoding/transcode.hpp` decodes and does not encode.
`HtmlConfig::editable` no longer writes `contenteditable` on the lines;
`odr.editing.enable()` does, so one render serves both modes as it does
for a document.
`docs/design/txt-editing.md` holds its decisions, beside the two documents
the other editors already have. It is not a document view, so the document
one is the wrong place for any of it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KKFKbUVCYF2VhujdmjhhPW
andiwand
force-pushed
the
feat/txt-editing
branch
from
September 10, 2026 14:37
77e0732 to
99bcb53
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🤖 Generated with Claude Code
#877 is merged, so this now sits on main. Closes the open question
editing.mdhas carried since the mode was built.What it was
text.jswas the one editor that answered to nobody — its ownbeforeinputgate, its own undo stacks, its own paste handling, and no
odr.editingonthe page at all, because
text_file.cppwritestext.jsand notediting.js. So a host that wired the mode once and opened a.txtfoundodr.generateDiffundefined, could not askisEditable(), got noonEditChangeto light its save button, and had noundo()for its toolbar.Decision 11 of
editing.mdsays that cannot happen — "odr.editingis onevery document view either way…
odr.generateDiff()never goes missing". Itwas true of every view except this one.
And nothing persisted: a
.txtis aTextFile, not aDocumentFile, soDocument::edit/savedo not reach it.What it is now
It attaches. The view writes
editing.jsand the page-leveldata-odr-editable/data-odr-keyboard, andtext.jsbecomes an editor onthe mode rather than beside it. Its undo needed nothing new — it is the one
editor that recorded an inverse per change before any of this work started, so
canUndo/canRedo/undo/redoanswer honestly on day one.One operation. A plain file has no element tree — no runs, no paragraphs,
no ids — so none of the document operations apply. What it has instead is
setContent {text}, carrying the file's whole text, and coalescing keeps thelog at exactly one operation however long the session runs.
Not a line at a time, which was the obvious alternative: a line number is a
path, and decision 1 is that an operation must not address by one — inserting
a line shifts every line after it. A document escapes that with ids from its
registry; a plain file has no registry, because lines are not elements. Finer
than a line would want offsets, which decision 2 refused over UTF-16 versus
UTF-8. And
write_editedproduces the complete bytes either way, so a finerlog would only be reassembled before writing. Where it bites is a large file
re-crossing the bridge; the answer there is one
replaceLines {from, to, text}diff hunk, not per-line ops — recorded in the design doc.
A write path, shaped like
PdfFile::annotaterather thanDocument::save,because a
.txtis aTextFile:if (text_file.is_savable()) text_file.write_edited(odr_editing_get_operations(), out);txtnow stateseditandsaveinFileTypeCapabilities.The encoding, stated rather than hidden
encoding/transcode.hpphasto_utf8and nothing in the other direction.So
write_editedwrites UTF-8 whatever the source encoding was — a Shift-JISfile that saves comes back as UTF-8. The API doc and the changelog say so, so a
host can warn;
is_savable()refuses only what cannot be decoded at all,since the view hands those bytes to the browser as they are and what comes
back could not be put back. A real
from_utf8is recorded as the open questionit is.
Breaking
HtmlConfig::editableno longer writescontenteditableon the lines —odr.editing.enable()does, so one render serves both modes as it already doesfor a document.
Checks
test/browser/plaintext/is new: 36 checks over the mode, the log, the gutterand undo. The plain-text editor has never had a check page.
Reference output
Every
.txtpage changes:data-odr-keyboardon the body, theediting.jslink, and no
contenteditable. Say the word and I'll regen and advance thepins once this and #877 are in.