Skip to content

feat(fragments): move a section, page or component between sites with its styles - #640

Draft
DavidBabinec wants to merge 13 commits into
mainfrom
feat/site-fragments
Draft

DavidBabinec wants to merge 13 commits into
mainfrom
feat/site-fragments

Conversation

@DavidBabinec

@DavidBabinec DavidBabinec commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

What

A site fragment is a portable piece of a site: a section, a page or a component, with everything it needs to render the same elsewhere (classes and selectors, conditions, components, tokens, scale steps, tables, media). src/core/fragments extracts one from a live site or a transfer archive and plans its insert into another site, and Spotlight Insert fragment from file… shows that plan, inserts it as one undo step, and reports what was added or kept. Helpers shared with site import and paste live once in core.

Why

Copy and paste and saved layouts only work inside one site: they match classes by id and drop the selectors, conditions, tokens and media a section depends on. Moving a section between sites meant rebuilding its styles by hand. The insert never overwrites anything the site already has, and only fetches the media the plan adds.

Verification

Unit tests use synthetic sites. In a browser, two sections from two other sites went into a third: styles, images and a looped table survived, both landed as siblings, and the published page matched the editor.

BUILD_EXIT=0  LINT_EXIT=0  TEST_EXIT=0
 7223 pass
 0 fail

A fragment is one versioned file that carries a section, a page or a Visual
Component with everything it needs to render in another site: classes and the
ambient rules built on them, conditions, the component closure, the tokens its
styles read, the tables its loops and forms use, its media and its page links.
extractFragment builds one from a live site or a site-transfer bundle, and the
barrel stays headless so build scripts can import it under Bun.
planFragmentInsert decides everything an insert adds without touching the
site: fresh node ids, classes and components reused by name, ambient rules and
conditions only when missing, colours added with their full definitions, scale
steps slotted into the site's own groups without moving the base, tables
matched by slug, links remapped by slug or turned into "#", and the same outlet
and component cycle guards saved layouts use. The report lists every addition
so nothing changes silently.
commitFragmentPlan runs the server work first (media reused by name and size
or uploaded, fonts installed or registered, tables and rows created, missing
fields appended), resolves the plan against the ids that produced, then hands
it to the editor's new fragments slice, which applies it in one store
transaction and regenerates framework utilities when the insert added a colour
or a scale step. A failed upload or table write stops before the document is
touched.
"Insert fragment from file..." opens a dialog that reads a fragment package,
plans the insert against the open document, and shows what it will add and
what it keeps from the site before anything changes. Insert runs the commit
and the dialog then lists what was added, including links that became "#",
rules the fragment depended on through its old parents, and other notes.
Creating a data table is a sensitive action, so the first real insert of a
fragment with a loop or a form failed with step_up_required. Every server write
of the insert now goes through runStepUp, which asks for the password and
retries, and closing that prompt reads as a cancelled insert. The report also
stops listing an empty media row.
New sites now start with font-heading, font-body and font-mono, so a
fragment's token of the same name is kept and the family the fragment bound to
it is no longer needed. The planner now installs a family only when an added
token binds to it or a carried rule names it, so inserting into a new site
never downloads a font or uploads a font file for a token the site keeps.
Adds the feature doc (format, closure, merge rules, commit, script usage) and
links it from the docs that describe paste, saved layouts, Spotlight, HTML
import, site import and site transfer. Also records the new headless gate, the
shared style rule registry helpers, the fragments store slice, and refreshes
the gate counts.
After an insert the new section stays selected, and the paste rule put the next fragment inside that container, so two sections inserted in a row ended up nested. Fragments now land as the next sibling of the selection, or at the end of the document.
The fragment engine carried its own copies of helpers Instatic already
has: the root-scope selector test from the site importer, the repeater-aware
media id walk from the data.rows loop source, the framework class id prefix,
the condition registration the paste and site import paths both inline, the
page ref builder, the subtree id map and the error message helper. Each copy
was a second place to keep in step. The shared ones now live in core
(isRootScopeSelector in page-tree, collectCellMediaIds in data/cells,
addMissingConditions in page-tree, FRAMEWORK_ID_PREFIX exported from the
framework) and every caller, old and new, goes through them.

It also drops what nothing used: the extractor's mediaSrc option (no caller
passes it), the plan's create flag on custom properties (apply looks the rule
up itself), a second copy of the font-family scan, and the move of
indexStyleRulesByName into core, which only the editor store needs.
…bilities for fragments

The insert fragment slice compared documents with its own copy of
isSameActiveDocument, and the Spotlight command restated the site-write
capability list editor.ts already defines. Both now import the existing
ones. The dialog prints the fragment kind directly instead of mapping each
kind to itself, the command's keywords drop "library", which suggested a
feature that does not exist, and the headless gate no longer describes who
runs the build scripts.
Five test files each rebuilt the same source, extracted the same hero
section, and the plan and commit tests restated the same plan and adapter
setup in every case. A fixture helper extracts once per file and the plan
and commit tests build their target through one function. Planning and
extraction are pure, so sharing the result changes no assertion: the same
60 tests run the same 253 expectations.
…importer

The reference listed indexStyleRulesByName as shared page-tree API, but it
stays with the editor store's import linking; the helpers fragments really
share with site import and HTML paste are the order allocator, the
root-scope selector test and condition registration.
Unresolved links are reported in report.links.unresolved, never as a
link-unresolved warning, so the code only widened the union UI code has to
handle. The token planner also stops returning its family reuse map, which
only it reads.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant