Skip to content

Arena 11.1.0: locale, Angular forms, confirmations, the icon rail and the Tailwind theme sheet - #40

Merged
JCarlosHidalgo merged 44 commits into
mainfrom
develop
Sep 11, 2026
Merged

JCarlosHidalgo merged 44 commits into
mainfrom
develop

Conversation

@JCarlosHidalgo

Copy link
Copy Markdown
Contributor

Arena 11.1.0. A minor: new component surface in both layers, nothing removed.

What lands

  • Every word Arena draws comes from the locale. The words are a contract type whose defaults generate:api-types writes into both layers; a consumer provides the locale once, sentences with a value are one template filled the same way in both layers, and the calendar formats every date in the provided locale. A suite renders every component under a sentinel locale and lists any English it still draws.
  • Angular forms binding. Six form directives bind the data-entry controls to reactive and template-driven forms through a separate forms entry point; forms is an optional peer the package root never reaches. A radio group can be disabled whole, in both layers.
  • A confirmation asked for from code. A contract type, awaited from a handler in React and from a service in Angular, with a confirmation queue beside the toast queue on both npm pages.
  • The dialog can fill a narrow screen, inset by the device's safe area; ArenaBreakpoint restates the bp group and check:script-tokens holds it.
  • The side nav collapses to an icon rail in both layers, keeping every name its rows had; a tooltip that repeats its trigger's name stops repeating it.
  • Calendar events carry detail lines the chip sheds as it shrinks and always announces.
  • Both packages ship Arena's Tailwind theme as a sheet the consumer's Tailwind compiles, and check:packages compiles it the way a consumer would.
  • Every surface that states a version states 11.1.0.

Verified

  • bun run check --release after bun run build:release: 87 of 87 steps ran and passed, none from the cache.
  • bun scripts/check/arena/check-release.ts after tagging: Release OK, and v11.1.0 is the tip of develop.
  • Arena develop green for the tagged commit on ubuntu, macOS and Windows.

🤖 Generated with Claude Code

https://claude.ai/code/session_01SKvjMtCxY2Hd4dFtJtQ6x8

JCarlosHidalgo and others added 30 commits September 10, 2026 20:53
…tes their defaults into both layers

ArenaLocale declares every word a component draws of its own and the locale dates are
formatted in, each field carrying the word drawn today. generate:api-types emits
ARENA_DEFAULT_LOCALE from those defaults into LocaleDefaults.generated.ts in each layer,
and check:api diffs it through buildApiModules like the type modules.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SKvjMtCxY2Hd4dFtJtQ6x8
… same way in both layers

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SKvjMtCxY2Hd4dFtJtQ6x8
…it reads any other provider

Angular provides ARENA_LOCALE through provideArenaLocale; React through ArenaLocaleProvider
and useArenaLocale. Both merge a partial over ARENA_DEFAULT_LOCALE with the same
arenaMergeLocale, which check:shared-arithmetic holds identical. Both npm pages say what the
locale answers and in which order a word resolves, and consumer-install's budget moves to
what the Angular page now measures.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SKvjMtCxY2Hd4dFtJtQ6x8
… English it still draws

Red on purpose: every case names the words its component still draws in English, which is the
checklist the rest of this phase works through, and the Angular test surface does not compile
until the calendar's formatters take the locale, which the next commit does.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SKvjMtCxY2Hd4dFtJtQ6x8
…own words from it

arenaFormatDate and arenaRangeTitle take the locale in both layers, and dateFormatter keys its
cache by locale and options. Today, the nav arrows' names, the region and grid names and the
event's Actions button read the locale; hour labels keep h23 and weekStartsOn stays a member.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SKvjMtCxY2Hd4dFtJtQ6x8
…locale

Toast, sheet, alert, tag, skeleton, switch, avatar and pagination read their close, dismiss,
remove, loading, confirmation, presence and page-arrow words from the provided locale.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SKvjMtCxY2Hd4dFtJtQ6x8
…nswer when they are absent

ArenaSpinner.label, ArenaErrorState.title, ArenaConfirmDialog.eyebrow, confirmLabel and
cancelLabel, ArenaBulkActionBar.noun and ArenaCommandPalette.placeholder carry no contract
default; each layer resolves the member, then the locale. The table's empty fallback, the
confirmation's required-text prompt and the bulk bar's count, clear and toolbar words read the
locale too, the count through the {count} and {noun} template.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SKvjMtCxY2Hd4dFtJtQ6x8
…e, and the palette's dialog is named Command palette

