Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ All notable changes to this project will be documented in this file.

## [Unreleased]

## [0.0.24] - 2026-09-29

### BREAKING

- **`MagicController.onRefreshUI` is removed; `MagicPerfHooks.sink` replaces it.** The single controller hook saw that a controller repainted but not why, and nothing else magic does at runtime. There is no alias: tooling that assigned `onRefreshUI` assigns `MagicPerfHooks.sink` and reads `ControllerNotified.controller` instead. A published `magic_devtools` that still assigns the old static does not compile against this release. (`lib/src/http/magic_controller.dart`, `lib/src/perf/magic_perf_hooks.dart`)
Expand All @@ -16,6 +18,7 @@ All notable changes to this project will be documented in this file.

### Changed

- **Every sibling floor names this batch's release, and the devtools install pins move with it.** `fluttersdk_wind` moves `^1.7.0` to `^1.8.0` and `fluttersdk_artisan` `^0.0.16` to `^0.0.17`. The old ranges already admitted both, so a fresh `pub get` resolves nothing differently. `magic:install --with-devtools`, the `install.yaml` post-install message and `doc/packages/magic-devtools.md` now write `magic_devtools ^0.0.8`, `fluttersdk_dusk ^0.0.17` and `fluttersdk_telescope ^0.0.9`: magic_devtools 0.0.7 assigns the removed `onRefreshUI` and does not compile against this release. The plugin references carry the batch's plugin versions (notifications 0.3.6, deeplink 0.1.5, social_auth 0.0.7, payments 0.0.7, sentry 0.0.2, devtools 0.0.8, starter 0.0.38), and `plugin-devtools.md` documents 0.0.8's `!kReleaseMode` guard. (`pubspec.yaml`, `lib/src/cli/commands/magic_install_command.dart`, `install.yaml`, `doc/packages/magic-devtools.md`, `skills/magic-framework/references/`)
- **A `MagicStatefulView` under an opaque route defers its rebuilds until it is uncovered.** `MagicStatefulViewState` called `setState` on every controller notification, so a page covered by a pushed route rebuilt for frames nobody saw: in uptizm, a manual check on a monitor detail page notified its controller six times and rebuilt the ~50-row list underneath each time. The view now reads its ambient `TickerMode` through `TickerMode.getValuesNotifier` (no dependency, so covering or uncovering an idle view costs no build) and, while tickers are disabled, only marks itself stale; when they come back it rebuilds once, and not at all if nothing notified (a parent or dependency rebuild while covered also settles it). Flutter disables tickers for content it does not paint: a route under an opaque one, an inactive go_router shell branch, an offstage `IndexedStack` child. A page under a dialog, bottom sheet or popover, and a view outside any `Navigator`, rebuild on every notification as before. The contract is the ticker mode, not visibility: an app that wraps a painted view in `TickerMode(enabled: false)` to freeze its animations freezes its controller rebuilds too, until the tickers come back. `ControllerNotified` still fires per notification; a deferred rebuild simply does not happen, so build counts downstream fall honestly. A `MagicSelector` or `ListenableBuilder` inside a covered view listens for itself and is not deferred. (`lib/src/ui/magic_view.dart`, `test/ui/magic_view_covered_route_test.dart`, `doc/basics/views.md`, `skills/magic-framework/references/controllers-views.md`)
- **`Repository` notifies only when a write actually changed the cache.** `upsertFromList`, `upsertFromShow` and `patch` notified on every call, so a `check.recorded` broadcast restating the cached reading, or a `refresh` answering the row already held, rebuilt every `RepositoryQuery` listener over the repository (in uptizm, all ~50 monitor rows per notify). Each now compares the row's raw stored attributes before and after the merge, deeply and with map keys in any order, and notifies only for a new row or a changed attribute; `evict` already skipped an absent id. `upsertFromShow` still stores the answered instance, the `RepositoryUpserted` perf event still fires per merge, and a `RepositoryQuery` reload still notifies when its page lands. A caller that repainted on the repository's notify to clear its own loading flag after an unchanged answer must call its own `refreshUI()`. The deep comparison `BaseGuard` added for `AuthRestored.changed` moved to an internal, unexported helper both now share. (`lib/src/data/repository.dart`, `lib/src/support/same_value.dart`, `lib/src/auth/guards/base_guard.dart`, `doc/eloquent/repositories.md`, `skills/magic-framework/SKILL.md`)
- **`Model.casts` defaults to a const empty map, and `make:model` writes `get casts => const {};`.** `getAttribute` reads `casts` on every call, so a non-const map literal allocated a fresh map on every attribute read. Every `casts` example in `doc/` and the skill is now a const literal too; cast semantics are unchanged. A model generated before this change keeps its non-const literal until it is edited. (`lib/src/database/eloquent/model.dart`, `assets/stubs/model.stub`, `doc/`, `skills/magic-framework/`)
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

