Skip to content

elements: rebuild the styled tier on useRender grafts (rename ui to elements)#214

Merged
lifeiscontent merged 32 commits into
mainfrom
prototype/autocomplete-passthrough
Jul 3, 2026
Merged

elements: rebuild the styled tier on useRender grafts (rename ui to elements)#214
lifeiscontent merged 32 commits into
mainfrom
prototype/autocomplete-passthrough

Conversation

@lifeiscontent

Copy link
Copy Markdown
Collaborator

Every part in the old ui tier wrapped the Base UI primitive it styled, which fused our styling layer to Base UI's API. This rebuilds the tier so each part is a plain styled intrinsic element (useRender + a cva) importing nothing from @base-ui/react beyond useRender/mergeProps. Base UI (and base) behavior grafts onto the styled element in components via render — behavior part outer, styled part as the render target — and components now owns every Root, provider, portal, context, and default.

Since the tier is just styled elements now, "ui" stopped describing it, so the tier is renamed to elements: src/elements, @plane/propel/elements/* subpaths, Elements/ story titles.

Along the way:

  • styled elements repeated across 3+ families collapsed into single internal/ primitives (positioner, backdrop, arrow, popup-viewport, overlay-title, overlay-description, listbox-popup, listbox-item)
  • base/text-area is gone (<Field.Control render={<textarea />}> covers it); base/composite stays, grafted in components/list
  • AGENTS.md rules 1, 1a, 2, 4a and 6a rewritten for the model
  • the comment composer story sizes its toolbar icons through ToolbarItemIcon now, so its formatting bar renders the same as the Toolbar story (and the Figma comment box)

vp check is clean and the full browser suite passes (119 files / 535 tests).

…lements)

The old ui tier wrapped Base UI parts directly, fusing styling to Base UI's
API. Every part in the tier is now a plain styled intrinsic element built
with useRender + cva, importing nothing from @base-ui/react beyond
useRender/mergeProps. Base UI (and base) behavior grafts onto the styled
element in components via render — behavior part outer, styled part as the
render target — and components owns every Root, provider, portal, context,
and default.

Since the tier is just styled elements now, "ui" stopped describing it:
renamed to elements (src/elements, @plane/propel/elements/*, Elements/
story titles).

Also:
- dedupe styled elements repeated across 3+ families into internal/
  (positioner, backdrop, arrow, popup-viewport, overlay-title,
  overlay-description, listbox-popup, listbox-item)
- drop base/text-area (Field.Control render={<textarea/>} covers it);
  keep base/composite, grafted in components/list
- rewrite the AGENTS.md protocol (rules 1, 1a, 2, 4a, 6a) for the model
The composer's formatting bar passed bare lucide icons to ToolbarButton/
ToolbarToggle, so they rendered at lucide's default 24px instead of the
14px compact glyph the toolbar sizes through its ToolbarItemIcon slot
(--node-size). Wrap each icon like the Toolbar story does — the two bars
now render identically, matching the Figma comment box.
Copilot AI review requested due to automatic review settings July 2, 2026 05:50
@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown

📚 Storybook preview: https://pr-214-propel-storybook.vamsi-906.workers.dev

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review this pull request because it exceeds the maximum number of files (300). Try reducing the number of changed files and requesting a review from Copilot again.

The indicator flipped a chevron-down 180deg (down closed, up open).
Disclosure carets read better pointing into the collapsed content: rotate
the caret -90deg while closed (inline-end, RTL-mirrored like the list
section trigger) and back to 0 when the panel opens.
The bar has no intrinsic width (w-full root, flex-1 min-w-0 track), so the
bare Default/Indeterminate renders collapsed to 0px under the centered
layout — Default showed only its 60% label and Indeterminate showed
nothing. Wrap the meta in the same w-64 the sibling stories (and Meter)
already use.
Every listbox family was rendering broken rows:

- The item grid (grid-cols-[1rem_1fr]) places children positionally, and
  Base UI unmounts ItemIndicator while unselected, so unselected
  combobox/select rows slid their text into the 1rem marker column and
  wrapped at min-content (us-central-1 across three lines).
- Autocomplete has no indicator at all, so every suggestion wrapped.

Give ListboxItem a layout axis: "indicator" keeps the marker-column grid
for combobox/select, "plain" is a flush flex row for autocomplete. Graft
the indicators with keepMounted so the gutter holds on every row, and
hide the glyph off data-selected in the styled marker instead.
Copilot AI review requested due to automatic review settings July 2, 2026 06:14

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review this pull request because it exceeds the maximum number of files (300). Try reducing the number of changed files and requesting a review from Copilot again.

Base UI ships multi-value support on Combobox (multiple + a
Chips/Chip/ChipRemove anatomy), Select (multiple), and ToggleGroup
(multiple); CheckboxGroup is inherently multi. Cover each:

- combobox: new styled elements parts ComboboxChips (wrapping multiselect
  input frame on the shared field surface), ComboboxChip (neutral tag with
  the roving-focus ring), and ComboboxChipRemove; Multiple stories in both
  tiers wire them through Combobox.Value with remove/append interaction
  twins
- select: Multiple story — the trigger joins the selected labels and every
  selected row keeps its check
- toggle-group: the elements Multiple story was hidden behind
  !dev/!autodocs tags and drew Bold/Italic with alignment glyphs — now
  visible with the right icons, plus a components-tier Multiple story;
  interaction twins assert additive selection
The slot vocabulary had drifted: four suffixes (Icon/Indicator/Slot/Node)
covered three concepts, menu and context-menu named the same slots
differently, two families hand-rolled the glyph sizing the node-slot class
already owns, and the node props were named by position+type instead of
the slot they fill.

Parts, following Base UI's suffix usage (Icon/Indicator/Group):
- InputIconSlot -> InputIcon, CheckboxInlineStartNode -> CheckboxIcon
- MenuItemSelectedIndicator -> MenuItemIndicator (mirrors
  Select/Combobox ItemIndicator; ContextMenuItemIndicator was already
  right), MenuItemSubmenuIndicator -> MenuSubmenuTriggerIndicator
- ToastCloseSlot -> ToastCloseGroup (node wrappers are Groups, like
  ToastActionGroup)
- TableCellSlot deleted - it was nodeSlotClass verbatim, so TableCell
  composes the internal NodeSlot like Tab does

Props, named for the slot they fill (Base UI names by usage:
placeholder/title/description):
- single Icon slot -> icon (banner, checkbox, tab, accordion trigger,
  menu/context-menu rows)
- dual Icon slots -> startIcon/endIcon (button, anchor-button, badge,
  pill, input-field, table-cell)
- MenuItemTrailing -> trailing, MenuLabelMeta -> meta

Mechanics: every glyph slot now sizes through nodeSlotClass +
--node-size (list, navigation-menu, combobox, breadcrumb, toast status,
pill/icon-button spinners) and every decorative slot bakes aria-hidden
(list item icon, navigation-menu icon). Rule 6e in AGENTS.md records the
convention.
Copilot AI review requested due to automatic review settings July 2, 2026 08:21

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review this pull request because it exceeds the maximum number of files (300). Try reducing the number of changed files and requesting a review from Copilot again.

Sweep of the public surface against the protocol rules before the first
publish:

- PopoverContent/MenuContent exposed a width prop — a native attribute
  name rule 6b bans. Renamed the axis to sizing (values unchanged:
  anchor/sm/md/lg/auto), including the internal OverlayPanel axis and the
  exported types (MenuContentSizing, PopoverContentSizing).
- The slot renames had left their Props types behind: CheckboxIcon still
  shipped CheckboxInlineStartNodeProps, and likewise InputIcon,
  MenuItemIndicator, MenuSubmenuTriggerIndicator, ToastCloseGroup. All
  five now match their parts.
- The toast's stopgap Solid* status glyphs sat unexported inside
  components/toast, violating the every-file-is-public index rule; they
  now live in internal/solid-icons (they'll be replaced by the Foundations
  icon set anyway), with StatusIconProps renamed SolidIconProps.
- elements/field forgot to export FieldItemContent and FieldLabelGroup;
  the components tier was importing them by file path around the index.
- Toast and Tooltip stories now declare their subcomponents like every
  other compound family.
- tabs' underline cvas renamed to match their parts
  (tabUnderlineBarVariants etc.); input stories drop the old "IconSlots"
  wording.
- The index rule now names the two blessed private-file exceptions
  (family context modules, .shared compositions) so the next sweep
  doesn't trip on them.

vp check, the full story suite (119 files / 544 tests), attw, and publint
are all green.
The Default story ran its play in the visible story — the last one doing
so; every other family splits tests into !dev/!autodocs/!manifest twins.
Copilot AI review requested due to automatic review settings July 2, 2026 08:36

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review this pull request because it exceeds the maximum number of files (300). Try reducing the number of changed files and requesting a review from Copilot again.

…ories

Consumers of the components tier no longer import @base-ui/react for
anything — the acceptance check is that no components story needs it.

Composed ready-mades where anatomy was heavy:
- select: SelectTrigger (value + chevron baked, icon overridable),
  SelectLabel, SelectList, SelectItem (check indicator + ItemText baked)
- autocomplete: AutocompleteInputGroup (clear/trigger as role-named
  control props, icon slot), AutocompleteList, AutocompleteItem,
  AutocompleteEmpty
- combobox: ComboboxInputGroup, ComboboxChips (data-driven chips with a
  required localizable removeLabel), ComboboxList, ComboboxItem,
  ComboboxEmpty
- navigation-menu: full anatomy wrapped (List/Item/Trigger with the caret
  baked/Content/Link/Viewport)
- toolbar: ToolbarToggleGroup now grafts Base UI ToggleGroup itself

Propel-named passthroughs for the behavior-only roles stories still
reached into Base UI for: AlertDialogTrigger/Close, DrawerTrigger/Close,
ContextMenuTrigger/Separator, MenuTrigger/Group/Separator, the breadcrumb
and menubar triggers, PreviewCardTrigger, and DirectionProvider (RTL apps
compose from propel too). Index comments telling consumers to compose
@base-ui/react directly are gone.

Stories: every visible story is product-focused (render only; the last
inline play — menubar — moved to a hidden twin earlier); new showcase
stories cover the previously unshown visual axes (text-area magnitude/
surface/resize, number-field/select-field/field-family magnitudes,
radio+checkbox group densities, context-menu tones, toggle/toggle-group/
slider/otp-field/avatar-group/scroll-area magnitudes), and components/list
finally has a story (navigation + collapsible section + roving-focus
twin).

vp check, 120 story files / 574 tests, attw, and publint are green.
The bordered form-control frames all composed the shared
field-control-surface (border/focus/danger) but re-spelled everything
above it, and had drifted: input and text-area carried a hover fill,
border shift, transition, and focused-hover correction that autocomplete,
combobox, and number-field silently lacked; the inner inputs were spelled
four ways (only the plain input had the accent caret); disabled state
used :has(:disabled) in some frames and data-disabled in others.

Two internal class helpers now own the shared design:
- control-group: the focus-within surface + transition + hover pack +
  focused-hover correction + BOTH disabled selector forms
- control-input: transparent field, primary text, placeholder color,
  accent caret, disabled cursor/text

input, text-area, autocomplete, combobox (input row and chips frame), and
number-field recompose from them, keeping only their own geometry (width,
alignment, gap, radius, magnitude). Net effect: the pickers gained the
input's hover/transition treatment and every inner input gained the
accent caret.

DX: ComboboxInputGroup gets an icon slot prop (new elements ComboboxIcon,
mirroring AutocompleteIcon's placeholder-tint-brightens-on-focus look) so
a search-style combobox composes the same way as autocomplete.
Copilot AI review requested due to automatic review settings July 2, 2026 09:05

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review this pull request because it exceeds the maximum number of files (300). Try reducing the number of changed files and requesting a review from Copilot again.

Seventeen Family-Icon parts were byte-duplicates of the node-slot chrome
(seven literally bare nodeSlotClass, the rest differing only by a tint or
a local node size), four spinners were identical, and the disclosure
carets had re-spelled the same rotation per family — with real drift:
breadcrumb implemented the same end-closed/down-open affordance on an
opposite glyph convention, and the menu/context-menu submenu pointers
were byte-identical twins.

Following the ListboxItem precedent (rule 4a), the duplicates are gone:

- internal Icon (tint/magnitude axes, both defaulting to inherit — the
  first sanctioned rule-12 default) replaces Button/AnchorButton/
  IconButton/Badge/Pill/Toggle/ContextMenuItem/ToolbarItem/
  AccordionTrigger/MenuItem/MenubarTrigger/Select/Checkbox/Input/
  BreadcrumbTrigger/MenuSearch/ListItem icons. Families with genuinely
  distinct styling keep their part (toast/banner/alert-dialog tones,
  avatar's scale, autocomplete/combobox focus-brightening,
  navigation-menu's Base-UI-named rotating Icon).
- internal DisclosureIndicator carries ONE glyph convention — pass a
  chevron-down; motion: disclose (end closed, down open), pointEnd
  (submenu), flip (dropdown) — reading data-panel-open/data-popup-open
  from the trigger's group. Replaces the accordion/collapsible/
  list-section/breadcrumb/menu-submenu/context-menu-submenu carets;
  visuals unchanged.
- internal Spinner replaces the four identical animate-spin slots.

DX: the icon-shaped controls (IconButton, Toggle, ToolbarButton,
ToolbarToggle) now wrap bare svg children in the slot themselves, so
consumers write <IconButton aria-label><X/></IconButton> with no inner
part. Rule 6e records all of it.
The controls disagreed on what a magnitude means: input was padding-based
md/lg/xl, autocomplete and select ran sm/md/lg at 28/32/36 but differed
on the text size of the SAME step (select md was text-13, autocomplete md
text-14), the otp digits ran the same pixel scale one label off (32/36/40
as sm/md/lg), and combobox had no axis at all (fixed 36px).

internal/control-group now exports THE scale — sm 28/text-13/14px glyphs,
md 32/text-14/16px, lg 36/text-14/16px, xl 44/text-16/20px — and input,
autocomplete (uniform px-3 now), select trigger, combobox (new required
magnitude axis on the input group and chips frame, threaded through the
ready-mades and combobox-field), and otp (relabeled md/lg/xl; the old xl
grows 40->44) compose from it. autocomplete-field/combobox-field thread
their FieldMagnitude straight through instead of hardcoding.

Deliberately left: text-area's magnitude is a typography-only scale for a
multiline control; number-field's heights stay coupled to its stepper
IconButton squares.
Copilot AI review requested due to automatic review settings July 2, 2026 09:39
Copilot AI review requested due to automatic review settings July 2, 2026 10:37
- ToolbarInput: an inline text input in the toolbar's roving tab order (a
  filter box). Invented design pending a Figma spec, flagged for polish:
  a miniature field on the shared control surface at self focus, height
  tracking the toolbar's density (24/28px), text-13 with the standard
  placeholder color. Styled elements part + components graft + a
  WithFilter story so design can review it in situ.
- FieldControl: Base UI's Field.Control passthrough so hand-composed
  fields register their control (name/validation/data-invalid) without a
  Base UI import.
- MenuViewport: Base UI's content-morph container grafted onto the shared
  relative popup viewport, for menus that transition content in place.

With these, every rendering Base UI part has a propel counterpart; the
only unwrapped surface is the advanced escape hatches (floating-ui-react,
unstable-use-media-query) by choice.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review this pull request because it exceeds the maximum number of files (300). Try reducing the number of changed files and requesting a review from Copilot again.

A 37-agent audit fetched each Base UI component docs page, mapped its
hero + feature examples against our stories, and filled the gaps —
~60 new stories with hidden interaction twins across the tier: async
search and grouped/fuzzy/limit/autoHighlight autocomplete (plus a
command-palette composition), detached-trigger and close-confirmation
and nested flows for dialog/alert-dialog/drawer/menu, form async/server/
schema validation, checkbox form integration and nested parent tri-state,
open-on-hover menus, toolbar links, and more. All async examples are
deterministic (in-story delays, Status live regions).

Real findings the new tests forced out:

- Form external errors deadlocked: field-level error props marked the
  field invalid, Form blocks submission while invalid, and only submission
  cleared the error state. FieldHelperText now leaves the Base UI error
  channel open when no explicit error is passed (unmatched FieldError), so
  Form errors display and clear through Base UI's own lifecycle; the
  validation harnesses drop the double plumbing.
- ToolbarLink rendered a <button>: grafting Base UI's anchor part onto the
  button-tag styled element loses the tag. elements/toolbar gains a real
  <a> item part sharing the item chrome (rule 6c), and the ready-made
  grafts onto it.
- DialogBody scroll regions need keyboard access (axe): the tall-content
  story now carries tabIndex={0} with the rationale.
- SelectTrigger/Avatar/Collapsible API gaps from the audit: Select.Value
  placeholder threaded through the baked trigger; Avatar/WorkspaceAvatar
  gain the fallback delay prop; Collapsible passes keepMounted/
  hiddenUntilFound to its panel.

Test-semantics lessons encoded in the twins: Base UI validates onBlur
only once a field is dirty; nested dialogs/alert-dialogs make the parent
popup inert (out of the a11y tree); entry/exit transitions need settling
before visibility assertions; live-region text carries an invisible
word-joiner briefly after mount (regex matchers); axe flags floating-ui
focus guards while popups are open (suppressed per-story with rationale).

Rules 2b/2c rewritten in AGENTS.md: elements stories are pure UI-config
showcases (behavior stories live in components; sweep pending), and
hook-using stories use the named-function render pattern.

Suite: 120 files / 717 tests green; check, attw, publint clean.
Every elements story is now a static styled-part showcase: parts rendered
directly with every visual axis and every state pinned via the data-*/
aria attributes Base UI would set — zero @base-ui/react imports in the
tier (the one family the sweep missed, input-field, done by hand). Hidden
CSS canaries assert the pinned selectors actually compile; behavior plays
moved to components stories where they weren't already covered
(autocomplete/combobox invalid propagation, form constraint validation,
list section disclosure, otp invalid, tooltip shortcut, banner dismiss,
dialog non-dismissable, avatar delayed fallback) and hook stories use the
named-function render pattern (rule 2c).

