A writer that gives back the file it was given - #45
Merged
Conversation
`parseIdf(text, schema, { preserveFormatting: true })` retains the syntax layer
feature 005 built and an index from each statement to the object it produced.
`writeIdf` then walks the two together: every object nothing has changed is
reproduced from the characters it was read from, and everything between the
objects is copied.
The gaps are unconditional and only the statements are decided. Every character
is either inside a statement or in a gap, because the layer tiles the text, so
the comments, the blank lines, the line endings whatever they are and the
presence or absence of a trailing newline are all copied without a decision.
Three requirements fall out rather than being enforced: nothing is reordered, no
header is added, and the version statement is not moved.
The read makes ONE pass. `scanIdf` and `lex` now expose their scan handlers, and
the preserving read composes them, so the option costs the layer's own budget
rather than a second read of the text. With the option off nothing is built and
reading costs what it cost before.
THE TOUCHED RECORD closes three paths that notified nobody:
- A rename rewrites every referencing field directly, bypassing the accessor on
purpose, so none of those objects said they had changed. A writer trusting the
listener emitted them from their original text, producing a file that loads
and names a construction layer that no longer exists. The mark is made on both
branches of the retarget loop; the second one is reached only by a reference
held inside an extensible repeat.
- `get extensible()` handed back the object's own array, so pushing a vertex
reached the object without passing any accessor. It now returns an Array
subclass whose mutators are heard, holding repeats whose fields are accessors.
No `Proxy`: one would charge every vertex READ to catch a write. One spelling
is still not heard, replacing a whole repeat by index, and it is documented and
tested as the limit it is.
- Creation, cloning and attachment leave an object with no index, so it is
touched from the moment it exists.
The object notation preserves on all-or-nothing terms, in both languages: the
retained text comes back only while nothing has been touched, added or removed,
and removal is decided from the count at read rather than from a scan of the
survivors, which cannot see one.
Asking to preserve and to reformat is refused rather than resolved. Asking to
preserve and for a different output form produces the form, because a form is a
different artifact the source was never going to express.
KNOWN, MEASURED, NOT FIXED: this puts the shared install 11.2 KB over its
1.75 MiB budget. The plan assumed 98.3 KB of headroom; main actually has 32.7 KB
and the writer is 47.7 KB of dist, over a third of it source maps and doc
comments. Reported rather than absorbed.
… a rename Six examples under docs-snippets/how-to/preserve-formatting, compiled against the published surface as every other example is: reading with preservation, writing back unchanged, one edit showing as one object, asking a document whether a write will preserve, the refusal and what is granted beside it, and the object notation's all-or-nothing terms.
Measured, on the reference model's 33,012 vertices: reading a coordinate through an armed repeat cost 33 times what reading it off a plain object cost. That is precisely the price rejecting a `Proxy` was meant to avoid, arrived at by another road. A document read WITHOUT preservation has no touched record to maintain and nothing a preserving write would consult, so it keeps the plain array and reads a coordinate at 1.75x a pre-materialised one, which is the getter call and nothing else. A document read WITH preservation is being edited, and an edit that survives the save matters more there than the throughput of reading a vertex. bench/preserve.mjs is the measurement, reported rather than gated: two of its three numbers are success criteria and the third is the item the plan named as the one to watch, and none of them has a defended threshold yet. It reports one number the plan did not expect. Reading with preservation costs 1.54x a plain read, not the 1.25x SC-005 states. The two are different claims: the layer's own budget, that scanIdf costs no more than a quarter over a plain read, holds at 0.53x and is gated in bench/budget.mjs. A preserving read does both jobs, so its floor is the sum, and the composed single pass saves the character walk rather than the per-event work. Reported, not hidden.
SC-012 was amended from 1.75 MB to 1.875 MB. The gate's own header carries the reasoning, as it carries the reasoning for the first amendment: the writer is 47.7 KB and is not optional weight, 1.8 MiB would have cleared the measurement by less than the feature cost, and 1.875 MiB is the next figure in the same binary series and leaves 114 KB. It also records the lever nobody pulled. 396 KB of the install is source maps, which serve debugging and nothing at runtime; dropping them from the published files would free more than twice what this raise freed, and it is a packaging decision rather than one feature's to make. Written down so the next time this figure is under pressure, raising it again is a choice against a known alternative rather than the only idea in the room. packages/idfkit/language.js said "98.3 KB free". That figure was taken when the language service landed and was three features stale; feature 006 was planned against it and found 32.7 KB. It now says what the gate says.
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.
Closes #12.
parseIdf(text, schema, { preserveFormatting: true })retains feature 005's syntax layer and an index from each statement to the object it produced.writeIdfthen walks the two together: every object nothing has changed is reproduced from the characters it was read from, and everything between the objects is copied.Blocked on idfkit/idfkit-conformance#4. This branch pins
conformance-2026.10andgovernance-2026.14, and those tags are cut from that repository'smainafter it merges. CI here is red until they exist — that is "publish before you pin" working, not a fault in this branch.Why the diff is small for what it does
The gaps between statements are emitted unconditionally and only the statements are decided. Every character is either inside a statement or in a gap, because the layer tiles the text, so the comments, the blank lines that group four hundred surfaces into rooms, the line endings whatever they are, and the presence or absence of a trailing newline are all copied without a decision. Three requirements fall out rather than being enforced: nothing is reordered, no header is added, and the version statement is not moved.
The read makes one pass.
scanIdfandlexnow expose their scan handlers and the preserving read composes them, so the option costs the layer's own work rather than a second read of the text. With the option off, nothing is built.The touched record closes three paths that notified nobody
get extensible()handed back the object's own array. It now returns anArraysubclass whose mutators are heard, holding repeats whose fields are own accessors. NoProxy.One spelling is still not heard — replacing a whole repeat by index — and it is documented and tested as the limit it is, with
spliceasserted beside it as the tracked alternative.Measured, and one number moved because of it
bench/preserve.mjsreports three figures. One of them changed the design: reading a coordinate through an armed repeat cost 33x a plain read over the reference model's 33,012 vertices, which is exactly the price rejecting aProxywas meant to avoid, reached by another road. Repeats are now armed only when the document carries a retained source — an editing document pays it, a geometry pipeline does not, and the non-preserving path is back to 1.75x.It also reports a number the plan did not expect: a preserving read costs 1.54x a plain read, not the 1.25x SC-005 states. The two are different claims. The layer's own budget, that
scanIdfcosts no more than a quarter over a plain read, holds at 0.53x and is gated inbench/budget.mjs. A preserving read does both jobs, so its floor is the sum.SC-012 was amended
The writer is 47.7 KB of
distand put the install 14,308 bytes over 1.75 MB. The budget moved to 1.875 MiB, in its own commit, with the reasoning in the gate's header: the capability is core, 1.8 MiB would have cleared the measurement by less than the feature cost, and this leaves 114 KB. The lever nobody pulled is recorded there too — 396 KB of the install is source maps, which serve debugging and nothing at runtime.packages/idfkit/language.jsclaimed "98.3 KB free". That was three features stale; the real figure when this work started was 32.7 KB, and the plan was written against the wrong one.