@roku-sdk/ai 0.0.0-stage → 2.19.0
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.
- package/LICENSE +21 -0
- package/README.md +88 -2
- package/package.json +47 -3
- package/skills/ads-and-raf/SKILL.md +231 -0
- package/skills/ads-and-raf/references/integration-flows.md +332 -0
- package/skills/app-packaging/SKILL.md +107 -0
- package/skills/app-performance/SKILL.md +133 -0
- package/skills/cli-dev-loop/SKILL.md +217 -0
- package/skills/cli-dev-loop/references/logs-and-crashes.md +114 -0
- package/skills/cpu-and-heap-profiling/SKILL.md +108 -0
- package/skills/cpu-and-heap-profiling/references/reading-profiles.md +52 -0
- package/skills/data-entry-keyboards/SKILL.md +160 -0
- package/skills/device-management/SKILL.md +158 -0
- package/skills/dialogs-and-modals/SKILL.md +210 -0
- package/skills/effects/SKILL.md +183 -0
- package/skills/fetch-and-data-loading/SKILL.md +164 -0
- package/skills/fetch-and-data-loading/references/https-and-certificates.md +79 -0
- package/skills/flex-grid/SKILL.md +236 -0
- package/skills/flex-grid/references/focus-integration.md +355 -0
- package/skills/flex-grid/references/plugins.md +201 -0
- package/skills/focus-management/SKILL.md +210 -0
- package/skills/focus-management/references/focus-recipes.md +290 -0
- package/skills/fonts-and-glyphs/SKILL.md +231 -0
- package/skills/fonts-and-glyphs/references/glyph-tables.md +128 -0
- package/skills/logging-and-errors/SKILL.md +227 -0
- package/skills/logging-and-errors/references/uncaught-exceptions.md +137 -0
- package/skills/mcp-ai-dev-loop/SKILL.md +189 -0
- package/skills/mcp-ai-dev-loop/references/crash-and-console.md +68 -0
- package/skills/mcp-ai-dev-loop/references/live-debugging.md +129 -0
- package/skills/mcp-setup/SKILL.md +131 -0
- package/skills/project-setup/SKILL.md +210 -0
- package/skills/project-setup/references/roku-config.md +120 -0
- package/skills/screen-navigation/SKILL.md +166 -0
- package/skills/sdk-components/SKILL.md +213 -0
- package/skills/sdk-components/references/dialogs.md +148 -0
- package/skills/sdk-components/references/scenegraph-node-spelling.md +123 -0
- package/skills/sdk-installation/SKILL.md +129 -0
- package/skills/slideshow/SKILL.md +157 -0
- package/skills/slideshow/references/focus-and-handoff.md +187 -0
- package/skills/solid-on-roku/SKILL.md +181 -0
- package/skills/solid-on-roku/references/hermes-and-exports.md +130 -0
- package/skills/testing/SKILL.md +147 -0
- package/skills/testing/references/async-and-timing.md +97 -0
- package/skills/user-accounts-and-purchases/SKILL.md +164 -0
- package/skills-manifest.json +316 -0
- package/skills-manifest.schema.json +62 -0
- package/src/__tests__/manifest.test.ts +267 -0
- package/src/__tests__/snippets.test.ts +221 -0
- package/src/__tests__/trigger-routing.test.ts +77 -0
- package/src/index.ts +36 -0
- package/src/manifest.ts +215 -0
- package/src/router.ts +147 -0
- package/src/snippets.ts +212 -0
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
# Fonts and Glyphs
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
Two questions decide whether text in an RSG SDK app renders the way you meant: which typeface is
|
|
6
|
+
supplying the characters, and whether that typeface actually has them. Get the first wrong and
|
|
7
|
+
text comes out at a size nobody can read, or ignores the size you set. Get the second wrong and
|
|
8
|
+
you ship empty boxes — and the boxes appear on a device you did not test, because the firmware's
|
|
9
|
+
fallback font hides the problem everywhere else.
|
|
10
|
+
|
|
11
|
+
## When to Use
|
|
12
|
+
|
|
13
|
+
- text rendering as empty boxes, on any device or only on some, and whether `fallbackGlyph` helps
|
|
14
|
+
- symbols that were fine until the app started shipping a typeface of its own
|
|
15
|
+
- a symbol needed in a label — arrow, chevron, star, check mark, remote-button icon
|
|
16
|
+
- text that renders far too small, or ignores the size you set
|
|
17
|
+
- shipping a `.ttf` or `.otf` with the app, and a build that refuses two of them
|
|
18
|
+
- deciding whether a shape should be a character at all
|
|
19
|
+
|
|
20
|
+
### When not to use
|
|
21
|
+
|
|
22
|
+
| Situation | Use instead | Because |
|
|
23
|
+
|---|---|---|
|
|
24
|
+
| Which node draws the text — `Label`, `SimpleLabel`, `ScrollingLabel`, `MultiStyleLabel` | The `sdk-components` skill | This skill owns the typeface, that one owns the node |
|
|
25
|
+
| Brand marks, multi-colour or gradient artwork | The `sdk-components` skill | That is a `Poster` and an image, not a character |
|
|
26
|
+
| Where an asset file belongs in the project before anything can import it | The `project-setup` skill | Project layout is that skill's job |
|
|
27
|
+
|
|
28
|
+
## Read the reference first for this task
|
|
29
|
+
|
|
30
|
+
| If the request is | Read now, before answering |
|
|
31
|
+
|---|---|
|
|
32
|
+
| Naming a specific glyph constant, or auditing which ones survive a custom font | `references/glyph-tables.md` |
|
|
33
|
+
|
|
34
|
+
The body tells you which *group* a symbol falls in and what that means. The 45 constants, their
|
|
35
|
+
codepoints and their three groups are only in the reference. Guessing a constant name is the one
|
|
36
|
+
mistake here that compiles.
|
|
37
|
+
|
|
38
|
+
## Quick Reference
|
|
39
|
+
|
|
40
|
+
| Job | Reach for |
|
|
41
|
+
|---|---|
|
|
42
|
+
| Any arrow, chevron, star, tick, or remote-button icon | A named constant from `@roku-sdk/components/glyphs` — never a typed or escaped literal |
|
|
43
|
+
| A character you can simply type (`·`, `°`, currency, quotes, maths) | Type it. These get no constants on purpose |
|
|
44
|
+
| A brand mark or anything multi-colour | Not a character at all — that is `sdk-components` territory |
|
|
45
|
+
| Set a typeface on a `Label` | A `Font` node in the `font` prop, with **both** `uri` and `size` |
|
|
46
|
+
| `Font.size` | Points. **Defaults to 1**, which is why an otherwise correct font renders as a line of dots |
|
|
47
|
+
| Set a typeface on a `SimpleLabel` | `fontUri` plus `fontSize` — no `Font` node |
|
|
48
|
+
| Built-in font names | Ten. The eight `font:…SystemFont` names are **fixed size and ignore `fontSize`**; only `font:SystemFontFile` and `font:BoldSystemFontFile` respect it |
|
|
49
|
+
| Ship a typeface with the app | `import fontUri from "./fonts/whatever.ttf"` — the import resolves to the packaged path at build time |
|
|
50
|
+
| Two font files with the same filename | A build error if their contents differ, reuse if identical. Rename one |
|
|
51
|
+
| Wait until a font can be measured | `whenFontReady(ref)` or `createFontReady(fontRef)` from `@roku-sdk/rsg-ts/runtime` — never a fixed timer |
|
|
52
|
+
| Text arriving from outside with characters your font lacks | Constrain the text or widen the typeface. **`Font.fallbackGlyph` does not substitute anything on current firmware** — see instruction 8 |
|
|
53
|
+
| A `Font` with a `size` and no `uri` | Draws nothing at all. It does not fall back to the system font — omit the `Font` node instead |
|
|
54
|
+
| Which symbols survive a custom app font | The 32 private-use glyphs and `●` `★` `☆`. The other ten do not |
|
|
55
|
+
|
|
56
|
+
## Instructions
|
|
57
|
+
|
|
58
|
+
1. **Take symbols from the constants, not from your keyboard or an escape.** Import by name from
|
|
59
|
+
`@roku-sdk/components/glyphs` and interpolate. A literal like `"\uE025"` is the same character
|
|
60
|
+
today and an unlabelled magic number the moment the font revision moves; the constant for that
|
|
61
|
+
one is `LEFT_BUTTON_GLYPH`.
|
|
62
|
+
|
|
63
|
+
2. **Know the one fact that decides whether a symbol renders: which font supplies it.** The
|
|
64
|
+
constants are grouped by that, not by codepoint:
|
|
65
|
+
- **RokuFont01 private-use glyphs** (32 of them) — RokuFont01 is the firmware's system-wide
|
|
66
|
+
fallback font, so these render under a system font *and* under a custom app font.
|
|
67
|
+
- **Unicode characters that RokuFont01 backs** — `●` `★` `☆`. Same fallback, so also safe.
|
|
68
|
+
- **RokuUI-Regular symbols** (10 of them — the arrows, dashes, bullet, ellipsis, tick and
|
|
69
|
+
ballot X). RokuUI-Regular is the default UI font but is **not** in the fallback chain. Under
|
|
70
|
+
a system font they are fine; under a custom app font they get no backstop and go tofu unless
|
|
71
|
+
your font happens to supply them.
|
|
72
|
+
|
|
73
|
+
So the counter-intuitive part: if your app ships a typeface, the constants that need care are
|
|
74
|
+
the plain-Unicode-looking ones, not the exotic private-use ones.
|
|
75
|
+
|
|
76
|
+
3. **Remember that a custom font wins outright where it defines a codepoint.** No fallback occurs.
|
|
77
|
+
A licensed font carrying its own private-use glyphs at `U+E0xx` will render *those* in place of
|
|
78
|
+
the Roku icons, which looks like corruption rather than a font choice.
|
|
79
|
+
|
|
80
|
+
4. **Give a `Font` node a `size`, always.** `Font.size` is in points and defaults to **1**. A
|
|
81
|
+
`Font` with a correct `uri` and no `size` is the classic "the font loaded but the text is
|
|
82
|
+
unreadable" report. Set both together and treat them as one prop.
|
|
83
|
+
|
|
84
|
+
5. **On `SimpleLabel`, use `fontUri` and `fontSize` — and know which built-in names ignore the
|
|
85
|
+
size.** Of the ten built-in names, eight are fixed-size (`font:Smallest…`, `font:Small…`,
|
|
86
|
+
`font:Medium…`, `font:Large…`, each plain and `Bold`) and **discard `fontSize` silently**. Only
|
|
87
|
+
`font:SystemFontFile` and `font:BoldSystemFontFile`, and any font file of your own, respect it.
|
|
88
|
+
If a size change does nothing, the name is the reason.
|
|
89
|
+
|
|
90
|
+
6. **Import font files; do not write packaged paths by hand.** `import fontUri from
|
|
91
|
+
"./fonts/inter.ttf"` gives you a string that is the packaged path, and the build copies the
|
|
92
|
+
file for you. Both `.ttf` and `.otf` are declared. Writing the path as a literal skips the copy
|
|
93
|
+
and ships a reference to a file that is not there.
|
|
94
|
+
|
|
95
|
+
7. **Expect a build error when two font files share a filename.** The build keys them by filename
|
|
96
|
+
alone, so two files called `Inter.ttf` in different directories collide. Identical contents are
|
|
97
|
+
silently reused; different contents fail the build with a `Font naming conflict` naming both
|
|
98
|
+
sources. Rename one — the directory does not disambiguate them.
|
|
99
|
+
|
|
100
|
+
8. **Do not plan around `fallbackGlyph`.** `Font.fallbackGlyph` reads like the answer to
|
|
101
|
+
uncontrollable text — a character of your choosing instead of a box — and on current firmware
|
|
102
|
+
it does not deliver one. Measured on a Roku Native running 16.0.4: set as a bare `u25CF` (the
|
|
103
|
+
form the SDK's own documentation shows) and as a literal `●`, against both a symbol only the
|
|
104
|
+
default UI font has and a codepoint no font on the device has, the boxes were unchanged in
|
|
105
|
+
every combination. The property is real and the SDK does forward it; what is missing is any
|
|
106
|
+
visible effect. Treat unsupported characters as something to prevent, not something to
|
|
107
|
+
substitute: constrain the text, or ship a typeface that covers it.
|
|
108
|
+
|
|
109
|
+
9. **Do not use firmware placeholder syntax for app text.** Translated firmware strings may
|
|
110
|
+
contain tokens like `{BACK_BUTTON_GLYPH}`. Those are runtime text tokens resolved by the
|
|
111
|
+
firmware's own string layer, not TypeScript symbols; putting one in a label renders the braces
|
|
112
|
+
literally. Interpolate the imported constant instead.
|
|
113
|
+
|
|
114
|
+
10. **Wait for a font with `whenFontReady`, not a timer.** `getFontMeasure()` throws
|
|
115
|
+
`"Font is not yet loaded"` until the GfxFont is published. Current firmware has no Font
|
|
116
|
+
`loadStatus` field; use `whenFontReady(ref)` (promise) or `createFontReady(fontRef)`
|
|
117
|
+
(Solid accessor that cancels on cleanup; pass `() => props.uri` or similar as its second
|
|
118
|
+
argument when the `<font>` node's `uri` or `size` can change) from `@roku-sdk/rsg-ts/runtime`, which keep
|
|
119
|
+
working when one ships. Do not copy the fixed 100ms / 500ms timer stubs from older
|
|
120
|
+
examples — those race on a cold cache. A `<font>` that is not on a drawn label is only
|
|
121
|
+
published if `preUpdate` is set before `uri` and `size`; without it the font never loads
|
|
122
|
+
and the wait never resolves.
|
|
123
|
+
|
|
124
|
+
## Measuring a font
|
|
125
|
+
|
|
126
|
+
`whenFontReady` and `createFontReady` ship in SDK 2.19.0. On 2.18.0 and earlier they are
|
|
127
|
+
not exported from `@roku-sdk/rsg-ts/runtime`.
|
|
128
|
+
|
|
129
|
+
Capture the Font node's `TmNodeRef` from `ref={node => … node.r}`, then wait before measuring:
|
|
130
|
+
|
|
131
|
+
```tsx rsg-skill=complete
|
|
132
|
+
import { getFontMeasure, whenFontReady, createFontReady } from "@roku-sdk/rsg-ts/runtime";
|
|
133
|
+
import { createEffect, createSignal, type Component } from "@roku-sdk/solid";
|
|
134
|
+
import type { TmNodeRef } from "@roku-sdk/interfaces";
|
|
135
|
+
|
|
136
|
+
export const MeasuredTitle: Component<{ text: string; uri: string }> = props => {
|
|
137
|
+
const [fontRef, setFontRef] = createSignal<TmNodeRef>();
|
|
138
|
+
// The key restarts the wait when `uri` changes on this same <font> node.
|
|
139
|
+
const fontReady = createFontReady(fontRef, () => props.uri);
|
|
140
|
+
const [width, setWidth] = createSignal(0);
|
|
141
|
+
|
|
142
|
+
createEffect(() => {
|
|
143
|
+
if (!fontReady()) return;
|
|
144
|
+
const ref = fontRef();
|
|
145
|
+
if (!ref) return;
|
|
146
|
+
setWidth(getFontMeasure().getOneLineWidth(ref, props.text, 1920));
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
return (
|
|
150
|
+
<label
|
|
151
|
+
text={props.text}
|
|
152
|
+
width={width() || 600}
|
|
153
|
+
height={48}
|
|
154
|
+
color="0xFFFFFFFF"
|
|
155
|
+
font={<font uri={props.uri} size={32} ref={node => setFontRef(node.r)} />}
|
|
156
|
+
/>
|
|
157
|
+
);
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
// Imperative form (same wait, no Solid helper):
|
|
161
|
+
async function measureOnce(ref: TmNodeRef, text: string): Promise<number> {
|
|
162
|
+
await whenFontReady(ref);
|
|
163
|
+
return getFontMeasure().getOneLineWidth(ref, text, 1920);
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Example
|
|
168
|
+
|
|
169
|
+
A row of chevrons and a rating, using a bundled typeface — imported, sized, and with a fallback
|
|
170
|
+
character for anything the typeface lacks.
|
|
171
|
+
|
|
172
|
+
```tsx rsg-skill=complete
|
|
173
|
+
import { BLACK_STAR, LEFT_BUTTON_GLYPH, RIGHT_BUTTON_GLYPH, WHITE_STAR } from "@roku-sdk/components/glyphs";
|
|
174
|
+
import { Font, Label, LayoutGroup } from "@roku-sdk/components";
|
|
175
|
+
|
|
176
|
+
import titleFontUri from "./fonts/title.ttf";
|
|
177
|
+
|
|
178
|
+
function Rating(props: { filled: number; total: number }) {
|
|
179
|
+
return (
|
|
180
|
+
<label
|
|
181
|
+
text={BLACK_STAR.repeat(props.filled) + WHITE_STAR.repeat(props.total - props.filled)}
|
|
182
|
+
width={200}
|
|
183
|
+
height={48}
|
|
184
|
+
color="0xFFFFFFFF"
|
|
185
|
+
/>
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function CarouselChrome(props: { title: string; rating: number }) {
|
|
190
|
+
return (
|
|
191
|
+
<layoutGroup layoutDirection="horiz" itemSpacings={[16]}>
|
|
192
|
+
{/* Private-use glyphs: the firmware fallback covers these even under a custom font. */}
|
|
193
|
+
<label text={LEFT_BUTTON_GLYPH} width={48} height={48} color="0xFFFFFFFF" />
|
|
194
|
+
<label
|
|
195
|
+
text={props.title}
|
|
196
|
+
width={600}
|
|
197
|
+
height={48}
|
|
198
|
+
color="0xFFFFFFFF"
|
|
199
|
+
// `size` is not optional in practice -- its default is 1 point,
|
|
200
|
+
// and a `Font` without a `uri` draws nothing at all.
|
|
201
|
+
font={<font uri={titleFontUri} size={32} />}
|
|
202
|
+
/>
|
|
203
|
+
<Rating filled={props.rating} total={5} />
|
|
204
|
+
<label text={RIGHT_BUTTON_GLYPH} width={48} height={48} color="0xFFFFFFFF" />
|
|
205
|
+
</layoutGroup>
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## Anti-patterns
|
|
211
|
+
|
|
212
|
+
Keyed by what you see on screen, because that is what arrives first.
|
|
213
|
+
|
|
214
|
+
| Symptom | Cause | Fix |
|
|
215
|
+
|---|---|---|
|
|
216
|
+
| Text shows empty boxes where symbols should be | The character is in neither font — geometric arrows like `◀` `▶` are the usual culprit | Use the named constant; `LEFT_ARROW` and friends are the shapes that exist |
|
|
217
|
+
| Arrows, ticks and dashes vanished after the app shipped its own typeface | Those are RokuUI-Regular symbols and get no RokuFont01 backstop under a custom font | Keep them in system-font text, or make the custom typeface supply them |
|
|
218
|
+
| Icons turned into unrelated shapes under a custom typeface | The custom font defines the same private-use codepoints and wins outright | Use a font without private-use glyphs, or draw those icons in system-font text |
|
|
219
|
+
| Text renders at size 1 — a readable string, unreadably small | `Font.size` was left at its default | Set `size` whenever you set `uri` |
|
|
220
|
+
| Changing the size does nothing at all | The `fontUri` names one of the eight fixed-size built-ins | Switch to `font:SystemFontFile` or a font file |
|
|
221
|
+
| Characters from a backend show as boxes and `fallbackGlyph` changed nothing | It has no visible effect on current firmware, in either value form | Constrain the text or widen the typeface; do not keep tuning the value |
|
|
222
|
+
| A label went completely blank after a `Font` node was added | The `Font` has a `size` but no `uri`, which draws nothing rather than falling back | Give it a `uri`, or drop the `Font` node and let the system font apply |
|
|
223
|
+
| The build fails naming two font files | Same filename, different contents, keyed by filename | Rename one of them |
|
|
224
|
+
| A private-use escape like `"\uE0nn"` sits in app code | The constant exists and the literal does not say which symbol it is | Import the constant |
|
|
225
|
+
| Braces render literally in a label | Firmware placeholder syntax used as app text | Interpolate the constant |
|
|
226
|
+
| `getFontMeasure` throws `"Font is not yet loaded"` or returns 0 | Measured before the GfxFont published; a fixed timer was too short | `await whenFontReady(ref)` or `createFontReady(fontRef)` — never a fixed timer |
|
|
227
|
+
|
|
228
|
+
The glyph groups here are device-verified against firmware 15.3.5 on a Roku Express 4K+ — every
|
|
229
|
+
constant rendered in a label under a custom typeface. Re-verify on a device rather than re-wording
|
|
230
|
+
if you need to change what they claim. `IFontProps` and `ISimpleLabelProps` carry TSDoc in the
|
|
231
|
+
installed package; prefer it over any prose, including this file, when the two disagree.
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# Glyph tables
|
|
2
|
+
|
|
3
|
+
45 constants, from `@roku-sdk/components/glyphs`. They are split into three tables here for the
|
|
4
|
+
one reason that matters at runtime — **which font supplies the character** — because that, not the
|
|
5
|
+
codepoint, decides whether a symbol survives a custom app typeface.
|
|
6
|
+
|
|
7
|
+
| Group | Count | Under a system font | Under a custom app font |
|
|
8
|
+
|---|---|---|---|
|
|
9
|
+
| RokuFont01 private-use glyphs | 32 | renders | renders — firmware fallback applies |
|
|
10
|
+
| Unicode characters RokuFont01 backs | 3 | renders | renders — same fallback |
|
|
11
|
+
| RokuUI-Regular symbols | 10 | renders | **tofu**, unless the custom font supplies them |
|
|
12
|
+
|
|
13
|
+
35 of the 45 are safe under a custom typeface. The ten that are not are the ones that look most
|
|
14
|
+
ordinary, which is why this split is worth carrying.
|
|
15
|
+
|
|
16
|
+
Verified on device twice, on different hardware and different firmware, by rendering the
|
|
17
|
+
constants in a label with a custom TrueType font.
|
|
18
|
+
|
|
19
|
+
- Roku Express 4K+, firmware 15.3.5: the private-use glyphs and the stars came through; `←` `↑`
|
|
20
|
+
`→` `↓` `✓` `✗` were empty boxes. `•` `…` `–` `—` rendered only because that particular font
|
|
21
|
+
happened to contain them — do not read that as a guarantee.
|
|
22
|
+
- Roku Native (2910X), firmware 16.0.4, 1080p: same split. Under the system font every constant
|
|
23
|
+
rendered; under a custom TrueType the button glyphs and `★` `☆` rendered and `←` `→` `✓` were
|
|
24
|
+
boxes. The same run also showed a `Font` with a `uri` and no `size` drawing a speck, and
|
|
25
|
+
`fallbackGlyph` — as `u25CF` and as a literal `●` — leaving every box exactly as it was, both
|
|
26
|
+
for a RokuUI-only symbol and for a codepoint (`U+0E5B`, `U+A64B`) that no font on the device
|
|
27
|
+
has.
|
|
28
|
+
|
|
29
|
+
The two runs agree, which is the point of having both: this split is a property of the firmware's
|
|
30
|
+
font chain, not of one box.
|
|
31
|
+
|
|
32
|
+
## 1. RokuFont01 — private-use glyphs (safe under a custom font)
|
|
33
|
+
|
|
34
|
+
RokuFont01 is wired in as the system-wide fallback font in firmware, so these resolve wherever
|
|
35
|
+
they appear.
|
|
36
|
+
|
|
37
|
+
| Constant | Codepoint | Placeholder | Description |
|
|
38
|
+
|---|---|---|---|
|
|
39
|
+
| `OPTIONS_GLYPH` | `U+E000` | `{OPTIONS_GLYPH}` | Options / star (no border) |
|
|
40
|
+
| `OK_BUTTON_GLYPH` | `U+E001` | `{OK_BUTTON_GLYPH}` | OK in circle |
|
|
41
|
+
| `OPTIONS_BUTTON_GLYPH` | `U+E002` | `{OPTIONS_BUTTON_GLYPH}` | Options / star (with border) |
|
|
42
|
+
| `MICROPHONE_GLYPH` | `U+E003` | `{MICROPHONE_GLYPH}` | Microphone (no border) |
|
|
43
|
+
| `PLAY_PAUSE_BUTTON_GLYPH` | `U+E004` | `{PLAY_PAUSE_BUTTON_GLYPH}` | Play/pause (with border) |
|
|
44
|
+
| `PLAY_PAUSE_GLYPH` | `U+E005` | `{PLAY_PAUSE_GLYPH}` | Play/pause (no border) |
|
|
45
|
+
| `DOT_GLYPH` | `U+E006` | `{DOT_GLYPH}` | Baseline-aligned dot (icon-row form) |
|
|
46
|
+
| `HOME_GLYPH` | `U+E007` | `{HOME_GLYPH}` | Home (no border) |
|
|
47
|
+
| `SEARCH_GLYPH` | `U+E008` | `{SEARCH_GLYPH}` | Search / magnifying glass |
|
|
48
|
+
| `REMOTE_GLYPH` | `U+E009` | `{REMOTE_GLYPH}` | Remote control |
|
|
49
|
+
| `REPLAY_GLYPH` | `U+E010` | `{REPLAY_GLYPH}` | Instant replay |
|
|
50
|
+
| `MICROPHONE_BUTTON_GLYPH` | `U+E011` | `{MICROPHONE_BUTTON_GLYPH}` | Microphone (with border) |
|
|
51
|
+
| `SEARCH_BUTTON_GLYPH` | `U+E012` | `{SEARCH_BUTTON_GLYPH}` | Search (with border) |
|
|
52
|
+
| `FAST_FORWARD_GLYPH` | `U+E013` | `{FAST_FORWARD_GLYPH}` | Fast forward |
|
|
53
|
+
| `REWIND_GLYPH` | `U+E014` | `{REWIND_GLYPH}` | Rewind |
|
|
54
|
+
| `SHORTCUT_ONE_BUTTON_GLYPH` | `U+E015` | `{SHORTCUT_ONE_BUTTON_GLYPH}` | Shortcut 1 |
|
|
55
|
+
| `SHORTCUT_TWO_BUTTON_GLYPH` | `U+E016` | `{SHORTCUT_TWO_BUTTON_GLYPH}` | Shortcut 2 |
|
|
56
|
+
| `CLOSED_CAPTIONS_GLYPH` | `U+E017` | `{CLOSED_CAPTIONS_GLYPH}` | Closed captions |
|
|
57
|
+
| `LIVE_TV_GLYPH` | `U+E018` | `{LIVE_TV_GLYPH}` | Live TV |
|
|
58
|
+
| `SLEEP_TIMER_GLYPH` | `U+E019` | `{SLEEP_TIMER_GLYPH}` | Sleep timer |
|
|
59
|
+
| `GENERAL_APP_GLYPH` | `U+E020` | `{GENERAL_APP_GLYPH}` | General app icon |
|
|
60
|
+
| `BACK_BUTTON_GLYPH` | `U+E021` | `{BACK_BUTTON_GLYPH}` | Back |
|
|
61
|
+
| `HOME_BUTTON_GLYPH` | `U+E022` | `{HOME_BUTTON_GLYPH}` | Home button |
|
|
62
|
+
| `QUICK_LAUNCH_BUTTON_GLYPH` | `U+E023` | `{QUICK_LAUNCH_BUTTON_GLYPH}` | Quick launch / rocket |
|
|
63
|
+
| `DOWN_BUTTON_GLYPH` | `U+E024` | `{DOWN_BUTTON_GLYPH}` | Down |
|
|
64
|
+
| `LEFT_BUTTON_GLYPH` | `U+E025` | `{LEFT_BUTTON_GLYPH}` | Left |
|
|
65
|
+
| `RIGHT_BUTTON_GLYPH` | `U+E026` | `{RIGHT_BUTTON_GLYPH}` | Right |
|
|
66
|
+
| `KIDS_PROFILE_GLYPH` | `U+E027` | `{KIDS_PROFILE_GLYPH}` | Kids profile |
|
|
67
|
+
| `UP_BUTTON_GLYPH` | `U+E028` | `{UP_BUTTON_GLYPH}` | Up |
|
|
68
|
+
| `POWER_BUTTON_GLYPH` | `U+E029` | `{POWER_BUTTON_GLYPH}` | Power |
|
|
69
|
+
| `THUMBS_UP_GLYPH` | `U+E030` | `{THUMBS_UP_GLYPH}` | Thumbs up |
|
|
70
|
+
| `THUMBS_DOWN_GLYPH` | `U+E031` | `{THUMBS_DOWN_GLYPH}` | Thumbs down |
|
|
71
|
+
|
|
72
|
+
The numbering is decimal-style: the A–F slots in each decade (`U+E00A`–`U+E00F`, `U+E01A`–`U+E01F`,
|
|
73
|
+
`U+E02A`–`U+E02F`) are unassigned. The font defines 32 private-use codepoints and the table lists
|
|
74
|
+
all of them.
|
|
75
|
+
|
|
76
|
+
`DOT_GLYPH` (`U+E006`) and `BLACK_CIRCLE` (`U+25CF`) are the same outline at two vertical
|
|
77
|
+
positions — `DOT_GLYPH` sits on the baseline to line up with the other icon glyphs, `BLACK_CIRCLE`
|
|
78
|
+
is raised to text centre for use inside a run of text. Both have full outline data; neither is
|
|
79
|
+
reserved.
|
|
80
|
+
|
|
81
|
+
## 2. Unicode characters RokuFont01 backs (safe under a custom font)
|
|
82
|
+
|
|
83
|
+
Real Unicode codepoints, but RokuUI-Regular does not contain them — they resolve through the same
|
|
84
|
+
RokuFont01 fallback as the private-use glyphs above. That makes them the *safe* shapes to use in
|
|
85
|
+
custom-font text.
|
|
86
|
+
|
|
87
|
+
| Constant | Codepoint | Char | Description |
|
|
88
|
+
|---|---|---|---|
|
|
89
|
+
| `BLACK_CIRCLE` | `U+25CF` | ● | Dot raised to text centre, for use inside a text run |
|
|
90
|
+
| `BLACK_STAR` | `U+2605` | ★ | Filled star |
|
|
91
|
+
| `WHITE_STAR` | `U+2606` | ☆ | Outlined star |
|
|
92
|
+
|
|
93
|
+
## 3. RokuUI-Regular symbols (no backstop under a custom font)
|
|
94
|
+
|
|
95
|
+
Part of the default UI font, so they need no fallback under a system font. Under a custom app font
|
|
96
|
+
there is no RokuFont01 backstop for them either. **These are the ten constants to audit if your
|
|
97
|
+
app ships its own typeface.**
|
|
98
|
+
|
|
99
|
+
| Constant | Codepoint | Char | Description |
|
|
100
|
+
|---|---|---|---|
|
|
101
|
+
| `BULLET` | `U+2022` | • | Bullet |
|
|
102
|
+
| `ELLIPSIS` | `U+2026` | … | Horizontal ellipsis |
|
|
103
|
+
| `EN_DASH` | `U+2013` | – | En dash |
|
|
104
|
+
| `EM_DASH` | `U+2014` | — | Em dash |
|
|
105
|
+
| `LEFT_ARROW` | `U+2190` | ← | Leftwards arrow |
|
|
106
|
+
| `UP_ARROW` | `U+2191` | ↑ | Upwards arrow |
|
|
107
|
+
| `RIGHT_ARROW` | `U+2192` | → | Rightwards arrow |
|
|
108
|
+
| `DOWN_ARROW` | `U+2193` | ↓ | Downwards arrow |
|
|
109
|
+
| `CHECK_MARK` | `U+2713` | ✓ | Check mark |
|
|
110
|
+
| `BALLOT_X` | `U+2717` | ✗ | Ballot X |
|
|
111
|
+
|
|
112
|
+
## What has no constant, on purpose
|
|
113
|
+
|
|
114
|
+
Characters you can type directly get none: `·` middle dot, `°` degree, currency signs, maths
|
|
115
|
+
operators, quotation marks. Type them. A constant would add a name to look up without adding
|
|
116
|
+
anything the compiler or the device cares about.
|
|
117
|
+
|
|
118
|
+
Geometric arrows `◀` and `▶` are in **neither** font and have no constants because they do not
|
|
119
|
+
render. Reach for `LEFT_ARROW` / `RIGHT_ARROW`, or the bordered `LEFT_BUTTON_GLYPH` /
|
|
120
|
+
`RIGHT_BUTTON_GLYPH` when you want the remote-button look.
|
|
121
|
+
|
|
122
|
+
## Firmware placeholders
|
|
123
|
+
|
|
124
|
+
The `{CONSTANT_NAME}` column above is the firmware's own placeholder syntax, used in translated
|
|
125
|
+
firmware strings and resolved by the firmware's string layer. It is not a TypeScript symbol and it
|
|
126
|
+
is not template syntax: putting `{BACK_BUTTON_GLYPH}` in a label renders those braces literally.
|
|
127
|
+
Use the placeholder form only where you are explicitly producing text for something that resolves
|
|
128
|
+
firmware placeholders, and interpolate the imported constant everywhere else.
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
# Logging and Errors
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
An RSG SDK TypeScript app reports what it is doing through one exported `logger`, and survives a
|
|
6
|
+
throw through Solid's `ErrorBoundary`. Both have a platform edge that catches people out: log
|
|
7
|
+
levels are enforced in **two** places, one of which deletes your call before the app ever runs,
|
|
8
|
+
and the SDK already wraps every screen in a boundary whose fallback draws nothing — so the
|
|
9
|
+
default shape of a failure here is a blank screen rather than a crash.
|
|
10
|
+
|
|
11
|
+
## When to Use
|
|
12
|
+
|
|
13
|
+
- a log level raised and nothing new appears
|
|
14
|
+
- log levels configured somewhere that turns out to have no effect
|
|
15
|
+
- `console` calls present while developing and gone from the build handed over
|
|
16
|
+
- a screen that comes up blank with no crash and no stack
|
|
17
|
+
- containing a throw so one bad screen does not take the app with it, or recovering afterwards
|
|
18
|
+
- an uncaught-exception handler that never fires, or fires and then behaves unexpectedly
|
|
19
|
+
- a crash stack that cannot be traced back to the TypeScript that produced it
|
|
20
|
+
|
|
21
|
+
### When not to use
|
|
22
|
+
|
|
23
|
+
| Situation | Use instead | Because |
|
|
24
|
+
|---|---|---|
|
|
25
|
+
| Getting logs **off** a device from a shell script or a CI job | The `cli-dev-loop` skill | This skill is about a line being emitted; collecting it afterwards is a different surface |
|
|
26
|
+
| Getting logs off a device, or decoding a stack, inside an agent session | The `mcp-ai-dev-loop` skill | Same distinction, reached through tool calls rather than a shell |
|
|
27
|
+
| What the running app costs in frames, memory or startup time | The `app-performance` skill | Instrumenting for cost is measurement, not reporting |
|
|
28
|
+
| Which Solid primitives `@roku-sdk/solid` exports at all | The `solid-on-roku` skill | This skill owns the failure, not the export surface |
|
|
29
|
+
|
|
30
|
+
## Read the reference first for this task
|
|
31
|
+
|
|
32
|
+
| If the request is | Read now, before answering |
|
|
33
|
+
|---|---|
|
|
34
|
+
| Writing or debugging a `setUncaughtExceptionHandler` callback | `references/uncaught-exceptions.md` |
|
|
35
|
+
|
|
36
|
+
The tables below say that the handler exists, what it never sees, and that registering one cannot
|
|
37
|
+
be undone. Its timeout, its inverted `done` flag and the conditions under which it silently does
|
|
38
|
+
nothing are only in the reference, and a handler written from the summary alone is wrong in ways
|
|
39
|
+
that do not show up until something throws in production.
|
|
40
|
+
|
|
41
|
+
## Quick Reference
|
|
42
|
+
|
|
43
|
+
| Question | Answer |
|
|
44
|
+
|---|---|
|
|
45
|
+
| Get a logger | `import { logger } from "@roku-sdk/rsg-ts"` — the root one; `logger.getLogger("myscreen")` for a child |
|
|
46
|
+
| Levels available | `trace`, `debug`, `info`, `warn`, `error`. There is no `fatal`, and `createLogger` is not exported |
|
|
47
|
+
| Why a message is a function | `logger.info(() => \`...\`)` — the thunk is only invoked once the level check passes, so a filtered line costs nothing to build |
|
|
48
|
+
| The two level knobs | `app.logger.baseLogLevel` **deletes** call sites at build time (default INFO in production builds); `app.logger.currentLogLevel` filters what remains at runtime (default ERROR; also enforced as a runtime backstop at the base level) |
|
|
49
|
+
| Which one to raise | Whichever is higher wins. Raising only `currentLogLevel` recovers nothing a lower `baseLogLevel` already deleted |
|
|
50
|
+
| Setting levels via `buildVariables` | A silent no-op — the build overwrites `__BASE_LOG_LEVEL__` and `__CURRENT_LOG_LEVEL__` itself |
|
|
51
|
+
| Contain a throw | `<ErrorBoundary fallback={…}>` around the subtree, from `@roku-sdk/solid` |
|
|
52
|
+
| Recover from one | `catchError` for a scoped throw, `resetErrorBoundaries` to retry every boundary |
|
|
53
|
+
| Blank screen, no crash, no stack | The SDK's own boundary caught a root-component throw. One `error` line under source `btstp` is the entire evidence |
|
|
54
|
+
| `console` in a shipped build | Dropped by `rk build`, kept by `rk dev`; `dropConsole: false` keeps them, and then `installConsolePolyfill()` restores the helpers |
|
|
55
|
+
| Trace a stack back to TypeScript | Call `enableExceptionTagging()` before the code that can throw. Inside `main()`, before the anchor node, covers throws after `main()`; a throw at module top level needs the call in the entry file's first import |
|
|
56
|
+
| An uncaught-exception handler | `setUncaughtExceptionHandler` — never sees an `ErrorBoundary` catch, sees nothing thrown before it registers, and registration is permanent |
|
|
57
|
+
| Where these live | `logger`, `main`, `enableExceptionTagging` and `setUncaughtExceptionHandler` all come from `@roku-sdk/rsg-ts`; `installConsolePolyfill` only from `@roku-sdk/native` |
|
|
58
|
+
|
|
59
|
+
## Instructions
|
|
60
|
+
|
|
61
|
+
1. **Log through the exported `logger`, and give each area a child.** `logger` is the root;
|
|
62
|
+
`logger.getLogger("playback")` tags that area's lines. `createLogger` is deliberately module
|
|
63
|
+
private, so a child logger is the only sanctioned way to get a distinct source.
|
|
64
|
+
|
|
65
|
+
2. **Pass a thunk, not a string.** Every method takes `() => string`. The level check runs first,
|
|
66
|
+
so an interpolation inside the thunk is never paid for on a filtered line. Building the string
|
|
67
|
+
at the call site throws that away.
|
|
68
|
+
|
|
69
|
+
3. **Understand which level does what, because only one of them can be undone.**
|
|
70
|
+
- `baseLogLevel` is a **build-time** cut. Any statement calling a method below it is replaced
|
|
71
|
+
with `void 0;` in the source before the bundler sees it, so the call is not in the artifact
|
|
72
|
+
at all. Nothing at runtime can bring it back.
|
|
73
|
+
- `currentLogLevel` is a **runtime** filter over whatever survived, resolved once at module
|
|
74
|
+
load.
|
|
75
|
+
|
|
76
|
+
So diagnose in that order: if a line is missing, ask whether it was deleted before asking
|
|
77
|
+
whether it was filtered. The defaults are `INFO` for the base cut and `ERROR` for the runtime
|
|
78
|
+
filter, which is why a `debug` line is absent from a default build twice over.
|
|
79
|
+
|
|
80
|
+
4. **Know what the build-time cut checks and what it does not.** It matches a whole expression
|
|
81
|
+
statement whose call target is a log method (e.g. `.trace`, `.debug`) on a receiver that is
|
|
82
|
+
recognised as an SDK logger binding in that file. Specifically, the receiver must be:
|
|
83
|
+
- A named import of `logger` (or an aliased import) from one of the SDK logger modules
|
|
84
|
+
(`@roku-sdk/rsg-ts` or `@roku-sdk/common`), or common's own relative `./logger` import.
|
|
85
|
+
- A child logger derived from any of the above via `const X = logger.getLogger("area")`
|
|
86
|
+
(and any further `.getLogger(…)` chains at module level).
|
|
87
|
+
- An inline `logger.getLogger("a").debug(…)` call in statement position.
|
|
88
|
+
|
|
89
|
+
Non-SDK receivers (`someLibrary.debug(…)`, `console.debug(…)`, `myEmitter.trace(…)`) are
|
|
90
|
+
never removed. Shadowing is respected: when a local binding reuses the name — any parameter
|
|
91
|
+
(constructor, accessor and destructured ones included), a `catch` binding, a loop variable, or
|
|
92
|
+
a local declaration — calls through it are left untouched.
|
|
93
|
+
|
|
94
|
+
One documented limitation: a logger exported from one module and re-imported under a
|
|
95
|
+
*different* import path (not one of the recognised specifiers) is not recognised by the
|
|
96
|
+
per-file pass and is not stripped at build time. The runtime backstop (see below) still
|
|
97
|
+
silences it. The sanctioned pattern is: import `logger` from the SDK and derive children
|
|
98
|
+
with `getLogger` in the same module.
|
|
99
|
+
|
|
100
|
+
A second limitation: inside a function, only a child derived directly from a recognised SDK
|
|
101
|
+
logger is stripped. A grandchild built from a function-local child — `const a =
|
|
102
|
+
logger.getLogger("a"); const b = a.getLogger("b"); b.debug(…)`, or inline
|
|
103
|
+
`a.getLogger("b").debug(…)` — is kept at build time. Like the case above, this only ever
|
|
104
|
+
leaves a call in (never removes a non-SDK one), and the runtime backstop still silences it.
|
|
105
|
+
Derive nested children at module level if you want them stripped.
|
|
106
|
+
|
|
107
|
+
Child loggers must be declared with `const`. A `let` or `var` child
|
|
108
|
+
(`let l = logger.getLogger("x")`) can be reassigned to some other object, so its calls are
|
|
109
|
+
kept at build time rather than risk deleting app code. The runtime backstop still silences them.
|
|
110
|
+
|
|
111
|
+
Only a bare statement is stripped. `const line = logger.debug(…)`, or a call guarded by
|
|
112
|
+
`&&`, survives — so a log statement written in an unusual shape will not be stripped as
|
|
113
|
+
expected.
|
|
114
|
+
|
|
115
|
+
5. **Set levels in `app.logger`, never in `buildVariables`.** The two keys the logger reads are
|
|
116
|
+
`__BASE_LOG_LEVEL__` and `__CURRENT_LOG_LEVEL__`, and the build writes both itself —
|
|
117
|
+
unconditionally, after copying your `buildVariables` — then restores them only from
|
|
118
|
+
`app.logger`. So a value you put in `buildVariables` under either name is overwritten with an
|
|
119
|
+
empty string, which the reader treats exactly like an unset variable. You get the defaults,
|
|
120
|
+
with no warning that your setting was discarded.
|
|
121
|
+
|
|
122
|
+
6. **Wrap a subtree that can throw in `ErrorBoundary`, and draw something in the fallback.** The
|
|
123
|
+
SDK already puts one boundary around the whole app, and its fallback returns nothing — so an
|
|
124
|
+
uncontained throw in a root component is a blank screen rather than a crash. That is the SDK
|
|
125
|
+
working as designed, not a missing feature. Your own boundary, closer to the risk, is what
|
|
126
|
+
turns it into a message the user can act on.
|
|
127
|
+
|
|
128
|
+
7. **Recover with `catchError` or `resetErrorBoundaries`.** `catchError` handles a throw inside
|
|
129
|
+
one computation without unmounting a subtree; `resetErrorBoundaries` re-runs every boundary's
|
|
130
|
+
children, which is the "try again" button's implementation.
|
|
131
|
+
|
|
132
|
+
8. **Expect `console` to disappear from the build you hand over.** `rk build` drops `console.*`
|
|
133
|
+
and `debugger`; `rk dev` keeps them. Set `dropConsole: false` when a build must keep them
|
|
134
|
+
on purpose — and note the helper methods (`count`, `group`, `assert` and the timing pair) come
|
|
135
|
+
from a polyfill that only auto-installs in a development build, so a production build that
|
|
136
|
+
keeps `console` also needs `installConsolePolyfill()` from `@roku-sdk/native`. Plain
|
|
137
|
+
`console.log`, `warn` and `error` are the runtime's own and are never wrapped. None of this
|
|
138
|
+
replaces `logger`: `console` has no levels, no sources and no build-time cut.
|
|
139
|
+
|
|
140
|
+
9. **Call `enableExceptionTagging()` before the code that can throw**, when traces from this
|
|
141
|
+
build have to be resolvable later. Inside `main()`, before registering the anchor node, covers
|
|
142
|
+
throws from the `main()` callback onward. A throw while the bundle is still being evaluated
|
|
143
|
+
needs the call in the same module the entry file imports first (step 10). It marks each trace
|
|
144
|
+
with the build's source-map identity. It is idempotent, and calling it costs nothing at runtime
|
|
145
|
+
beyond the tagging itself. Turning a captured stack back into TypeScript afterwards is
|
|
146
|
+
`cli-dev-loop` from a shell and `mcp-ai-dev-loop` from a session — this step is only the app's half.
|
|
147
|
+
|
|
148
|
+
10. **Reach for `setUncaughtExceptionHandler` only for what a boundary cannot catch**, and read
|
|
149
|
+
the reference before writing one. It replaces the SDK's default permanently, it never sees a
|
|
150
|
+
throw an `ErrorBoundary` already caught, and it is not the mechanism behind the blank screen
|
|
151
|
+
in step 6. It also misses anything thrown before it registers, and every module's top-level
|
|
152
|
+
code runs before `main()` is called. When a module can throw at its top level, register in
|
|
153
|
+
the entry file's first import rather than inside `main()`.
|
|
154
|
+
|
|
155
|
+
## Render-thread transaction errors
|
|
156
|
+
|
|
157
|
+
On firmware with `roku.Transaction.setErrorCallback`, SDK `main()` installs a default
|
|
158
|
+
error-level logger under source `tx` before declaring app components. It reports each
|
|
159
|
+
render-thread transaction error with its reason, message and available operation, node ID
|
|
160
|
+
and property name. These asynchronous errors do not throw through `ErrorBoundary` or the
|
|
161
|
+
uncaught-exception handler. Apps calling `roku.Transaction.setErrorCallback` themselves
|
|
162
|
+
replace this default; accessors are valid only during that callback. Older firmware without
|
|
163
|
+
the callback API is unchanged and cannot surface these errors through this SDK handler.
|
|
164
|
+
|
|
165
|
+
## Example
|
|
166
|
+
|
|
167
|
+
An app entry point that tags its traces, and a screen that contains its own failures instead of
|
|
168
|
+
letting the SDK's blank fallback have them.
|
|
169
|
+
|
|
170
|
+
```tsx rsg-skill=complete
|
|
171
|
+
import { Label, LayoutGroup } from "@roku-sdk/components";
|
|
172
|
+
import { type AnchorNodeImplementation, defineAnchorNode, FieldType, logger } from "@roku-sdk/rsg-ts";
|
|
173
|
+
import { ErrorBoundary } from "@roku-sdk/solid";
|
|
174
|
+
|
|
175
|
+
const screenLog = logger.getLogger("playback");
|
|
176
|
+
|
|
177
|
+
const PlaybackScreenDescription = {
|
|
178
|
+
componentName: "PlaybackScreen",
|
|
179
|
+
extends: "Group" as const,
|
|
180
|
+
inputFields: {
|
|
181
|
+
assetId: FieldType.String("")
|
|
182
|
+
},
|
|
183
|
+
outputFields: {}
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
function NowPlaying(props: { assetId: string }) {
|
|
187
|
+
// The thunk is only invoked once the level check passes.
|
|
188
|
+
screenLog.info(() => `rendering asset ${props.assetId}`);
|
|
189
|
+
return <label text={props.assetId} width={800} height={48} color="0xFFFFFFFF" />;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const PlaybackScreen: AnchorNodeImplementation<typeof PlaybackScreenDescription> = inputs => (
|
|
193
|
+
<layoutGroup layoutDirection="vert">
|
|
194
|
+
<ErrorBoundary
|
|
195
|
+
fallback={error => {
|
|
196
|
+
// Without a fallback that draws, this subtree is simply absent.
|
|
197
|
+
screenLog.error(() => `playback failed: ${error.message}`);
|
|
198
|
+
return <label text="Playback unavailable" width={800} height={48} color="0xFF6666FF" />;
|
|
199
|
+
}}
|
|
200
|
+
>
|
|
201
|
+
<NowPlaying assetId={inputs.assetId ?? ""} />
|
|
202
|
+
</ErrorBoundary>
|
|
203
|
+
</layoutGroup>
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
export default defineAnchorNode(PlaybackScreenDescription, PlaybackScreen);
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## Common Mistakes
|
|
210
|
+
|
|
211
|
+
| Symptom | Cause | Fix |
|
|
212
|
+
|---|---|---|
|
|
213
|
+
| A level was raised, the app rebuilt, and the lines are still missing | `baseLogLevel` deleted the call sites; raising the runtime filter cannot restore a call that is not in the artifact | Raise `app.logger.baseLogLevel` and rebuild |
|
|
214
|
+
| Levels set through `buildVariables` change nothing, and nothing warns | The build overwrites `__BASE_LOG_LEVEL__` and `__CURRENT_LOG_LEVEL__` with an empty string before restoring them from `app.logger` | Set them in `app.logger` |
|
|
215
|
+
| A screen is blank, the app is still running, and there is no stack | A root component threw and the SDK's own boundary caught it; its fallback draws nothing | Search the log for source `btstp`; add your own `ErrorBoundary` closer to the risk |
|
|
216
|
+
| An `ErrorBoundary` was added and the screen is still blank | The `fallback` returns nothing, or the boundary sits above the component that throws rather than around it | Return a real node from `fallback`; put the boundary around the risky subtree |
|
|
217
|
+
| An uncaught-exception handler never fires for a render-time throw | A boundary caught it first, and the handler only sees what nothing caught | Handle it in the boundary; keep the handler for what escapes |
|
|
218
|
+
| The app exits from the splash screen, and the handler never fires | A module threw at its top level while the bundle was evaluated, before the handler was registered inside `main()` | Register in a module the entry file imports first, as a bare `import "./…"` |
|
|
219
|
+
| A registered handler cannot be swapped back to the SDK default | Registration replaces the default permanently, by design | Register once, at startup, and branch inside your own handler |
|
|
220
|
+
| A handler was registered and does nothing at all, silently | `globalThis.Process` was unavailable, so the call warned and returned — and at the default runtime filter that warning is itself invisible | Read the reference; confirm the handler registers where the runtime provides it |
|
|
221
|
+
| Diagnostics print in development and are gone from the shipped build | `rk build` drops `console.*` by default | Use `logger`, or set `dropConsole: false` deliberately |
|
|
222
|
+
| `console` timing and grouping helpers are missing in a build that kept `console` | The polyfill auto-installs only in a development build | Call `installConsolePolyfill()` from `@roku-sdk/native` |
|
|
223
|
+
| A captured stack cannot be matched to a build | The app never tagged its traces, or tagged them inside `main()` after a module had already thrown | Call `enableExceptionTagging()` before that code. Inside `main()` covers throws after it; a module top-level throw needs the call in the entry file's first import |
|
|
224
|
+
|
|
225
|
+
`Logger`, `setUncaughtExceptionHandler` and `ErrorBoundary` are typed with TSDoc in the installed
|
|
226
|
+
packages. Read an option there rather than guessing its name, and prefer it over any prose,
|
|
227
|
+
including this file, when the two disagree.
|