Fixes the sweep forced out:

- Tailwind v4's rotate-*/translate-* compile to the standalone CSS
  rotate/translate properties — canaries asserting transform read none;
  they now read the right property.
- ContextMenuItem's selected check was invisible: the styled indicator
  hides its glyph unless data-selected is present, and the conditional
  render never set it. Real visual bug, now pinned.
- Calendar's nav-button dimming keyed off :disabled, but react-day-picker
  sets only aria-disabled on nav — the style could never fire; the
  contract now keys off aria-disabled.
- Static showcase a11y: multiple pinned DrawerHeaders are banner
  landmarks (wrapped per-mock in labeled sections); pinned-open table
  scroller needs tabindex; pinned hover/disabled states trip axe
  color-contrast on visuals it never sees live (suppressed per-story with
  rationale — the tertiary-on-hover-fill token is flagged for design);
  select twins close their popup before the axe pass so Base UI's focus
  guards don't trip aria-hidden-focus.

API gaps the sweep reported, deferred as the follow-up punch list: the
ready-made Button cannot render a custom tag (nativeButton unexposed,
consumer render silently clobbered); menu/context-menu checkbox+radio
rows lack ready-mades; grouped OTP slots aren't expressible; the
LinearProgress label part has no components surface; DialogPopupMagnitude
and FieldMagnitude per-axis types aren't re-exported.

