Skip to content

1.1.0: thinking-orb indicators, controller API, and a full correctness/docs audit - #9

Merged
itsarvinddev merged 4 commits into
masterfrom
feat/v1.1.0-audit-orbs
Sep 13, 2026
Merged

itsarvinddev merged 4 commits into
masterfrom
feat/v1.1.0-audit-orbs

Conversation

@itsarvinddev

Copy link
Copy Markdown
Owner

Full audit and update of the package, plus the headline new feature.

Thinking orbs (new)

Nine animated "thought orb" loading indicators for AI and agent interfaces.

The nine thinking-orb states

lib/src/orbs/ is a Dart port of the MIT-licensed
thinking-orbs engine by Jakub Antalik (credited in
the README alongside Bright Sunu's Flutter port).
Filled circles over a rotated, z-sorted 3D point field — no shaders, blurs or image assets, so it
renders identically on every Flutter target.

  • Two separately tuned size tiers (inline text below 40px, chat avatar at or above), not one scaled up.
  • One shared clock, so every orb on screen stays in step regardless of when it mounted.
  • TickerMode-aware, pauses on request, and paints a representative still frame under reduced motion.
  • Usable standalone as ThinkingOrb, in any button family via LoadingIndicator.orb(), or app-wide
    through LoadingButtonThemeData.indicator.

Verified numerically against upstream's published golden vectors — 90 tests over 11,288 dots and
341 lines at 1e-4 tolerance, so this is a port rather than an approximation.

Fixed

  • *AutoLoadingButton stuck forever. A throwing onPressed left the button a permanently
    disabled spinner, hung every awaiter, and escaped as an unhandled async error. Affected all five
    families. Now clears on both paths; tap-path errors go to FlutterError and doPress() callers
    receive the error.
  • Double submit. The guard read a state that was not written until ~200ms after the tap, so a
    fast double tap ran onPressed twice — a charged-twice bug on a payment button. Replaced with a
    synchronous latch.
  • onPressed no longer waits on haptics and the press animation before running (which also made
    every LoadingButton untestable).
  • The uncancellable Future.delayed reset is now a Timer cancelled on dispose.
  • ArgonButton/ArgonTimerButton no longer force-unwrap a nullable onTap or loader, and their
    escaping startLoading/stopLoading closures are mounted-guarded.
  • Removed the duplicate Semantics button node that shadowed the real button's accessible name.
  • Dropped rxdart: zero runtime dependencies.

Added (all additive)

LoadingButtonController + LoadingButtonValue, LoadingButtonTheme/LoadingButtonThemeData
(deprecating the global LoadingButtonConfig singleton), LoadingButtonSizing (intrinsic / expand /
fixed — the old fixed 200x50 stays the default), determinate progress, LoadingIndicator,
Material 3 colour resolution, debounce/cooldown, buttonStyle, tooltip, focusNode,
enabled, and onFailure(Object, StackTrace) deprecating onError.

Breaking

Only the SDK floor: Dart >=3.4.0 / Flutter >=3.22.0. This is a correction, not a removal — the
package has used WidgetStatesController since 1.0.0, so the declared flutter: ">=1.17.0" had been
fiction for several releases and users below 3.22 were getting a confusing compile error instead of a
clean pub resolution message. One small typing break is disclosed in MIGRATION.md
(ArgonTimerButton.loader).

Docs

README, MIGRATION and CHANGELOG rewritten. The previous README was actively misleading: every
LoadingButton sample failed to compile, and 10 of 13 rows in its main properties table documented
parameters removed before 1.0.0. MIGRATION told readers to depend on loading_button — a different
package.

Verification

  • 347 tests, up from void main() {}.
  • flutter analyze --fatal-infos clean under a strengthened lint set (strict-casts / strict-inference
    / strict-raw-types).
  • pana 160/160, WASM-ready, all six platforms.
  • flutter pub publish --dry-run: 0 warnings.
  • Example app rebuilt as a five-page gallery with no third-party dependencies.
  • GitHub Actions CI added (format, analyze, test, example build, pana, publish dry-run; stable + beta).

Work was audited across eight dimensions and then adversarially reviewed across four lenses; the 25
confirmed review findings are fixed in 656329b, each with a regression test verified to fail without
its fix.

Merges #8 (thanks @alr2413).

🤖 Generated with Claude Code

itsarvinddev and others added 2 commits September 12, 2026 16:48
…ness pass

Adds a family of nine animated "thinking orb" loading indicators for AI and
agent interfaces, a controller/theme/sizing API for LoadingButton, and fixes
the correctness, accessibility and packaging defects found by a full audit.

Thinking orbs
- lib/src/orbs/ is a Dart port of the MIT-licensed thinking-orbs engine by
  Jakub Antalik. Nine states, two hand-tuned size tiers, one shared clock so
  several orbs stay in step, TickerMode-aware, and a representative still
  frame under reduced motion. Filled circles only: no shaders or assets.
- Verified against upstream's published golden vectors dot by dot --
  90 tests over 11,288 dots and 341 lines at 1e-4 tolerance.
- Usable standalone as ThinkingOrb, or in any button family via
  LoadingIndicator.orb(), or app-wide via LoadingButtonThemeData.indicator.

Correctness
- AutoLoadingButtonState: a throwing callback left the button a permanently
  disabled spinner, hung every awaiter, and escaped as an unhandled async
  error. The loading flag is now cleared on both paths; tap-path errors go to
  FlutterError and awaited doPress() callers receive the error themselves.
- LoadingButton: the double-tap guard read a state that was not written until
  ~200ms after the tap, so a fast double tap ran onPressed twice. Replaced
  with a synchronous latch taken before the first await.
- onPressed no longer waits on haptics and the press animation before running.
- The uncancellable Future.delayed reset is now a cancelled-on-dispose Timer.
- ArgonButton/ArgonTimerButton no longer force-unwrap a nullable onTap or
  loader, and their escaping start/stopLoading closures are mounted-guarded.
- Dropped rxdart: the package now has zero runtime dependencies.

API (all additive; nothing removed)
- LoadingButtonController + LoadingButtonValue for external control.
- LoadingButtonTheme / LoadingButtonThemeData, deprecating LoadingButtonConfig.
- LoadingButtonSizing: intrinsic, expand and fixed alongside the legacy box.
- Determinate progress, LoadingIndicator, Material 3 colour resolution,
  debounce/cooldown, buttonStyle, tooltip, focusNode and enabled.
- onFailure(Object, StackTrace) deprecates onError.

Accessibility
- Removed the duplicate Semantics button node that shadowed the real button's
  label; state changes now ride on the child as a live region.

Packaging and docs
- environment corrected to Dart >=3.4.0 / Flutter >=3.22.0. The declared
  Flutter 1.17 floor had been fiction since WidgetStatesController was adopted.
- Added topics, platforms, .pubignore, a stronger lint set and GitHub Actions
  CI (analyze, test, format, pana, publish --dry-run).
- README, MIGRATION and CHANGELOG rewritten: every previous README sample
  failed to compile and the property tables documented parameters removed
  before 1.0.0.
- Example app rebuilt as a five-page gallery with no third-party dependencies.
- Test suite grew from `void main() {}` to 336 tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A four-lens review of the release commit produced 25 confirmed findings (4
refuted). None were crashes, but several were wrong behaviour or false
documentation, and eight were in code added by this release.

State machine
- A throwing onSuccess was caught by the same handler as onPressed, so a
  successful run reported itself as ActionState.error and called onFailure.
  The guarded region is now the user's action alone; onSuccess, onFailure,
  onError and onStateChanged run outside it and route their own throws to
  FlutterError instead of corrupting the state machine.
- `enabled` no longer writes ActionState.disabled into the value. A disabled
  button sharing a controller was permanently disabling its enabled siblings,
  and the controller-less cooldown timer then forced idle regardless of
  `enabled`, leaving a button painted enabled that ignored every tap. The
  disabled phase is now layered on at render time, so the painted state can no
  longer disagree with interactivity.
- initState no longer writes to the notifier. Mounting with `enabled: false`
  dispatched onStateChanged mid-build, and a listener that marked an ancestor
  dirty tripped a framework assertion.
- onStateChanged and the reset window now react to real transitions only.
  Progress ticks share the value, so a determinate upload fired a transition
  per tick and pushed its own success window back forever.

Controller
- dispose() no longer clears its bindings, which made attachmentCount report
  zero while buttons were still bound.
- setActionState preserves progress unless asked to clear it.
- press() documents what it actually does: with several buttons attached the
  shared value means exactly one run happens.

Orbs
- The shared clock re-anchors when the frame clock moves backwards, instead of
  emitting negative, test-order-dependent time.
- An orb mounted into a running app joins the clock on its first frame rather
  than painting one frame at t = 0.
- A translucent tint keeps its alpha. Done with Color.lerp because the channel
  accessors differ across the supported range: `.a` needs Flutter 3.27 and
  `.alpha` is deprecated in current stable.

AutoLoadingButton
- One FlutterError report per failed run, not one per tap that joined it.

Docs
- Corrected the IconTheme inheritance claim (it does not hold for
  LoadingButton), the indicator precedence chain (loadingText replaces the
  indicator rather than labelling it), the package-wide framing of the
  accessibility section (it was LoadingButton-only, and the other families lose
  their accessible name while busy), the builder-key hazard, a phantom
  IconLoadingButton in MIGRATION, and the pubspec claim of no platform channels.
- Disclosed the one break the release had not admitted: ArgonTimerButton.loader
  narrowed from Function(int) to Widget Function(int).
- coverage/ is excluded from the published archive and from git.

Adds test/regression_v110_test.dart, locking down each fix; verified that each
test fails without it. 347 tests pass, analyze is clean at --fatal-infos, and
pana scores 160/160.

Also adds doc/thinking-orbs.png, a contact sheet of the nine states at both
tuned sizes, generated by tool/generate_orb_sheet.dart and used by the README
and as a pub.dev screenshot.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings September 12, 2026 12:25

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

itsarvinddev and others added 2 commits September 12, 2026 17:59
Both passed locally and failed on GitHub Actions, for environment reasons
rather than defects in the package.

- format: `dart format` chooses its style from the package's RESOLVED language
  version, so running it before `pub get` made resolution fail, fell back to
  the SDK's own version, and reformatted the whole tree in the newer style.
  Resolve the root and the example first.
- pana: screenshot validation shells out to libwebp (webpinfo, cwebp, dwebp,
  gif2webp, webpmux). pub.dev's analysis environment has those and a bare
  runner does not, so all 8 screenshot checks failed with "No such file or
  directory" and cost the 10 example/screenshot points — 150/160 on CI against
  160/160 with libwebp present. Install it before scoring.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Screenshots
- Replace generated-image.png, a 2.5 MB promotional graphic rather than a
  screenshot, whose code (`LoadingButton(icon:, onTap:)`, an
  `AutoLoadingButton` class) matched no release of this package.
- Add real renders of the real widgets, produced by
  tool/generate_screenshots.dart and packed by tool/pack_animations.sh:
  an animated hero (spinner, thinking orb and determinate upload running,
  then succeeding), an animated strip of the nine orbs, a button-state matrix
  in light and dark, and a sheet of the button families.
- Rendering goes through the golden-file pipeline because
  RenderRepaintBoundary.toImage rasterises once per test process and then
  hangs. Scenes are scaled at paint time because that pipeline always
  captures at 1x.
- Only the three pubspec screenshots ship in the archive; the README loads the
  rest from GitHub. The archive dropped from 2 MB to 879 KB.

Library
- The default loading, success and error widgets inherit the button's
  foreground colour instead of hard-coding white, which was invisible on a
  light container. Rendering under the legacy colour strategy is unchanged.

Docs
- README restructured: hero first, a table of contents, an at-a-glance family
  comparison, and screenshots placed next to the sections they illustrate.
  Existing content is about 9% shorter with no factual loss.
- New "LLM and AI assistant support" section with a pasteable context block,
  six task prompts and a table of common mistakes.
- New llms.txt, a machine-readable API digest built from lib/. It lists the
  0.0.x parameters removed in 1.0.0 that assistants still emit, each with its
  replacement.
- Every README Dart sample and every llms.txt signature was compiled, and five
  prompts were followed literally and compiled.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@itsarvinddev
itsarvinddev merged commit e393b78 into master Sep 13, 2026
20 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