Skip to content

Docs: Document the component contribution contract - #67

Open
moersoy wants to merge 2 commits into
geastack:mainfrom
moersoy:docs/component-contract
Open

moersoy wants to merge 2 commits into
geastack:mainfrom
moersoy:docs/component-contract

Conversation

@moersoy

@moersoy moersoy commented Oct 17, 2026 •

Copy link
Copy Markdown

Closes #2.

Problem

The UI elements board lists many components to build, but nothing tells a contributor where a component goes, how to name its props and events, who owns its state, or what a PR has to prove. The first open component PR put its files in engine/components and shipped key handlers that never fire. A written contract would have ruled both out.

Change

docs/COMPONENTS.md, linked from both READMEs. It writes down the rules VirtualList and CameraView already follow and checks both components against them:

  • placement in packages/elements, plus the .tsx/.css/.d.ts/index/exports files a component touches;
  • function components by default. A class component (Component<GeaElement, Props>, props destructured in template) is only for a component that owns a resource and needs an explicit cleanup method;
  • controlled state, and prop names reused from the native controls;
  • class maps for state, with the caller's class on the root and fixed part classes inside;
  • composed versus native-control rendering, with a per-target table of how the control tags actually render: pixel engine, macOS, iOS, Windows;
  • what a PR brings: an example app, a native pipeline test, docs, and a target matrix. The matrix separates device, simulator/harness, browser-only and source-only evidence.

It also records what the engine and targets cannot do yet, so components don't depend on it:

  • no :focus/:checked/:disabled or attribute selectors in stylesheets;
  • onFocus/onBlur are dropped;
  • how key events route;
  • no Tab order;
  • no style prop forwarding;
  • an ASCII-only bitmap font;
  • the browser target is not evidence for a native target.

Open question for review

The doc proposes onChange(next: T) with a typed value as the one rule with no precedent in the existing elements. Native elements now use onChange(event: InputEvent) for a committed change, so the same name would carry a different payload on a component. The doc says so and leaves the choice to you. Settling it before #22–#46 land would avoid renames.

Tests

Documentation only; nothing was built or run. Each claim was checked against the source it cites at 617eb3f and in the apple, windows, cli, debugger and examples repos, and every cited path exists. Two limits were found while building the ProgressBar (#69): the style-prop limit and the need to call startCssAnimations before each pumpFrame in a test.

The UI elements board asks for many new components, but nothing told a
contributor where a component goes, how it names its props and events, who
owns its state, or what evidence a PR needs. The one open component PR put
its file in engine/components and shipped keyboard handlers that never
fire, which the existing structure would have ruled out.

docs/COMPONENTS.md writes down the rules VirtualList and CameraView already
follow: placement in packages/elements, the .tsx/.css/.d.ts/index/exports
files, function components unless a resource needs an explicit cleanup
method, the Component<GeaElement, Props> shape for class components with
props destructured in template, controlled state, native-control prop
names, and class maps for state with the caller's class on the root and
fixed part classes inside. It checks the two existing components against
the contract. It proposes typed onChange(next) as the one rule without
precedent, and notes that native elements now use onChange for a committed
InputEvent. Both READMEs link to it.

It records what the targets and the engine cannot do yet:

- How control tags render on the pixel engine (ESP32, RP2350, WASM
  simulator, Linux, Android), macOS, iOS (a UIKit view per node) and
  Windows (radio as a text field, select and stepper as plain views).
  A new native tag needs a NodeType, a Document factory, both geatsc tag
  maps, an intrinsic typing and a mapping in each native renderer.
- The browser target, the default for gea dev and gea simulate, is not
  evidence for a native target. Pebble has its own engine and is out of
  scope.
- Stylesheet rules have no :focus/:checked/:disabled, attribute selectors
  or +/~ combinators; .name:active sets background-color only. The DOM
  queries (querySelector, matches, closest) accept attribute selectors
  and :disabled.
- onFocus/onBlur are dropped on every native target. focusin and focusout
  fire when the active input changes, including on a tap outside every
  input and on focus(), which a native renderer can refuse. Key events
  target the focused node and bubble; onKeyDown on a component root
  becomes a document-level listener. There is no Tab order or focus ring,
  so components attach no key handlers until core#5.
- A Style object prop forwarded to a JSX style attribute fails as a C++
  static_assert, so components take no style prop.
- The bitmap font is ASCII only.

It lists the example, pipeline test and target matrix a PR must bring,
with a separate level for browser-only testing. For animation tests it
says to call startCssAnimations before each pumpFrame, as core/runtime.cpp
does on the device, and to re-query nodes after a function component
re-renders. It notes that the model pipeline test builds with --allow-any.

Tests: documentation only; nothing was built or run. Each claim was
checked in the source at geastack/core 617eb3f and the sibling repos,
including macos_renderer.mm, view_reconciler.mm, ios_main.mm,
win32_renderer.cpp, rpios_display.cpp, android_display.cpp,
tree_events.cpp, runtime.cpp, node.cpp, touch_runtime.cpp, input.cpp,
selector.cpp, style.cpp, node_model.h, cpp-mounted-lowering.ts,
cpp-mounted.ts, cpp-template-renderer.ts, build-gea-vite-geatsc.mjs,
gea_runtime.h, packages/core/index.d.ts, BookCover.tsx and the cli
README, and every cited path exists. The style-prop and animation-test
limits were hit while building ProgressBar, whose pipeline test passes
with the described approach.
@coderabbitai

coderabbitai Bot commented Oct 17, 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: 2b4f3f43-fc49-428b-a0ff-e1d6843ffda6
  • 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.

A native build resolves value imports of @geastack/elements to
components/index.ts, not index.d.ts. The contract listed the props type
export only for index.d.ts, so a component following it shipped a type
that `import { Name, type NameProps }` accepts in tsc but geatsc rejects
with "has no exported member". The Files table now puts the type export
in index.ts too, and a paragraph explains why.

Tests: a scratch app that imports InlineAlert with its variant type this
way fails to build natively while index.ts lacks the type and builds
once it has it; the same holds for ProgressBarProps and the VirtualList
types (core#69, core#71).
@moersoy

moersoy commented Oct 18, 2026

Copy link
Copy Markdown
Author

Pushed 77136e9: the Files table now puts the props type export in components/index.ts as well as index.d.ts, with a short paragraph explaining why. A native build resolves value imports to index.ts, so a type missing there fails in geatsc even though tsc accepts it. Seen while building #72 and fixed for the existing components in #69 and #71.

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.

Document the component contribution contract

1 participant