@module-federation/observability-plugin 0.0.0-alpha.0 → 0.0.0-feat-federationdiagnosticerror-20260511123257

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -46,12 +46,24 @@ read: window.__FEDERATION__.__OBSERVABILITY__["runtime_host"].getReport("mf-..."
46
46
  ```
47
47
 
48
48
  Run the `read:` command exactly as printed. If the browser global reader is not
49
- enabled, use the observability controller created by application code:
49
+ enabled, ask for the application-owned `onReport` output or the uploaded
50
+ observability record.
51
+
52
+ If the user wants to inspect loading chains or says the page is stuck loading
53
+ without an error, do not wait for a failure hint. Read recent reports directly:
50
54
 
51
55
  ```ts
52
- observability.getReport('mf-...');
56
+ window.__FEDERATION__.__OBSERVABILITY__['runtime_host'].getReports({ limit: 10 });
53
57
  ```
54
58
 
59
+ Then look for `status: "pending"` or `summary.outcome: "pending"`. Use
60
+ `startedAt`, `updatedAt`, `duration`, `summary.phases`, and
61
+ `diagnosis.pendingPhases` to explain where the trace is currently stuck. When
62
+ the browser reader is enabled in development mode, the console prints
63
+ `Observability trace started` for `loadRemote` and `loadShare` by default; use
64
+ that `traceId` to read the exact report. In production browser mode, this start
65
+ log is disabled unless the app explicitly sets `trace.printStart: true`.
66
+
55
67
  For the latest report:
56
68
 
57
69
  ```ts
@@ -90,8 +102,7 @@ errorCode: RUNTIME-...
90
102
 
91
103
  Do not assume the full browser report is publicly readable when the `read:` line
92
104
  is absent. Ask the application owner to export it with `exportReport(traceId)`,
93
- read it through the application-owned observability controller, or check the
94
- application's own uploaded observability record.
105
+ or check the application's own uploaded observability record.
95
106
 
96
107
  If the goal is ongoing observability instead of one-off debugging, ask whether
97
108
  the application has enabled `onReport` or `onEvent`. In production, those
@@ -170,12 +181,27 @@ not by an AI model.
170
181
 
171
182
  - `summary.outcome`: final outcome, such as `runtime-loaded`,
172
183
  `component-loaded`, or `failed`.
184
+ - `summary.runtimeLoaded`: the remote module loaded successfully in the runtime.
185
+ - `summary.loadCompleted`: the `loadRemote` flow ended; this is not by itself a
186
+ success signal.
187
+ - `summary.componentLoaded`: a component-level success signal was observed. It
188
+ can come from business `markComponentLoaded` or from the opt-in React lifecycle
189
+ observer reporting `component:react-mounted`.
173
190
  - `summary.phases`: per-phase status, duration, cache, retry, and recovery
174
191
  markers.
175
192
  - `summary.flags`: cross-phase cache, retry, fallback, and recovery markers.
176
- - `summary.shared`: last observed shared provider/version result.
193
+ - `summary.shared`: last observed shared provider/version result. If multiple
194
+ shared dependencies are involved, inspect every `phase: "shared"` event.
177
195
  - `summary.error`: compact error summary.
178
196
 
197
+ If React lifecycle observation is enabled, reports may include
198
+ `component:react-render-started`, `component:react-mounted`, or
199
+ `component:react-render-timeout`. Treat `component:react-mounted` as React mount
200
+ success only. It does not prove that business data, charts, or SDK
201
+ initialization completed. The wrapper also injects an `onMFRemoteLoaded` prop
202
+ into the remote React component; if the producer calls it, the report includes
203
+ `component:business-loaded`, which is the producer's explicit ready signal.
204
+
179
205
  ### Build files
180
206
 
181
207
  Runtime reports do not embed build evidence. If you need build-side facts, read
@@ -354,6 +380,7 @@ Shared dependency or async boundary configuration failed.
354
380
  Start with:
355
381
 
356
382
  - `summary.shared`
383
+ - all `events` entries where `phase === "shared"`
357
384
  - shared config in `.mf/observability/build-info.json` when available
358
385
  - `summary.error.lifecycle`
359
386
  - `diagnosis.actions`
@@ -365,6 +392,13 @@ Likely fixes:
365
392
  - Fix `eager` for sync shared usage.
366
393
  - Add an async startup or async boundary when needed.
367
394
 
395
+ Shared evidence is scoped to the MF instance that resolved the shared
396
+ dependency. It can identify the instance, shared package, selected
397
+ provider/version, and related config. Do not claim that a shared dependency was
398
+ caused by a specific remote/expose unless the report contains explicit evidence.
399
+ Bundler runtime chunk and module execution can trigger shared resolution after
400
+ remote loading, so remote/expose causality is not guaranteed.
401
+
368
402
  ### `RUNTIME-008`
369
403
 
370
404
  RemoteEntry loading or execution failed.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2020 ScriptedAlchemy LLC (Zack Jackson) Zhou Shaw (zhouxiao)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -15,21 +15,19 @@ Node file output, and no raw error stack in console output.
15
15
  import { createInstance } from '@module-federation/runtime';
16
16
  import { ObservabilityPlugin } from '@module-federation/observability-plugin';
17
17
 
18
- const observability = ObservabilityPlugin({
19
- level: 'verbose',
20
- browser: {
21
- enabled: true,
22
- scope: 'host',
23
- },
24
- });
25
-
26
18
  createInstance({
27
19
  name: 'host',
28
- plugins: [observability.plugin],
20
+ plugins: [
21
+ ObservabilityPlugin({
22
+ level: 'verbose',
23
+ browser: {
24
+ enabled: true,
25
+ scope: 'host',
26
+ },
27
+ }),
28
+ ],
29
29
  remotes: [],
30
30
  });
31
-
32
- const report = observability.getLatestReport();
33
31
  ```
34
32
 
35
33
  The plugin does not upload data or expose a browser global by default. Reports
@@ -68,7 +66,13 @@ Failure hints are printed with `console.error` so browser DevTools, CDP-based
68
66
  agents, Node logs, and log collection systems can detect that a Module
69
67
  Federation load failed and then use the printed `traceId` to fetch the full
70
68
  report. Successful or recovered reports are still available through the reader
71
- APIs and callbacks, but they are not promoted to console errors.
69
+ APIs and callbacks, but they are not promoted to console errors. When the
70
+ browser reader is enabled in development mode, the plugin prints a small
71
+ `console.info` line by default when a `loadRemote` or `loadShare` trace starts.
72
+ The line includes the `traceId` and read command so an agent can inspect pending
73
+ loading state before a timeout or error happens. In production browser mode,
74
+ start logs are disabled by default; set `trace.printStart: true` only when you
75
+ explicitly want them.
72
76
 
73
77
  The runtime only exposes the loading lifecycle hooks needed to know whether the
74
78
  main flow started, succeeded, or failed. This plugin listens to those hooks,
@@ -76,7 +80,8 @@ derives detailed reasons like shared version mismatch or eager boundary issues,
76
80
  and exposes the final loading state through a small `summary` object:
77
81
 
78
82
  - `runtime-loaded`: Module Federation finished loading the remote module.
79
- - `component-loaded`: business code called `markComponentLoaded`.
83
+ - `component-loaded`: business code called `markComponentLoaded`, or the
84
+ opt-in React lifecycle observer confirmed that the remote component mounted.
80
85
  - `failed`: the load failed and `failedPhase` points to the first specific
81
86
  failing phase.
82
87
  - `recovered`: loading hit an error but a fallback/recovery path returned a
@@ -120,10 +125,36 @@ mismatch, and eager boundary errors are not retried by the retry plugin by
120
125
  default because they are usually configuration or availability problems instead
121
126
  of transient network failures.
122
127
 
123
- Business code can mark its own success condition with a fixed event:
128
+ Business code can mark its own success condition with a fixed event. When React
129
+ component lifecycle observation is enabled, the wrapper injects an
130
+ `onMFRemoteLoaded` prop into the remote component. The producer can call it when
131
+ the component's own ready condition is met:
132
+
133
+ ```tsx
134
+ import { useEffect } from 'react';
135
+ import type { OnMFRemoteLoaded } from '@module-federation/observability-plugin';
136
+
137
+ export default function RemotePanel({ onMFRemoteLoaded }: { onMFRemoteLoaded?: OnMFRemoteLoaded }) {
138
+ useEffect(() => {
139
+ onMFRemoteLoaded?.({
140
+ metadata: {
141
+ dataReady: true,
142
+ },
143
+ });
144
+ }, [onMFRemoteLoaded]);
145
+
146
+ return <section>Remote panel</section>;
147
+ }
148
+ ```
149
+
150
+ If the app wants to mark readiness from the consumer side, it can still call the
151
+ instance method directly:
124
152
 
125
153
  ```ts
126
- observability.markComponentLoaded({
154
+ import { getInstance } from '@module-federation/runtime';
155
+ import '@module-federation/observability-plugin';
156
+
157
+ getInstance()?.markComponentLoaded({
127
158
  requestId: 'remote/Button',
128
159
  componentName: 'Button',
129
160
  metadata: {
@@ -132,9 +163,40 @@ observability.markComponentLoaded({
132
163
  });
133
164
  ```
134
165
 
135
- This records `component:business-loaded` on the same trace when possible.
166
+ Both paths record `component:business-loaded` on the same trace when possible.
136
167
  Business metadata is optional. String values keep their original details and are
137
- only length-limited, so user-provided metadata remains trustworthy.
168
+ only length-limited, so user-provided metadata remains trustworthy. The instance
169
+ method is attached when the observability plugin is registered. If an
170
+ application uses multiple runtime instances, call it on the instance that
171
+ registered this plugin.
172
+
173
+ React component lifecycle observation is available only when explicitly enabled:
174
+
175
+ ```ts
176
+ ObservabilityPlugin({
177
+ level: 'verbose',
178
+ react: {
179
+ enabled: true,
180
+ timeout: 5000,
181
+ consumerNames: ['runtime_host'],
182
+ remoteIds: ['remote/Button'],
183
+ },
184
+ });
185
+ ```
186
+
187
+ When this option is enabled, the plugin tries to wrap remote function components
188
+ returned by `loadRemote`. The wrapper does not add DOM nodes. It injects the
189
+ `onMFRemoteLoaded` prop and records
190
+ `component:react-render-started`, `component:react-mounted`, or
191
+ `component:react-render-timeout`. This is useful for dev and AI debugging, but
192
+ it only means React mounted the component. Keep using `markComponentLoaded` when
193
+ the business definition of success depends on data, charts, SDKs, or other async
194
+ work inside the remote component.
195
+
196
+ Use `react.consumerNames` or `react.remoteIds` to limit this behavior to the
197
+ consumers or remote requests you are actively debugging. If both are empty, the
198
+ plugin observes detected React function components for every runtime instance
199
+ that registered it.
138
200
 
139
201
  Browser output is available only when the plugin option explicitly enables it.
140
202
  When browser output is enabled, the report can be read from:
@@ -147,11 +209,29 @@ window.__FEDERATION__.__OBSERVABILITY__.host.findReports({ remote: 'remote1' });
147
209
  window.__FEDERATION__.__OBSERVABILITY__.host.exportReport('mf-trace-id');
148
210
  ```
149
211
 
150
- The same methods are available on the observability controller returned by
151
- `ObservabilityPlugin()`. `getReports({ limit })` returns recent reports newest
152
- first. `findReports()` can filter by `traceId`, `remote`, `expose`, `shared`,
153
- `status`, or `outcome`. `exportReport()` returns a copied report object, using
154
- the latest report when no `traceId` is provided.
212
+ `getReports({ limit })` returns recent reports newest first. `findReports()` can
213
+ filter by `traceId`, `remote`, `expose`, `shared`, `status`, or `outcome`.
214
+ `exportReport()` returns a copied report object, using the latest report when no
215
+ `traceId` is provided.
216
+
217
+ For agent-led development debugging where a page may stay in a loading state,
218
+ enable the browser reader. Development browser mode prints start traces by
219
+ default:
220
+
221
+ ```ts
222
+ ObservabilityPlugin({
223
+ level: 'verbose',
224
+ browser: {
225
+ enabled: true,
226
+ scope: 'host',
227
+ },
228
+ });
229
+ ```
230
+
231
+ This prints only `loadRemote` and `loadShare` start lines. It does not print a
232
+ line for every internal phase. Set `trace.printStart: false` to disable it in
233
+ development browser mode. In production browser mode, set
234
+ `trace.printStart: true` to opt in.
155
235
 
156
236
  For browser production use, set `browser.mode: "production"` when the runtime
157
237
  console must stay minimal:
@@ -169,10 +249,10 @@ ObservabilityPlugin({
169
249
  In production browser mode, the `console.error` hint only includes the `traceId`
170
250
  and known `errorCode`. It does not print the report body, raw stack, request
171
251
  URL, or `read:` command. Full reports are still available only through explicit
172
- user choices such as the observability controller, `exportReport()`, or an
173
- application-owned `onReport` upload. Production applications that want richer
174
- observability should prefer `onReport` / `onEvent` to forward reports to their
175
- own telemetry system instead of exposing a public browser global.
252
+ user choices such as `exportReport()` or an application-owned `onReport` upload.
253
+ Production applications that want richer observability should prefer
254
+ `onReport` / `onEvent` to forward reports to their own telemetry system instead
255
+ of exposing a public browser global.
176
256
 
177
257
  Node file output is provided by the Node-specific entry:
178
258
 
@@ -180,15 +260,15 @@ Node file output is provided by the Node-specific entry:
180
260
  import { createInstance } from '@module-federation/runtime';
181
261
  import { ObservabilityPlugin } from '@module-federation/observability-plugin/node';
182
262
 
183
- const observability = ObservabilityPlugin({
184
- level: 'verbose',
185
- fileOutput: true,
186
- directory: '.mf/observability',
187
- });
188
-
189
263
  createInstance({
190
264
  name: 'host',
191
- plugins: [observability.plugin],
265
+ plugins: [
266
+ ObservabilityPlugin({
267
+ level: 'verbose',
268
+ fileOutput: true,
269
+ directory: '.mf/observability',
270
+ }),
271
+ ],
192
272
  remotes: [],
193
273
  });
194
274
  ```
@@ -293,3 +373,13 @@ Shared dependency reports include only evidence fields such as package name,
293
373
  share scope, requested version, available versions, selected provider, and a
294
374
  reason like `missing-provider`, `version-mismatch`, or `sync-async-boundary`.
295
375
  They do not include shared factories, module values, source, or business data.
376
+
377
+ Shared observability is intentionally scoped to the Module Federation instance
378
+ that resolved the shared dependency. It can answer which MF instance loaded a
379
+ shared package, which registered provider/version was selected, and the related
380
+ scope/version/eager configuration. It does not guarantee a causal link from that
381
+ shared dependency back to a specific remote or expose, because shared resolution
382
+ can be triggered later by the bundler runtime while chunks and module factories
383
+ execute. When multiple shared dependencies are involved, read all
384
+ `phase: "shared"` events. `summary.shared` is only a compact last-observed
385
+ summary.
@@ -46,12 +46,24 @@ read: window.__FEDERATION__.__OBSERVABILITY__["runtime_host"].getReport("mf-..."
46
46
  ```
47
47
 
48
48
  Run the `read:` command exactly as printed. If the browser global reader is not
49
- enabled, use the observability controller created by application code:
49
+ enabled, ask for the application-owned `onReport` output or the uploaded
50
+ observability record.
51
+
52
+ If the user wants to inspect loading chains or says the page is stuck loading
53
+ without an error, do not wait for a failure hint. Read recent reports directly:
50
54
 
51
55
  ```ts
52
- observability.getReport('mf-...');
56
+ window.__FEDERATION__.__OBSERVABILITY__['runtime_host'].getReports({ limit: 10 });
53
57
  ```
54
58
 
59
+ Then look for `status: "pending"` or `summary.outcome: "pending"`. Use
60
+ `startedAt`, `updatedAt`, `duration`, `summary.phases`, and
61
+ `diagnosis.pendingPhases` to explain where the trace is currently stuck. When
62
+ the browser reader is enabled in development mode, the console prints
63
+ `Observability trace started` for `loadRemote` and `loadShare` by default; use
64
+ that `traceId` to read the exact report. In production browser mode, this start
65
+ log is disabled unless the app explicitly sets `trace.printStart: true`.
66
+
55
67
  For the latest report:
56
68
 
57
69
  ```ts
@@ -90,8 +102,7 @@ errorCode: RUNTIME-...
90
102
 
91
103
  Do not assume the full browser report is publicly readable when the `read:` line
92
104
  is absent. Ask the application owner to export it with `exportReport(traceId)`,
93
- read it through the application-owned observability controller, or check the
94
- application's own uploaded observability record.
105
+ or check the application's own uploaded observability record.
95
106
 
96
107
  If the goal is ongoing observability instead of one-off debugging, ask whether
97
108
  the application has enabled `onReport` or `onEvent`. In production, those
@@ -170,12 +181,27 @@ not by an AI model.
170
181
 
171
182
  - `summary.outcome`: final outcome, such as `runtime-loaded`,
172
183
  `component-loaded`, or `failed`.
184
+ - `summary.runtimeLoaded`: the remote module loaded successfully in the runtime.
185
+ - `summary.loadCompleted`: the `loadRemote` flow ended; this is not by itself a
186
+ success signal.
187
+ - `summary.componentLoaded`: a component-level success signal was observed. It
188
+ can come from business `markComponentLoaded` or from the opt-in React lifecycle
189
+ observer reporting `component:react-mounted`.
173
190
  - `summary.phases`: per-phase status, duration, cache, retry, and recovery
174
191
  markers.
175
192
  - `summary.flags`: cross-phase cache, retry, fallback, and recovery markers.
176
- - `summary.shared`: last observed shared provider/version result.
193
+ - `summary.shared`: last observed shared provider/version result. If multiple
194
+ shared dependencies are involved, inspect every `phase: "shared"` event.
177
195
  - `summary.error`: compact error summary.
178
196
 
197
+ If React lifecycle observation is enabled, reports may include
198
+ `component:react-render-started`, `component:react-mounted`, or
199
+ `component:react-render-timeout`. Treat `component:react-mounted` as React mount
200
+ success only. It does not prove that business data, charts, or SDK
201
+ initialization completed. The wrapper also injects an `onMFRemoteLoaded` prop
202
+ into the remote React component; if the producer calls it, the report includes
203
+ `component:business-loaded`, which is the producer's explicit ready signal.
204
+
179
205
  ### Build files
180
206
 
181
207
  Runtime reports do not embed build evidence. If you need build-side facts, read
@@ -354,6 +380,7 @@ Shared dependency or async boundary configuration failed.
354
380
  Start with:
355
381
 
356
382
  - `summary.shared`
383
+ - all `events` entries where `phase === "shared"`
357
384
  - shared config in `.mf/observability/build-info.json` when available
358
385
  - `summary.error.lifecycle`
359
386
  - `diagnosis.actions`
@@ -365,6 +392,13 @@ Likely fixes:
365
392
  - Fix `eager` for sync shared usage.
366
393
  - Add an async startup or async boundary when needed.
367
394
 
395
+ Shared evidence is scoped to the MF instance that resolved the shared
396
+ dependency. It can identify the instance, shared package, selected
397
+ provider/version, and related config. Do not claim that a shared dependency was
398
+ caused by a specific remote/expose unless the report contains explicit evidence.
399
+ Bundler runtime chunk and module execution can trigger shared resolution after
400
+ remote loading, so remote/expose causality is not guaranteed.
401
+
368
402
  ### `RUNTIME-008`
369
403
 
370
404
  RemoteEntry loading or execution failed.
@@ -0,0 +1,12 @@
1
+ # @module-federation/observability-plugin
2
+
3
+ ## 0.0.0-feat-federationdiagnosticerror-20260511123257
4
+
5
+ ### Minor Changes
6
+
7
+ - 9b21865: Add an opt-in observability plugin, a direct runtime plugin API with instance-bound component loaded marks, opt-in React component lifecycle observation with automatic `onMFRemoteLoaded` prop injection, opt-in start console traces for `loadRemote` and `loadShare`, a Node-specific export for file reports, a build-specific export for build summaries and build error reports, remote and shared lifecycle hooks, console trace hints, safe browser/Node report outputs, configurable error stack capture with explicit console raw-stack opt-ins, shared/eager loading evidence, final loading outcome summaries for Module Federation loading reports, deterministic fact reports for runtime and build failures, no-op return handling for observer hooks, detailed remote match/init/expose/factory phase events with phase durations, compact phase summaries, cache/retry/fallback markers, length-limited business component metadata, clipped moduleInfo evidence with preserved deployment locator fields for snapshot-dependent failures, normalized runtime error summaries with error codes, owner hints, retryability, and safe context, dedicated runtime error codes for invalid manifests, missing exposes, and remote container init failures, plus an AI troubleshooting guide for reading and fixing observability reports.
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies [5d4095d]
12
+ - @module-federation/sdk@0.0.0-feat-federationdiagnosticerror-20260511123257
package/dist/README.md CHANGED
@@ -15,21 +15,19 @@ Node file output, and no raw error stack in console output.
15
15
  import { createInstance } from '@module-federation/runtime';
16
16
  import { ObservabilityPlugin } from '@module-federation/observability-plugin';
17
17
 
18
- const observability = ObservabilityPlugin({
19
- level: 'verbose',
20
- browser: {
21
- enabled: true,
22
- scope: 'host',
23
- },
24
- });
25
-
26
18
  createInstance({
27
19
  name: 'host',
28
- plugins: [observability.plugin],
20
+ plugins: [
21
+ ObservabilityPlugin({
22
+ level: 'verbose',
23
+ browser: {
24
+ enabled: true,
25
+ scope: 'host',
26
+ },
27
+ }),
28
+ ],
29
29
  remotes: [],
30
30
  });
31
-
32
- const report = observability.getLatestReport();
33
31
  ```
34
32
 
35
33
  The plugin does not upload data or expose a browser global by default. Reports
@@ -68,7 +66,13 @@ Failure hints are printed with `console.error` so browser DevTools, CDP-based
68
66
  agents, Node logs, and log collection systems can detect that a Module
69
67
  Federation load failed and then use the printed `traceId` to fetch the full
70
68
  report. Successful or recovered reports are still available through the reader
71
- APIs and callbacks, but they are not promoted to console errors.
69
+ APIs and callbacks, but they are not promoted to console errors. When the
70
+ browser reader is enabled in development mode, the plugin prints a small
71
+ `console.info` line by default when a `loadRemote` or `loadShare` trace starts.
72
+ The line includes the `traceId` and read command so an agent can inspect pending
73
+ loading state before a timeout or error happens. In production browser mode,
74
+ start logs are disabled by default; set `trace.printStart: true` only when you
75
+ explicitly want them.
72
76
 
73
77
  The runtime only exposes the loading lifecycle hooks needed to know whether the
74
78
  main flow started, succeeded, or failed. This plugin listens to those hooks,
@@ -76,7 +80,8 @@ derives detailed reasons like shared version mismatch or eager boundary issues,
76
80
  and exposes the final loading state through a small `summary` object:
77
81
 
78
82
  - `runtime-loaded`: Module Federation finished loading the remote module.
79
- - `component-loaded`: business code called `markComponentLoaded`.
83
+ - `component-loaded`: business code called `markComponentLoaded`, or the
84
+ opt-in React lifecycle observer confirmed that the remote component mounted.
80
85
  - `failed`: the load failed and `failedPhase` points to the first specific
81
86
  failing phase.
82
87
  - `recovered`: loading hit an error but a fallback/recovery path returned a
@@ -120,10 +125,36 @@ mismatch, and eager boundary errors are not retried by the retry plugin by
120
125
  default because they are usually configuration or availability problems instead
121
126
  of transient network failures.
122
127
 
123
- Business code can mark its own success condition with a fixed event:
128
+ Business code can mark its own success condition with a fixed event. When React
129
+ component lifecycle observation is enabled, the wrapper injects an
130
+ `onMFRemoteLoaded` prop into the remote component. The producer can call it when
131
+ the component's own ready condition is met:
132
+
133
+ ```tsx
134
+ import { useEffect } from 'react';
135
+ import type { OnMFRemoteLoaded } from '@module-federation/observability-plugin';
136
+
137
+ export default function RemotePanel({ onMFRemoteLoaded }: { onMFRemoteLoaded?: OnMFRemoteLoaded }) {
138
+ useEffect(() => {
139
+ onMFRemoteLoaded?.({
140
+ metadata: {
141
+ dataReady: true,
142
+ },
143
+ });
144
+ }, [onMFRemoteLoaded]);
145
+
146
+ return <section>Remote panel</section>;
147
+ }
148
+ ```
149
+
150
+ If the app wants to mark readiness from the consumer side, it can still call the
151
+ instance method directly:
124
152
 
125
153
  ```ts
126
- observability.markComponentLoaded({
154
+ import { getInstance } from '@module-federation/runtime';
155
+ import '@module-federation/observability-plugin';
156
+
157
+ getInstance()?.markComponentLoaded({
127
158
  requestId: 'remote/Button',
128
159
  componentName: 'Button',
129
160
  metadata: {
@@ -132,9 +163,40 @@ observability.markComponentLoaded({
132
163
  });
133
164
  ```
134
165
 
135
- This records `component:business-loaded` on the same trace when possible.
166
+ Both paths record `component:business-loaded` on the same trace when possible.
136
167
  Business metadata is optional. String values keep their original details and are
137
- only length-limited, so user-provided metadata remains trustworthy.
168
+ only length-limited, so user-provided metadata remains trustworthy. The instance
169
+ method is attached when the observability plugin is registered. If an
170
+ application uses multiple runtime instances, call it on the instance that
171
+ registered this plugin.
172
+
173
+ React component lifecycle observation is available only when explicitly enabled:
174
+
175
+ ```ts
176
+ ObservabilityPlugin({
177
+ level: 'verbose',
178
+ react: {
179
+ enabled: true,
180
+ timeout: 5000,
181
+ consumerNames: ['runtime_host'],
182
+ remoteIds: ['remote/Button'],
183
+ },
184
+ });
185
+ ```
186
+
187
+ When this option is enabled, the plugin tries to wrap remote function components
188
+ returned by `loadRemote`. The wrapper does not add DOM nodes. It injects the
189
+ `onMFRemoteLoaded` prop and records
190
+ `component:react-render-started`, `component:react-mounted`, or
191
+ `component:react-render-timeout`. This is useful for dev and AI debugging, but
192
+ it only means React mounted the component. Keep using `markComponentLoaded` when
193
+ the business definition of success depends on data, charts, SDKs, or other async
194
+ work inside the remote component.
195
+
196
+ Use `react.consumerNames` or `react.remoteIds` to limit this behavior to the
197
+ consumers or remote requests you are actively debugging. If both are empty, the
198
+ plugin observes detected React function components for every runtime instance
199
+ that registered it.
138
200
 
139
201
  Browser output is available only when the plugin option explicitly enables it.
140
202
  When browser output is enabled, the report can be read from:
@@ -147,11 +209,29 @@ window.__FEDERATION__.__OBSERVABILITY__.host.findReports({ remote: 'remote1' });
147
209
  window.__FEDERATION__.__OBSERVABILITY__.host.exportReport('mf-trace-id');
148
210
  ```
149
211
 
150
- The same methods are available on the observability controller returned by
151
- `ObservabilityPlugin()`. `getReports({ limit })` returns recent reports newest
152
- first. `findReports()` can filter by `traceId`, `remote`, `expose`, `shared`,
153
- `status`, or `outcome`. `exportReport()` returns a copied report object, using
154
- the latest report when no `traceId` is provided.
212
+ `getReports({ limit })` returns recent reports newest first. `findReports()` can
213
+ filter by `traceId`, `remote`, `expose`, `shared`, `status`, or `outcome`.
214
+ `exportReport()` returns a copied report object, using the latest report when no
215
+ `traceId` is provided.
216
+
217
+ For agent-led development debugging where a page may stay in a loading state,
218
+ enable the browser reader. Development browser mode prints start traces by
219
+ default:
220
+
221
+ ```ts
222
+ ObservabilityPlugin({
223
+ level: 'verbose',
224
+ browser: {
225
+ enabled: true,
226
+ scope: 'host',
227
+ },
228
+ });
229
+ ```
230
+
231
+ This prints only `loadRemote` and `loadShare` start lines. It does not print a
232
+ line for every internal phase. Set `trace.printStart: false` to disable it in
233
+ development browser mode. In production browser mode, set
234
+ `trace.printStart: true` to opt in.
155
235
 
156
236
  For browser production use, set `browser.mode: "production"` when the runtime
157
237
  console must stay minimal:
@@ -169,10 +249,10 @@ ObservabilityPlugin({
169
249
  In production browser mode, the `console.error` hint only includes the `traceId`
170
250
  and known `errorCode`. It does not print the report body, raw stack, request
171
251
  URL, or `read:` command. Full reports are still available only through explicit
172
- user choices such as the observability controller, `exportReport()`, or an
173
- application-owned `onReport` upload. Production applications that want richer
174
- observability should prefer `onReport` / `onEvent` to forward reports to their
175
- own telemetry system instead of exposing a public browser global.
252
+ user choices such as `exportReport()` or an application-owned `onReport` upload.
253
+ Production applications that want richer observability should prefer
254
+ `onReport` / `onEvent` to forward reports to their own telemetry system instead
255
+ of exposing a public browser global.
176
256
 
177
257
  Node file output is provided by the Node-specific entry:
178
258
 
@@ -180,15 +260,15 @@ Node file output is provided by the Node-specific entry:
180
260
  import { createInstance } from '@module-federation/runtime';
181
261
  import { ObservabilityPlugin } from '@module-federation/observability-plugin/node';
182
262
 
183
- const observability = ObservabilityPlugin({
184
- level: 'verbose',
185
- fileOutput: true,
186
- directory: '.mf/observability',
187
- });
188
-
189
263
  createInstance({
190
264
  name: 'host',
191
- plugins: [observability.plugin],
265
+ plugins: [
266
+ ObservabilityPlugin({
267
+ level: 'verbose',
268
+ fileOutput: true,
269
+ directory: '.mf/observability',
270
+ }),
271
+ ],
192
272
  remotes: [],
193
273
  });
194
274
  ```
@@ -293,3 +373,13 @@ Shared dependency reports include only evidence fields such as package name,
293
373
  share scope, requested version, available versions, selected provider, and a
294
374
  reason like `missing-provider`, `version-mismatch`, or `sync-async-boundary`.
295
375
  They do not include shared factories, module values, source, or business data.
376
+
377
+ Shared observability is intentionally scoped to the Module Federation instance
378
+ that resolved the shared dependency. It can answer which MF instance loaded a
379
+ shared package, which registered provider/version was selected, and the related
380
+ scope/version/eager configuration. It does not guarantee a causal link from that
381
+ shared dependency back to a specific remote or expose, because shared resolution
382
+ can be triggered later by the bundler runtime while chunks and module factories
383
+ execute. When multiple shared dependencies are involved, read all
384
+ `phase: "shared"` events. `summary.shared` is only a compact last-observed
385
+ summary.