The palette's dialog name read ArenaCommand palette, a rename that reached a string; its locale
default is Command palette. The sentinel suite types into the palette through the native value
setter, so React's change tracking sees it, and treats the select's caret glyph as neutral.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SKvjMtCxY2Hd4dFtJtQ6x8
…om the locale; its numbers stay the chart's own

arenaPointTable takes its first heading as a parameter in both layers, and each chart passes the
locale's word for its table and its accessible name; the doughnut's legend name follows its
shape. Number formatting stays valueFormat's.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SKvjMtCxY2Hd4dFtJtQ6x8
The point-table suite passes the heading arenaPointTable now takes, and every sentinel case is
green in both layers.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SKvjMtCxY2Hd4dFtJtQ6x8
copy-is-english becomes copy-is-direct. Its body is shortened to pay for the sentence saying
Arena's own words follow the locale, because the router carrying it is budgeted. The design
contract's Language line separates Arena's documentation and defaults, which are English, from
a product's copy, which is in the product's language.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SKvjMtCxY2Hd4dFtJtQ6x8
…draws

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SKvjMtCxY2Hd4dFtJtQ6x8
… to the npm page

The sentence every Words paragraph repeated, and the chart family's note on numbers, are the npm
page's to state once; check:duplication held the copies to that.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SKvjMtCxY2Hd4dFtJtQ6x8
@angular/forms joins the Angular envelope and the optional peers, pinned as a devDependency.
check:architecture holds both optional peers to their own entry point and walks the package
root's import closure, failing any file in it that imports either; the stack page's peer table
gains the row, and consumer-stack's budget moves to what the page now measures.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SKvjMtCxY2Hd4dFtJtQ6x8
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SKvjMtCxY2Hd4dFtJtQ6x8
…ot importing forms

ArenaControlBinding holds the bound flag, the value and the disabled state a form writes, and
the callback a control reports through. It imports nothing from @angular/forms, and INTERNAL
records it and the double-binding warning as a seam no consumer leans on.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SKvjMtCxY2Hd4dFtJtQ6x8
…ng when a form binds it

Input, textarea, select, checkbox, radio group and switch provide ArenaControlBinding. While it
is bound a control draws the form's value over its own member, draws disabled when either says
so, and reports every change it emits through the binding as well; a switch with confirm reports
nothing on requestChange. Binding the control's own value member as well warns once.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SKvjMtCxY2Hd4dFtJtQ6x8
…iven forms

frameworks/angular/forms/ holds one ControlValueAccessor per data-entry control, selected by
formControl, formControlName or ngModel on the control's own element, and ARENA_FORM_CONTROLS
for one imports line. Each writes and reads through the control's ArenaControlBinding and marks
the control touched when focus leaves it; a number input reports a number, or null when emptied.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SKvjMtCxY2Hd4dFtJtQ6x8
…imary by name

SECONDARY_ENTRY_POINTS gains forms. Staging rewrites a secondary entry point's relative imports
of the primary to the package's own name, so ng-packagr compiles one ArenaControlBinding and the
forms module imports it; check:packages fails a secondary entry point with no exports key.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SKvjMtCxY2Hd4dFtJtQ6x8
…na binds to Angular forms and where a project's own air comes from

The Angular npm page gains a reactive-forms section, and each data-entry control's prompt says
which directive binds it and what it carries. The adoption tree gains a node asking what the code
around Arena brings: markup styled with CSS of its own takes the tokens and css/rhythm.css, and an
Angular project binding forms takes the forms entry point, whose peer joins the dependency table.
The tree's budget and consumer-install's move to what those pages now measure.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SKvjMtCxY2Hd4dFtJtQ6x8
…hecks

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SKvjMtCxY2Hd4dFtJtQ6x8
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SKvjMtCxY2Hd4dFtJtQ6x8
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SKvjMtCxY2Hd4dFtJtQ6x8
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SKvjMtCxY2Hd4dFtJtQ6x8
Each page gains a row beside the toast queue and a short section with the block that renders the
one open request; each ArenaConfirmDialog prompt points a caller asking from code at the queue.
consumer-install's budget moves to what the Angular page now measures.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SKvjMtCxY2Hd4dFtJtQ6x8
…the restatement

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SKvjMtCxY2Hd4dFtJtQ6x8
…evice's safe area

