## 1.2.7 - 2026-10-05
### Added
- Layout metrics are typed values now, not strings. `layout/CssLength` parses a CSS length once (leading-number scan, `calc()`/`min()`/`max()`/`clamp()`, unit conversion) and resolves it on demand, so the layout pass no longer re-parses `width`/`height`/`min-*`/`max-*`/`flex-basis` or the `box-sizing` keyword on every visit; `Style` gains lazy accessors (`widthLength()`, `isBorderBox()`, …) that compile at computed-style time and are invalidated on `clone()`/`copyFrom()` so animation buffers cannot read the previous frame's geometry. `Size`'s public API now delegates to `CssLength`, which keeps the two implementations from drifting apart.
- `overflow` and `visibility` normalize to the `Interaction.Overflow` / `Interaction.Visibility` enums, and `display`/`overflow` questions are answered by memoized `Style` accessors (`isFlexDisplay()`, `isGridDisplay()`, `isInFlow()`, `clipsOverflow()`, …) instead of `trim()`+`toLowerCase()` on every call. Only the memoized form changed: `Layout.isInFlow` and `Interaction.isDisplayed` keep their exact-match semantics, while `isFlexDisplay`/`isGridDisplay` stay case-insensitive, as before.
- The first full geometry commit of a document can be sliced across frames. A page whose initial commit would block one frame for hundreds of milliseconds now pays a per-frame budget instead (`initialCommitSliceEnabled`, `initialCommitSliceMs`, default 16 ms), and the document is not painted until its geometry is complete. `-Dapricityui.layout.sliceInitialCommit=false` restores the single-shot commit. Small documents and non-render callers (the tick path, hit testing) keep committing synchronously, because they rely on `commit()` returning with complete geometry.
- `ApricityUI.ensureDocument(path)` returns the existing document for a path or creates one, so callers no longer have to test `Document.get(path)` themselves.
### Changed
- `ApricityUI.createDocument(path)` now marks the document reload-persistent, which is what lets a document created from a screen survive a resource reload and open as an overlay. `Document.create(path)` is unchanged.
- Debug log volume is down substantially. The per-draw font warnings ("blank text draw", which fired once per line that had not been rasterized yet) are capped at 8 per process instead of 1200, and no longer embed the full raster cache key. `CanvasImageSupport`'s per-draw warnings are de-duplicated per resource, so a broken image logs once rather than every frame. Leftover commented-out log blocks, a dead `Image.loadTexture` overload, the unreachable `QwenHtmlOverlayDebug` class and one repeated failure log in each of `StyleAsyncHandler` and `NetworkAsyncHandler` are gone; a number of per-event `INFO` lines (`[AUI Screen]`, `[AUI Viewport]`, `NetworkUtil` chunking, Fabric network registration) moved to `DEBUG`.
### Fixed
- Buttons in the Ore and McUI themes no longer grow taller while pressed. `.button:active` carried hardcoded `padding-top: 9px; padding-bottom: 11px` — correct for the base button's `7px`/`13px`, but `.button-normal` (`8px`/`8px`) and `.button-small` (`5px`/`9px`) have no fixed height, so pressing them added 4 px and 6 px of height respectively and pushed the surrounding content down. Both variants now keep their own vertical padding sum on `:active`, so only the 2 px "pressed" shift remains.
- A grid could lay some of its items out against a different track list than the rest, leaving a card outside its container. Two independent causes, both fixed. First, a grid container with `width: auto` derived its available track space from an ancestor width whenever its own used size was not cached yet — and it never was on the first measurement, because `Size.of` short-circuits on that very cache. Second, `RenderElement.dependency()`, the stamp that decides whether a committed rect is still valid, mixed in only the `layoutVersion` counters along the route and not the ancestors' used sizes, while the layout caches' own stamp (`usedSizeDependency()`) did include them. When an ancestor's width changed without anyone bumping a counter, the layout was recomputed but the committed rect was not. `dependency()` now mixes in ancestor sizes like `usedSizeDependency()` does.
- A stale `position` cache could survive its container's resize permanently. `RenderElement.size`'s cache invalidates itself when its dependency goes stale, but it did not tell its children: `position` has no dependency stamp of its own and is only cleared explicitly, and a grid's track change does not travel the style-invalidation path. `size.get()` now drops its children's `position` and committed geometry when it finds itself stale. This is what made a mispositioned card stick until something forced a full relayout.
- Switching `:checked` recomputed the whole document. `Element.setChecked` requested a style recalc on the document element, so toggling any checkbox or radio re-ran style resolution over every element on the page. The affected set for a `:checked` change is the element's own subtree plus its following siblings' subtrees — the combinators (`>`, `+`, `~`, descendant) only look forwards and the selector engine has no `:has` — which is exactly `subtree(parent)`. The recalc is now scoped to that, and `enforceRadioGroupChecked` requests the same scope for each radio it unchecks, since those may live anywhere in the document and previously relied on the whole-document pass to refresh their own following siblings.
- A grid's `gap` was rounded to whole pixels. `Grid.Gaps` held `int`s and parsed the values with `Size.parse`, which rounds, so a non-integer gap (`1.84px` from a scrollbar inset, `3.5px` from a `var()` or a shorthand expansion) was silently changed and the tracks no longer added up to the container's width. Gaps are `double`s parsed through `CssLength` now.
- A scroll container's scrollbar could appear one frame late. `ScrollModel.commitLayoutMetrics()` both measures the scroll area and flips the scrollbar's visibility — a layout input, since the gutter comes out of the content box — but it only marked the layout dirty; whether that became an invalidation depended on where it was called from. The geometry-commit path calls it *after* committing geometry, so the invalidation landed on the next 20 Hz tick and the frame that had just been committed stayed laid out at the old width. The commit path now invalidates immediately (`commitScrollMetricsAfterLayoutCommit`).
Developer information
Projects on Modrinth are automatically available through a Maven repository for use with JVM build tools such as Gradle.
maven { name = "Modrinth"; url = "https://api.modrinth.com/maven" }