120 files / 785 tests green; check, attw, publint clean.
Copilot AI review requested due to automatic review settings July 2, 2026 16:36
…pings

The fourteen light-mode neutral primitives move to the new design values
(near-achromatic at the light end, darker and slightly varied in hue
through the mid/dark steps), and the light text mappings step darker to
match: --txt-placeholder neutral-800 -> neutral-900, --txt-disabled
neutral-700 -> neutral-800. The dark and contrast blocks already sat at
those mappings.

Full story suite (including the axe contrast gate across every visible
story) passes under the new palette.

Closes #215

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review this pull request because it exceeds the maximum number of files (300). Try reducing the number of changed files and requesting a review from Copilot again.

The last gaps between propel's components tier and Base UI's capability
surface, all pre-1.0-cheap and post-1.0-breaking:

- Button threads a consumer render into the styled element (it was
  silently clobbered while the type accepted it) and exposes
  nativeButton, so rendering as another tag works through the ready-made:
  <Button nativeButton={false} render={<div/>}>. Story + twin assert
  role/tabindex/Enter/Space/click activation.
- MenuRadioItem joins MenuCheckboxItem as a ready-made (kept-mounted
  radio dot reading MenuRadioGroup state), and context-menu gains BOTH
  selection ready-mades — ContextMenuCheckboxItem and
  ContextMenuRadioItem. The checkbox/radio indicators get their own
  visibility contract keyed off Base UI's data-unchecked (the shared
  itemIndicatorClass keys off data-selected, which these parts never
  receive). Stories + twins assert menuitemcheckbox/menuitemradio
  semantics.
