Skip to content

Elements: Add an InlineAlert component - #72

Open
moersoy wants to merge 1 commit into
geastack:mainfrom
moersoy:elements/inline-alert
Open

moersoy wants to merge 1 commit into
geastack:mainfrom
moersoy:elements/inline-alert

Conversation

@moersoy

@moersoy moersoy commented Oct 18, 2026 •

Copy link
Copy Markdown

Closes #37.

Problem

Apps have no reusable way to show persistent feedback inside a layout: an info, success, warning or error message with an optional title, an action and a way to dismiss it.

Change

InlineAlert in @geastack/elements, a function component built from plain views and native <button> elements. It follows the contract proposed in #2 (#67): placement in packages/elements, controlled, class maps for state, the caller's class on the root, fixed inline-alert__* part classes, and no style prop.

  • Props: variant ('info' | 'success' | 'warning' | 'error', default 'info'), title, message, severityLabel, actionLabel, onAction, dismissible, dismissLabel, onDismiss, id, class. The inlineAlertSeverityLabel(variant) helper is exported.
  • Severity beyond color. A visible severity label names the variant. It defaults to English, severityLabel takes a translation, and an empty string falls back to the default so the cue is never blank. dismissLabel does the same.
  • Optional parts. Title, message and the action button render only when their text is not empty. The dismiss button renders only when dismissible is set.
  • App-owned visibility. Dismissing calls onDismiss and nothing else; the app removes the alert.
  • Roles. The root has role="status", or role="alert" for errors. The browser target exposes it to screen readers; native targets ignore it.
  • Button layout. On the pixel engine an auto-width button in a flex row took the whole row, so the buttons use width: fit-content with max-width: 100%. The actions row wraps, so in a narrow alert the dismiss button moves to a second line instead of pushing the action out of the row.
  • Type exports. components/index.ts exports InlineAlertProps and InlineAlertVariant as well as index.d.ts. A native build resolves value imports to index.ts, so import { InlineAlert, type InlineAlertVariant } otherwise fails in geatsc with "has no exported member". The same gap in the existing components is fixed in Elements: Add a ProgressBar component #69 and Elements: Type VirtualList's item template as an element #71, and Docs: Document the component contribution contract #67 adds the rule to the contract.
  • CSS side effects. package.json marks *.css as side effects, the same change as Elements: Add a ProgressBar component #69.

Tests

packages/core/test/run-gea-elements-inline-alert-pipeline.sh builds an in-core fixture app (packages/core/test/fixtures/gea-elements-inline-alert). It needs no app from geastack/examples and does not depend on any other open PR. test_gea_elements_inline_alert_main.cpp checks, on the native engine:

  • each variant's class, its default or translated severity label, its role, and distinct fill and stripe colors per variant;
  • that the caller's id and class land on the root;
  • that title, message and buttons render only when given, with empty strings and an explicit undefined;
  • that empty severity and dismiss labels fall back to the defaults;
  • that the parts stack as severity, title, message, buttons;
  • that a long message and a long title wrap inside a 200 px alert that grows with them;
  • that each button is exactly as wide and tall as its one-line label plus padding and border, stays inside the actions row and ends its line. A pair of buttons shares one line in a wide alert and wraps in a narrow one;
  • that onAction and onDismiss fire once per press and only for their own alert;
  • that a dismiss button without onDismiss does nothing;
  • that an alert the app removes leaves the tree;
  • that a variant changed by the app re-renders with the new class, label and role.

Each of 29 mutations, applied one at a time, makes the test fail, including:

  • a wrong default variant or label, the translated label ignored, or empty labels kept;
  • the title, message or action guards turned into undefined checks, or the dismiss button always shown;
  • the callbacks swapped, or id, class or the role dropped or misassigned;
  • a wrong variant class or the error colors dropped;
  • no fit-content, no flex-wrap, no justify-content, a reversed actions row, or different button padding;
  • the title after the message, or nowrap on the message or title.

Other checks:

  • tsc 5.9.3 strict passes on the component and the fixture.
  • In the browser DOM dev server, the fixture renders, responds to clicks, sets the roles and wraps the narrow button pair.
  • The full core suite, run test by test on macOS with Homebrew bash 5, gives 154/156 on this branch. The two failures fail the same way on main (b2b1ac1): run-ui-memory-census.sh needs GEA_CENSUS_REFERENCE_ENGINE, and test_gea_vite_module_graph_assets.mjs is fixed by Point the module-graph asset test's compiler at this checkout's Core #66. That suite run predates the last review fixes, which touch only InlineAlert's own files, fixture and test; after them the InlineAlert test was run again and passes.

Target support

Target Status Evidence
Native test harness (host build of the pixel engine) tested the pipeline test above
Browser (DOM, dev-web.mjs) tested in browser fixture and demo checked in the dev server; not evidence for a native target
WASM simulator not run no Emscripten on the test machine
ESP32, RP2350, Linux, Android source-only same pixel engine as the harness. With the built-in bitmap font only ASCII draws, so non-ASCII labels need runtime TTF fonts or a --gea-font-charset
macOS, iOS, Windows source-only the buttons are drawn as native widgets there; whether their padding, border and fit-content sizing are honored is not verified
Pebble not covered separate engine