fillBelow takes an ArenaBreakpoint. Each layer measures the dialog's own scrim and, below the
named breakpoint, draws the manifest's fill variant: the panel takes the whole screen with no
radius and no shadow, the head and foot stick and pay their edge's inset, the body scrolls, and
width is ignored. React measures in an inner frame mounted only while open; Angular reads the
breakpoint only when fillBelow is set. SCALE_USES records the panel's shadow-none.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SKvjMtCxY2Hd4dFtJtQ6x8
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SKvjMtCxY2Hd4dFtJtQ6x8
…pares nodes the layer's way

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SKvjMtCxY2Hd4dFtJtQ6x8
…ps repeating the word

When a tooltip opens it reads its trigger's accessible name; if that name already contains the
label, the bubble wraps its text in an aria-hidden span. The trigger still references the
tooltip with aria-describedby, as the tooltip pattern requires, and the description it
contributes is empty, which is what Chromium's accessibility tree exposes for that shape. The
test is one pure function per layer, held identical by check:shared-arithmetic.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SKvjMtCxY2Hd4dFtJtQ6x8
JCarlosHidalgo and others added 14 commits September 10, 2026 22:34
…name its rows had

ArenaSideNav.collapsed narrows the list to a rail whose width is the control height plus the
row's padding on each side, a derivation the manifest writes so density moves it, animated over
the state duration and not at all under reduced motion. An item draws its icon alone with its
label as its visually hidden name and as a composed ArenaTooltip, a badge becomes a hidden dot
whose count joins the name, a section keeps its label as the group's hidden name under a
separator, and a collapsible's items render at the rail level with the label as their group
name and no trigger. An item with no icon is refused while collapsed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SKvjMtCxY2Hd4dFtJtQ6x8
… name

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SKvjMtCxY2Hd4dFtJtQ6x8
… compare nodes the layer's way

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SKvjMtCxY2Hd4dFtJtQ6x8
… function decides how many it draws

calendar-detail-line-h is the time label's line box, 16px in both layers measured in Chromium,
declared beside the calendar's other script tokens and excluded from the Tailwind scale with its
reason. arenaVisibleDetails draws none while the time label is shed and one more per line of
height above calendarTimeMinH, the same body in both layers.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SKvjMtCxY2Hd4dFtJtQ6x8
…d always announces

ArenaCalendarEvent.details draws one truncated line per entry under the time label, as many as
arenaVisibleDetails allows. An activatable chip's name is the title, the date, the time and every
detail; an inert chip keeps the lines it sheds in its text, visually hidden. The manifest's detail
slot sits on the time label's step, recorded in SCALE_USES, and the witness plugin paints both new
parts.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SKvjMtCxY2Hd4dFtJtQ6x8
… can say names them

The review event runs two hours and carries two details, so both layers' kitchen-sink pages draw
a chip with details. MemberForms' R1 paragraph stops listing the event's members as a closed set,
and the calendar and event prompts say what details are for and how they shed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SKvjMtCxY2Hd4dFtJtQ6x8
…ut in the layer does

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SKvjMtCxY2Hd4dFtJtQ6x8
…sumer's own compile

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SKvjMtCxY2Hd4dFtJtQ6x8
…ailwind compiles

css/tailwind-theme.css is written beside the other sheets and outside arena.css's import chain,
and each assembler declares the theme's sources among what it reads.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SKvjMtCxY2Hd4dFtJtQ6x8
…piles it the way a consumer would

No packaged sheet but css/tailwind-theme.css carries a compile-time directive, and that one is
named in COMPILED_BY_CONSUMER with its reason. Each packaged copy equals a fresh assembly, and a
consumer sheet compiled with Tailwind v4 against it resolves bg-base-100 and p-4 to Arena's
tokens, keeps a key declared below the import, reaches no default palette, and emits the
self-reference inside @layer theme, which holds because arena.css's token sheets load unlayered,
which the gate asserts too.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SKvjMtCxY2Hd4dFtJtQ6x8
…wind theme sheet and its utilities

Each npm page's css table gains the row, naming the utilities the sheet exports beside the theme.
The page reference says markup written with Tailwind reads the same tokens and spends the rhythm
steps as gap utilities, and node 10 of the adoption tree gains the Tailwind row. consumer-page,
consumer-install and consumer-coldstart move to what their stops now measure.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SKvjMtCxY2Hd4dFtJtQ6x8
…ay that layer names no other layer

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SKvjMtCxY2Hd4dFtJtQ6x8
…nd can be collapsed to a rail

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SKvjMtCxY2Hd4dFtJtQ6x8
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SKvjMtCxY2Hd4dFtJtQ6x8
@JCarlosHidalgo
JCarlosHidalgo merged commit 08ccfeb into main Sep 11, 2026
11 checks passed
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.

1 participant