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
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Security

- **The HTTP adapter no longer hands telescope a login's password or token.** It stringified request and response bodies with `toString()`, and a Dart `Map` prints as `{email: a@b.test, password: hunter2}`, which telescope's JSON masking cannot read, so a login body and a Sanctum login answer reached the agent-facing buffer in the clear. `onRequest`, `onResponse` and `onError` now mask the body with telescope's hidden request or response parameters before truncating it (`TelescopeRedaction.redactParameters` for a Dart structure, `redactBody` for a JSON string, which would otherwise stop parsing once cut at 8 KB); the masked copy is what gets recorded, and the request object the driver sends on is never touched. `MagicTelescopeIntegration.install()` also hides the header magic's `AuthInterceptor` writes the token under, read from `auth.token.header` (default `Authorization`), so a renamed header is masked too, and it does so on every call, since a telescope store reset drops the addition. Needs the telescope release that ships `TelescopeRedaction`. (`lib/src/telescope_integration.dart`)

### Changed

- **The floors name the releases this work needs.** `magic` moves `^0.0.22` to `^0.0.24` (`MagicPerfHooks`, the request ids, `AuthRestored.changed`, and the removal of `onRefreshUI`), `fluttersdk_dusk` `^0.0.16` to `^0.0.17` (`PerfMode` and the interaction readers), `fluttersdk_telescope` `^0.0.7` to `^0.0.9` (`TelescopeRedaction` and the record link fields) and `fluttersdk_wind` `^1.7.0` to `^1.8.0` (the size-only `MediaQuery` read the `mediaQuerySize` insight describes). Each is a real requirement: this release does not compile, or describes the wrong behaviour, below it. The README install snippet quotes the new dusk and telescope floors. (`pubspec.yaml`, `README.md`)
- **BREAKING: the perf path reads magic through `MagicPerfHooks.sink`, and `MagicController.onRefreshUI` is gone.** `MagicPerfIntegration` no longer hooks a single notify site: its session begin hook installs the sink for an attribution session only (a timing session, and an app between sessions, allocate no event and leave wind counting off), and the end hook removes it. `perfExtrasReader` returns dusk's documented key set: `controllerNotifies`, `notifyCauses`, `queryReloads`, `actions`, `events`, `casts`, `timerTicks`, `broadcasts` and `routeTransitions`. Needs the magic, dusk, telescope and wind releases that ship `MagicPerfHooks`, `PerfMode`, record links and per-type wind counters. (`lib/src/perf_integration.dart`)
- **HTTP records pair by request id.** The telescope interceptor matches each response or error to its request through `MagicRequest.id` / `MagicResponse.id` / `MagicError.id`, so two requests completing out of order keep their own URL and duration; only an answer without an id (a hand-built one; `Http.fake` installs no interceptors) falls back to the oldest request in flight that has no id either, with `attributedHeuristically: true`. A request with an id is never handed to an id-less answer, since its own answer would then find nothing to pair with and be dropped. Records carry `requestId`, `startUs` and `endUs`. (`lib/src/telescope_integration.dart`)
- **A span is linked from when it began, not when it ended.** `QueryReloaded`, `ActionRan` and `EventDispatched` arrive at span end, so resolving the link then dropped a reload that outlived its tap and gave one that ended during the next tap to that tap. `MagicPerfIntegration.interactionLink({startUs})` now accepts the zone handle when its window `[startUs, closedAtUs ?? now]` holds the span's start (closed or not), else dusk's `perfInteractionAt(startUs)` (`frame`), else `window`; an instant with no start keeps the open-handle and active-interaction rules. The `zoneInteractionId` test seam becomes `zoneInteraction` (it returns the handle's window), with a new `interactionIdAt` seam beside `activeInteractionId`. Needs the dusk release that exports `perfInteractionAt`. (`lib/src/perf_integration.dart`)
- **The `mediaQuerySize` insight describes a size-only subscription.** wind now reads `MediaQuery.sizeOf`, so a counted read rebuilds its widget on a resize or a rotation and not on a keyboard inset; the summary and next step said every MediaQuery change, which is no longer true. The metric name, threshold and firing rule are unchanged. (`lib/src/perf_insight_rules.dart`)

- **The install guard is `!kReleaseMode`.** `MagicDevtools.installPre` / `installPost` and the per-tool blocks are documented under `!kReleaseMode` instead of `kDebugMode`, so a profile build carries dusk and telescope and a perf measurement can run on it; release still tree-shakes both, and the guard still belongs at the call site. `lib/src/dusk_integration.dart` and `lib/src/perf_integration.dart` already said so, and the rest of the package said `kDebugMode`. Apps wired under `kDebugMode` keep working in debug and simply carry no tooling in profile. (`CLAUDE.md`, `README.md`, `lib/magic_devtools.dart`, `lib/dusk.dart`, `lib/telescope.dart`, `lib/src/magic_devtools.dart`, `lib/src/telescope_integration.dart`)

### Added

- **Interaction links on every record.** HTTP, query, event, model and cache records, and every sink row, carry `interactionId` and `linkedBy`: `zone` when the work read an open dusk interaction off its own zone, `frame` when it ran in the frame zone and joined dusk's active interaction, `window` when neither. `MagicPerfIntegration.interactionLink()` is the one rule. Gate records are not stamped: telescope's `GateRecord` has no link fields. (`lib/src/perf_integration.dart`, `lib/src/telescope_integration.dart`)
- **`perfTimelineReader` and `perfInsightContributors` are assigned.** The timeline reader returns the sink rows (notifies, query reloads, actions, event dispatches, timer ticks, broadcasts) plus one row per telescope HTTP, query, event, model and cache record, in dusk's row schema. The contributor runs `PerfInsightRules`: wind wrapper emissions per W-widget build, `mediaQuerySize` reads per frame, parse misses on a warm surface, notify storms by cause, timer-driven notifies per second, uncached query reloads per interaction, attribute casts per frame, and HTTP requests per interaction. Every rule states its threshold in `evidence.threshold` and normalises by painted frames. (`lib/src/perf_insight_rules.dart`)

## [0.0.7] - 2026-09-27

### Changed
Expand Down
5 changes: 3 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ live in magic; moving them out is what lets a production app depend on magic wit
driver and a runtime inspector into its resolution graph. Every consumer adds this package as a
**dev_dependency**, never a dependency.

That is also why every install call is guarded by `kDebugMode` AT THE CALL SITE, in the consumer's
That is also why every install call is guarded by `!kReleaseMode` AT THE CALL SITE (debug and profile
builds carry the tools, so a perf measurement runs on a profile build), in the consumer's
`main.dart`, and never inside a method here. Moving the guard inward defeats the release tree-shake and
pulls both tools into the production bundle, which is the one failure this package's whole shape is
arranged to prevent.
Expand Down Expand Up @@ -110,7 +111,7 @@ throws.
3. `CHANGELOG.md` gets a bullet under `## [Unreleased]` for every behavioural or interface change.
4. Never add a dependency that would let magic core reach dusk or telescope. The direction is
`magic_devtools` to the tools, never the reverse.
5. Never move a `kDebugMode` guard inside this package.
5. Never move a `!kReleaseMode` guard inside this package.

## Branching

Expand Down
28 changes: 16 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,24 +27,24 @@

`magic_devtools` is the Magic adapter layer for [`fluttersdk_dusk`](https://pub.dev/packages/fluttersdk_dusk) and [`fluttersdk_telescope`](https://pub.dev/packages/fluttersdk_telescope). It enriches dusk snapshots and telescope records with Magic-aware context (forms, navigation, controllers, gates, auth, broadcasting, HTTP) so an LLM agent or CI driver sees your app the way Magic sees it.

It is **debug-only**: you install and wire it under `kDebugMode`, so release builds tree-shake it entirely and it carries no runtime cost in production. This is exactly why it lives outside `magic` core; the framework keeps no dev-tooling production dependencies.
It is **dev-only**: you install and wire it under `!kReleaseMode`, so debug and profile builds carry it (a performance measurement needs a profile build) and release builds tree-shake it entirely and it carries no runtime cost in production. This is exactly why it lives outside `magic` core; the framework keeps no dev-tooling production dependencies.

Four import barrels:

- `package:magic_devtools/magic_devtools.dart`: `MagicDevtools` is the umbrella one-call wiring: `installPre()` boots both tool plugins (plus telescope's opt-in exception/dump watchers) before `Magic.init()`, `installPost()` wires both Magic integrations after it.
- `package:magic_devtools/dusk.dart`: `MagicDuskIntegration` registers 14 Magic-aware enrichers into fluttersdk_dusk's snapshot pipeline.
- `package:magic_devtools/telescope.dart`: `MagicTelescopeIntegration` registers 5 Magic watchers and `MagicHttpFacadeAdapter` into fluttersdk_telescope.
- `package:magic_devtools/telescope.dart`: `MagicTelescopeIntegration` registers 5 Magic watchers and `MagicHttpFacadeAdapter` into fluttersdk_telescope. The adapter masks credentials before a record reaches the agent-facing buffer: `password` / `token` style keys of a request or response body and the header `auth.token.header` names (default `Authorization`) read `********`.
- `package:magic_devtools/preview.dart`: `MagicPreview` hosts a dev-only component preview catalog via two plain pages (`/preview` and `/preview/:component`), tree-shaken from release builds.

## Install

`magic_devtools` and the tooling packages are imported in `lib/main.dart` (under `kDebugMode`), so they are regular `dependencies`, not `dev_dependencies`; `kDebugMode` tree-shakes them out of release builds, and because `lib/` imports them a `dev_dependencies` entry would trip the `depend_on_referenced_packages` lint. This matches how `fluttersdk_dusk` and `fluttersdk_telescope` are installed on their own.
`magic_devtools` and the tooling packages are imported in `lib/main.dart` (under `!kReleaseMode`), so they are regular `dependencies`, not `dev_dependencies`; `!kReleaseMode` tree-shakes them out of release builds, and because `lib/` imports them a `dev_dependencies` entry would trip the `depend_on_referenced_packages` lint. This matches how `fluttersdk_dusk` and `fluttersdk_telescope` are installed on their own.

```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
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 transitive resolution does not happen through `magic` itself.
Expand All @@ -55,36 +55,40 @@ Both integrations are debug-only and run in `lib/main.dart`. The ordering is loa

### Both tools at once (recommended)

`MagicDevtools` collapses the four blocks below into the two halves of that ordering. Keep the `kDebugMode` guard at the call site: moving it inside the methods would make the call live in release and defeat the tree-shake.
`MagicDevtools` collapses the four blocks below into the two halves of that ordering. Keep the `!kReleaseMode` guard at the call site: moving it inside the methods would make the call live in release and defeat the tree-shake.

```dart
if (kDebugMode) MagicDevtools.installPre(); // dusk + telescope plugins + exception/dump watchers
if (!kReleaseMode) MagicDevtools.installPre(); // dusk + telescope plugins + exception/dump watchers
await Magic.init(configFactories: [...]);
if (kDebugMode) MagicDevtools.installPost(); // MagicTelescopeIntegration + MagicDuskIntegration
if (!kReleaseMode) MagicDevtools.installPost(); // MagicTelescopeIntegration + MagicDuskIntegration
```

Reach for the individual barrels below when you need only one tool, or a non-standard telescope watcher set (register extra watchers with `TelescopePlugin.registerWatcher` after `installPre`).

### Performance sessions

`installPre()` also installs `MagicPerfIntegration`, the data path behind dusk's `perf_begin` / `perf_end` / `perf_trace`. During an attribution session it counts magic's runtime activity through `MagicPerfHooks.sink` and wind's build, wrapper and inherited-read counters, stamps every telescope record with the dusk interaction it belongs to (`linkedBy: zone | frame | window`), and contributes wind and magic insights (`PerfInsightRules`) whose thresholds each insight states in `evidence.threshold`. A timing session touches neither counter.

### Dusk

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

### Telescope

```dart
if (kDebugMode) {
if (!kReleaseMode) {
TelescopePlugin.install();
}
await Magic.init(configFactories: [...]);
if (kDebugMode) {
if (!kReleaseMode) {
MagicTelescopeIntegration.install();
}
```
Expand Down
4 changes: 2 additions & 2 deletions lib/dusk.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@
/// live during Magic boot:
///
/// ```dart
/// if (kDebugMode) {
/// if (!kReleaseMode) {
/// DuskPlugin.install();
/// }
/// await Magic.init(configFactories: [...]);
/// if (kDebugMode) {
/// if (!kReleaseMode) {
/// MagicDuskIntegration.install();
/// }
/// ```
Expand Down
2 changes: 1 addition & 1 deletion lib/magic_devtools.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
///
/// Exposes [MagicDevtools], the one-call `installPre` / `installPost` wiring
/// for fluttersdk_dusk and fluttersdk_telescope plus their Magic
/// integrations, installed around [Magic.init] under `kDebugMode`.
/// integrations, installed around [Magic.init] under `!kReleaseMode`.
///
/// See the finer-grained `dusk.dart`, `telescope.dart`, and `preview.dart`
/// barrels when you need direct access to a single integration, a
Expand Down
5 changes: 3 additions & 2 deletions lib/src/dusk_integration.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ import 'package:magic/magic.dart';
/// Glues magic's primitives (MagicForm, MagicRouter, Gate, Auth, Echo) into
/// the fluttersdk_dusk snapshot pipeline.
///
/// Host integration (debug-only):
/// Host integration (the consumer gates with `!kReleaseMode`, so debug and
/// profile both carry it and release tree-shakes it):
/// ```dart
/// if (kDebugMode) {
/// if (!kReleaseMode) {
/// DuskPlugin.install();
/// MagicDuskIntegration.install();
/// }
Expand Down
8 changes: 5 additions & 3 deletions lib/src/magic_devtools.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'dusk_integration.dart';
import 'perf_integration.dart';
import 'telescope_integration.dart';

export 'perf_insight_rules.dart';
export 'perf_integration.dart';

/// One-call wiring for the Magic dev-tooling bundle: fluttersdk_dusk +
Expand All @@ -25,7 +26,8 @@ export 'perf_integration.dart';
/// snapshot enrichers resolve dependencies through the IoC container
/// (`Magic.find` / `Magic.bound`).
///
/// Keep both calls inside a `kDebugMode` guard AT THE CALL SITE. Do not move
/// Keep both calls inside a `!kReleaseMode` guard AT THE CALL SITE (debug and
/// profile builds carry the tools; release tree-shakes them). Do not move
/// the guard inside these methods: a live (unguarded) call defeats the
/// release tree-shake and pulls dusk + telescope into the production bundle,
/// which is the whole reason this wiring lives outside `magic` core.
Expand All @@ -34,11 +36,11 @@ export 'perf_integration.dart';
/// 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 Down
Loading
Loading