Laravel-inspired Flutter framework: IoC container, 18 facades, Eloquent-style ORM, service providers, routing, auth, validation, broadcasting. UI is `fluttersdk_wind`; CLI + scaffolding run on `fluttersdk_artisan`.

**Stack:** Dart >=3.11.0 · Flutter >=3.41.0. Runtime deps: `fluttersdk_wind ^1.7.0`, `fluttersdk_artisan ^0.0.16`, `dio`, `go_router`, `sqlite3`, `flutter_secure_storage`, `encrypt`. No `mockito` (mock via contract inheritance), no code generation.
**Stack:** Dart >=3.11.0 · Flutter >=3.41.0. Runtime deps: `fluttersdk_wind ^1.8.0`, `fluttersdk_artisan ^0.0.17`, `dio`, `go_router`, `sqlite3`, `flutter_secure_storage`, `encrypt`. No `mockito` (mock via contract inheritance), no code generation.
**Branch:** `master` is the active **0.0.x** development line — what pub.dev resolves. Pre-1.0: breaking changes are allowed; `1.0.0` is the future stable milestone. Direct pushes blocked; everything lands via PR.

The dev-tooling adapters (`MagicDuskIntegration`, `MagicTelescopeIntegration`) live in the sibling `magic_devtools` package; magic core has zero dependency on `fluttersdk_dusk` / `fluttersdk_telescope`. CLI/scaffolding internals: `CLAUDE.local.md`. LLM-agent surface: `skills/magic-framework/SKILL.md`. Consumer overview: `README.md`.
Expand Down
6 changes: 3 additions & 3 deletions doc/packages/magic-devtools.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,9 @@ Add `magic_devtools` to your `pubspec.yaml`, plus whichever tooling packages you

```yaml
dependencies:
magic_devtools: ^0.0.7
fluttersdk_dusk: ^0.0.16 # add if you use dusk
fluttersdk_telescope: ^0.0.7 # add if you use telescope
magic_devtools: ^0.0.8
fluttersdk_dusk: ^0.0.17 # add if you use dusk
fluttersdk_telescope: ^0.0.9 # add if you use telescope
```

`magic_devtools` depends on `magic`, `fluttersdk_dusk`, and `fluttersdk_telescope` directly, so the tooling packages resolve through `magic_devtools` rather than transitively through `magic` itself.
Expand Down
14 changes: 7 additions & 7 deletions example/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -385,26 +385,26 @@ packages:
dependency: "direct main"
description:
name: fluttersdk_artisan
sha256: "1b905fa27f746120829fee8970412af8f21b8fb7005f4886da5f2e84960db0bc"
sha256: "812ed001ecd0b9c459822560847cf290454f8bd7ab5d7bc6476fa8a1aa87d853"
url: "https://pub.dev"
source: hosted
version: "0.0.16"
version: "0.0.17"
fluttersdk_wind:
dependency: transitive
description:
name: fluttersdk_wind
sha256: "25f6aa92e8c052c6c76fc0299a15222d42341b01f0bdd346249b3c3fbdc0e301"
sha256: "11c2241412aa6e4f2a8a83af1292555ac5078f0ef1113964df8e75fbaf9ede02"
url: "https://pub.dev"
source: hosted
version: "1.7.0"
version: "1.8.0"
fluttersdk_wind_diagnostics_contracts:
dependency: transitive
description:
name: fluttersdk_wind_diagnostics_contracts
sha256: "3c302f7504c9e8037072b4d2be2c266fb5488aa907eda1ce9321f1ee8556c972"
sha256: "909f743ed9f2b7c67ae68ae8b7af8dd997cd1f309167a9c1a6c1e283d18f19ce"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
version: "1.2.0"
glob:
dependency: transitive
description:
Expand Down Expand Up @@ -595,7 +595,7 @@ packages:
path: ".."
relative: true
source: path
version: "0.0.23"
version: "0.0.24"
matcher:
dependency: transitive
description:
Expand Down
2 changes: 1 addition & 1 deletion example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ dependencies:
cupertino_icons: ^1.0.9
magic:
path: ..
version: 0.0.23
version: 0.0.24
fluttersdk_artisan: ^0.0.14