Limitations

  • Native targets expose no role or status to assistive technology.
  • Right-to-left layout is not handled; the stripe is on the left edge.
  • In a native build, an alert inside a conditional branch keeps the props it was first rendered with. This is a geatsc behavior that affects any component; I filed it as Native build: a component inside a conditional branch keeps its first props compiler#6. The README points to the demo, which renders each alert from a class component whose template root is an element.

Related

  • Demo app: Add an InlineAlert demo app examples#4 adds apps/inline-alert. This PR's test does not need it, so either can merge first. The demo depends on @geastack/elements ^0.1.6, which does not export InlineAlert, so it type-checks only against this branch until a release. I've left the release to you.
  • Conflicts: only with Elements: Add a ProgressBar component #69 (ProgressBar), in packages/elements/README.md and the package.json exports, where both PRs add an entry, plus the identical sideEffects change. Whichever merges second needs a few lines of rebase, and I'll do it.

Apps had no reusable way to show persistent feedback inside a layout: an
info, success, warning or error message with an optional title, an
action and a way to dismiss it.

InlineAlert is a function component built from plain views and native
<button> elements. A class map on the box picks the variant's colors,
and a visible severity label names the variant, so the meaning does not
rely on color alone. The label defaults to English, severityLabel takes
a translation, and an empty label falls back to the default so the cue
is never blank; dismissLabel does the same. Title, message and the
action button render only when their text is not empty, and the dismiss
button only when dismissible is set. Dismissing calls onDismiss and
nothing else: the app owns visibility and removes the alert. The root
carries role="status", or role="alert" for errors, which the browser
target exposes to screen readers; native targets ignore it. The caller's
class goes on the root and the parts use fixed inline-alert__ classes.
There is no style prop, since geatsc has no rule for forwarding a Style
object through a function component.

On the pixel engine an auto-width button in a flex row took the whole
row, so the buttons use width: fit-content, with max-width: 100% for a
long label. The actions row wraps, so a pair of buttons that does not
fit in a narrow alert puts the dismiss button on a second line instead
of pushing the action button out of the row. The components index
exports the prop and variant types from index.ts as well as index.d.ts:
a native build resolves value imports to the .ts sources, so `import {
InlineAlert, type InlineAlertVariant }` otherwise failed with "has no
exported member". The elements package marks *.css as side effects, so a
bundler cannot drop the stylesheet import.

The README and .d.ts state the limits. With the built-in bitmap font,
pixel targets draw ASCII only, so a character such as a Turkish ü or ş
draws as "?" unless the app enables runtime TTF fonts or a
--gea-font-charset. Right-to-left layout is not handled. In a native
build, an alert inside a conditional branch keeps the props it was first
rendered with, a geatsc behavior that affects any component; the README
points to the demo in geastack/examples, which renders each alert from a
class component whose template root is an element.

Tests: added run-gea-elements-inline-alert-pipeline.sh, which builds an
in-core fixture app, so it needs no app in the examples checkout; like
the other app pipelines it compiles with --allow-any. It checks on the
native engine: each variant's class, default or translated severity
label, role, and distinct fill and stripe colors; the caller's id and
class on the root; that title, message and buttons render only when
given, with empty strings and an explicit undefined; that empty severity
and dismiss labels fall back to the defaults; that the parts stack as
severity, title, message, buttons; that a long message and a long title
wrap inside a 200 px alert that grows with them; that each button is
exactly as wide as its label plus padding and border, stays on one line
inside the actions row and ends its line, with a pair on one line in a
wide alert and wrapped in a narrow one; that onAction and onDismiss fire
once per press and only for their own alert, that a dismiss button
without onDismiss does nothing, that an app-removed alert leaves the
tree, and that a variant changed by the app re-renders with the new
class, label and role. Each of 29 mutations, applied one at a time,
makes it fail, among them: a wrong default variant or label, the
translated label ignored, empty severity or dismiss labels kept, title,
message or action guards changed to undefined checks, the dismiss button
always shown, the callbacks swapped, id or class not forwarded, the role
dropped or given to the wrong variant, a wrong variant class, no
fit-content, no flex-wrap, no justify-content, a reversed actions row,
narrower or taller button padding, the title after the message, the
error colors dropped, and nowrap on the message or title. tsc 5.9.3
strict passes on the component and the fixture. In the browser DOM dev
server the fixture renders, responds to clicks, sets the roles and wraps
the narrow button pair. Full core suite on this branch with Homebrew
bash 5: 154 of 156 pass; the two failures,
test_gea_vite_module_graph_assets.mjs and run-ui-memory-census.sh, fail
the same way on main (b2b1ac1) and are environment gaps. That suite run
predates the last round of review fixes, which change only InlineAlert's
own files, its fixture and its test; after them the InlineAlert test was
run again and passes. Not run: the WASM simulator, devices, and the
macOS, iOS and Windows renderers, which draw the buttons as native
widgets (source only).
@coderabbitai

coderabbitai Bot commented Oct 18, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: cdafad73-fb46-4112-b7bb-3140e1d670b1
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

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.

Add InlineAlert

1 participant