Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
324ef0f
docs(design): ST0 implementation plan — sampler lifecycle seam + char…
krassx Oct 14, 2026
3bd7ff3
docs(design): rewrite ST0 plan around the tracker/producer structure
krassx Oct 14, 2026
f7fb6cf
feat(capture): TraceProducer contract and base (baseline-friendly emi…
krassx Oct 14, 2026
bca9d2e
feat(capture): traces provider is a producer container (isolation, la…
krassx Oct 14, 2026
f2d6db6
feat(node): ProcessStatsTracker — one timer, shared state, lazy event…
krassx Oct 14, 2026
ae7ab55
feat(node): node trace producers over ProcessStatsTracker; guarded la…
krassx Oct 14, 2026
402761e
feat(node,bun,deno): launch wires trace producers; Deno opts out of e…
krassx Oct 14, 2026
e53286e
feat(browser,webview): BatteryTracker + BatteryProducer (event-driven…
krassx Oct 14, 2026
e744f70
docs: record ST0′ (trace producers over shared trackers)
krassx Oct 14, 2026
80a1fcc
test(bugsee): classify systemTraceProducers as a reset relaunch option
krassx Oct 14, 2026
a7fe9b1
fix(capture): refresh only HELD trace values; a failed poll no longer…
krassx Oct 14, 2026
e39ea62
fix(node): no fabricated event-loop lag baseline; disable a histogram…
krassx Oct 14, 2026
37b094e
fix(node): guard ProcessStatsTracker activation; unwind and retry on …
krassx Oct 14, 2026
72eacb0
fix(node,bun,deno): the launch scheduler and onError reach the system…
krassx Oct 14, 2026
1144f94
fix(browser,webview): no unhandled rejection after getBattery() resol…
krassx Oct 14, 2026
26a1fe1
fix(capture,node,browser,webview): harden the system-traces provider;…
krassx Oct 14, 2026
9aeefa8
test(node,bun,deno,browser,webview): close the test gaps that let rea…
krassx Oct 14, 2026
201e5fa
docs: PROGRESS/ST0′, bun/deno barrels, systemTraceProducers JSDoc, re…
krassx Oct 14, 2026
95b2c23
test(capture): cover the tick's outer catch (a throwing clock) now th…
krassx Oct 14, 2026
d0c23c8
fix(node): ProcessStatsTracker.current() retries a failed activation …
krassx Oct 15, 2026
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
32 changes: 25 additions & 7 deletions docs/PROGRESS.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ Read this first; then `docs/design/sdk-design.md` (Draft v3) for the full archit
- `buildNodeEnvironment(input, probe)` — §8.6 envelope via injectable `SystemProbe`. Applies `optionsToWire` to `sdk.options` (dots → colons; server treats dots as nested-document paths).
- `createUncaughtExceptionProvider` / `createUnhandledRejectionProvider` — process-event detection, V8 stack parsed + scrubbed.
- `createNodeHttpInterceptor` — wraps `node:http(s)` `request` + `get` (mechanism `'http'`), captures axios/got/node-fetch that bypass global fetch. **Transparent error handling**: re-raises the original error when it is the sole `'error'` listener so an otherwise-uncaught request error still crashes the app.
- `createNodeSystemMetricsSampler` — process memory (rss/heapTotal/heapUsed/external/**arrayBuffers**), **system memory (`ram_system_total`/`ram_system_free` — Android parity)**, CPU user/system per-sample deltas + **normalized `cpu_usage_process` %** (over wall-time/cores), event-loop lag **mean/max/p99** + **utilization** (perf_hooks `monitorEventLoopDelay` + `eventLoopUtilization`). All readers injectable.
- `createNodeSystemTraceProducers` (the DEFAULT since ST0′; the legacy `createNodeSystemMetricsSampler` below is now only the `systemMetricsSampler` override) / `createNodeSystemMetricsSampler` — process memory (rss/heapTotal/heapUsed/external/**arrayBuffers**), **system memory (`ram_system_total`/`ram_system_free` — Android parity)**, CPU user/system per-sample deltas + **normalized `cpu_usage_process` %** (over wall-time/cores), event-loop lag **mean/max/p99** + **utilization** (perf_hooks `monitorEventLoopDelay` + `eventLoopUtilization`). All readers injectable.
- `createNodeSystemEventsSource` — process lifecycle (`process_started` on activate, `process_exiting`, **`process_before_exit`** clean-drain, `process_warning`, and **`process_signal`** for SIGTERM/SIGINT). Signal capture is PASSIVE: it emits the event, and only when the SDK is the sole handler does it remove itself and **re-raise** (`process.kill`) to restore Node's default termination — so it never hangs a process or hijacks an app's own signal handler.
- **`launch(appToken, options)`** — the composition root: builds the transport (internal-tagged), api/uploader/upload pipeline, durable wrapper (when `dataDir`/`bundleStore` present), env builder, store (in-memory / file-backed), resolves the friendly options once via `NODE_OPTION_DEFINITIONS`, registers gated capture providers (console→log; network umbrella with `node:http` folded in; system traces/events) + detection providers, calls `client.launch()`, runs durable `recover()` on start, installs the uncaughtException → `flush(timeout)` → `proc.exit(1)` policy (opt-out `exitOnUncaught`, default `shutdownTimeoutMs: 3000`), and augments `client.stop()` to remove its process listener. Returns the started `BugseeClient`.

Expand All @@ -77,7 +77,7 @@ Read this first; then `docs/design/sdk-design.md` (Draft v3) for the full archit
**`@bugsee/browser`**:
- `buildBrowserEnvironment(input, probe)` — §8.6 envelope (`platform.type: 'web'`) via injectable `BrowserProbe` (navigator/screen/Intl; raw UA as `platform.version` — backend parses; deviceMemory/hardwareConcurrency optional). `optionsToWire` on `sdk.options`.
- `createWindowErrorProvider` / `createUnhandledRejectionProvider` — window `error` → crash / `unhandledrejection` → error; `parseStack` dispatches V8 (`at fn (loc)`) vs SpiderMonkey/JSC (`fn@loc`) dialects (core's `parseLocation` reused).
- `createBrowserSystemTracesSampler` (traces: `ram_js_heap_*` (grouped, Android `ram_jvm_heap` parity) + `connection` (transport name from the viewer's CONNECTION_STATES vocabulary, `navigator.onLine` first) + `orientation` (the Android/iOS `Orientation` int, never the browser's `{type, angle}`) + `battery`/`charging`, each degrading where its API is absent) + `createBrowserSystemEventsSource` (events: `process_started`, `pagehide`→`process_exiting`, `visibilitychange`→`process_foreground`/`process_background`, `online`/`offline`, `orientationchange`→`orientation_changed`). See the capture-completeness milestone in §7.
- `createBrowserSystemTracesSampler` (traces: `ram_js_heap_*` (grouped, Android `ram_jvm_heap` parity) + `connection` (transport name from the viewer's CONNECTION_STATES vocabulary, `navigator.onLine` first) + `orientation` (the Android/iOS `Orientation` int, never the browser's `{type, angle}`), each degrading where its API is absent; `battery`/`charging` moved OUT of the sampler in ST0′ — they come from `BatteryProducer` over `BatteryTracker`, see `createBrowserSystemTraceProducers`) + `createBrowserSystemEventsSource` (events: `process_started`, `pagehide`→`process_exiting`, `visibilitychange`→`process_foreground`/`process_background`, `online`/`offline`, `orientationchange`→`orientation_changed`). See the capture-completeness milestone in §7.
- `createBrowserInputSource` (input: capture-phase/passive DOM listeners → the dedicated **`input`** stream (`input.json`) via the runtime-agnostic `createInputProvider`) — **pointerdown/pointerup/pointercancel** (Pointer Events, so `pointerType` gives the wire `tool` for free and a touchpad correctly reports Mouse; `pointerId` keys the gesture; `button` covers secondary/middle) + **keydown**, emitting the viewer's `RecordingTouchEvent` shape. (`change`/`submit`/`focus` left this stream on 2026-08-31 — they are state-change signals, not device input; see `createUiBreadcrumbSource` below.) PII-safe `describeTarget` (tag/id/class/type/text/selector) masks anything matching the SHARED sensitive-input definition or `[data-bugsee-hidden]`; typed text never captured (AltGr/emoji/IME-robust); **every keystroke aimed at a sensitive/masked field is dropped outright**; throw-isolated. **It does NOT write to `events.user`** — see the `*.user` rule below. See the capture-completeness milestone in §7.
- `createUiBreadcrumbSource` / `createUiBreadcrumbProvider` (**UI breadcrumbs**, 2026-08-31): the state-change half of the DOM interaction split — capture-phase/passive listeners for `change`/`submit`/`focusin` → `client.addBreadcrumb` (so the app's `breadcrumbFilter` runs) in Android `BreadcrumbInputGesture`'s shape: `{type:'user', category:'ui.change'|'ui.submit'|'ui.focus', level:'info', data:{'view.id','view.class','view.tag'}, timestamp: the DOM event's own moment (`performance.timeOrigin + Event.timeStamp`, clamped to now)}`. `type:'user'` is Android's literal for a user-ORIGINATED breadcrumb — **not** the `*.user` stream (these are `breadcrumbs` entries). Why not `input`: `InputEvent.type` is Android's `InputEventStage` (`unknown|begin|move|end|scroll|keydown|keyup`), which a DOM-only value cannot join, and Android already splits its input dispatcher (→ `input.json`) from its gesture dispatcher (→ breadcrumbs). Reuses `describeTarget`; a sensitive/app-masked target **drops the breadcrumb outright** (the focus/change rhythm on a masked field is itself a side channel). Gated by `captureInteractions` (the option means "do not watch what I click and type", whatever stream it lands on). Wired into `@bugsee/browser` **and** `@bugsee/webview` (which also now declares `breadcrumbs` in `hello.caps`).
- `createDomSnapshot` / `createViewtreeSnapshotSource` (view hierarchy: an at-report DOM-tree snapshot → `viewtree`, via the core `reportSnapshots` pull-seam) — reuses `describeTarget` per node + rounded `getBoundingClientRect`; masked subtrees collapse to `{tag, masked, rect}`; bounded (maxNodes/maxDepth) + per-node throw-isolated. Gated by `captureViewHierarchy`. See §7.
Expand Down Expand Up @@ -125,7 +125,7 @@ Pluggable extensions (APM/replay/etc.) remain the contract for non-core features
| Durable bundle queue | `BundleStore` (type) + `createDurableUploadPipeline` | `createNodeBundleStore` (fs files) | IndexedDB-backed |
| Capture sources | `console`, `fetch`, `xhr`, `sendBeacon`, `ws`, `sse`, `webtransport` (self-skip if absent) | `node:http` added via `installNetworkCapture({ additionalSources })` | DOM-specific sources |
| Process events | — | `process` (`uncaughtException`, `unhandledRejection`, `exit`, `warning`) | `window.onerror`, `unhandledrejection` |
| System metrics | `TraceSample[]` sampler injected | `createNodeSystemMetricsSampler` | `performance.memory` etc. |
| System metrics | trace producers over shared trackers (`TraceProducer`); legacy `TraceSample[]` sampler still accepted | `createNodeSystemTraceProducers` (legacy `createNodeSystemMetricsSampler` only as an override) | `createBrowserSystemTracesSampler` (`performance.memory` etc.) + `createBrowserSystemTraceProducers` (battery) |
| Clock / Scheduler / SystemProbe | injectable interfaces in core | real defaults (`Date.now`, `setInterval`, `os.*`) | platform defaults |

Cross-runtime capture interceptors **self-skip when their global is absent**, so `@bugsee/capture` can be imported safely on any runtime; only the runtime-specific *additionalSources* differ.
Expand Down Expand Up @@ -389,8 +389,24 @@ solid), in slices:
`orientation_changed`, alongside `process_started`/`pagehide`. Injected env (window/document/screen),
graceful degradation.
- **CE2 — system TRACES breadth (DONE, `main`):** `createBrowserSystemTracesSampler` adds `connection`
(navigator.connection), `orientation` (screen.orientation), `battery`/`charging` (cached BatteryManager)
to the existing `ram_js_heap_*`. Android trace-name parity; degrades per-API.
(navigator.connection), `orientation` (screen.orientation), `battery`/`charging` (since ST0′ via the
event-driven `BatteryTracker`, not the sampler) to the existing `ram_js_heap_*`. Android trace-name parity; degrades per-API.
- **ST0′ (DONE, `feat/trace-producers`):** `traces.system` is now producers over shared trackers.
`@bugsee/capture` has `TraceProducer`/`TraceProducerBase`; the traces provider is a producer container
(per-producer start/stop isolation, last-value refresh so a rolling window never loses a rarely-changing
trace, legacy `sample` kept). Node: `ProcessStatsTracker` (one timer; memory/system RAM/CPU/event-loop) +
`createNodeSystemTraceProducers` with the Bun/Deno honesty flags on its deps (Deno omits event-loop lag).
Browser: `BatteryTracker` (`getBattery` only on activation, abortable) + `BatteryProducer`; the `charging`
trace is `{ state: 1|2|3 }` (the viewer's table keys), not a boolean. **Bun/Deno** wire the same
`createNodeSystemTraceProducers` from their launches (forwarding the launch `scheduler`/`onError`; Deno adds
`measuresEventLoopDelay: false`); **webview** adopts `createBrowserSystemTraceProducers` next to its sampler.
New launch option **`systemTraceProducers`** (node/bun/deno) — RESET on relaunch, because producer instances
are stateful (a reused instance's `start()` is a no-op). **Refresh applies only to HELD values**:
`TraceSink.emit(…, { hold })` — `emitIfChanged` holds (battery/charging) and is re-recorded every
`refreshMs` = `deriveRefreshMs(window)` (window/4 clamped to [2 s, 10 s]); polled values (cpu/memory/lag/ELU)
and legacy `sample()` rows are never refreshed, so a failed poll leaves a gap instead of a frozen line. Event
-loop lag is omitted until the delay histogram has a sample (no fabricated 0 ms baseline). Legacy samplers
remain until a cleanup slice; remaining browser traces migrate in follow-on slices.
- **CE3 — input capture (DONE, `main`; re-based onto the `input` stream 2026-08-27; the state-change
signals split off to breadcrumbs 2026-08-31):**
`createBrowserInputSource` (browser) — capture-phase, passive, observe-only DOM listeners for
Expand Down Expand Up @@ -676,9 +692,11 @@ Profiler all present).
Deno 2 is node-API-compatible, so `@bugsee/deno` mirrors `@bugsee/bun`: re-exports the ENTIRE `@bugsee/node`
composition (transport, fs storage, node:http capture, crash detection, durable queue + capture recovery,
**CPU profiling, ANR/hang detection**) and overrides only the Deno identity probe (`platform.type: 'deno'`,
version from `Deno.version.deno` with a node-compat fallback) + the shared guarded `perf_hooks` sampler.
version from `Deno.version.deno` with a node-compat fallback) + the guarded `perf_hooks` trace producers.
- **Shared sampler refactor:** the guarded perf_hooks sampler moved from `@bugsee/bun` to `@bugsee/node` as
`createGuardedSystemMetricsSampler` (runtime-agnostic; Bun + Deno both use it; bun's local copy removed).
`createGuardedSystemMetricsSampler` (bun's local copy removed). Since ST0′ neither launch uses it: Bun + Deno
both pass `createNodeSystemTraceProducers` (whose lazy guarded event-loop sources replace it); it remains
exported as a legacy `systemMetricsSampler`.
- **Verified on REAL Deno 2.8.3:** `Deno.version.deno` = '2.8.3' (note: `process.version` is the *node-compat*
version, so the probe reads the Deno global, not `process.versions`); and ALL diagnostics primitives work —
`node:inspector` Profiler (CPU profiling), `worker_threads` + `SharedArrayBuffer` + `Worker#unref` (ANR),
Expand Down
Loading
Loading