Skip to Content
ReferenceTelemetry events

Telemetry Events

This page is the canonical schema for both the OTEL telemetry exported by ftui-runtime/telemetry and the local JSONL evidence sink used for E2E capture and replay. It also documents semantic input events, which are the high-level events the gesture recognizer produces.

Telemetry is opt-in. It requires the ftui-runtime/telemetry Cargo feature plus OTEL_EXPORTER_OTLP_ENDPOINT. With the feature off, this code and its dependencies are excluded from the build. See /reference/feature-flags and /reference/env-vars.

Goals

  • Define a stable, explicit event schema for OTEL spans/events.
  • Establish conservative default redaction of sensitive data.
  • Enable consumers to build dashboards and alerts.
  • Provide clear semantics for user-supplied fields.

Non-goals: performance-oriented micro-tracing, complete UI-state reconstruction from telemetry, real-time streaming without batching.

OTEL Event Categories

Runtime Phase Events

High-level spans for the Elm/Bubbletea runtime loop.

Span NameDescriptionFields
ftui.program.initModel initializationmodel_type, cmd_count
ftui.program.updateSingle update cyclemsg_type, duration_us, cmd_type
ftui.program.viewView renderingduration_us, widget_count
ftui.program.subscriptionsSubscription managementactive_count, started, stopped

Render Pipeline Events

Spans for the render kernel (buffer, diff, presenter).

Span NameDescriptionFields
ftui.render.frameComplete frame cyclewidth, height, duration_us
ftui.render.diffBuffer diff computationchanges_count, rows_skipped, duration_us
ftui.render.presentANSI emissionbytes_written, runs_count, duration_us
ftui.render.flushOutput flushduration_us, sync_mode
ftui.reflow.applyResize application outcomewidth, height, debounce_ms, latency_ms, rate_hz
ftui.reflow.placeholderResize placeholder shownwidth, height, rate_hz

Decision Events

Point-in-time events for auditable decisions.

Event NameDescriptionFields
ftui.decision.degradationDegradation level changedegradation_level, reason_code, budget_remaining
ftui.decision.fallbackCapability fallbackcapability, fallback_to, reason_code
ftui.decision.resizeResize handling decisionstrategy, debounce_active, coalesced, same_size, width, height, rate_hz
ftui.decision.screen_modeScreen mode selectionmode, ui_height, anchor

Runtime Mode Contract Extensions

For the runtime-performance lane, degradation and fallback events are not complete unless they also explain the user-visible service mode:

  • ftui.decision.degradation — must carry runtime_mode, runtime_mode_before, runtime_mode_after, pressure_class, degradation_level, queue_depth, queue_capacity, queue_high_water, coalescing_state, coalesced_count, deferred_count, dropped_count, reason_code, strict_guarantees, degraded_behaviors, recovery_target, signal_surface, and work_disposition. If it closes a degraded interval, it must also carry recovery_completed, recovery_latency_ms, and degraded_interval_ms.
  • ftui.decision.fallback — must carry runtime_mode, rollback_required, operator_action, and reason_code. If the fallback changes active service quality, it must also carry pressure_class, any active degradation_level, and work_disposition.

Input Events

Spans for input processing (redacted by default).

Span NameDescriptionFields
ftui.input.eventInput event processingevent_type (no content!)
ftui.input.macroMacro playbackmacro_id, event_count

Field Schema

Common Fields (All Spans)

service.name string - From OTEL_SERVICE_NAME or "ftui-runtime" service.version string - FrankenTUI version telemetry.sdk string - "ftui-telemetry" host.arch string - Target architecture process.pid int - Process ID

Duration Fields

All duration fields use microseconds (us) for precision:

duration_us u64 - Elapsed time in microseconds

Decision Evidence Fields

Decision events include structured evidence:

decision.rule string - Rule/heuristic applied decision.inputs string - JSON-serialized input state (redacted) decision.action string - Chosen action decision.confidence f32 - Confidence score (0.0-1.0) if applicable

Redaction Policy

Principles

  1. Conservative by default — err on the side of not emitting.
  2. No PII — never emit user input content, file paths, or secrets.
  3. Structural only — emit types and counts, not values.
  4. Opt-in detail — verbose fields require explicit configuration.

Never Emit (Hard Redaction)

CategoryExamples
User input contentKey characters, text buffer contents, passwords
File pathsLog files, config paths, temp files
Environment variablesBeyond OTEL_* and FTUI_* prefixes
Memory addressesPointer values, buffer addresses
Process argumentsCommand-line arguments
User identifiersUsernames, home directories

Conditionally Emit (Soft Redaction)

These are omitted by default but can be enabled via FTUI_TELEMETRY_VERBOSE=true:

CategoryWhen Enabled
Widget typesFull widget type names
Message typesModel::Message enum variants
Command typesCmd enum variants
Capability detailsFull terminal capability report

Always Emit (No Redaction)

CategoryExamples
CountsWidget count, change count, event count
DurationsAll timing measurements
DimensionsBuffer width/height, UI height
Enum variantsScreen mode, degradation level
Boolean flagsMouse enabled, sync available