dev_dependencies:
Expand Down
6 changes: 3 additions & 3 deletions install.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,9 @@ post_install:
wired into lib/main.dart under kDebugMode (release builds tree-shake them),
so add them to dependencies, not dev_dependencies (lib/ imports them):

magic_devtools: ^0.0.7
fluttersdk_dusk: ^0.0.16
fluttersdk_telescope: ^0.0.7
magic_devtools: ^0.0.8
fluttersdk_dusk: ^0.0.17
fluttersdk_telescope: ^0.0.9

Then:

Expand Down
6 changes: 3 additions & 3 deletions lib/src/cli/commands/magic_install_command.dart
Original file line number Diff line number Diff line change
Expand Up @@ -990,9 +990,9 @@ class MagicInstallCommand extends ArtisanInstallCommand {
/// `lib/main.dart` imports them; the `kDebugMode` gate tree-shakes them from
/// release builds.
static const Map<String, String> _devtoolsDependencies = <String, String>{
'magic_devtools': '^0.0.7',
'fluttersdk_dusk': '^0.0.16',
'fluttersdk_telescope': '^0.0.7',
'magic_devtools': '^0.0.8',
'fluttersdk_dusk': '^0.0.17',
'fluttersdk_telescope': '^0.0.9',
};

/// Injects the debug-trio runtime wiring into a generated `lib/main.dart`
Expand Down
9 changes: 5 additions & 4 deletions pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: magic
description: "A Laravel-inspired Flutter framework with Eloquent ORM, routing, and MVC architecture."
version: 0.0.23
version: 0.0.24
homepage: https://magic.fluttersdk.com
repository: https://github.com/fluttersdk/magic
issue_tracker: https://github.com/fluttersdk/magic/issues
Expand Down Expand Up @@ -29,8 +29,9 @@ dependencies:
# `bg-surface` alias an app actually paints its canvas with. On 1.6.0 that is
# #FFFFFF over #F9FAFB in light and #111827 over #07090C in dark, on every
# screen, and the dark one is not subtle. Nothing fails to compile below it,
# which is exactly why the floor has to say so.
fluttersdk_wind: ^1.7.0
# which is exactly why the floor has to say so. The 2026-09-29 batch moves
# it to 1.8.0, the release this version is verified against.
fluttersdk_wind: ^1.8.0
meta: ^1.16.0
more: ^4.7.0
dio: ^5.9.0
Expand All @@ -56,7 +57,7 @@ dependencies:
faker: ^2.2.0
web_socket_channel: ^3.0.0
yaml: ^3.1.3
fluttersdk_artisan: ^0.0.16
fluttersdk_artisan: ^0.0.17
flutter_test:
sdk: flutter

Expand Down
4 changes: 2 additions & 2 deletions skills/magic-framework/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
name: magic-framework
description: "Write correct, idiomatic code in a Flutter app that depends on the `magic` framework (Laravel-inspired: IoC container, 18 facades, Eloquent-style ORM, service providers, reactive controllers, GoRouter routing, validation, auth, broadcasting, MagicAction writes, Repository row caches, SessionScope tenant resets). Use whenever code imports `package:magic/magic.dart` or `package:magic/testing.dart`, or the work touches Magic.init, MagicApp, a facade (Auth/Http/Cache/DB/Echo/Event/Gate/Config/Lang/Launch/Log/Pick/MagicRoute/Schema/Session/Storage/Vault/Crypt), a Model, MagicController, a MagicView, MagicFormData, FormRequest, MagicAction, MagicFormObject, Repository, SessionScope, BroadcastListeners, a ServiceProvider, a migration, or the artisan make:* CLI. UI styling is Wind (separate wind-ui skill). Do NOT use for plain Flutter or Wind-only work with no magic import."
when_to_use: "Use proactively when editing or scaffolding a magic app: Magic.init / a facade / a Model / a MagicController or MagicView / a form (MagicFormData, FormRequest, Validator, MagicFormObject) / a write (MagicAction, RunsActions) / a resource cache (Repository, RepositoryQuery) / a tenant boundary (SessionScope, SessionScoped) / a shared realtime channel (BroadcastListeners, ListensToBroadcasts) / a ServiceProvider / a route or MagicMiddleware / a migration / MagicStateMixin + RxStatus + fetchList / Session flash + old() + trans() / testing with MagicTest + Http.fake/Auth.fake / the artisan make:* CLI / the magic_deeplink, magic_notifications, magic_social_auth, magic_starter, magic_payments, magic_devtools, or magic_sentry plugins. Trigger even when the user does not say the word 'magic'. Do NOT trigger for plain Flutter or Wind-only UI with no package:magic import."
version: 0.1.56
version: 0.1.57
---

<!-- magic 0.0.23 | Skill v0.1.56 (2026-09-29). API surface verified against lib/src. -->
<!-- magic 0.0.24 | Skill v0.1.57 (2026-09-29). API surface verified against lib/src. -->

# Magic Framework

Expand Down
2 changes: 1 addition & 1 deletion skills/magic-framework/references/plugin-deeplink.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<!-- magic_deeplink v0.1.4 | Updated: 2026-09-27 -->
<!-- magic_deeplink v0.1.5 | Updated: 2026-09-29 -->

# magic_deeplink Plugin

Expand Down
28 changes: 14 additions & 14 deletions skills/magic-framework/references/plugin-devtools.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<!-- magic_devtools v0.0.7 | Updated: 2026-09-27 -->
<!-- magic_devtools v0.0.8 | Updated: 2026-09-29 -->

# magic_devtools Plugin

Expand All @@ -14,20 +14,20 @@ The one-step path, from a project that already has magic installed:
dart run magic:artisan magic:install --with-devtools
```

That adds the three packages to `dependencies` and injects the `kDebugMode` blocks into `lib/main.dart`. It is idempotent, so a re-run never duplicates the wiring.
That adds the three packages to `dependencies` and injects the guarded blocks into `lib/main.dart`. It is idempotent, so a re-run never duplicates the wiring. The installer still writes `kDebugMode`; from 0.0.8 the package documents `!kReleaseMode`, so a profile build (the only honest build to measure) carries the tooling. Change the two guards by hand when you measure performance.

The manual path, when the app is already installed and only the tooling is being added:

```yaml
dependencies:
magic_devtools: ^0.0.7
fluttersdk_dusk: ^0.0.16 # add if you use dusk
fluttersdk_telescope: ^0.0.7 # add if you use telescope
magic_devtools: ^0.0.8
fluttersdk_dusk: ^0.0.17 # add if you use dusk
fluttersdk_telescope: ^0.0.9 # add if you use telescope
```

`magic_devtools` 0.0.7 declares `fluttersdk_dusk ^0.0.16` and `fluttersdk_telescope ^0.0.7`, beside `magic ^0.0.22` and `fluttersdk_wind ^1.7.0`: the newest of each sibling at that release, so the lines above match its own floors. The perf data path alone needs less, and those minimums are why the floors matter at all: `perf_readers.dart` (dusk 0.0.12), `FramePerfWatcher` / `TelescopeStore.recentFramePerf` (telescope 0.0.5), `MagicPerfHooks.sink` (magic, floor moves to the release after 0.0.7 that ships it; no number pinned here) and `WindPerfCounters` (wind 1.5.0). A caret range resolves to the newest, so a fresh graph always worked; an app whose own constraints hold one sibling back gets a satisfiable graph that then fails on undefined symbols.
`magic_devtools` 0.0.8 declares `fluttersdk_dusk ^0.0.17` and `fluttersdk_telescope ^0.0.9`, beside `magic ^0.0.24` and `fluttersdk_wind ^1.8.0`: the newest of each sibling at that release, so the lines above match its own floors. Three of them are real requirements: `MagicPerfHooks.sink` and the request ids arrive in magic 0.0.24, `PerfMode` and the interaction readers in dusk 0.0.17, and `TelescopeRedaction` plus the record link fields in telescope 0.0.9. The older minimums are `perf_readers.dart` (dusk 0.0.12), `FramePerfWatcher` / `TelescopeStore.recentFramePerf` (telescope 0.0.5) and `WindPerfCounters` (wind 1.5.0). magic 0.0.24 removed `MagicController.onRefreshUI`, which 0.0.7 assigned, so 0.0.7 does not compile against it: move both together. A caret range resolves to the newest, so a fresh graph always worked; an app whose own constraints hold one sibling back gets a satisfiable graph that then fails on undefined symbols.

These are regular `dependencies`, not `dev_dependencies`: `lib/main.dart` imports them, so a `dev_dependencies` entry trips the `depend_on_referenced_packages` lint. The `kDebugMode` guard is what keeps them out of a release build, not the dependency section.
These are regular `dependencies`, not `dev_dependencies`: `lib/main.dart` imports them, so a `dev_dependencies` entry trips the `depend_on_referenced_packages` lint. The `!kReleaseMode` guard is what keeps them out of a release build, not the dependency section.

Then wire the CLI side of each tool:

Expand All @@ -45,11 +45,11 @@ dart run magic:artisan mcp:install # surfaces the dusk_* / telescope_*
void main() async {
WidgetsFlutterBinding.ensureInitialized();

if (kDebugMode) MagicDevtools.installPre();
if (!kReleaseMode) MagicDevtools.installPre();

await Magic.init(configFactories: [...]);

if (kDebugMode) MagicDevtools.installPost();
if (!kReleaseMode) MagicDevtools.installPost();

runApp(const MyApp());
}
Expand All @@ -63,7 +63,7 @@ void main() async {
Both halves are idempotent, so a second call in the same isolate is safe. `installPre()` is NOT safe to call LATE, though, and that changed in 0.0.4: the perf integration registers a `NavigatorObserver`, and `MagicRouter.addObserver` throws a `StateError` once the router has been built. A host that installs behind a lazy debug toggle after `runApp` used to get harmless no-ops and now crashes. The throw is deliberate: a silently unregistered observer would produce a performance report with no route transitions and nothing to explain their absence.

> [!WARNING]
> Keep `kDebugMode` at the CALL SITE. Moving the guard inside `installPre` / `installPost` makes the call live in release, which defeats the tree-shake and pulls dusk plus telescope into the production bundle. That tree-shake is the entire reason this package exists separately from magic core.
> Keep `!kReleaseMode` at the CALL SITE (`kDebugMode` before 0.0.8, which left a profile build without the perf path). Moving the guard inside `installPre` / `installPost` makes the call live in release, which defeats the tree-shake and pulls dusk plus telescope into the production bundle. That tree-shake is the entire reason this package exists separately from magic core.

## The four import barrels

Expand All @@ -77,9 +77,9 @@ Both halves are idempotent, so a second call in the same isolate is safe. `insta
Single-tool wiring keeps the same pre/post split:

```dart
if (kDebugMode) DuskPlugin.install();
if (!kReleaseMode) DuskPlugin.install();
await Magic.init(configFactories: [...]);
if (kDebugMode) MagicDuskIntegration.install();
if (!kReleaseMode) MagicDuskIntegration.install();
```

## MagicPerfIntegration: the performance data path (0.0.4+)
Expand Down Expand Up @@ -139,12 +139,12 @@ Two rules decide whether the catalog appears at all:

| Mistake | Consequence | Fix |
|:--------|:------------|:----|
| `kDebugMode` moved inside `installPre` / `installPost` | Dusk and telescope ship in the release bundle | Guard at the call site |
| `!kReleaseMode` moved inside `installPre` / `installPost` | Dusk and telescope ship in the release bundle | Guard at the call site |
| `installPost()` called before `Magic.init()` | Enrichers and the HTTP adapter cannot resolve through the container | Keep the two calls on either side of `init` |
| `installPre()` behind a lazy debug toggle, after `runApp` | `StateError` from `MagicRouter.addObserver`: the router locks its observers once built (0.0.4+) | Call it at boot, before `Magic.init()`, and nowhere else |
| A perf report full of zeros | A pointer was never assigned; every dusk default is a structurally-complete no-op, so it reports zeros instead of failing | Check `MagicDevtools.installPre()` actually ran (it is what installs `MagicPerfIntegration`) |
| `MagicPreview.registerRoutes()` from anywhere but a provider `boot()` | Router already locked, `/preview` missing or `StateError` | Move it into `boot()` |
| Preview entries held in a top-level `const` list | Widget references survive the release tree-shake (dart-lang/sdk#33920) | Return them from `previewEntries()`, which is what the codegen already does |
| `magic_devtools` in `dev_dependencies` | `depend_on_referenced_packages` lint | Regular `dependencies`, guarded by `kDebugMode` |
| `magic_devtools` in `dev_dependencies` | `depend_on_referenced_packages` lint | Regular `dependencies`, guarded by `!kReleaseMode` |

For the tool surfaces themselves (the `dusk_*` and `telescope_*` MCP tools, the CLI verbs, the ring buffers), load the `fluttersdk-dusk` and `fluttersdk-telescope` skills. This file covers only the Magic adapter layer.
Loading
Loading