Skip to content

feat(page-tree): show a node only when its row says so - #357

Open
mostafasadeghidev wants to merge 2 commits into
CoreBunch:mainfrom
mostafasadeghidev:feat/conditional-visibility
Open

feat(page-tree): show a node only when its row says so#357
mostafasadeghidev wants to merge 2 commits into
CoreBunch:mainfrom
mostafasadeghidev:feat/conditional-visibility

Conversation

@mostafasadeghidev

Copy link
Copy Markdown

The gap

A list whose items are not uniform cannot be built today.

Two nodes sit in one card — a video player and a "Coming soon" caption — and exactly one belongs on any given row, decided by whether that row's video field is filled. The card is authored once, so every row gets a blank player or every row gets the caption. Visual CMSs generally call this conditional visibility and content sites lean on it heavily; Instatic has only hidden, which is the same on every render.

The change

visibleWhen on a node names a source, a field and a test:

{ source: 'currentEntry', field: 'video', test: 'isSet' }
  • source — the set the prop bindings already use, so currentEntry inside a base.loop is that iteration's row. Dotted paths work (author.name).
  • testisSet / isNotSet only. Comparisons against a value need an operand and a type model; every case met so far is "does this row have one".

Set from the Properties panel's Attributes view, or with the setNodeVisibleWhen store action.

Three decisions worth reviewing

The publisher hides; the editor canvas does not. Everywhere else the two surfaces are kept identical, and this is the one place they differ on purpose: the canvas is where the node gets edited, and one hidden because the preview row happens to have no video is one the author cannot click. The panel states the rule in words instead.

A malformed condition parses to undefined and the node stays visible — the only safe direction, since the alternative is silently erasing content that was rendering fine.

Dynamic detection gains rule 2c. A condition reading a request-dependent source makes the node a Layer C hole. This is a stronger dependency than rule 2: a request-dependent binding changes what a node says, a request-dependent condition changes whether it is there at all, and baking it would freeze one visitor's answer into the artefact for everyone.

Impact

Additive and optional. A node without visibleWhen behaves exactly as before; no persisted data changes shape; no migration.

setNodeVisibleWhen lives in a new page-tree/nodeVisibility.ts rather than mutations.ts, which the module-size gate records as grandfathered debt that may only shrink.

Verification

bun run build          # tsc -b && vite build — clean
bun test src/core/templates/__tests__/visibilityCondition.test.ts   # 11 pass
bun test src/__tests__/architecture                                 # same failures as main, none new
bun run lint           # clean

Architecture gates were compared against a clean main checkout commit-by-commit: identical failure set, nothing added.

A list whose items are not uniform cannot be built today. Two nodes sit in one
card — a video player and a "Coming soon" caption — and exactly one belongs on
any given row, decided by whether that row's video field is filled. The card is
authored once, so every row gets a blank player or every row gets the caption.
Visual CMSs generally call this conditional visibility, and content sites lean
on it heavily; Instatic had no equivalent, only the static `hidden` switch.

`visibleWhen` on a node names a source, a field and a test. The source is the
set the prop bindings already use, so `currentEntry` inside a loop is that
iteration's row. Two tests only — `isSet` and `isNotSet` — because comparisons
against a value need an operand and a type model, and every case met so far is
"does this row have one".

Evaluated by the publisher beside `hidden`, where the effect is identical and
only the reason differs. Deliberately NOT evaluated by the editor canvas: that
is where the node gets edited, and one hidden because the preview row happens
to have no video is one the author cannot click. It is the single place the two
surfaces differ on purpose, and both the evaluator and this commit say so.

A malformed condition parses to `undefined` and the node stays visible — the
only safe direction, since the alternative is silently erasing content that was
rendering fine.

Dynamic detection gains rule 2c: a condition reading a request-dependent source
makes the node a Layer C hole. Whether it renders at all now depends on that
source, which is a stronger dependency than any prop binding — baking it would
freeze one request's answer into the artefact for every visitor.

The mutation lives in its own module rather than `mutations.ts`, which the size
gate records as grandfathered debt that may only shrink.
…anel

Without a control the condition could only be written by a plugin, which left
the engine feature unreachable to the person it is for. Three fields in the
Attributes view — source, field name, test — plus a sentence stating the rule
in words.

That sentence is doing real work. The node stays on the canvas whatever the
condition says, because the canvas is where it gets edited and one hidden by
the preview row is one the author cannot click. So the panel is the only place
the editor can honestly report what will happen at publish time, and it says
so outright rather than leaving the author to wonder why nothing moved.

An empty field name clears the condition instead of storing it: a half-typed
rule would otherwise hide the node against a field named "", which is never
what was meant.

Docs updated in the same change — `hidden` vs `visibleWhen` and what counts as
set in the page-tree reference, rule 2c in the publisher's detection table
with why a visibility dependency is stronger than a prop-binding one.
@mostafasadeghidev
mostafasadeghidev marked this pull request as ready for review August 9, 2026 03:00
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