- OTPField gains groups (e.g. groups={[3, 3]} renders 123-456 with an
  OTPFieldSeparator; counts validated against length).
- LinearProgress gains label — Base UI's Progress.Label grafted onto the
  styled LinearProgressLabel, which previously had no components surface.
- DialogPopupMagnitude and FieldMagnitude are re-exported per rule 9.

120 files / 794 tests green; check, attw, publint clean.
Copilot AI review requested due to automatic review settings July 2, 2026 16:55

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review this pull request because it exceeds the maximum number of files (300). Try reducing the number of changed files and requesting a review from Copilot again.

The element x look 2x2 (Button / Anchor / AnchorButton / ButtonAnchor)
collapses: everything interactive here is a Base UI Button graft, so ONE
component exists per LOOK and real navigation renders an <a> through the
same API (nativeButton={false} + render={<a href/>} — Base UI announces
the control as a button while the <a> keeps native navigation).

- Button and IconButton stay; both now thread a consumer render into
  their styled element and expose nativeButton.
- ButtonAnchor is renamed AnchorButton (the link-text look) and follows
  Button's conventions exactly: startIcon/endIcon sized to the link's
  node scale, loading spinner with aria-busy + dimmed label (new
  AnchorButtonLabel part), nativeButton/render threading. The link
  chrome gains the inline-flex/gap/node-size layout the icons need.
