Skip to content

`StudioScope`

StudioScope is the InheritedWidget PoseFlow Studio screens use to reach their backing store. Hosts wrap their Studio routes in a StudioScope at the router level, screens read their persistence via StudioScope.of(context) rather than threading it through every constructor.

Lives in package:pose_flow_studio, you only need this when building a Studio host, not when consuming PoseFlow on the runtime side. See Building a Studio host for the full integration pattern.

Shape

class StudioScope extends InheritedWidget {
const StudioScope({
super.key,
required this.persistence,
this.assetBasePath = 'pose_flow/',
this.metadataPanelBuilder,
required super.child,
});
final StudioPersistence persistence;
final String assetBasePath;
final Widget Function(BuildContext context, String exerciseId)?
metadataPanelBuilder;
}

Fields

FieldMeaning
persistenceThe StudioPersistence bundle, phase repository, version repository, catalog, audit log. Hosts implement these against their preferred backend (Firestore, Drift, in-memory, etc.). See Studio persistence.
assetBasePathWeb-only, directory under web/ containing the pose engine worker + WASM + weights. Defaults to pose_flow/. Override if your host serves the assets from a different path.
metadataPanelBuilderOptional host hook for a metadata-editor side panel. When supplied, PoseFlowStudioScreen renders the returned widget as a floating panel on the right side so the trainer can edit movement-tracking and CMS-side metadata (body part, muscle groups, hero video URL, etc.) without leaving the Studio. The CMS wires this to its ExerciseMetadataPanel widget; the standalone showcase leaves it null and the panel is suppressed.

Usage

Wrap your Studio routes

import 'package:pose_flow_studio/pose_flow_studio.dart';
GoRoute(
path: '/studio/:id',
builder: (context, state) => StudioScope(
persistence: myStudioPersistence,
assetBasePath: 'blazeflow/',
metadataPanelBuilder: (ctx, id) => ExerciseMetadataPanel(id: id),
child: PoseFlowStudioScreen(
movementId: state.pathParameters['id'],
),
),
);

Read from a Studio screen

final persistence = StudioScope.of(context);
final movements = await persistence.catalog.list();

There’s also StudioScope.maybeOf(context), returns null instead of asserting when no scope is in the tree. Useful for screens that support both router-wired (production) and direct constructor- injected (test) wiring.

Read the asset base path

final base = StudioScope.assetBasePathOf(context);
// 'pose_flow/' for showcase, 'blazeflow/' for CMS

CameraTrackingView and the web the pose engine worker loader call this to resolve their asset URLs. Falls back to the package default when no scope is in context.

The StudioPersistence bundle

StudioPersistence groups the four backing repositories the Studio needs:

FieldPurpose
phasesRead / write the canonical .pose document per movement.
versionsHistory of phase-document versions for undo + rollback.
catalogThe list view: movement summaries (id, name, last modified).
auditAppend-only log of authoring actions for the CMS audit panel.

Hosts pick implementations per backend. The CMS wires Firestore- backed implementations; the standalone showcase wires Drift (SQLite) for offline-friendly authoring; tests wire in-memory stubs. See Studio persistence for the backend contract.

Why InheritedWidget?

The Studio screens form a tall widget tree (router → scaffold → editor → panel → field), and threading the persistence bundle through every constructor is friction without value. InheritedWidget is Flutter’s standard way to expose ambient context, StudioScope.of(context) is constant-time and re-builds only the widgets that explicitly subscribe.

The CMS and standalone showcase both wire via this scope; tests typically pass persistence directly via the screen’s optional constructor parameter to avoid the InheritedWidget wrapper.