Guide · @staticflux/perf · @staticflux/loader · @staticflux/devtools · @staticflux/core
Performance, observability & devtools
StaticFlux defines its own performance model. Framework primitives emit OpenTelemetry-shaped spans automatically; opting in turns them into journey traces, budgets and devtools.
#One telemetry fabric
Spans, events and typed metrics flow through createTelemetry with per-subsystem verbosity (off | critical | standard | verbose | debug), switchable at runtime (devtools, ?sf-debug=core/di:verbose). Sinks are pluggable and the fabric buffers until the first one attaches. Built-in spans cover DI resolution, lifecycle transitions, navigation stages, chunk loads, requests, gate decisions, worker messages and renders.
export const ExportDuration = metric.histogram('studio/export-duration', { unit: 'ms' });
unit({ id: 'studio/export-unit', provides: ExportToken, scope: 'feature', informs: [ExportDuration], create })
// consumers declare reads: [ExportDuration]; read-but-never-informed is a build error (R17.21)
#Journeys and feature marks
Register perfUnits and every navigation becomes a journey: app.boot (core, critical units, shell frame) → nav.pipeline (one span per stage, chunk loads inside) → feature (activation, render) → view. Each phase reports total time and critical path, and journeys are classified by start (cold | warm | soft), activation (fresh | warm | archived), code source (network | http-cache | sw) and data source (network | cache | snapshot).
Feature marks — placeholderShown, firstRender, largestRender, interactive, complete — are measured by the framework (render commit + next frame, Element Timing where supported, a largest-element heuristic elsewhere), labelled with their method, and never conflated with browser LCP. Features declare who reports which: readiness: { firstRender: 'framework', interactive: 'feature' }.
const units = [
...perfUnits,
markBudgetUnit(defineMarkBudget({ feature: '*', mark: 'interactive', maxMs: 2_500, starts: ['cold'], reason: 'R12.7' })),
];
Detectors (long animation frames mapped to chunk ids, forced layouts, per-feature layout shift, render counts and render storms, input latency, memory) all degrade where Safari lacks the entry types. Every runtime rule violation is one typed violation event with a stable code and a fix — the conformance stream that devtools show and CI lab runs fail on.
#Loading: logical chunks and warmup tiers
chunk('feature:studio-desktop', { entry: 'src/features/studio-desktop/index.ts', tier: 'on-demand',
budget: { category: 'heavy', reason: 'Editor + rich surface; loaded only after authorize' } });
chunk('feature:agent-chat', { entry: 'src/features/agent-chat/index.ts', tier: 'likely', budget: { category: 'feature' } });
chunk('lib:shell', { load: 'eager', reason: 'Shell frame renders before core-ready' }); // eager needs a reason
Stable logical ids map to hashed files through sf-manifest.json. Tiers: critical (SW precache), likely (idle background), speculative (hover, viewport, prediction — network- and device-aware), on-demand. The chunk loader checks the gate and profile for the whole closure before fetching (I-8), preloads dependencies with the right crossorigin, retries with cache-busting, and reports warmup hits and wasted bytes. A tiny inline mode detector picks a per-profile boot plan so code for other modes is never fetched.
#Devtools
if (import.meta.env.DEV) {
const { installDevtools } = await import('@staticflux/devtools');
installDevtools({ injector: app.injector, units: appUnits }); // Ctrl/Cmd+Shift+D
}
A bounded telemetry sink plus inspectors: boot waterfall with critical path and per-unit DI timings, navigation journeys, the DI graph, lifecycle states, gate decision traces with on-demand explain(), a store inspector, violations grouped by code with fixes, metric values, and controls for verbosity and app commands. The panel is its own lazy chunk; production builds exclude the package.
AGENTS.md on GitHub: @staticflux/perf · @staticflux/loader · @staticflux/devtools · @staticflux/core