site-core: a collection category's lead survives the round trip (tile-lab#3) - #3
Merged
Merged
Conversation
`parseSite` collected a `collection` category's lead paragraph into
`group.lead`, the model builder dropped it, and `serializeSite` had
nowhere to write it back — so `serializeSite(parseSite(page)) !== page`
for every collection whose categories carry an intro, and every save
through every door (MCP `save_page`, the console editor, batch
`save_pages`) silently deleted those paragraphs. Found by reef W88
against a real 制作実績 page. `people` — the other two-level coral — has
kept its group lead since the day it was written; collection walked the
same shape and only ever built half the model. Fixed by adding the one
missing model field and the one missing emit, in the exact position the
parser reads it from: between the `###` heading and the first `####`
item. Emitted after the items it would re-parse as the LAST item's body
and change the paragraph's owner on every save instead of deleting it.
🔴 The audit for the same class found a SECOND one, and it is worse
because it is visible. `splitCellHeading` parses a leading Lucide
shortcode (`### :book-open: The Comic`) into `c.icon`; astro's
GridCell.astro RENDERS it; `serializeSite` never emitted it. So a grid
icon rendered correctly until the first save and then vanished off the
built page. Same one-term fix, icon before emoji — the order
splitCellHeading reads them.
🩸 And the reason neither was caught: `serialize∘parse` was already a
STABLE FIXPOINT on the broken code and on all eight fixtures this
package ships. Of course it was — dropping the same text twice drops
nothing the second time. An idempotence check agrees with itself while
both halves are wrong. site-core-roundtrip.test.mjs therefore carries
two rulers, neither of which asks the parser whether it agrees with
itself: one canonical page per coral asserted byte-identical, and a
content-preservation sweep over every .md in the package that claims to
be a sitetile page, comparing the author's bytes to the output's bytes
as a multiset (set membership cannot see a lost duplicate). Both rulers
are proven to fire, by control tests that hand them a real deletion —
and the fixture sweep prints its sample count and asserts a floor,
because a ruler that measures nothing reports zero and passes forever.
Worth stating plainly: the shipped fixtures are all green and always
were. Not one of them uses collection, people, faq or timeline, which
is exactly why the bug lived here. The corals table is the part of this
file that carries those structures.
Found but NOT fixed, both measured, both needing a decision rather than
a symmetry fix:
· a `form` field's non-`- option` body prose is dropped by the model
builder, which by design carries only structure (labels/kinds/
options) — giving helper text a home needs a render decision.
· a collection category's lead is not rendered by EITHER renderer
(site-core's own, or astro's Collection.astro), though People.astro
renders the people equivalent. This commit stops the paragraph being
deleted; making it appear changes how existing live pages look, and
that is the site owner's call, not this fix's.
The whole repo suite is green (scripts/test.sh, exit 0). The astro smoke
and browser smokes skipped for missing deps and say so by name; neither
covers this change, which is confined to serializeSite — the astro build
parses and never serializes, so its output is unchanged by construction.
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.
What
parseSitecollected acollectioncategory's lead paragraph intogroup.lead, the modelbuilder dropped it, and
serializeSitehad nowhere to write it back — soserializeSite(parseSite(page)) !== pagefor every collection whose categories carry an intro,and every save path silently deleted those paragraphs.
people(the other two-level coral) haskept its group lead since it was written;
collectionwalked the same shape and only ever builthalf the model. Fixed by adding the one missing model field and the one missing emit, in the exact
position the parser reads it from: between the
###heading and the first####item.A second instance of the same class, worse because it is visible:
splitCellHeadingparses aleading Lucide shortcode (
### :book-open: The Comic) intoc.icon, the renderer draws it, butserializeSitenever emitted it — so a grid icon rendered until the first save and then vanished.Same one-term fix.
site-core-roundtrip.test.mjsis new: one canonical page per coral asserted byte-identical, plusa content-preservation sweep over every shipped
.mdfixture comparing authored bytes to outputbytes as a multiset (so a lost duplicate can't hide behind set membership). Both rulers are proven
to fire against a real, injected deletion.
Test output
Repo gate (
bash scripts/test.sh), exit 0, all green (astro/browser smokes skip for missingoptional deps, as they do on a stock checkout):
Not fixed here (measured, needs a decision, not a symmetry fix)
formfield's non-- optionbody prose is dropped by the model builder by design; givinghelper text a home needs a render decision.
peopleequivalent is. This change stops the paragraph being deleted; making it appear would change how
existing live pages look, which is a site-owner decision, not this fix's.