From 43f41923fec0d4ffc8c902637f12e34ac4fe0efd Mon Sep 17 00:00:00 2001 From: Mathew Goldsborough <1759329+mgoldsborough@users.noreply.github.com> Date: Tue, 23 Jun 2026 21:53:23 -1000 Subject: [PATCH 1/3] fix(ui): supply the root-height chain AppFrame's height:100% needs 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. --- CHANGELOG.md | 12 ++++++++++- README.md | 1 + package-lock.json | 4 ++-- package.json | 7 ++++++- src/__tests__/ui/base.test.ts | 33 +++++++++++++++++++++++++++++++ src/__tests__/ui/layouts.test.tsx | 14 +++++++++++++ src/ui/base.ts | 24 ++++++++++++++++++++++ src/ui/internal/base-reset.ts | 33 +++++++++++++++++++++++++++++++ src/ui/layouts/AppFrame.tsx | 11 +++++++++++ tsup.config.ts | 1 + 10 files changed, 136 insertions(+), 4 deletions(-) create mode 100644 src/__tests__/ui/base.test.ts create mode 100644 src/ui/base.ts create mode 100644 src/ui/internal/base-reset.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index e374cf7..ed761a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,17 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](https://semver.org/). -## [0.10.2] - 2026-06-23 +## [0.11.0] - 2026-06-23 + +Fixes a silent full-pane collapse. `AppFrame` fills its host pane with `height: 100%`, but that only resolves against a definite-height ancestor chain (`#root` → `body` → `html`) — and the SDK shipped no such chain. A Synapse app iframe is its own bare document, so unless the app added `html, body, #root { height: 100% }` to its own `index.html`, `AppFrame`'s `height: 100%` resolved against a content-height ancestor and the whole app collapsed to content height: full width, short height (e.g. an empty board rendering as a ~442px band inside an 868px pane). The precondition the shell depended on was neither supplied nor enforced, so multiple apps hit it. + +### Added + +- `@nimblebrain/synapse/ui/base` — a side-effect import that injects the root-height chain (`html, body, #root { height: 100% }`) plus `body { margin: 0 }`. Import it in an app entry to establish the chain before first paint (no layout jump), or for a full-pane app that renders without `AppFrame`. + +### Fixed + +- `AppFrame` now establishes the root-height chain itself, calling the same base reset on render, so **every** app built on it fills the pane with no per-app `index.html` requirement — existing apps included. The fix uses a percentage chain rather than a viewport unit (`vh`/`dvh`): percentages resolve against the actual allocated pane, staying correct on hosts that give an app a pane shorter than the viewport (where a viewport unit would overflow with a second scrollbar). `AppFrame`'s `height: 100%` and internal scroll model are unchanged; the missing precondition is simply now supplied. Fixes three `ui` tokens that were theme-blind in dark mode. `tokens.bgSubtle`, `tokens.fgFaint`, and `tokens.borderStrong` reference CSS vars (`--color-background-tertiary`, `--color-text-tertiary`, `--color-border-secondary`) that no host injected, so they always resolved to their hardcoded **light** fallbacks — rendering white-on-white surfaces, invisible borders, and illegible faint text whenever a host signaled dark. This hit the SDK's own components (Card, ListRow, Prose, Table, Badge, Button, Avatar, SearchField, SegmentedControl, EmptyState, StatusDot) and any app pairing one of these with a theme-aware token. A `var()` fallback is a static literal that can't branch on theme, so the fix is a theme-aware default layer, not a smarter fallback. diff --git a/README.md b/README.md index 646e353..baa23f8 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,7 @@ npm install @nimblebrain/synapse | `@nimblebrain/synapse/react` | React hooks and providers (`AppProvider`, `SynapseProvider`) | | `@nimblebrain/synapse/ui` | Component library — tokens, primitives, components, layouts | | `@nimblebrain/synapse/ui/fonts` | Side-effect import that loads the brand fonts into the iframe | +| `@nimblebrain/synapse/ui/base` | Side-effect import that establishes the root-height chain (`html, body, #root`) the app shell fills. `AppFrame` does this automatically on render; import it in your entry to apply it before first paint | | `@nimblebrain/synapse/vite` | Vite plugin for dev mode | | `@nimblebrain/synapse/codegen` | CLI + programmatic code generation | | `@nimblebrain/synapse/iife` | Pre-built IIFE bundle for `