Skip to content

fix(ui): supply the root-height chain AppFrame's height:100% needs#23

Merged
mgoldsborough merged 3 commits into
mainfrom
fix/appframe-root-height
Jun 24, 2026
Merged

fix(ui): supply the root-height chain AppFrame's height:100% needs#23
mgoldsborough merged 3 commits into
mainfrom
fix/appframe-root-height

Conversation

@mgoldsborough

Copy link
Copy Markdown
Contributor

Closes #22.

The bug

AppFrame fills its host pane with height: 100%, but that only resolves against a definite-height ancestor chain (#rootbodyhtml) — and the SDK shipped no such chain anywhere. A Synapse app iframe is its own bare document, so unless an app hand-added html, body, #root { height: 100% } to its index.html, AppFrame's height: 100% resolved against a content-height ancestor and the whole shell collapsed to content height: full width, short height (e.g. an empty board rendering as a ~442px band inside an 868px pane). A precondition the shell depends on but neither supplies nor enforces is a framework defect — it bit more than one app.

It went unnoticed because the gallery wraps every AppFrame in a Frame with a definite height: 380, so the SDK never exercised AppFrame-as-pane-root.

The fix

Supply the missing precondition rather than change the height model:

  • New @nimblebrain/synapse/ui/base — a side-effect import that injects html, body, #root { height: 100% } + body { margin: 0 } via the existing ensureStyle helper. Import it in an app entry to establish the chain before first paint.
  • AppFrame calls the same reset on render (injectBaseReset(), render-body, matching the components' ensureStyle pattern), so every app built on it fills the pane at runtime with no per-app index.html requirement — existing apps included. The reset module is imported as a function, not a side-effect, so the ui barrel still injects nothing on import; only rendering AppFrame (or the explicit ui/base import) applies it.

AppFrame's height: 100%, minHeight: 0, and internal scroll model are unchanged.

Why a percentage chain, not a viewport unit

The issue originally proposed minHeight: 100dvh on the component. An adversarial review surfaced two shipping counter-signals against viewport units here:

  • synapse-collateral already injects html, body, #root { height: 100% } (the exact percentage chain).
  • synapse-astro-editor carries a production comment rejecting vh/dvh because the platform can allocate a pane shorter than the viewport, where a viewport unit overflows with a second scrollbar.

Percentages resolve against the actual allocated pane on every embedding model (iframe or host panel); a viewport unit assumes pane == viewport, which isn't guaranteed. So both the reporter's minHeight: 100dvh floor and a height: 100dvh were rejected in favor of supplying the percentage chain. A min-height floor also wouldn't deliver the shell's pinned-header / internally-scrolling-body contract under overflow — a definite-height chain does.

Tests

npm run ci (lint → typecheck → build → test) green; 389 tests pass. Version bumped to 0.11.0 (additive ui/base export) with a CHANGELOG entry, so it's release-ready on merge.

AppFrame fills its host pane with height:100%, which only resolves against
a definite-height ancestor chain (#root -> body -> html). The SDK shipped no
such chain, so a bare app iframe resolved height:100% against a content-height
ancestor and the whole shell collapsed to content height: full width, short
height (issue #22). The precondition the shell depended on was neither supplied
nor enforced, so multiple apps hit it.

AppFrame now establishes the chain itself on render via injectBaseReset(), so
every app built on it fills the pane with no per-app index.html requirement.
The same reset ships as a `@nimblebrain/synapse/ui/base` side-effect import for
apps that want the chain before first paint (no layout jump) or that render a
full-pane root without AppFrame.

The reset is a percentage chain (html, body, #root { height: 100% } plus
body { margin: 0 }), not a viewport unit: percentages resolve against the
actual allocated pane, staying correct on hosts that give an app a pane shorter
than the viewport, where a vh/dvh unit would overflow with a second scrollbar.
AppFrame's height:100% and internal scroll model are unchanged.
The 0.11.0 entry was written by replacing the 0.10.2 heading, which dropped
0.10.2 from the changelog and orphaned its (already-shipped) dark-mode-token
notes under 0.11.0 — leaving a malformed section with two ### Fixed blocks.
Re-add the 0.10.2 section (verbatim to the v0.10.2 tag, which is published as
npm latest) above a now self-contained 0.11.0 section.
@mgoldsborough mgoldsborough added the qa-reviewed QA review completed with no critical issues label Jun 24, 2026
`/vh/` already matches inside `dvh`, so the alternation was redundant; the
comment now records that matching "vh" catches "dvh" too.
@mgoldsborough mgoldsborough merged commit 9b79132 into main Jun 24, 2026
5 checks passed
@mgoldsborough mgoldsborough deleted the fix/appframe-root-height branch June 24, 2026 08:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

qa-reviewed QA review completed with no critical issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AppFrame uses height:100% with no root-height guarantee — apps silently collapse to content height in the host iframe

1 participant