Skip to content

RigCheck: runtime for signatures, animations, sheets, wrap, scaled frames, record formulas and fixes - #81

Merged
BuildCodelessly merged 14 commits into
masterfrom
claude/tender-johnson-1xpj1j
Oct 24, 2026
Merged

BuildCodelessly merged 14 commits into
masterfrom
claude/tender-johnson-1xpj1j

Conversation

@BuildCodelessly

@BuildCodelessly BuildCodelessly commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Summary

This PR is the runtime side of the RigCheck rebuild; the model fields are in Codelessly/CodelesslyAPI#46. Everything is listed under Unreleased in CHANGELOG.md.

Up to date with master (8.3.0). Where 8.3.0 already shipped the same thing, master's version stays and this branch's duplicate was removed:

  • wrap (CodelesslyWrap)
  • repeating gradients (gradientRepeatEvery)
  • the record formulas: object, merge, set, setAt, append, take, skip, reverse, indexOf, substring, padLeft, thousands
  • text field flags and empty errorText
  • dialog presentation (presentCanvas)
  • list item text on the design canvas

Added on top of master

  • Nodes and actions:
    • the signature pad;
    • loop animations (LoopAnimationBox);
    • cursorEnter, cursorExit and hover reactions.
  • Sheets:
    • Side sheets (LayoutPresentationRoute).
    • Bottom sheets take showDragHandle and maxHeightFactor, both off by default.
    • A bottom sheet now hugs its canvas, so a tap above it dismisses it.
    • confirm can open as a dialog, bottom sheet, adaptive or side sheet.
  • Toasts: accent bar, icon color, font and corner radius.
  • Disabled nodes: a node bound disabled with no Disabled state of its own fades to 40%.
  • Forms and formulas:
    • errorText can also be bound in variables, and a boolean error binding shows the error borders without a message;
    • left, right, toMap, and an index parameter in list lambdas (indexOf passes it too);
    • isEmpty(null) is true, and list isEmpty / isNotEmpty work.
  • Layout:
    • contentFit (ScaledContent);
    • the parent size variable;
    • a spacer flexes even under host wrappers;
    • a fixed-count grid keeps its count at every width;
    • shrinkWrapRows;
    • a child filling across a hug row stretches to its siblings (IntrinsicHeight).

Tests: locally, with codelessly_api overridden to the API branch, every CI step passes: dart format (0 changed), flutter analyze (no issues), flutter test (842 passed), and the example flutter build web --wasm.

Merge order

CI here stays red until Codelessly/CodelesslyAPI#46 is released, because CI resolves codelessly_api ^6.2.0 from pub.

  1. Merge RigCheck: signature node, loop animations, sheet dialogs, wrap, scaled frames and more CodelesslyAPI#46 and release it as 6.3.0.
  2. Raise this package's constraint to codelessly_api: ^6.3.0, merge, and release as codelessly_sdk 8.4.0.
  3. Codelessly/CodelesslyEditor#711 builds against both.

Known Issues

  • The version bump and constraint change are left to the release step, as in earlier releases.

🤖 Generated with Claude Code

https://claude.ai/code/session_011WedhUVWdWR5swqZYMuGfP

claude added 14 commits October 23, 2026 16:48
…up, pad

Actions could read list items but not build or change one: there was no
way to find an item's index, make a map, or copy a record with one field
changed. These let a setVariable update one item of a list variable, e.g.
set(defects, findIndex(defects, d => d.id == id), merge(d, obj(...))).
group() formats 412908 as 412,908.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011WedhUVWdWR5swqZYMuGfP
Gradient fills use the paint's tileMode and scale the gradient so that one
run is gradientExtent pixels long (the radius of a radial gradient), so a
short gradient with hard stops paints stripes on rectangles, frames and
anything else that builds its fills with buildFills.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PassiveSignatureTransformer draws a SignatureNode: background, border
(error border when its error binding is true), sign line with an x, the
placeholder while empty, and the strokes in round-cap ink smoothed with
quadratic curves. Drawing claims the pointer at once, so a scrolling
parent does not take a stroke. A finished stroke writes all strokes, as
[[[x, y], ...], ...], to the bound value variable (or the node value)
and fires changed; setting the variable to [] clears the pad.

