On-screen debugger UI component for TV apps built with React and the Accedo XDK.
@accedo/onscreen-debugger is a non-intrusive, overlay-style debugging panel designed for connected-TV and set-top-box applications. It intercepts console methods and network calls at runtime and surfaces the captured data in a TV-navigable modal UI — without requiring a laptop or remote DevTools connection.
The tool is fully gated by environment: it is a no-op in production builds and can be toggled on/off per session via a key sequence on the remote control.
| Category | Detail |
|---|---|
| Console capture | Wraps console.log, debug, info, warn, and error. Each entry records the call site (file + function name). |
| Network capture | Intercepts fetch, XMLHttpRequest, and navigator.sendBeacon. Uses a hybrid PerformanceObserver + wrapper approach to merge request detail with resource timing data (duration, transfer size, etc.). Falls back gracefully when PerformanceObserver is unavailable. |
| Analytics filters | Built-in URL filters for DAL, SAS, and Logstash traffic. |
| TV-native navigation | Dedicated LRUD (directional focus management) instance isolated from the host app's own navigation tree. Arrow keys and OK/BACK are handled entirely within the modal. |
| Key sequence trigger | Press ← ← ← → ← to open/focus the modal without disrupting the host app's remote-control flow. |
| Persistent state | Recording flags, enabled mode, and quick-key-sequence setting are persisted to localStorage via Zustand. |
| Auto-refresh | The entry list polls every 2 s by default; auto-scroll to the latest entry is also configurable. |
| Three visibility modes | hidden — modal not rendered; not-focusable — modal visible in the background; focusable — modal takes full remote-control focus. |
| Entry limits | 100 entries per console type (oldest 50% flushed when full); 500 entries for network traffic. |
| Dependency | Version |
|---|---|
| Node.js | >= 14.16.0 (project is developed and tested on 14.16.1) |
| React | >= 17 |
@accedo/xdk-core |
>= 4.0.0 |
@accedo/xdk-virtual-key |
>= 4.0.0 |
The host application must provide a working XDK environment (key-event dispatch, environment.addEventListener, etc.).
npm install @accedo/onscreen-debuggerThe package is published to the Accedo Artifactory registry. Make sure your
.npmrcpoints tohttps://repo.cloud.accedo.tv/artifactory/api/npm/accedo-ps-npm-local/for the@accedoscope.
Call useOnScreenDebugger once, near the top of your component tree. This hook installs all the console and network interceptors and registers the key-sequence listener.
import { useOnScreenDebugger } from '@accedo/onscreen-debugger';
const App = () => {
useOnScreenDebugger();
return <YourApp />;
};Optional networkUrlPatterns (NetworkApiUrlPatternsFamily[]): each item is one toolbar section. Within a family, each key of urlPatterns is a filter button; each value is a substring matched with String.includes against the request URL. The last button in that section is Every ${name} (union of all patterns in that family). Omit the option or pass [] to hide all Network API sections. Prefer a useMemo’d array when passing an inline literal so the hook does not reset on every render.
import { useMemo } from 'react';
import { useOnScreenDebugger } from '@accedo/onscreen-debugger';
const families = useMemo(
() => [
{
name: 'Analytics',
urlPatterns: {
myAnalytics: 'analytics.example.com',
billingApi: '/api/billing',
},
},
{
name: 'CDN',
id: 'cdn',
urlPatterns: { assets: '/static/' },
},
],
[]
);
useOnScreenDebugger({ networkUrlPatterns: families });Composite filter ids (for custom UIs reading the store) use familyId, a NUL delimiter (\u0000), then either a pattern key or __every__ for the aggregate. Helpers buildNetworkApiFilterMode, parseNetworkApiFilterMode, and constants NETWORK_API_FILTER_DELIMITER / NETWORK_API_EVERY_SUBMODE are exported from the package.
Place <OnScreenDebugger /> anywhere in the tree — it renders nothing when hidden and takes over the full viewport when active.
import { OnScreenDebugger } from '@accedo/onscreen-debugger';
const App = () => {
useOnScreenDebugger();
return (
<>
<YourApp />
<OnScreenDebugger />
</>
);
};import { useToggleDebugModal } from '@accedo/onscreen-debugger';
const MyComponent = () => {
const { toggleDebugModal } = useToggleDebugModal();
return (
<button onClick={() => toggleDebugModal('focusable')}>
Open Debugger
</button>
);
};import {
useIsDebuggerEnabled,
useDebugModalVisibility,
getDebugModalVisibilitySync,
getIsOnScreenDebuggerActiveSync,
} from '@accedo/onscreen-debugger';If you build your own UI for captured data instead of (or in addition to) <OnScreenDebugger />, your data components can import useOnScreenDebuggerStore only—they do not need useOnScreenDebugger. Subscribe with Zustand selectors for the same slices the modal uses (log, debug, info, warn, error, networkTraffic, networkApiUrlPatternFamilies, plus any other fields on the store you need).
Interceptors still have to be installed once for the store to fill: call useOnScreenDebugger() a single time near the root (see Usage 1). This package does not expose a separate installer; skip mounting <OnScreenDebugger /> only when your UI fully replaces it.
// App (or another top-level module): install capture — once
import { useOnScreenDebugger } from '@accedo/onscreen-debugger';
const App = () => {
useOnScreenDebugger();
return (
<>
<YourApp />
<MyCustomDebugPanel />
</>
);
};// MyCustomDebugPanel.tsx — data only, no useOnScreenDebugger import
import { useOnScreenDebuggerStore } from '@accedo/onscreen-debugger';
const MyCustomDebugPanel = () => {
const logs = useOnScreenDebuggerStore(s => s.log);
const debugs = useOnScreenDebuggerStore(s => s.debug);
const infos = useOnScreenDebuggerStore(s => s.info);
const warns = useOnScreenDebuggerStore(s => s.warn);
const errors = useOnScreenDebuggerStore(s => s.error);
const networkTraffic = useOnScreenDebuggerStore(s => s.networkTraffic);
// Render logs, debugs, infos, warns, errors, networkTraffic (LogEntry[] / your types) as you like
return null;
};Outside React (e.g. logging, tests, or non-UI modules), read the latest snapshot with useOnScreenDebuggerStore.getState() and the same property names (log, debug, info, warn, error, networkTraffic, recording flags, etc.). See the store-backed helpers in the API Reference for other reactive accessors.
Installs all runtime interceptors and key-sequence listener. Must be called once in a mounted React component. Is a no-op when NODE_ENV === 'production' or the debugger mode is 'off'.
Optional argument: { networkUrlPatterns?: NetworkApiUrlPatternsFamily[] }. When non-empty, the modal renders one “Filter by Network API — {name}” section per family (see Usage 1). Normalized families are mirrored on the store as networkApiUrlPatternFamilies; they are not persisted to localStorage.
Exported types: UseOnScreenDebuggerOptions, NetworkApiUrlPatternsFamily, NormalizedNetworkApiUrlPatternsFamily.
Returns { toggleDebugModal(visibility: DebugModalVisibility) }. Use to switch between 'hidden', 'not-focusable', and 'focusable'.
Returns the current OnScreenDebuggerMode ('off' | 'active-on-start' | 'active').
Returns the current DebugModalVisibility as a reactive value.
Direct access to the full Zustand store. Useful for reading or updating recording flags, flushing entries, etc.
The full debugger modal. Renders null when debugModalVisibility === 'hidden'. No props required.
type DebugModalVisibility = 'hidden' | 'not-focusable' | 'focusable';
type OnScreenDebuggerMode = 'off' | 'active-on-start' | 'active';
type LogEntry = {
params: string[];
extraParams?: {
networkTraffic?: FetchEntryType;
options?: Record<string, any>;
log?: string[];
};
callSite?: CallSiteInfo;
time: number;
type: 'log' | 'debug' | 'info' | 'warn' | 'error' | 'networkTraffic';
id: string;
};| Variable | Default | Description |
|---|---|---|
NODE_ENV |
— | Set to 'production' to fully disable all interceptors and UI. |
APP_ENV |
'dev' |
Controls whether the initial debugger mode reads from localStorage (!== 'production') or is forced 'off'. |
| Input | Action |
|---|---|
← ← ← → ← |
Toggle the modal to focusable mode (non-production only). |
↑ ↓ ← → |
Navigate entries and toolbar buttons while modal is focused. |
OK |
Open entry details / toggle super-expanded details view. |
BACK |
Step back through focus levels: details → entry list → toolbar → return to app. |
The following store keys are persisted to localStorage under the key on-screen-debugger:
isEnabledquickKeySequencerecordLog,recordDebug,recordInfo,recordWarn,recordErrorrecordNetworkTraffic
Log entries and network traffic are not persisted (in-memory only).
# Install dependencies
npm install
# Build (ESM + CJS bundles + type declarations)
npm run build
# Watch mode (Rollup)
npm run dev
# Type check only
npm run typecheck
# Lint TypeScript
npm run lint
npm run lint:fix
# Lint SCSS
npm run lint:styles
npm run lint:styles:fix
# Format all source files
npm run format
# Check formatting without writing
npm run format:checkHusky runs lint-staged on pre-commit: ESLint + Prettier on staged .ts / .tsx files, and Stylelint + Prettier on staged .scss files.
The build uses Rollup with @rollup/plugin-typescript, rollup-plugin-postcss, and Sass. .scss files are compiled as CSS Modules (localsConvention: 'dashesOnly') and emitted into the JS bundles with runtime style injection. After Rollup, type declarations are emitted with tsc --project tsconfig.build.json.
Two entry points are published:
| Entry | Import path |
|---|---|
| Main (hooks, store, types) | @accedo/onscreen-debugger |
| Component only | @accedo/onscreen-debugger/dist/OnScreenDebugger |
Internal Accedo package — not for public distribution.