User-Supplied Field Handling

Custom Span Attributes

Applications may attach custom attributes via tracing:

tracing::info_span!("my_operation", custom.field = "value");

Policy:

  • Prefix requirement: custom fields MUST use a namespace prefix (e.g., app., custom.).
  • No automatic redaction: the application is responsible for not emitting sensitive data.
  • Pass-through: custom fields are passed to the OTEL exporter unchanged.

Custom Events

tracing::info!(target: "app.audit", action = "user_action");

Policy:

  • Filtered by target: only targets matching app.* or custom.* are exported.
  • Rate limiting: custom events are subject to the same batching as built-in events.
  • Documentation: applications should document their custom event schemas.

Schema Versioning

All telemetry includes a schema version:

ftui.schema_version string - Semantic version (e.g., "1.0.0")
LevelPatternRule
Patch1.0.xAdditive only, no breaking changes
Minor1.x.0New fields, deprecated fields still emitted
Majorx.0.0Breaking changes, old fields may be removed

Current version: 1.0.0 (initial stable schema).

Invariants

  1. Redaction completeness — no user input content escapes to telemetry.
  2. Schema stability — breaking changes require a major version bump.
  3. Duration precision — all durations use microseconds.
  4. Deterministic field set — same operation produces same field names.
  5. Bounded cardinality — enum-typed fields have known cardinality.

Failure Modes

ScenarioBehavior
Serialization errorLog warning, omit event
Field value overflowSaturate to max value
Unknown field typeStringify with .to_string()
Custom field collisionPrefix with app.

JSONL Evidence Sink

Deterministic decision logs are emitted via the local JSONL evidence sink. This is distinct from OTEL spans and is intended for E2E capture + replay.

Configuration (see crates/ftui-runtime/src/evidence_sink.rs):

use ftui_runtime::{EvidenceSinkConfig, EvidenceSinkDestination, ProgramConfig}; let config = ProgramConfig::default() .with_evidence_sink( EvidenceSinkConfig::default() .with_enabled(true) .with_destination(EvidenceSinkDestination::file("evidence.jsonl")) .with_flush_on_write(true), );

Notes:

  • Default is disabled.
  • Destination: Stdout or File(path).
  • Flush-on-write defaults to true (recommended for tests).
  • Resize decision JSONL requires CoalescerConfig::with_logging(true).
  • BOCPD JSONL requires BocpdConfig::with_logging(true).

Each line is a single JSON object; field sets are event-specific. The following sections enumerate them.

Event: diff_decision

Required fields:

  • run_id (string)
  • event_idx (u64)
  • strategy (Full | DirtyRows | FullRedraw)
  • cost_full, cost_dirty, cost_redraw
  • posterior_mean, posterior_variance, alpha, beta
  • dirty_rows, total_rows, total_cells
  • span_count, span_coverage_pct, max_span_len
  • fallback_reason, scan_cost_estimate
  • tile_used, tile_fallback
  • tile_w, tile_h, tile_size, tiles_x, tiles_y
  • dirty_tiles, dirty_tile_count, dirty_cells, dirty_tile_ratio, dirty_cell_ratio
  • scanned_tiles, skipped_tiles, skipped_tile_count
  • tile_scan_cells_estimate, sat_build_cost_est
  • bayesian_enabled, dirty_rows_enabled

Runtime defaults (RuntimeDiffConfig): bayesian_enabled = true, dirty_rows_enabled = true, reset_on_resize = true, reset_on_invalidation = true.

Example:

{"event":"diff_decision","run_id":"diff-4242","event_idx":12,"strategy":"DirtyRows","cost_full":4800.0,"cost_dirty":1200.0,"cost_redraw":0.0,"posterior_mean":0.036,"posterior_variance":0.00034,"alpha":3.5,"beta":92.5,"dirty_rows":10,"total_rows":40,"total_cells":4800,"span_count":6,"span_coverage_pct":12.5,"max_span_len":18,"fallback_reason":"none","scan_cost_estimate":600,"tile_used":true,"tile_fallback":"none","tile_w":16,"tile_h":16,"tile_size":256,"tiles_x":5,"tiles_y":3,"dirty_tiles":2,"dirty_tile_count":2,"dirty_cells":40,"dirty_tile_ratio":0.133,"dirty_cell_ratio":0.008,"scanned_tiles":2,"skipped_tiles":13,"skipped_tile_count":13,"tile_scan_cells_estimate":512,"sat_build_cost_est":4800,"bayesian_enabled":true,"dirty_rows_enabled":true}

Notes:

  • span_coverage_pct is a 0–100 percentage of total cells covered by spans (including full rows).
  • fallback_reason values: none, no_spans, no_dirty_rows, span_overflow, full_strategy, full_redraw.

Event: config (resize coalescer)

Required fields:

  • steady_delay_ms, burst_delay_ms, hard_deadline_ms
  • burst_enter_rate, burst_exit_rate
  • cooldown_frames, rate_window_size
  • logging_enabled

Event: decision (resize coalescer)

Required fields:

  • idx, elapsed_ms, dt_ms, event_rate
  • regime (steady | burst)
  • action (apply | apply_forced | apply_immediate | coalesce | skip_same_size)
  • pending_w, pending_h, applied_w, applied_h
  • time_since_render_ms, coalesce_ms, forced

Event: decision_evidence (resize coalescer)

Required fields:

  • log_bayes_factor (float or "inf")
  • regime_contribution, timing_contribution, rate_contribution
  • explanation

Event: bocpd

Schema versioned (schema_version: "bocpd-v1"). Required fields:

  • p_burst, log_bf, obs_ms, regime
  • ll_steady, ll_burst
  • runlen_mean, runlen_var, runlen_mode, runlen_p95, runlen_tail
  • delay_ms (nullable), forced_deadline (nullable)
  • n_obs

Example:

{"schema_version":"bocpd-v1","event":"bocpd","p_burst":0.7321,"log_bf":1.204,"obs_ms":18.0,"regime":"burst","ll_steady":0.001234,"ll_burst":0.056789,"runlen_mean":12.4,"runlen_var":9.1,"runlen_mode":9,"runlen_p95":21,"runlen_tail":0.042,"delay_ms":40,"forced_deadline":false,"n_obs":64}

Event: allocation_budget_config

Required fields: alpha, mu_0, sigma_sq, cusum_k, cusum_h, lambda, window_size.

Event: allocation_budget_evidence

Required fields: frame, x, residual, cusum_plus, cusum_minus, e_value, alert (bool).

Event: mode_transition

For runtime-performance tracking. Required fields:

  • run_id, event_idx
  • runtime_mode
  • runtime_mode_before / runtime_mode_after — one of healthy, stressed, degraded, recovered
  • pressure_class — one of steady_state, input_backpressure, mixed_workload, shutdown_pressure, capability_fallback
  • degradation_level
  • queue_depth, queue_capacity, queue_high_water
  • coalescing_state, coalesced_count, deferred_count, dropped_count
  • reason_code, strict_guarantees, degraded_behaviors, recovery_target, signal_surface, work_disposition

When mode_transition closes a degraded interval, it must also include:

  • recovery_completed, recovery_latency_ms, degraded_interval_ms

Event: recovery_complete

Required fields:

  • all fields required by mode_transition when it closes a degraded interval
  • pending_work_drained

Semantic Input Events

Separately from telemetry, ftui-core also emits semantic input events — high-level gestures derived from raw terminal input. Your Model::update receives both the raw Event stream and the SemanticEvent stream; you choose which to consume per widget.

Architecture

Terminal Input Event (ftui-core) ─ Key, Mouse, Resize, Focus, Paste, Tick GestureRecognizer ─ Click detection, drag tracking, chord sequences SemanticEvent ─ Click, DoubleClick, DragStart, DragEnd, Chord, … Model.update() ─ receives both streams

SemanticEvent variants

pub enum SemanticEvent { // Mouse gestures Click { pos: Position, button: MouseButton }, DoubleClick { pos: Position, button: MouseButton }, TripleClick { pos: Position, button: MouseButton }, LongPress { pos: Position, duration: Duration }, // Drag gestures DragStart { pos: Position, button: MouseButton }, DragMove { start: Position, current: Position, delta: (i16, i16) }, DragEnd { start: Position, end: Position }, DragCancel, // Keyboard gestures Chord { sequence: Vec<ChordKey> }, // Touch-like gestures Swipe { direction: SwipeDirection, distance: u16, velocity: f32 }, }

When to Use Which

ScenarioRawSemantic
Text input field
Button click
Double-click to edit
Drag to reorder items
Scroll wheel handling
Key repeat for movement
Long-press context menu
Vim-like key sequences
Custom gesture detection
Game-like input

Gesture Config Defaults

GestureConfig { multi_click_timeout: Duration::from_millis(300), long_press_threshold: Duration::from_millis(500), drag_threshold: 3, // cells chord_timeout: Duration::from_millis(1000), swipe_velocity_threshold: 50.0, // cells/sec click_tolerance: 1, // cells }

Gesture Invariants

  1. Drag sequences are well-formed. Every DragStart is followed by zero or more DragMove events, ending with exactly one DragEnd or DragCancel.
  2. Click and Drag are mutually exclusive. A mouse-down/mouse-up sequence produces either click events OR drag events, never both.
  3. Click multiplicity is monotonic. Within a multi-click window, you see ClickDoubleClickTripleClick in order.
  4. Chords are non-empty. A Chord event always contains at least two keys.
  5. Focus loss cancels drags. Losing window focus emits DragCancel if a drag was in progress.

Failure Modes

FailureCauseResult
Chord timeoutKeys pressed too slowlyNo chord emitted; raw keys pass through
Focus loss mid-dragWindow deactivatedDragCancel emitted
Escape mid-dragUser pressed EscapeDragCancel emitted
Click outside thresholdMovement beyond click_toleranceResets to single click

See Also