Also fixes the isEmpty and isNotEmpty accessors of list variables, which
read the list as a map and gave null, so ${signature.isEmpty} works.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Nodes with a loopAnimation build inside a LoopAnimationBox, which repeats
a pulse, fade, scale or spin on its own ticker and disposes it with the
node. A `loopAnimationEnabled` condition or variable turns it on and off
without remounting the node, and under a disabled TickerMode (the
editor's design canvas) it holds still at its start.

Nodes with cursorEnter, cursorExit or hover reactions get a translucent
MouseRegion that runs them, so hover state can be kept with set variable
actions. Taps and regions behind still get through.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…sword fields

isEmpty on a text variable holding "" was false: the empty text reads as
null and isEmpty(null) returned false, so a form couldn't check that its
error messages were cleared before submitting.

A fixed-height text field expands, which leaves maxLines null, and only
single-line fields were obscured, so a PIN or password field in a fixed
box showed its text. An obscured field that isn't explicitly multi-line
now keeps one line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011WedhUVWdWR5swqZYMuGfP
showLayoutDialog pushes a LayoutPresentationRoute that presents the
canvas per ShowDialogAction.presentation:
- dialog: centered at its design size, now scaling and fading in
  (0.96 to 1, 320 ms easeOutCubic).
- bottomSheet: full width at the bottom, top corners radius 10, a 40x4
  grab handle 8 px from the top, as tall as the canvas (its content when
  it shrink-wraps) up to maxHeightFactor of the screen; slides up.
- sideSheet: full height on the right, sheetWidth (or the canvas's
  design width) wide, with a shadow; slides in from the right.
The barrier color, barrier dismissal, route params and Navigate back
results work as before. A new CanvasSizing.fillWidth sizes the bottom
sheet's canvas. What a show-dialog's canvas passes back on Navigate back
becomes the chain's result for the actions after it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011WedhUVWdWR5swqZYMuGfP
A text's ${...} preview on the design canvas (VariableSpan) read item
through the editor's scope resolver, which always picks the list's first
item, while the same row's fill, stroke and visible bindings read the
row's own item from IndexedItemProvider. On the render canvas each
preview row painted item 0's text with its own bindings, and
${= flags[item.id].note} showed the placeholder "Flags" in rows whose
own item had a flag but item 0 had none.

VariablePreview.textFor takes the IndexedItem of the build context; when
it carries data, item reads it. Lists built without item data keep the
resolver's first item.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ntent

The spacer transformer returned its own Expanded, and the row/column
passed it through untouched. The editor's render canvas wraps every node
in GeometryProbes (render objects), so the Expanded no longer sat directly
under the Flex: its flex was dropped and the spacer kept its 100 px design
size. The row/column now wraps the spacer like any other flexing child,
outside everything built for it, and the transformer only builds its box.

DesignCellGridDelegate turned a fixed cross-axis count into "as many
design-size cells as fit", with the design cell taken from the node box,
so a 100 wide grid from JSON got a dozen columns. A fixed count is now
SliverGridDelegateWithFixedCrossAxisCount: the count is kept and the
grid's width divided between cells.

GridDelegateProperties.shrinkWrapRows builds the grid as a list of rows
of Expanded cells in an IntrinsicHeight (IntrinsicWidth when horizontal),
each as tall as its tallest item; items that can't report intrinsics
keep their own size in a row as tall as the tallest.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011WedhUVWdWR5swqZYMuGfP
…p, scaled frames, parent size

- Text fields evaluate their errorText binding (formulas included); an
  empty message is no error, so the error border shows only while there is
  one. An error binding (bool) gives the error borders without a message.
  enabled, obscureText, readOnly, showCursor, maxLength, isCollapsed and
  isDense follow their bindings too.
- Formulas: reverse, substring (negative indexes count from the end),
  left, right and toMap(list, key, value). Lambdas take an optional index
  parameter, (item, i) => ..., in every list function. All documented in
  functionDocs.
- ConfirmAction.presentation: the built-in dialog and a dialogId canvas
  show as a dialog, bottom sheet or side sheet through
  LayoutPresentationRoute, like showDialog.
- Toasts draw an accentColor bar down the left edge and take iconColor,
  fontFamily and cornerRadius.
- A node disabled through its disabled binding, with no Disabled state of
  its own, fades to 40 %; it already ignored taps.
- RowColumnNode.wrap builds a Wrap with wrapSpacing and wrapRunSpacing.
  Spacers and children hidden at runtime are left out.
- FrameNode.contentFit lays the children out at the frame's design size
  and scales them into the frame (ScaledContent).
- The parent variable: ${parent.width} and ${parent.height} are the size
  a node's parent gives it (ParentSizeScope), so a binding can switch a
  layout on its container's width. Nodes that read it are built in a
  LayoutBuilder and count as blocking intrinsics.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011WedhUVWdWR5swqZYMuGfP
Navigate, replace and showDialog find a canvas in any published layout,
whatever page it was designed on; a layout group on another page picks
the canvas for the width; app variables are shared across pages.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011WedhUVWdWR5swqZYMuGfP
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011WedhUVWdWR5swqZYMuGfP
Where master released the same feature, master's version stays and this
branch's duplicate goes:
- wrap: master's CodelesslyWrap (spacing, runSpacing); this branch's Wrap
  and wrapSpacing/wrapRunSpacing are gone.
- repeating gradients: master's gradientRepeatEvery; tileMode,
  gradientExtent and GradientExtentTransform are gone.
- formulas: master's object, merge, set, setAt, append, take, skip,
  reverse, indexOf (with a predicate), substring, padLeft and thousands
  replace findIndex, group and pad; this branch adds left, right, toMap and
  the lambda index (indexOf passes it too).
- text fields: master's bindable flags, one-line obscured fields and empty
  errorText; this branch adds an errorText bound in variables and a boolean
  error binding that shows the error borders.
- dialogs: master's presentCanvas (dialog, bottom sheet, adaptive,
  alignment); this branch adds side sheets (LayoutPresentationRoute), and
  drag handle and height cap options for bottom sheets, off by default so
  saved sheets look as before. A bottom sheet now hugs its canvas, so a tap
  above it dismisses it.
- list item text on the design canvas: master's fix, falling back to the
  first item when the list item carries no data.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011WedhUVWdWR5swqZYMuGfP
Measured with IntrinsicHeight / IntrinsicWidth when a non-spacer child
fills the cross axis and another non-spacer child gives it a size, as
the editor's solver lays it out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011WedhUVWdWR5swqZYMuGfP
@BuildCodelessly
BuildCodelessly merged commit 25fbb54 into master Oct 24, 2026
0 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants