feat(theme): [ENG-47561] add the docs measure and the prose + mono body registers - #905

Merged
isaque-bock-azion merged 3 commits into
feat/webkit-documentationfrom
feat/ENG-47561-docs-measure-and-mono-body
Aug 24, 2026
Merged

feat(theme): [ENG-47561] add the docs measure and the prose + mono body registers#905
isaque-bock-azion merged 3 commits into
feat/webkit-documentationfrom
feat/ENG-47561-docs-measure-and-mono-body

Conversation

@gabriel-lisboa-azion

@gabriel-lisboa-aziongabriel-lisboa-azion commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • text-body-prose-md — body copy at --leading-relaxed (1.625). The whole text-body-* set is snug (1.375), and the documentation prose ladder was fitted against a 26px line box, so prose needs its own register. Line-height belongs in the token: check-authoring's leading-raw check deliberately rejects a leading-* override even in token form.
  • text-body-code-sm — the mono body register. The label-code-* set is leading-none, built for one-row-per-line code; this one wraps. Required by DocPrompt.
  • layout-measure-docs (--container-2xl, 752px) + layout-column-docs, completing the measure family (column / focused / form / form-create / docs). Docs are the one column capped by typography rather than payload width. The utility is generated by COLUMN_MEASURE + the columnUtility factory, so two entries produce the whole class.

How to test

  1. node packages/theme/src/scripts/build-tokens.mjs then git diff --stat packages/theme/dist — expect no further change; the committed dist is already current.
  2. cd packages/theme && node --test "test/**/*.test.mjs" — 12/12 pass, including the zero-unit assertions over every emitted token.
  3. grep "text-body-prose-md-line-height\|text-body-md-line-height" packages/theme/dist/v4/globals.css — expect 1.625 and 1.375 respectively; that difference is the whole point of the new token.
  4. grep -A5 '@utility text-body-code-sm' packages/theme/dist/v4/globals.css and the same for layout-column-docs — each must emit a real rule body. A token that compiles to nothing is the silent failure mode here.
  5. grep "layout-measure':" packages/theme/src/tokens/semantic/layouts.data.js — must still read var(--container-7xl) / 1620px (see Notes).

Notes

  • Deliberately excluded: the working branch this was carved from also retunes layout-measure from --container-7xl (1620px) to --container-6xl (1388px), moving home from the focused measure to the data measure. That is a separate product decision that would have silently changed the width of every data page, so it is not here — layout-measure and layout-measure-focused keep main's values and main's prose.
  • Stacked on chore: [ENG-47606] add the documentation component category #904 (the documentation category), which DocProse also needs. Merge chore: [ENG-47606] add the documentation component category #904 first; this retargets automatically.
  • No component consumes layout-column-docs yet: DocProse deliberately has no measure of its own, so the consumer caps the column.
  • Related: ENG-47561, ENG-47606, and the sibling component issues.

Delivery

The Documentation family is delivered on the feature branch feat/docs-components-bundle, not piecemeal to main — so main takes the whole family as one merge, while review stays per-component.

Merge order, bottom-up — each PR's base is the one before it, and each retargets automatically as its parent lands:

#904#905#906#907#908#909#910#911#912#913#914#915#916#917#918feat/docs-components-bundlemain

#904the documentation category (standards only)
#905theme: the docs measure + the prose and mono body registers
#906#918DocProse · DocCallout · DocCard · DocCardGroup · DocItem · DocSteps + DocStep · DocFrame · DocUpdate · DocPrompt · DocTooltip · DocPagination · DocPageHeader · DocOnThisPage

@gabriel-lisboa-aziongabriel-lisboa-azion changed the title feat(theme): [ENG-47561] add the docs measure and the mono body registerfeat(theme): [ENG-47561] add the docs measure and the prose + mono body registersAug 22, 2026
@gabriel-lisboa-azion
gabriel-lisboa-azionforce-pushed the feat/ENG-47561-docs-measure-and-mono-body branch from d3d46c0 to bc6575cCompareAugust 22, 2026 14:11
@gabriel-lisboa-azion
gabriel-lisboa-azion changed the base branch from main to chore/ENG-47606-documentation-categoryAugust 22, 2026 14:11
@isaque-bock-azion
isaque-bock-azion changed the base branch from chore/ENG-47606-documentation-category to feat/webkit-documentationAugust 24, 2026 13:04
@isaque-bock-azion
isaque-bock-azion merged commit e864f62 into feat/webkit-documentationAug 24, 2026
47 of 51 checks passed
@isaque-bock-azion
isaque-bock-azion deleted the feat/ENG-47561-docs-measure-and-mono-body branch August 24, 2026 13:14
isaque-bock-azion added a commit that referenced this pull request Aug 26, 2026
* chore: [ENG-47606] add the documentation component category (#904)
* feat(theme): [ENG-47561] add the docs measure and the prose + mono body registers (#905)
* chore: [ENG-47606] add the documentation component category
* feat(theme): [ENG-47561] add the docs measure and the mono body register
* feat(theme): [ENG-47606] add the prose body register
* fix(theme): [ENG-47606] widen the docs measure to the form column
The documentation column was capped at 752px, narrower than every other measure in the system. Docs pages carry code blocks, card grids and tables, none of which fit a prose measure, so the column now takes the same 1192px the form measure uses.
The token is defined here, so it is set here: the doc component stack carries the same value, and an add/add disagreement between the two made every PR in that stack unmergeable.
* feat(webkit): [ENG-47616] add DocOnThisPage, the documentation TOC rail (#918)
* chore: [ENG-47606] add the documentation component category
* feat(theme): [ENG-47561] add the docs measure and the mono body register
* feat(theme): [ENG-47606] add the prose body register
* feat(webkit): [ENG-47606] add DocProse, the documentation typography contract
* test(storybook): add the linux baselines for the DocProse stories
* fix(theme): [ENG-47606] widen the docs measure to the form column
The documentation column was capped at 752px, narrower than every other measure in the system. Docs pages carry code blocks, card grids and tables, none of which fit a prose measure, so the column now takes the same 1192px the form measure uses.
* fix(webkit): [ENG-47606] pin the prose section step and separate the heading registers
The section rung was a descending pair that read 32 on a phone and on a laptop and only reached 48 at xl, so the same page broke its sections at different gaps by window width. It is now a flat 56 to open and 48 to close, taken from the theme's primitive spacing steps.
Each heading level now names two registers instead of one. On one register per level the base column collapsed: h2 was 16 (the body's own size), h3 and h4 were both 14, and between 640 and 768 the page title was overtaken by its own h2.
Weight and leading are no longer restated here — every element takes its type token verbatim, so the ladder is size and space only.
* feat(webkit): [ENG-47607] add DocCallout, the documentation admonition
* test(storybook): add the linux baselines for the DocProse and DocCallout stories
* fix(webkit): [ENG-47607] step an inline callout link to the only other weight the system defines
font-medium is 500, a weight no text token in the theme uses — the set is light (300) and normal (400). An inline link inside a callout now steps to 400.
* feat(webkit): [ENG-47611] add DocCard, the documentation grid cell
* test(storybook): add the linux baselines for the documentation stories
* feat(webkit): [ENG-47612] add DocCardGroup, the documentation card grid
* test(storybook): add the linux baselines for the doccardgroup stories
* feat(webkit): [ENG-47609] add DocItem, the documentation list row
* test(storybook): add the linux baselines for the docitem stories
* feat(webkit): [ENG-47613] add DocSteps and DocStep, the documentation procedure list
* test(storybook): add the linux baselines for the docsteps stories
* feat(webkit): [ENG-47608] add DocFrame, the documentation figure with a full-screen view
* test(storybook): add the linux baselines for the docframe stories
* test(storybook): stop snapshotting the autoplaying DocFrame clip
* feat(webkit): [ENG-47615] add DocUpdate and the heading-nav composable
* test(storybook): add the linux baselines for the docupdate stories
* feat(webkit): [ENG-47561] add DocPrompt, the copyable agent prompt
* test(storybook): add the linux baselines for the docprompt stories
* feat(webkit): [ENG-47610] add DocTooltip, the inline gloss
* test(storybook): add the linux baselines for the doctooltip stories
* fix(webkit): [ENG-47610] let the tooltip headline take its type token's weight
The headline restated font-medium (500), a weight no text token in the theme defines. It now takes text-body-sm verbatim, and the step to the tip below it is carried by ink colour.
* feat(webkit): [ENG-47618] add DocPagination, the documentation prev/next pair
* test(storybook): add the linux baselines for the docpagination stories
* feat(webkit): [ENG-47617] add DocPageHeader, the documentation masthead
* test(storybook): add the linux baselines for the docpageheader stories
* fix(webkit): [ENG-47617] stop the page title being overtaken by its own h2
text-heading-lg runs 18/18/30, so between 640 and 768 the masthead title rendered smaller than the prose h2 under it (20). The title now takes the same descending pair the prose h1 uses — text-heading-2xl with text-heading-xl from sm — which reads 30/30/36 and stays above every heading below it. Closes the theme gap the spec recorded.
The deck stops restating font-light; text-body-md already resolves to 300.
* feat(webkit): [ENG-47616] add DocOnThisPage, the documentation TOC rail
* test(storybook): add the linux baselines for the doconthispage stories
* fix(webkit): [ENG-47616] mark the rail's opening line so a shell can align to it
A docs shell aligns the outline rail against the page bar beside it, and needed a stable thing to measure. data-toc-title marks the rail's first line so the shell reaches it without depending on this component's internal structure.
* style(webkit): sort the use-heading-nav imports
* test(storybook): regenerate the documentation baselines after the typography fixes
---------
Co-authored-by: Gab <gabriel.mendonca@azion.com>
isaque-bock-azion added a commit that referenced this pull request Aug 28, 2026
#939)
* feat(theme): add --container-site, the marketing site's shared measure (#923)
* feat(theme): add --container-site, the marketing site's shared measure
The azion.com pages are a single vertical frame: the nav bar, the hero's inner
column, every section below it and the footer all sit on the same centred
column, and the border-x that runs down the page is only continuous because
those four agree to the pixel. A width re-typed per band cannot hold that — the
frame is one decision, so it gets one place to live.
`5xl` (1192px) is a rung of the shared container ladder, not a number chosen for
this page. It is its own token rather than a reference to one of the console
measures because the two answer different questions: a console measure caps a
payload (a table, a form), while this one is the width of a frame whose four
bands have to line up, so retuning either must not move the other.
* refactor(webkit): point the footer column at --container-site (#928)
The footer's column named `--container-5xl` directly. On the marketing site the
nav bar, the hero band, the framed sections and this footer are ONE vertical
frame, and its side rules only meet because all four resolve to the same width —
so the measure has to be a token they share, not a rung each of them names.
Retuning it now moves them together, which a hardcoded rung here could not do.
No visual change: `--container-site` resolves to `--container-5xl`, which is
what the class already said.
* feat(webkit): add the resizable-panel layout primitive (#902)
* feat(webkit): add the resizable-panel layout primitive
A group of adjacent regions whose shared edges the reader can drag — the layout
behind a workspace, where an editor, a terminal under it and a preview beside it
are all content and the split between them is the reader's to set.
It exists because `Sidebar` was being used for both jobs. `Sidebar` is the app's
NAVIGATION rail: it collapses to icons, it hides, it is the thing you leave to go
somewhere. A terminal, a preview and a code editor are not places you navigate to
— they are the work — and dressing two of them as navigation rails produced a
screen with three "sidebars" and one document, on an axis `Sidebar` does not even
have: it has no horizontal edge, so a terminal under an editor had to hand-roll
its own splitter. This component owns the EDGE; `Sidebar` goes back to owning
navigation.
Composition: `ResizablePanel` with `Pane` and `Handle`, attached for dot-notation
and exported standalone. The compound is built by naming the static surface and
casting once rather than with `Object.assign`, whose inferred type walks each
part's local `interface Props` into the declaration and trips TS4082.
Ships the spec, a browser-mode test suite, the story, the `-root` and
per-sub-component exports, and a size budget.
* test(storybook): regenerate linux baselines for resizable-panel
Adds the 24 snapshots for the four new ResizablePanel stories across the 6-mode
matrix. Generated by the Storybook Regenerate Baseline workflow on this branch's
HEAD — baselines are ubuntu-only, so a local run would not match the folder the
visual gate compares against.
No existing snapshot is modified or deleted; the component is additive.
* test(webkit): compose resizable-panel through its named sub-component exports
Every other composition suite in the package renders through the named exports
(`AccordionItem`, `DropdownTrigger`, …) and checks dot-notation once, as an
identity assertion against those same exports. This file was the only one that
rendered through the root's statics — `h(ResizablePanel.Pane, …)` — which made
static analysis flag eight unguarded member accesses: a `.vue` default export is
generated by the SFC compiler, so the analyzer sees no `export default`, reads
the imported binding as undefined, and reports every `.Pane` / `.Handle` read on
it.
Compose through `ResizablePanelPane` / `ResizablePanelHandle` instead, and keep
the compound contract in the one place it is the subject rather than the vehicle,
asserting identity against those exports through a guarded read.
Same nine tests, same behaviour covered — the dot-notation contract is now
verified more tightly than `toBeTruthy` did.
---------
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* feat(webkit): give Select, MultiSelect and Dropdown one shared leading column (#935)
A list that mixes iconed and icon-less options used to run two label edges: an
option with a glyph indented its text, one without it did not, so the eye had no
column to read down. Fixing it per-option is not possible — no option knows
whether any sibling has an icon.
So the reservation is made by the LIST, with a self-triggering `:has()` rule.
Every option renders a fixed leading box and marks itself `data-leading` only
when it actually has a glyph; the box is `hidden` and turns back on under
`group-has-[[data-leading]]/options:flex`. `:has()` matches the marker even
while the box is hidden, so one iconed option is enough to open the column for
the whole list — and a list with no icons at all reserves nothing and pays no
indent. The same `group/options` marker is on all three panels, so Select,
MultiSelect and Dropdown behave identically.
The group label is now OPTIONAL, and sentence-case `text-label-sm` rather than
an uppercase overline: an unlabelled group is a grouping the reader does not
need named, and an overline read as a second heading level inside a panel that
has none.
Group rhythm is `--spacing-sm` above every group but the first — the same step
CommandMenu already puts between two bare groups. Dropdown keeps its hairline
and Select does not: the Figma frame for the Select panel separates groups by
space alone. Dropdown's divider moves to the group root, which spans the panel's
full inner width, so it reads edge-to-edge like the panel's own border; the
horizontal inset the rows need moves onto the content wrapper.
Panels also gain top/bottom slots for content that is not an option (a search
row, a footer action), and the panel's vertical padding steps up one so the
first and last row do not crowd its border.
Includes the 10 dropdown visual baselines this moves.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* fix(icons): resolve currentColor per theme in the colored-icon CSS (#930)
Astro's "A" shipped invisible on every light-mode surface.
Some brand logos are only partly colored: Astro is a fixed red-to-magenta
gradient swoosh under an "A" that is the brand's INK — black on a light
background, white on a dark one. That part is authored as `fill="currentColor"`,
which is what `validate-svg.mjs` asks for, and it resolves correctly wherever
the SVG is INLINED (dist/color-catalog.json → the gallery, Storybook).
A `background-image` is a separate document, so it inherits nothing from the
element painting it. `currentColor` there resolves against the SVG's own root,
not the consumer's text color, and no amount of `color` on the `<i>` reaches it.
Left alone it lands on the UA default and the icon disappears in one theme —
which is what happened: `ai-astro.svg` had the "A" hardcoded `#FAFAFA`, so it
was white-on-white everywhere in light mode.
So the generator resolves it. An icon containing `currentColor` is emitted
twice: the base rule with the light ink, and an override with the dark ink under
the same selectors @aziontech/theme uses for its dark tokens
(`[data-theme=dark]`, `.dark`, `.azion.azion-dark`). The override's extra
selector out-specifies the base, so source order does not decide the winner.
The inks are literals (`#141414` / `#FAFAFA`) rather than `var(--text-default)`
by necessity — same document isolation — and are the theme's own values for that
token in each mode. A fully-colored logo with no ink parts still emits one rule,
exactly as before: 17 colored icons, 1 theme-aware.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* docs(webkit): ship the webkit-create-surface and webkit-errors skills (#895)
* docs(webkit): ship the webkit-create-surface and webkit-errors skills
webkit-create-surface fixes where a create or settings form lives — a first-level
resource creates on a page, anything created inside a resource opens a drawer,
nothing creates in a dialog — plus the anatomy all of them share, what belongs
behind the collapsed Advanced band, and the commit model each surface uses.
webkit-errors is the one decision table for where a failure goes: client-side
validation on the field, a server rejection scoped to one field as a Message in
that section, an unscoped request failure as a toast with Retry, the signed-out
screens where auth never toasts, and the async failure that outlives its screen.
Both join the CLI bundle and the ui-craft routing table; webkit-form and
webkit-ui-states now hand their error rows off to webkit-errors.
* docs(webkit): ship the webkit-microcopy consumer skill (#931)
The writing rules for Azion product interfaces, as a skill the adoption toolkit
ships — so a consuming project's agent writes the same strings the console does
instead of inventing a house style per screen.
It fixes the things that were being decided per-PR: punctuation and
capitalization (no em dash, no ampersand, no parentheses in labels, no bold in
running text, sentence case with Azion product names capitalized), which surface
each kind of copy belongs on (a label names, a description constrains, a Message
explains impact, a Popover defines, a Tag states), one word per concept (Select
not Pick, Application not Edge Application, and no `edge` as a prefix or a
location), navigation and IA naming, and copy that survives every breakpoint.
Registered in the three places a consumer skill has to be registered or it never
reaches a project: the CLI's file list (`plan.js`), the UI-craft pack in
`plan.test.mjs` (now 18 skills), and the routing index in `CLAUDE.fragment.md`,
where it gets its own **Words** line — it is the only skill about strings rather
than structure, and it applies during every other one.
Distinct from the generic `content-microcopy` the plan drops: that one is
house-agnostic, this one carries Azion's own vocabulary.
---------
Co-authored-by: Isaque Böck <isaque.santos@azion.com>
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* feat(theme): add the page-enter and content-enter arrival animations (#896)
* feat(theme): add the page-enter and content-enter arrival animations
The two entrances an app shell needs, as a pair. page-enter is the route
transition: it starts one --layout-boundary-inline to the LEFT of where it lands
and travels right while fading up, so a page reads as arriving FROM the nav row
that was clicked — a plain fade has no origin and a right-side arrival reads as
going back. content-enter is content settling inside a page already on screen, so
it rises a hair instead of travelling, and takes a --content-enter-delay stagger
(with backwards fill, or a delayed follower would paint its landed state first).
Neither fills forwards: page-enter's box is usually the scroll container, and a
lingering translate would make it a containing block for any fixed descendant.
* docs(webkit): teach the two arrival animations in webkit-motion-polish
Documents both utilities and their knobs, and the failure they exist to prevent:
a page arrives once, so a mount-time entrance inside a page whose route
transition is still running lockesteps with it into one diagonal move (identical
opacities frame for frame is the tell). Adds that shape and the two-shells-per-
screen variant to the silent-failure table. catalog.json rebuilt so the two
animations reach the MCP and validate-spec-compliance.
---------
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* docs: rewrite add-animation for the CSS-first theme (#897)
The steps still described the deleted v3 plugin (semantic/animations.js,
addUtilities/addComponents, the camelCase keyframe object form). An animation is
now two edits under primitives/animations/ — the shorthand in animate.js, which
is what makes animate-<name> a utility at all, and the CSS-string keyframes in
keyframes.js, which Tailwind v4 does NOT generate — compiled by build:tokens,
which must run before catalog:build or nothing renders.
Also records the three things the two new arrivals taught: a per-instance value
rides a var() with a token default inside the keyframe, a stagger rides a var()
delay with backwards fill, and fill is a decision — forwards leaves a translate
that makes the element a containing block for any fixed descendant.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* fix(webkit): give table cells block padding so tall content can breathe (#899)
A cell was `min-h-12` with horizontal padding only. For a single-line cell that
is invisible — the 48px minimum decides the height on its own against ~20px of
text — so every existing row renders exactly as before.
It only shows on a cell whose content is TALLER than the minimum: a wrapped list
of tags, a two-line summary. There the height came from the content alone, which
put the first and last lines flush against the row's borders with no breathing
room at all.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* docs(webkit): bring webkit-lists onto the current list-page shape (#929)
The shipped consumer skill still described the list page as it was before three
decisions landed, so a consumer following it built a page the product no longer
looks like:
- A first-level module index opens with a `PageHeading` again, and the module's
create action lives in it. The shell draws the breadcrumb only from the second
crumb up, so at first level the heading is the ONLY place the module's name
appears in the content.
- The size ladder is the hierarchy: the heading's action is the page's one
`large` (40px) control and the whole controls row is `medium` (32px). At 40px
both rows competed and the create button stopped being the page's answer.
- The controls row is narrowing only — `#actions` there is for an action on the
LISTING, not the page's own.
`last_updated` moves to 2026-08-24.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* feat: webkit documentation (#919)
* chore: [ENG-47606] add the documentation component category (#904)
* feat(theme): [ENG-47561] add the docs measure and the prose + mono body registers (#905)
* chore: [ENG-47606] add the documentation component category
* feat(theme): [ENG-47561] add the docs measure and the mono body register
* feat(theme): [ENG-47606] add the prose body register
* fix(theme): [ENG-47606] widen the docs measure to the form column
The documentation column was capped at 752px, narrower than every other measure in the system. Docs pages carry code blocks, card grids and tables, none of which fit a prose measure, so the column now takes the same 1192px the form measure uses.
The token is defined here, so it is set here: the doc component stack carries the same value, and an add/add disagreement between the two made every PR in that stack unmergeable.
* feat(webkit): [ENG-47616] add DocOnThisPage, the documentation TOC rail (#918)
* chore: [ENG-47606] add the documentation component category
* feat(theme): [ENG-47561] add the docs measure and the mono body register
* feat(theme): [ENG-47606] add the prose body register
* feat(webkit): [ENG-47606] add DocProse, the documentation typography contract
* test(storybook): add the linux baselines for the DocProse stories
* fix(theme): [ENG-47606] widen the docs measure to the form column
The documentation column was capped at 752px, narrower than every other measure in the system. Docs pages carry code blocks, card grids and tables, none of which fit a prose measure, so the column now takes the same 1192px the form measure uses.
* fix(webkit): [ENG-47606] pin the prose section step and separate the heading registers
The section rung was a descending pair that read 32 on a phone and on a laptop and only reached 48 at xl, so the same page broke its sections at different gaps by window width. It is now a flat 56 to open and 48 to close, taken from the theme's primitive spacing steps.
Each heading level now names two registers instead of one. On one register per level the base column collapsed: h2 was 16 (the body's own size), h3 and h4 were both 14, and between 640 and 768 the page title was overtaken by its own h2.
Weight and leading are no longer restated here — every element takes its type token verbatim, so the ladder is size and space only.
* feat(webkit): [ENG-47607] add DocCallout, the documentation admonition
* test(storybook): add the linux baselines for the DocProse and DocCallout stories
* fix(webkit): [ENG-47607] step an inline callout link to the only other weight the system defines
font-medium is 500, a weight no text token in the theme uses — the set is light (300) and normal (400). An inline link inside a callout now steps to 400.
* feat(webkit): [ENG-47611] add DocCard, the documentation grid cell
* test(storybook): add the linux baselines for the documentation stories
* feat(webkit): [ENG-47612] add DocCardGroup, the documentation card grid
* test(storybook): add the linux baselines for the doccardgroup stories
* feat(webkit): [ENG-47609] add DocItem, the documentation list row
* test(storybook): add the linux baselines for the docitem stories
* feat(webkit): [ENG-47613] add DocSteps and DocStep, the documentation procedure list
* test(storybook): add the linux baselines for the docsteps stories
* feat(webkit): [ENG-47608] add DocFrame, the documentation figure with a full-screen view
* test(storybook): add the linux baselines for the docframe stories
* test(storybook): stop snapshotting the autoplaying DocFrame clip
* feat(webkit): [ENG-47615] add DocUpdate and the heading-nav composable
* test(storybook): add the linux baselines for the docupdate stories
* feat(webkit): [ENG-47561] add DocPrompt, the copyable agent prompt
* test(storybook): add the linux baselines for the docprompt stories
* feat(webkit): [ENG-47610] add DocTooltip, the inline gloss
* test(storybook): add the linux baselines for the doctooltip stories
* fix(webkit): [ENG-47610] let the tooltip headline take its type token's weight
The headline restated font-medium (500), a weight no text token in the theme defines. It now takes text-body-sm verbatim, and the step to the tip below it is carried by ink colour.
* feat(webkit): [ENG-47618] add DocPagination, the documentation prev/next pair
* test(storybook): add the linux baselines for the docpagination stories
* feat(webkit): [ENG-47617] add DocPageHeader, the documentation masthead
* test(storybook): add the linux baselines for the docpageheader stories
* fix(webkit): [ENG-47617] stop the page title being overtaken by its own h2
text-heading-lg runs 18/18/30, so between 640 and 768 the masthead title rendered smaller than the prose h2 under it (20). The title now takes the same descending pair the prose h1 uses — text-heading-2xl with text-heading-xl from sm — which reads 30/30/36 and stays above every heading below it. Closes the theme gap the spec recorded.
The deck stops restating font-light; text-body-md already resolves to 300.
* feat(webkit): [ENG-47616] add DocOnThisPage, the documentation TOC rail
* test(storybook): add the linux baselines for the doconthispage stories
* fix(webkit): [ENG-47616] mark the rail's opening line so a shell can align to it
A docs shell aligns the outline rail against the page bar beside it, and needed a stable thing to measure. data-toc-title marks the rail's first line so the shell reaches it without depending on this component's internal structure.
* style(webkit): sort the use-heading-nav imports
* test(storybook): regenerate the documentation baselines after the typography fixes
---------
Co-authored-by: Gab <gabriel.mendonca@azion.com>
* fix(webkit): copy-button tooltip, card-box header inset, flow attrs (#936)
* fix(webkit): copy-button tooltip, card-box header inset, flow attrs
Three unrelated small fixes.
COPY-BUTTON COMPOSES TOOLTIP. An icon-only button says nothing to a pointer user
until it is hovered, so the glyph was unlabelled in practice — and the
"Copied" confirmation existed only as an accessible-name swap, announced to a
screen reader and invisible to everyone else. One string now names the control
AND labels its tooltip, so the two can never disagree: `ariaLabel` while idle,
`copiedLabel` for the two seconds after a write. The confirmation is visible
because it rides the same state.
CARD-BOX HEADER INSET `sm` → `md`. The header's horizontal padding now matches
the content's `p-(--spacing-md)` and the footer's `px-(--spacing-md)`, so the
title sits on the same vertical line as the first thing under it — and, in a
flush card, as the rows of whatever fills it (ItemList, Accordion and LogView
are all inset `--spacing-md`). At `--spacing-sm` every card header sat 4px left
of its own body.
FLOW FORWARDS `$attrs`. It declared `inheritAttrs: false` and then never spread
`$attrs` on its root, merging only `attrs.class`. Everything else a consumer
passed — `id`, `aria-*`, `data-*`, listeners — was silently dropped, which
`.claude/rules/root-element.md` exists to prevent. Same shape as its siblings
now: `v-bind="$attrs"` on the real root, with `cn` still merging the class.
* test(storybook): regenerate the card-box baselines for the header inset
* chore(webkit): raise the doc-prompt budget for the copy-button tooltip
DocPrompt composes CopyButton, which now wraps its IconButton in the
Tooltip overlay so the icon-only control is legible on hover. The
overlay machinery raises the measured entry from 6.20 KB to 8.61 KB;
the budget moves to 9 KB to carry that composition with headroom.
---------
Co-authored-by: Isaque Böck <isaque.santos@azion.com>
* fix(webkit): emit navigate when a collapsed breadcrumb crumb is picked
`select` is the Dropdown's event, not the option's: `Dropdown.Option`
forwards its activation to the root through the injected context, and the
root is what emits `select(event, value)`. The listener sat on the option,
where it never fired — so picking a collapsed crumb below `md` did
nothing at all. The menu closed and the reader stayed on the page. That is
the whole trail on a phone, since the collapsed shape is the only one a
narrow bar renders.
The test that covered this was skipped as a harness limitation ("@select
does not fire under test interaction"). It was not a harness limitation —
it was this bug, reproducing exactly as a real browser did. The listener
moved to the root and the test is un-skipped.
---------
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
Co-authored-by: Isaque Böck <isaque.santos@azion.com>
isaque-bock-azion added a commit that referenced this pull request Aug 28, 2026
* feat(webkit): let a table column declare a minWidth floor
A bounded column — a chip list, a date, a count, an id — could only be sized by
a fixed `width`, and a fixed width is a bet in both directions: guess high and
the column reserves space the rest of the row needs, guess low and the content
inside it truncates (a chip list ends up as `SQL In…`). `minWidth` only has to
be right about the minimum: the column resolves to the widest of that floor and
what it actually holds — its header, with the sort affordance, and every
rendered body cell. `width` still wins when both are set, so an explicit width
remains the consumer overriding the measurement outright.
ONE width is resolved per column and applied to the header and to every body
cell, which keeps the invariant fixed widths exist for: the column resolves
identically in every row. A per-cell `min-width` would let each row size to its
own content and drift the header away from the body.
It has to be measured, because a flex row cannot express "as wide as the widest
cell in this column" — every row is its own flex container and knows nothing
about its siblings. The measurement is the same clone-based one the resize floor
already uses, so a column's auto width and its drag floor can never disagree.
Three things the measurement has to respect:
- Debounced (32ms). The rendered content changes in bursts — a sort, a page
turn, a column toggled back on — and measuring reads layout, so an
un-coalesced version would reflow per keystroke of a filter.
- Skipped while `loading`. Skeleton rows are not the content; measuring against
them would size every column to a placeholder and never grow it back.
- Re-run on `document.fonts.ready`. Text width is font-dependent and the first
paint can happen on the fallback face.
`autoWidths` joins the row `v-memo` so a resolved width actually reaches the
body, and feeds `frozenWidthOf` so a frozen `minWidth` column contributes its
real width to a neighbour's sticky offset.
* test(storybook): regenerate the table baselines for the column min-width
* fix(webkit): emit navigate when a collapsed breadcrumb crumb is picked (#939)
* feat(theme): add --container-site, the marketing site's shared measure (#923)
* feat(theme): add --container-site, the marketing site's shared measure
The azion.com pages are a single vertical frame: the nav bar, the hero's inner
column, every section below it and the footer all sit on the same centred
column, and the border-x that runs down the page is only continuous because
those four agree to the pixel. A width re-typed per band cannot hold that — the
frame is one decision, so it gets one place to live.
`5xl` (1192px) is a rung of the shared container ladder, not a number chosen for
this page. It is its own token rather than a reference to one of the console
measures because the two answer different questions: a console measure caps a
payload (a table, a form), while this one is the width of a frame whose four
bands have to line up, so retuning either must not move the other.
* refactor(webkit): point the footer column at --container-site (#928)
The footer's column named `--container-5xl` directly. On the marketing site the
nav bar, the hero band, the framed sections and this footer are ONE vertical
frame, and its side rules only meet because all four resolve to the same width —
so the measure has to be a token they share, not a rung each of them names.
Retuning it now moves them together, which a hardcoded rung here could not do.
No visual change: `--container-site` resolves to `--container-5xl`, which is
what the class already said.
* feat(webkit): add the resizable-panel layout primitive (#902)
* feat(webkit): add the resizable-panel layout primitive
A group of adjacent regions whose shared edges the reader can drag — the layout
behind a workspace, where an editor, a terminal under it and a preview beside it
are all content and the split between them is the reader's to set.
It exists because `Sidebar` was being used for both jobs. `Sidebar` is the app's
NAVIGATION rail: it collapses to icons, it hides, it is the thing you leave to go
somewhere. A terminal, a preview and a code editor are not places you navigate to
— they are the work — and dressing two of them as navigation rails produced a
screen with three "sidebars" and one document, on an axis `Sidebar` does not even
have: it has no horizontal edge, so a terminal under an editor had to hand-roll
its own splitter. This component owns the EDGE; `Sidebar` goes back to owning
navigation.
Composition: `ResizablePanel` with `Pane` and `Handle`, attached for dot-notation
and exported standalone. The compound is built by naming the static surface and
casting once rather than with `Object.assign`, whose inferred type walks each
part's local `interface Props` into the declaration and trips TS4082.
Ships the spec, a browser-mode test suite, the story, the `-root` and
per-sub-component exports, and a size budget.
* test(storybook): regenerate linux baselines for resizable-panel
Adds the 24 snapshots for the four new ResizablePanel stories across the 6-mode
matrix. Generated by the Storybook Regenerate Baseline workflow on this branch's
HEAD — baselines are ubuntu-only, so a local run would not match the folder the
visual gate compares against.
No existing snapshot is modified or deleted; the component is additive.
* test(webkit): compose resizable-panel through its named sub-component exports
Every other composition suite in the package renders through the named exports
(`AccordionItem`, `DropdownTrigger`, …) and checks dot-notation once, as an
identity assertion against those same exports. This file was the only one that
rendered through the root's statics — `h(ResizablePanel.Pane, …)` — which made
static analysis flag eight unguarded member accesses: a `.vue` default export is
generated by the SFC compiler, so the analyzer sees no `export default`, reads
the imported binding as undefined, and reports every `.Pane` / `.Handle` read on
it.
Compose through `ResizablePanelPane` / `ResizablePanelHandle` instead, and keep
the compound contract in the one place it is the subject rather than the vehicle,
asserting identity against those exports through a guarded read.
Same nine tests, same behaviour covered — the dot-notation contract is now
verified more tightly than `toBeTruthy` did.
---------
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* feat(webkit): give Select, MultiSelect and Dropdown one shared leading column (#935)
A list that mixes iconed and icon-less options used to run two label edges: an
option with a glyph indented its text, one without it did not, so the eye had no
column to read down. Fixing it per-option is not possible — no option knows
whether any sibling has an icon.
So the reservation is made by the LIST, with a self-triggering `:has()` rule.
Every option renders a fixed leading box and marks itself `data-leading` only
when it actually has a glyph; the box is `hidden` and turns back on under
`group-has-[[data-leading]]/options:flex`. `:has()` matches the marker even
while the box is hidden, so one iconed option is enough to open the column for
the whole list — and a list with no icons at all reserves nothing and pays no
indent. The same `group/options` marker is on all three panels, so Select,
MultiSelect and Dropdown behave identically.
The group label is now OPTIONAL, and sentence-case `text-label-sm` rather than
an uppercase overline: an unlabelled group is a grouping the reader does not
need named, and an overline read as a second heading level inside a panel that
has none.
Group rhythm is `--spacing-sm` above every group but the first — the same step
CommandMenu already puts between two bare groups. Dropdown keeps its hairline
and Select does not: the Figma frame for the Select panel separates groups by
space alone. Dropdown's divider moves to the group root, which spans the panel's
full inner width, so it reads edge-to-edge like the panel's own border; the
horizontal inset the rows need moves onto the content wrapper.
Panels also gain top/bottom slots for content that is not an option (a search
row, a footer action), and the panel's vertical padding steps up one so the
first and last row do not crowd its border.
Includes the 10 dropdown visual baselines this moves.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* fix(icons): resolve currentColor per theme in the colored-icon CSS (#930)
Astro's "A" shipped invisible on every light-mode surface.
Some brand logos are only partly colored: Astro is a fixed red-to-magenta
gradient swoosh under an "A" that is the brand's INK — black on a light
background, white on a dark one. That part is authored as `fill="currentColor"`,
which is what `validate-svg.mjs` asks for, and it resolves correctly wherever
the SVG is INLINED (dist/color-catalog.json → the gallery, Storybook).
A `background-image` is a separate document, so it inherits nothing from the
element painting it. `currentColor` there resolves against the SVG's own root,
not the consumer's text color, and no amount of `color` on the `<i>` reaches it.
Left alone it lands on the UA default and the icon disappears in one theme —
which is what happened: `ai-astro.svg` had the "A" hardcoded `#FAFAFA`, so it
was white-on-white everywhere in light mode.
So the generator resolves it. An icon containing `currentColor` is emitted
twice: the base rule with the light ink, and an override with the dark ink under
the same selectors @aziontech/theme uses for its dark tokens
(`[data-theme=dark]`, `.dark`, `.azion.azion-dark`). The override's extra
selector out-specifies the base, so source order does not decide the winner.
The inks are literals (`#141414` / `#FAFAFA`) rather than `var(--text-default)`
by necessity — same document isolation — and are the theme's own values for that
token in each mode. A fully-colored logo with no ink parts still emits one rule,
exactly as before: 17 colored icons, 1 theme-aware.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* docs(webkit): ship the webkit-create-surface and webkit-errors skills (#895)
* docs(webkit): ship the webkit-create-surface and webkit-errors skills
webkit-create-surface fixes where a create or settings form lives — a first-level
resource creates on a page, anything created inside a resource opens a drawer,
nothing creates in a dialog — plus the anatomy all of them share, what belongs
behind the collapsed Advanced band, and the commit model each surface uses.
webkit-errors is the one decision table for where a failure goes: client-side
validation on the field, a server rejection scoped to one field as a Message in
that section, an unscoped request failure as a toast with Retry, the signed-out
screens where auth never toasts, and the async failure that outlives its screen.
Both join the CLI bundle and the ui-craft routing table; webkit-form and
webkit-ui-states now hand their error rows off to webkit-errors.
* docs(webkit): ship the webkit-microcopy consumer skill (#931)
The writing rules for Azion product interfaces, as a skill the adoption toolkit
ships — so a consuming project's agent writes the same strings the console does
instead of inventing a house style per screen.
It fixes the things that were being decided per-PR: punctuation and
capitalization (no em dash, no ampersand, no parentheses in labels, no bold in
running text, sentence case with Azion product names capitalized), which surface
each kind of copy belongs on (a label names, a description constrains, a Message
explains impact, a Popover defines, a Tag states), one word per concept (Select
not Pick, Application not Edge Application, and no `edge` as a prefix or a
location), navigation and IA naming, and copy that survives every breakpoint.
Registered in the three places a consumer skill has to be registered or it never
reaches a project: the CLI's file list (`plan.js`), the UI-craft pack in
`plan.test.mjs` (now 18 skills), and the routing index in `CLAUDE.fragment.md`,
where it gets its own **Words** line — it is the only skill about strings rather
than structure, and it applies during every other one.
Distinct from the generic `content-microcopy` the plan drops: that one is
house-agnostic, this one carries Azion's own vocabulary.
---------
Co-authored-by: Isaque Böck <isaque.santos@azion.com>
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* feat(theme): add the page-enter and content-enter arrival animations (#896)
* feat(theme): add the page-enter and content-enter arrival animations
The two entrances an app shell needs, as a pair. page-enter is the route
transition: it starts one --layout-boundary-inline to the LEFT of where it lands
and travels right while fading up, so a page reads as arriving FROM the nav row
that was clicked — a plain fade has no origin and a right-side arrival reads as
going back. content-enter is content settling inside a page already on screen, so
it rises a hair instead of travelling, and takes a --content-enter-delay stagger
(with backwards fill, or a delayed follower would paint its landed state first).
Neither fills forwards: page-enter's box is usually the scroll container, and a
lingering translate would make it a containing block for any fixed descendant.
* docs(webkit): teach the two arrival animations in webkit-motion-polish
Documents both utilities and their knobs, and the failure they exist to prevent:
a page arrives once, so a mount-time entrance inside a page whose route
transition is still running lockesteps with it into one diagonal move (identical
opacities frame for frame is the tell). Adds that shape and the two-shells-per-
screen variant to the silent-failure table. catalog.json rebuilt so the two
animations reach the MCP and validate-spec-compliance.
---------
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* docs: rewrite add-animation for the CSS-first theme (#897)
The steps still described the deleted v3 plugin (semantic/animations.js,
addUtilities/addComponents, the camelCase keyframe object form). An animation is
now two edits under primitives/animations/ — the shorthand in animate.js, which
is what makes animate-<name> a utility at all, and the CSS-string keyframes in
keyframes.js, which Tailwind v4 does NOT generate — compiled by build:tokens,
which must run before catalog:build or nothing renders.
Also records the three things the two new arrivals taught: a per-instance value
rides a var() with a token default inside the keyframe, a stagger rides a var()
delay with backwards fill, and fill is a decision — forwards leaves a translate
that makes the element a containing block for any fixed descendant.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* fix(webkit): give table cells block padding so tall content can breathe (#899)
A cell was `min-h-12` with horizontal padding only. For a single-line cell that
is invisible — the 48px minimum decides the height on its own against ~20px of
text — so every existing row renders exactly as before.
It only shows on a cell whose content is TALLER than the minimum: a wrapped list
of tags, a two-line summary. There the height came from the content alone, which
put the first and last lines flush against the row's borders with no breathing
room at all.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* docs(webkit): bring webkit-lists onto the current list-page shape (#929)
The shipped consumer skill still described the list page as it was before three
decisions landed, so a consumer following it built a page the product no longer
looks like:
- A first-level module index opens with a `PageHeading` again, and the module's
create action lives in it. The shell draws the breadcrumb only from the second
crumb up, so at first level the heading is the ONLY place the module's name
appears in the content.
- The size ladder is the hierarchy: the heading's action is the page's one
`large` (40px) control and the whole controls row is `medium` (32px). At 40px
both rows competed and the create button stopped being the page's answer.
- The controls row is narrowing only — `#actions` there is for an action on the
LISTING, not the page's own.
`last_updated` moves to 2026-08-24.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* feat: webkit documentation (#919)
* chore: [ENG-47606] add the documentation component category (#904)
* feat(theme): [ENG-47561] add the docs measure and the prose + mono body registers (#905)
* chore: [ENG-47606] add the documentation component category
* feat(theme): [ENG-47561] add the docs measure and the mono body register
* feat(theme): [ENG-47606] add the prose body register
* fix(theme): [ENG-47606] widen the docs measure to the form column
The documentation column was capped at 752px, narrower than every other measure in the system. Docs pages carry code blocks, card grids and tables, none of which fit a prose measure, so the column now takes the same 1192px the form measure uses.
The token is defined here, so it is set here: the doc component stack carries the same value, and an add/add disagreement between the two made every PR in that stack unmergeable.
* feat(webkit): [ENG-47616] add DocOnThisPage, the documentation TOC rail (#918)
* chore: [ENG-47606] add the documentation component category
* feat(theme): [ENG-47561] add the docs measure and the mono body register
* feat(theme): [ENG-47606] add the prose body register
* feat(webkit): [ENG-47606] add DocProse, the documentation typography contract
* test(storybook): add the linux baselines for the DocProse stories
* fix(theme): [ENG-47606] widen the docs measure to the form column
The documentation column was capped at 752px, narrower than every other measure in the system. Docs pages carry code blocks, card grids and tables, none of which fit a prose measure, so the column now takes the same 1192px the form measure uses.
* fix(webkit): [ENG-47606] pin the prose section step and separate the heading registers
The section rung was a descending pair that read 32 on a phone and on a laptop and only reached 48 at xl, so the same page broke its sections at different gaps by window width. It is now a flat 56 to open and 48 to close, taken from the theme's primitive spacing steps.
Each heading level now names two registers instead of one. On one register per level the base column collapsed: h2 was 16 (the body's own size), h3 and h4 were both 14, and between 640 and 768 the page title was overtaken by its own h2.
Weight and leading are no longer restated here — every element takes its type token verbatim, so the ladder is size and space only.
* feat(webkit): [ENG-47607] add DocCallout, the documentation admonition
* test(storybook): add the linux baselines for the DocProse and DocCallout stories
* fix(webkit): [ENG-47607] step an inline callout link to the only other weight the system defines
font-medium is 500, a weight no text token in the theme uses — the set is light (300) and normal (400). An inline link inside a callout now steps to 400.
* feat(webkit): [ENG-47611] add DocCard, the documentation grid cell
* test(storybook): add the linux baselines for the documentation stories
* feat(webkit): [ENG-47612] add DocCardGroup, the documentation card grid
* test(storybook): add the linux baselines for the doccardgroup stories
* feat(webkit): [ENG-47609] add DocItem, the documentation list row
* test(storybook): add the linux baselines for the docitem stories
* feat(webkit): [ENG-47613] add DocSteps and DocStep, the documentation procedure list
* test(storybook): add the linux baselines for the docsteps stories
* feat(webkit): [ENG-47608] add DocFrame, the documentation figure with a full-screen view
* test(storybook): add the linux baselines for the docframe stories
* test(storybook): stop snapshotting the autoplaying DocFrame clip
* feat(webkit): [ENG-47615] add DocUpdate and the heading-nav composable
* test(storybook): add the linux baselines for the docupdate stories
* feat(webkit): [ENG-47561] add DocPrompt, the copyable agent prompt
* test(storybook): add the linux baselines for the docprompt stories
* feat(webkit): [ENG-47610] add DocTooltip, the inline gloss
* test(storybook): add the linux baselines for the doctooltip stories
* fix(webkit): [ENG-47610] let the tooltip headline take its type token's weight
The headline restated font-medium (500), a weight no text token in the theme defines. It now takes text-body-sm verbatim, and the step to the tip below it is carried by ink colour.
* feat(webkit): [ENG-47618] add DocPagination, the documentation prev/next pair
* test(storybook): add the linux baselines for the docpagination stories
* feat(webkit): [ENG-47617] add DocPageHeader, the documentation masthead
* test(storybook): add the linux baselines for the docpageheader stories
* fix(webkit): [ENG-47617] stop the page title being overtaken by its own h2
text-heading-lg runs 18/18/30, so between 640 and 768 the masthead title rendered smaller than the prose h2 under it (20). The title now takes the same descending pair the prose h1 uses — text-heading-2xl with text-heading-xl from sm — which reads 30/30/36 and stays above every heading below it. Closes the theme gap the spec recorded.
The deck stops restating font-light; text-body-md already resolves to 300.
* feat(webkit): [ENG-47616] add DocOnThisPage, the documentation TOC rail
* test(storybook): add the linux baselines for the doconthispage stories
* fix(webkit): [ENG-47616] mark the rail's opening line so a shell can align to it
A docs shell aligns the outline rail against the page bar beside it, and needed a stable thing to measure. data-toc-title marks the rail's first line so the shell reaches it without depending on this component's internal structure.
* style(webkit): sort the use-heading-nav imports
* test(storybook): regenerate the documentation baselines after the typography fixes
---------
Co-authored-by: Gab <gabriel.mendonca@azion.com>
* fix(webkit): copy-button tooltip, card-box header inset, flow attrs (#936)
* fix(webkit): copy-button tooltip, card-box header inset, flow attrs
Three unrelated small fixes.
COPY-BUTTON COMPOSES TOOLTIP. An icon-only button says nothing to a pointer user
until it is hovered, so the glyph was unlabelled in practice — and the
"Copied" confirmation existed only as an accessible-name swap, announced to a
screen reader and invisible to everyone else. One string now names the control
AND labels its tooltip, so the two can never disagree: `ariaLabel` while idle,
`copiedLabel` for the two seconds after a write. The confirmation is visible
because it rides the same state.
CARD-BOX HEADER INSET `sm` → `md`. The header's horizontal padding now matches
the content's `p-(--spacing-md)` and the footer's `px-(--spacing-md)`, so the
title sits on the same vertical line as the first thing under it — and, in a
flush card, as the rows of whatever fills it (ItemList, Accordion and LogView
are all inset `--spacing-md`). At `--spacing-sm` every card header sat 4px left
of its own body.
FLOW FORWARDS `$attrs`. It declared `inheritAttrs: false` and then never spread
`$attrs` on its root, merging only `attrs.class`. Everything else a consumer
passed — `id`, `aria-*`, `data-*`, listeners — was silently dropped, which
`.claude/rules/root-element.md` exists to prevent. Same shape as its siblings
now: `v-bind="$attrs"` on the real root, with `cn` still merging the class.
* test(storybook): regenerate the card-box baselines for the header inset
* chore(webkit): raise the doc-prompt budget for the copy-button tooltip
DocPrompt composes CopyButton, which now wraps its IconButton in the
Tooltip overlay so the icon-only control is legible on hover. The
overlay machinery raises the measured entry from 6.20 KB to 8.61 KB;
the budget moves to 9 KB to carry that composition with headroom.
---------
Co-authored-by: Isaque Böck <isaque.santos@azion.com>
* fix(webkit): emit navigate when a collapsed breadcrumb crumb is picked
`select` is the Dropdown's event, not the option's: `Dropdown.Option`
forwards its activation to the root through the injected context, and the
root is what emits `select(event, value)`. The listener sat on the option,
where it never fired — so picking a collapsed crumb below `md` did
nothing at all. The menu closed and the reader stayed on the page. That is
the whole trail on a phone, since the collapsed shape is the only one a
narrow bar renders.
The test that covered this was skipped as a harness limitation ("@select
does not fire under test interaction"). It was not a harness limitation —
it was this bug, reproducing exactly as a real browser did. The listener
moved to the root and the test is un-skipped.
---------
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
Co-authored-by: Isaque Böck <isaque.santos@azion.com>
---------
Co-authored-by: Isaque dos Santos <isaque.santos@azion.com>
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@gabriel-lisboa-azion@isaque-bock-azion
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

feat(theme): [ENG-47561] add the docs measure and the prose + mono body registers - #905

Merged
isaque-bock-azion merged 3 commits into
feat/webkit-documentationfrom
feat/ENG-47561-docs-measure-and-mono-body
Aug 24, 2026
Merged

feat(theme): [ENG-47561] add the docs measure and the prose + mono body registers#905
isaque-bock-azion merged 3 commits into
feat/webkit-documentationfrom
feat/ENG-47561-docs-measure-and-mono-body

Conversation

@gabriel-lisboa-azion

@gabriel-lisboa-aziongabriel-lisboa-azion commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • text-body-prose-md — body copy at --leading-relaxed (1.625). The whole text-body-* set is snug (1.375), and the documentation prose ladder was fitted against a 26px line box, so prose needs its own register. Line-height belongs in the token: check-authoring's leading-raw check deliberately rejects a leading-* override even in token form.
  • text-body-code-sm — the mono body register. The label-code-* set is leading-none, built for one-row-per-line code; this one wraps. Required by DocPrompt.
  • layout-measure-docs (--container-2xl, 752px) + layout-column-docs, completing the measure family (column / focused / form / form-create / docs). Docs are the one column capped by typography rather than payload width. The utility is generated by COLUMN_MEASURE + the columnUtility factory, so two entries produce the whole class.

How to test

  1. node packages/theme/src/scripts/build-tokens.mjs then git diff --stat packages/theme/dist — expect no further change; the committed dist is already current.
  2. cd packages/theme && node --test "test/**/*.test.mjs" — 12/12 pass, including the zero-unit assertions over every emitted token.
  3. grep "text-body-prose-md-line-height\|text-body-md-line-height" packages/theme/dist/v4/globals.css — expect 1.625 and 1.375 respectively; that difference is the whole point of the new token.
  4. grep -A5 '@utility text-body-code-sm' packages/theme/dist/v4/globals.css and the same for layout-column-docs — each must emit a real rule body. A token that compiles to nothing is the silent failure mode here.
  5. grep "layout-measure':" packages/theme/src/tokens/semantic/layouts.data.js — must still read var(--container-7xl) / 1620px (see Notes).

Notes

  • Deliberately excluded: the working branch this was carved from also retunes layout-measure from --container-7xl (1620px) to --container-6xl (1388px), moving home from the focused measure to the data measure. That is a separate product decision that would have silently changed the width of every data page, so it is not here — layout-measure and layout-measure-focused keep main's values and main's prose.
  • Stacked on chore: [ENG-47606] add the documentation component category #904 (the documentation category), which DocProse also needs. Merge chore: [ENG-47606] add the documentation component category #904 first; this retargets automatically.
  • No component consumes layout-column-docs yet: DocProse deliberately has no measure of its own, so the consumer caps the column.
  • Related: ENG-47561, ENG-47606, and the sibling component issues.

Delivery

The Documentation family is delivered on the feature branch feat/docs-components-bundle, not piecemeal to main — so main takes the whole family as one merge, while review stays per-component.

Merge order, bottom-up — each PR's base is the one before it, and each retargets automatically as its parent lands:

#904#905#906#907#908#909#910#911#912#913#914#915#916#917#918feat/docs-components-bundlemain

#904the documentation category (standards only)
#905theme: the docs measure + the prose and mono body registers
#906#918DocProse · DocCallout · DocCard · DocCardGroup · DocItem · DocSteps + DocStep · DocFrame · DocUpdate · DocPrompt · DocTooltip · DocPagination · DocPageHeader · DocOnThisPage

@gabriel-lisboa-aziongabriel-lisboa-azion changed the title feat(theme): [ENG-47561] add the docs measure and the mono body registerfeat(theme): [ENG-47561] add the docs measure and the prose + mono body registersAug 22, 2026
@gabriel-lisboa-azion
gabriel-lisboa-azionforce-pushed the feat/ENG-47561-docs-measure-and-mono-body branch from d3d46c0 to bc6575cCompareAugust 22, 2026 14:11
@gabriel-lisboa-azion
gabriel-lisboa-azion changed the base branch from main to chore/ENG-47606-documentation-categoryAugust 22, 2026 14:11
@isaque-bock-azion
isaque-bock-azion changed the base branch from chore/ENG-47606-documentation-category to feat/webkit-documentationAugust 24, 2026 13:04
@isaque-bock-azion
isaque-bock-azion merged commit e864f62 into feat/webkit-documentationAug 24, 2026
47 of 51 checks passed
@isaque-bock-azion
isaque-bock-azion deleted the feat/ENG-47561-docs-measure-and-mono-body branch August 24, 2026 13:14
isaque-bock-azion added a commit that referenced this pull request Aug 26, 2026
* chore: [ENG-47606] add the documentation component category (#904)
* feat(theme): [ENG-47561] add the docs measure and the prose + mono body registers (#905)
* chore: [ENG-47606] add the documentation component category
* feat(theme): [ENG-47561] add the docs measure and the mono body register
* feat(theme): [ENG-47606] add the prose body register
* fix(theme): [ENG-47606] widen the docs measure to the form column
The documentation column was capped at 752px, narrower than every other measure in the system. Docs pages carry code blocks, card grids and tables, none of which fit a prose measure, so the column now takes the same 1192px the form measure uses.
The token is defined here, so it is set here: the doc component stack carries the same value, and an add/add disagreement between the two made every PR in that stack unmergeable.
* feat(webkit): [ENG-47616] add DocOnThisPage, the documentation TOC rail (#918)
* chore: [ENG-47606] add the documentation component category
* feat(theme): [ENG-47561] add the docs measure and the mono body register
* feat(theme): [ENG-47606] add the prose body register
* feat(webkit): [ENG-47606] add DocProse, the documentation typography contract
* test(storybook): add the linux baselines for the DocProse stories
* fix(theme): [ENG-47606] widen the docs measure to the form column
The documentation column was capped at 752px, narrower than every other measure in the system. Docs pages carry code blocks, card grids and tables, none of which fit a prose measure, so the column now takes the same 1192px the form measure uses.
* fix(webkit): [ENG-47606] pin the prose section step and separate the heading registers
The section rung was a descending pair that read 32 on a phone and on a laptop and only reached 48 at xl, so the same page broke its sections at different gaps by window width. It is now a flat 56 to open and 48 to close, taken from the theme's primitive spacing steps.
Each heading level now names two registers instead of one. On one register per level the base column collapsed: h2 was 16 (the body's own size), h3 and h4 were both 14, and between 640 and 768 the page title was overtaken by its own h2.
Weight and leading are no longer restated here — every element takes its type token verbatim, so the ladder is size and space only.
* feat(webkit): [ENG-47607] add DocCallout, the documentation admonition
* test(storybook): add the linux baselines for the DocProse and DocCallout stories
* fix(webkit): [ENG-47607] step an inline callout link to the only other weight the system defines
font-medium is 500, a weight no text token in the theme uses — the set is light (300) and normal (400). An inline link inside a callout now steps to 400.
* feat(webkit): [ENG-47611] add DocCard, the documentation grid cell
* test(storybook): add the linux baselines for the documentation stories
* feat(webkit): [ENG-47612] add DocCardGroup, the documentation card grid
* test(storybook): add the linux baselines for the doccardgroup stories
* feat(webkit): [ENG-47609] add DocItem, the documentation list row
* test(storybook): add the linux baselines for the docitem stories
* feat(webkit): [ENG-47613] add DocSteps and DocStep, the documentation procedure list
* test(storybook): add the linux baselines for the docsteps stories
* feat(webkit): [ENG-47608] add DocFrame, the documentation figure with a full-screen view
* test(storybook): add the linux baselines for the docframe stories
* test(storybook): stop snapshotting the autoplaying DocFrame clip
* feat(webkit): [ENG-47615] add DocUpdate and the heading-nav composable
* test(storybook): add the linux baselines for the docupdate stories
* feat(webkit): [ENG-47561] add DocPrompt, the copyable agent prompt
* test(storybook): add the linux baselines for the docprompt stories
* feat(webkit): [ENG-47610] add DocTooltip, the inline gloss
* test(storybook): add the linux baselines for the doctooltip stories
* fix(webkit): [ENG-47610] let the tooltip headline take its type token's weight
The headline restated font-medium (500), a weight no text token in the theme defines. It now takes text-body-sm verbatim, and the step to the tip below it is carried by ink colour.
* feat(webkit): [ENG-47618] add DocPagination, the documentation prev/next pair
* test(storybook): add the linux baselines for the docpagination stories
* feat(webkit): [ENG-47617] add DocPageHeader, the documentation masthead
* test(storybook): add the linux baselines for the docpageheader stories
* fix(webkit): [ENG-47617] stop the page title being overtaken by its own h2
text-heading-lg runs 18/18/30, so between 640 and 768 the masthead title rendered smaller than the prose h2 under it (20). The title now takes the same descending pair the prose h1 uses — text-heading-2xl with text-heading-xl from sm — which reads 30/30/36 and stays above every heading below it. Closes the theme gap the spec recorded.
The deck stops restating font-light; text-body-md already resolves to 300.
* feat(webkit): [ENG-47616] add DocOnThisPage, the documentation TOC rail
* test(storybook): add the linux baselines for the doconthispage stories
* fix(webkit): [ENG-47616] mark the rail's opening line so a shell can align to it
A docs shell aligns the outline rail against the page bar beside it, and needed a stable thing to measure. data-toc-title marks the rail's first line so the shell reaches it without depending on this component's internal structure.
* style(webkit): sort the use-heading-nav imports
* test(storybook): regenerate the documentation baselines after the typography fixes
---------
Co-authored-by: Gab <gabriel.mendonca@azion.com>
isaque-bock-azion added a commit that referenced this pull request Aug 28, 2026
#939)
* feat(theme): add --container-site, the marketing site's shared measure (#923)
* feat(theme): add --container-site, the marketing site's shared measure
The azion.com pages are a single vertical frame: the nav bar, the hero's inner
column, every section below it and the footer all sit on the same centred
column, and the border-x that runs down the page is only continuous because
those four agree to the pixel. A width re-typed per band cannot hold that — the
frame is one decision, so it gets one place to live.
`5xl` (1192px) is a rung of the shared container ladder, not a number chosen for
this page. It is its own token rather than a reference to one of the console
measures because the two answer different questions: a console measure caps a
payload (a table, a form), while this one is the width of a frame whose four
bands have to line up, so retuning either must not move the other.
* refactor(webkit): point the footer column at --container-site (#928)
The footer's column named `--container-5xl` directly. On the marketing site the
nav bar, the hero band, the framed sections and this footer are ONE vertical
frame, and its side rules only meet because all four resolve to the same width —
so the measure has to be a token they share, not a rung each of them names.
Retuning it now moves them together, which a hardcoded rung here could not do.
No visual change: `--container-site` resolves to `--container-5xl`, which is
what the class already said.
* feat(webkit): add the resizable-panel layout primitive (#902)
* feat(webkit): add the resizable-panel layout primitive
A group of adjacent regions whose shared edges the reader can drag — the layout
behind a workspace, where an editor, a terminal under it and a preview beside it
are all content and the split between them is the reader's to set.
It exists because `Sidebar` was being used for both jobs. `Sidebar` is the app's
NAVIGATION rail: it collapses to icons, it hides, it is the thing you leave to go
somewhere. A terminal, a preview and a code editor are not places you navigate to
— they are the work — and dressing two of them as navigation rails produced a
screen with three "sidebars" and one document, on an axis `Sidebar` does not even
have: it has no horizontal edge, so a terminal under an editor had to hand-roll
its own splitter. This component owns the EDGE; `Sidebar` goes back to owning
navigation.
Composition: `ResizablePanel` with `Pane` and `Handle`, attached for dot-notation
and exported standalone. The compound is built by naming the static surface and
casting once rather than with `Object.assign`, whose inferred type walks each
part's local `interface Props` into the declaration and trips TS4082.
Ships the spec, a browser-mode test suite, the story, the `-root` and
per-sub-component exports, and a size budget.
* test(storybook): regenerate linux baselines for resizable-panel
Adds the 24 snapshots for the four new ResizablePanel stories across the 6-mode
matrix. Generated by the Storybook Regenerate Baseline workflow on this branch's
HEAD — baselines are ubuntu-only, so a local run would not match the folder the
visual gate compares against.
No existing snapshot is modified or deleted; the component is additive.
* test(webkit): compose resizable-panel through its named sub-component exports
Every other composition suite in the package renders through the named exports
(`AccordionItem`, `DropdownTrigger`, …) and checks dot-notation once, as an
identity assertion against those same exports. This file was the only one that
rendered through the root's statics — `h(ResizablePanel.Pane, …)` — which made
static analysis flag eight unguarded member accesses: a `.vue` default export is
generated by the SFC compiler, so the analyzer sees no `export default`, reads
the imported binding as undefined, and reports every `.Pane` / `.Handle` read on
it.
Compose through `ResizablePanelPane` / `ResizablePanelHandle` instead, and keep
the compound contract in the one place it is the subject rather than the vehicle,
asserting identity against those exports through a guarded read.
Same nine tests, same behaviour covered — the dot-notation contract is now
verified more tightly than `toBeTruthy` did.
---------
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* feat(webkit): give Select, MultiSelect and Dropdown one shared leading column (#935)
A list that mixes iconed and icon-less options used to run two label edges: an
option with a glyph indented its text, one without it did not, so the eye had no
column to read down. Fixing it per-option is not possible — no option knows
whether any sibling has an icon.
So the reservation is made by the LIST, with a self-triggering `:has()` rule.
Every option renders a fixed leading box and marks itself `data-leading` only
when it actually has a glyph; the box is `hidden` and turns back on under
`group-has-[[data-leading]]/options:flex`. `:has()` matches the marker even
while the box is hidden, so one iconed option is enough to open the column for
the whole list — and a list with no icons at all reserves nothing and pays no
indent. The same `group/options` marker is on all three panels, so Select,
MultiSelect and Dropdown behave identically.
The group label is now OPTIONAL, and sentence-case `text-label-sm` rather than
an uppercase overline: an unlabelled group is a grouping the reader does not
need named, and an overline read as a second heading level inside a panel that
has none.
Group rhythm is `--spacing-sm` above every group but the first — the same step
CommandMenu already puts between two bare groups. Dropdown keeps its hairline
and Select does not: the Figma frame for the Select panel separates groups by
space alone. Dropdown's divider moves to the group root, which spans the panel's
full inner width, so it reads edge-to-edge like the panel's own border; the
horizontal inset the rows need moves onto the content wrapper.
Panels also gain top/bottom slots for content that is not an option (a search
row, a footer action), and the panel's vertical padding steps up one so the
first and last row do not crowd its border.
Includes the 10 dropdown visual baselines this moves.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* fix(icons): resolve currentColor per theme in the colored-icon CSS (#930)
Astro's "A" shipped invisible on every light-mode surface.
Some brand logos are only partly colored: Astro is a fixed red-to-magenta
gradient swoosh under an "A" that is the brand's INK — black on a light
background, white on a dark one. That part is authored as `fill="currentColor"`,
which is what `validate-svg.mjs` asks for, and it resolves correctly wherever
the SVG is INLINED (dist/color-catalog.json → the gallery, Storybook).
A `background-image` is a separate document, so it inherits nothing from the
element painting it. `currentColor` there resolves against the SVG's own root,
not the consumer's text color, and no amount of `color` on the `<i>` reaches it.
Left alone it lands on the UA default and the icon disappears in one theme —
which is what happened: `ai-astro.svg` had the "A" hardcoded `#FAFAFA`, so it
was white-on-white everywhere in light mode.
So the generator resolves it. An icon containing `currentColor` is emitted
twice: the base rule with the light ink, and an override with the dark ink under
the same selectors @aziontech/theme uses for its dark tokens
(`[data-theme=dark]`, `.dark`, `.azion.azion-dark`). The override's extra
selector out-specifies the base, so source order does not decide the winner.
The inks are literals (`#141414` / `#FAFAFA`) rather than `var(--text-default)`
by necessity — same document isolation — and are the theme's own values for that
token in each mode. A fully-colored logo with no ink parts still emits one rule,
exactly as before: 17 colored icons, 1 theme-aware.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* docs(webkit): ship the webkit-create-surface and webkit-errors skills (#895)
* docs(webkit): ship the webkit-create-surface and webkit-errors skills
webkit-create-surface fixes where a create or settings form lives — a first-level
resource creates on a page, anything created inside a resource opens a drawer,
nothing creates in a dialog — plus the anatomy all of them share, what belongs
behind the collapsed Advanced band, and the commit model each surface uses.
webkit-errors is the one decision table for where a failure goes: client-side
validation on the field, a server rejection scoped to one field as a Message in
that section, an unscoped request failure as a toast with Retry, the signed-out
screens where auth never toasts, and the async failure that outlives its screen.
Both join the CLI bundle and the ui-craft routing table; webkit-form and
webkit-ui-states now hand their error rows off to webkit-errors.
* docs(webkit): ship the webkit-microcopy consumer skill (#931)
The writing rules for Azion product interfaces, as a skill the adoption toolkit
ships — so a consuming project's agent writes the same strings the console does
instead of inventing a house style per screen.
It fixes the things that were being decided per-PR: punctuation and
capitalization (no em dash, no ampersand, no parentheses in labels, no bold in
running text, sentence case with Azion product names capitalized), which surface
each kind of copy belongs on (a label names, a description constrains, a Message
explains impact, a Popover defines, a Tag states), one word per concept (Select
not Pick, Application not Edge Application, and no `edge` as a prefix or a
location), navigation and IA naming, and copy that survives every breakpoint.
Registered in the three places a consumer skill has to be registered or it never
reaches a project: the CLI's file list (`plan.js`), the UI-craft pack in
`plan.test.mjs` (now 18 skills), and the routing index in `CLAUDE.fragment.md`,
where it gets its own **Words** line — it is the only skill about strings rather
than structure, and it applies during every other one.
Distinct from the generic `content-microcopy` the plan drops: that one is
house-agnostic, this one carries Azion's own vocabulary.
---------
Co-authored-by: Isaque Böck <isaque.santos@azion.com>
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* feat(theme): add the page-enter and content-enter arrival animations (#896)
* feat(theme): add the page-enter and content-enter arrival animations
The two entrances an app shell needs, as a pair. page-enter is the route
transition: it starts one --layout-boundary-inline to the LEFT of where it lands
and travels right while fading up, so a page reads as arriving FROM the nav row
that was clicked — a plain fade has no origin and a right-side arrival reads as
going back. content-enter is content settling inside a page already on screen, so
it rises a hair instead of travelling, and takes a --content-enter-delay stagger
(with backwards fill, or a delayed follower would paint its landed state first).
Neither fills forwards: page-enter's box is usually the scroll container, and a
lingering translate would make it a containing block for any fixed descendant.
* docs(webkit): teach the two arrival animations in webkit-motion-polish
Documents both utilities and their knobs, and the failure they exist to prevent:
a page arrives once, so a mount-time entrance inside a page whose route
transition is still running lockesteps with it into one diagonal move (identical
opacities frame for frame is the tell). Adds that shape and the two-shells-per-
screen variant to the silent-failure table. catalog.json rebuilt so the two
animations reach the MCP and validate-spec-compliance.
---------
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* docs: rewrite add-animation for the CSS-first theme (#897)
The steps still described the deleted v3 plugin (semantic/animations.js,
addUtilities/addComponents, the camelCase keyframe object form). An animation is
now two edits under primitives/animations/ — the shorthand in animate.js, which
is what makes animate-<name> a utility at all, and the CSS-string keyframes in
keyframes.js, which Tailwind v4 does NOT generate — compiled by build:tokens,
which must run before catalog:build or nothing renders.
Also records the three things the two new arrivals taught: a per-instance value
rides a var() with a token default inside the keyframe, a stagger rides a var()
delay with backwards fill, and fill is a decision — forwards leaves a translate
that makes the element a containing block for any fixed descendant.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* fix(webkit): give table cells block padding so tall content can breathe (#899)
A cell was `min-h-12` with horizontal padding only. For a single-line cell that
is invisible — the 48px minimum decides the height on its own against ~20px of
text — so every existing row renders exactly as before.
It only shows on a cell whose content is TALLER than the minimum: a wrapped list
of tags, a two-line summary. There the height came from the content alone, which
put the first and last lines flush against the row's borders with no breathing
room at all.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* docs(webkit): bring webkit-lists onto the current list-page shape (#929)
The shipped consumer skill still described the list page as it was before three
decisions landed, so a consumer following it built a page the product no longer
looks like:
- A first-level module index opens with a `PageHeading` again, and the module's
create action lives in it. The shell draws the breadcrumb only from the second
crumb up, so at first level the heading is the ONLY place the module's name
appears in the content.
- The size ladder is the hierarchy: the heading's action is the page's one
`large` (40px) control and the whole controls row is `medium` (32px). At 40px
both rows competed and the create button stopped being the page's answer.
- The controls row is narrowing only — `#actions` there is for an action on the
LISTING, not the page's own.
`last_updated` moves to 2026-08-24.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* feat: webkit documentation (#919)
* chore: [ENG-47606] add the documentation component category (#904)
* feat(theme): [ENG-47561] add the docs measure and the prose + mono body registers (#905)
* chore: [ENG-47606] add the documentation component category
* feat(theme): [ENG-47561] add the docs measure and the mono body register
* feat(theme): [ENG-47606] add the prose body register
* fix(theme): [ENG-47606] widen the docs measure to the form column
The documentation column was capped at 752px, narrower than every other measure in the system. Docs pages carry code blocks, card grids and tables, none of which fit a prose measure, so the column now takes the same 1192px the form measure uses.
The token is defined here, so it is set here: the doc component stack carries the same value, and an add/add disagreement between the two made every PR in that stack unmergeable.
* feat(webkit): [ENG-47616] add DocOnThisPage, the documentation TOC rail (#918)
* chore: [ENG-47606] add the documentation component category
* feat(theme): [ENG-47561] add the docs measure and the mono body register
* feat(theme): [ENG-47606] add the prose body register
* feat(webkit): [ENG-47606] add DocProse, the documentation typography contract
* test(storybook): add the linux baselines for the DocProse stories
* fix(theme): [ENG-47606] widen the docs measure to the form column
The documentation column was capped at 752px, narrower than every other measure in the system. Docs pages carry code blocks, card grids and tables, none of which fit a prose measure, so the column now takes the same 1192px the form measure uses.
* fix(webkit): [ENG-47606] pin the prose section step and separate the heading registers
The section rung was a descending pair that read 32 on a phone and on a laptop and only reached 48 at xl, so the same page broke its sections at different gaps by window width. It is now a flat 56 to open and 48 to close, taken from the theme's primitive spacing steps.
Each heading level now names two registers instead of one. On one register per level the base column collapsed: h2 was 16 (the body's own size), h3 and h4 were both 14, and between 640 and 768 the page title was overtaken by its own h2.
Weight and leading are no longer restated here — every element takes its type token verbatim, so the ladder is size and space only.
* feat(webkit): [ENG-47607] add DocCallout, the documentation admonition
* test(storybook): add the linux baselines for the DocProse and DocCallout stories
* fix(webkit): [ENG-47607] step an inline callout link to the only other weight the system defines
font-medium is 500, a weight no text token in the theme uses — the set is light (300) and normal (400). An inline link inside a callout now steps to 400.
* feat(webkit): [ENG-47611] add DocCard, the documentation grid cell
* test(storybook): add the linux baselines for the documentation stories
* feat(webkit): [ENG-47612] add DocCardGroup, the documentation card grid
* test(storybook): add the linux baselines for the doccardgroup stories
* feat(webkit): [ENG-47609] add DocItem, the documentation list row
* test(storybook): add the linux baselines for the docitem stories
* feat(webkit): [ENG-47613] add DocSteps and DocStep, the documentation procedure list
* test(storybook): add the linux baselines for the docsteps stories
* feat(webkit): [ENG-47608] add DocFrame, the documentation figure with a full-screen view
* test(storybook): add the linux baselines for the docframe stories
* test(storybook): stop snapshotting the autoplaying DocFrame clip
* feat(webkit): [ENG-47615] add DocUpdate and the heading-nav composable
* test(storybook): add the linux baselines for the docupdate stories
* feat(webkit): [ENG-47561] add DocPrompt, the copyable agent prompt
* test(storybook): add the linux baselines for the docprompt stories
* feat(webkit): [ENG-47610] add DocTooltip, the inline gloss
* test(storybook): add the linux baselines for the doctooltip stories
* fix(webkit): [ENG-47610] let the tooltip headline take its type token's weight
The headline restated font-medium (500), a weight no text token in the theme defines. It now takes text-body-sm verbatim, and the step to the tip below it is carried by ink colour.
* feat(webkit): [ENG-47618] add DocPagination, the documentation prev/next pair
* test(storybook): add the linux baselines for the docpagination stories
* feat(webkit): [ENG-47617] add DocPageHeader, the documentation masthead
* test(storybook): add the linux baselines for the docpageheader stories
* fix(webkit): [ENG-47617] stop the page title being overtaken by its own h2
text-heading-lg runs 18/18/30, so between 640 and 768 the masthead title rendered smaller than the prose h2 under it (20). The title now takes the same descending pair the prose h1 uses — text-heading-2xl with text-heading-xl from sm — which reads 30/30/36 and stays above every heading below it. Closes the theme gap the spec recorded.
The deck stops restating font-light; text-body-md already resolves to 300.
* feat(webkit): [ENG-47616] add DocOnThisPage, the documentation TOC rail
* test(storybook): add the linux baselines for the doconthispage stories
* fix(webkit): [ENG-47616] mark the rail's opening line so a shell can align to it
A docs shell aligns the outline rail against the page bar beside it, and needed a stable thing to measure. data-toc-title marks the rail's first line so the shell reaches it without depending on this component's internal structure.
* style(webkit): sort the use-heading-nav imports
* test(storybook): regenerate the documentation baselines after the typography fixes
---------
Co-authored-by: Gab <gabriel.mendonca@azion.com>
* fix(webkit): copy-button tooltip, card-box header inset, flow attrs (#936)
* fix(webkit): copy-button tooltip, card-box header inset, flow attrs
Three unrelated small fixes.
COPY-BUTTON COMPOSES TOOLTIP. An icon-only button says nothing to a pointer user
until it is hovered, so the glyph was unlabelled in practice — and the
"Copied" confirmation existed only as an accessible-name swap, announced to a
screen reader and invisible to everyone else. One string now names the control
AND labels its tooltip, so the two can never disagree: `ariaLabel` while idle,
`copiedLabel` for the two seconds after a write. The confirmation is visible
because it rides the same state.
CARD-BOX HEADER INSET `sm` → `md`. The header's horizontal padding now matches
the content's `p-(--spacing-md)` and the footer's `px-(--spacing-md)`, so the
title sits on the same vertical line as the first thing under it — and, in a
flush card, as the rows of whatever fills it (ItemList, Accordion and LogView
are all inset `--spacing-md`). At `--spacing-sm` every card header sat 4px left
of its own body.
FLOW FORWARDS `$attrs`. It declared `inheritAttrs: false` and then never spread
`$attrs` on its root, merging only `attrs.class`. Everything else a consumer
passed — `id`, `aria-*`, `data-*`, listeners — was silently dropped, which
`.claude/rules/root-element.md` exists to prevent. Same shape as its siblings
now: `v-bind="$attrs"` on the real root, with `cn` still merging the class.
* test(storybook): regenerate the card-box baselines for the header inset
* chore(webkit): raise the doc-prompt budget for the copy-button tooltip
DocPrompt composes CopyButton, which now wraps its IconButton in the
Tooltip overlay so the icon-only control is legible on hover. The
overlay machinery raises the measured entry from 6.20 KB to 8.61 KB;
the budget moves to 9 KB to carry that composition with headroom.
---------
Co-authored-by: Isaque Böck <isaque.santos@azion.com>
* fix(webkit): emit navigate when a collapsed breadcrumb crumb is picked
`select` is the Dropdown's event, not the option's: `Dropdown.Option`
forwards its activation to the root through the injected context, and the
root is what emits `select(event, value)`. The listener sat on the option,
where it never fired — so picking a collapsed crumb below `md` did
nothing at all. The menu closed and the reader stayed on the page. That is
the whole trail on a phone, since the collapsed shape is the only one a
narrow bar renders.
The test that covered this was skipped as a harness limitation ("@select
does not fire under test interaction"). It was not a harness limitation —
it was this bug, reproducing exactly as a real browser did. The listener
moved to the root and the test is un-skipped.
---------
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
Co-authored-by: Isaque Böck <isaque.santos@azion.com>
isaque-bock-azion added a commit that referenced this pull request Aug 28, 2026
* feat(webkit): let a table column declare a minWidth floor
A bounded column — a chip list, a date, a count, an id — could only be sized by
a fixed `width`, and a fixed width is a bet in both directions: guess high and
the column reserves space the rest of the row needs, guess low and the content
inside it truncates (a chip list ends up as `SQL In…`). `minWidth` only has to
be right about the minimum: the column resolves to the widest of that floor and
what it actually holds — its header, with the sort affordance, and every
rendered body cell. `width` still wins when both are set, so an explicit width
remains the consumer overriding the measurement outright.
ONE width is resolved per column and applied to the header and to every body
cell, which keeps the invariant fixed widths exist for: the column resolves
identically in every row. A per-cell `min-width` would let each row size to its
own content and drift the header away from the body.
It has to be measured, because a flex row cannot express "as wide as the widest
cell in this column" — every row is its own flex container and knows nothing
about its siblings. The measurement is the same clone-based one the resize floor
already uses, so a column's auto width and its drag floor can never disagree.
Three things the measurement has to respect:
- Debounced (32ms). The rendered content changes in bursts — a sort, a page
turn, a column toggled back on — and measuring reads layout, so an
un-coalesced version would reflow per keystroke of a filter.
- Skipped while `loading`. Skeleton rows are not the content; measuring against
them would size every column to a placeholder and never grow it back.
- Re-run on `document.fonts.ready`. Text width is font-dependent and the first
paint can happen on the fallback face.
`autoWidths` joins the row `v-memo` so a resolved width actually reaches the
body, and feeds `frozenWidthOf` so a frozen `minWidth` column contributes its
real width to a neighbour's sticky offset.
* test(storybook): regenerate the table baselines for the column min-width
* fix(webkit): emit navigate when a collapsed breadcrumb crumb is picked (#939)
* feat(theme): add --container-site, the marketing site's shared measure (#923)
* feat(theme): add --container-site, the marketing site's shared measure
The azion.com pages are a single vertical frame: the nav bar, the hero's inner
column, every section below it and the footer all sit on the same centred
column, and the border-x that runs down the page is only continuous because
those four agree to the pixel. A width re-typed per band cannot hold that — the
frame is one decision, so it gets one place to live.
`5xl` (1192px) is a rung of the shared container ladder, not a number chosen for
this page. It is its own token rather than a reference to one of the console
measures because the two answer different questions: a console measure caps a
payload (a table, a form), while this one is the width of a frame whose four
bands have to line up, so retuning either must not move the other.
* refactor(webkit): point the footer column at --container-site (#928)
The footer's column named `--container-5xl` directly. On the marketing site the
nav bar, the hero band, the framed sections and this footer are ONE vertical
frame, and its side rules only meet because all four resolve to the same width —
so the measure has to be a token they share, not a rung each of them names.
Retuning it now moves them together, which a hardcoded rung here could not do.
No visual change: `--container-site` resolves to `--container-5xl`, which is
what the class already said.
* feat(webkit): add the resizable-panel layout primitive (#902)
* feat(webkit): add the resizable-panel layout primitive
A group of adjacent regions whose shared edges the reader can drag — the layout
behind a workspace, where an editor, a terminal under it and a preview beside it
are all content and the split between them is the reader's to set.
It exists because `Sidebar` was being used for both jobs. `Sidebar` is the app's
NAVIGATION rail: it collapses to icons, it hides, it is the thing you leave to go
somewhere. A terminal, a preview and a code editor are not places you navigate to
— they are the work — and dressing two of them as navigation rails produced a
screen with three "sidebars" and one document, on an axis `Sidebar` does not even
have: it has no horizontal edge, so a terminal under an editor had to hand-roll
its own splitter. This component owns the EDGE; `Sidebar` goes back to owning
navigation.
Composition: `ResizablePanel` with `Pane` and `Handle`, attached for dot-notation
and exported standalone. The compound is built by naming the static surface and
casting once rather than with `Object.assign`, whose inferred type walks each
part's local `interface Props` into the declaration and trips TS4082.
Ships the spec, a browser-mode test suite, the story, the `-root` and
per-sub-component exports, and a size budget.
* test(storybook): regenerate linux baselines for resizable-panel
Adds the 24 snapshots for the four new ResizablePanel stories across the 6-mode
matrix. Generated by the Storybook Regenerate Baseline workflow on this branch's
HEAD — baselines are ubuntu-only, so a local run would not match the folder the
visual gate compares against.
No existing snapshot is modified or deleted; the component is additive.
* test(webkit): compose resizable-panel through its named sub-component exports
Every other composition suite in the package renders through the named exports
(`AccordionItem`, `DropdownTrigger`, …) and checks dot-notation once, as an
identity assertion against those same exports. This file was the only one that
rendered through the root's statics — `h(ResizablePanel.Pane, …)` — which made
static analysis flag eight unguarded member accesses: a `.vue` default export is
generated by the SFC compiler, so the analyzer sees no `export default`, reads
the imported binding as undefined, and reports every `.Pane` / `.Handle` read on
it.
Compose through `ResizablePanelPane` / `ResizablePanelHandle` instead, and keep
the compound contract in the one place it is the subject rather than the vehicle,
asserting identity against those exports through a guarded read.
Same nine tests, same behaviour covered — the dot-notation contract is now
verified more tightly than `toBeTruthy` did.
---------
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* feat(webkit): give Select, MultiSelect and Dropdown one shared leading column (#935)
A list that mixes iconed and icon-less options used to run two label edges: an
option with a glyph indented its text, one without it did not, so the eye had no
column to read down. Fixing it per-option is not possible — no option knows
whether any sibling has an icon.
So the reservation is made by the LIST, with a self-triggering `:has()` rule.
Every option renders a fixed leading box and marks itself `data-leading` only
when it actually has a glyph; the box is `hidden` and turns back on under
`group-has-[[data-leading]]/options:flex`. `:has()` matches the marker even
while the box is hidden, so one iconed option is enough to open the column for
the whole list — and a list with no icons at all reserves nothing and pays no
indent. The same `group/options` marker is on all three panels, so Select,
MultiSelect and Dropdown behave identically.
The group label is now OPTIONAL, and sentence-case `text-label-sm` rather than
an uppercase overline: an unlabelled group is a grouping the reader does not
need named, and an overline read as a second heading level inside a panel that
has none.
Group rhythm is `--spacing-sm` above every group but the first — the same step
CommandMenu already puts between two bare groups. Dropdown keeps its hairline
and Select does not: the Figma frame for the Select panel separates groups by
space alone. Dropdown's divider moves to the group root, which spans the panel's
full inner width, so it reads edge-to-edge like the panel's own border; the
horizontal inset the rows need moves onto the content wrapper.
Panels also gain top/bottom slots for content that is not an option (a search
row, a footer action), and the panel's vertical padding steps up one so the
first and last row do not crowd its border.
Includes the 10 dropdown visual baselines this moves.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* fix(icons): resolve currentColor per theme in the colored-icon CSS (#930)
Astro's "A" shipped invisible on every light-mode surface.
Some brand logos are only partly colored: Astro is a fixed red-to-magenta
gradient swoosh under an "A" that is the brand's INK — black on a light
background, white on a dark one. That part is authored as `fill="currentColor"`,
which is what `validate-svg.mjs` asks for, and it resolves correctly wherever
the SVG is INLINED (dist/color-catalog.json → the gallery, Storybook).
A `background-image` is a separate document, so it inherits nothing from the
element painting it. `currentColor` there resolves against the SVG's own root,
not the consumer's text color, and no amount of `color` on the `<i>` reaches it.
Left alone it lands on the UA default and the icon disappears in one theme —
which is what happened: `ai-astro.svg` had the "A" hardcoded `#FAFAFA`, so it
was white-on-white everywhere in light mode.
So the generator resolves it. An icon containing `currentColor` is emitted
twice: the base rule with the light ink, and an override with the dark ink under
the same selectors @aziontech/theme uses for its dark tokens
(`[data-theme=dark]`, `.dark`, `.azion.azion-dark`). The override's extra
selector out-specifies the base, so source order does not decide the winner.
The inks are literals (`#141414` / `#FAFAFA`) rather than `var(--text-default)`
by necessity — same document isolation — and are the theme's own values for that
token in each mode. A fully-colored logo with no ink parts still emits one rule,
exactly as before: 17 colored icons, 1 theme-aware.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* docs(webkit): ship the webkit-create-surface and webkit-errors skills (#895)
* docs(webkit): ship the webkit-create-surface and webkit-errors skills
webkit-create-surface fixes where a create or settings form lives — a first-level
resource creates on a page, anything created inside a resource opens a drawer,
nothing creates in a dialog — plus the anatomy all of them share, what belongs
behind the collapsed Advanced band, and the commit model each surface uses.
webkit-errors is the one decision table for where a failure goes: client-side
validation on the field, a server rejection scoped to one field as a Message in
that section, an unscoped request failure as a toast with Retry, the signed-out
screens where auth never toasts, and the async failure that outlives its screen.
Both join the CLI bundle and the ui-craft routing table; webkit-form and
webkit-ui-states now hand their error rows off to webkit-errors.
* docs(webkit): ship the webkit-microcopy consumer skill (#931)
The writing rules for Azion product interfaces, as a skill the adoption toolkit
ships — so a consuming project's agent writes the same strings the console does
instead of inventing a house style per screen.
It fixes the things that were being decided per-PR: punctuation and
capitalization (no em dash, no ampersand, no parentheses in labels, no bold in
running text, sentence case with Azion product names capitalized), which surface
each kind of copy belongs on (a label names, a description constrains, a Message
explains impact, a Popover defines, a Tag states), one word per concept (Select
not Pick, Application not Edge Application, and no `edge` as a prefix or a
location), navigation and IA naming, and copy that survives every breakpoint.
Registered in the three places a consumer skill has to be registered or it never
reaches a project: the CLI's file list (`plan.js`), the UI-craft pack in
`plan.test.mjs` (now 18 skills), and the routing index in `CLAUDE.fragment.md`,
where it gets its own **Words** line — it is the only skill about strings rather
than structure, and it applies during every other one.
Distinct from the generic `content-microcopy` the plan drops: that one is
house-agnostic, this one carries Azion's own vocabulary.
---------
Co-authored-by: Isaque Böck <isaque.santos@azion.com>
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* feat(theme): add the page-enter and content-enter arrival animations (#896)
* feat(theme): add the page-enter and content-enter arrival animations
The two entrances an app shell needs, as a pair. page-enter is the route
transition: it starts one --layout-boundary-inline to the LEFT of where it lands
and travels right while fading up, so a page reads as arriving FROM the nav row
that was clicked — a plain fade has no origin and a right-side arrival reads as
going back. content-enter is content settling inside a page already on screen, so
it rises a hair instead of travelling, and takes a --content-enter-delay stagger
(with backwards fill, or a delayed follower would paint its landed state first).
Neither fills forwards: page-enter's box is usually the scroll container, and a
lingering translate would make it a containing block for any fixed descendant.
* docs(webkit): teach the two arrival animations in webkit-motion-polish
Documents both utilities and their knobs, and the failure they exist to prevent:
a page arrives once, so a mount-time entrance inside a page whose route
transition is still running lockesteps with it into one diagonal move (identical
opacities frame for frame is the tell). Adds that shape and the two-shells-per-
screen variant to the silent-failure table. catalog.json rebuilt so the two
animations reach the MCP and validate-spec-compliance.
---------
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* docs: rewrite add-animation for the CSS-first theme (#897)
The steps still described the deleted v3 plugin (semantic/animations.js,
addUtilities/addComponents, the camelCase keyframe object form). An animation is
now two edits under primitives/animations/ — the shorthand in animate.js, which
is what makes animate-<name> a utility at all, and the CSS-string keyframes in
keyframes.js, which Tailwind v4 does NOT generate — compiled by build:tokens,
which must run before catalog:build or nothing renders.
Also records the three things the two new arrivals taught: a per-instance value
rides a var() with a token default inside the keyframe, a stagger rides a var()
delay with backwards fill, and fill is a decision — forwards leaves a translate
that makes the element a containing block for any fixed descendant.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* fix(webkit): give table cells block padding so tall content can breathe (#899)
A cell was `min-h-12` with horizontal padding only. For a single-line cell that
is invisible — the 48px minimum decides the height on its own against ~20px of
text — so every existing row renders exactly as before.
It only shows on a cell whose content is TALLER than the minimum: a wrapped list
of tags, a two-line summary. There the height came from the content alone, which
put the first and last lines flush against the row's borders with no breathing
room at all.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* docs(webkit): bring webkit-lists onto the current list-page shape (#929)
The shipped consumer skill still described the list page as it was before three
decisions landed, so a consumer following it built a page the product no longer
looks like:
- A first-level module index opens with a `PageHeading` again, and the module's
create action lives in it. The shell draws the breadcrumb only from the second
crumb up, so at first level the heading is the ONLY place the module's name
appears in the content.
- The size ladder is the hierarchy: the heading's action is the page's one
`large` (40px) control and the whole controls row is `medium` (32px). At 40px
both rows competed and the create button stopped being the page's answer.
- The controls row is narrowing only — `#actions` there is for an action on the
LISTING, not the page's own.
`last_updated` moves to 2026-08-24.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* feat: webkit documentation (#919)
* chore: [ENG-47606] add the documentation component category (#904)
* feat(theme): [ENG-47561] add the docs measure and the prose + mono body registers (#905)
* chore: [ENG-47606] add the documentation component category
* feat(theme): [ENG-47561] add the docs measure and the mono body register
* feat(theme): [ENG-47606] add the prose body register
* fix(theme): [ENG-47606] widen the docs measure to the form column
The documentation column was capped at 752px, narrower than every other measure in the system. Docs pages carry code blocks, card grids and tables, none of which fit a prose measure, so the column now takes the same 1192px the form measure uses.
The token is defined here, so it is set here: the doc component stack carries the same value, and an add/add disagreement between the two made every PR in that stack unmergeable.
* feat(webkit): [ENG-47616] add DocOnThisPage, the documentation TOC rail (#918)
* chore: [ENG-47606] add the documentation component category
* feat(theme): [ENG-47561] add the docs measure and the mono body register
* feat(theme): [ENG-47606] add the prose body register
* feat(webkit): [ENG-47606] add DocProse, the documentation typography contract
* test(storybook): add the linux baselines for the DocProse stories
* fix(theme): [ENG-47606] widen the docs measure to the form column
The documentation column was capped at 752px, narrower than every other measure in the system. Docs pages carry code blocks, card grids and tables, none of which fit a prose measure, so the column now takes the same 1192px the form measure uses.
* fix(webkit): [ENG-47606] pin the prose section step and separate the heading registers
The section rung was a descending pair that read 32 on a phone and on a laptop and only reached 48 at xl, so the same page broke its sections at different gaps by window width. It is now a flat 56 to open and 48 to close, taken from the theme's primitive spacing steps.
Each heading level now names two registers instead of one. On one register per level the base column collapsed: h2 was 16 (the body's own size), h3 and h4 were both 14, and between 640 and 768 the page title was overtaken by its own h2.
Weight and leading are no longer restated here — every element takes its type token verbatim, so the ladder is size and space only.
* feat(webkit): [ENG-47607] add DocCallout, the documentation admonition
* test(storybook): add the linux baselines for the DocProse and DocCallout stories
* fix(webkit): [ENG-47607] step an inline callout link to the only other weight the system defines
font-medium is 500, a weight no text token in the theme uses — the set is light (300) and normal (400). An inline link inside a callout now steps to 400.
* feat(webkit): [ENG-47611] add DocCard, the documentation grid cell
* test(storybook): add the linux baselines for the documentation stories
* feat(webkit): [ENG-47612] add DocCardGroup, the documentation card grid
* test(storybook): add the linux baselines for the doccardgroup stories
* feat(webkit): [ENG-47609] add DocItem, the documentation list row
* test(storybook): add the linux baselines for the docitem stories
* feat(webkit): [ENG-47613] add DocSteps and DocStep, the documentation procedure list
* test(storybook): add the linux baselines for the docsteps stories
* feat(webkit): [ENG-47608] add DocFrame, the documentation figure with a full-screen view
* test(storybook): add the linux baselines for the docframe stories
* test(storybook): stop snapshotting the autoplaying DocFrame clip
* feat(webkit): [ENG-47615] add DocUpdate and the heading-nav composable
* test(storybook): add the linux baselines for the docupdate stories
* feat(webkit): [ENG-47561] add DocPrompt, the copyable agent prompt
* test(storybook): add the linux baselines for the docprompt stories
* feat(webkit): [ENG-47610] add DocTooltip, the inline gloss
* test(storybook): add the linux baselines for the doctooltip stories
* fix(webkit): [ENG-47610] let the tooltip headline take its type token's weight
The headline restated font-medium (500), a weight no text token in the theme defines. It now takes text-body-sm verbatim, and the step to the tip below it is carried by ink colour.
* feat(webkit): [ENG-47618] add DocPagination, the documentation prev/next pair
* test(storybook): add the linux baselines for the docpagination stories
* feat(webkit): [ENG-47617] add DocPageHeader, the documentation masthead
* test(storybook): add the linux baselines for the docpageheader stories
* fix(webkit): [ENG-47617] stop the page title being overtaken by its own h2
text-heading-lg runs 18/18/30, so between 640 and 768 the masthead title rendered smaller than the prose h2 under it (20). The title now takes the same descending pair the prose h1 uses — text-heading-2xl with text-heading-xl from sm — which reads 30/30/36 and stays above every heading below it. Closes the theme gap the spec recorded.
The deck stops restating font-light; text-body-md already resolves to 300.
* feat(webkit): [ENG-47616] add DocOnThisPage, the documentation TOC rail
* test(storybook): add the linux baselines for the doconthispage stories
* fix(webkit): [ENG-47616] mark the rail's opening line so a shell can align to it
A docs shell aligns the outline rail against the page bar beside it, and needed a stable thing to measure. data-toc-title marks the rail's first line so the shell reaches it without depending on this component's internal structure.
* style(webkit): sort the use-heading-nav imports
* test(storybook): regenerate the documentation baselines after the typography fixes
---------
Co-authored-by: Gab <gabriel.mendonca@azion.com>
* fix(webkit): copy-button tooltip, card-box header inset, flow attrs (#936)
* fix(webkit): copy-button tooltip, card-box header inset, flow attrs
Three unrelated small fixes.
COPY-BUTTON COMPOSES TOOLTIP. An icon-only button says nothing to a pointer user
until it is hovered, so the glyph was unlabelled in practice — and the
"Copied" confirmation existed only as an accessible-name swap, announced to a
screen reader and invisible to everyone else. One string now names the control
AND labels its tooltip, so the two can never disagree: `ariaLabel` while idle,
`copiedLabel` for the two seconds after a write. The confirmation is visible
because it rides the same state.
CARD-BOX HEADER INSET `sm` → `md`. The header's horizontal padding now matches
the content's `p-(--spacing-md)` and the footer's `px-(--spacing-md)`, so the
title sits on the same vertical line as the first thing under it — and, in a
flush card, as the rows of whatever fills it (ItemList, Accordion and LogView
are all inset `--spacing-md`). At `--spacing-sm` every card header sat 4px left
of its own body.
FLOW FORWARDS `$attrs`. It declared `inheritAttrs: false` and then never spread
`$attrs` on its root, merging only `attrs.class`. Everything else a consumer
passed — `id`, `aria-*`, `data-*`, listeners — was silently dropped, which
`.claude/rules/root-element.md` exists to prevent. Same shape as its siblings
now: `v-bind="$attrs"` on the real root, with `cn` still merging the class.
* test(storybook): regenerate the card-box baselines for the header inset
* chore(webkit): raise the doc-prompt budget for the copy-button tooltip
DocPrompt composes CopyButton, which now wraps its IconButton in the
Tooltip overlay so the icon-only control is legible on hover. The
overlay machinery raises the measured entry from 6.20 KB to 8.61 KB;
the budget moves to 9 KB to carry that composition with headroom.
---------
Co-authored-by: Isaque Böck <isaque.santos@azion.com>
* fix(webkit): emit navigate when a collapsed breadcrumb crumb is picked
`select` is the Dropdown's event, not the option's: `Dropdown.Option`
forwards its activation to the root through the injected context, and the
root is what emits `select(event, value)`. The listener sat on the option,
where it never fired — so picking a collapsed crumb below `md` did
nothing at all. The menu closed and the reader stayed on the page. That is
the whole trail on a phone, since the collapsed shape is the only one a
narrow bar renders.
The test that covered this was skipped as a harness limitation ("@select
does not fire under test interaction"). It was not a harness limitation —
it was this bug, reproducing exactly as a real browser did. The listener
moved to the root and the test is un-skipped.
---------
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
Co-authored-by: Isaque Böck <isaque.santos@azion.com>
---------
Co-authored-by: Isaque dos Santos <isaque.santos@azion.com>
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@gabriel-lisboa-azion@isaque-bock-azion
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(theme): [ENG-47561] add the docs measure and the prose + mono body registers - #905

Merged
isaque-bock-azion merged 3 commits into
feat/webkit-documentationfrom
feat/ENG-47561-docs-measure-and-mono-body
Aug 24, 2026
Merged

feat(theme): [ENG-47561] add the docs measure and the prose + mono body registers#905
isaque-bock-azion merged 3 commits into
feat/webkit-documentationfrom
feat/ENG-47561-docs-measure-and-mono-body

Conversation

@gabriel-lisboa-azion

@gabriel-lisboa-aziongabriel-lisboa-azion commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • text-body-prose-md — body copy at --leading-relaxed (1.625). The whole text-body-* set is snug (1.375), and the documentation prose ladder was fitted against a 26px line box, so prose needs its own register. Line-height belongs in the token: check-authoring's leading-raw check deliberately rejects a leading-* override even in token form.
  • text-body-code-sm — the mono body register. The label-code-* set is leading-none, built for one-row-per-line code; this one wraps. Required by DocPrompt.
  • layout-measure-docs (--container-2xl, 752px) + layout-column-docs, completing the measure family (column / focused / form / form-create / docs). Docs are the one column capped by typography rather than payload width. The utility is generated by COLUMN_MEASURE + the columnUtility factory, so two entries produce the whole class.

How to test

  1. node packages/theme/src/scripts/build-tokens.mjs then git diff --stat packages/theme/dist — expect no further change; the committed dist is already current.
  2. cd packages/theme && node --test "test/**/*.test.mjs" — 12/12 pass, including the zero-unit assertions over every emitted token.
  3. grep "text-body-prose-md-line-height\|text-body-md-line-height" packages/theme/dist/v4/globals.css — expect 1.625 and 1.375 respectively; that difference is the whole point of the new token.
  4. grep -A5 '@utility text-body-code-sm' packages/theme/dist/v4/globals.css and the same for layout-column-docs — each must emit a real rule body. A token that compiles to nothing is the silent failure mode here.
  5. grep "layout-measure':" packages/theme/src/tokens/semantic/layouts.data.js — must still read var(--container-7xl) / 1620px (see Notes).

Notes

  • Deliberately excluded: the working branch this was carved from also retunes layout-measure from --container-7xl (1620px) to --container-6xl (1388px), moving home from the focused measure to the data measure. That is a separate product decision that would have silently changed the width of every data page, so it is not here — layout-measure and layout-measure-focused keep main's values and main's prose.
  • Stacked on chore: [ENG-47606] add the documentation component category #904 (the documentation category), which DocProse also needs. Merge chore: [ENG-47606] add the documentation component category #904 first; this retargets automatically.
  • No component consumes layout-column-docs yet: DocProse deliberately has no measure of its own, so the consumer caps the column.
  • Related: ENG-47561, ENG-47606, and the sibling component issues.

Delivery

The Documentation family is delivered on the feature branch feat/docs-components-bundle, not piecemeal to main — so main takes the whole family as one merge, while review stays per-component.

Merge order, bottom-up — each PR's base is the one before it, and each retargets automatically as its parent lands:

#904#905#906#907#908#909#910#911#912#913#914#915#916#917#918feat/docs-components-bundlemain

#904the documentation category (standards only)
#905theme: the docs measure + the prose and mono body registers
#906#918DocProse · DocCallout · DocCard · DocCardGroup · DocItem · DocSteps + DocStep · DocFrame · DocUpdate · DocPrompt · DocTooltip · DocPagination · DocPageHeader · DocOnThisPage

@gabriel-lisboa-aziongabriel-lisboa-azion changed the title feat(theme): [ENG-47561] add the docs measure and the mono body registerfeat(theme): [ENG-47561] add the docs measure and the prose + mono body registersAug 22, 2026
@gabriel-lisboa-azion
gabriel-lisboa-azionforce-pushed the feat/ENG-47561-docs-measure-and-mono-body branch from d3d46c0 to bc6575cCompareAugust 22, 2026 14:11
@gabriel-lisboa-azion
gabriel-lisboa-azion changed the base branch from main to chore/ENG-47606-documentation-categoryAugust 22, 2026 14:11
@isaque-bock-azion
isaque-bock-azion changed the base branch from chore/ENG-47606-documentation-category to feat/webkit-documentationAugust 24, 2026 13:04
@isaque-bock-azion
isaque-bock-azion merged commit e864f62 into feat/webkit-documentationAug 24, 2026
47 of 51 checks passed
@isaque-bock-azion
isaque-bock-azion deleted the feat/ENG-47561-docs-measure-and-mono-body branch August 24, 2026 13:14
isaque-bock-azion added a commit that referenced this pull request Aug 26, 2026
* chore: [ENG-47606] add the documentation component category (#904)
* feat(theme): [ENG-47561] add the docs measure and the prose + mono body registers (#905)
* chore: [ENG-47606] add the documentation component category
* feat(theme): [ENG-47561] add the docs measure and the mono body register
* feat(theme): [ENG-47606] add the prose body register
* fix(theme): [ENG-47606] widen the docs measure to the form column
The documentation column was capped at 752px, narrower than every other measure in the system. Docs pages carry code blocks, card grids and tables, none of which fit a prose measure, so the column now takes the same 1192px the form measure uses.
The token is defined here, so it is set here: the doc component stack carries the same value, and an add/add disagreement between the two made every PR in that stack unmergeable.
* feat(webkit): [ENG-47616] add DocOnThisPage, the documentation TOC rail (#918)
* chore: [ENG-47606] add the documentation component category
* feat(theme): [ENG-47561] add the docs measure and the mono body register
* feat(theme): [ENG-47606] add the prose body register
* feat(webkit): [ENG-47606] add DocProse, the documentation typography contract
* test(storybook): add the linux baselines for the DocProse stories
* fix(theme): [ENG-47606] widen the docs measure to the form column
The documentation column was capped at 752px, narrower than every other measure in the system. Docs pages carry code blocks, card grids and tables, none of which fit a prose measure, so the column now takes the same 1192px the form measure uses.
* fix(webkit): [ENG-47606] pin the prose section step and separate the heading registers
The section rung was a descending pair that read 32 on a phone and on a laptop and only reached 48 at xl, so the same page broke its sections at different gaps by window width. It is now a flat 56 to open and 48 to close, taken from the theme's primitive spacing steps.
Each heading level now names two registers instead of one. On one register per level the base column collapsed: h2 was 16 (the body's own size), h3 and h4 were both 14, and between 640 and 768 the page title was overtaken by its own h2.
Weight and leading are no longer restated here — every element takes its type token verbatim, so the ladder is size and space only.
* feat(webkit): [ENG-47607] add DocCallout, the documentation admonition
* test(storybook): add the linux baselines for the DocProse and DocCallout stories
* fix(webkit): [ENG-47607] step an inline callout link to the only other weight the system defines
font-medium is 500, a weight no text token in the theme uses — the set is light (300) and normal (400). An inline link inside a callout now steps to 400.
* feat(webkit): [ENG-47611] add DocCard, the documentation grid cell
* test(storybook): add the linux baselines for the documentation stories
* feat(webkit): [ENG-47612] add DocCardGroup, the documentation card grid
* test(storybook): add the linux baselines for the doccardgroup stories
* feat(webkit): [ENG-47609] add DocItem, the documentation list row
* test(storybook): add the linux baselines for the docitem stories
* feat(webkit): [ENG-47613] add DocSteps and DocStep, the documentation procedure list
* test(storybook): add the linux baselines for the docsteps stories
* feat(webkit): [ENG-47608] add DocFrame, the documentation figure with a full-screen view
* test(storybook): add the linux baselines for the docframe stories
* test(storybook): stop snapshotting the autoplaying DocFrame clip
* feat(webkit): [ENG-47615] add DocUpdate and the heading-nav composable
* test(storybook): add the linux baselines for the docupdate stories
* feat(webkit): [ENG-47561] add DocPrompt, the copyable agent prompt
* test(storybook): add the linux baselines for the docprompt stories
* feat(webkit): [ENG-47610] add DocTooltip, the inline gloss
* test(storybook): add the linux baselines for the doctooltip stories
* fix(webkit): [ENG-47610] let the tooltip headline take its type token's weight
The headline restated font-medium (500), a weight no text token in the theme defines. It now takes text-body-sm verbatim, and the step to the tip below it is carried by ink colour.
* feat(webkit): [ENG-47618] add DocPagination, the documentation prev/next pair
* test(storybook): add the linux baselines for the docpagination stories
* feat(webkit): [ENG-47617] add DocPageHeader, the documentation masthead
* test(storybook): add the linux baselines for the docpageheader stories
* fix(webkit): [ENG-47617] stop the page title being overtaken by its own h2
text-heading-lg runs 18/18/30, so between 640 and 768 the masthead title rendered smaller than the prose h2 under it (20). The title now takes the same descending pair the prose h1 uses — text-heading-2xl with text-heading-xl from sm — which reads 30/30/36 and stays above every heading below it. Closes the theme gap the spec recorded.
The deck stops restating font-light; text-body-md already resolves to 300.
* feat(webkit): [ENG-47616] add DocOnThisPage, the documentation TOC rail
* test(storybook): add the linux baselines for the doconthispage stories
* fix(webkit): [ENG-47616] mark the rail's opening line so a shell can align to it
A docs shell aligns the outline rail against the page bar beside it, and needed a stable thing to measure. data-toc-title marks the rail's first line so the shell reaches it without depending on this component's internal structure.
* style(webkit): sort the use-heading-nav imports
* test(storybook): regenerate the documentation baselines after the typography fixes
---------
Co-authored-by: Gab <gabriel.mendonca@azion.com>
isaque-bock-azion added a commit that referenced this pull request Aug 28, 2026
#939)
* feat(theme): add --container-site, the marketing site's shared measure (#923)
* feat(theme): add --container-site, the marketing site's shared measure
The azion.com pages are a single vertical frame: the nav bar, the hero's inner
column, every section below it and the footer all sit on the same centred
column, and the border-x that runs down the page is only continuous because
those four agree to the pixel. A width re-typed per band cannot hold that — the
frame is one decision, so it gets one place to live.
`5xl` (1192px) is a rung of the shared container ladder, not a number chosen for
this page. It is its own token rather than a reference to one of the console
measures because the two answer different questions: a console measure caps a
payload (a table, a form), while this one is the width of a frame whose four
bands have to line up, so retuning either must not move the other.
* refactor(webkit): point the footer column at --container-site (#928)
The footer's column named `--container-5xl` directly. On the marketing site the
nav bar, the hero band, the framed sections and this footer are ONE vertical
frame, and its side rules only meet because all four resolve to the same width —
so the measure has to be a token they share, not a rung each of them names.
Retuning it now moves them together, which a hardcoded rung here could not do.
No visual change: `--container-site` resolves to `--container-5xl`, which is
what the class already said.
* feat(webkit): add the resizable-panel layout primitive (#902)
* feat(webkit): add the resizable-panel layout primitive
A group of adjacent regions whose shared edges the reader can drag — the layout
behind a workspace, where an editor, a terminal under it and a preview beside it
are all content and the split between them is the reader's to set.
It exists because `Sidebar` was being used for both jobs. `Sidebar` is the app's
NAVIGATION rail: it collapses to icons, it hides, it is the thing you leave to go
somewhere. A terminal, a preview and a code editor are not places you navigate to
— they are the work — and dressing two of them as navigation rails produced a
screen with three "sidebars" and one document, on an axis `Sidebar` does not even
have: it has no horizontal edge, so a terminal under an editor had to hand-roll
its own splitter. This component owns the EDGE; `Sidebar` goes back to owning
navigation.
Composition: `ResizablePanel` with `Pane` and `Handle`, attached for dot-notation
and exported standalone. The compound is built by naming the static surface and
casting once rather than with `Object.assign`, whose inferred type walks each
part's local `interface Props` into the declaration and trips TS4082.
Ships the spec, a browser-mode test suite, the story, the `-root` and
per-sub-component exports, and a size budget.
* test(storybook): regenerate linux baselines for resizable-panel
Adds the 24 snapshots for the four new ResizablePanel stories across the 6-mode
matrix. Generated by the Storybook Regenerate Baseline workflow on this branch's
HEAD — baselines are ubuntu-only, so a local run would not match the folder the
visual gate compares against.
No existing snapshot is modified or deleted; the component is additive.
* test(webkit): compose resizable-panel through its named sub-component exports
Every other composition suite in the package renders through the named exports
(`AccordionItem`, `DropdownTrigger`, …) and checks dot-notation once, as an
identity assertion against those same exports. This file was the only one that
rendered through the root's statics — `h(ResizablePanel.Pane, …)` — which made
static analysis flag eight unguarded member accesses: a `.vue` default export is
generated by the SFC compiler, so the analyzer sees no `export default`, reads
the imported binding as undefined, and reports every `.Pane` / `.Handle` read on
it.
Compose through `ResizablePanelPane` / `ResizablePanelHandle` instead, and keep
the compound contract in the one place it is the subject rather than the vehicle,
asserting identity against those exports through a guarded read.
Same nine tests, same behaviour covered — the dot-notation contract is now
verified more tightly than `toBeTruthy` did.
---------
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* feat(webkit): give Select, MultiSelect and Dropdown one shared leading column (#935)
A list that mixes iconed and icon-less options used to run two label edges: an
option with a glyph indented its text, one without it did not, so the eye had no
column to read down. Fixing it per-option is not possible — no option knows
whether any sibling has an icon.
So the reservation is made by the LIST, with a self-triggering `:has()` rule.
Every option renders a fixed leading box and marks itself `data-leading` only
when it actually has a glyph; the box is `hidden` and turns back on under
`group-has-[[data-leading]]/options:flex`. `:has()` matches the marker even
while the box is hidden, so one iconed option is enough to open the column for
the whole list — and a list with no icons at all reserves nothing and pays no
indent. The same `group/options` marker is on all three panels, so Select,
MultiSelect and Dropdown behave identically.
The group label is now OPTIONAL, and sentence-case `text-label-sm` rather than
an uppercase overline: an unlabelled group is a grouping the reader does not
need named, and an overline read as a second heading level inside a panel that
has none.
Group rhythm is `--spacing-sm` above every group but the first — the same step
CommandMenu already puts between two bare groups. Dropdown keeps its hairline
and Select does not: the Figma frame for the Select panel separates groups by
space alone. Dropdown's divider moves to the group root, which spans the panel's
full inner width, so it reads edge-to-edge like the panel's own border; the
horizontal inset the rows need moves onto the content wrapper.
Panels also gain top/bottom slots for content that is not an option (a search
row, a footer action), and the panel's vertical padding steps up one so the
first and last row do not crowd its border.
Includes the 10 dropdown visual baselines this moves.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* fix(icons): resolve currentColor per theme in the colored-icon CSS (#930)
Astro's "A" shipped invisible on every light-mode surface.
Some brand logos are only partly colored: Astro is a fixed red-to-magenta
gradient swoosh under an "A" that is the brand's INK — black on a light
background, white on a dark one. That part is authored as `fill="currentColor"`,
which is what `validate-svg.mjs` asks for, and it resolves correctly wherever
the SVG is INLINED (dist/color-catalog.json → the gallery, Storybook).
A `background-image` is a separate document, so it inherits nothing from the
element painting it. `currentColor` there resolves against the SVG's own root,
not the consumer's text color, and no amount of `color` on the `<i>` reaches it.
Left alone it lands on the UA default and the icon disappears in one theme —
which is what happened: `ai-astro.svg` had the "A" hardcoded `#FAFAFA`, so it
was white-on-white everywhere in light mode.
So the generator resolves it. An icon containing `currentColor` is emitted
twice: the base rule with the light ink, and an override with the dark ink under
the same selectors @aziontech/theme uses for its dark tokens
(`[data-theme=dark]`, `.dark`, `.azion.azion-dark`). The override's extra
selector out-specifies the base, so source order does not decide the winner.
The inks are literals (`#141414` / `#FAFAFA`) rather than `var(--text-default)`
by necessity — same document isolation — and are the theme's own values for that
token in each mode. A fully-colored logo with no ink parts still emits one rule,
exactly as before: 17 colored icons, 1 theme-aware.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* docs(webkit): ship the webkit-create-surface and webkit-errors skills (#895)
* docs(webkit): ship the webkit-create-surface and webkit-errors skills
webkit-create-surface fixes where a create or settings form lives — a first-level
resource creates on a page, anything created inside a resource opens a drawer,
nothing creates in a dialog — plus the anatomy all of them share, what belongs
behind the collapsed Advanced band, and the commit model each surface uses.
webkit-errors is the one decision table for where a failure goes: client-side
validation on the field, a server rejection scoped to one field as a Message in
that section, an unscoped request failure as a toast with Retry, the signed-out
screens where auth never toasts, and the async failure that outlives its screen.
Both join the CLI bundle and the ui-craft routing table; webkit-form and
webkit-ui-states now hand their error rows off to webkit-errors.
* docs(webkit): ship the webkit-microcopy consumer skill (#931)
The writing rules for Azion product interfaces, as a skill the adoption toolkit
ships — so a consuming project's agent writes the same strings the console does
instead of inventing a house style per screen.
It fixes the things that were being decided per-PR: punctuation and
capitalization (no em dash, no ampersand, no parentheses in labels, no bold in
running text, sentence case with Azion product names capitalized), which surface
each kind of copy belongs on (a label names, a description constrains, a Message
explains impact, a Popover defines, a Tag states), one word per concept (Select
not Pick, Application not Edge Application, and no `edge` as a prefix or a
location), navigation and IA naming, and copy that survives every breakpoint.
Registered in the three places a consumer skill has to be registered or it never
reaches a project: the CLI's file list (`plan.js`), the UI-craft pack in
`plan.test.mjs` (now 18 skills), and the routing index in `CLAUDE.fragment.md`,
where it gets its own **Words** line — it is the only skill about strings rather
than structure, and it applies during every other one.
Distinct from the generic `content-microcopy` the plan drops: that one is
house-agnostic, this one carries Azion's own vocabulary.
---------
Co-authored-by: Isaque Böck <isaque.santos@azion.com>
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* feat(theme): add the page-enter and content-enter arrival animations (#896)
* feat(theme): add the page-enter and content-enter arrival animations
The two entrances an app shell needs, as a pair. page-enter is the route
transition: it starts one --layout-boundary-inline to the LEFT of where it lands
and travels right while fading up, so a page reads as arriving FROM the nav row
that was clicked — a plain fade has no origin and a right-side arrival reads as
going back. content-enter is content settling inside a page already on screen, so
it rises a hair instead of travelling, and takes a --content-enter-delay stagger
(with backwards fill, or a delayed follower would paint its landed state first).
Neither fills forwards: page-enter's box is usually the scroll container, and a
lingering translate would make it a containing block for any fixed descendant.
* docs(webkit): teach the two arrival animations in webkit-motion-polish
Documents both utilities and their knobs, and the failure they exist to prevent:
a page arrives once, so a mount-time entrance inside a page whose route
transition is still running lockesteps with it into one diagonal move (identical
opacities frame for frame is the tell). Adds that shape and the two-shells-per-
screen variant to the silent-failure table. catalog.json rebuilt so the two
animations reach the MCP and validate-spec-compliance.
---------
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* docs: rewrite add-animation for the CSS-first theme (#897)
The steps still described the deleted v3 plugin (semantic/animations.js,
addUtilities/addComponents, the camelCase keyframe object form). An animation is
now two edits under primitives/animations/ — the shorthand in animate.js, which
is what makes animate-<name> a utility at all, and the CSS-string keyframes in
keyframes.js, which Tailwind v4 does NOT generate — compiled by build:tokens,
which must run before catalog:build or nothing renders.
Also records the three things the two new arrivals taught: a per-instance value
rides a var() with a token default inside the keyframe, a stagger rides a var()
delay with backwards fill, and fill is a decision — forwards leaves a translate
that makes the element a containing block for any fixed descendant.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* fix(webkit): give table cells block padding so tall content can breathe (#899)
A cell was `min-h-12` with horizontal padding only. For a single-line cell that
is invisible — the 48px minimum decides the height on its own against ~20px of
text — so every existing row renders exactly as before.
It only shows on a cell whose content is TALLER than the minimum: a wrapped list
of tags, a two-line summary. There the height came from the content alone, which
put the first and last lines flush against the row's borders with no breathing
room at all.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* docs(webkit): bring webkit-lists onto the current list-page shape (#929)
The shipped consumer skill still described the list page as it was before three
decisions landed, so a consumer following it built a page the product no longer
looks like:
- A first-level module index opens with a `PageHeading` again, and the module's
create action lives in it. The shell draws the breadcrumb only from the second
crumb up, so at first level the heading is the ONLY place the module's name
appears in the content.
- The size ladder is the hierarchy: the heading's action is the page's one
`large` (40px) control and the whole controls row is `medium` (32px). At 40px
both rows competed and the create button stopped being the page's answer.
- The controls row is narrowing only — `#actions` there is for an action on the
LISTING, not the page's own.
`last_updated` moves to 2026-08-24.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* feat: webkit documentation (#919)
* chore: [ENG-47606] add the documentation component category (#904)
* feat(theme): [ENG-47561] add the docs measure and the prose + mono body registers (#905)
* chore: [ENG-47606] add the documentation component category
* feat(theme): [ENG-47561] add the docs measure and the mono body register
* feat(theme): [ENG-47606] add the prose body register
* fix(theme): [ENG-47606] widen the docs measure to the form column
The documentation column was capped at 752px, narrower than every other measure in the system. Docs pages carry code blocks, card grids and tables, none of which fit a prose measure, so the column now takes the same 1192px the form measure uses.
The token is defined here, so it is set here: the doc component stack carries the same value, and an add/add disagreement between the two made every PR in that stack unmergeable.
* feat(webkit): [ENG-47616] add DocOnThisPage, the documentation TOC rail (#918)
* chore: [ENG-47606] add the documentation component category
* feat(theme): [ENG-47561] add the docs measure and the mono body register
* feat(theme): [ENG-47606] add the prose body register
* feat(webkit): [ENG-47606] add DocProse, the documentation typography contract
* test(storybook): add the linux baselines for the DocProse stories
* fix(theme): [ENG-47606] widen the docs measure to the form column
The documentation column was capped at 752px, narrower than every other measure in the system. Docs pages carry code blocks, card grids and tables, none of which fit a prose measure, so the column now takes the same 1192px the form measure uses.
* fix(webkit): [ENG-47606] pin the prose section step and separate the heading registers
The section rung was a descending pair that read 32 on a phone and on a laptop and only reached 48 at xl, so the same page broke its sections at different gaps by window width. It is now a flat 56 to open and 48 to close, taken from the theme's primitive spacing steps.
Each heading level now names two registers instead of one. On one register per level the base column collapsed: h2 was 16 (the body's own size), h3 and h4 were both 14, and between 640 and 768 the page title was overtaken by its own h2.
Weight and leading are no longer restated here — every element takes its type token verbatim, so the ladder is size and space only.
* feat(webkit): [ENG-47607] add DocCallout, the documentation admonition
* test(storybook): add the linux baselines for the DocProse and DocCallout stories
* fix(webkit): [ENG-47607] step an inline callout link to the only other weight the system defines
font-medium is 500, a weight no text token in the theme uses — the set is light (300) and normal (400). An inline link inside a callout now steps to 400.
* feat(webkit): [ENG-47611] add DocCard, the documentation grid cell
* test(storybook): add the linux baselines for the documentation stories
* feat(webkit): [ENG-47612] add DocCardGroup, the documentation card grid
* test(storybook): add the linux baselines for the doccardgroup stories
* feat(webkit): [ENG-47609] add DocItem, the documentation list row
* test(storybook): add the linux baselines for the docitem stories
* feat(webkit): [ENG-47613] add DocSteps and DocStep, the documentation procedure list
* test(storybook): add the linux baselines for the docsteps stories
* feat(webkit): [ENG-47608] add DocFrame, the documentation figure with a full-screen view
* test(storybook): add the linux baselines for the docframe stories
* test(storybook): stop snapshotting the autoplaying DocFrame clip
* feat(webkit): [ENG-47615] add DocUpdate and the heading-nav composable
* test(storybook): add the linux baselines for the docupdate stories
* feat(webkit): [ENG-47561] add DocPrompt, the copyable agent prompt
* test(storybook): add the linux baselines for the docprompt stories
* feat(webkit): [ENG-47610] add DocTooltip, the inline gloss
* test(storybook): add the linux baselines for the doctooltip stories
* fix(webkit): [ENG-47610] let the tooltip headline take its type token's weight
The headline restated font-medium (500), a weight no text token in the theme defines. It now takes text-body-sm verbatim, and the step to the tip below it is carried by ink colour.
* feat(webkit): [ENG-47618] add DocPagination, the documentation prev/next pair
* test(storybook): add the linux baselines for the docpagination stories
* feat(webkit): [ENG-47617] add DocPageHeader, the documentation masthead
* test(storybook): add the linux baselines for the docpageheader stories
* fix(webkit): [ENG-47617] stop the page title being overtaken by its own h2
text-heading-lg runs 18/18/30, so between 640 and 768 the masthead title rendered smaller than the prose h2 under it (20). The title now takes the same descending pair the prose h1 uses — text-heading-2xl with text-heading-xl from sm — which reads 30/30/36 and stays above every heading below it. Closes the theme gap the spec recorded.
The deck stops restating font-light; text-body-md already resolves to 300.
* feat(webkit): [ENG-47616] add DocOnThisPage, the documentation TOC rail
* test(storybook): add the linux baselines for the doconthispage stories
* fix(webkit): [ENG-47616] mark the rail's opening line so a shell can align to it
A docs shell aligns the outline rail against the page bar beside it, and needed a stable thing to measure. data-toc-title marks the rail's first line so the shell reaches it without depending on this component's internal structure.
* style(webkit): sort the use-heading-nav imports
* test(storybook): regenerate the documentation baselines after the typography fixes
---------
Co-authored-by: Gab <gabriel.mendonca@azion.com>
* fix(webkit): copy-button tooltip, card-box header inset, flow attrs (#936)
* fix(webkit): copy-button tooltip, card-box header inset, flow attrs
Three unrelated small fixes.
COPY-BUTTON COMPOSES TOOLTIP. An icon-only button says nothing to a pointer user
until it is hovered, so the glyph was unlabelled in practice — and the
"Copied" confirmation existed only as an accessible-name swap, announced to a
screen reader and invisible to everyone else. One string now names the control
AND labels its tooltip, so the two can never disagree: `ariaLabel` while idle,
`copiedLabel` for the two seconds after a write. The confirmation is visible
because it rides the same state.
CARD-BOX HEADER INSET `sm` → `md`. The header's horizontal padding now matches
the content's `p-(--spacing-md)` and the footer's `px-(--spacing-md)`, so the
title sits on the same vertical line as the first thing under it — and, in a
flush card, as the rows of whatever fills it (ItemList, Accordion and LogView
are all inset `--spacing-md`). At `--spacing-sm` every card header sat 4px left
of its own body.
FLOW FORWARDS `$attrs`. It declared `inheritAttrs: false` and then never spread
`$attrs` on its root, merging only `attrs.class`. Everything else a consumer
passed — `id`, `aria-*`, `data-*`, listeners — was silently dropped, which
`.claude/rules/root-element.md` exists to prevent. Same shape as its siblings
now: `v-bind="$attrs"` on the real root, with `cn` still merging the class.
* test(storybook): regenerate the card-box baselines for the header inset
* chore(webkit): raise the doc-prompt budget for the copy-button tooltip
DocPrompt composes CopyButton, which now wraps its IconButton in the
Tooltip overlay so the icon-only control is legible on hover. The
overlay machinery raises the measured entry from 6.20 KB to 8.61 KB;
the budget moves to 9 KB to carry that composition with headroom.
---------
Co-authored-by: Isaque Böck <isaque.santos@azion.com>
* fix(webkit): emit navigate when a collapsed breadcrumb crumb is picked
`select` is the Dropdown's event, not the option's: `Dropdown.Option`
forwards its activation to the root through the injected context, and the
root is what emits `select(event, value)`. The listener sat on the option,
where it never fired — so picking a collapsed crumb below `md` did
nothing at all. The menu closed and the reader stayed on the page. That is
the whole trail on a phone, since the collapsed shape is the only one a
narrow bar renders.
The test that covered this was skipped as a harness limitation ("@select
does not fire under test interaction"). It was not a harness limitation —
it was this bug, reproducing exactly as a real browser did. The listener
moved to the root and the test is un-skipped.
---------
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
Co-authored-by: Isaque Böck <isaque.santos@azion.com>
isaque-bock-azion added a commit that referenced this pull request Aug 28, 2026
* feat(webkit): let a table column declare a minWidth floor
A bounded column — a chip list, a date, a count, an id — could only be sized by
a fixed `width`, and a fixed width is a bet in both directions: guess high and
the column reserves space the rest of the row needs, guess low and the content
inside it truncates (a chip list ends up as `SQL In…`). `minWidth` only has to
be right about the minimum: the column resolves to the widest of that floor and
what it actually holds — its header, with the sort affordance, and every
rendered body cell. `width` still wins when both are set, so an explicit width
remains the consumer overriding the measurement outright.
ONE width is resolved per column and applied to the header and to every body
cell, which keeps the invariant fixed widths exist for: the column resolves
identically in every row. A per-cell `min-width` would let each row size to its
own content and drift the header away from the body.
It has to be measured, because a flex row cannot express "as wide as the widest
cell in this column" — every row is its own flex container and knows nothing
about its siblings. The measurement is the same clone-based one the resize floor
already uses, so a column's auto width and its drag floor can never disagree.
Three things the measurement has to respect:
- Debounced (32ms). The rendered content changes in bursts — a sort, a page
turn, a column toggled back on — and measuring reads layout, so an
un-coalesced version would reflow per keystroke of a filter.
- Skipped while `loading`. Skeleton rows are not the content; measuring against
them would size every column to a placeholder and never grow it back.
- Re-run on `document.fonts.ready`. Text width is font-dependent and the first
paint can happen on the fallback face.
`autoWidths` joins the row `v-memo` so a resolved width actually reaches the
body, and feeds `frozenWidthOf` so a frozen `minWidth` column contributes its
real width to a neighbour's sticky offset.
* test(storybook): regenerate the table baselines for the column min-width
* fix(webkit): emit navigate when a collapsed breadcrumb crumb is picked (#939)
* feat(theme): add --container-site, the marketing site's shared measure (#923)
* feat(theme): add --container-site, the marketing site's shared measure
The azion.com pages are a single vertical frame: the nav bar, the hero's inner
column, every section below it and the footer all sit on the same centred
column, and the border-x that runs down the page is only continuous because
those four agree to the pixel. A width re-typed per band cannot hold that — the
frame is one decision, so it gets one place to live.
`5xl` (1192px) is a rung of the shared container ladder, not a number chosen for
this page. It is its own token rather than a reference to one of the console
measures because the two answer different questions: a console measure caps a
payload (a table, a form), while this one is the width of a frame whose four
bands have to line up, so retuning either must not move the other.
* refactor(webkit): point the footer column at --container-site (#928)
The footer's column named `--container-5xl` directly. On the marketing site the
nav bar, the hero band, the framed sections and this footer are ONE vertical
frame, and its side rules only meet because all four resolve to the same width —
so the measure has to be a token they share, not a rung each of them names.
Retuning it now moves them together, which a hardcoded rung here could not do.
No visual change: `--container-site` resolves to `--container-5xl`, which is
what the class already said.
* feat(webkit): add the resizable-panel layout primitive (#902)
* feat(webkit): add the resizable-panel layout primitive
A group of adjacent regions whose shared edges the reader can drag — the layout
behind a workspace, where an editor, a terminal under it and a preview beside it
are all content and the split between them is the reader's to set.
It exists because `Sidebar` was being used for both jobs. `Sidebar` is the app's
NAVIGATION rail: it collapses to icons, it hides, it is the thing you leave to go
somewhere. A terminal, a preview and a code editor are not places you navigate to
— they are the work — and dressing two of them as navigation rails produced a
screen with three "sidebars" and one document, on an axis `Sidebar` does not even
have: it has no horizontal edge, so a terminal under an editor had to hand-roll
its own splitter. This component owns the EDGE; `Sidebar` goes back to owning
navigation.
Composition: `ResizablePanel` with `Pane` and `Handle`, attached for dot-notation
and exported standalone. The compound is built by naming the static surface and
casting once rather than with `Object.assign`, whose inferred type walks each
part's local `interface Props` into the declaration and trips TS4082.
Ships the spec, a browser-mode test suite, the story, the `-root` and
per-sub-component exports, and a size budget.
* test(storybook): regenerate linux baselines for resizable-panel
Adds the 24 snapshots for the four new ResizablePanel stories across the 6-mode
matrix. Generated by the Storybook Regenerate Baseline workflow on this branch's
HEAD — baselines are ubuntu-only, so a local run would not match the folder the
visual gate compares against.
No existing snapshot is modified or deleted; the component is additive.
* test(webkit): compose resizable-panel through its named sub-component exports
Every other composition suite in the package renders through the named exports
(`AccordionItem`, `DropdownTrigger`, …) and checks dot-notation once, as an
identity assertion against those same exports. This file was the only one that
rendered through the root's statics — `h(ResizablePanel.Pane, …)` — which made
static analysis flag eight unguarded member accesses: a `.vue` default export is
generated by the SFC compiler, so the analyzer sees no `export default`, reads
the imported binding as undefined, and reports every `.Pane` / `.Handle` read on
it.
Compose through `ResizablePanelPane` / `ResizablePanelHandle` instead, and keep
the compound contract in the one place it is the subject rather than the vehicle,
asserting identity against those exports through a guarded read.
Same nine tests, same behaviour covered — the dot-notation contract is now
verified more tightly than `toBeTruthy` did.
---------
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* feat(webkit): give Select, MultiSelect and Dropdown one shared leading column (#935)
A list that mixes iconed and icon-less options used to run two label edges: an
option with a glyph indented its text, one without it did not, so the eye had no
column to read down. Fixing it per-option is not possible — no option knows
whether any sibling has an icon.
So the reservation is made by the LIST, with a self-triggering `:has()` rule.
Every option renders a fixed leading box and marks itself `data-leading` only
when it actually has a glyph; the box is `hidden` and turns back on under
`group-has-[[data-leading]]/options:flex`. `:has()` matches the marker even
while the box is hidden, so one iconed option is enough to open the column for
the whole list — and a list with no icons at all reserves nothing and pays no
indent. The same `group/options` marker is on all three panels, so Select,
MultiSelect and Dropdown behave identically.
The group label is now OPTIONAL, and sentence-case `text-label-sm` rather than
an uppercase overline: an unlabelled group is a grouping the reader does not
need named, and an overline read as a second heading level inside a panel that
has none.
Group rhythm is `--spacing-sm` above every group but the first — the same step
CommandMenu already puts between two bare groups. Dropdown keeps its hairline
and Select does not: the Figma frame for the Select panel separates groups by
space alone. Dropdown's divider moves to the group root, which spans the panel's
full inner width, so it reads edge-to-edge like the panel's own border; the
horizontal inset the rows need moves onto the content wrapper.
Panels also gain top/bottom slots for content that is not an option (a search
row, a footer action), and the panel's vertical padding steps up one so the
first and last row do not crowd its border.
Includes the 10 dropdown visual baselines this moves.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* fix(icons): resolve currentColor per theme in the colored-icon CSS (#930)
Astro's "A" shipped invisible on every light-mode surface.
Some brand logos are only partly colored: Astro is a fixed red-to-magenta
gradient swoosh under an "A" that is the brand's INK — black on a light
background, white on a dark one. That part is authored as `fill="currentColor"`,
which is what `validate-svg.mjs` asks for, and it resolves correctly wherever
the SVG is INLINED (dist/color-catalog.json → the gallery, Storybook).
A `background-image` is a separate document, so it inherits nothing from the
element painting it. `currentColor` there resolves against the SVG's own root,
not the consumer's text color, and no amount of `color` on the `<i>` reaches it.
Left alone it lands on the UA default and the icon disappears in one theme —
which is what happened: `ai-astro.svg` had the "A" hardcoded `#FAFAFA`, so it
was white-on-white everywhere in light mode.
So the generator resolves it. An icon containing `currentColor` is emitted
twice: the base rule with the light ink, and an override with the dark ink under
the same selectors @aziontech/theme uses for its dark tokens
(`[data-theme=dark]`, `.dark`, `.azion.azion-dark`). The override's extra
selector out-specifies the base, so source order does not decide the winner.
The inks are literals (`#141414` / `#FAFAFA`) rather than `var(--text-default)`
by necessity — same document isolation — and are the theme's own values for that
token in each mode. A fully-colored logo with no ink parts still emits one rule,
exactly as before: 17 colored icons, 1 theme-aware.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* docs(webkit): ship the webkit-create-surface and webkit-errors skills (#895)
* docs(webkit): ship the webkit-create-surface and webkit-errors skills
webkit-create-surface fixes where a create or settings form lives — a first-level
resource creates on a page, anything created inside a resource opens a drawer,
nothing creates in a dialog — plus the anatomy all of them share, what belongs
behind the collapsed Advanced band, and the commit model each surface uses.
webkit-errors is the one decision table for where a failure goes: client-side
validation on the field, a server rejection scoped to one field as a Message in
that section, an unscoped request failure as a toast with Retry, the signed-out
screens where auth never toasts, and the async failure that outlives its screen.
Both join the CLI bundle and the ui-craft routing table; webkit-form and
webkit-ui-states now hand their error rows off to webkit-errors.
* docs(webkit): ship the webkit-microcopy consumer skill (#931)
The writing rules for Azion product interfaces, as a skill the adoption toolkit
ships — so a consuming project's agent writes the same strings the console does
instead of inventing a house style per screen.
It fixes the things that were being decided per-PR: punctuation and
capitalization (no em dash, no ampersand, no parentheses in labels, no bold in
running text, sentence case with Azion product names capitalized), which surface
each kind of copy belongs on (a label names, a description constrains, a Message
explains impact, a Popover defines, a Tag states), one word per concept (Select
not Pick, Application not Edge Application, and no `edge` as a prefix or a
location), navigation and IA naming, and copy that survives every breakpoint.
Registered in the three places a consumer skill has to be registered or it never
reaches a project: the CLI's file list (`plan.js`), the UI-craft pack in
`plan.test.mjs` (now 18 skills), and the routing index in `CLAUDE.fragment.md`,
where it gets its own **Words** line — it is the only skill about strings rather
than structure, and it applies during every other one.
Distinct from the generic `content-microcopy` the plan drops: that one is
house-agnostic, this one carries Azion's own vocabulary.
---------
Co-authored-by: Isaque Böck <isaque.santos@azion.com>
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* feat(theme): add the page-enter and content-enter arrival animations (#896)
* feat(theme): add the page-enter and content-enter arrival animations
The two entrances an app shell needs, as a pair. page-enter is the route
transition: it starts one --layout-boundary-inline to the LEFT of where it lands
and travels right while fading up, so a page reads as arriving FROM the nav row
that was clicked — a plain fade has no origin and a right-side arrival reads as
going back. content-enter is content settling inside a page already on screen, so
it rises a hair instead of travelling, and takes a --content-enter-delay stagger
(with backwards fill, or a delayed follower would paint its landed state first).
Neither fills forwards: page-enter's box is usually the scroll container, and a
lingering translate would make it a containing block for any fixed descendant.
* docs(webkit): teach the two arrival animations in webkit-motion-polish
Documents both utilities and their knobs, and the failure they exist to prevent:
a page arrives once, so a mount-time entrance inside a page whose route
transition is still running lockesteps with it into one diagonal move (identical
opacities frame for frame is the tell). Adds that shape and the two-shells-per-
screen variant to the silent-failure table. catalog.json rebuilt so the two
animations reach the MCP and validate-spec-compliance.
---------
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* docs: rewrite add-animation for the CSS-first theme (#897)
The steps still described the deleted v3 plugin (semantic/animations.js,
addUtilities/addComponents, the camelCase keyframe object form). An animation is
now two edits under primitives/animations/ — the shorthand in animate.js, which
is what makes animate-<name> a utility at all, and the CSS-string keyframes in
keyframes.js, which Tailwind v4 does NOT generate — compiled by build:tokens,
which must run before catalog:build or nothing renders.
Also records the three things the two new arrivals taught: a per-instance value
rides a var() with a token default inside the keyframe, a stagger rides a var()
delay with backwards fill, and fill is a decision — forwards leaves a translate
that makes the element a containing block for any fixed descendant.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* fix(webkit): give table cells block padding so tall content can breathe (#899)
A cell was `min-h-12` with horizontal padding only. For a single-line cell that
is invisible — the 48px minimum decides the height on its own against ~20px of
text — so every existing row renders exactly as before.
It only shows on a cell whose content is TALLER than the minimum: a wrapped list
of tags, a two-line summary. There the height came from the content alone, which
put the first and last lines flush against the row's borders with no breathing
room at all.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* docs(webkit): bring webkit-lists onto the current list-page shape (#929)
The shipped consumer skill still described the list page as it was before three
decisions landed, so a consumer following it built a page the product no longer
looks like:
- A first-level module index opens with a `PageHeading` again, and the module's
create action lives in it. The shell draws the breadcrumb only from the second
crumb up, so at first level the heading is the ONLY place the module's name
appears in the content.
- The size ladder is the hierarchy: the heading's action is the page's one
`large` (40px) control and the whole controls row is `medium` (32px). At 40px
both rows competed and the create button stopped being the page's answer.
- The controls row is narrowing only — `#actions` there is for an action on the
LISTING, not the page's own.
`last_updated` moves to 2026-08-24.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* feat: webkit documentation (#919)
* chore: [ENG-47606] add the documentation component category (#904)
* feat(theme): [ENG-47561] add the docs measure and the prose + mono body registers (#905)
* chore: [ENG-47606] add the documentation component category
* feat(theme): [ENG-47561] add the docs measure and the mono body register
* feat(theme): [ENG-47606] add the prose body register
* fix(theme): [ENG-47606] widen the docs measure to the form column
The documentation column was capped at 752px, narrower than every other measure in the system. Docs pages carry code blocks, card grids and tables, none of which fit a prose measure, so the column now takes the same 1192px the form measure uses.
The token is defined here, so it is set here: the doc component stack carries the same value, and an add/add disagreement between the two made every PR in that stack unmergeable.
* feat(webkit): [ENG-47616] add DocOnThisPage, the documentation TOC rail (#918)
* chore: [ENG-47606] add the documentation component category
* feat(theme): [ENG-47561] add the docs measure and the mono body register
* feat(theme): [ENG-47606] add the prose body register
* feat(webkit): [ENG-47606] add DocProse, the documentation typography contract
* test(storybook): add the linux baselines for the DocProse stories
* fix(theme): [ENG-47606] widen the docs measure to the form column
The documentation column was capped at 752px, narrower than every other measure in the system. Docs pages carry code blocks, card grids and tables, none of which fit a prose measure, so the column now takes the same 1192px the form measure uses.
* fix(webkit): [ENG-47606] pin the prose section step and separate the heading registers
The section rung was a descending pair that read 32 on a phone and on a laptop and only reached 48 at xl, so the same page broke its sections at different gaps by window width. It is now a flat 56 to open and 48 to close, taken from the theme's primitive spacing steps.
Each heading level now names two registers instead of one. On one register per level the base column collapsed: h2 was 16 (the body's own size), h3 and h4 were both 14, and between 640 and 768 the page title was overtaken by its own h2.
Weight and leading are no longer restated here — every element takes its type token verbatim, so the ladder is size and space only.
* feat(webkit): [ENG-47607] add DocCallout, the documentation admonition
* test(storybook): add the linux baselines for the DocProse and DocCallout stories
* fix(webkit): [ENG-47607] step an inline callout link to the only other weight the system defines
font-medium is 500, a weight no text token in the theme uses — the set is light (300) and normal (400). An inline link inside a callout now steps to 400.
* feat(webkit): [ENG-47611] add DocCard, the documentation grid cell
* test(storybook): add the linux baselines for the documentation stories
* feat(webkit): [ENG-47612] add DocCardGroup, the documentation card grid
* test(storybook): add the linux baselines for the doccardgroup stories
* feat(webkit): [ENG-47609] add DocItem, the documentation list row
* test(storybook): add the linux baselines for the docitem stories
* feat(webkit): [ENG-47613] add DocSteps and DocStep, the documentation procedure list
* test(storybook): add the linux baselines for the docsteps stories
* feat(webkit): [ENG-47608] add DocFrame, the documentation figure with a full-screen view
* test(storybook): add the linux baselines for the docframe stories
* test(storybook): stop snapshotting the autoplaying DocFrame clip
* feat(webkit): [ENG-47615] add DocUpdate and the heading-nav composable
* test(storybook): add the linux baselines for the docupdate stories
* feat(webkit): [ENG-47561] add DocPrompt, the copyable agent prompt
* test(storybook): add the linux baselines for the docprompt stories
* feat(webkit): [ENG-47610] add DocTooltip, the inline gloss
* test(storybook): add the linux baselines for the doctooltip stories
* fix(webkit): [ENG-47610] let the tooltip headline take its type token's weight
The headline restated font-medium (500), a weight no text token in the theme defines. It now takes text-body-sm verbatim, and the step to the tip below it is carried by ink colour.
* feat(webkit): [ENG-47618] add DocPagination, the documentation prev/next pair
* test(storybook): add the linux baselines for the docpagination stories
* feat(webkit): [ENG-47617] add DocPageHeader, the documentation masthead
* test(storybook): add the linux baselines for the docpageheader stories
* fix(webkit): [ENG-47617] stop the page title being overtaken by its own h2
text-heading-lg runs 18/18/30, so between 640 and 768 the masthead title rendered smaller than the prose h2 under it (20). The title now takes the same descending pair the prose h1 uses — text-heading-2xl with text-heading-xl from sm — which reads 30/30/36 and stays above every heading below it. Closes the theme gap the spec recorded.
The deck stops restating font-light; text-body-md already resolves to 300.
* feat(webkit): [ENG-47616] add DocOnThisPage, the documentation TOC rail
* test(storybook): add the linux baselines for the doconthispage stories
* fix(webkit): [ENG-47616] mark the rail's opening line so a shell can align to it
A docs shell aligns the outline rail against the page bar beside it, and needed a stable thing to measure. data-toc-title marks the rail's first line so the shell reaches it without depending on this component's internal structure.
* style(webkit): sort the use-heading-nav imports
* test(storybook): regenerate the documentation baselines after the typography fixes
---------
Co-authored-by: Gab <gabriel.mendonca@azion.com>
* fix(webkit): copy-button tooltip, card-box header inset, flow attrs (#936)
* fix(webkit): copy-button tooltip, card-box header inset, flow attrs
Three unrelated small fixes.
COPY-BUTTON COMPOSES TOOLTIP. An icon-only button says nothing to a pointer user
until it is hovered, so the glyph was unlabelled in practice — and the
"Copied" confirmation existed only as an accessible-name swap, announced to a
screen reader and invisible to everyone else. One string now names the control
AND labels its tooltip, so the two can never disagree: `ariaLabel` while idle,
`copiedLabel` for the two seconds after a write. The confirmation is visible
because it rides the same state.
CARD-BOX HEADER INSET `sm` → `md`. The header's horizontal padding now matches
the content's `p-(--spacing-md)` and the footer's `px-(--spacing-md)`, so the
title sits on the same vertical line as the first thing under it — and, in a
flush card, as the rows of whatever fills it (ItemList, Accordion and LogView
are all inset `--spacing-md`). At `--spacing-sm` every card header sat 4px left
of its own body.
FLOW FORWARDS `$attrs`. It declared `inheritAttrs: false` and then never spread
`$attrs` on its root, merging only `attrs.class`. Everything else a consumer
passed — `id`, `aria-*`, `data-*`, listeners — was silently dropped, which
`.claude/rules/root-element.md` exists to prevent. Same shape as its siblings
now: `v-bind="$attrs"` on the real root, with `cn` still merging the class.
* test(storybook): regenerate the card-box baselines for the header inset
* chore(webkit): raise the doc-prompt budget for the copy-button tooltip
DocPrompt composes CopyButton, which now wraps its IconButton in the
Tooltip overlay so the icon-only control is legible on hover. The
overlay machinery raises the measured entry from 6.20 KB to 8.61 KB;
the budget moves to 9 KB to carry that composition with headroom.
---------
Co-authored-by: Isaque Böck <isaque.santos@azion.com>
* fix(webkit): emit navigate when a collapsed breadcrumb crumb is picked
`select` is the Dropdown's event, not the option's: `Dropdown.Option`
forwards its activation to the root through the injected context, and the
root is what emits `select(event, value)`. The listener sat on the option,
where it never fired — so picking a collapsed crumb below `md` did
nothing at all. The menu closed and the reader stayed on the page. That is
the whole trail on a phone, since the collapsed shape is the only one a
narrow bar renders.
The test that covered this was skipped as a harness limitation ("@select
does not fire under test interaction"). It was not a harness limitation —
it was this bug, reproducing exactly as a real browser did. The listener
moved to the root and the test is un-skipped.
---------
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
Co-authored-by: Isaque Böck <isaque.santos@azion.com>
---------
Co-authored-by: Isaque dos Santos <isaque.santos@azion.com>
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@gabriel-lisboa-azion@isaque-bock-azion
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(theme): [ENG-47561] add the docs measure and the prose + mono body registers - #905

Merged
isaque-bock-azion merged 3 commits into
feat/webkit-documentationfrom
feat/ENG-47561-docs-measure-and-mono-body
Aug 24, 2026
Merged

feat(theme): [ENG-47561] add the docs measure and the prose + mono body registers#905
isaque-bock-azion merged 3 commits into
feat/webkit-documentationfrom
feat/ENG-47561-docs-measure-and-mono-body

Conversation

@gabriel-lisboa-azion

@gabriel-lisboa-aziongabriel-lisboa-azion commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • text-body-prose-md — body copy at --leading-relaxed (1.625). The whole text-body-* set is snug (1.375), and the documentation prose ladder was fitted against a 26px line box, so prose needs its own register. Line-height belongs in the token: check-authoring's leading-raw check deliberately rejects a leading-* override even in token form.
  • text-body-code-sm — the mono body register. The label-code-* set is leading-none, built for one-row-per-line code; this one wraps. Required by DocPrompt.
  • layout-measure-docs (--container-2xl, 752px) + layout-column-docs, completing the measure family (column / focused / form / form-create / docs). Docs are the one column capped by typography rather than payload width. The utility is generated by COLUMN_MEASURE + the columnUtility factory, so two entries produce the whole class.

How to test

  1. node packages/theme/src/scripts/build-tokens.mjs then git diff --stat packages/theme/dist — expect no further change; the committed dist is already current.
  2. cd packages/theme && node --test "test/**/*.test.mjs" — 12/12 pass, including the zero-unit assertions over every emitted token.
  3. grep "text-body-prose-md-line-height\|text-body-md-line-height" packages/theme/dist/v4/globals.css — expect 1.625 and 1.375 respectively; that difference is the whole point of the new token.
  4. grep -A5 '@utility text-body-code-sm' packages/theme/dist/v4/globals.css and the same for layout-column-docs — each must emit a real rule body. A token that compiles to nothing is the silent failure mode here.
  5. grep "layout-measure':" packages/theme/src/tokens/semantic/layouts.data.js — must still read var(--container-7xl) / 1620px (see Notes).

Notes

  • Deliberately excluded: the working branch this was carved from also retunes layout-measure from --container-7xl (1620px) to --container-6xl (1388px), moving home from the focused measure to the data measure. That is a separate product decision that would have silently changed the width of every data page, so it is not here — layout-measure and layout-measure-focused keep main's values and main's prose.
  • Stacked on chore: [ENG-47606] add the documentation component category #904 (the documentation category), which DocProse also needs. Merge chore: [ENG-47606] add the documentation component category #904 first; this retargets automatically.
  • No component consumes layout-column-docs yet: DocProse deliberately has no measure of its own, so the consumer caps the column.
  • Related: ENG-47561, ENG-47606, and the sibling component issues.

Delivery

The Documentation family is delivered on the feature branch feat/docs-components-bundle, not piecemeal to main — so main takes the whole family as one merge, while review stays per-component.

Merge order, bottom-up — each PR's base is the one before it, and each retargets automatically as its parent lands:

#904#905#906#907#908#909#910#911#912#913#914#915#916#917#918feat/docs-components-bundlemain

#904the documentation category (standards only)
#905theme: the docs measure + the prose and mono body registers
#906#918DocProse · DocCallout · DocCard · DocCardGroup · DocItem · DocSteps + DocStep · DocFrame · DocUpdate · DocPrompt · DocTooltip · DocPagination · DocPageHeader · DocOnThisPage

@gabriel-lisboa-aziongabriel-lisboa-azion changed the title feat(theme): [ENG-47561] add the docs measure and the mono body registerfeat(theme): [ENG-47561] add the docs measure and the prose + mono body registersAug 22, 2026
@gabriel-lisboa-azion
gabriel-lisboa-azionforce-pushed the feat/ENG-47561-docs-measure-and-mono-body branch from d3d46c0 to bc6575cCompareAugust 22, 2026 14:11
@gabriel-lisboa-azion
gabriel-lisboa-azion changed the base branch from main to chore/ENG-47606-documentation-categoryAugust 22, 2026 14:11
@isaque-bock-azion
isaque-bock-azion changed the base branch from chore/ENG-47606-documentation-category to feat/webkit-documentationAugust 24, 2026 13:04
@isaque-bock-azion
isaque-bock-azion merged commit e864f62 into feat/webkit-documentationAug 24, 2026
47 of 51 checks passed
@isaque-bock-azion
isaque-bock-azion deleted the feat/ENG-47561-docs-measure-and-mono-body branch August 24, 2026 13:14
isaque-bock-azion added a commit that referenced this pull request Aug 26, 2026
* chore: [ENG-47606] add the documentation component category (#904)
* feat(theme): [ENG-47561] add the docs measure and the prose + mono body registers (#905)
* chore: [ENG-47606] add the documentation component category
* feat(theme): [ENG-47561] add the docs measure and the mono body register
* feat(theme): [ENG-47606] add the prose body register
* fix(theme): [ENG-47606] widen the docs measure to the form column
The documentation column was capped at 752px, narrower than every other measure in the system. Docs pages carry code blocks, card grids and tables, none of which fit a prose measure, so the column now takes the same 1192px the form measure uses.
The token is defined here, so it is set here: the doc component stack carries the same value, and an add/add disagreement between the two made every PR in that stack unmergeable.
* feat(webkit): [ENG-47616] add DocOnThisPage, the documentation TOC rail (#918)
* chore: [ENG-47606] add the documentation component category
* feat(theme): [ENG-47561] add the docs measure and the mono body register
* feat(theme): [ENG-47606] add the prose body register
* feat(webkit): [ENG-47606] add DocProse, the documentation typography contract
* test(storybook): add the linux baselines for the DocProse stories
* fix(theme): [ENG-47606] widen the docs measure to the form column
The documentation column was capped at 752px, narrower than every other measure in the system. Docs pages carry code blocks, card grids and tables, none of which fit a prose measure, so the column now takes the same 1192px the form measure uses.
* fix(webkit): [ENG-47606] pin the prose section step and separate the heading registers
The section rung was a descending pair that read 32 on a phone and on a laptop and only reached 48 at xl, so the same page broke its sections at different gaps by window width. It is now a flat 56 to open and 48 to close, taken from the theme's primitive spacing steps.
Each heading level now names two registers instead of one. On one register per level the base column collapsed: h2 was 16 (the body's own size), h3 and h4 were both 14, and between 640 and 768 the page title was overtaken by its own h2.
Weight and leading are no longer restated here — every element takes its type token verbatim, so the ladder is size and space only.
* feat(webkit): [ENG-47607] add DocCallout, the documentation admonition
* test(storybook): add the linux baselines for the DocProse and DocCallout stories
* fix(webkit): [ENG-47607] step an inline callout link to the only other weight the system defines
font-medium is 500, a weight no text token in the theme uses — the set is light (300) and normal (400). An inline link inside a callout now steps to 400.
* feat(webkit): [ENG-47611] add DocCard, the documentation grid cell
* test(storybook): add the linux baselines for the documentation stories
* feat(webkit): [ENG-47612] add DocCardGroup, the documentation card grid
* test(storybook): add the linux baselines for the doccardgroup stories
* feat(webkit): [ENG-47609] add DocItem, the documentation list row
* test(storybook): add the linux baselines for the docitem stories
* feat(webkit): [ENG-47613] add DocSteps and DocStep, the documentation procedure list
* test(storybook): add the linux baselines for the docsteps stories
* feat(webkit): [ENG-47608] add DocFrame, the documentation figure with a full-screen view
* test(storybook): add the linux baselines for the docframe stories
* test(storybook): stop snapshotting the autoplaying DocFrame clip
* feat(webkit): [ENG-47615] add DocUpdate and the heading-nav composable
* test(storybook): add the linux baselines for the docupdate stories
* feat(webkit): [ENG-47561] add DocPrompt, the copyable agent prompt
* test(storybook): add the linux baselines for the docprompt stories
* feat(webkit): [ENG-47610] add DocTooltip, the inline gloss
* test(storybook): add the linux baselines for the doctooltip stories
* fix(webkit): [ENG-47610] let the tooltip headline take its type token's weight
The headline restated font-medium (500), a weight no text token in the theme defines. It now takes text-body-sm verbatim, and the step to the tip below it is carried by ink colour.
* feat(webkit): [ENG-47618] add DocPagination, the documentation prev/next pair
* test(storybook): add the linux baselines for the docpagination stories
* feat(webkit): [ENG-47617] add DocPageHeader, the documentation masthead
* test(storybook): add the linux baselines for the docpageheader stories
* fix(webkit): [ENG-47617] stop the page title being overtaken by its own h2
text-heading-lg runs 18/18/30, so between 640 and 768 the masthead title rendered smaller than the prose h2 under it (20). The title now takes the same descending pair the prose h1 uses — text-heading-2xl with text-heading-xl from sm — which reads 30/30/36 and stays above every heading below it. Closes the theme gap the spec recorded.
The deck stops restating font-light; text-body-md already resolves to 300.
* feat(webkit): [ENG-47616] add DocOnThisPage, the documentation TOC rail
* test(storybook): add the linux baselines for the doconthispage stories
* fix(webkit): [ENG-47616] mark the rail's opening line so a shell can align to it
A docs shell aligns the outline rail against the page bar beside it, and needed a stable thing to measure. data-toc-title marks the rail's first line so the shell reaches it without depending on this component's internal structure.
* style(webkit): sort the use-heading-nav imports
* test(storybook): regenerate the documentation baselines after the typography fixes
---------
Co-authored-by: Gab <gabriel.mendonca@azion.com>
isaque-bock-azion added a commit that referenced this pull request Aug 28, 2026
#939)
* feat(theme): add --container-site, the marketing site's shared measure (#923)
* feat(theme): add --container-site, the marketing site's shared measure
The azion.com pages are a single vertical frame: the nav bar, the hero's inner
column, every section below it and the footer all sit on the same centred
column, and the border-x that runs down the page is only continuous because
those four agree to the pixel. A width re-typed per band cannot hold that — the
frame is one decision, so it gets one place to live.
`5xl` (1192px) is a rung of the shared container ladder, not a number chosen for
this page. It is its own token rather than a reference to one of the console
measures because the two answer different questions: a console measure caps a
payload (a table, a form), while this one is the width of a frame whose four
bands have to line up, so retuning either must not move the other.
* refactor(webkit): point the footer column at --container-site (#928)
The footer's column named `--container-5xl` directly. On the marketing site the
nav bar, the hero band, the framed sections and this footer are ONE vertical
frame, and its side rules only meet because all four resolve to the same width —
so the measure has to be a token they share, not a rung each of them names.
Retuning it now moves them together, which a hardcoded rung here could not do.
No visual change: `--container-site` resolves to `--container-5xl`, which is
what the class already said.
* feat(webkit): add the resizable-panel layout primitive (#902)
* feat(webkit): add the resizable-panel layout primitive
A group of adjacent regions whose shared edges the reader can drag — the layout
behind a workspace, where an editor, a terminal under it and a preview beside it
are all content and the split between them is the reader's to set.
It exists because `Sidebar` was being used for both jobs. `Sidebar` is the app's
NAVIGATION rail: it collapses to icons, it hides, it is the thing you leave to go
somewhere. A terminal, a preview and a code editor are not places you navigate to
— they are the work — and dressing two of them as navigation rails produced a
screen with three "sidebars" and one document, on an axis `Sidebar` does not even
have: it has no horizontal edge, so a terminal under an editor had to hand-roll
its own splitter. This component owns the EDGE; `Sidebar` goes back to owning
navigation.
Composition: `ResizablePanel` with `Pane` and `Handle`, attached for dot-notation
and exported standalone. The compound is built by naming the static surface and
casting once rather than with `Object.assign`, whose inferred type walks each
part's local `interface Props` into the declaration and trips TS4082.
Ships the spec, a browser-mode test suite, the story, the `-root` and
per-sub-component exports, and a size budget.
* test(storybook): regenerate linux baselines for resizable-panel
Adds the 24 snapshots for the four new ResizablePanel stories across the 6-mode
matrix. Generated by the Storybook Regenerate Baseline workflow on this branch's
HEAD — baselines are ubuntu-only, so a local run would not match the folder the
visual gate compares against.
No existing snapshot is modified or deleted; the component is additive.
* test(webkit): compose resizable-panel through its named sub-component exports
Every other composition suite in the package renders through the named exports
(`AccordionItem`, `DropdownTrigger`, …) and checks dot-notation once, as an
identity assertion against those same exports. This file was the only one that
rendered through the root's statics — `h(ResizablePanel.Pane, …)` — which made
static analysis flag eight unguarded member accesses: a `.vue` default export is
generated by the SFC compiler, so the analyzer sees no `export default`, reads
the imported binding as undefined, and reports every `.Pane` / `.Handle` read on
it.
Compose through `ResizablePanelPane` / `ResizablePanelHandle` instead, and keep
the compound contract in the one place it is the subject rather than the vehicle,
asserting identity against those exports through a guarded read.
Same nine tests, same behaviour covered — the dot-notation contract is now
verified more tightly than `toBeTruthy` did.
---------
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* feat(webkit): give Select, MultiSelect and Dropdown one shared leading column (#935)
A list that mixes iconed and icon-less options used to run two label edges: an
option with a glyph indented its text, one without it did not, so the eye had no
column to read down. Fixing it per-option is not possible — no option knows
whether any sibling has an icon.
So the reservation is made by the LIST, with a self-triggering `:has()` rule.
Every option renders a fixed leading box and marks itself `data-leading` only
when it actually has a glyph; the box is `hidden` and turns back on under
`group-has-[[data-leading]]/options:flex`. `:has()` matches the marker even
while the box is hidden, so one iconed option is enough to open the column for
the whole list — and a list with no icons at all reserves nothing and pays no
indent. The same `group/options` marker is on all three panels, so Select,
MultiSelect and Dropdown behave identically.
The group label is now OPTIONAL, and sentence-case `text-label-sm` rather than
an uppercase overline: an unlabelled group is a grouping the reader does not
need named, and an overline read as a second heading level inside a panel that
has none.
Group rhythm is `--spacing-sm` above every group but the first — the same step
CommandMenu already puts between two bare groups. Dropdown keeps its hairline
and Select does not: the Figma frame for the Select panel separates groups by
space alone. Dropdown's divider moves to the group root, which spans the panel's
full inner width, so it reads edge-to-edge like the panel's own border; the
horizontal inset the rows need moves onto the content wrapper.
Panels also gain top/bottom slots for content that is not an option (a search
row, a footer action), and the panel's vertical padding steps up one so the
first and last row do not crowd its border.
Includes the 10 dropdown visual baselines this moves.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* fix(icons): resolve currentColor per theme in the colored-icon CSS (#930)
Astro's "A" shipped invisible on every light-mode surface.
Some brand logos are only partly colored: Astro is a fixed red-to-magenta
gradient swoosh under an "A" that is the brand's INK — black on a light
background, white on a dark one. That part is authored as `fill="currentColor"`,
which is what `validate-svg.mjs` asks for, and it resolves correctly wherever
the SVG is INLINED (dist/color-catalog.json → the gallery, Storybook).
A `background-image` is a separate document, so it inherits nothing from the
element painting it. `currentColor` there resolves against the SVG's own root,
not the consumer's text color, and no amount of `color` on the `<i>` reaches it.
Left alone it lands on the UA default and the icon disappears in one theme —
which is what happened: `ai-astro.svg` had the "A" hardcoded `#FAFAFA`, so it
was white-on-white everywhere in light mode.
So the generator resolves it. An icon containing `currentColor` is emitted
twice: the base rule with the light ink, and an override with the dark ink under
the same selectors @aziontech/theme uses for its dark tokens
(`[data-theme=dark]`, `.dark`, `.azion.azion-dark`). The override's extra
selector out-specifies the base, so source order does not decide the winner.
The inks are literals (`#141414` / `#FAFAFA`) rather than `var(--text-default)`
by necessity — same document isolation — and are the theme's own values for that
token in each mode. A fully-colored logo with no ink parts still emits one rule,
exactly as before: 17 colored icons, 1 theme-aware.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* docs(webkit): ship the webkit-create-surface and webkit-errors skills (#895)
* docs(webkit): ship the webkit-create-surface and webkit-errors skills
webkit-create-surface fixes where a create or settings form lives — a first-level
resource creates on a page, anything created inside a resource opens a drawer,
nothing creates in a dialog — plus the anatomy all of them share, what belongs
behind the collapsed Advanced band, and the commit model each surface uses.
webkit-errors is the one decision table for where a failure goes: client-side
validation on the field, a server rejection scoped to one field as a Message in
that section, an unscoped request failure as a toast with Retry, the signed-out
screens where auth never toasts, and the async failure that outlives its screen.
Both join the CLI bundle and the ui-craft routing table; webkit-form and
webkit-ui-states now hand their error rows off to webkit-errors.
* docs(webkit): ship the webkit-microcopy consumer skill (#931)
The writing rules for Azion product interfaces, as a skill the adoption toolkit
ships — so a consuming project's agent writes the same strings the console does
instead of inventing a house style per screen.
It fixes the things that were being decided per-PR: punctuation and
capitalization (no em dash, no ampersand, no parentheses in labels, no bold in
running text, sentence case with Azion product names capitalized), which surface
each kind of copy belongs on (a label names, a description constrains, a Message
explains impact, a Popover defines, a Tag states), one word per concept (Select
not Pick, Application not Edge Application, and no `edge` as a prefix or a
location), navigation and IA naming, and copy that survives every breakpoint.
Registered in the three places a consumer skill has to be registered or it never
reaches a project: the CLI's file list (`plan.js`), the UI-craft pack in
`plan.test.mjs` (now 18 skills), and the routing index in `CLAUDE.fragment.md`,
where it gets its own **Words** line — it is the only skill about strings rather
than structure, and it applies during every other one.
Distinct from the generic `content-microcopy` the plan drops: that one is
house-agnostic, this one carries Azion's own vocabulary.
---------
Co-authored-by: Isaque Böck <isaque.santos@azion.com>
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* feat(theme): add the page-enter and content-enter arrival animations (#896)
* feat(theme): add the page-enter and content-enter arrival animations
The two entrances an app shell needs, as a pair. page-enter is the route
transition: it starts one --layout-boundary-inline to the LEFT of where it lands
and travels right while fading up, so a page reads as arriving FROM the nav row
that was clicked — a plain fade has no origin and a right-side arrival reads as
going back. content-enter is content settling inside a page already on screen, so
it rises a hair instead of travelling, and takes a --content-enter-delay stagger
(with backwards fill, or a delayed follower would paint its landed state first).
Neither fills forwards: page-enter's box is usually the scroll container, and a
lingering translate would make it a containing block for any fixed descendant.
* docs(webkit): teach the two arrival animations in webkit-motion-polish
Documents both utilities and their knobs, and the failure they exist to prevent:
a page arrives once, so a mount-time entrance inside a page whose route
transition is still running lockesteps with it into one diagonal move (identical
opacities frame for frame is the tell). Adds that shape and the two-shells-per-
screen variant to the silent-failure table. catalog.json rebuilt so the two
animations reach the MCP and validate-spec-compliance.
---------
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* docs: rewrite add-animation for the CSS-first theme (#897)
The steps still described the deleted v3 plugin (semantic/animations.js,
addUtilities/addComponents, the camelCase keyframe object form). An animation is
now two edits under primitives/animations/ — the shorthand in animate.js, which
is what makes animate-<name> a utility at all, and the CSS-string keyframes in
keyframes.js, which Tailwind v4 does NOT generate — compiled by build:tokens,
which must run before catalog:build or nothing renders.
Also records the three things the two new arrivals taught: a per-instance value
rides a var() with a token default inside the keyframe, a stagger rides a var()
delay with backwards fill, and fill is a decision — forwards leaves a translate
that makes the element a containing block for any fixed descendant.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* fix(webkit): give table cells block padding so tall content can breathe (#899)
A cell was `min-h-12` with horizontal padding only. For a single-line cell that
is invisible — the 48px minimum decides the height on its own against ~20px of
text — so every existing row renders exactly as before.
It only shows on a cell whose content is TALLER than the minimum: a wrapped list
of tags, a two-line summary. There the height came from the content alone, which
put the first and last lines flush against the row's borders with no breathing
room at all.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* docs(webkit): bring webkit-lists onto the current list-page shape (#929)
The shipped consumer skill still described the list page as it was before three
decisions landed, so a consumer following it built a page the product no longer
looks like:
- A first-level module index opens with a `PageHeading` again, and the module's
create action lives in it. The shell draws the breadcrumb only from the second
crumb up, so at first level the heading is the ONLY place the module's name
appears in the content.
- The size ladder is the hierarchy: the heading's action is the page's one
`large` (40px) control and the whole controls row is `medium` (32px). At 40px
both rows competed and the create button stopped being the page's answer.
- The controls row is narrowing only — `#actions` there is for an action on the
LISTING, not the page's own.
`last_updated` moves to 2026-08-24.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* feat: webkit documentation (#919)
* chore: [ENG-47606] add the documentation component category (#904)
* feat(theme): [ENG-47561] add the docs measure and the prose + mono body registers (#905)
* chore: [ENG-47606] add the documentation component category
* feat(theme): [ENG-47561] add the docs measure and the mono body register
* feat(theme): [ENG-47606] add the prose body register
* fix(theme): [ENG-47606] widen the docs measure to the form column
The documentation column was capped at 752px, narrower than every other measure in the system. Docs pages carry code blocks, card grids and tables, none of which fit a prose measure, so the column now takes the same 1192px the form measure uses.
The token is defined here, so it is set here: the doc component stack carries the same value, and an add/add disagreement between the two made every PR in that stack unmergeable.
* feat(webkit): [ENG-47616] add DocOnThisPage, the documentation TOC rail (#918)
* chore: [ENG-47606] add the documentation component category
* feat(theme): [ENG-47561] add the docs measure and the mono body register
* feat(theme): [ENG-47606] add the prose body register
* feat(webkit): [ENG-47606] add DocProse, the documentation typography contract
* test(storybook): add the linux baselines for the DocProse stories
* fix(theme): [ENG-47606] widen the docs measure to the form column
The documentation column was capped at 752px, narrower than every other measure in the system. Docs pages carry code blocks, card grids and tables, none of which fit a prose measure, so the column now takes the same 1192px the form measure uses.
* fix(webkit): [ENG-47606] pin the prose section step and separate the heading registers
The section rung was a descending pair that read 32 on a phone and on a laptop and only reached 48 at xl, so the same page broke its sections at different gaps by window width. It is now a flat 56 to open and 48 to close, taken from the theme's primitive spacing steps.
Each heading level now names two registers instead of one. On one register per level the base column collapsed: h2 was 16 (the body's own size), h3 and h4 were both 14, and between 640 and 768 the page title was overtaken by its own h2.
Weight and leading are no longer restated here — every element takes its type token verbatim, so the ladder is size and space only.
* feat(webkit): [ENG-47607] add DocCallout, the documentation admonition
* test(storybook): add the linux baselines for the DocProse and DocCallout stories
* fix(webkit): [ENG-47607] step an inline callout link to the only other weight the system defines
font-medium is 500, a weight no text token in the theme uses — the set is light (300) and normal (400). An inline link inside a callout now steps to 400.
* feat(webkit): [ENG-47611] add DocCard, the documentation grid cell
* test(storybook): add the linux baselines for the documentation stories
* feat(webkit): [ENG-47612] add DocCardGroup, the documentation card grid
* test(storybook): add the linux baselines for the doccardgroup stories
* feat(webkit): [ENG-47609] add DocItem, the documentation list row
* test(storybook): add the linux baselines for the docitem stories
* feat(webkit): [ENG-47613] add DocSteps and DocStep, the documentation procedure list
* test(storybook): add the linux baselines for the docsteps stories
* feat(webkit): [ENG-47608] add DocFrame, the documentation figure with a full-screen view
* test(storybook): add the linux baselines for the docframe stories
* test(storybook): stop snapshotting the autoplaying DocFrame clip
* feat(webkit): [ENG-47615] add DocUpdate and the heading-nav composable
* test(storybook): add the linux baselines for the docupdate stories
* feat(webkit): [ENG-47561] add DocPrompt, the copyable agent prompt
* test(storybook): add the linux baselines for the docprompt stories
* feat(webkit): [ENG-47610] add DocTooltip, the inline gloss
* test(storybook): add the linux baselines for the doctooltip stories
* fix(webkit): [ENG-47610] let the tooltip headline take its type token's weight
The headline restated font-medium (500), a weight no text token in the theme defines. It now takes text-body-sm verbatim, and the step to the tip below it is carried by ink colour.
* feat(webkit): [ENG-47618] add DocPagination, the documentation prev/next pair
* test(storybook): add the linux baselines for the docpagination stories
* feat(webkit): [ENG-47617] add DocPageHeader, the documentation masthead
* test(storybook): add the linux baselines for the docpageheader stories
* fix(webkit): [ENG-47617] stop the page title being overtaken by its own h2
text-heading-lg runs 18/18/30, so between 640 and 768 the masthead title rendered smaller than the prose h2 under it (20). The title now takes the same descending pair the prose h1 uses — text-heading-2xl with text-heading-xl from sm — which reads 30/30/36 and stays above every heading below it. Closes the theme gap the spec recorded.
The deck stops restating font-light; text-body-md already resolves to 300.
* feat(webkit): [ENG-47616] add DocOnThisPage, the documentation TOC rail
* test(storybook): add the linux baselines for the doconthispage stories
* fix(webkit): [ENG-47616] mark the rail's opening line so a shell can align to it
A docs shell aligns the outline rail against the page bar beside it, and needed a stable thing to measure. data-toc-title marks the rail's first line so the shell reaches it without depending on this component's internal structure.
* style(webkit): sort the use-heading-nav imports
* test(storybook): regenerate the documentation baselines after the typography fixes
---------
Co-authored-by: Gab <gabriel.mendonca@azion.com>
* fix(webkit): copy-button tooltip, card-box header inset, flow attrs (#936)
* fix(webkit): copy-button tooltip, card-box header inset, flow attrs
Three unrelated small fixes.
COPY-BUTTON COMPOSES TOOLTIP. An icon-only button says nothing to a pointer user
until it is hovered, so the glyph was unlabelled in practice — and the
"Copied" confirmation existed only as an accessible-name swap, announced to a
screen reader and invisible to everyone else. One string now names the control
AND labels its tooltip, so the two can never disagree: `ariaLabel` while idle,
`copiedLabel` for the two seconds after a write. The confirmation is visible
because it rides the same state.
CARD-BOX HEADER INSET `sm` → `md`. The header's horizontal padding now matches
the content's `p-(--spacing-md)` and the footer's `px-(--spacing-md)`, so the
title sits on the same vertical line as the first thing under it — and, in a
flush card, as the rows of whatever fills it (ItemList, Accordion and LogView
are all inset `--spacing-md`). At `--spacing-sm` every card header sat 4px left
of its own body.
FLOW FORWARDS `$attrs`. It declared `inheritAttrs: false` and then never spread
`$attrs` on its root, merging only `attrs.class`. Everything else a consumer
passed — `id`, `aria-*`, `data-*`, listeners — was silently dropped, which
`.claude/rules/root-element.md` exists to prevent. Same shape as its siblings
now: `v-bind="$attrs"` on the real root, with `cn` still merging the class.
* test(storybook): regenerate the card-box baselines for the header inset
* chore(webkit): raise the doc-prompt budget for the copy-button tooltip
DocPrompt composes CopyButton, which now wraps its IconButton in the
Tooltip overlay so the icon-only control is legible on hover. The
overlay machinery raises the measured entry from 6.20 KB to 8.61 KB;
the budget moves to 9 KB to carry that composition with headroom.
---------
Co-authored-by: Isaque Böck <isaque.santos@azion.com>
* fix(webkit): emit navigate when a collapsed breadcrumb crumb is picked
`select` is the Dropdown's event, not the option's: `Dropdown.Option`
forwards its activation to the root through the injected context, and the
root is what emits `select(event, value)`. The listener sat on the option,
where it never fired — so picking a collapsed crumb below `md` did
nothing at all. The menu closed and the reader stayed on the page. That is
the whole trail on a phone, since the collapsed shape is the only one a
narrow bar renders.
The test that covered this was skipped as a harness limitation ("@select
does not fire under test interaction"). It was not a harness limitation —
it was this bug, reproducing exactly as a real browser did. The listener
moved to the root and the test is un-skipped.
---------
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
Co-authored-by: Isaque Böck <isaque.santos@azion.com>
isaque-bock-azion added a commit that referenced this pull request Aug 28, 2026
* feat(webkit): let a table column declare a minWidth floor
A bounded column — a chip list, a date, a count, an id — could only be sized by
a fixed `width`, and a fixed width is a bet in both directions: guess high and
the column reserves space the rest of the row needs, guess low and the content
inside it truncates (a chip list ends up as `SQL In…`). `minWidth` only has to
be right about the minimum: the column resolves to the widest of that floor and
what it actually holds — its header, with the sort affordance, and every
rendered body cell. `width` still wins when both are set, so an explicit width
remains the consumer overriding the measurement outright.
ONE width is resolved per column and applied to the header and to every body
cell, which keeps the invariant fixed widths exist for: the column resolves
identically in every row. A per-cell `min-width` would let each row size to its
own content and drift the header away from the body.
It has to be measured, because a flex row cannot express "as wide as the widest
cell in this column" — every row is its own flex container and knows nothing
about its siblings. The measurement is the same clone-based one the resize floor
already uses, so a column's auto width and its drag floor can never disagree.
Three things the measurement has to respect:
- Debounced (32ms). The rendered content changes in bursts — a sort, a page
turn, a column toggled back on — and measuring reads layout, so an
un-coalesced version would reflow per keystroke of a filter.
- Skipped while `loading`. Skeleton rows are not the content; measuring against
them would size every column to a placeholder and never grow it back.
- Re-run on `document.fonts.ready`. Text width is font-dependent and the first
paint can happen on the fallback face.
`autoWidths` joins the row `v-memo` so a resolved width actually reaches the
body, and feeds `frozenWidthOf` so a frozen `minWidth` column contributes its
real width to a neighbour's sticky offset.
* test(storybook): regenerate the table baselines for the column min-width
* fix(webkit): emit navigate when a collapsed breadcrumb crumb is picked (#939)
* feat(theme): add --container-site, the marketing site's shared measure (#923)
* feat(theme): add --container-site, the marketing site's shared measure
The azion.com pages are a single vertical frame: the nav bar, the hero's inner
column, every section below it and the footer all sit on the same centred
column, and the border-x that runs down the page is only continuous because
those four agree to the pixel. A width re-typed per band cannot hold that — the
frame is one decision, so it gets one place to live.
`5xl` (1192px) is a rung of the shared container ladder, not a number chosen for
this page. It is its own token rather than a reference to one of the console
measures because the two answer different questions: a console measure caps a
payload (a table, a form), while this one is the width of a frame whose four
bands have to line up, so retuning either must not move the other.
* refactor(webkit): point the footer column at --container-site (#928)
The footer's column named `--container-5xl` directly. On the marketing site the
nav bar, the hero band, the framed sections and this footer are ONE vertical
frame, and its side rules only meet because all four resolve to the same width —
so the measure has to be a token they share, not a rung each of them names.
Retuning it now moves them together, which a hardcoded rung here could not do.
No visual change: `--container-site` resolves to `--container-5xl`, which is
what the class already said.
* feat(webkit): add the resizable-panel layout primitive (#902)
* feat(webkit): add the resizable-panel layout primitive
A group of adjacent regions whose shared edges the reader can drag — the layout
behind a workspace, where an editor, a terminal under it and a preview beside it
are all content and the split between them is the reader's to set.
It exists because `Sidebar` was being used for both jobs. `Sidebar` is the app's
NAVIGATION rail: it collapses to icons, it hides, it is the thing you leave to go
somewhere. A terminal, a preview and a code editor are not places you navigate to
— they are the work — and dressing two of them as navigation rails produced a
screen with three "sidebars" and one document, on an axis `Sidebar` does not even
have: it has no horizontal edge, so a terminal under an editor had to hand-roll
its own splitter. This component owns the EDGE; `Sidebar` goes back to owning
navigation.
Composition: `ResizablePanel` with `Pane` and `Handle`, attached for dot-notation
and exported standalone. The compound is built by naming the static surface and
casting once rather than with `Object.assign`, whose inferred type walks each
part's local `interface Props` into the declaration and trips TS4082.
Ships the spec, a browser-mode test suite, the story, the `-root` and
per-sub-component exports, and a size budget.
* test(storybook): regenerate linux baselines for resizable-panel
Adds the 24 snapshots for the four new ResizablePanel stories across the 6-mode
matrix. Generated by the Storybook Regenerate Baseline workflow on this branch's
HEAD — baselines are ubuntu-only, so a local run would not match the folder the
visual gate compares against.
No existing snapshot is modified or deleted; the component is additive.
* test(webkit): compose resizable-panel through its named sub-component exports
Every other composition suite in the package renders through the named exports
(`AccordionItem`, `DropdownTrigger`, …) and checks dot-notation once, as an
identity assertion against those same exports. This file was the only one that
rendered through the root's statics — `h(ResizablePanel.Pane, …)` — which made
static analysis flag eight unguarded member accesses: a `.vue` default export is
generated by the SFC compiler, so the analyzer sees no `export default`, reads
the imported binding as undefined, and reports every `.Pane` / `.Handle` read on
it.
Compose through `ResizablePanelPane` / `ResizablePanelHandle` instead, and keep
the compound contract in the one place it is the subject rather than the vehicle,
asserting identity against those exports through a guarded read.
Same nine tests, same behaviour covered — the dot-notation contract is now
verified more tightly than `toBeTruthy` did.
---------
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* feat(webkit): give Select, MultiSelect and Dropdown one shared leading column (#935)
A list that mixes iconed and icon-less options used to run two label edges: an
option with a glyph indented its text, one without it did not, so the eye had no
column to read down. Fixing it per-option is not possible — no option knows
whether any sibling has an icon.
So the reservation is made by the LIST, with a self-triggering `:has()` rule.
Every option renders a fixed leading box and marks itself `data-leading` only
when it actually has a glyph; the box is `hidden` and turns back on under
`group-has-[[data-leading]]/options:flex`. `:has()` matches the marker even
while the box is hidden, so one iconed option is enough to open the column for
the whole list — and a list with no icons at all reserves nothing and pays no
indent. The same `group/options` marker is on all three panels, so Select,
MultiSelect and Dropdown behave identically.
The group label is now OPTIONAL, and sentence-case `text-label-sm` rather than
an uppercase overline: an unlabelled group is a grouping the reader does not
need named, and an overline read as a second heading level inside a panel that
has none.
Group rhythm is `--spacing-sm` above every group but the first — the same step
CommandMenu already puts between two bare groups. Dropdown keeps its hairline
and Select does not: the Figma frame for the Select panel separates groups by
space alone. Dropdown's divider moves to the group root, which spans the panel's
full inner width, so it reads edge-to-edge like the panel's own border; the
horizontal inset the rows need moves onto the content wrapper.
Panels also gain top/bottom slots for content that is not an option (a search
row, a footer action), and the panel's vertical padding steps up one so the
first and last row do not crowd its border.
Includes the 10 dropdown visual baselines this moves.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* fix(icons): resolve currentColor per theme in the colored-icon CSS (#930)
Astro's "A" shipped invisible on every light-mode surface.
Some brand logos are only partly colored: Astro is a fixed red-to-magenta
gradient swoosh under an "A" that is the brand's INK — black on a light
background, white on a dark one. That part is authored as `fill="currentColor"`,
which is what `validate-svg.mjs` asks for, and it resolves correctly wherever
the SVG is INLINED (dist/color-catalog.json → the gallery, Storybook).
A `background-image` is a separate document, so it inherits nothing from the
element painting it. `currentColor` there resolves against the SVG's own root,
not the consumer's text color, and no amount of `color` on the `<i>` reaches it.
Left alone it lands on the UA default and the icon disappears in one theme —
which is what happened: `ai-astro.svg` had the "A" hardcoded `#FAFAFA`, so it
was white-on-white everywhere in light mode.
So the generator resolves it. An icon containing `currentColor` is emitted
twice: the base rule with the light ink, and an override with the dark ink under
the same selectors @aziontech/theme uses for its dark tokens
(`[data-theme=dark]`, `.dark`, `.azion.azion-dark`). The override's extra
selector out-specifies the base, so source order does not decide the winner.
The inks are literals (`#141414` / `#FAFAFA`) rather than `var(--text-default)`
by necessity — same document isolation — and are the theme's own values for that
token in each mode. A fully-colored logo with no ink parts still emits one rule,
exactly as before: 17 colored icons, 1 theme-aware.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* docs(webkit): ship the webkit-create-surface and webkit-errors skills (#895)
* docs(webkit): ship the webkit-create-surface and webkit-errors skills
webkit-create-surface fixes where a create or settings form lives — a first-level
resource creates on a page, anything created inside a resource opens a drawer,
nothing creates in a dialog — plus the anatomy all of them share, what belongs
behind the collapsed Advanced band, and the commit model each surface uses.
webkit-errors is the one decision table for where a failure goes: client-side
validation on the field, a server rejection scoped to one field as a Message in
that section, an unscoped request failure as a toast with Retry, the signed-out
screens where auth never toasts, and the async failure that outlives its screen.
Both join the CLI bundle and the ui-craft routing table; webkit-form and
webkit-ui-states now hand their error rows off to webkit-errors.
* docs(webkit): ship the webkit-microcopy consumer skill (#931)
The writing rules for Azion product interfaces, as a skill the adoption toolkit
ships — so a consuming project's agent writes the same strings the console does
instead of inventing a house style per screen.
It fixes the things that were being decided per-PR: punctuation and
capitalization (no em dash, no ampersand, no parentheses in labels, no bold in
running text, sentence case with Azion product names capitalized), which surface
each kind of copy belongs on (a label names, a description constrains, a Message
explains impact, a Popover defines, a Tag states), one word per concept (Select
not Pick, Application not Edge Application, and no `edge` as a prefix or a
location), navigation and IA naming, and copy that survives every breakpoint.
Registered in the three places a consumer skill has to be registered or it never
reaches a project: the CLI's file list (`plan.js`), the UI-craft pack in
`plan.test.mjs` (now 18 skills), and the routing index in `CLAUDE.fragment.md`,
where it gets its own **Words** line — it is the only skill about strings rather
than structure, and it applies during every other one.
Distinct from the generic `content-microcopy` the plan drops: that one is
house-agnostic, this one carries Azion's own vocabulary.
---------
Co-authored-by: Isaque Böck <isaque.santos@azion.com>
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* feat(theme): add the page-enter and content-enter arrival animations (#896)
* feat(theme): add the page-enter and content-enter arrival animations
The two entrances an app shell needs, as a pair. page-enter is the route
transition: it starts one --layout-boundary-inline to the LEFT of where it lands
and travels right while fading up, so a page reads as arriving FROM the nav row
that was clicked — a plain fade has no origin and a right-side arrival reads as
going back. content-enter is content settling inside a page already on screen, so
it rises a hair instead of travelling, and takes a --content-enter-delay stagger
(with backwards fill, or a delayed follower would paint its landed state first).
Neither fills forwards: page-enter's box is usually the scroll container, and a
lingering translate would make it a containing block for any fixed descendant.
* docs(webkit): teach the two arrival animations in webkit-motion-polish
Documents both utilities and their knobs, and the failure they exist to prevent:
a page arrives once, so a mount-time entrance inside a page whose route
transition is still running lockesteps with it into one diagonal move (identical
opacities frame for frame is the tell). Adds that shape and the two-shells-per-
screen variant to the silent-failure table. catalog.json rebuilt so the two
animations reach the MCP and validate-spec-compliance.
---------
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* docs: rewrite add-animation for the CSS-first theme (#897)
The steps still described the deleted v3 plugin (semantic/animations.js,
addUtilities/addComponents, the camelCase keyframe object form). An animation is
now two edits under primitives/animations/ — the shorthand in animate.js, which
is what makes animate-<name> a utility at all, and the CSS-string keyframes in
keyframes.js, which Tailwind v4 does NOT generate — compiled by build:tokens,
which must run before catalog:build or nothing renders.
Also records the three things the two new arrivals taught: a per-instance value
rides a var() with a token default inside the keyframe, a stagger rides a var()
delay with backwards fill, and fill is a decision — forwards leaves a translate
that makes the element a containing block for any fixed descendant.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* fix(webkit): give table cells block padding so tall content can breathe (#899)
A cell was `min-h-12` with horizontal padding only. For a single-line cell that
is invisible — the 48px minimum decides the height on its own against ~20px of
text — so every existing row renders exactly as before.
It only shows on a cell whose content is TALLER than the minimum: a wrapped list
of tags, a two-line summary. There the height came from the content alone, which
put the first and last lines flush against the row's borders with no breathing
room at all.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* docs(webkit): bring webkit-lists onto the current list-page shape (#929)
The shipped consumer skill still described the list page as it was before three
decisions landed, so a consumer following it built a page the product no longer
looks like:
- A first-level module index opens with a `PageHeading` again, and the module's
create action lives in it. The shell draws the breadcrumb only from the second
crumb up, so at first level the heading is the ONLY place the module's name
appears in the content.
- The size ladder is the hierarchy: the heading's action is the page's one
`large` (40px) control and the whole controls row is `medium` (32px). At 40px
both rows competed and the create button stopped being the page's answer.
- The controls row is narrowing only — `#actions` there is for an action on the
LISTING, not the page's own.
`last_updated` moves to 2026-08-24.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* feat: webkit documentation (#919)
* chore: [ENG-47606] add the documentation component category (#904)
* feat(theme): [ENG-47561] add the docs measure and the prose + mono body registers (#905)
* chore: [ENG-47606] add the documentation component category
* feat(theme): [ENG-47561] add the docs measure and the mono body register
* feat(theme): [ENG-47606] add the prose body register
* fix(theme): [ENG-47606] widen the docs measure to the form column
The documentation column was capped at 752px, narrower than every other measure in the system. Docs pages carry code blocks, card grids and tables, none of which fit a prose measure, so the column now takes the same 1192px the form measure uses.
The token is defined here, so it is set here: the doc component stack carries the same value, and an add/add disagreement between the two made every PR in that stack unmergeable.
* feat(webkit): [ENG-47616] add DocOnThisPage, the documentation TOC rail (#918)
* chore: [ENG-47606] add the documentation component category
* feat(theme): [ENG-47561] add the docs measure and the mono body register
* feat(theme): [ENG-47606] add the prose body register
* feat(webkit): [ENG-47606] add DocProse, the documentation typography contract
* test(storybook): add the linux baselines for the DocProse stories
* fix(theme): [ENG-47606] widen the docs measure to the form column
The documentation column was capped at 752px, narrower than every other measure in the system. Docs pages carry code blocks, card grids and tables, none of which fit a prose measure, so the column now takes the same 1192px the form measure uses.
* fix(webkit): [ENG-47606] pin the prose section step and separate the heading registers
The section rung was a descending pair that read 32 on a phone and on a laptop and only reached 48 at xl, so the same page broke its sections at different gaps by window width. It is now a flat 56 to open and 48 to close, taken from the theme's primitive spacing steps.
Each heading level now names two registers instead of one. On one register per level the base column collapsed: h2 was 16 (the body's own size), h3 and h4 were both 14, and between 640 and 768 the page title was overtaken by its own h2.
Weight and leading are no longer restated here — every element takes its type token verbatim, so the ladder is size and space only.
* feat(webkit): [ENG-47607] add DocCallout, the documentation admonition
* test(storybook): add the linux baselines for the DocProse and DocCallout stories
* fix(webkit): [ENG-47607] step an inline callout link to the only other weight the system defines
font-medium is 500, a weight no text token in the theme uses — the set is light (300) and normal (400). An inline link inside a callout now steps to 400.
* feat(webkit): [ENG-47611] add DocCard, the documentation grid cell
* test(storybook): add the linux baselines for the documentation stories
* feat(webkit): [ENG-47612] add DocCardGroup, the documentation card grid
* test(storybook): add the linux baselines for the doccardgroup stories
* feat(webkit): [ENG-47609] add DocItem, the documentation list row
* test(storybook): add the linux baselines for the docitem stories
* feat(webkit): [ENG-47613] add DocSteps and DocStep, the documentation procedure list
* test(storybook): add the linux baselines for the docsteps stories
* feat(webkit): [ENG-47608] add DocFrame, the documentation figure with a full-screen view
* test(storybook): add the linux baselines for the docframe stories
* test(storybook): stop snapshotting the autoplaying DocFrame clip
* feat(webkit): [ENG-47615] add DocUpdate and the heading-nav composable
* test(storybook): add the linux baselines for the docupdate stories
* feat(webkit): [ENG-47561] add DocPrompt, the copyable agent prompt
* test(storybook): add the linux baselines for the docprompt stories
* feat(webkit): [ENG-47610] add DocTooltip, the inline gloss
* test(storybook): add the linux baselines for the doctooltip stories
* fix(webkit): [ENG-47610] let the tooltip headline take its type token's weight
The headline restated font-medium (500), a weight no text token in the theme defines. It now takes text-body-sm verbatim, and the step to the tip below it is carried by ink colour.
* feat(webkit): [ENG-47618] add DocPagination, the documentation prev/next pair
* test(storybook): add the linux baselines for the docpagination stories
* feat(webkit): [ENG-47617] add DocPageHeader, the documentation masthead
* test(storybook): add the linux baselines for the docpageheader stories
* fix(webkit): [ENG-47617] stop the page title being overtaken by its own h2
text-heading-lg runs 18/18/30, so between 640 and 768 the masthead title rendered smaller than the prose h2 under it (20). The title now takes the same descending pair the prose h1 uses — text-heading-2xl with text-heading-xl from sm — which reads 30/30/36 and stays above every heading below it. Closes the theme gap the spec recorded.
The deck stops restating font-light; text-body-md already resolves to 300.
* feat(webkit): [ENG-47616] add DocOnThisPage, the documentation TOC rail
* test(storybook): add the linux baselines for the doconthispage stories
* fix(webkit): [ENG-47616] mark the rail's opening line so a shell can align to it
A docs shell aligns the outline rail against the page bar beside it, and needed a stable thing to measure. data-toc-title marks the rail's first line so the shell reaches it without depending on this component's internal structure.
* style(webkit): sort the use-heading-nav imports
* test(storybook): regenerate the documentation baselines after the typography fixes
---------
Co-authored-by: Gab <gabriel.mendonca@azion.com>
* fix(webkit): copy-button tooltip, card-box header inset, flow attrs (#936)
* fix(webkit): copy-button tooltip, card-box header inset, flow attrs
Three unrelated small fixes.
COPY-BUTTON COMPOSES TOOLTIP. An icon-only button says nothing to a pointer user
until it is hovered, so the glyph was unlabelled in practice — and the
"Copied" confirmation existed only as an accessible-name swap, announced to a
screen reader and invisible to everyone else. One string now names the control
AND labels its tooltip, so the two can never disagree: `ariaLabel` while idle,
`copiedLabel` for the two seconds after a write. The confirmation is visible
because it rides the same state.
CARD-BOX HEADER INSET `sm` → `md`. The header's horizontal padding now matches
the content's `p-(--spacing-md)` and the footer's `px-(--spacing-md)`, so the
title sits on the same vertical line as the first thing under it — and, in a
flush card, as the rows of whatever fills it (ItemList, Accordion and LogView
are all inset `--spacing-md`). At `--spacing-sm` every card header sat 4px left
of its own body.
FLOW FORWARDS `$attrs`. It declared `inheritAttrs: false` and then never spread
`$attrs` on its root, merging only `attrs.class`. Everything else a consumer
passed — `id`, `aria-*`, `data-*`, listeners — was silently dropped, which
`.claude/rules/root-element.md` exists to prevent. Same shape as its siblings
now: `v-bind="$attrs"` on the real root, with `cn` still merging the class.
* test(storybook): regenerate the card-box baselines for the header inset
* chore(webkit): raise the doc-prompt budget for the copy-button tooltip
DocPrompt composes CopyButton, which now wraps its IconButton in the
Tooltip overlay so the icon-only control is legible on hover. The
overlay machinery raises the measured entry from 6.20 KB to 8.61 KB;
the budget moves to 9 KB to carry that composition with headroom.
---------
Co-authored-by: Isaque Böck <isaque.santos@azion.com>
* fix(webkit): emit navigate when a collapsed breadcrumb crumb is picked
`select` is the Dropdown's event, not the option's: `Dropdown.Option`
forwards its activation to the root through the injected context, and the
root is what emits `select(event, value)`. The listener sat on the option,
where it never fired — so picking a collapsed crumb below `md` did
nothing at all. The menu closed and the reader stayed on the page. That is
the whole trail on a phone, since the collapsed shape is the only one a
narrow bar renders.
The test that covered this was skipped as a harness limitation ("@select
does not fire under test interaction"). It was not a harness limitation —
it was this bug, reproducing exactly as a real browser did. The listener
moved to the root and the test is un-skipped.
---------
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
Co-authored-by: Isaque Böck <isaque.santos@azion.com>
---------
Co-authored-by: Isaque dos Santos <isaque.santos@azion.com>
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@gabriel-lisboa-azion@isaque-bock-azion
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

feat(theme): [ENG-47561] add the docs measure and the prose + mono body registers - #905

Merged
isaque-bock-azion merged 3 commits into
feat/webkit-documentationfrom
feat/ENG-47561-docs-measure-and-mono-body
Aug 24, 2026
Merged

feat(theme): [ENG-47561] add the docs measure and the prose + mono body registers#905
isaque-bock-azion merged 3 commits into
feat/webkit-documentationfrom
feat/ENG-47561-docs-measure-and-mono-body

Conversation

@gabriel-lisboa-azion

@gabriel-lisboa-aziongabriel-lisboa-azion commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • text-body-prose-md — body copy at --leading-relaxed (1.625). The whole text-body-* set is snug (1.375), and the documentation prose ladder was fitted against a 26px line box, so prose needs its own register. Line-height belongs in the token: check-authoring's leading-raw check deliberately rejects a leading-* override even in token form.
  • text-body-code-sm — the mono body register. The label-code-* set is leading-none, built for one-row-per-line code; this one wraps. Required by DocPrompt.
  • layout-measure-docs (--container-2xl, 752px) + layout-column-docs, completing the measure family (column / focused / form / form-create / docs). Docs are the one column capped by typography rather than payload width. The utility is generated by COLUMN_MEASURE + the columnUtility factory, so two entries produce the whole class.

How to test

  1. node packages/theme/src/scripts/build-tokens.mjs then git diff --stat packages/theme/dist — expect no further change; the committed dist is already current.
  2. cd packages/theme && node --test "test/**/*.test.mjs" — 12/12 pass, including the zero-unit assertions over every emitted token.
  3. grep "text-body-prose-md-line-height\|text-body-md-line-height" packages/theme/dist/v4/globals.css — expect 1.625 and 1.375 respectively; that difference is the whole point of the new token.
  4. grep -A5 '@utility text-body-code-sm' packages/theme/dist/v4/globals.css and the same for layout-column-docs — each must emit a real rule body. A token that compiles to nothing is the silent failure mode here.
  5. grep "layout-measure':" packages/theme/src/tokens/semantic/layouts.data.js — must still read var(--container-7xl) / 1620px (see Notes).

Notes

  • Deliberately excluded: the working branch this was carved from also retunes layout-measure from --container-7xl (1620px) to --container-6xl (1388px), moving home from the focused measure to the data measure. That is a separate product decision that would have silently changed the width of every data page, so it is not here — layout-measure and layout-measure-focused keep main's values and main's prose.
  • Stacked on chore: [ENG-47606] add the documentation component category #904 (the documentation category), which DocProse also needs. Merge chore: [ENG-47606] add the documentation component category #904 first; this retargets automatically.
  • No component consumes layout-column-docs yet: DocProse deliberately has no measure of its own, so the consumer caps the column.
  • Related: ENG-47561, ENG-47606, and the sibling component issues.

Delivery

The Documentation family is delivered on the feature branch feat/docs-components-bundle, not piecemeal to main — so main takes the whole family as one merge, while review stays per-component.

Merge order, bottom-up — each PR's base is the one before it, and each retargets automatically as its parent lands:

#904#905#906#907#908#909#910#911#912#913#914#915#916#917#918feat/docs-components-bundlemain

#904the documentation category (standards only)
#905theme: the docs measure + the prose and mono body registers
#906#918DocProse · DocCallout · DocCard · DocCardGroup · DocItem · DocSteps + DocStep · DocFrame · DocUpdate · DocPrompt · DocTooltip · DocPagination · DocPageHeader · DocOnThisPage

@gabriel-lisboa-aziongabriel-lisboa-azion changed the title feat(theme): [ENG-47561] add the docs measure and the mono body registerfeat(theme): [ENG-47561] add the docs measure and the prose + mono body registersAug 22, 2026
@gabriel-lisboa-azion
gabriel-lisboa-azionforce-pushed the feat/ENG-47561-docs-measure-and-mono-body branch from d3d46c0 to bc6575cCompareAugust 22, 2026 14:11
@gabriel-lisboa-azion
gabriel-lisboa-azion changed the base branch from main to chore/ENG-47606-documentation-categoryAugust 22, 2026 14:11
@isaque-bock-azion
isaque-bock-azion changed the base branch from chore/ENG-47606-documentation-category to feat/webkit-documentationAugust 24, 2026 13:04
@isaque-bock-azion
isaque-bock-azion merged commit e864f62 into feat/webkit-documentationAug 24, 2026
47 of 51 checks passed
@isaque-bock-azion
isaque-bock-azion deleted the feat/ENG-47561-docs-measure-and-mono-body branch August 24, 2026 13:14
isaque-bock-azion added a commit that referenced this pull request Aug 26, 2026
* chore: [ENG-47606] add the documentation component category (#904)
* feat(theme): [ENG-47561] add the docs measure and the prose + mono body registers (#905)
* chore: [ENG-47606] add the documentation component category
* feat(theme): [ENG-47561] add the docs measure and the mono body register
* feat(theme): [ENG-47606] add the prose body register
* fix(theme): [ENG-47606] widen the docs measure to the form column
The documentation column was capped at 752px, narrower than every other measure in the system. Docs pages carry code blocks, card grids and tables, none of which fit a prose measure, so the column now takes the same 1192px the form measure uses.
The token is defined here, so it is set here: the doc component stack carries the same value, and an add/add disagreement between the two made every PR in that stack unmergeable.
* feat(webkit): [ENG-47616] add DocOnThisPage, the documentation TOC rail (#918)
* chore: [ENG-47606] add the documentation component category
* feat(theme): [ENG-47561] add the docs measure and the mono body register
* feat(theme): [ENG-47606] add the prose body register
* feat(webkit): [ENG-47606] add DocProse, the documentation typography contract
* test(storybook): add the linux baselines for the DocProse stories
* fix(theme): [ENG-47606] widen the docs measure to the form column
The documentation column was capped at 752px, narrower than every other measure in the system. Docs pages carry code blocks, card grids and tables, none of which fit a prose measure, so the column now takes the same 1192px the form measure uses.
* fix(webkit): [ENG-47606] pin the prose section step and separate the heading registers
The section rung was a descending pair that read 32 on a phone and on a laptop and only reached 48 at xl, so the same page broke its sections at different gaps by window width. It is now a flat 56 to open and 48 to close, taken from the theme's primitive spacing steps.
Each heading level now names two registers instead of one. On one register per level the base column collapsed: h2 was 16 (the body's own size), h3 and h4 were both 14, and between 640 and 768 the page title was overtaken by its own h2.
Weight and leading are no longer restated here — every element takes its type token verbatim, so the ladder is size and space only.
* feat(webkit): [ENG-47607] add DocCallout, the documentation admonition
* test(storybook): add the linux baselines for the DocProse and DocCallout stories
* fix(webkit): [ENG-47607] step an inline callout link to the only other weight the system defines
font-medium is 500, a weight no text token in the theme uses — the set is light (300) and normal (400). An inline link inside a callout now steps to 400.
* feat(webkit): [ENG-47611] add DocCard, the documentation grid cell
* test(storybook): add the linux baselines for the documentation stories
* feat(webkit): [ENG-47612] add DocCardGroup, the documentation card grid
* test(storybook): add the linux baselines for the doccardgroup stories
* feat(webkit): [ENG-47609] add DocItem, the documentation list row
* test(storybook): add the linux baselines for the docitem stories
* feat(webkit): [ENG-47613] add DocSteps and DocStep, the documentation procedure list
* test(storybook): add the linux baselines for the docsteps stories
* feat(webkit): [ENG-47608] add DocFrame, the documentation figure with a full-screen view
* test(storybook): add the linux baselines for the docframe stories
* test(storybook): stop snapshotting the autoplaying DocFrame clip
* feat(webkit): [ENG-47615] add DocUpdate and the heading-nav composable
* test(storybook): add the linux baselines for the docupdate stories
* feat(webkit): [ENG-47561] add DocPrompt, the copyable agent prompt
* test(storybook): add the linux baselines for the docprompt stories
* feat(webkit): [ENG-47610] add DocTooltip, the inline gloss
* test(storybook): add the linux baselines for the doctooltip stories
* fix(webkit): [ENG-47610] let the tooltip headline take its type token's weight
The headline restated font-medium (500), a weight no text token in the theme defines. It now takes text-body-sm verbatim, and the step to the tip below it is carried by ink colour.
* feat(webkit): [ENG-47618] add DocPagination, the documentation prev/next pair
* test(storybook): add the linux baselines for the docpagination stories
* feat(webkit): [ENG-47617] add DocPageHeader, the documentation masthead
* test(storybook): add the linux baselines for the docpageheader stories
* fix(webkit): [ENG-47617] stop the page title being overtaken by its own h2
text-heading-lg runs 18/18/30, so between 640 and 768 the masthead title rendered smaller than the prose h2 under it (20). The title now takes the same descending pair the prose h1 uses — text-heading-2xl with text-heading-xl from sm — which reads 30/30/36 and stays above every heading below it. Closes the theme gap the spec recorded.
The deck stops restating font-light; text-body-md already resolves to 300.
* feat(webkit): [ENG-47616] add DocOnThisPage, the documentation TOC rail
* test(storybook): add the linux baselines for the doconthispage stories
* fix(webkit): [ENG-47616] mark the rail's opening line so a shell can align to it
A docs shell aligns the outline rail against the page bar beside it, and needed a stable thing to measure. data-toc-title marks the rail's first line so the shell reaches it without depending on this component's internal structure.
* style(webkit): sort the use-heading-nav imports
* test(storybook): regenerate the documentation baselines after the typography fixes
---------
Co-authored-by: Gab <gabriel.mendonca@azion.com>
isaque-bock-azion added a commit that referenced this pull request Aug 28, 2026
#939)
* feat(theme): add --container-site, the marketing site's shared measure (#923)
* feat(theme): add --container-site, the marketing site's shared measure
The azion.com pages are a single vertical frame: the nav bar, the hero's inner
column, every section below it and the footer all sit on the same centred
column, and the border-x that runs down the page is only continuous because
those four agree to the pixel. A width re-typed per band cannot hold that — the
frame is one decision, so it gets one place to live.
`5xl` (1192px) is a rung of the shared container ladder, not a number chosen for
this page. It is its own token rather than a reference to one of the console
measures because the two answer different questions: a console measure caps a
payload (a table, a form), while this one is the width of a frame whose four
bands have to line up, so retuning either must not move the other.
* refactor(webkit): point the footer column at --container-site (#928)
The footer's column named `--container-5xl` directly. On the marketing site the
nav bar, the hero band, the framed sections and this footer are ONE vertical
frame, and its side rules only meet because all four resolve to the same width —
so the measure has to be a token they share, not a rung each of them names.
Retuning it now moves them together, which a hardcoded rung here could not do.
No visual change: `--container-site` resolves to `--container-5xl`, which is
what the class already said.
* feat(webkit): add the resizable-panel layout primitive (#902)
* feat(webkit): add the resizable-panel layout primitive
A group of adjacent regions whose shared edges the reader can drag — the layout
behind a workspace, where an editor, a terminal under it and a preview beside it
are all content and the split between them is the reader's to set.
It exists because `Sidebar` was being used for both jobs. `Sidebar` is the app's
NAVIGATION rail: it collapses to icons, it hides, it is the thing you leave to go
somewhere. A terminal, a preview and a code editor are not places you navigate to
— they are the work — and dressing two of them as navigation rails produced a
screen with three "sidebars" and one document, on an axis `Sidebar` does not even
have: it has no horizontal edge, so a terminal under an editor had to hand-roll
its own splitter. This component owns the EDGE; `Sidebar` goes back to owning
navigation.
Composition: `ResizablePanel` with `Pane` and `Handle`, attached for dot-notation
and exported standalone. The compound is built by naming the static surface and
casting once rather than with `Object.assign`, whose inferred type walks each
part's local `interface Props` into the declaration and trips TS4082.
Ships the spec, a browser-mode test suite, the story, the `-root` and
per-sub-component exports, and a size budget.
* test(storybook): regenerate linux baselines for resizable-panel
Adds the 24 snapshots for the four new ResizablePanel stories across the 6-mode
matrix. Generated by the Storybook Regenerate Baseline workflow on this branch's
HEAD — baselines are ubuntu-only, so a local run would not match the folder the
visual gate compares against.
No existing snapshot is modified or deleted; the component is additive.
* test(webkit): compose resizable-panel through its named sub-component exports
Every other composition suite in the package renders through the named exports
(`AccordionItem`, `DropdownTrigger`, …) and checks dot-notation once, as an
identity assertion against those same exports. This file was the only one that
rendered through the root's statics — `h(ResizablePanel.Pane, …)` — which made
static analysis flag eight unguarded member accesses: a `.vue` default export is
generated by the SFC compiler, so the analyzer sees no `export default`, reads
the imported binding as undefined, and reports every `.Pane` / `.Handle` read on
it.
Compose through `ResizablePanelPane` / `ResizablePanelHandle` instead, and keep
the compound contract in the one place it is the subject rather than the vehicle,
asserting identity against those exports through a guarded read.
Same nine tests, same behaviour covered — the dot-notation contract is now
verified more tightly than `toBeTruthy` did.
---------
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* feat(webkit): give Select, MultiSelect and Dropdown one shared leading column (#935)
A list that mixes iconed and icon-less options used to run two label edges: an
option with a glyph indented its text, one without it did not, so the eye had no
column to read down. Fixing it per-option is not possible — no option knows
whether any sibling has an icon.
So the reservation is made by the LIST, with a self-triggering `:has()` rule.
Every option renders a fixed leading box and marks itself `data-leading` only
when it actually has a glyph; the box is `hidden` and turns back on under
`group-has-[[data-leading]]/options:flex`. `:has()` matches the marker even
while the box is hidden, so one iconed option is enough to open the column for
the whole list — and a list with no icons at all reserves nothing and pays no
indent. The same `group/options` marker is on all three panels, so Select,
MultiSelect and Dropdown behave identically.
The group label is now OPTIONAL, and sentence-case `text-label-sm` rather than
an uppercase overline: an unlabelled group is a grouping the reader does not
need named, and an overline read as a second heading level inside a panel that
has none.
Group rhythm is `--spacing-sm` above every group but the first — the same step
CommandMenu already puts between two bare groups. Dropdown keeps its hairline
and Select does not: the Figma frame for the Select panel separates groups by
space alone. Dropdown's divider moves to the group root, which spans the panel's
full inner width, so it reads edge-to-edge like the panel's own border; the
horizontal inset the rows need moves onto the content wrapper.
Panels also gain top/bottom slots for content that is not an option (a search
row, a footer action), and the panel's vertical padding steps up one so the
first and last row do not crowd its border.
Includes the 10 dropdown visual baselines this moves.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* fix(icons): resolve currentColor per theme in the colored-icon CSS (#930)
Astro's "A" shipped invisible on every light-mode surface.
Some brand logos are only partly colored: Astro is a fixed red-to-magenta
gradient swoosh under an "A" that is the brand's INK — black on a light
background, white on a dark one. That part is authored as `fill="currentColor"`,
which is what `validate-svg.mjs` asks for, and it resolves correctly wherever
the SVG is INLINED (dist/color-catalog.json → the gallery, Storybook).
A `background-image` is a separate document, so it inherits nothing from the
element painting it. `currentColor` there resolves against the SVG's own root,
not the consumer's text color, and no amount of `color` on the `<i>` reaches it.
Left alone it lands on the UA default and the icon disappears in one theme —
which is what happened: `ai-astro.svg` had the "A" hardcoded `#FAFAFA`, so it
was white-on-white everywhere in light mode.
So the generator resolves it. An icon containing `currentColor` is emitted
twice: the base rule with the light ink, and an override with the dark ink under
the same selectors @aziontech/theme uses for its dark tokens
(`[data-theme=dark]`, `.dark`, `.azion.azion-dark`). The override's extra
selector out-specifies the base, so source order does not decide the winner.
The inks are literals (`#141414` / `#FAFAFA`) rather than `var(--text-default)`
by necessity — same document isolation — and are the theme's own values for that
token in each mode. A fully-colored logo with no ink parts still emits one rule,
exactly as before: 17 colored icons, 1 theme-aware.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* docs(webkit): ship the webkit-create-surface and webkit-errors skills (#895)
* docs(webkit): ship the webkit-create-surface and webkit-errors skills
webkit-create-surface fixes where a create or settings form lives — a first-level
resource creates on a page, anything created inside a resource opens a drawer,
nothing creates in a dialog — plus the anatomy all of them share, what belongs
behind the collapsed Advanced band, and the commit model each surface uses.
webkit-errors is the one decision table for where a failure goes: client-side
validation on the field, a server rejection scoped to one field as a Message in
that section, an unscoped request failure as a toast with Retry, the signed-out
screens where auth never toasts, and the async failure that outlives its screen.
Both join the CLI bundle and the ui-craft routing table; webkit-form and
webkit-ui-states now hand their error rows off to webkit-errors.
* docs(webkit): ship the webkit-microcopy consumer skill (#931)
The writing rules for Azion product interfaces, as a skill the adoption toolkit
ships — so a consuming project's agent writes the same strings the console does
instead of inventing a house style per screen.
It fixes the things that were being decided per-PR: punctuation and
capitalization (no em dash, no ampersand, no parentheses in labels, no bold in
running text, sentence case with Azion product names capitalized), which surface
each kind of copy belongs on (a label names, a description constrains, a Message
explains impact, a Popover defines, a Tag states), one word per concept (Select
not Pick, Application not Edge Application, and no `edge` as a prefix or a
location), navigation and IA naming, and copy that survives every breakpoint.
Registered in the three places a consumer skill has to be registered or it never
reaches a project: the CLI's file list (`plan.js`), the UI-craft pack in
`plan.test.mjs` (now 18 skills), and the routing index in `CLAUDE.fragment.md`,
where it gets its own **Words** line — it is the only skill about strings rather
than structure, and it applies during every other one.
Distinct from the generic `content-microcopy` the plan drops: that one is
house-agnostic, this one carries Azion's own vocabulary.
---------
Co-authored-by: Isaque Böck <isaque.santos@azion.com>
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* feat(theme): add the page-enter and content-enter arrival animations (#896)
* feat(theme): add the page-enter and content-enter arrival animations
The two entrances an app shell needs, as a pair. page-enter is the route
transition: it starts one --layout-boundary-inline to the LEFT of where it lands
and travels right while fading up, so a page reads as arriving FROM the nav row
that was clicked — a plain fade has no origin and a right-side arrival reads as
going back. content-enter is content settling inside a page already on screen, so
it rises a hair instead of travelling, and takes a --content-enter-delay stagger
(with backwards fill, or a delayed follower would paint its landed state first).
Neither fills forwards: page-enter's box is usually the scroll container, and a
lingering translate would make it a containing block for any fixed descendant.
* docs(webkit): teach the two arrival animations in webkit-motion-polish
Documents both utilities and their knobs, and the failure they exist to prevent:
a page arrives once, so a mount-time entrance inside a page whose route
transition is still running lockesteps with it into one diagonal move (identical
opacities frame for frame is the tell). Adds that shape and the two-shells-per-
screen variant to the silent-failure table. catalog.json rebuilt so the two
animations reach the MCP and validate-spec-compliance.
---------
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* docs: rewrite add-animation for the CSS-first theme (#897)
The steps still described the deleted v3 plugin (semantic/animations.js,
addUtilities/addComponents, the camelCase keyframe object form). An animation is
now two edits under primitives/animations/ — the shorthand in animate.js, which
is what makes animate-<name> a utility at all, and the CSS-string keyframes in
keyframes.js, which Tailwind v4 does NOT generate — compiled by build:tokens,
which must run before catalog:build or nothing renders.
Also records the three things the two new arrivals taught: a per-instance value
rides a var() with a token default inside the keyframe, a stagger rides a var()
delay with backwards fill, and fill is a decision — forwards leaves a translate
that makes the element a containing block for any fixed descendant.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* fix(webkit): give table cells block padding so tall content can breathe (#899)
A cell was `min-h-12` with horizontal padding only. For a single-line cell that
is invisible — the 48px minimum decides the height on its own against ~20px of
text — so every existing row renders exactly as before.
It only shows on a cell whose content is TALLER than the minimum: a wrapped list
of tags, a two-line summary. There the height came from the content alone, which
put the first and last lines flush against the row's borders with no breathing
room at all.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* docs(webkit): bring webkit-lists onto the current list-page shape (#929)
The shipped consumer skill still described the list page as it was before three
decisions landed, so a consumer following it built a page the product no longer
looks like:
- A first-level module index opens with a `PageHeading` again, and the module's
create action lives in it. The shell draws the breadcrumb only from the second
crumb up, so at first level the heading is the ONLY place the module's name
appears in the content.
- The size ladder is the hierarchy: the heading's action is the page's one
`large` (40px) control and the whole controls row is `medium` (32px). At 40px
both rows competed and the create button stopped being the page's answer.
- The controls row is narrowing only — `#actions` there is for an action on the
LISTING, not the page's own.
`last_updated` moves to 2026-08-24.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* feat: webkit documentation (#919)
* chore: [ENG-47606] add the documentation component category (#904)
* feat(theme): [ENG-47561] add the docs measure and the prose + mono body registers (#905)
* chore: [ENG-47606] add the documentation component category
* feat(theme): [ENG-47561] add the docs measure and the mono body register
* feat(theme): [ENG-47606] add the prose body register
* fix(theme): [ENG-47606] widen the docs measure to the form column
The documentation column was capped at 752px, narrower than every other measure in the system. Docs pages carry code blocks, card grids and tables, none of which fit a prose measure, so the column now takes the same 1192px the form measure uses.
The token is defined here, so it is set here: the doc component stack carries the same value, and an add/add disagreement between the two made every PR in that stack unmergeable.
* feat(webkit): [ENG-47616] add DocOnThisPage, the documentation TOC rail (#918)
* chore: [ENG-47606] add the documentation component category
* feat(theme): [ENG-47561] add the docs measure and the mono body register
* feat(theme): [ENG-47606] add the prose body register
* feat(webkit): [ENG-47606] add DocProse, the documentation typography contract
* test(storybook): add the linux baselines for the DocProse stories
* fix(theme): [ENG-47606] widen the docs measure to the form column
The documentation column was capped at 752px, narrower than every other measure in the system. Docs pages carry code blocks, card grids and tables, none of which fit a prose measure, so the column now takes the same 1192px the form measure uses.
* fix(webkit): [ENG-47606] pin the prose section step and separate the heading registers
The section rung was a descending pair that read 32 on a phone and on a laptop and only reached 48 at xl, so the same page broke its sections at different gaps by window width. It is now a flat 56 to open and 48 to close, taken from the theme's primitive spacing steps.
Each heading level now names two registers instead of one. On one register per level the base column collapsed: h2 was 16 (the body's own size), h3 and h4 were both 14, and between 640 and 768 the page title was overtaken by its own h2.
Weight and leading are no longer restated here — every element takes its type token verbatim, so the ladder is size and space only.
* feat(webkit): [ENG-47607] add DocCallout, the documentation admonition
* test(storybook): add the linux baselines for the DocProse and DocCallout stories
* fix(webkit): [ENG-47607] step an inline callout link to the only other weight the system defines
font-medium is 500, a weight no text token in the theme uses — the set is light (300) and normal (400). An inline link inside a callout now steps to 400.
* feat(webkit): [ENG-47611] add DocCard, the documentation grid cell
* test(storybook): add the linux baselines for the documentation stories
* feat(webkit): [ENG-47612] add DocCardGroup, the documentation card grid
* test(storybook): add the linux baselines for the doccardgroup stories
* feat(webkit): [ENG-47609] add DocItem, the documentation list row
* test(storybook): add the linux baselines for the docitem stories
* feat(webkit): [ENG-47613] add DocSteps and DocStep, the documentation procedure list
* test(storybook): add the linux baselines for the docsteps stories
* feat(webkit): [ENG-47608] add DocFrame, the documentation figure with a full-screen view
* test(storybook): add the linux baselines for the docframe stories
* test(storybook): stop snapshotting the autoplaying DocFrame clip
* feat(webkit): [ENG-47615] add DocUpdate and the heading-nav composable
* test(storybook): add the linux baselines for the docupdate stories
* feat(webkit): [ENG-47561] add DocPrompt, the copyable agent prompt
* test(storybook): add the linux baselines for the docprompt stories
* feat(webkit): [ENG-47610] add DocTooltip, the inline gloss
* test(storybook): add the linux baselines for the doctooltip stories
* fix(webkit): [ENG-47610] let the tooltip headline take its type token's weight
The headline restated font-medium (500), a weight no text token in the theme defines. It now takes text-body-sm verbatim, and the step to the tip below it is carried by ink colour.
* feat(webkit): [ENG-47618] add DocPagination, the documentation prev/next pair
* test(storybook): add the linux baselines for the docpagination stories
* feat(webkit): [ENG-47617] add DocPageHeader, the documentation masthead
* test(storybook): add the linux baselines for the docpageheader stories
* fix(webkit): [ENG-47617] stop the page title being overtaken by its own h2
text-heading-lg runs 18/18/30, so between 640 and 768 the masthead title rendered smaller than the prose h2 under it (20). The title now takes the same descending pair the prose h1 uses — text-heading-2xl with text-heading-xl from sm — which reads 30/30/36 and stays above every heading below it. Closes the theme gap the spec recorded.
The deck stops restating font-light; text-body-md already resolves to 300.
* feat(webkit): [ENG-47616] add DocOnThisPage, the documentation TOC rail
* test(storybook): add the linux baselines for the doconthispage stories
* fix(webkit): [ENG-47616] mark the rail's opening line so a shell can align to it
A docs shell aligns the outline rail against the page bar beside it, and needed a stable thing to measure. data-toc-title marks the rail's first line so the shell reaches it without depending on this component's internal structure.
* style(webkit): sort the use-heading-nav imports
* test(storybook): regenerate the documentation baselines after the typography fixes
---------
Co-authored-by: Gab <gabriel.mendonca@azion.com>
* fix(webkit): copy-button tooltip, card-box header inset, flow attrs (#936)
* fix(webkit): copy-button tooltip, card-box header inset, flow attrs
Three unrelated small fixes.
COPY-BUTTON COMPOSES TOOLTIP. An icon-only button says nothing to a pointer user
until it is hovered, so the glyph was unlabelled in practice — and the
"Copied" confirmation existed only as an accessible-name swap, announced to a
screen reader and invisible to everyone else. One string now names the control
AND labels its tooltip, so the two can never disagree: `ariaLabel` while idle,
`copiedLabel` for the two seconds after a write. The confirmation is visible
because it rides the same state.
CARD-BOX HEADER INSET `sm` → `md`. The header's horizontal padding now matches
the content's `p-(--spacing-md)` and the footer's `px-(--spacing-md)`, so the
title sits on the same vertical line as the first thing under it — and, in a
flush card, as the rows of whatever fills it (ItemList, Accordion and LogView
are all inset `--spacing-md`). At `--spacing-sm` every card header sat 4px left
of its own body.
FLOW FORWARDS `$attrs`. It declared `inheritAttrs: false` and then never spread
`$attrs` on its root, merging only `attrs.class`. Everything else a consumer
passed — `id`, `aria-*`, `data-*`, listeners — was silently dropped, which
`.claude/rules/root-element.md` exists to prevent. Same shape as its siblings
now: `v-bind="$attrs"` on the real root, with `cn` still merging the class.
* test(storybook): regenerate the card-box baselines for the header inset
* chore(webkit): raise the doc-prompt budget for the copy-button tooltip
DocPrompt composes CopyButton, which now wraps its IconButton in the
Tooltip overlay so the icon-only control is legible on hover. The
overlay machinery raises the measured entry from 6.20 KB to 8.61 KB;
the budget moves to 9 KB to carry that composition with headroom.
---------
Co-authored-by: Isaque Böck <isaque.santos@azion.com>
* fix(webkit): emit navigate when a collapsed breadcrumb crumb is picked
`select` is the Dropdown's event, not the option's: `Dropdown.Option`
forwards its activation to the root through the injected context, and the
root is what emits `select(event, value)`. The listener sat on the option,
where it never fired — so picking a collapsed crumb below `md` did
nothing at all. The menu closed and the reader stayed on the page. That is
the whole trail on a phone, since the collapsed shape is the only one a
narrow bar renders.
The test that covered this was skipped as a harness limitation ("@select
does not fire under test interaction"). It was not a harness limitation —
it was this bug, reproducing exactly as a real browser did. The listener
moved to the root and the test is un-skipped.
---------
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
Co-authored-by: Isaque Böck <isaque.santos@azion.com>
isaque-bock-azion added a commit that referenced this pull request Aug 28, 2026
* feat(webkit): let a table column declare a minWidth floor
A bounded column — a chip list, a date, a count, an id — could only be sized by
a fixed `width`, and a fixed width is a bet in both directions: guess high and
the column reserves space the rest of the row needs, guess low and the content
inside it truncates (a chip list ends up as `SQL In…`). `minWidth` only has to
be right about the minimum: the column resolves to the widest of that floor and
what it actually holds — its header, with the sort affordance, and every
rendered body cell. `width` still wins when both are set, so an explicit width
remains the consumer overriding the measurement outright.
ONE width is resolved per column and applied to the header and to every body
cell, which keeps the invariant fixed widths exist for: the column resolves
identically in every row. A per-cell `min-width` would let each row size to its
own content and drift the header away from the body.
It has to be measured, because a flex row cannot express "as wide as the widest
cell in this column" — every row is its own flex container and knows nothing
about its siblings. The measurement is the same clone-based one the resize floor
already uses, so a column's auto width and its drag floor can never disagree.
Three things the measurement has to respect:
- Debounced (32ms). The rendered content changes in bursts — a sort, a page
turn, a column toggled back on — and measuring reads layout, so an
un-coalesced version would reflow per keystroke of a filter.
- Skipped while `loading`. Skeleton rows are not the content; measuring against
them would size every column to a placeholder and never grow it back.
- Re-run on `document.fonts.ready`. Text width is font-dependent and the first
paint can happen on the fallback face.
`autoWidths` joins the row `v-memo` so a resolved width actually reaches the
body, and feeds `frozenWidthOf` so a frozen `minWidth` column contributes its
real width to a neighbour's sticky offset.
* test(storybook): regenerate the table baselines for the column min-width
* fix(webkit): emit navigate when a collapsed breadcrumb crumb is picked (#939)
* feat(theme): add --container-site, the marketing site's shared measure (#923)
* feat(theme): add --container-site, the marketing site's shared measure
The azion.com pages are a single vertical frame: the nav bar, the hero's inner
column, every section below it and the footer all sit on the same centred
column, and the border-x that runs down the page is only continuous because
those four agree to the pixel. A width re-typed per band cannot hold that — the
frame is one decision, so it gets one place to live.
`5xl` (1192px) is a rung of the shared container ladder, not a number chosen for
this page. It is its own token rather than a reference to one of the console
measures because the two answer different questions: a console measure caps a
payload (a table, a form), while this one is the width of a frame whose four
bands have to line up, so retuning either must not move the other.
* refactor(webkit): point the footer column at --container-site (#928)
The footer's column named `--container-5xl` directly. On the marketing site the
nav bar, the hero band, the framed sections and this footer are ONE vertical
frame, and its side rules only meet because all four resolve to the same width —
so the measure has to be a token they share, not a rung each of them names.
Retuning it now moves them together, which a hardcoded rung here could not do.
No visual change: `--container-site` resolves to `--container-5xl`, which is
what the class already said.
* feat(webkit): add the resizable-panel layout primitive (#902)
* feat(webkit): add the resizable-panel layout primitive
A group of adjacent regions whose shared edges the reader can drag — the layout
behind a workspace, where an editor, a terminal under it and a preview beside it
are all content and the split between them is the reader's to set.
It exists because `Sidebar` was being used for both jobs. `Sidebar` is the app's
NAVIGATION rail: it collapses to icons, it hides, it is the thing you leave to go
somewhere. A terminal, a preview and a code editor are not places you navigate to
— they are the work — and dressing two of them as navigation rails produced a
screen with three "sidebars" and one document, on an axis `Sidebar` does not even
have: it has no horizontal edge, so a terminal under an editor had to hand-roll
its own splitter. This component owns the EDGE; `Sidebar` goes back to owning
navigation.
Composition: `ResizablePanel` with `Pane` and `Handle`, attached for dot-notation
and exported standalone. The compound is built by naming the static surface and
casting once rather than with `Object.assign`, whose inferred type walks each
part's local `interface Props` into the declaration and trips TS4082.
Ships the spec, a browser-mode test suite, the story, the `-root` and
per-sub-component exports, and a size budget.
* test(storybook): regenerate linux baselines for resizable-panel
Adds the 24 snapshots for the four new ResizablePanel stories across the 6-mode
matrix. Generated by the Storybook Regenerate Baseline workflow on this branch's
HEAD — baselines are ubuntu-only, so a local run would not match the folder the
visual gate compares against.
No existing snapshot is modified or deleted; the component is additive.
* test(webkit): compose resizable-panel through its named sub-component exports
Every other composition suite in the package renders through the named exports
(`AccordionItem`, `DropdownTrigger`, …) and checks dot-notation once, as an
identity assertion against those same exports. This file was the only one that
rendered through the root's statics — `h(ResizablePanel.Pane, …)` — which made
static analysis flag eight unguarded member accesses: a `.vue` default export is
generated by the SFC compiler, so the analyzer sees no `export default`, reads
the imported binding as undefined, and reports every `.Pane` / `.Handle` read on
it.
Compose through `ResizablePanelPane` / `ResizablePanelHandle` instead, and keep
the compound contract in the one place it is the subject rather than the vehicle,
asserting identity against those exports through a guarded read.
Same nine tests, same behaviour covered — the dot-notation contract is now
verified more tightly than `toBeTruthy` did.
---------
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* feat(webkit): give Select, MultiSelect and Dropdown one shared leading column (#935)
A list that mixes iconed and icon-less options used to run two label edges: an
option with a glyph indented its text, one without it did not, so the eye had no
column to read down. Fixing it per-option is not possible — no option knows
whether any sibling has an icon.
So the reservation is made by the LIST, with a self-triggering `:has()` rule.
Every option renders a fixed leading box and marks itself `data-leading` only
when it actually has a glyph; the box is `hidden` and turns back on under
`group-has-[[data-leading]]/options:flex`. `:has()` matches the marker even
while the box is hidden, so one iconed option is enough to open the column for
the whole list — and a list with no icons at all reserves nothing and pays no
indent. The same `group/options` marker is on all three panels, so Select,
MultiSelect and Dropdown behave identically.
The group label is now OPTIONAL, and sentence-case `text-label-sm` rather than
an uppercase overline: an unlabelled group is a grouping the reader does not
need named, and an overline read as a second heading level inside a panel that
has none.
Group rhythm is `--spacing-sm` above every group but the first — the same step
CommandMenu already puts between two bare groups. Dropdown keeps its hairline
and Select does not: the Figma frame for the Select panel separates groups by
space alone. Dropdown's divider moves to the group root, which spans the panel's
full inner width, so it reads edge-to-edge like the panel's own border; the
horizontal inset the rows need moves onto the content wrapper.
Panels also gain top/bottom slots for content that is not an option (a search
row, a footer action), and the panel's vertical padding steps up one so the
first and last row do not crowd its border.
Includes the 10 dropdown visual baselines this moves.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* fix(icons): resolve currentColor per theme in the colored-icon CSS (#930)
Astro's "A" shipped invisible on every light-mode surface.
Some brand logos are only partly colored: Astro is a fixed red-to-magenta
gradient swoosh under an "A" that is the brand's INK — black on a light
background, white on a dark one. That part is authored as `fill="currentColor"`,
which is what `validate-svg.mjs` asks for, and it resolves correctly wherever
the SVG is INLINED (dist/color-catalog.json → the gallery, Storybook).
A `background-image` is a separate document, so it inherits nothing from the
element painting it. `currentColor` there resolves against the SVG's own root,
not the consumer's text color, and no amount of `color` on the `<i>` reaches it.
Left alone it lands on the UA default and the icon disappears in one theme —
which is what happened: `ai-astro.svg` had the "A" hardcoded `#FAFAFA`, so it
was white-on-white everywhere in light mode.
So the generator resolves it. An icon containing `currentColor` is emitted
twice: the base rule with the light ink, and an override with the dark ink under
the same selectors @aziontech/theme uses for its dark tokens
(`[data-theme=dark]`, `.dark`, `.azion.azion-dark`). The override's extra
selector out-specifies the base, so source order does not decide the winner.
The inks are literals (`#141414` / `#FAFAFA`) rather than `var(--text-default)`
by necessity — same document isolation — and are the theme's own values for that
token in each mode. A fully-colored logo with no ink parts still emits one rule,
exactly as before: 17 colored icons, 1 theme-aware.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* docs(webkit): ship the webkit-create-surface and webkit-errors skills (#895)
* docs(webkit): ship the webkit-create-surface and webkit-errors skills
webkit-create-surface fixes where a create or settings form lives — a first-level
resource creates on a page, anything created inside a resource opens a drawer,
nothing creates in a dialog — plus the anatomy all of them share, what belongs
behind the collapsed Advanced band, and the commit model each surface uses.
webkit-errors is the one decision table for where a failure goes: client-side
validation on the field, a server rejection scoped to one field as a Message in
that section, an unscoped request failure as a toast with Retry, the signed-out
screens where auth never toasts, and the async failure that outlives its screen.
Both join the CLI bundle and the ui-craft routing table; webkit-form and
webkit-ui-states now hand their error rows off to webkit-errors.
* docs(webkit): ship the webkit-microcopy consumer skill (#931)
The writing rules for Azion product interfaces, as a skill the adoption toolkit
ships — so a consuming project's agent writes the same strings the console does
instead of inventing a house style per screen.
It fixes the things that were being decided per-PR: punctuation and
capitalization (no em dash, no ampersand, no parentheses in labels, no bold in
running text, sentence case with Azion product names capitalized), which surface
each kind of copy belongs on (a label names, a description constrains, a Message
explains impact, a Popover defines, a Tag states), one word per concept (Select
not Pick, Application not Edge Application, and no `edge` as a prefix or a
location), navigation and IA naming, and copy that survives every breakpoint.
Registered in the three places a consumer skill has to be registered or it never
reaches a project: the CLI's file list (`plan.js`), the UI-craft pack in
`plan.test.mjs` (now 18 skills), and the routing index in `CLAUDE.fragment.md`,
where it gets its own **Words** line — it is the only skill about strings rather
than structure, and it applies during every other one.
Distinct from the generic `content-microcopy` the plan drops: that one is
house-agnostic, this one carries Azion's own vocabulary.
---------
Co-authored-by: Isaque Böck <isaque.santos@azion.com>
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* feat(theme): add the page-enter and content-enter arrival animations (#896)
* feat(theme): add the page-enter and content-enter arrival animations
The two entrances an app shell needs, as a pair. page-enter is the route
transition: it starts one --layout-boundary-inline to the LEFT of where it lands
and travels right while fading up, so a page reads as arriving FROM the nav row
that was clicked — a plain fade has no origin and a right-side arrival reads as
going back. content-enter is content settling inside a page already on screen, so
it rises a hair instead of travelling, and takes a --content-enter-delay stagger
(with backwards fill, or a delayed follower would paint its landed state first).
Neither fills forwards: page-enter's box is usually the scroll container, and a
lingering translate would make it a containing block for any fixed descendant.
* docs(webkit): teach the two arrival animations in webkit-motion-polish
Documents both utilities and their knobs, and the failure they exist to prevent:
a page arrives once, so a mount-time entrance inside a page whose route
transition is still running lockesteps with it into one diagonal move (identical
opacities frame for frame is the tell). Adds that shape and the two-shells-per-
screen variant to the silent-failure table. catalog.json rebuilt so the two
animations reach the MCP and validate-spec-compliance.
---------
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* docs: rewrite add-animation for the CSS-first theme (#897)
The steps still described the deleted v3 plugin (semantic/animations.js,
addUtilities/addComponents, the camelCase keyframe object form). An animation is
now two edits under primitives/animations/ — the shorthand in animate.js, which
is what makes animate-<name> a utility at all, and the CSS-string keyframes in
keyframes.js, which Tailwind v4 does NOT generate — compiled by build:tokens,
which must run before catalog:build or nothing renders.
Also records the three things the two new arrivals taught: a per-instance value
rides a var() with a token default inside the keyframe, a stagger rides a var()
delay with backwards fill, and fill is a decision — forwards leaves a translate
that makes the element a containing block for any fixed descendant.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* fix(webkit): give table cells block padding so tall content can breathe (#899)
A cell was `min-h-12` with horizontal padding only. For a single-line cell that
is invisible — the 48px minimum decides the height on its own against ~20px of
text — so every existing row renders exactly as before.
It only shows on a cell whose content is TALLER than the minimum: a wrapped list
of tags, a two-line summary. There the height came from the content alone, which
put the first and last lines flush against the row's borders with no breathing
room at all.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* docs(webkit): bring webkit-lists onto the current list-page shape (#929)
The shipped consumer skill still described the list page as it was before three
decisions landed, so a consumer following it built a page the product no longer
looks like:
- A first-level module index opens with a `PageHeading` again, and the module's
create action lives in it. The shell draws the breadcrumb only from the second
crumb up, so at first level the heading is the ONLY place the module's name
appears in the content.
- The size ladder is the hierarchy: the heading's action is the page's one
`large` (40px) control and the whole controls row is `medium` (32px). At 40px
both rows competed and the create button stopped being the page's answer.
- The controls row is narrowing only — `#actions` there is for an action on the
LISTING, not the page's own.
`last_updated` moves to 2026-08-24.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* feat: webkit documentation (#919)
* chore: [ENG-47606] add the documentation component category (#904)
* feat(theme): [ENG-47561] add the docs measure and the prose + mono body registers (#905)
* chore: [ENG-47606] add the documentation component category
* feat(theme): [ENG-47561] add the docs measure and the mono body register
* feat(theme): [ENG-47606] add the prose body register
* fix(theme): [ENG-47606] widen the docs measure to the form column
The documentation column was capped at 752px, narrower than every other measure in the system. Docs pages carry code blocks, card grids and tables, none of which fit a prose measure, so the column now takes the same 1192px the form measure uses.
The token is defined here, so it is set here: the doc component stack carries the same value, and an add/add disagreement between the two made every PR in that stack unmergeable.
* feat(webkit): [ENG-47616] add DocOnThisPage, the documentation TOC rail (#918)
* chore: [ENG-47606] add the documentation component category
* feat(theme): [ENG-47561] add the docs measure and the mono body register
* feat(theme): [ENG-47606] add the prose body register
* feat(webkit): [ENG-47606] add DocProse, the documentation typography contract
* test(storybook): add the linux baselines for the DocProse stories
* fix(theme): [ENG-47606] widen the docs measure to the form column
The documentation column was capped at 752px, narrower than every other measure in the system. Docs pages carry code blocks, card grids and tables, none of which fit a prose measure, so the column now takes the same 1192px the form measure uses.
* fix(webkit): [ENG-47606] pin the prose section step and separate the heading registers
The section rung was a descending pair that read 32 on a phone and on a laptop and only reached 48 at xl, so the same page broke its sections at different gaps by window width. It is now a flat 56 to open and 48 to close, taken from the theme's primitive spacing steps.
Each heading level now names two registers instead of one. On one register per level the base column collapsed: h2 was 16 (the body's own size), h3 and h4 were both 14, and between 640 and 768 the page title was overtaken by its own h2.
Weight and leading are no longer restated here — every element takes its type token verbatim, so the ladder is size and space only.
* feat(webkit): [ENG-47607] add DocCallout, the documentation admonition
* test(storybook): add the linux baselines for the DocProse and DocCallout stories
* fix(webkit): [ENG-47607] step an inline callout link to the only other weight the system defines
font-medium is 500, a weight no text token in the theme uses — the set is light (300) and normal (400). An inline link inside a callout now steps to 400.
* feat(webkit): [ENG-47611] add DocCard, the documentation grid cell
* test(storybook): add the linux baselines for the documentation stories
* feat(webkit): [ENG-47612] add DocCardGroup, the documentation card grid
* test(storybook): add the linux baselines for the doccardgroup stories
* feat(webkit): [ENG-47609] add DocItem, the documentation list row
* test(storybook): add the linux baselines for the docitem stories
* feat(webkit): [ENG-47613] add DocSteps and DocStep, the documentation procedure list
* test(storybook): add the linux baselines for the docsteps stories
* feat(webkit): [ENG-47608] add DocFrame, the documentation figure with a full-screen view
* test(storybook): add the linux baselines for the docframe stories
* test(storybook): stop snapshotting the autoplaying DocFrame clip
* feat(webkit): [ENG-47615] add DocUpdate and the heading-nav composable
* test(storybook): add the linux baselines for the docupdate stories
* feat(webkit): [ENG-47561] add DocPrompt, the copyable agent prompt
* test(storybook): add the linux baselines for the docprompt stories
* feat(webkit): [ENG-47610] add DocTooltip, the inline gloss
* test(storybook): add the linux baselines for the doctooltip stories
* fix(webkit): [ENG-47610] let the tooltip headline take its type token's weight
The headline restated font-medium (500), a weight no text token in the theme defines. It now takes text-body-sm verbatim, and the step to the tip below it is carried by ink colour.
* feat(webkit): [ENG-47618] add DocPagination, the documentation prev/next pair
* test(storybook): add the linux baselines for the docpagination stories
* feat(webkit): [ENG-47617] add DocPageHeader, the documentation masthead
* test(storybook): add the linux baselines for the docpageheader stories
* fix(webkit): [ENG-47617] stop the page title being overtaken by its own h2
text-heading-lg runs 18/18/30, so between 640 and 768 the masthead title rendered smaller than the prose h2 under it (20). The title now takes the same descending pair the prose h1 uses — text-heading-2xl with text-heading-xl from sm — which reads 30/30/36 and stays above every heading below it. Closes the theme gap the spec recorded.
The deck stops restating font-light; text-body-md already resolves to 300.
* feat(webkit): [ENG-47616] add DocOnThisPage, the documentation TOC rail
* test(storybook): add the linux baselines for the doconthispage stories
* fix(webkit): [ENG-47616] mark the rail's opening line so a shell can align to it
A docs shell aligns the outline rail against the page bar beside it, and needed a stable thing to measure. data-toc-title marks the rail's first line so the shell reaches it without depending on this component's internal structure.
* style(webkit): sort the use-heading-nav imports
* test(storybook): regenerate the documentation baselines after the typography fixes
---------
Co-authored-by: Gab <gabriel.mendonca@azion.com>
* fix(webkit): copy-button tooltip, card-box header inset, flow attrs (#936)
* fix(webkit): copy-button tooltip, card-box header inset, flow attrs
Three unrelated small fixes.
COPY-BUTTON COMPOSES TOOLTIP. An icon-only button says nothing to a pointer user
until it is hovered, so the glyph was unlabelled in practice — and the
"Copied" confirmation existed only as an accessible-name swap, announced to a
screen reader and invisible to everyone else. One string now names the control
AND labels its tooltip, so the two can never disagree: `ariaLabel` while idle,
`copiedLabel` for the two seconds after a write. The confirmation is visible
because it rides the same state.
CARD-BOX HEADER INSET `sm` → `md`. The header's horizontal padding now matches
the content's `p-(--spacing-md)` and the footer's `px-(--spacing-md)`, so the
title sits on the same vertical line as the first thing under it — and, in a
flush card, as the rows of whatever fills it (ItemList, Accordion and LogView
are all inset `--spacing-md`). At `--spacing-sm` every card header sat 4px left
of its own body.
FLOW FORWARDS `$attrs`. It declared `inheritAttrs: false` and then never spread
`$attrs` on its root, merging only `attrs.class`. Everything else a consumer
passed — `id`, `aria-*`, `data-*`, listeners — was silently dropped, which
`.claude/rules/root-element.md` exists to prevent. Same shape as its siblings
now: `v-bind="$attrs"` on the real root, with `cn` still merging the class.
* test(storybook): regenerate the card-box baselines for the header inset
* chore(webkit): raise the doc-prompt budget for the copy-button tooltip
DocPrompt composes CopyButton, which now wraps its IconButton in the
Tooltip overlay so the icon-only control is legible on hover. The
overlay machinery raises the measured entry from 6.20 KB to 8.61 KB;
the budget moves to 9 KB to carry that composition with headroom.
---------
Co-authored-by: Isaque Böck <isaque.santos@azion.com>
* fix(webkit): emit navigate when a collapsed breadcrumb crumb is picked
`select` is the Dropdown's event, not the option's: `Dropdown.Option`
forwards its activation to the root through the injected context, and the
root is what emits `select(event, value)`. The listener sat on the option,
where it never fired — so picking a collapsed crumb below `md` did
nothing at all. The menu closed and the reader stayed on the page. That is
the whole trail on a phone, since the collapsed shape is the only one a
narrow bar renders.
The test that covered this was skipped as a harness limitation ("@select
does not fire under test interaction"). It was not a harness limitation —
it was this bug, reproducing exactly as a real browser did. The listener
moved to the root and the test is un-skipped.
---------
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
Co-authored-by: Isaque Böck <isaque.santos@azion.com>
---------
Co-authored-by: Isaque dos Santos <isaque.santos@azion.com>
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@gabriel-lisboa-azion@isaque-bock-azion
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(theme): [ENG-47561] add the docs measure and the prose + mono body registers - #905

Merged
isaque-bock-azion merged 3 commits into
feat/webkit-documentationfrom
feat/ENG-47561-docs-measure-and-mono-body
Aug 24, 2026
Merged

feat(theme): [ENG-47561] add the docs measure and the prose + mono body registers#905
isaque-bock-azion merged 3 commits into
feat/webkit-documentationfrom
feat/ENG-47561-docs-measure-and-mono-body

Conversation

@gabriel-lisboa-azion

@gabriel-lisboa-aziongabriel-lisboa-azion commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • text-body-prose-md — body copy at --leading-relaxed (1.625). The whole text-body-* set is snug (1.375), and the documentation prose ladder was fitted against a 26px line box, so prose needs its own register. Line-height belongs in the token: check-authoring's leading-raw check deliberately rejects a leading-* override even in token form.
  • text-body-code-sm — the mono body register. The label-code-* set is leading-none, built for one-row-per-line code; this one wraps. Required by DocPrompt.
  • layout-measure-docs (--container-2xl, 752px) + layout-column-docs, completing the measure family (column / focused / form / form-create / docs). Docs are the one column capped by typography rather than payload width. The utility is generated by COLUMN_MEASURE + the columnUtility factory, so two entries produce the whole class.

How to test

  1. node packages/theme/src/scripts/build-tokens.mjs then git diff --stat packages/theme/dist — expect no further change; the committed dist is already current.
  2. cd packages/theme && node --test "test/**/*.test.mjs" — 12/12 pass, including the zero-unit assertions over every emitted token.
  3. grep "text-body-prose-md-line-height\|text-body-md-line-height" packages/theme/dist/v4/globals.css — expect 1.625 and 1.375 respectively; that difference is the whole point of the new token.
  4. grep -A5 '@utility text-body-code-sm' packages/theme/dist/v4/globals.css and the same for layout-column-docs — each must emit a real rule body. A token that compiles to nothing is the silent failure mode here.
  5. grep "layout-measure':" packages/theme/src/tokens/semantic/layouts.data.js — must still read var(--container-7xl) / 1620px (see Notes).

Notes

  • Deliberately excluded: the working branch this was carved from also retunes layout-measure from --container-7xl (1620px) to --container-6xl (1388px), moving home from the focused measure to the data measure. That is a separate product decision that would have silently changed the width of every data page, so it is not here — layout-measure and layout-measure-focused keep main's values and main's prose.
  • Stacked on chore: [ENG-47606] add the documentation component category #904 (the documentation category), which DocProse also needs. Merge chore: [ENG-47606] add the documentation component category #904 first; this retargets automatically.
  • No component consumes layout-column-docs yet: DocProse deliberately has no measure of its own, so the consumer caps the column.
  • Related: ENG-47561, ENG-47606, and the sibling component issues.

Delivery

The Documentation family is delivered on the feature branch feat/docs-components-bundle, not piecemeal to main — so main takes the whole family as one merge, while review stays per-component.

Merge order, bottom-up — each PR's base is the one before it, and each retargets automatically as its parent lands:

#904#905#906#907#908#909#910#911#912#913#914#915#916#917#918feat/docs-components-bundlemain

#904the documentation category (standards only)
#905theme: the docs measure + the prose and mono body registers
#906#918DocProse · DocCallout · DocCard · DocCardGroup · DocItem · DocSteps + DocStep · DocFrame · DocUpdate · DocPrompt · DocTooltip · DocPagination · DocPageHeader · DocOnThisPage

@gabriel-lisboa-aziongabriel-lisboa-azion changed the title feat(theme): [ENG-47561] add the docs measure and the mono body registerfeat(theme): [ENG-47561] add the docs measure and the prose + mono body registersAug 22, 2026
@gabriel-lisboa-azion
gabriel-lisboa-azionforce-pushed the feat/ENG-47561-docs-measure-and-mono-body branch from d3d46c0 to bc6575cCompareAugust 22, 2026 14:11
@gabriel-lisboa-azion
gabriel-lisboa-azion changed the base branch from main to chore/ENG-47606-documentation-categoryAugust 22, 2026 14:11
@isaque-bock-azion
isaque-bock-azion changed the base branch from chore/ENG-47606-documentation-category to feat/webkit-documentationAugust 24, 2026 13:04
@isaque-bock-azion
isaque-bock-azion merged commit e864f62 into feat/webkit-documentationAug 24, 2026
47 of 51 checks passed
@isaque-bock-azion
isaque-bock-azion deleted the feat/ENG-47561-docs-measure-and-mono-body branch August 24, 2026 13:14
isaque-bock-azion added a commit that referenced this pull request Aug 26, 2026
* chore: [ENG-47606] add the documentation component category (#904)
* feat(theme): [ENG-47561] add the docs measure and the prose + mono body registers (#905)
* chore: [ENG-47606] add the documentation component category
* feat(theme): [ENG-47561] add the docs measure and the mono body register
* feat(theme): [ENG-47606] add the prose body register
* fix(theme): [ENG-47606] widen the docs measure to the form column
The documentation column was capped at 752px, narrower than every other measure in the system. Docs pages carry code blocks, card grids and tables, none of which fit a prose measure, so the column now takes the same 1192px the form measure uses.
The token is defined here, so it is set here: the doc component stack carries the same value, and an add/add disagreement between the two made every PR in that stack unmergeable.
* feat(webkit): [ENG-47616] add DocOnThisPage, the documentation TOC rail (#918)
* chore: [ENG-47606] add the documentation component category
* feat(theme): [ENG-47561] add the docs measure and the mono body register
* feat(theme): [ENG-47606] add the prose body register
* feat(webkit): [ENG-47606] add DocProse, the documentation typography contract
* test(storybook): add the linux baselines for the DocProse stories
* fix(theme): [ENG-47606] widen the docs measure to the form column
The documentation column was capped at 752px, narrower than every other measure in the system. Docs pages carry code blocks, card grids and tables, none of which fit a prose measure, so the column now takes the same 1192px the form measure uses.
* fix(webkit): [ENG-47606] pin the prose section step and separate the heading registers
The section rung was a descending pair that read 32 on a phone and on a laptop and only reached 48 at xl, so the same page broke its sections at different gaps by window width. It is now a flat 56 to open and 48 to close, taken from the theme's primitive spacing steps.
Each heading level now names two registers instead of one. On one register per level the base column collapsed: h2 was 16 (the body's own size), h3 and h4 were both 14, and between 640 and 768 the page title was overtaken by its own h2.
Weight and leading are no longer restated here — every element takes its type token verbatim, so the ladder is size and space only.
* feat(webkit): [ENG-47607] add DocCallout, the documentation admonition
* test(storybook): add the linux baselines for the DocProse and DocCallout stories
* fix(webkit): [ENG-47607] step an inline callout link to the only other weight the system defines
font-medium is 500, a weight no text token in the theme uses — the set is light (300) and normal (400). An inline link inside a callout now steps to 400.
* feat(webkit): [ENG-47611] add DocCard, the documentation grid cell
* test(storybook): add the linux baselines for the documentation stories
* feat(webkit): [ENG-47612] add DocCardGroup, the documentation card grid
* test(storybook): add the linux baselines for the doccardgroup stories
* feat(webkit): [ENG-47609] add DocItem, the documentation list row
* test(storybook): add the linux baselines for the docitem stories
* feat(webkit): [ENG-47613] add DocSteps and DocStep, the documentation procedure list
* test(storybook): add the linux baselines for the docsteps stories
* feat(webkit): [ENG-47608] add DocFrame, the documentation figure with a full-screen view
* test(storybook): add the linux baselines for the docframe stories
* test(storybook): stop snapshotting the autoplaying DocFrame clip
* feat(webkit): [ENG-47615] add DocUpdate and the heading-nav composable
* test(storybook): add the linux baselines for the docupdate stories
* feat(webkit): [ENG-47561] add DocPrompt, the copyable agent prompt
* test(storybook): add the linux baselines for the docprompt stories
* feat(webkit): [ENG-47610] add DocTooltip, the inline gloss
* test(storybook): add the linux baselines for the doctooltip stories
* fix(webkit): [ENG-47610] let the tooltip headline take its type token's weight
The headline restated font-medium (500), a weight no text token in the theme defines. It now takes text-body-sm verbatim, and the step to the tip below it is carried by ink colour.
* feat(webkit): [ENG-47618] add DocPagination, the documentation prev/next pair
* test(storybook): add the linux baselines for the docpagination stories
* feat(webkit): [ENG-47617] add DocPageHeader, the documentation masthead
* test(storybook): add the linux baselines for the docpageheader stories
* fix(webkit): [ENG-47617] stop the page title being overtaken by its own h2
text-heading-lg runs 18/18/30, so between 640 and 768 the masthead title rendered smaller than the prose h2 under it (20). The title now takes the same descending pair the prose h1 uses — text-heading-2xl with text-heading-xl from sm — which reads 30/30/36 and stays above every heading below it. Closes the theme gap the spec recorded.
The deck stops restating font-light; text-body-md already resolves to 300.
* feat(webkit): [ENG-47616] add DocOnThisPage, the documentation TOC rail
* test(storybook): add the linux baselines for the doconthispage stories
* fix(webkit): [ENG-47616] mark the rail's opening line so a shell can align to it
A docs shell aligns the outline rail against the page bar beside it, and needed a stable thing to measure. data-toc-title marks the rail's first line so the shell reaches it without depending on this component's internal structure.
* style(webkit): sort the use-heading-nav imports
* test(storybook): regenerate the documentation baselines after the typography fixes
---------
Co-authored-by: Gab <gabriel.mendonca@azion.com>
isaque-bock-azion added a commit that referenced this pull request Aug 28, 2026
#939)
* feat(theme): add --container-site, the marketing site's shared measure (#923)
* feat(theme): add --container-site, the marketing site's shared measure
The azion.com pages are a single vertical frame: the nav bar, the hero's inner
column, every section below it and the footer all sit on the same centred
column, and the border-x that runs down the page is only continuous because
those four agree to the pixel. A width re-typed per band cannot hold that — the
frame is one decision, so it gets one place to live.
`5xl` (1192px) is a rung of the shared container ladder, not a number chosen for
this page. It is its own token rather than a reference to one of the console
measures because the two answer different questions: a console measure caps a
payload (a table, a form), while this one is the width of a frame whose four
bands have to line up, so retuning either must not move the other.
* refactor(webkit): point the footer column at --container-site (#928)
The footer's column named `--container-5xl` directly. On the marketing site the
nav bar, the hero band, the framed sections and this footer are ONE vertical
frame, and its side rules only meet because all four resolve to the same width —
so the measure has to be a token they share, not a rung each of them names.
Retuning it now moves them together, which a hardcoded rung here could not do.
No visual change: `--container-site` resolves to `--container-5xl`, which is
what the class already said.
* feat(webkit): add the resizable-panel layout primitive (#902)
* feat(webkit): add the resizable-panel layout primitive
A group of adjacent regions whose shared edges the reader can drag — the layout
behind a workspace, where an editor, a terminal under it and a preview beside it
are all content and the split between them is the reader's to set.
It exists because `Sidebar` was being used for both jobs. `Sidebar` is the app's
NAVIGATION rail: it collapses to icons, it hides, it is the thing you leave to go
somewhere. A terminal, a preview and a code editor are not places you navigate to
— they are the work — and dressing two of them as navigation rails produced a
screen with three "sidebars" and one document, on an axis `Sidebar` does not even
have: it has no horizontal edge, so a terminal under an editor had to hand-roll
its own splitter. This component owns the EDGE; `Sidebar` goes back to owning
navigation.
Composition: `ResizablePanel` with `Pane` and `Handle`, attached for dot-notation
and exported standalone. The compound is built by naming the static surface and
casting once rather than with `Object.assign`, whose inferred type walks each
part's local `interface Props` into the declaration and trips TS4082.
Ships the spec, a browser-mode test suite, the story, the `-root` and
per-sub-component exports, and a size budget.
* test(storybook): regenerate linux baselines for resizable-panel
Adds the 24 snapshots for the four new ResizablePanel stories across the 6-mode
matrix. Generated by the Storybook Regenerate Baseline workflow on this branch's
HEAD — baselines are ubuntu-only, so a local run would not match the folder the
visual gate compares against.
No existing snapshot is modified or deleted; the component is additive.
* test(webkit): compose resizable-panel through its named sub-component exports
Every other composition suite in the package renders through the named exports
(`AccordionItem`, `DropdownTrigger`, …) and checks dot-notation once, as an
identity assertion against those same exports. This file was the only one that
rendered through the root's statics — `h(ResizablePanel.Pane, …)` — which made
static analysis flag eight unguarded member accesses: a `.vue` default export is
generated by the SFC compiler, so the analyzer sees no `export default`, reads
the imported binding as undefined, and reports every `.Pane` / `.Handle` read on
it.
Compose through `ResizablePanelPane` / `ResizablePanelHandle` instead, and keep
the compound contract in the one place it is the subject rather than the vehicle,
asserting identity against those exports through a guarded read.
Same nine tests, same behaviour covered — the dot-notation contract is now
verified more tightly than `toBeTruthy` did.
---------
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* feat(webkit): give Select, MultiSelect and Dropdown one shared leading column (#935)
A list that mixes iconed and icon-less options used to run two label edges: an
option with a glyph indented its text, one without it did not, so the eye had no
column to read down. Fixing it per-option is not possible — no option knows
whether any sibling has an icon.
So the reservation is made by the LIST, with a self-triggering `:has()` rule.
Every option renders a fixed leading box and marks itself `data-leading` only
when it actually has a glyph; the box is `hidden` and turns back on under
`group-has-[[data-leading]]/options:flex`. `:has()` matches the marker even
while the box is hidden, so one iconed option is enough to open the column for
the whole list — and a list with no icons at all reserves nothing and pays no
indent. The same `group/options` marker is on all three panels, so Select,
MultiSelect and Dropdown behave identically.
The group label is now OPTIONAL, and sentence-case `text-label-sm` rather than
an uppercase overline: an unlabelled group is a grouping the reader does not
need named, and an overline read as a second heading level inside a panel that
has none.
Group rhythm is `--spacing-sm` above every group but the first — the same step
CommandMenu already puts between two bare groups. Dropdown keeps its hairline
and Select does not: the Figma frame for the Select panel separates groups by
space alone. Dropdown's divider moves to the group root, which spans the panel's
full inner width, so it reads edge-to-edge like the panel's own border; the
horizontal inset the rows need moves onto the content wrapper.
Panels also gain top/bottom slots for content that is not an option (a search
row, a footer action), and the panel's vertical padding steps up one so the
first and last row do not crowd its border.
Includes the 10 dropdown visual baselines this moves.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* fix(icons): resolve currentColor per theme in the colored-icon CSS (#930)
Astro's "A" shipped invisible on every light-mode surface.
Some brand logos are only partly colored: Astro is a fixed red-to-magenta
gradient swoosh under an "A" that is the brand's INK — black on a light
background, white on a dark one. That part is authored as `fill="currentColor"`,
which is what `validate-svg.mjs` asks for, and it resolves correctly wherever
the SVG is INLINED (dist/color-catalog.json → the gallery, Storybook).
A `background-image` is a separate document, so it inherits nothing from the
element painting it. `currentColor` there resolves against the SVG's own root,
not the consumer's text color, and no amount of `color` on the `<i>` reaches it.
Left alone it lands on the UA default and the icon disappears in one theme —
which is what happened: `ai-astro.svg` had the "A" hardcoded `#FAFAFA`, so it
was white-on-white everywhere in light mode.
So the generator resolves it. An icon containing `currentColor` is emitted
twice: the base rule with the light ink, and an override with the dark ink under
the same selectors @aziontech/theme uses for its dark tokens
(`[data-theme=dark]`, `.dark`, `.azion.azion-dark`). The override's extra
selector out-specifies the base, so source order does not decide the winner.
The inks are literals (`#141414` / `#FAFAFA`) rather than `var(--text-default)`
by necessity — same document isolation — and are the theme's own values for that
token in each mode. A fully-colored logo with no ink parts still emits one rule,
exactly as before: 17 colored icons, 1 theme-aware.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* docs(webkit): ship the webkit-create-surface and webkit-errors skills (#895)
* docs(webkit): ship the webkit-create-surface and webkit-errors skills
webkit-create-surface fixes where a create or settings form lives — a first-level
resource creates on a page, anything created inside a resource opens a drawer,
nothing creates in a dialog — plus the anatomy all of them share, what belongs
behind the collapsed Advanced band, and the commit model each surface uses.
webkit-errors is the one decision table for where a failure goes: client-side
validation on the field, a server rejection scoped to one field as a Message in
that section, an unscoped request failure as a toast with Retry, the signed-out
screens where auth never toasts, and the async failure that outlives its screen.
Both join the CLI bundle and the ui-craft routing table; webkit-form and
webkit-ui-states now hand their error rows off to webkit-errors.
* docs(webkit): ship the webkit-microcopy consumer skill (#931)
The writing rules for Azion product interfaces, as a skill the adoption toolkit
ships — so a consuming project's agent writes the same strings the console does
instead of inventing a house style per screen.
It fixes the things that were being decided per-PR: punctuation and
capitalization (no em dash, no ampersand, no parentheses in labels, no bold in
running text, sentence case with Azion product names capitalized), which surface
each kind of copy belongs on (a label names, a description constrains, a Message
explains impact, a Popover defines, a Tag states), one word per concept (Select
not Pick, Application not Edge Application, and no `edge` as a prefix or a
location), navigation and IA naming, and copy that survives every breakpoint.
Registered in the three places a consumer skill has to be registered or it never
reaches a project: the CLI's file list (`plan.js`), the UI-craft pack in
`plan.test.mjs` (now 18 skills), and the routing index in `CLAUDE.fragment.md`,
where it gets its own **Words** line — it is the only skill about strings rather
than structure, and it applies during every other one.
Distinct from the generic `content-microcopy` the plan drops: that one is
house-agnostic, this one carries Azion's own vocabulary.
---------
Co-authored-by: Isaque Böck <isaque.santos@azion.com>
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* feat(theme): add the page-enter and content-enter arrival animations (#896)
* feat(theme): add the page-enter and content-enter arrival animations
The two entrances an app shell needs, as a pair. page-enter is the route
transition: it starts one --layout-boundary-inline to the LEFT of where it lands
and travels right while fading up, so a page reads as arriving FROM the nav row
that was clicked — a plain fade has no origin and a right-side arrival reads as
going back. content-enter is content settling inside a page already on screen, so
it rises a hair instead of travelling, and takes a --content-enter-delay stagger
(with backwards fill, or a delayed follower would paint its landed state first).
Neither fills forwards: page-enter's box is usually the scroll container, and a
lingering translate would make it a containing block for any fixed descendant.
* docs(webkit): teach the two arrival animations in webkit-motion-polish
Documents both utilities and their knobs, and the failure they exist to prevent:
a page arrives once, so a mount-time entrance inside a page whose route
transition is still running lockesteps with it into one diagonal move (identical
opacities frame for frame is the tell). Adds that shape and the two-shells-per-
screen variant to the silent-failure table. catalog.json rebuilt so the two
animations reach the MCP and validate-spec-compliance.
---------
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* docs: rewrite add-animation for the CSS-first theme (#897)
The steps still described the deleted v3 plugin (semantic/animations.js,
addUtilities/addComponents, the camelCase keyframe object form). An animation is
now two edits under primitives/animations/ — the shorthand in animate.js, which
is what makes animate-<name> a utility at all, and the CSS-string keyframes in
keyframes.js, which Tailwind v4 does NOT generate — compiled by build:tokens,
which must run before catalog:build or nothing renders.
Also records the three things the two new arrivals taught: a per-instance value
rides a var() with a token default inside the keyframe, a stagger rides a var()
delay with backwards fill, and fill is a decision — forwards leaves a translate
that makes the element a containing block for any fixed descendant.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* fix(webkit): give table cells block padding so tall content can breathe (#899)
A cell was `min-h-12` with horizontal padding only. For a single-line cell that
is invisible — the 48px minimum decides the height on its own against ~20px of
text — so every existing row renders exactly as before.
It only shows on a cell whose content is TALLER than the minimum: a wrapped list
of tags, a two-line summary. There the height came from the content alone, which
put the first and last lines flush against the row's borders with no breathing
room at all.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* docs(webkit): bring webkit-lists onto the current list-page shape (#929)
The shipped consumer skill still described the list page as it was before three
decisions landed, so a consumer following it built a page the product no longer
looks like:
- A first-level module index opens with a `PageHeading` again, and the module's
create action lives in it. The shell draws the breadcrumb only from the second
crumb up, so at first level the heading is the ONLY place the module's name
appears in the content.
- The size ladder is the hierarchy: the heading's action is the page's one
`large` (40px) control and the whole controls row is `medium` (32px). At 40px
both rows competed and the create button stopped being the page's answer.
- The controls row is narrowing only — `#actions` there is for an action on the
LISTING, not the page's own.
`last_updated` moves to 2026-08-24.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* feat: webkit documentation (#919)
* chore: [ENG-47606] add the documentation component category (#904)
* feat(theme): [ENG-47561] add the docs measure and the prose + mono body registers (#905)
* chore: [ENG-47606] add the documentation component category
* feat(theme): [ENG-47561] add the docs measure and the mono body register
* feat(theme): [ENG-47606] add the prose body register
* fix(theme): [ENG-47606] widen the docs measure to the form column
The documentation column was capped at 752px, narrower than every other measure in the system. Docs pages carry code blocks, card grids and tables, none of which fit a prose measure, so the column now takes the same 1192px the form measure uses.
The token is defined here, so it is set here: the doc component stack carries the same value, and an add/add disagreement between the two made every PR in that stack unmergeable.
* feat(webkit): [ENG-47616] add DocOnThisPage, the documentation TOC rail (#918)
* chore: [ENG-47606] add the documentation component category
* feat(theme): [ENG-47561] add the docs measure and the mono body register
* feat(theme): [ENG-47606] add the prose body register
* feat(webkit): [ENG-47606] add DocProse, the documentation typography contract
* test(storybook): add the linux baselines for the DocProse stories
* fix(theme): [ENG-47606] widen the docs measure to the form column
The documentation column was capped at 752px, narrower than every other measure in the system. Docs pages carry code blocks, card grids and tables, none of which fit a prose measure, so the column now takes the same 1192px the form measure uses.
* fix(webkit): [ENG-47606] pin the prose section step and separate the heading registers
The section rung was a descending pair that read 32 on a phone and on a laptop and only reached 48 at xl, so the same page broke its sections at different gaps by window width. It is now a flat 56 to open and 48 to close, taken from the theme's primitive spacing steps.
Each heading level now names two registers instead of one. On one register per level the base column collapsed: h2 was 16 (the body's own size), h3 and h4 were both 14, and between 640 and 768 the page title was overtaken by its own h2.
Weight and leading are no longer restated here — every element takes its type token verbatim, so the ladder is size and space only.
* feat(webkit): [ENG-47607] add DocCallout, the documentation admonition
* test(storybook): add the linux baselines for the DocProse and DocCallout stories
* fix(webkit): [ENG-47607] step an inline callout link to the only other weight the system defines
font-medium is 500, a weight no text token in the theme uses — the set is light (300) and normal (400). An inline link inside a callout now steps to 400.
* feat(webkit): [ENG-47611] add DocCard, the documentation grid cell
* test(storybook): add the linux baselines for the documentation stories
* feat(webkit): [ENG-47612] add DocCardGroup, the documentation card grid
* test(storybook): add the linux baselines for the doccardgroup stories
* feat(webkit): [ENG-47609] add DocItem, the documentation list row
* test(storybook): add the linux baselines for the docitem stories
* feat(webkit): [ENG-47613] add DocSteps and DocStep, the documentation procedure list
* test(storybook): add the linux baselines for the docsteps stories
* feat(webkit): [ENG-47608] add DocFrame, the documentation figure with a full-screen view
* test(storybook): add the linux baselines for the docframe stories
* test(storybook): stop snapshotting the autoplaying DocFrame clip
* feat(webkit): [ENG-47615] add DocUpdate and the heading-nav composable
* test(storybook): add the linux baselines for the docupdate stories
* feat(webkit): [ENG-47561] add DocPrompt, the copyable agent prompt
* test(storybook): add the linux baselines for the docprompt stories
* feat(webkit): [ENG-47610] add DocTooltip, the inline gloss
* test(storybook): add the linux baselines for the doctooltip stories
* fix(webkit): [ENG-47610] let the tooltip headline take its type token's weight
The headline restated font-medium (500), a weight no text token in the theme defines. It now takes text-body-sm verbatim, and the step to the tip below it is carried by ink colour.
* feat(webkit): [ENG-47618] add DocPagination, the documentation prev/next pair
* test(storybook): add the linux baselines for the docpagination stories
* feat(webkit): [ENG-47617] add DocPageHeader, the documentation masthead
* test(storybook): add the linux baselines for the docpageheader stories
* fix(webkit): [ENG-47617] stop the page title being overtaken by its own h2
text-heading-lg runs 18/18/30, so between 640 and 768 the masthead title rendered smaller than the prose h2 under it (20). The title now takes the same descending pair the prose h1 uses — text-heading-2xl with text-heading-xl from sm — which reads 30/30/36 and stays above every heading below it. Closes the theme gap the spec recorded.
The deck stops restating font-light; text-body-md already resolves to 300.
* feat(webkit): [ENG-47616] add DocOnThisPage, the documentation TOC rail
* test(storybook): add the linux baselines for the doconthispage stories
* fix(webkit): [ENG-47616] mark the rail's opening line so a shell can align to it
A docs shell aligns the outline rail against the page bar beside it, and needed a stable thing to measure. data-toc-title marks the rail's first line so the shell reaches it without depending on this component's internal structure.
* style(webkit): sort the use-heading-nav imports
* test(storybook): regenerate the documentation baselines after the typography fixes
---------
Co-authored-by: Gab <gabriel.mendonca@azion.com>
* fix(webkit): copy-button tooltip, card-box header inset, flow attrs (#936)
* fix(webkit): copy-button tooltip, card-box header inset, flow attrs
Three unrelated small fixes.
COPY-BUTTON COMPOSES TOOLTIP. An icon-only button says nothing to a pointer user
until it is hovered, so the glyph was unlabelled in practice — and the
"Copied" confirmation existed only as an accessible-name swap, announced to a
screen reader and invisible to everyone else. One string now names the control
AND labels its tooltip, so the two can never disagree: `ariaLabel` while idle,
`copiedLabel` for the two seconds after a write. The confirmation is visible
because it rides the same state.
CARD-BOX HEADER INSET `sm` → `md`. The header's horizontal padding now matches
the content's `p-(--spacing-md)` and the footer's `px-(--spacing-md)`, so the
title sits on the same vertical line as the first thing under it — and, in a
flush card, as the rows of whatever fills it (ItemList, Accordion and LogView
are all inset `--spacing-md`). At `--spacing-sm` every card header sat 4px left
of its own body.
FLOW FORWARDS `$attrs`. It declared `inheritAttrs: false` and then never spread
`$attrs` on its root, merging only `attrs.class`. Everything else a consumer
passed — `id`, `aria-*`, `data-*`, listeners — was silently dropped, which
`.claude/rules/root-element.md` exists to prevent. Same shape as its siblings
now: `v-bind="$attrs"` on the real root, with `cn` still merging the class.
* test(storybook): regenerate the card-box baselines for the header inset
* chore(webkit): raise the doc-prompt budget for the copy-button tooltip
DocPrompt composes CopyButton, which now wraps its IconButton in the
Tooltip overlay so the icon-only control is legible on hover. The
overlay machinery raises the measured entry from 6.20 KB to 8.61 KB;
the budget moves to 9 KB to carry that composition with headroom.
---------
Co-authored-by: Isaque Böck <isaque.santos@azion.com>
* fix(webkit): emit navigate when a collapsed breadcrumb crumb is picked
`select` is the Dropdown's event, not the option's: `Dropdown.Option`
forwards its activation to the root through the injected context, and the
root is what emits `select(event, value)`. The listener sat on the option,
where it never fired — so picking a collapsed crumb below `md` did
nothing at all. The menu closed and the reader stayed on the page. That is
the whole trail on a phone, since the collapsed shape is the only one a
narrow bar renders.
The test that covered this was skipped as a harness limitation ("@select
does not fire under test interaction"). It was not a harness limitation —
it was this bug, reproducing exactly as a real browser did. The listener
moved to the root and the test is un-skipped.
---------
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
Co-authored-by: Isaque Böck <isaque.santos@azion.com>
isaque-bock-azion added a commit that referenced this pull request Aug 28, 2026
* feat(webkit): let a table column declare a minWidth floor
A bounded column — a chip list, a date, a count, an id — could only be sized by
a fixed `width`, and a fixed width is a bet in both directions: guess high and
the column reserves space the rest of the row needs, guess low and the content
inside it truncates (a chip list ends up as `SQL In…`). `minWidth` only has to
be right about the minimum: the column resolves to the widest of that floor and
what it actually holds — its header, with the sort affordance, and every
rendered body cell. `width` still wins when both are set, so an explicit width
remains the consumer overriding the measurement outright.
ONE width is resolved per column and applied to the header and to every body
cell, which keeps the invariant fixed widths exist for: the column resolves
identically in every row. A per-cell `min-width` would let each row size to its
own content and drift the header away from the body.
It has to be measured, because a flex row cannot express "as wide as the widest
cell in this column" — every row is its own flex container and knows nothing
about its siblings. The measurement is the same clone-based one the resize floor
already uses, so a column's auto width and its drag floor can never disagree.
Three things the measurement has to respect:
- Debounced (32ms). The rendered content changes in bursts — a sort, a page
turn, a column toggled back on — and measuring reads layout, so an
un-coalesced version would reflow per keystroke of a filter.
- Skipped while `loading`. Skeleton rows are not the content; measuring against
them would size every column to a placeholder and never grow it back.
- Re-run on `document.fonts.ready`. Text width is font-dependent and the first
paint can happen on the fallback face.
`autoWidths` joins the row `v-memo` so a resolved width actually reaches the
body, and feeds `frozenWidthOf` so a frozen `minWidth` column contributes its
real width to a neighbour's sticky offset.
* test(storybook): regenerate the table baselines for the column min-width
* fix(webkit): emit navigate when a collapsed breadcrumb crumb is picked (#939)
* feat(theme): add --container-site, the marketing site's shared measure (#923)
* feat(theme): add --container-site, the marketing site's shared measure
The azion.com pages are a single vertical frame: the nav bar, the hero's inner
column, every section below it and the footer all sit on the same centred
column, and the border-x that runs down the page is only continuous because
those four agree to the pixel. A width re-typed per band cannot hold that — the
frame is one decision, so it gets one place to live.
`5xl` (1192px) is a rung of the shared container ladder, not a number chosen for
this page. It is its own token rather than a reference to one of the console
measures because the two answer different questions: a console measure caps a
payload (a table, a form), while this one is the width of a frame whose four
bands have to line up, so retuning either must not move the other.
* refactor(webkit): point the footer column at --container-site (#928)
The footer's column named `--container-5xl` directly. On the marketing site the
nav bar, the hero band, the framed sections and this footer are ONE vertical
frame, and its side rules only meet because all four resolve to the same width —
so the measure has to be a token they share, not a rung each of them names.
Retuning it now moves them together, which a hardcoded rung here could not do.
No visual change: `--container-site` resolves to `--container-5xl`, which is
what the class already said.
* feat(webkit): add the resizable-panel layout primitive (#902)
* feat(webkit): add the resizable-panel layout primitive
A group of adjacent regions whose shared edges the reader can drag — the layout
behind a workspace, where an editor, a terminal under it and a preview beside it
are all content and the split between them is the reader's to set.
It exists because `Sidebar` was being used for both jobs. `Sidebar` is the app's
NAVIGATION rail: it collapses to icons, it hides, it is the thing you leave to go
somewhere. A terminal, a preview and a code editor are not places you navigate to
— they are the work — and dressing two of them as navigation rails produced a
screen with three "sidebars" and one document, on an axis `Sidebar` does not even
have: it has no horizontal edge, so a terminal under an editor had to hand-roll
its own splitter. This component owns the EDGE; `Sidebar` goes back to owning
navigation.
Composition: `ResizablePanel` with `Pane` and `Handle`, attached for dot-notation
and exported standalone. The compound is built by naming the static surface and
casting once rather than with `Object.assign`, whose inferred type walks each
part's local `interface Props` into the declaration and trips TS4082.
Ships the spec, a browser-mode test suite, the story, the `-root` and
per-sub-component exports, and a size budget.
* test(storybook): regenerate linux baselines for resizable-panel
Adds the 24 snapshots for the four new ResizablePanel stories across the 6-mode
matrix. Generated by the Storybook Regenerate Baseline workflow on this branch's
HEAD — baselines are ubuntu-only, so a local run would not match the folder the
visual gate compares against.
No existing snapshot is modified or deleted; the component is additive.
* test(webkit): compose resizable-panel through its named sub-component exports
Every other composition suite in the package renders through the named exports
(`AccordionItem`, `DropdownTrigger`, …) and checks dot-notation once, as an
identity assertion against those same exports. This file was the only one that
rendered through the root's statics — `h(ResizablePanel.Pane, …)` — which made
static analysis flag eight unguarded member accesses: a `.vue` default export is
generated by the SFC compiler, so the analyzer sees no `export default`, reads
the imported binding as undefined, and reports every `.Pane` / `.Handle` read on
it.
Compose through `ResizablePanelPane` / `ResizablePanelHandle` instead, and keep
the compound contract in the one place it is the subject rather than the vehicle,
asserting identity against those exports through a guarded read.
Same nine tests, same behaviour covered — the dot-notation contract is now
verified more tightly than `toBeTruthy` did.
---------
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* feat(webkit): give Select, MultiSelect and Dropdown one shared leading column (#935)
A list that mixes iconed and icon-less options used to run two label edges: an
option with a glyph indented its text, one without it did not, so the eye had no
column to read down. Fixing it per-option is not possible — no option knows
whether any sibling has an icon.
So the reservation is made by the LIST, with a self-triggering `:has()` rule.
Every option renders a fixed leading box and marks itself `data-leading` only
when it actually has a glyph; the box is `hidden` and turns back on under
`group-has-[[data-leading]]/options:flex`. `:has()` matches the marker even
while the box is hidden, so one iconed option is enough to open the column for
the whole list — and a list with no icons at all reserves nothing and pays no
indent. The same `group/options` marker is on all three panels, so Select,
MultiSelect and Dropdown behave identically.
The group label is now OPTIONAL, and sentence-case `text-label-sm` rather than
an uppercase overline: an unlabelled group is a grouping the reader does not
need named, and an overline read as a second heading level inside a panel that
has none.
Group rhythm is `--spacing-sm` above every group but the first — the same step
CommandMenu already puts between two bare groups. Dropdown keeps its hairline
and Select does not: the Figma frame for the Select panel separates groups by
space alone. Dropdown's divider moves to the group root, which spans the panel's
full inner width, so it reads edge-to-edge like the panel's own border; the
horizontal inset the rows need moves onto the content wrapper.
Panels also gain top/bottom slots for content that is not an option (a search
row, a footer action), and the panel's vertical padding steps up one so the
first and last row do not crowd its border.
Includes the 10 dropdown visual baselines this moves.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* fix(icons): resolve currentColor per theme in the colored-icon CSS (#930)
Astro's "A" shipped invisible on every light-mode surface.
Some brand logos are only partly colored: Astro is a fixed red-to-magenta
gradient swoosh under an "A" that is the brand's INK — black on a light
background, white on a dark one. That part is authored as `fill="currentColor"`,
which is what `validate-svg.mjs` asks for, and it resolves correctly wherever
the SVG is INLINED (dist/color-catalog.json → the gallery, Storybook).
A `background-image` is a separate document, so it inherits nothing from the
element painting it. `currentColor` there resolves against the SVG's own root,
not the consumer's text color, and no amount of `color` on the `<i>` reaches it.
Left alone it lands on the UA default and the icon disappears in one theme —
which is what happened: `ai-astro.svg` had the "A" hardcoded `#FAFAFA`, so it
was white-on-white everywhere in light mode.
So the generator resolves it. An icon containing `currentColor` is emitted
twice: the base rule with the light ink, and an override with the dark ink under
the same selectors @aziontech/theme uses for its dark tokens
(`[data-theme=dark]`, `.dark`, `.azion.azion-dark`). The override's extra
selector out-specifies the base, so source order does not decide the winner.
The inks are literals (`#141414` / `#FAFAFA`) rather than `var(--text-default)`
by necessity — same document isolation — and are the theme's own values for that
token in each mode. A fully-colored logo with no ink parts still emits one rule,
exactly as before: 17 colored icons, 1 theme-aware.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* docs(webkit): ship the webkit-create-surface and webkit-errors skills (#895)
* docs(webkit): ship the webkit-create-surface and webkit-errors skills
webkit-create-surface fixes where a create or settings form lives — a first-level
resource creates on a page, anything created inside a resource opens a drawer,
nothing creates in a dialog — plus the anatomy all of them share, what belongs
behind the collapsed Advanced band, and the commit model each surface uses.
webkit-errors is the one decision table for where a failure goes: client-side
validation on the field, a server rejection scoped to one field as a Message in
that section, an unscoped request failure as a toast with Retry, the signed-out
screens where auth never toasts, and the async failure that outlives its screen.
Both join the CLI bundle and the ui-craft routing table; webkit-form and
webkit-ui-states now hand their error rows off to webkit-errors.
* docs(webkit): ship the webkit-microcopy consumer skill (#931)
The writing rules for Azion product interfaces, as a skill the adoption toolkit
ships — so a consuming project's agent writes the same strings the console does
instead of inventing a house style per screen.
It fixes the things that were being decided per-PR: punctuation and
capitalization (no em dash, no ampersand, no parentheses in labels, no bold in
running text, sentence case with Azion product names capitalized), which surface
each kind of copy belongs on (a label names, a description constrains, a Message
explains impact, a Popover defines, a Tag states), one word per concept (Select
not Pick, Application not Edge Application, and no `edge` as a prefix or a
location), navigation and IA naming, and copy that survives every breakpoint.
Registered in the three places a consumer skill has to be registered or it never
reaches a project: the CLI's file list (`plan.js`), the UI-craft pack in
`plan.test.mjs` (now 18 skills), and the routing index in `CLAUDE.fragment.md`,
where it gets its own **Words** line — it is the only skill about strings rather
than structure, and it applies during every other one.
Distinct from the generic `content-microcopy` the plan drops: that one is
house-agnostic, this one carries Azion's own vocabulary.
---------
Co-authored-by: Isaque Böck <isaque.santos@azion.com>
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* feat(theme): add the page-enter and content-enter arrival animations (#896)
* feat(theme): add the page-enter and content-enter arrival animations
The two entrances an app shell needs, as a pair. page-enter is the route
transition: it starts one --layout-boundary-inline to the LEFT of where it lands
and travels right while fading up, so a page reads as arriving FROM the nav row
that was clicked — a plain fade has no origin and a right-side arrival reads as
going back. content-enter is content settling inside a page already on screen, so
it rises a hair instead of travelling, and takes a --content-enter-delay stagger
(with backwards fill, or a delayed follower would paint its landed state first).
Neither fills forwards: page-enter's box is usually the scroll container, and a
lingering translate would make it a containing block for any fixed descendant.
* docs(webkit): teach the two arrival animations in webkit-motion-polish
Documents both utilities and their knobs, and the failure they exist to prevent:
a page arrives once, so a mount-time entrance inside a page whose route
transition is still running lockesteps with it into one diagonal move (identical
opacities frame for frame is the tell). Adds that shape and the two-shells-per-
screen variant to the silent-failure table. catalog.json rebuilt so the two
animations reach the MCP and validate-spec-compliance.
---------
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* docs: rewrite add-animation for the CSS-first theme (#897)
The steps still described the deleted v3 plugin (semantic/animations.js,
addUtilities/addComponents, the camelCase keyframe object form). An animation is
now two edits under primitives/animations/ — the shorthand in animate.js, which
is what makes animate-<name> a utility at all, and the CSS-string keyframes in
keyframes.js, which Tailwind v4 does NOT generate — compiled by build:tokens,
which must run before catalog:build or nothing renders.
Also records the three things the two new arrivals taught: a per-instance value
rides a var() with a token default inside the keyframe, a stagger rides a var()
delay with backwards fill, and fill is a decision — forwards leaves a translate
that makes the element a containing block for any fixed descendant.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* fix(webkit): give table cells block padding so tall content can breathe (#899)
A cell was `min-h-12` with horizontal padding only. For a single-line cell that
is invisible — the 48px minimum decides the height on its own against ~20px of
text — so every existing row renders exactly as before.
It only shows on a cell whose content is TALLER than the minimum: a wrapped list
of tags, a two-line summary. There the height came from the content alone, which
put the first and last lines flush against the row's borders with no breathing
room at all.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* docs(webkit): bring webkit-lists onto the current list-page shape (#929)
The shipped consumer skill still described the list page as it was before three
decisions landed, so a consumer following it built a page the product no longer
looks like:
- A first-level module index opens with a `PageHeading` again, and the module's
create action lives in it. The shell draws the breadcrumb only from the second
crumb up, so at first level the heading is the ONLY place the module's name
appears in the content.
- The size ladder is the hierarchy: the heading's action is the page's one
`large` (40px) control and the whole controls row is `medium` (32px). At 40px
both rows competed and the create button stopped being the page's answer.
- The controls row is narrowing only — `#actions` there is for an action on the
LISTING, not the page's own.
`last_updated` moves to 2026-08-24.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* feat: webkit documentation (#919)
* chore: [ENG-47606] add the documentation component category (#904)
* feat(theme): [ENG-47561] add the docs measure and the prose + mono body registers (#905)
* chore: [ENG-47606] add the documentation component category
* feat(theme): [ENG-47561] add the docs measure and the mono body register
* feat(theme): [ENG-47606] add the prose body register
* fix(theme): [ENG-47606] widen the docs measure to the form column
The documentation column was capped at 752px, narrower than every other measure in the system. Docs pages carry code blocks, card grids and tables, none of which fit a prose measure, so the column now takes the same 1192px the form measure uses.
The token is defined here, so it is set here: the doc component stack carries the same value, and an add/add disagreement between the two made every PR in that stack unmergeable.
* feat(webkit): [ENG-47616] add DocOnThisPage, the documentation TOC rail (#918)
* chore: [ENG-47606] add the documentation component category
* feat(theme): [ENG-47561] add the docs measure and the mono body register
* feat(theme): [ENG-47606] add the prose body register
* feat(webkit): [ENG-47606] add DocProse, the documentation typography contract
* test(storybook): add the linux baselines for the DocProse stories
* fix(theme): [ENG-47606] widen the docs measure to the form column
The documentation column was capped at 752px, narrower than every other measure in the system. Docs pages carry code blocks, card grids and tables, none of which fit a prose measure, so the column now takes the same 1192px the form measure uses.
* fix(webkit): [ENG-47606] pin the prose section step and separate the heading registers
The section rung was a descending pair that read 32 on a phone and on a laptop and only reached 48 at xl, so the same page broke its sections at different gaps by window width. It is now a flat 56 to open and 48 to close, taken from the theme's primitive spacing steps.
Each heading level now names two registers instead of one. On one register per level the base column collapsed: h2 was 16 (the body's own size), h3 and h4 were both 14, and between 640 and 768 the page title was overtaken by its own h2.
Weight and leading are no longer restated here — every element takes its type token verbatim, so the ladder is size and space only.
* feat(webkit): [ENG-47607] add DocCallout, the documentation admonition
* test(storybook): add the linux baselines for the DocProse and DocCallout stories
* fix(webkit): [ENG-47607] step an inline callout link to the only other weight the system defines
font-medium is 500, a weight no text token in the theme uses — the set is light (300) and normal (400). An inline link inside a callout now steps to 400.
* feat(webkit): [ENG-47611] add DocCard, the documentation grid cell
* test(storybook): add the linux baselines for the documentation stories
* feat(webkit): [ENG-47612] add DocCardGroup, the documentation card grid
* test(storybook): add the linux baselines for the doccardgroup stories
* feat(webkit): [ENG-47609] add DocItem, the documentation list row
* test(storybook): add the linux baselines for the docitem stories
* feat(webkit): [ENG-47613] add DocSteps and DocStep, the documentation procedure list
* test(storybook): add the linux baselines for the docsteps stories
* feat(webkit): [ENG-47608] add DocFrame, the documentation figure with a full-screen view
* test(storybook): add the linux baselines for the docframe stories
* test(storybook): stop snapshotting the autoplaying DocFrame clip
* feat(webkit): [ENG-47615] add DocUpdate and the heading-nav composable
* test(storybook): add the linux baselines for the docupdate stories
* feat(webkit): [ENG-47561] add DocPrompt, the copyable agent prompt
* test(storybook): add the linux baselines for the docprompt stories
* feat(webkit): [ENG-47610] add DocTooltip, the inline gloss
* test(storybook): add the linux baselines for the doctooltip stories
* fix(webkit): [ENG-47610] let the tooltip headline take its type token's weight
The headline restated font-medium (500), a weight no text token in the theme defines. It now takes text-body-sm verbatim, and the step to the tip below it is carried by ink colour.
* feat(webkit): [ENG-47618] add DocPagination, the documentation prev/next pair
* test(storybook): add the linux baselines for the docpagination stories
* feat(webkit): [ENG-47617] add DocPageHeader, the documentation masthead
* test(storybook): add the linux baselines for the docpageheader stories
* fix(webkit): [ENG-47617] stop the page title being overtaken by its own h2
text-heading-lg runs 18/18/30, so between 640 and 768 the masthead title rendered smaller than the prose h2 under it (20). The title now takes the same descending pair the prose h1 uses — text-heading-2xl with text-heading-xl from sm — which reads 30/30/36 and stays above every heading below it. Closes the theme gap the spec recorded.
The deck stops restating font-light; text-body-md already resolves to 300.
* feat(webkit): [ENG-47616] add DocOnThisPage, the documentation TOC rail
* test(storybook): add the linux baselines for the doconthispage stories
* fix(webkit): [ENG-47616] mark the rail's opening line so a shell can align to it
A docs shell aligns the outline rail against the page bar beside it, and needed a stable thing to measure. data-toc-title marks the rail's first line so the shell reaches it without depending on this component's internal structure.
* style(webkit): sort the use-heading-nav imports
* test(storybook): regenerate the documentation baselines after the typography fixes
---------
Co-authored-by: Gab <gabriel.mendonca@azion.com>
* fix(webkit): copy-button tooltip, card-box header inset, flow attrs (#936)
* fix(webkit): copy-button tooltip, card-box header inset, flow attrs
Three unrelated small fixes.
COPY-BUTTON COMPOSES TOOLTIP. An icon-only button says nothing to a pointer user
until it is hovered, so the glyph was unlabelled in practice — and the
"Copied" confirmation existed only as an accessible-name swap, announced to a
screen reader and invisible to everyone else. One string now names the control
AND labels its tooltip, so the two can never disagree: `ariaLabel` while idle,
`copiedLabel` for the two seconds after a write. The confirmation is visible
because it rides the same state.
CARD-BOX HEADER INSET `sm` → `md`. The header's horizontal padding now matches
the content's `p-(--spacing-md)` and the footer's `px-(--spacing-md)`, so the
title sits on the same vertical line as the first thing under it — and, in a
flush card, as the rows of whatever fills it (ItemList, Accordion and LogView
are all inset `--spacing-md`). At `--spacing-sm` every card header sat 4px left
of its own body.
FLOW FORWARDS `$attrs`. It declared `inheritAttrs: false` and then never spread
`$attrs` on its root, merging only `attrs.class`. Everything else a consumer
passed — `id`, `aria-*`, `data-*`, listeners — was silently dropped, which
`.claude/rules/root-element.md` exists to prevent. Same shape as its siblings
now: `v-bind="$attrs"` on the real root, with `cn` still merging the class.
* test(storybook): regenerate the card-box baselines for the header inset
* chore(webkit): raise the doc-prompt budget for the copy-button tooltip
DocPrompt composes CopyButton, which now wraps its IconButton in the
Tooltip overlay so the icon-only control is legible on hover. The
overlay machinery raises the measured entry from 6.20 KB to 8.61 KB;
the budget moves to 9 KB to carry that composition with headroom.
---------
Co-authored-by: Isaque Böck <isaque.santos@azion.com>
* fix(webkit): emit navigate when a collapsed breadcrumb crumb is picked
`select` is the Dropdown's event, not the option's: `Dropdown.Option`
forwards its activation to the root through the injected context, and the
root is what emits `select(event, value)`. The listener sat on the option,
where it never fired — so picking a collapsed crumb below `md` did
nothing at all. The menu closed and the reader stayed on the page. That is
the whole trail on a phone, since the collapsed shape is the only one a
narrow bar renders.
The test that covered this was skipped as a harness limitation ("@select
does not fire under test interaction"). It was not a harness limitation —
it was this bug, reproducing exactly as a real browser did. The listener
moved to the root and the test is un-skipped.
---------
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
Co-authored-by: Isaque Böck <isaque.santos@azion.com>
---------
Co-authored-by: Isaque dos Santos <isaque.santos@azion.com>
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@gabriel-lisboa-azion@isaque-bock-azion
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(theme): [ENG-47561] add the docs measure and the prose + mono body registers - #905

Merged
isaque-bock-azion merged 3 commits into
feat/webkit-documentationfrom
feat/ENG-47561-docs-measure-and-mono-body
Aug 24, 2026
Merged

feat(theme): [ENG-47561] add the docs measure and the prose + mono body registers#905
isaque-bock-azion merged 3 commits into
feat/webkit-documentationfrom
feat/ENG-47561-docs-measure-and-mono-body

Conversation

@gabriel-lisboa-azion

@gabriel-lisboa-aziongabriel-lisboa-azion commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • text-body-prose-md — body copy at --leading-relaxed (1.625). The whole text-body-* set is snug (1.375), and the documentation prose ladder was fitted against a 26px line box, so prose needs its own register. Line-height belongs in the token: check-authoring's leading-raw check deliberately rejects a leading-* override even in token form.
  • text-body-code-sm — the mono body register. The label-code-* set is leading-none, built for one-row-per-line code; this one wraps. Required by DocPrompt.
  • layout-measure-docs (--container-2xl, 752px) + layout-column-docs, completing the measure family (column / focused / form / form-create / docs). Docs are the one column capped by typography rather than payload width. The utility is generated by COLUMN_MEASURE + the columnUtility factory, so two entries produce the whole class.

How to test

  1. node packages/theme/src/scripts/build-tokens.mjs then git diff --stat packages/theme/dist — expect no further change; the committed dist is already current.
  2. cd packages/theme && node --test "test/**/*.test.mjs" — 12/12 pass, including the zero-unit assertions over every emitted token.
  3. grep "text-body-prose-md-line-height\|text-body-md-line-height" packages/theme/dist/v4/globals.css — expect 1.625 and 1.375 respectively; that difference is the whole point of the new token.
  4. grep -A5 '@utility text-body-code-sm' packages/theme/dist/v4/globals.css and the same for layout-column-docs — each must emit a real rule body. A token that compiles to nothing is the silent failure mode here.
  5. grep "layout-measure':" packages/theme/src/tokens/semantic/layouts.data.js — must still read var(--container-7xl) / 1620px (see Notes).

Notes

  • Deliberately excluded: the working branch this was carved from also retunes layout-measure from --container-7xl (1620px) to --container-6xl (1388px), moving home from the focused measure to the data measure. That is a separate product decision that would have silently changed the width of every data page, so it is not here — layout-measure and layout-measure-focused keep main's values and main's prose.
  • Stacked on chore: [ENG-47606] add the documentation component category #904 (the documentation category), which DocProse also needs. Merge chore: [ENG-47606] add the documentation component category #904 first; this retargets automatically.
  • No component consumes layout-column-docs yet: DocProse deliberately has no measure of its own, so the consumer caps the column.
  • Related: ENG-47561, ENG-47606, and the sibling component issues.

Delivery

The Documentation family is delivered on the feature branch feat/docs-components-bundle, not piecemeal to main — so main takes the whole family as one merge, while review stays per-component.

Merge order, bottom-up — each PR's base is the one before it, and each retargets automatically as its parent lands:

#904#905#906#907#908#909#910#911#912#913#914#915#916#917#918feat/docs-components-bundlemain

#904the documentation category (standards only)
#905theme: the docs measure + the prose and mono body registers
#906#918DocProse · DocCallout · DocCard · DocCardGroup · DocItem · DocSteps + DocStep · DocFrame · DocUpdate · DocPrompt · DocTooltip · DocPagination · DocPageHeader · DocOnThisPage

@gabriel-lisboa-aziongabriel-lisboa-azion changed the title feat(theme): [ENG-47561] add the docs measure and the mono body registerfeat(theme): [ENG-47561] add the docs measure and the prose + mono body registersAug 22, 2026
@gabriel-lisboa-azion
gabriel-lisboa-azionforce-pushed the feat/ENG-47561-docs-measure-and-mono-body branch from d3d46c0 to bc6575cCompareAugust 22, 2026 14:11
@gabriel-lisboa-azion
gabriel-lisboa-azion changed the base branch from main to chore/ENG-47606-documentation-categoryAugust 22, 2026 14:11
@isaque-bock-azion
isaque-bock-azion changed the base branch from chore/ENG-47606-documentation-category to feat/webkit-documentationAugust 24, 2026 13:04
@isaque-bock-azion
isaque-bock-azion merged commit e864f62 into feat/webkit-documentationAug 24, 2026
47 of 51 checks passed
@isaque-bock-azion
isaque-bock-azion deleted the feat/ENG-47561-docs-measure-and-mono-body branch August 24, 2026 13:14
isaque-bock-azion added a commit that referenced this pull request Aug 26, 2026
* chore: [ENG-47606] add the documentation component category (#904)
* feat(theme): [ENG-47561] add the docs measure and the prose + mono body registers (#905)
* chore: [ENG-47606] add the documentation component category
* feat(theme): [ENG-47561] add the docs measure and the mono body register
* feat(theme): [ENG-47606] add the prose body register
* fix(theme): [ENG-47606] widen the docs measure to the form column
The documentation column was capped at 752px, narrower than every other measure in the system. Docs pages carry code blocks, card grids and tables, none of which fit a prose measure, so the column now takes the same 1192px the form measure uses.
The token is defined here, so it is set here: the doc component stack carries the same value, and an add/add disagreement between the two made every PR in that stack unmergeable.
* feat(webkit): [ENG-47616] add DocOnThisPage, the documentation TOC rail (#918)
* chore: [ENG-47606] add the documentation component category
* feat(theme): [ENG-47561] add the docs measure and the mono body register
* feat(theme): [ENG-47606] add the prose body register
* feat(webkit): [ENG-47606] add DocProse, the documentation typography contract
* test(storybook): add the linux baselines for the DocProse stories
* fix(theme): [ENG-47606] widen the docs measure to the form column
The documentation column was capped at 752px, narrower than every other measure in the system. Docs pages carry code blocks, card grids and tables, none of which fit a prose measure, so the column now takes the same 1192px the form measure uses.
* fix(webkit): [ENG-47606] pin the prose section step and separate the heading registers
The section rung was a descending pair that read 32 on a phone and on a laptop and only reached 48 at xl, so the same page broke its sections at different gaps by window width. It is now a flat 56 to open and 48 to close, taken from the theme's primitive spacing steps.
Each heading level now names two registers instead of one. On one register per level the base column collapsed: h2 was 16 (the body's own size), h3 and h4 were both 14, and between 640 and 768 the page title was overtaken by its own h2.
Weight and leading are no longer restated here — every element takes its type token verbatim, so the ladder is size and space only.
* feat(webkit): [ENG-47607] add DocCallout, the documentation admonition
* test(storybook): add the linux baselines for the DocProse and DocCallout stories
* fix(webkit): [ENG-47607] step an inline callout link to the only other weight the system defines
font-medium is 500, a weight no text token in the theme uses — the set is light (300) and normal (400). An inline link inside a callout now steps to 400.
* feat(webkit): [ENG-47611] add DocCard, the documentation grid cell
* test(storybook): add the linux baselines for the documentation stories
* feat(webkit): [ENG-47612] add DocCardGroup, the documentation card grid
* test(storybook): add the linux baselines for the doccardgroup stories
* feat(webkit): [ENG-47609] add DocItem, the documentation list row
* test(storybook): add the linux baselines for the docitem stories
* feat(webkit): [ENG-47613] add DocSteps and DocStep, the documentation procedure list
* test(storybook): add the linux baselines for the docsteps stories
* feat(webkit): [ENG-47608] add DocFrame, the documentation figure with a full-screen view
* test(storybook): add the linux baselines for the docframe stories
* test(storybook): stop snapshotting the autoplaying DocFrame clip
* feat(webkit): [ENG-47615] add DocUpdate and the heading-nav composable
* test(storybook): add the linux baselines for the docupdate stories
* feat(webkit): [ENG-47561] add DocPrompt, the copyable agent prompt
* test(storybook): add the linux baselines for the docprompt stories
* feat(webkit): [ENG-47610] add DocTooltip, the inline gloss
* test(storybook): add the linux baselines for the doctooltip stories
* fix(webkit): [ENG-47610] let the tooltip headline take its type token's weight
The headline restated font-medium (500), a weight no text token in the theme defines. It now takes text-body-sm verbatim, and the step to the tip below it is carried by ink colour.
* feat(webkit): [ENG-47618] add DocPagination, the documentation prev/next pair
* test(storybook): add the linux baselines for the docpagination stories
* feat(webkit): [ENG-47617] add DocPageHeader, the documentation masthead
* test(storybook): add the linux baselines for the docpageheader stories
* fix(webkit): [ENG-47617] stop the page title being overtaken by its own h2
text-heading-lg runs 18/18/30, so between 640 and 768 the masthead title rendered smaller than the prose h2 under it (20). The title now takes the same descending pair the prose h1 uses — text-heading-2xl with text-heading-xl from sm — which reads 30/30/36 and stays above every heading below it. Closes the theme gap the spec recorded.
The deck stops restating font-light; text-body-md already resolves to 300.
* feat(webkit): [ENG-47616] add DocOnThisPage, the documentation TOC rail
* test(storybook): add the linux baselines for the doconthispage stories
* fix(webkit): [ENG-47616] mark the rail's opening line so a shell can align to it
A docs shell aligns the outline rail against the page bar beside it, and needed a stable thing to measure. data-toc-title marks the rail's first line so the shell reaches it without depending on this component's internal structure.
* style(webkit): sort the use-heading-nav imports
* test(storybook): regenerate the documentation baselines after the typography fixes
---------
Co-authored-by: Gab <gabriel.mendonca@azion.com>
isaque-bock-azion added a commit that referenced this pull request Aug 28, 2026
#939)
* feat(theme): add --container-site, the marketing site's shared measure (#923)
* feat(theme): add --container-site, the marketing site's shared measure
The azion.com pages are a single vertical frame: the nav bar, the hero's inner
column, every section below it and the footer all sit on the same centred
column, and the border-x that runs down the page is only continuous because
those four agree to the pixel. A width re-typed per band cannot hold that — the
frame is one decision, so it gets one place to live.
`5xl` (1192px) is a rung of the shared container ladder, not a number chosen for
this page. It is its own token rather than a reference to one of the console
measures because the two answer different questions: a console measure caps a
payload (a table, a form), while this one is the width of a frame whose four
bands have to line up, so retuning either must not move the other.
* refactor(webkit): point the footer column at --container-site (#928)
The footer's column named `--container-5xl` directly. On the marketing site the
nav bar, the hero band, the framed sections and this footer are ONE vertical
frame, and its side rules only meet because all four resolve to the same width —
so the measure has to be a token they share, not a rung each of them names.
Retuning it now moves them together, which a hardcoded rung here could not do.
No visual change: `--container-site` resolves to `--container-5xl`, which is
what the class already said.
* feat(webkit): add the resizable-panel layout primitive (#902)
* feat(webkit): add the resizable-panel layout primitive
A group of adjacent regions whose shared edges the reader can drag — the layout
behind a workspace, where an editor, a terminal under it and a preview beside it
are all content and the split between them is the reader's to set.
It exists because `Sidebar` was being used for both jobs. `Sidebar` is the app's
NAVIGATION rail: it collapses to icons, it hides, it is the thing you leave to go
somewhere. A terminal, a preview and a code editor are not places you navigate to
— they are the work — and dressing two of them as navigation rails produced a
screen with three "sidebars" and one document, on an axis `Sidebar` does not even
have: it has no horizontal edge, so a terminal under an editor had to hand-roll
its own splitter. This component owns the EDGE; `Sidebar` goes back to owning
navigation.
Composition: `ResizablePanel` with `Pane` and `Handle`, attached for dot-notation
and exported standalone. The compound is built by naming the static surface and
casting once rather than with `Object.assign`, whose inferred type walks each
part's local `interface Props` into the declaration and trips TS4082.
Ships the spec, a browser-mode test suite, the story, the `-root` and
per-sub-component exports, and a size budget.
* test(storybook): regenerate linux baselines for resizable-panel
Adds the 24 snapshots for the four new ResizablePanel stories across the 6-mode
matrix. Generated by the Storybook Regenerate Baseline workflow on this branch's
HEAD — baselines are ubuntu-only, so a local run would not match the folder the
visual gate compares against.
No existing snapshot is modified or deleted; the component is additive.
* test(webkit): compose resizable-panel through its named sub-component exports
Every other composition suite in the package renders through the named exports
(`AccordionItem`, `DropdownTrigger`, …) and checks dot-notation once, as an
identity assertion against those same exports. This file was the only one that
rendered through the root's statics — `h(ResizablePanel.Pane, …)` — which made
static analysis flag eight unguarded member accesses: a `.vue` default export is
generated by the SFC compiler, so the analyzer sees no `export default`, reads
the imported binding as undefined, and reports every `.Pane` / `.Handle` read on
it.
Compose through `ResizablePanelPane` / `ResizablePanelHandle` instead, and keep
the compound contract in the one place it is the subject rather than the vehicle,
asserting identity against those exports through a guarded read.
Same nine tests, same behaviour covered — the dot-notation contract is now
verified more tightly than `toBeTruthy` did.
---------
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* feat(webkit): give Select, MultiSelect and Dropdown one shared leading column (#935)
A list that mixes iconed and icon-less options used to run two label edges: an
option with a glyph indented its text, one without it did not, so the eye had no
column to read down. Fixing it per-option is not possible — no option knows
whether any sibling has an icon.
So the reservation is made by the LIST, with a self-triggering `:has()` rule.
Every option renders a fixed leading box and marks itself `data-leading` only
when it actually has a glyph; the box is `hidden` and turns back on under
`group-has-[[data-leading]]/options:flex`. `:has()` matches the marker even
while the box is hidden, so one iconed option is enough to open the column for
the whole list — and a list with no icons at all reserves nothing and pays no
indent. The same `group/options` marker is on all three panels, so Select,
MultiSelect and Dropdown behave identically.
The group label is now OPTIONAL, and sentence-case `text-label-sm` rather than
an uppercase overline: an unlabelled group is a grouping the reader does not
need named, and an overline read as a second heading level inside a panel that
has none.
Group rhythm is `--spacing-sm` above every group but the first — the same step
CommandMenu already puts between two bare groups. Dropdown keeps its hairline
and Select does not: the Figma frame for the Select panel separates groups by
space alone. Dropdown's divider moves to the group root, which spans the panel's
full inner width, so it reads edge-to-edge like the panel's own border; the
horizontal inset the rows need moves onto the content wrapper.
Panels also gain top/bottom slots for content that is not an option (a search
row, a footer action), and the panel's vertical padding steps up one so the
first and last row do not crowd its border.
Includes the 10 dropdown visual baselines this moves.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* fix(icons): resolve currentColor per theme in the colored-icon CSS (#930)
Astro's "A" shipped invisible on every light-mode surface.
Some brand logos are only partly colored: Astro is a fixed red-to-magenta
gradient swoosh under an "A" that is the brand's INK — black on a light
background, white on a dark one. That part is authored as `fill="currentColor"`,
which is what `validate-svg.mjs` asks for, and it resolves correctly wherever
the SVG is INLINED (dist/color-catalog.json → the gallery, Storybook).
A `background-image` is a separate document, so it inherits nothing from the
element painting it. `currentColor` there resolves against the SVG's own root,
not the consumer's text color, and no amount of `color` on the `<i>` reaches it.
Left alone it lands on the UA default and the icon disappears in one theme —
which is what happened: `ai-astro.svg` had the "A" hardcoded `#FAFAFA`, so it
was white-on-white everywhere in light mode.
So the generator resolves it. An icon containing `currentColor` is emitted
twice: the base rule with the light ink, and an override with the dark ink under
the same selectors @aziontech/theme uses for its dark tokens
(`[data-theme=dark]`, `.dark`, `.azion.azion-dark`). The override's extra
selector out-specifies the base, so source order does not decide the winner.
The inks are literals (`#141414` / `#FAFAFA`) rather than `var(--text-default)`
by necessity — same document isolation — and are the theme's own values for that
token in each mode. A fully-colored logo with no ink parts still emits one rule,
exactly as before: 17 colored icons, 1 theme-aware.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* docs(webkit): ship the webkit-create-surface and webkit-errors skills (#895)
* docs(webkit): ship the webkit-create-surface and webkit-errors skills
webkit-create-surface fixes where a create or settings form lives — a first-level
resource creates on a page, anything created inside a resource opens a drawer,
nothing creates in a dialog — plus the anatomy all of them share, what belongs
behind the collapsed Advanced band, and the commit model each surface uses.
webkit-errors is the one decision table for where a failure goes: client-side
validation on the field, a server rejection scoped to one field as a Message in
that section, an unscoped request failure as a toast with Retry, the signed-out
screens where auth never toasts, and the async failure that outlives its screen.
Both join the CLI bundle and the ui-craft routing table; webkit-form and
webkit-ui-states now hand their error rows off to webkit-errors.
* docs(webkit): ship the webkit-microcopy consumer skill (#931)
The writing rules for Azion product interfaces, as a skill the adoption toolkit
ships — so a consuming project's agent writes the same strings the console does
instead of inventing a house style per screen.
It fixes the things that were being decided per-PR: punctuation and
capitalization (no em dash, no ampersand, no parentheses in labels, no bold in
running text, sentence case with Azion product names capitalized), which surface
each kind of copy belongs on (a label names, a description constrains, a Message
explains impact, a Popover defines, a Tag states), one word per concept (Select
not Pick, Application not Edge Application, and no `edge` as a prefix or a
location), navigation and IA naming, and copy that survives every breakpoint.
Registered in the three places a consumer skill has to be registered or it never
reaches a project: the CLI's file list (`plan.js`), the UI-craft pack in
`plan.test.mjs` (now 18 skills), and the routing index in `CLAUDE.fragment.md`,
where it gets its own **Words** line — it is the only skill about strings rather
than structure, and it applies during every other one.
Distinct from the generic `content-microcopy` the plan drops: that one is
house-agnostic, this one carries Azion's own vocabulary.
---------
Co-authored-by: Isaque Böck <isaque.santos@azion.com>
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* feat(theme): add the page-enter and content-enter arrival animations (#896)
* feat(theme): add the page-enter and content-enter arrival animations
The two entrances an app shell needs, as a pair. page-enter is the route
transition: it starts one --layout-boundary-inline to the LEFT of where it lands
and travels right while fading up, so a page reads as arriving FROM the nav row
that was clicked — a plain fade has no origin and a right-side arrival reads as
going back. content-enter is content settling inside a page already on screen, so
it rises a hair instead of travelling, and takes a --content-enter-delay stagger
(with backwards fill, or a delayed follower would paint its landed state first).
Neither fills forwards: page-enter's box is usually the scroll container, and a
lingering translate would make it a containing block for any fixed descendant.
* docs(webkit): teach the two arrival animations in webkit-motion-polish
Documents both utilities and their knobs, and the failure they exist to prevent:
a page arrives once, so a mount-time entrance inside a page whose route
transition is still running lockesteps with it into one diagonal move (identical
opacities frame for frame is the tell). Adds that shape and the two-shells-per-
screen variant to the silent-failure table. catalog.json rebuilt so the two
animations reach the MCP and validate-spec-compliance.
---------
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* docs: rewrite add-animation for the CSS-first theme (#897)
The steps still described the deleted v3 plugin (semantic/animations.js,
addUtilities/addComponents, the camelCase keyframe object form). An animation is
now two edits under primitives/animations/ — the shorthand in animate.js, which
is what makes animate-<name> a utility at all, and the CSS-string keyframes in
keyframes.js, which Tailwind v4 does NOT generate — compiled by build:tokens,
which must run before catalog:build or nothing renders.
Also records the three things the two new arrivals taught: a per-instance value
rides a var() with a token default inside the keyframe, a stagger rides a var()
delay with backwards fill, and fill is a decision — forwards leaves a translate
that makes the element a containing block for any fixed descendant.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* fix(webkit): give table cells block padding so tall content can breathe (#899)
A cell was `min-h-12` with horizontal padding only. For a single-line cell that
is invisible — the 48px minimum decides the height on its own against ~20px of
text — so every existing row renders exactly as before.
It only shows on a cell whose content is TALLER than the minimum: a wrapped list
of tags, a two-line summary. There the height came from the content alone, which
put the first and last lines flush against the row's borders with no breathing
room at all.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* docs(webkit): bring webkit-lists onto the current list-page shape (#929)
The shipped consumer skill still described the list page as it was before three
decisions landed, so a consumer following it built a page the product no longer
looks like:
- A first-level module index opens with a `PageHeading` again, and the module's
create action lives in it. The shell draws the breadcrumb only from the second
crumb up, so at first level the heading is the ONLY place the module's name
appears in the content.
- The size ladder is the hierarchy: the heading's action is the page's one
`large` (40px) control and the whole controls row is `medium` (32px). At 40px
both rows competed and the create button stopped being the page's answer.
- The controls row is narrowing only — `#actions` there is for an action on the
LISTING, not the page's own.
`last_updated` moves to 2026-08-24.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* feat: webkit documentation (#919)
* chore: [ENG-47606] add the documentation component category (#904)
* feat(theme): [ENG-47561] add the docs measure and the prose + mono body registers (#905)
* chore: [ENG-47606] add the documentation component category
* feat(theme): [ENG-47561] add the docs measure and the mono body register
* feat(theme): [ENG-47606] add the prose body register
* fix(theme): [ENG-47606] widen the docs measure to the form column
The documentation column was capped at 752px, narrower than every other measure in the system. Docs pages carry code blocks, card grids and tables, none of which fit a prose measure, so the column now takes the same 1192px the form measure uses.
The token is defined here, so it is set here: the doc component stack carries the same value, and an add/add disagreement between the two made every PR in that stack unmergeable.
* feat(webkit): [ENG-47616] add DocOnThisPage, the documentation TOC rail (#918)
* chore: [ENG-47606] add the documentation component category
* feat(theme): [ENG-47561] add the docs measure and the mono body register
* feat(theme): [ENG-47606] add the prose body register
* feat(webkit): [ENG-47606] add DocProse, the documentation typography contract
* test(storybook): add the linux baselines for the DocProse stories
* fix(theme): [ENG-47606] widen the docs measure to the form column
The documentation column was capped at 752px, narrower than every other measure in the system. Docs pages carry code blocks, card grids and tables, none of which fit a prose measure, so the column now takes the same 1192px the form measure uses.
* fix(webkit): [ENG-47606] pin the prose section step and separate the heading registers
The section rung was a descending pair that read 32 on a phone and on a laptop and only reached 48 at xl, so the same page broke its sections at different gaps by window width. It is now a flat 56 to open and 48 to close, taken from the theme's primitive spacing steps.
Each heading level now names two registers instead of one. On one register per level the base column collapsed: h2 was 16 (the body's own size), h3 and h4 were both 14, and between 640 and 768 the page title was overtaken by its own h2.
Weight and leading are no longer restated here — every element takes its type token verbatim, so the ladder is size and space only.
* feat(webkit): [ENG-47607] add DocCallout, the documentation admonition
* test(storybook): add the linux baselines for the DocProse and DocCallout stories
* fix(webkit): [ENG-47607] step an inline callout link to the only other weight the system defines
font-medium is 500, a weight no text token in the theme uses — the set is light (300) and normal (400). An inline link inside a callout now steps to 400.
* feat(webkit): [ENG-47611] add DocCard, the documentation grid cell
* test(storybook): add the linux baselines for the documentation stories
* feat(webkit): [ENG-47612] add DocCardGroup, the documentation card grid
* test(storybook): add the linux baselines for the doccardgroup stories
* feat(webkit): [ENG-47609] add DocItem, the documentation list row
* test(storybook): add the linux baselines for the docitem stories
* feat(webkit): [ENG-47613] add DocSteps and DocStep, the documentation procedure list
* test(storybook): add the linux baselines for the docsteps stories
* feat(webkit): [ENG-47608] add DocFrame, the documentation figure with a full-screen view
* test(storybook): add the linux baselines for the docframe stories
* test(storybook): stop snapshotting the autoplaying DocFrame clip
* feat(webkit): [ENG-47615] add DocUpdate and the heading-nav composable
* test(storybook): add the linux baselines for the docupdate stories
* feat(webkit): [ENG-47561] add DocPrompt, the copyable agent prompt
* test(storybook): add the linux baselines for the docprompt stories
* feat(webkit): [ENG-47610] add DocTooltip, the inline gloss
* test(storybook): add the linux baselines for the doctooltip stories
* fix(webkit): [ENG-47610] let the tooltip headline take its type token's weight
The headline restated font-medium (500), a weight no text token in the theme defines. It now takes text-body-sm verbatim, and the step to the tip below it is carried by ink colour.
* feat(webkit): [ENG-47618] add DocPagination, the documentation prev/next pair
* test(storybook): add the linux baselines for the docpagination stories
* feat(webkit): [ENG-47617] add DocPageHeader, the documentation masthead
* test(storybook): add the linux baselines for the docpageheader stories
* fix(webkit): [ENG-47617] stop the page title being overtaken by its own h2
text-heading-lg runs 18/18/30, so between 640 and 768 the masthead title rendered smaller than the prose h2 under it (20). The title now takes the same descending pair the prose h1 uses — text-heading-2xl with text-heading-xl from sm — which reads 30/30/36 and stays above every heading below it. Closes the theme gap the spec recorded.
The deck stops restating font-light; text-body-md already resolves to 300.
* feat(webkit): [ENG-47616] add DocOnThisPage, the documentation TOC rail
* test(storybook): add the linux baselines for the doconthispage stories
* fix(webkit): [ENG-47616] mark the rail's opening line so a shell can align to it
A docs shell aligns the outline rail against the page bar beside it, and needed a stable thing to measure. data-toc-title marks the rail's first line so the shell reaches it without depending on this component's internal structure.
* style(webkit): sort the use-heading-nav imports
* test(storybook): regenerate the documentation baselines after the typography fixes
---------
Co-authored-by: Gab <gabriel.mendonca@azion.com>
* fix(webkit): copy-button tooltip, card-box header inset, flow attrs (#936)
* fix(webkit): copy-button tooltip, card-box header inset, flow attrs
Three unrelated small fixes.
COPY-BUTTON COMPOSES TOOLTIP. An icon-only button says nothing to a pointer user
until it is hovered, so the glyph was unlabelled in practice — and the
"Copied" confirmation existed only as an accessible-name swap, announced to a
screen reader and invisible to everyone else. One string now names the control
AND labels its tooltip, so the two can never disagree: `ariaLabel` while idle,
`copiedLabel` for the two seconds after a write. The confirmation is visible
because it rides the same state.
CARD-BOX HEADER INSET `sm` → `md`. The header's horizontal padding now matches
the content's `p-(--spacing-md)` and the footer's `px-(--spacing-md)`, so the
title sits on the same vertical line as the first thing under it — and, in a
flush card, as the rows of whatever fills it (ItemList, Accordion and LogView
are all inset `--spacing-md`). At `--spacing-sm` every card header sat 4px left
of its own body.
FLOW FORWARDS `$attrs`. It declared `inheritAttrs: false` and then never spread
`$attrs` on its root, merging only `attrs.class`. Everything else a consumer
passed — `id`, `aria-*`, `data-*`, listeners — was silently dropped, which
`.claude/rules/root-element.md` exists to prevent. Same shape as its siblings
now: `v-bind="$attrs"` on the real root, with `cn` still merging the class.
* test(storybook): regenerate the card-box baselines for the header inset
* chore(webkit): raise the doc-prompt budget for the copy-button tooltip
DocPrompt composes CopyButton, which now wraps its IconButton in the
Tooltip overlay so the icon-only control is legible on hover. The
overlay machinery raises the measured entry from 6.20 KB to 8.61 KB;
the budget moves to 9 KB to carry that composition with headroom.
---------
Co-authored-by: Isaque Böck <isaque.santos@azion.com>
* fix(webkit): emit navigate when a collapsed breadcrumb crumb is picked
`select` is the Dropdown's event, not the option's: `Dropdown.Option`
forwards its activation to the root through the injected context, and the
root is what emits `select(event, value)`. The listener sat on the option,
where it never fired — so picking a collapsed crumb below `md` did
nothing at all. The menu closed and the reader stayed on the page. That is
the whole trail on a phone, since the collapsed shape is the only one a
narrow bar renders.
The test that covered this was skipped as a harness limitation ("@select
does not fire under test interaction"). It was not a harness limitation —
it was this bug, reproducing exactly as a real browser did. The listener
moved to the root and the test is un-skipped.
---------
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
Co-authored-by: Isaque Böck <isaque.santos@azion.com>
isaque-bock-azion added a commit that referenced this pull request Aug 28, 2026
* feat(webkit): let a table column declare a minWidth floor
A bounded column — a chip list, a date, a count, an id — could only be sized by
a fixed `width`, and a fixed width is a bet in both directions: guess high and
the column reserves space the rest of the row needs, guess low and the content
inside it truncates (a chip list ends up as `SQL In…`). `minWidth` only has to
be right about the minimum: the column resolves to the widest of that floor and
what it actually holds — its header, with the sort affordance, and every
rendered body cell. `width` still wins when both are set, so an explicit width
remains the consumer overriding the measurement outright.
ONE width is resolved per column and applied to the header and to every body
cell, which keeps the invariant fixed widths exist for: the column resolves
identically in every row. A per-cell `min-width` would let each row size to its
own content and drift the header away from the body.
It has to be measured, because a flex row cannot express "as wide as the widest
cell in this column" — every row is its own flex container and knows nothing
about its siblings. The measurement is the same clone-based one the resize floor
already uses, so a column's auto width and its drag floor can never disagree.
Three things the measurement has to respect:
- Debounced (32ms). The rendered content changes in bursts — a sort, a page
turn, a column toggled back on — and measuring reads layout, so an
un-coalesced version would reflow per keystroke of a filter.
- Skipped while `loading`. Skeleton rows are not the content; measuring against
them would size every column to a placeholder and never grow it back.
- Re-run on `document.fonts.ready`. Text width is font-dependent and the first
paint can happen on the fallback face.
`autoWidths` joins the row `v-memo` so a resolved width actually reaches the
body, and feeds `frozenWidthOf` so a frozen `minWidth` column contributes its
real width to a neighbour's sticky offset.
* test(storybook): regenerate the table baselines for the column min-width
* fix(webkit): emit navigate when a collapsed breadcrumb crumb is picked (#939)
* feat(theme): add --container-site, the marketing site's shared measure (#923)
* feat(theme): add --container-site, the marketing site's shared measure
The azion.com pages are a single vertical frame: the nav bar, the hero's inner
column, every section below it and the footer all sit on the same centred
column, and the border-x that runs down the page is only continuous because
those four agree to the pixel. A width re-typed per band cannot hold that — the
frame is one decision, so it gets one place to live.
`5xl` (1192px) is a rung of the shared container ladder, not a number chosen for
this page. It is its own token rather than a reference to one of the console
measures because the two answer different questions: a console measure caps a
payload (a table, a form), while this one is the width of a frame whose four
bands have to line up, so retuning either must not move the other.
* refactor(webkit): point the footer column at --container-site (#928)
The footer's column named `--container-5xl` directly. On the marketing site the
nav bar, the hero band, the framed sections and this footer are ONE vertical
frame, and its side rules only meet because all four resolve to the same width —
so the measure has to be a token they share, not a rung each of them names.
Retuning it now moves them together, which a hardcoded rung here could not do.
No visual change: `--container-site` resolves to `--container-5xl`, which is
what the class already said.
* feat(webkit): add the resizable-panel layout primitive (#902)
* feat(webkit): add the resizable-panel layout primitive
A group of adjacent regions whose shared edges the reader can drag — the layout
behind a workspace, where an editor, a terminal under it and a preview beside it
are all content and the split between them is the reader's to set.
It exists because `Sidebar` was being used for both jobs. `Sidebar` is the app's
NAVIGATION rail: it collapses to icons, it hides, it is the thing you leave to go
somewhere. A terminal, a preview and a code editor are not places you navigate to
— they are the work — and dressing two of them as navigation rails produced a
screen with three "sidebars" and one document, on an axis `Sidebar` does not even
have: it has no horizontal edge, so a terminal under an editor had to hand-roll
its own splitter. This component owns the EDGE; `Sidebar` goes back to owning
navigation.
Composition: `ResizablePanel` with `Pane` and `Handle`, attached for dot-notation
and exported standalone. The compound is built by naming the static surface and
casting once rather than with `Object.assign`, whose inferred type walks each
part's local `interface Props` into the declaration and trips TS4082.
Ships the spec, a browser-mode test suite, the story, the `-root` and
per-sub-component exports, and a size budget.
* test(storybook): regenerate linux baselines for resizable-panel
Adds the 24 snapshots for the four new ResizablePanel stories across the 6-mode
matrix. Generated by the Storybook Regenerate Baseline workflow on this branch's
HEAD — baselines are ubuntu-only, so a local run would not match the folder the
visual gate compares against.
No existing snapshot is modified or deleted; the component is additive.
* test(webkit): compose resizable-panel through its named sub-component exports
Every other composition suite in the package renders through the named exports
(`AccordionItem`, `DropdownTrigger`, …) and checks dot-notation once, as an
identity assertion against those same exports. This file was the only one that
rendered through the root's statics — `h(ResizablePanel.Pane, …)` — which made
static analysis flag eight unguarded member accesses: a `.vue` default export is
generated by the SFC compiler, so the analyzer sees no `export default`, reads
the imported binding as undefined, and reports every `.Pane` / `.Handle` read on
it.
Compose through `ResizablePanelPane` / `ResizablePanelHandle` instead, and keep
the compound contract in the one place it is the subject rather than the vehicle,
asserting identity against those exports through a guarded read.
Same nine tests, same behaviour covered — the dot-notation contract is now
verified more tightly than `toBeTruthy` did.
---------
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* feat(webkit): give Select, MultiSelect and Dropdown one shared leading column (#935)
A list that mixes iconed and icon-less options used to run two label edges: an
option with a glyph indented its text, one without it did not, so the eye had no
column to read down. Fixing it per-option is not possible — no option knows
whether any sibling has an icon.
So the reservation is made by the LIST, with a self-triggering `:has()` rule.
Every option renders a fixed leading box and marks itself `data-leading` only
when it actually has a glyph; the box is `hidden` and turns back on under
`group-has-[[data-leading]]/options:flex`. `:has()` matches the marker even
while the box is hidden, so one iconed option is enough to open the column for
the whole list — and a list with no icons at all reserves nothing and pays no
indent. The same `group/options` marker is on all three panels, so Select,
MultiSelect and Dropdown behave identically.
The group label is now OPTIONAL, and sentence-case `text-label-sm` rather than
an uppercase overline: an unlabelled group is a grouping the reader does not
need named, and an overline read as a second heading level inside a panel that
has none.
Group rhythm is `--spacing-sm` above every group but the first — the same step
CommandMenu already puts between two bare groups. Dropdown keeps its hairline
and Select does not: the Figma frame for the Select panel separates groups by
space alone. Dropdown's divider moves to the group root, which spans the panel's
full inner width, so it reads edge-to-edge like the panel's own border; the
horizontal inset the rows need moves onto the content wrapper.
Panels also gain top/bottom slots for content that is not an option (a search
row, a footer action), and the panel's vertical padding steps up one so the
first and last row do not crowd its border.
Includes the 10 dropdown visual baselines this moves.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* fix(icons): resolve currentColor per theme in the colored-icon CSS (#930)
Astro's "A" shipped invisible on every light-mode surface.
Some brand logos are only partly colored: Astro is a fixed red-to-magenta
gradient swoosh under an "A" that is the brand's INK — black on a light
background, white on a dark one. That part is authored as `fill="currentColor"`,
which is what `validate-svg.mjs` asks for, and it resolves correctly wherever
the SVG is INLINED (dist/color-catalog.json → the gallery, Storybook).
A `background-image` is a separate document, so it inherits nothing from the
element painting it. `currentColor` there resolves against the SVG's own root,
not the consumer's text color, and no amount of `color` on the `<i>` reaches it.
Left alone it lands on the UA default and the icon disappears in one theme —
which is what happened: `ai-astro.svg` had the "A" hardcoded `#FAFAFA`, so it
was white-on-white everywhere in light mode.
So the generator resolves it. An icon containing `currentColor` is emitted
twice: the base rule with the light ink, and an override with the dark ink under
the same selectors @aziontech/theme uses for its dark tokens
(`[data-theme=dark]`, `.dark`, `.azion.azion-dark`). The override's extra
selector out-specifies the base, so source order does not decide the winner.
The inks are literals (`#141414` / `#FAFAFA`) rather than `var(--text-default)`
by necessity — same document isolation — and are the theme's own values for that
token in each mode. A fully-colored logo with no ink parts still emits one rule,
exactly as before: 17 colored icons, 1 theme-aware.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* docs(webkit): ship the webkit-create-surface and webkit-errors skills (#895)
* docs(webkit): ship the webkit-create-surface and webkit-errors skills
webkit-create-surface fixes where a create or settings form lives — a first-level
resource creates on a page, anything created inside a resource opens a drawer,
nothing creates in a dialog — plus the anatomy all of them share, what belongs
behind the collapsed Advanced band, and the commit model each surface uses.
webkit-errors is the one decision table for where a failure goes: client-side
validation on the field, a server rejection scoped to one field as a Message in
that section, an unscoped request failure as a toast with Retry, the signed-out
screens where auth never toasts, and the async failure that outlives its screen.
Both join the CLI bundle and the ui-craft routing table; webkit-form and
webkit-ui-states now hand their error rows off to webkit-errors.
* docs(webkit): ship the webkit-microcopy consumer skill (#931)
The writing rules for Azion product interfaces, as a skill the adoption toolkit
ships — so a consuming project's agent writes the same strings the console does
instead of inventing a house style per screen.
It fixes the things that were being decided per-PR: punctuation and
capitalization (no em dash, no ampersand, no parentheses in labels, no bold in
running text, sentence case with Azion product names capitalized), which surface
each kind of copy belongs on (a label names, a description constrains, a Message
explains impact, a Popover defines, a Tag states), one word per concept (Select
not Pick, Application not Edge Application, and no `edge` as a prefix or a
location), navigation and IA naming, and copy that survives every breakpoint.
Registered in the three places a consumer skill has to be registered or it never
reaches a project: the CLI's file list (`plan.js`), the UI-craft pack in
`plan.test.mjs` (now 18 skills), and the routing index in `CLAUDE.fragment.md`,
where it gets its own **Words** line — it is the only skill about strings rather
than structure, and it applies during every other one.
Distinct from the generic `content-microcopy` the plan drops: that one is
house-agnostic, this one carries Azion's own vocabulary.
---------
Co-authored-by: Isaque Böck <isaque.santos@azion.com>
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* feat(theme): add the page-enter and content-enter arrival animations (#896)
* feat(theme): add the page-enter and content-enter arrival animations
The two entrances an app shell needs, as a pair. page-enter is the route
transition: it starts one --layout-boundary-inline to the LEFT of where it lands
and travels right while fading up, so a page reads as arriving FROM the nav row
that was clicked — a plain fade has no origin and a right-side arrival reads as
going back. content-enter is content settling inside a page already on screen, so
it rises a hair instead of travelling, and takes a --content-enter-delay stagger
(with backwards fill, or a delayed follower would paint its landed state first).
Neither fills forwards: page-enter's box is usually the scroll container, and a
lingering translate would make it a containing block for any fixed descendant.
* docs(webkit): teach the two arrival animations in webkit-motion-polish
Documents both utilities and their knobs, and the failure they exist to prevent:
a page arrives once, so a mount-time entrance inside a page whose route
transition is still running lockesteps with it into one diagonal move (identical
opacities frame for frame is the tell). Adds that shape and the two-shells-per-
screen variant to the silent-failure table. catalog.json rebuilt so the two
animations reach the MCP and validate-spec-compliance.
---------
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* docs: rewrite add-animation for the CSS-first theme (#897)
The steps still described the deleted v3 plugin (semantic/animations.js,
addUtilities/addComponents, the camelCase keyframe object form). An animation is
now two edits under primitives/animations/ — the shorthand in animate.js, which
is what makes animate-<name> a utility at all, and the CSS-string keyframes in
keyframes.js, which Tailwind v4 does NOT generate — compiled by build:tokens,
which must run before catalog:build or nothing renders.
Also records the three things the two new arrivals taught: a per-instance value
rides a var() with a token default inside the keyframe, a stagger rides a var()
delay with backwards fill, and fill is a decision — forwards leaves a translate
that makes the element a containing block for any fixed descendant.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* fix(webkit): give table cells block padding so tall content can breathe (#899)
A cell was `min-h-12` with horizontal padding only. For a single-line cell that
is invisible — the 48px minimum decides the height on its own against ~20px of
text — so every existing row renders exactly as before.
It only shows on a cell whose content is TALLER than the minimum: a wrapped list
of tags, a two-line summary. There the height came from the content alone, which
put the first and last lines flush against the row's borders with no breathing
room at all.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* docs(webkit): bring webkit-lists onto the current list-page shape (#929)
The shipped consumer skill still described the list page as it was before three
decisions landed, so a consumer following it built a page the product no longer
looks like:
- A first-level module index opens with a `PageHeading` again, and the module's
create action lives in it. The shell draws the breadcrumb only from the second
crumb up, so at first level the heading is the ONLY place the module's name
appears in the content.
- The size ladder is the hierarchy: the heading's action is the page's one
`large` (40px) control and the whole controls row is `medium` (32px). At 40px
both rows competed and the create button stopped being the page's answer.
- The controls row is narrowing only — `#actions` there is for an action on the
LISTING, not the page's own.
`last_updated` moves to 2026-08-24.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* feat: webkit documentation (#919)
* chore: [ENG-47606] add the documentation component category (#904)
* feat(theme): [ENG-47561] add the docs measure and the prose + mono body registers (#905)
* chore: [ENG-47606] add the documentation component category
* feat(theme): [ENG-47561] add the docs measure and the mono body register
* feat(theme): [ENG-47606] add the prose body register
* fix(theme): [ENG-47606] widen the docs measure to the form column
The documentation column was capped at 752px, narrower than every other measure in the system. Docs pages carry code blocks, card grids and tables, none of which fit a prose measure, so the column now takes the same 1192px the form measure uses.
The token is defined here, so it is set here: the doc component stack carries the same value, and an add/add disagreement between the two made every PR in that stack unmergeable.
* feat(webkit): [ENG-47616] add DocOnThisPage, the documentation TOC rail (#918)
* chore: [ENG-47606] add the documentation component category
* feat(theme): [ENG-47561] add the docs measure and the mono body register
* feat(theme): [ENG-47606] add the prose body register
* feat(webkit): [ENG-47606] add DocProse, the documentation typography contract
* test(storybook): add the linux baselines for the DocProse stories
* fix(theme): [ENG-47606] widen the docs measure to the form column
The documentation column was capped at 752px, narrower than every other measure in the system. Docs pages carry code blocks, card grids and tables, none of which fit a prose measure, so the column now takes the same 1192px the form measure uses.
* fix(webkit): [ENG-47606] pin the prose section step and separate the heading registers
The section rung was a descending pair that read 32 on a phone and on a laptop and only reached 48 at xl, so the same page broke its sections at different gaps by window width. It is now a flat 56 to open and 48 to close, taken from the theme's primitive spacing steps.
Each heading level now names two registers instead of one. On one register per level the base column collapsed: h2 was 16 (the body's own size), h3 and h4 were both 14, and between 640 and 768 the page title was overtaken by its own h2.
Weight and leading are no longer restated here — every element takes its type token verbatim, so the ladder is size and space only.
* feat(webkit): [ENG-47607] add DocCallout, the documentation admonition
* test(storybook): add the linux baselines for the DocProse and DocCallout stories
* fix(webkit): [ENG-47607] step an inline callout link to the only other weight the system defines
font-medium is 500, a weight no text token in the theme uses — the set is light (300) and normal (400). An inline link inside a callout now steps to 400.
* feat(webkit): [ENG-47611] add DocCard, the documentation grid cell
* test(storybook): add the linux baselines for the documentation stories
* feat(webkit): [ENG-47612] add DocCardGroup, the documentation card grid
* test(storybook): add the linux baselines for the doccardgroup stories
* feat(webkit): [ENG-47609] add DocItem, the documentation list row
* test(storybook): add the linux baselines for the docitem stories
* feat(webkit): [ENG-47613] add DocSteps and DocStep, the documentation procedure list
* test(storybook): add the linux baselines for the docsteps stories
* feat(webkit): [ENG-47608] add DocFrame, the documentation figure with a full-screen view
* test(storybook): add the linux baselines for the docframe stories
* test(storybook): stop snapshotting the autoplaying DocFrame clip
* feat(webkit): [ENG-47615] add DocUpdate and the heading-nav composable
* test(storybook): add the linux baselines for the docupdate stories
* feat(webkit): [ENG-47561] add DocPrompt, the copyable agent prompt
* test(storybook): add the linux baselines for the docprompt stories
* feat(webkit): [ENG-47610] add DocTooltip, the inline gloss
* test(storybook): add the linux baselines for the doctooltip stories
* fix(webkit): [ENG-47610] let the tooltip headline take its type token's weight
The headline restated font-medium (500), a weight no text token in the theme defines. It now takes text-body-sm verbatim, and the step to the tip below it is carried by ink colour.
* feat(webkit): [ENG-47618] add DocPagination, the documentation prev/next pair
* test(storybook): add the linux baselines for the docpagination stories
* feat(webkit): [ENG-47617] add DocPageHeader, the documentation masthead
* test(storybook): add the linux baselines for the docpageheader stories
* fix(webkit): [ENG-47617] stop the page title being overtaken by its own h2
text-heading-lg runs 18/18/30, so between 640 and 768 the masthead title rendered smaller than the prose h2 under it (20). The title now takes the same descending pair the prose h1 uses — text-heading-2xl with text-heading-xl from sm — which reads 30/30/36 and stays above every heading below it. Closes the theme gap the spec recorded.
The deck stops restating font-light; text-body-md already resolves to 300.
* feat(webkit): [ENG-47616] add DocOnThisPage, the documentation TOC rail
* test(storybook): add the linux baselines for the doconthispage stories
* fix(webkit): [ENG-47616] mark the rail's opening line so a shell can align to it
A docs shell aligns the outline rail against the page bar beside it, and needed a stable thing to measure. data-toc-title marks the rail's first line so the shell reaches it without depending on this component's internal structure.
* style(webkit): sort the use-heading-nav imports
* test(storybook): regenerate the documentation baselines after the typography fixes
---------
Co-authored-by: Gab <gabriel.mendonca@azion.com>
* fix(webkit): copy-button tooltip, card-box header inset, flow attrs (#936)
* fix(webkit): copy-button tooltip, card-box header inset, flow attrs
Three unrelated small fixes.
COPY-BUTTON COMPOSES TOOLTIP. An icon-only button says nothing to a pointer user
until it is hovered, so the glyph was unlabelled in practice — and the
"Copied" confirmation existed only as an accessible-name swap, announced to a
screen reader and invisible to everyone else. One string now names the control
AND labels its tooltip, so the two can never disagree: `ariaLabel` while idle,
`copiedLabel` for the two seconds after a write. The confirmation is visible
because it rides the same state.
CARD-BOX HEADER INSET `sm` → `md`. The header's horizontal padding now matches
the content's `p-(--spacing-md)` and the footer's `px-(--spacing-md)`, so the
title sits on the same vertical line as the first thing under it — and, in a
flush card, as the rows of whatever fills it (ItemList, Accordion and LogView
are all inset `--spacing-md`). At `--spacing-sm` every card header sat 4px left
of its own body.
FLOW FORWARDS `$attrs`. It declared `inheritAttrs: false` and then never spread
`$attrs` on its root, merging only `attrs.class`. Everything else a consumer
passed — `id`, `aria-*`, `data-*`, listeners — was silently dropped, which
`.claude/rules/root-element.md` exists to prevent. Same shape as its siblings
now: `v-bind="$attrs"` on the real root, with `cn` still merging the class.
* test(storybook): regenerate the card-box baselines for the header inset
* chore(webkit): raise the doc-prompt budget for the copy-button tooltip
DocPrompt composes CopyButton, which now wraps its IconButton in the
Tooltip overlay so the icon-only control is legible on hover. The
overlay machinery raises the measured entry from 6.20 KB to 8.61 KB;
the budget moves to 9 KB to carry that composition with headroom.
---------
Co-authored-by: Isaque Böck <isaque.santos@azion.com>
* fix(webkit): emit navigate when a collapsed breadcrumb crumb is picked
`select` is the Dropdown's event, not the option's: `Dropdown.Option`
forwards its activation to the root through the injected context, and the
root is what emits `select(event, value)`. The listener sat on the option,
where it never fired — so picking a collapsed crumb below `md` did
nothing at all. The menu closed and the reader stayed on the page. That is
the whole trail on a phone, since the collapsed shape is the only one a
narrow bar renders.
The test that covered this was skipped as a harness limitation ("@select
does not fire under test interaction"). It was not a harness limitation —
it was this bug, reproducing exactly as a real browser did. The listener
moved to the root and the test is un-skipped.
---------
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
Co-authored-by: Isaque Böck <isaque.santos@azion.com>
---------
Co-authored-by: Isaque dos Santos <isaque.santos@azion.com>
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@gabriel-lisboa-azion@isaque-bock-azion
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

feat(theme): [ENG-47561] add the docs measure and the prose + mono body registers - #905

Merged
isaque-bock-azion merged 3 commits into
feat/webkit-documentationfrom
feat/ENG-47561-docs-measure-and-mono-body
Aug 24, 2026
Merged

feat(theme): [ENG-47561] add the docs measure and the prose + mono body registers#905
isaque-bock-azion merged 3 commits into
feat/webkit-documentationfrom
feat/ENG-47561-docs-measure-and-mono-body

Conversation

@gabriel-lisboa-azion

@gabriel-lisboa-aziongabriel-lisboa-azion commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • text-body-prose-md — body copy at --leading-relaxed (1.625). The whole text-body-* set is snug (1.375), and the documentation prose ladder was fitted against a 26px line box, so prose needs its own register. Line-height belongs in the token: check-authoring's leading-raw check deliberately rejects a leading-* override even in token form.
  • text-body-code-sm — the mono body register. The label-code-* set is leading-none, built for one-row-per-line code; this one wraps. Required by DocPrompt.
  • layout-measure-docs (--container-2xl, 752px) + layout-column-docs, completing the measure family (column / focused / form / form-create / docs). Docs are the one column capped by typography rather than payload width. The utility is generated by COLUMN_MEASURE + the columnUtility factory, so two entries produce the whole class.

How to test

  1. node packages/theme/src/scripts/build-tokens.mjs then git diff --stat packages/theme/dist — expect no further change; the committed dist is already current.
  2. cd packages/theme && node --test "test/**/*.test.mjs" — 12/12 pass, including the zero-unit assertions over every emitted token.
  3. grep "text-body-prose-md-line-height\|text-body-md-line-height" packages/theme/dist/v4/globals.css — expect 1.625 and 1.375 respectively; that difference is the whole point of the new token.
  4. grep -A5 '@utility text-body-code-sm' packages/theme/dist/v4/globals.css and the same for layout-column-docs — each must emit a real rule body. A token that compiles to nothing is the silent failure mode here.
  5. grep "layout-measure':" packages/theme/src/tokens/semantic/layouts.data.js — must still read var(--container-7xl) / 1620px (see Notes).

Notes

  • Deliberately excluded: the working branch this was carved from also retunes layout-measure from --container-7xl (1620px) to --container-6xl (1388px), moving home from the focused measure to the data measure. That is a separate product decision that would have silently changed the width of every data page, so it is not here — layout-measure and layout-measure-focused keep main's values and main's prose.
  • Stacked on chore: [ENG-47606] add the documentation component category #904 (the documentation category), which DocProse also needs. Merge chore: [ENG-47606] add the documentation component category #904 first; this retargets automatically.
  • No component consumes layout-column-docs yet: DocProse deliberately has no measure of its own, so the consumer caps the column.
  • Related: ENG-47561, ENG-47606, and the sibling component issues.

Delivery

The Documentation family is delivered on the feature branch feat/docs-components-bundle, not piecemeal to main — so main takes the whole family as one merge, while review stays per-component.

Merge order, bottom-up — each PR's base is the one before it, and each retargets automatically as its parent lands:

#904#905#906#907#908#909#910#911#912#913#914#915#916#917#918feat/docs-components-bundlemain

#904the documentation category (standards only)
#905theme: the docs measure + the prose and mono body registers
#906#918DocProse · DocCallout · DocCard · DocCardGroup · DocItem · DocSteps + DocStep · DocFrame · DocUpdate · DocPrompt · DocTooltip · DocPagination · DocPageHeader · DocOnThisPage

@gabriel-lisboa-aziongabriel-lisboa-azion changed the title feat(theme): [ENG-47561] add the docs measure and the mono body registerfeat(theme): [ENG-47561] add the docs measure and the prose + mono body registersAug 22, 2026
@gabriel-lisboa-azion
gabriel-lisboa-azionforce-pushed the feat/ENG-47561-docs-measure-and-mono-body branch from d3d46c0 to bc6575cCompareAugust 22, 2026 14:11
@gabriel-lisboa-azion
gabriel-lisboa-azion changed the base branch from main to chore/ENG-47606-documentation-categoryAugust 22, 2026 14:11
@isaque-bock-azion
isaque-bock-azion changed the base branch from chore/ENG-47606-documentation-category to feat/webkit-documentationAugust 24, 2026 13:04
@isaque-bock-azion
isaque-bock-azion merged commit e864f62 into feat/webkit-documentationAug 24, 2026
47 of 51 checks passed
@isaque-bock-azion
isaque-bock-azion deleted the feat/ENG-47561-docs-measure-and-mono-body branch August 24, 2026 13:14
isaque-bock-azion added a commit that referenced this pull request Aug 26, 2026
* chore: [ENG-47606] add the documentation component category (#904)
* feat(theme): [ENG-47561] add the docs measure and the prose + mono body registers (#905)
* chore: [ENG-47606] add the documentation component category
* feat(theme): [ENG-47561] add the docs measure and the mono body register
* feat(theme): [ENG-47606] add the prose body register
* fix(theme): [ENG-47606] widen the docs measure to the form column
The documentation column was capped at 752px, narrower than every other measure in the system. Docs pages carry code blocks, card grids and tables, none of which fit a prose measure, so the column now takes the same 1192px the form measure uses.
The token is defined here, so it is set here: the doc component stack carries the same value, and an add/add disagreement between the two made every PR in that stack unmergeable.
* feat(webkit): [ENG-47616] add DocOnThisPage, the documentation TOC rail (#918)
* chore: [ENG-47606] add the documentation component category
* feat(theme): [ENG-47561] add the docs measure and the mono body register
* feat(theme): [ENG-47606] add the prose body register
* feat(webkit): [ENG-47606] add DocProse, the documentation typography contract
* test(storybook): add the linux baselines for the DocProse stories
* fix(theme): [ENG-47606] widen the docs measure to the form column
The documentation column was capped at 752px, narrower than every other measure in the system. Docs pages carry code blocks, card grids and tables, none of which fit a prose measure, so the column now takes the same 1192px the form measure uses.
* fix(webkit): [ENG-47606] pin the prose section step and separate the heading registers
The section rung was a descending pair that read 32 on a phone and on a laptop and only reached 48 at xl, so the same page broke its sections at different gaps by window width. It is now a flat 56 to open and 48 to close, taken from the theme's primitive spacing steps.
Each heading level now names two registers instead of one. On one register per level the base column collapsed: h2 was 16 (the body's own size), h3 and h4 were both 14, and between 640 and 768 the page title was overtaken by its own h2.
Weight and leading are no longer restated here — every element takes its type token verbatim, so the ladder is size and space only.
* feat(webkit): [ENG-47607] add DocCallout, the documentation admonition
* test(storybook): add the linux baselines for the DocProse and DocCallout stories
* fix(webkit): [ENG-47607] step an inline callout link to the only other weight the system defines
font-medium is 500, a weight no text token in the theme uses — the set is light (300) and normal (400). An inline link inside a callout now steps to 400.
* feat(webkit): [ENG-47611] add DocCard, the documentation grid cell
* test(storybook): add the linux baselines for the documentation stories
* feat(webkit): [ENG-47612] add DocCardGroup, the documentation card grid
* test(storybook): add the linux baselines for the doccardgroup stories
* feat(webkit): [ENG-47609] add DocItem, the documentation list row
* test(storybook): add the linux baselines for the docitem stories
* feat(webkit): [ENG-47613] add DocSteps and DocStep, the documentation procedure list
* test(storybook): add the linux baselines for the docsteps stories
* feat(webkit): [ENG-47608] add DocFrame, the documentation figure with a full-screen view
* test(storybook): add the linux baselines for the docframe stories
* test(storybook): stop snapshotting the autoplaying DocFrame clip
* feat(webkit): [ENG-47615] add DocUpdate and the heading-nav composable
* test(storybook): add the linux baselines for the docupdate stories
* feat(webkit): [ENG-47561] add DocPrompt, the copyable agent prompt
* test(storybook): add the linux baselines for the docprompt stories
* feat(webkit): [ENG-47610] add DocTooltip, the inline gloss
* test(storybook): add the linux baselines for the doctooltip stories
* fix(webkit): [ENG-47610] let the tooltip headline take its type token's weight
The headline restated font-medium (500), a weight no text token in the theme defines. It now takes text-body-sm verbatim, and the step to the tip below it is carried by ink colour.
* feat(webkit): [ENG-47618] add DocPagination, the documentation prev/next pair
* test(storybook): add the linux baselines for the docpagination stories
* feat(webkit): [ENG-47617] add DocPageHeader, the documentation masthead
* test(storybook): add the linux baselines for the docpageheader stories
* fix(webkit): [ENG-47617] stop the page title being overtaken by its own h2
text-heading-lg runs 18/18/30, so between 640 and 768 the masthead title rendered smaller than the prose h2 under it (20). The title now takes the same descending pair the prose h1 uses — text-heading-2xl with text-heading-xl from sm — which reads 30/30/36 and stays above every heading below it. Closes the theme gap the spec recorded.
The deck stops restating font-light; text-body-md already resolves to 300.
* feat(webkit): [ENG-47616] add DocOnThisPage, the documentation TOC rail
* test(storybook): add the linux baselines for the doconthispage stories
* fix(webkit): [ENG-47616] mark the rail's opening line so a shell can align to it
A docs shell aligns the outline rail against the page bar beside it, and needed a stable thing to measure. data-toc-title marks the rail's first line so the shell reaches it without depending on this component's internal structure.
* style(webkit): sort the use-heading-nav imports
* test(storybook): regenerate the documentation baselines after the typography fixes
---------
Co-authored-by: Gab <gabriel.mendonca@azion.com>
isaque-bock-azion added a commit that referenced this pull request Aug 28, 2026
#939)
* feat(theme): add --container-site, the marketing site's shared measure (#923)
* feat(theme): add --container-site, the marketing site's shared measure
The azion.com pages are a single vertical frame: the nav bar, the hero's inner
column, every section below it and the footer all sit on the same centred
column, and the border-x that runs down the page is only continuous because
those four agree to the pixel. A width re-typed per band cannot hold that — the
frame is one decision, so it gets one place to live.
`5xl` (1192px) is a rung of the shared container ladder, not a number chosen for
this page. It is its own token rather than a reference to one of the console
measures because the two answer different questions: a console measure caps a
payload (a table, a form), while this one is the width of a frame whose four
bands have to line up, so retuning either must not move the other.
* refactor(webkit): point the footer column at --container-site (#928)
The footer's column named `--container-5xl` directly. On the marketing site the
nav bar, the hero band, the framed sections and this footer are ONE vertical
frame, and its side rules only meet because all four resolve to the same width —
so the measure has to be a token they share, not a rung each of them names.
Retuning it now moves them together, which a hardcoded rung here could not do.
No visual change: `--container-site` resolves to `--container-5xl`, which is
what the class already said.
* feat(webkit): add the resizable-panel layout primitive (#902)
* feat(webkit): add the resizable-panel layout primitive
A group of adjacent regions whose shared edges the reader can drag — the layout
behind a workspace, where an editor, a terminal under it and a preview beside it
are all content and the split between them is the reader's to set.
It exists because `Sidebar` was being used for both jobs. `Sidebar` is the app's
NAVIGATION rail: it collapses to icons, it hides, it is the thing you leave to go
somewhere. A terminal, a preview and a code editor are not places you navigate to
— they are the work — and dressing two of them as navigation rails produced a
screen with three "sidebars" and one document, on an axis `Sidebar` does not even
have: it has no horizontal edge, so a terminal under an editor had to hand-roll
its own splitter. This component owns the EDGE; `Sidebar` goes back to owning
navigation.
Composition: `ResizablePanel` with `Pane` and `Handle`, attached for dot-notation
and exported standalone. The compound is built by naming the static surface and
casting once rather than with `Object.assign`, whose inferred type walks each
part's local `interface Props` into the declaration and trips TS4082.
Ships the spec, a browser-mode test suite, the story, the `-root` and
per-sub-component exports, and a size budget.
* test(storybook): regenerate linux baselines for resizable-panel
Adds the 24 snapshots for the four new ResizablePanel stories across the 6-mode
matrix. Generated by the Storybook Regenerate Baseline workflow on this branch's
HEAD — baselines are ubuntu-only, so a local run would not match the folder the
visual gate compares against.
No existing snapshot is modified or deleted; the component is additive.
* test(webkit): compose resizable-panel through its named sub-component exports
Every other composition suite in the package renders through the named exports
(`AccordionItem`, `DropdownTrigger`, …) and checks dot-notation once, as an
identity assertion against those same exports. This file was the only one that
rendered through the root's statics — `h(ResizablePanel.Pane, …)` — which made
static analysis flag eight unguarded member accesses: a `.vue` default export is
generated by the SFC compiler, so the analyzer sees no `export default`, reads
the imported binding as undefined, and reports every `.Pane` / `.Handle` read on
it.
Compose through `ResizablePanelPane` / `ResizablePanelHandle` instead, and keep
the compound contract in the one place it is the subject rather than the vehicle,
asserting identity against those exports through a guarded read.
Same nine tests, same behaviour covered — the dot-notation contract is now
verified more tightly than `toBeTruthy` did.
---------
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* feat(webkit): give Select, MultiSelect and Dropdown one shared leading column (#935)
A list that mixes iconed and icon-less options used to run two label edges: an
option with a glyph indented its text, one without it did not, so the eye had no
column to read down. Fixing it per-option is not possible — no option knows
whether any sibling has an icon.
So the reservation is made by the LIST, with a self-triggering `:has()` rule.
Every option renders a fixed leading box and marks itself `data-leading` only
when it actually has a glyph; the box is `hidden` and turns back on under
`group-has-[[data-leading]]/options:flex`. `:has()` matches the marker even
while the box is hidden, so one iconed option is enough to open the column for
the whole list — and a list with no icons at all reserves nothing and pays no
indent. The same `group/options` marker is on all three panels, so Select,
MultiSelect and Dropdown behave identically.
The group label is now OPTIONAL, and sentence-case `text-label-sm` rather than
an uppercase overline: an unlabelled group is a grouping the reader does not
need named, and an overline read as a second heading level inside a panel that
has none.
Group rhythm is `--spacing-sm` above every group but the first — the same step
CommandMenu already puts between two bare groups. Dropdown keeps its hairline
and Select does not: the Figma frame for the Select panel separates groups by
space alone. Dropdown's divider moves to the group root, which spans the panel's
full inner width, so it reads edge-to-edge like the panel's own border; the
horizontal inset the rows need moves onto the content wrapper.
Panels also gain top/bottom slots for content that is not an option (a search
row, a footer action), and the panel's vertical padding steps up one so the
first and last row do not crowd its border.
Includes the 10 dropdown visual baselines this moves.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* fix(icons): resolve currentColor per theme in the colored-icon CSS (#930)
Astro's "A" shipped invisible on every light-mode surface.
Some brand logos are only partly colored: Astro is a fixed red-to-magenta
gradient swoosh under an "A" that is the brand's INK — black on a light
background, white on a dark one. That part is authored as `fill="currentColor"`,
which is what `validate-svg.mjs` asks for, and it resolves correctly wherever
the SVG is INLINED (dist/color-catalog.json → the gallery, Storybook).
A `background-image` is a separate document, so it inherits nothing from the
element painting it. `currentColor` there resolves against the SVG's own root,
not the consumer's text color, and no amount of `color` on the `<i>` reaches it.
Left alone it lands on the UA default and the icon disappears in one theme —
which is what happened: `ai-astro.svg` had the "A" hardcoded `#FAFAFA`, so it
was white-on-white everywhere in light mode.
So the generator resolves it. An icon containing `currentColor` is emitted
twice: the base rule with the light ink, and an override with the dark ink under
the same selectors @aziontech/theme uses for its dark tokens
(`[data-theme=dark]`, `.dark`, `.azion.azion-dark`). The override's extra
selector out-specifies the base, so source order does not decide the winner.
The inks are literals (`#141414` / `#FAFAFA`) rather than `var(--text-default)`
by necessity — same document isolation — and are the theme's own values for that
token in each mode. A fully-colored logo with no ink parts still emits one rule,
exactly as before: 17 colored icons, 1 theme-aware.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* docs(webkit): ship the webkit-create-surface and webkit-errors skills (#895)
* docs(webkit): ship the webkit-create-surface and webkit-errors skills
webkit-create-surface fixes where a create or settings form lives — a first-level
resource creates on a page, anything created inside a resource opens a drawer,
nothing creates in a dialog — plus the anatomy all of them share, what belongs
behind the collapsed Advanced band, and the commit model each surface uses.
webkit-errors is the one decision table for where a failure goes: client-side
validation on the field, a server rejection scoped to one field as a Message in
that section, an unscoped request failure as a toast with Retry, the signed-out
screens where auth never toasts, and the async failure that outlives its screen.
Both join the CLI bundle and the ui-craft routing table; webkit-form and
webkit-ui-states now hand their error rows off to webkit-errors.
* docs(webkit): ship the webkit-microcopy consumer skill (#931)
The writing rules for Azion product interfaces, as a skill the adoption toolkit
ships — so a consuming project's agent writes the same strings the console does
instead of inventing a house style per screen.
It fixes the things that were being decided per-PR: punctuation and
capitalization (no em dash, no ampersand, no parentheses in labels, no bold in
running text, sentence case with Azion product names capitalized), which surface
each kind of copy belongs on (a label names, a description constrains, a Message
explains impact, a Popover defines, a Tag states), one word per concept (Select
not Pick, Application not Edge Application, and no `edge` as a prefix or a
location), navigation and IA naming, and copy that survives every breakpoint.
Registered in the three places a consumer skill has to be registered or it never
reaches a project: the CLI's file list (`plan.js`), the UI-craft pack in
`plan.test.mjs` (now 18 skills), and the routing index in `CLAUDE.fragment.md`,
where it gets its own **Words** line — it is the only skill about strings rather
than structure, and it applies during every other one.
Distinct from the generic `content-microcopy` the plan drops: that one is
house-agnostic, this one carries Azion's own vocabulary.
---------
Co-authored-by: Isaque Böck <isaque.santos@azion.com>
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* feat(theme): add the page-enter and content-enter arrival animations (#896)
* feat(theme): add the page-enter and content-enter arrival animations
The two entrances an app shell needs, as a pair. page-enter is the route
transition: it starts one --layout-boundary-inline to the LEFT of where it lands
and travels right while fading up, so a page reads as arriving FROM the nav row
that was clicked — a plain fade has no origin and a right-side arrival reads as
going back. content-enter is content settling inside a page already on screen, so
it rises a hair instead of travelling, and takes a --content-enter-delay stagger
(with backwards fill, or a delayed follower would paint its landed state first).
Neither fills forwards: page-enter's box is usually the scroll container, and a
lingering translate would make it a containing block for any fixed descendant.
* docs(webkit): teach the two arrival animations in webkit-motion-polish
Documents both utilities and their knobs, and the failure they exist to prevent:
a page arrives once, so a mount-time entrance inside a page whose route
transition is still running lockesteps with it into one diagonal move (identical
opacities frame for frame is the tell). Adds that shape and the two-shells-per-
screen variant to the silent-failure table. catalog.json rebuilt so the two
animations reach the MCP and validate-spec-compliance.
---------
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* docs: rewrite add-animation for the CSS-first theme (#897)
The steps still described the deleted v3 plugin (semantic/animations.js,
addUtilities/addComponents, the camelCase keyframe object form). An animation is
now two edits under primitives/animations/ — the shorthand in animate.js, which
is what makes animate-<name> a utility at all, and the CSS-string keyframes in
keyframes.js, which Tailwind v4 does NOT generate — compiled by build:tokens,
which must run before catalog:build or nothing renders.
Also records the three things the two new arrivals taught: a per-instance value
rides a var() with a token default inside the keyframe, a stagger rides a var()
delay with backwards fill, and fill is a decision — forwards leaves a translate
that makes the element a containing block for any fixed descendant.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* fix(webkit): give table cells block padding so tall content can breathe (#899)
A cell was `min-h-12` with horizontal padding only. For a single-line cell that
is invisible — the 48px minimum decides the height on its own against ~20px of
text — so every existing row renders exactly as before.
It only shows on a cell whose content is TALLER than the minimum: a wrapped list
of tags, a two-line summary. There the height came from the content alone, which
put the first and last lines flush against the row's borders with no breathing
room at all.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* docs(webkit): bring webkit-lists onto the current list-page shape (#929)
The shipped consumer skill still described the list page as it was before three
decisions landed, so a consumer following it built a page the product no longer
looks like:
- A first-level module index opens with a `PageHeading` again, and the module's
create action lives in it. The shell draws the breadcrumb only from the second
crumb up, so at first level the heading is the ONLY place the module's name
appears in the content.
- The size ladder is the hierarchy: the heading's action is the page's one
`large` (40px) control and the whole controls row is `medium` (32px). At 40px
both rows competed and the create button stopped being the page's answer.
- The controls row is narrowing only — `#actions` there is for an action on the
LISTING, not the page's own.
`last_updated` moves to 2026-08-24.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* feat: webkit documentation (#919)
* chore: [ENG-47606] add the documentation component category (#904)
* feat(theme): [ENG-47561] add the docs measure and the prose + mono body registers (#905)
* chore: [ENG-47606] add the documentation component category
* feat(theme): [ENG-47561] add the docs measure and the mono body register
* feat(theme): [ENG-47606] add the prose body register
* fix(theme): [ENG-47606] widen the docs measure to the form column
The documentation column was capped at 752px, narrower than every other measure in the system. Docs pages carry code blocks, card grids and tables, none of which fit a prose measure, so the column now takes the same 1192px the form measure uses.
The token is defined here, so it is set here: the doc component stack carries the same value, and an add/add disagreement between the two made every PR in that stack unmergeable.
* feat(webkit): [ENG-47616] add DocOnThisPage, the documentation TOC rail (#918)
* chore: [ENG-47606] add the documentation component category
* feat(theme): [ENG-47561] add the docs measure and the mono body register
* feat(theme): [ENG-47606] add the prose body register
* feat(webkit): [ENG-47606] add DocProse, the documentation typography contract
* test(storybook): add the linux baselines for the DocProse stories
* fix(theme): [ENG-47606] widen the docs measure to the form column
The documentation column was capped at 752px, narrower than every other measure in the system. Docs pages carry code blocks, card grids and tables, none of which fit a prose measure, so the column now takes the same 1192px the form measure uses.
* fix(webkit): [ENG-47606] pin the prose section step and separate the heading registers
The section rung was a descending pair that read 32 on a phone and on a laptop and only reached 48 at xl, so the same page broke its sections at different gaps by window width. It is now a flat 56 to open and 48 to close, taken from the theme's primitive spacing steps.
Each heading level now names two registers instead of one. On one register per level the base column collapsed: h2 was 16 (the body's own size), h3 and h4 were both 14, and between 640 and 768 the page title was overtaken by its own h2.
Weight and leading are no longer restated here — every element takes its type token verbatim, so the ladder is size and space only.
* feat(webkit): [ENG-47607] add DocCallout, the documentation admonition
* test(storybook): add the linux baselines for the DocProse and DocCallout stories
* fix(webkit): [ENG-47607] step an inline callout link to the only other weight the system defines
font-medium is 500, a weight no text token in the theme uses — the set is light (300) and normal (400). An inline link inside a callout now steps to 400.
* feat(webkit): [ENG-47611] add DocCard, the documentation grid cell
* test(storybook): add the linux baselines for the documentation stories
* feat(webkit): [ENG-47612] add DocCardGroup, the documentation card grid
* test(storybook): add the linux baselines for the doccardgroup stories
* feat(webkit): [ENG-47609] add DocItem, the documentation list row
* test(storybook): add the linux baselines for the docitem stories
* feat(webkit): [ENG-47613] add DocSteps and DocStep, the documentation procedure list
* test(storybook): add the linux baselines for the docsteps stories
* feat(webkit): [ENG-47608] add DocFrame, the documentation figure with a full-screen view
* test(storybook): add the linux baselines for the docframe stories
* test(storybook): stop snapshotting the autoplaying DocFrame clip
* feat(webkit): [ENG-47615] add DocUpdate and the heading-nav composable
* test(storybook): add the linux baselines for the docupdate stories
* feat(webkit): [ENG-47561] add DocPrompt, the copyable agent prompt
* test(storybook): add the linux baselines for the docprompt stories
* feat(webkit): [ENG-47610] add DocTooltip, the inline gloss
* test(storybook): add the linux baselines for the doctooltip stories
* fix(webkit): [ENG-47610] let the tooltip headline take its type token's weight
The headline restated font-medium (500), a weight no text token in the theme defines. It now takes text-body-sm verbatim, and the step to the tip below it is carried by ink colour.
* feat(webkit): [ENG-47618] add DocPagination, the documentation prev/next pair
* test(storybook): add the linux baselines for the docpagination stories
* feat(webkit): [ENG-47617] add DocPageHeader, the documentation masthead
* test(storybook): add the linux baselines for the docpageheader stories
* fix(webkit): [ENG-47617] stop the page title being overtaken by its own h2
text-heading-lg runs 18/18/30, so between 640 and 768 the masthead title rendered smaller than the prose h2 under it (20). The title now takes the same descending pair the prose h1 uses — text-heading-2xl with text-heading-xl from sm — which reads 30/30/36 and stays above every heading below it. Closes the theme gap the spec recorded.
The deck stops restating font-light; text-body-md already resolves to 300.
* feat(webkit): [ENG-47616] add DocOnThisPage, the documentation TOC rail
* test(storybook): add the linux baselines for the doconthispage stories
* fix(webkit): [ENG-47616] mark the rail's opening line so a shell can align to it
A docs shell aligns the outline rail against the page bar beside it, and needed a stable thing to measure. data-toc-title marks the rail's first line so the shell reaches it without depending on this component's internal structure.
* style(webkit): sort the use-heading-nav imports
* test(storybook): regenerate the documentation baselines after the typography fixes
---------
Co-authored-by: Gab <gabriel.mendonca@azion.com>
* fix(webkit): copy-button tooltip, card-box header inset, flow attrs (#936)
* fix(webkit): copy-button tooltip, card-box header inset, flow attrs
Three unrelated small fixes.
COPY-BUTTON COMPOSES TOOLTIP. An icon-only button says nothing to a pointer user
until it is hovered, so the glyph was unlabelled in practice — and the
"Copied" confirmation existed only as an accessible-name swap, announced to a
screen reader and invisible to everyone else. One string now names the control
AND labels its tooltip, so the two can never disagree: `ariaLabel` while idle,
`copiedLabel` for the two seconds after a write. The confirmation is visible
because it rides the same state.
CARD-BOX HEADER INSET `sm` → `md`. The header's horizontal padding now matches
the content's `p-(--spacing-md)` and the footer's `px-(--spacing-md)`, so the
title sits on the same vertical line as the first thing under it — and, in a
flush card, as the rows of whatever fills it (ItemList, Accordion and LogView
are all inset `--spacing-md`). At `--spacing-sm` every card header sat 4px left
of its own body.
FLOW FORWARDS `$attrs`. It declared `inheritAttrs: false` and then never spread
`$attrs` on its root, merging only `attrs.class`. Everything else a consumer
passed — `id`, `aria-*`, `data-*`, listeners — was silently dropped, which
`.claude/rules/root-element.md` exists to prevent. Same shape as its siblings
now: `v-bind="$attrs"` on the real root, with `cn` still merging the class.
* test(storybook): regenerate the card-box baselines for the header inset
* chore(webkit): raise the doc-prompt budget for the copy-button tooltip
DocPrompt composes CopyButton, which now wraps its IconButton in the
Tooltip overlay so the icon-only control is legible on hover. The
overlay machinery raises the measured entry from 6.20 KB to 8.61 KB;
the budget moves to 9 KB to carry that composition with headroom.
---------
Co-authored-by: Isaque Böck <isaque.santos@azion.com>
* fix(webkit): emit navigate when a collapsed breadcrumb crumb is picked
`select` is the Dropdown's event, not the option's: `Dropdown.Option`
forwards its activation to the root through the injected context, and the
root is what emits `select(event, value)`. The listener sat on the option,
where it never fired — so picking a collapsed crumb below `md` did
nothing at all. The menu closed and the reader stayed on the page. That is
the whole trail on a phone, since the collapsed shape is the only one a
narrow bar renders.
The test that covered this was skipped as a harness limitation ("@select
does not fire under test interaction"). It was not a harness limitation —
it was this bug, reproducing exactly as a real browser did. The listener
moved to the root and the test is un-skipped.
---------
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
Co-authored-by: Isaque Böck <isaque.santos@azion.com>
isaque-bock-azion added a commit that referenced this pull request Aug 28, 2026
* feat(webkit): let a table column declare a minWidth floor
A bounded column — a chip list, a date, a count, an id — could only be sized by
a fixed `width`, and a fixed width is a bet in both directions: guess high and
the column reserves space the rest of the row needs, guess low and the content
inside it truncates (a chip list ends up as `SQL In…`). `minWidth` only has to
be right about the minimum: the column resolves to the widest of that floor and
what it actually holds — its header, with the sort affordance, and every
rendered body cell. `width` still wins when both are set, so an explicit width
remains the consumer overriding the measurement outright.
ONE width is resolved per column and applied to the header and to every body
cell, which keeps the invariant fixed widths exist for: the column resolves
identically in every row. A per-cell `min-width` would let each row size to its
own content and drift the header away from the body.
It has to be measured, because a flex row cannot express "as wide as the widest
cell in this column" — every row is its own flex container and knows nothing
about its siblings. The measurement is the same clone-based one the resize floor
already uses, so a column's auto width and its drag floor can never disagree.
Three things the measurement has to respect:
- Debounced (32ms). The rendered content changes in bursts — a sort, a page
turn, a column toggled back on — and measuring reads layout, so an
un-coalesced version would reflow per keystroke of a filter.
- Skipped while `loading`. Skeleton rows are not the content; measuring against
them would size every column to a placeholder and never grow it back.
- Re-run on `document.fonts.ready`. Text width is font-dependent and the first
paint can happen on the fallback face.
`autoWidths` joins the row `v-memo` so a resolved width actually reaches the
body, and feeds `frozenWidthOf` so a frozen `minWidth` column contributes its
real width to a neighbour's sticky offset.
* test(storybook): regenerate the table baselines for the column min-width
* fix(webkit): emit navigate when a collapsed breadcrumb crumb is picked (#939)
* feat(theme): add --container-site, the marketing site's shared measure (#923)
* feat(theme): add --container-site, the marketing site's shared measure
The azion.com pages are a single vertical frame: the nav bar, the hero's inner
column, every section below it and the footer all sit on the same centred
column, and the border-x that runs down the page is only continuous because
those four agree to the pixel. A width re-typed per band cannot hold that — the
frame is one decision, so it gets one place to live.
`5xl` (1192px) is a rung of the shared container ladder, not a number chosen for
this page. It is its own token rather than a reference to one of the console
measures because the two answer different questions: a console measure caps a
payload (a table, a form), while this one is the width of a frame whose four
bands have to line up, so retuning either must not move the other.
* refactor(webkit): point the footer column at --container-site (#928)
The footer's column named `--container-5xl` directly. On the marketing site the
nav bar, the hero band, the framed sections and this footer are ONE vertical
frame, and its side rules only meet because all four resolve to the same width —
so the measure has to be a token they share, not a rung each of them names.
Retuning it now moves them together, which a hardcoded rung here could not do.
No visual change: `--container-site` resolves to `--container-5xl`, which is
what the class already said.
* feat(webkit): add the resizable-panel layout primitive (#902)
* feat(webkit): add the resizable-panel layout primitive
A group of adjacent regions whose shared edges the reader can drag — the layout
behind a workspace, where an editor, a terminal under it and a preview beside it
are all content and the split between them is the reader's to set.
It exists because `Sidebar` was being used for both jobs. `Sidebar` is the app's
NAVIGATION rail: it collapses to icons, it hides, it is the thing you leave to go
somewhere. A terminal, a preview and a code editor are not places you navigate to
— they are the work — and dressing two of them as navigation rails produced a
screen with three "sidebars" and one document, on an axis `Sidebar` does not even
have: it has no horizontal edge, so a terminal under an editor had to hand-roll
its own splitter. This component owns the EDGE; `Sidebar` goes back to owning
navigation.
Composition: `ResizablePanel` with `Pane` and `Handle`, attached for dot-notation
and exported standalone. The compound is built by naming the static surface and
casting once rather than with `Object.assign`, whose inferred type walks each
part's local `interface Props` into the declaration and trips TS4082.
Ships the spec, a browser-mode test suite, the story, the `-root` and
per-sub-component exports, and a size budget.
* test(storybook): regenerate linux baselines for resizable-panel
Adds the 24 snapshots for the four new ResizablePanel stories across the 6-mode
matrix. Generated by the Storybook Regenerate Baseline workflow on this branch's
HEAD — baselines are ubuntu-only, so a local run would not match the folder the
visual gate compares against.
No existing snapshot is modified or deleted; the component is additive.
* test(webkit): compose resizable-panel through its named sub-component exports
Every other composition suite in the package renders through the named exports
(`AccordionItem`, `DropdownTrigger`, …) and checks dot-notation once, as an
identity assertion against those same exports. This file was the only one that
rendered through the root's statics — `h(ResizablePanel.Pane, …)` — which made
static analysis flag eight unguarded member accesses: a `.vue` default export is
generated by the SFC compiler, so the analyzer sees no `export default`, reads
the imported binding as undefined, and reports every `.Pane` / `.Handle` read on
it.
Compose through `ResizablePanelPane` / `ResizablePanelHandle` instead, and keep
the compound contract in the one place it is the subject rather than the vehicle,
asserting identity against those exports through a guarded read.
Same nine tests, same behaviour covered — the dot-notation contract is now
verified more tightly than `toBeTruthy` did.
---------
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* feat(webkit): give Select, MultiSelect and Dropdown one shared leading column (#935)
A list that mixes iconed and icon-less options used to run two label edges: an
option with a glyph indented its text, one without it did not, so the eye had no
column to read down. Fixing it per-option is not possible — no option knows
whether any sibling has an icon.
So the reservation is made by the LIST, with a self-triggering `:has()` rule.
Every option renders a fixed leading box and marks itself `data-leading` only
when it actually has a glyph; the box is `hidden` and turns back on under
`group-has-[[data-leading]]/options:flex`. `:has()` matches the marker even
while the box is hidden, so one iconed option is enough to open the column for
the whole list — and a list with no icons at all reserves nothing and pays no
indent. The same `group/options` marker is on all three panels, so Select,
MultiSelect and Dropdown behave identically.
The group label is now OPTIONAL, and sentence-case `text-label-sm` rather than
an uppercase overline: an unlabelled group is a grouping the reader does not
need named, and an overline read as a second heading level inside a panel that
has none.
Group rhythm is `--spacing-sm` above every group but the first — the same step
CommandMenu already puts between two bare groups. Dropdown keeps its hairline
and Select does not: the Figma frame for the Select panel separates groups by
space alone. Dropdown's divider moves to the group root, which spans the panel's
full inner width, so it reads edge-to-edge like the panel's own border; the
horizontal inset the rows need moves onto the content wrapper.
Panels also gain top/bottom slots for content that is not an option (a search
row, a footer action), and the panel's vertical padding steps up one so the
first and last row do not crowd its border.
Includes the 10 dropdown visual baselines this moves.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* fix(icons): resolve currentColor per theme in the colored-icon CSS (#930)
Astro's "A" shipped invisible on every light-mode surface.
Some brand logos are only partly colored: Astro is a fixed red-to-magenta
gradient swoosh under an "A" that is the brand's INK — black on a light
background, white on a dark one. That part is authored as `fill="currentColor"`,
which is what `validate-svg.mjs` asks for, and it resolves correctly wherever
the SVG is INLINED (dist/color-catalog.json → the gallery, Storybook).
A `background-image` is a separate document, so it inherits nothing from the
element painting it. `currentColor` there resolves against the SVG's own root,
not the consumer's text color, and no amount of `color` on the `<i>` reaches it.
Left alone it lands on the UA default and the icon disappears in one theme —
which is what happened: `ai-astro.svg` had the "A" hardcoded `#FAFAFA`, so it
was white-on-white everywhere in light mode.
So the generator resolves it. An icon containing `currentColor` is emitted
twice: the base rule with the light ink, and an override with the dark ink under
the same selectors @aziontech/theme uses for its dark tokens
(`[data-theme=dark]`, `.dark`, `.azion.azion-dark`). The override's extra
selector out-specifies the base, so source order does not decide the winner.
The inks are literals (`#141414` / `#FAFAFA`) rather than `var(--text-default)`
by necessity — same document isolation — and are the theme's own values for that
token in each mode. A fully-colored logo with no ink parts still emits one rule,
exactly as before: 17 colored icons, 1 theme-aware.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* docs(webkit): ship the webkit-create-surface and webkit-errors skills (#895)
* docs(webkit): ship the webkit-create-surface and webkit-errors skills
webkit-create-surface fixes where a create or settings form lives — a first-level
resource creates on a page, anything created inside a resource opens a drawer,
nothing creates in a dialog — plus the anatomy all of them share, what belongs
behind the collapsed Advanced band, and the commit model each surface uses.
webkit-errors is the one decision table for where a failure goes: client-side
validation on the field, a server rejection scoped to one field as a Message in
that section, an unscoped request failure as a toast with Retry, the signed-out
screens where auth never toasts, and the async failure that outlives its screen.
Both join the CLI bundle and the ui-craft routing table; webkit-form and
webkit-ui-states now hand their error rows off to webkit-errors.
* docs(webkit): ship the webkit-microcopy consumer skill (#931)
The writing rules for Azion product interfaces, as a skill the adoption toolkit
ships — so a consuming project's agent writes the same strings the console does
instead of inventing a house style per screen.
It fixes the things that were being decided per-PR: punctuation and
capitalization (no em dash, no ampersand, no parentheses in labels, no bold in
running text, sentence case with Azion product names capitalized), which surface
each kind of copy belongs on (a label names, a description constrains, a Message
explains impact, a Popover defines, a Tag states), one word per concept (Select
not Pick, Application not Edge Application, and no `edge` as a prefix or a
location), navigation and IA naming, and copy that survives every breakpoint.
Registered in the three places a consumer skill has to be registered or it never
reaches a project: the CLI's file list (`plan.js`), the UI-craft pack in
`plan.test.mjs` (now 18 skills), and the routing index in `CLAUDE.fragment.md`,
where it gets its own **Words** line — it is the only skill about strings rather
than structure, and it applies during every other one.
Distinct from the generic `content-microcopy` the plan drops: that one is
house-agnostic, this one carries Azion's own vocabulary.
---------
Co-authored-by: Isaque Böck <isaque.santos@azion.com>
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* feat(theme): add the page-enter and content-enter arrival animations (#896)
* feat(theme): add the page-enter and content-enter arrival animations
The two entrances an app shell needs, as a pair. page-enter is the route
transition: it starts one --layout-boundary-inline to the LEFT of where it lands
and travels right while fading up, so a page reads as arriving FROM the nav row
that was clicked — a plain fade has no origin and a right-side arrival reads as
going back. content-enter is content settling inside a page already on screen, so
it rises a hair instead of travelling, and takes a --content-enter-delay stagger
(with backwards fill, or a delayed follower would paint its landed state first).
Neither fills forwards: page-enter's box is usually the scroll container, and a
lingering translate would make it a containing block for any fixed descendant.
* docs(webkit): teach the two arrival animations in webkit-motion-polish
Documents both utilities and their knobs, and the failure they exist to prevent:
a page arrives once, so a mount-time entrance inside a page whose route
transition is still running lockesteps with it into one diagonal move (identical
opacities frame for frame is the tell). Adds that shape and the two-shells-per-
screen variant to the silent-failure table. catalog.json rebuilt so the two
animations reach the MCP and validate-spec-compliance.
---------
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* docs: rewrite add-animation for the CSS-first theme (#897)
The steps still described the deleted v3 plugin (semantic/animations.js,
addUtilities/addComponents, the camelCase keyframe object form). An animation is
now two edits under primitives/animations/ — the shorthand in animate.js, which
is what makes animate-<name> a utility at all, and the CSS-string keyframes in
keyframes.js, which Tailwind v4 does NOT generate — compiled by build:tokens,
which must run before catalog:build or nothing renders.
Also records the three things the two new arrivals taught: a per-instance value
rides a var() with a token default inside the keyframe, a stagger rides a var()
delay with backwards fill, and fill is a decision — forwards leaves a translate
that makes the element a containing block for any fixed descendant.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* fix(webkit): give table cells block padding so tall content can breathe (#899)
A cell was `min-h-12` with horizontal padding only. For a single-line cell that
is invisible — the 48px minimum decides the height on its own against ~20px of
text — so every existing row renders exactly as before.
It only shows on a cell whose content is TALLER than the minimum: a wrapped list
of tags, a two-line summary. There the height came from the content alone, which
put the first and last lines flush against the row's borders with no breathing
room at all.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* docs(webkit): bring webkit-lists onto the current list-page shape (#929)
The shipped consumer skill still described the list page as it was before three
decisions landed, so a consumer following it built a page the product no longer
looks like:
- A first-level module index opens with a `PageHeading` again, and the module's
create action lives in it. The shell draws the breadcrumb only from the second
crumb up, so at first level the heading is the ONLY place the module's name
appears in the content.
- The size ladder is the hierarchy: the heading's action is the page's one
`large` (40px) control and the whole controls row is `medium` (32px). At 40px
both rows competed and the create button stopped being the page's answer.
- The controls row is narrowing only — `#actions` there is for an action on the
LISTING, not the page's own.
`last_updated` moves to 2026-08-24.
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
* feat: webkit documentation (#919)
* chore: [ENG-47606] add the documentation component category (#904)
* feat(theme): [ENG-47561] add the docs measure and the prose + mono body registers (#905)
* chore: [ENG-47606] add the documentation component category
* feat(theme): [ENG-47561] add the docs measure and the mono body register
* feat(theme): [ENG-47606] add the prose body register
* fix(theme): [ENG-47606] widen the docs measure to the form column
The documentation column was capped at 752px, narrower than every other measure in the system. Docs pages carry code blocks, card grids and tables, none of which fit a prose measure, so the column now takes the same 1192px the form measure uses.
The token is defined here, so it is set here: the doc component stack carries the same value, and an add/add disagreement between the two made every PR in that stack unmergeable.
* feat(webkit): [ENG-47616] add DocOnThisPage, the documentation TOC rail (#918)
* chore: [ENG-47606] add the documentation component category
* feat(theme): [ENG-47561] add the docs measure and the mono body register
* feat(theme): [ENG-47606] add the prose body register
* feat(webkit): [ENG-47606] add DocProse, the documentation typography contract
* test(storybook): add the linux baselines for the DocProse stories
* fix(theme): [ENG-47606] widen the docs measure to the form column
The documentation column was capped at 752px, narrower than every other measure in the system. Docs pages carry code blocks, card grids and tables, none of which fit a prose measure, so the column now takes the same 1192px the form measure uses.
* fix(webkit): [ENG-47606] pin the prose section step and separate the heading registers
The section rung was a descending pair that read 32 on a phone and on a laptop and only reached 48 at xl, so the same page broke its sections at different gaps by window width. It is now a flat 56 to open and 48 to close, taken from the theme's primitive spacing steps.
Each heading level now names two registers instead of one. On one register per level the base column collapsed: h2 was 16 (the body's own size), h3 and h4 were both 14, and between 640 and 768 the page title was overtaken by its own h2.
Weight and leading are no longer restated here — every element takes its type token verbatim, so the ladder is size and space only.
* feat(webkit): [ENG-47607] add DocCallout, the documentation admonition
* test(storybook): add the linux baselines for the DocProse and DocCallout stories
* fix(webkit): [ENG-47607] step an inline callout link to the only other weight the system defines
font-medium is 500, a weight no text token in the theme uses — the set is light (300) and normal (400). An inline link inside a callout now steps to 400.
* feat(webkit): [ENG-47611] add DocCard, the documentation grid cell
* test(storybook): add the linux baselines for the documentation stories
* feat(webkit): [ENG-47612] add DocCardGroup, the documentation card grid
* test(storybook): add the linux baselines for the doccardgroup stories
* feat(webkit): [ENG-47609] add DocItem, the documentation list row
* test(storybook): add the linux baselines for the docitem stories
* feat(webkit): [ENG-47613] add DocSteps and DocStep, the documentation procedure list
* test(storybook): add the linux baselines for the docsteps stories
* feat(webkit): [ENG-47608] add DocFrame, the documentation figure with a full-screen view
* test(storybook): add the linux baselines for the docframe stories
* test(storybook): stop snapshotting the autoplaying DocFrame clip
* feat(webkit): [ENG-47615] add DocUpdate and the heading-nav composable
* test(storybook): add the linux baselines for the docupdate stories
* feat(webkit): [ENG-47561] add DocPrompt, the copyable agent prompt
* test(storybook): add the linux baselines for the docprompt stories
* feat(webkit): [ENG-47610] add DocTooltip, the inline gloss
* test(storybook): add the linux baselines for the doctooltip stories
* fix(webkit): [ENG-47610] let the tooltip headline take its type token's weight
The headline restated font-medium (500), a weight no text token in the theme defines. It now takes text-body-sm verbatim, and the step to the tip below it is carried by ink colour.
* feat(webkit): [ENG-47618] add DocPagination, the documentation prev/next pair
* test(storybook): add the linux baselines for the docpagination stories
* feat(webkit): [ENG-47617] add DocPageHeader, the documentation masthead
* test(storybook): add the linux baselines for the docpageheader stories
* fix(webkit): [ENG-47617] stop the page title being overtaken by its own h2
text-heading-lg runs 18/18/30, so between 640 and 768 the masthead title rendered smaller than the prose h2 under it (20). The title now takes the same descending pair the prose h1 uses — text-heading-2xl with text-heading-xl from sm — which reads 30/30/36 and stays above every heading below it. Closes the theme gap the spec recorded.
The deck stops restating font-light; text-body-md already resolves to 300.
* feat(webkit): [ENG-47616] add DocOnThisPage, the documentation TOC rail
* test(storybook): add the linux baselines for the doconthispage stories
* fix(webkit): [ENG-47616] mark the rail's opening line so a shell can align to it
A docs shell aligns the outline rail against the page bar beside it, and needed a stable thing to measure. data-toc-title marks the rail's first line so the shell reaches it without depending on this component's internal structure.
* style(webkit): sort the use-heading-nav imports
* test(storybook): regenerate the documentation baselines after the typography fixes
---------
Co-authored-by: Gab <gabriel.mendonca@azion.com>
* fix(webkit): copy-button tooltip, card-box header inset, flow attrs (#936)
* fix(webkit): copy-button tooltip, card-box header inset, flow attrs
Three unrelated small fixes.
COPY-BUTTON COMPOSES TOOLTIP. An icon-only button says nothing to a pointer user
until it is hovered, so the glyph was unlabelled in practice — and the
"Copied" confirmation existed only as an accessible-name swap, announced to a
screen reader and invisible to everyone else. One string now names the control
AND labels its tooltip, so the two can never disagree: `ariaLabel` while idle,
`copiedLabel` for the two seconds after a write. The confirmation is visible
because it rides the same state.
CARD-BOX HEADER INSET `sm` → `md`. The header's horizontal padding now matches
the content's `p-(--spacing-md)` and the footer's `px-(--spacing-md)`, so the
title sits on the same vertical line as the first thing under it — and, in a
flush card, as the rows of whatever fills it (ItemList, Accordion and LogView
are all inset `--spacing-md`). At `--spacing-sm` every card header sat 4px left
of its own body.
FLOW FORWARDS `$attrs`. It declared `inheritAttrs: false` and then never spread
`$attrs` on its root, merging only `attrs.class`. Everything else a consumer
passed — `id`, `aria-*`, `data-*`, listeners — was silently dropped, which
`.claude/rules/root-element.md` exists to prevent. Same shape as its siblings
now: `v-bind="$attrs"` on the real root, with `cn` still merging the class.
* test(storybook): regenerate the card-box baselines for the header inset
* chore(webkit): raise the doc-prompt budget for the copy-button tooltip
DocPrompt composes CopyButton, which now wraps its IconButton in the
Tooltip overlay so the icon-only control is legible on hover. The
overlay machinery raises the measured entry from 6.20 KB to 8.61 KB;
the budget moves to 9 KB to carry that composition with headroom.
---------
Co-authored-by: Isaque Böck <isaque.santos@azion.com>
* fix(webkit): emit navigate when a collapsed breadcrumb crumb is picked
`select` is the Dropdown's event, not the option's: `Dropdown.Option`
forwards its activation to the root through the injected context, and the
root is what emits `select(event, value)`. The listener sat on the option,
where it never fired — so picking a collapsed crumb below `md` did
nothing at all. The menu closed and the reader stayed on the page. That is
the whole trail on a phone, since the collapsed shape is the only one a
narrow bar renders.
The test that covered this was skipped as a harness limitation ("@select
does not fire under test interaction"). It was not a harness limitation —
it was this bug, reproducing exactly as a real browser did. The listener
moved to the root and the test is un-skipped.
---------
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
Co-authored-by: Isaque Böck <isaque.santos@azion.com>
---------
Co-authored-by: Isaque dos Santos <isaque.santos@azion.com>
Co-authored-by: robson-junior-azion <robson.junior@azion.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@gabriel-lisboa-azion@isaque-bock-azion