- The old <a>-element families are deleted: AnchorButton (button chrome)
  is Button-with-render now, Anchor (plain link) is
  AnchorButton-with-render. AsLink stories + twins on both surviving
  families pin the recipe and Base UI's role contract.
- Rule 6d rewritten: suffix = the look, prefix = a content trait, and
  the element is never part of the name.

116 story files / 781 tests green; check, attw, publint clean.
- @base-ui/react 1.5.0 -> 1.6.0 (one migration: OTPFieldPreview
  graduated to the stable OTPField export), lucide-react 1.23,
  tailwindcss/@tailwindcss/vite 4.3.2, playwright 1.61.1, wrangler
  4.106.0, oxlint/@oxlint/plugins 1.72, typescript-native-preview
  refresh. catalogMode: prefer moved the shared ranges into the
  workspace catalog. The vitest 4.1.9 / vite-plus 0.2.1 coupling and
  @types/node major are deliberately untouched.

- Peer deps audited for publish: react/react-dom ^18||^19 is honest (no
  React-19-only APIs in shipped source — verified; base-ui itself peers
  ^17||^18||^19, its date-fns peers are optional), tailwindcss ^4 stays
  the one true peer, and @base-ui/react stays a regular dependency so
  consumers never manage it.

- CI's recurring EAGAIN (fork fails spawning attw right after the
  ~120-entry dts build, ~1-in-4 pass rate) gets the cheap package-side
  mitigation: the build script retries once in place — by the second
  attempt the build's worker threads are reaped. The workflow file is
  out of this repo auth's scope.

116 story files / 781 tests green under 1.6; check, attw, publint clean.
Copilot AI review requested due to automatic review settings July 2, 2026 17:17

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review this pull request because it exceeds the maximum number of files (300). Try reducing the number of changed files and requesting a review from Copilot again.

vp run executes package scripts without a shell, so the previous
'vp pack || vp pack' passed the retry as literal arguments and never
fired. The retry now lives in scripts/pack-retry.mjs: on the runner's
intermittent fork-EAGAIN (os error 11, tail of the dts build) it waits
five seconds for the worker threads to reap and runs vp pack once more.
The ring hard-coded determinate (value: number) while LinearProgress and
Base UI accept null. value is now number | null: while indeterminate a
fixed quarter arc shows and the svg spins it (keyed off the root's
data-indeterminate through a named group), with aria-valuenow unset.
Indeterminate story + hidden twin mirror the linear family's.
Copilot AI review requested due to automatic review settings July 2, 2026 17:29

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review this pull request because it exceeds the maximum number of files (300). Try reducing the number of changed files and requesting a review from Copilot again.

…n CI

Root cause of the persisting EAGAIN, from the run logs: CI's job runs the
783-test browser suite and starts the build ONE SECOND later, while
chromium's process tree is still being reaped — the dts build's spawn
burst then forks into the pressure and fails. The previous retry made it
worse in exactly that window: its sleep spawned a node child, which under
EAGAIN itself fails to spawn instantly, so the second vp pack launched
milliseconds after the first into the same pressure (the run log shows
the failure and both retries sharing one timestamp).

The sleep now blocks in-process (Atomics.wait — no fork needed), the
script settles 10s BEFORE the first attempt when CI is set, and it makes
three attempts 10s apart, logging each.
@lifeiscontent lifeiscontent merged commit 0277208 into main Jul 3, 2026
2 checks passed
@lifeiscontent lifeiscontent deleted the prototype/autocomplete-passthrough branch July 3, 2026 10:32
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.

2 participants