@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.
Files changed (53) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +88 -2
  3. package/package.json +47 -3
  4. package/skills/ads-and-raf/SKILL.md +231 -0
  5. package/skills/ads-and-raf/references/integration-flows.md +332 -0
  6. package/skills/app-packaging/SKILL.md +107 -0
  7. package/skills/app-performance/SKILL.md +133 -0
  8. package/skills/cli-dev-loop/SKILL.md +217 -0
  9. package/skills/cli-dev-loop/references/logs-and-crashes.md +114 -0
  10. package/skills/cpu-and-heap-profiling/SKILL.md +108 -0
  11. package/skills/cpu-and-heap-profiling/references/reading-profiles.md +52 -0
  12. package/skills/data-entry-keyboards/SKILL.md +160 -0
  13. package/skills/device-management/SKILL.md +158 -0
  14. package/skills/dialogs-and-modals/SKILL.md +210 -0
  15. package/skills/effects/SKILL.md +183 -0
  16. package/skills/fetch-and-data-loading/SKILL.md +164 -0
  17. package/skills/fetch-and-data-loading/references/https-and-certificates.md +79 -0
  18. package/skills/flex-grid/SKILL.md +236 -0
  19. package/skills/flex-grid/references/focus-integration.md +355 -0
  20. package/skills/flex-grid/references/plugins.md +201 -0
  21. package/skills/focus-management/SKILL.md +210 -0
  22. package/skills/focus-management/references/focus-recipes.md +290 -0
  23. package/skills/fonts-and-glyphs/SKILL.md +231 -0
  24. package/skills/fonts-and-glyphs/references/glyph-tables.md +128 -0
  25. package/skills/logging-and-errors/SKILL.md +227 -0
  26. package/skills/logging-and-errors/references/uncaught-exceptions.md +137 -0
  27. package/skills/mcp-ai-dev-loop/SKILL.md +189 -0
  28. package/skills/mcp-ai-dev-loop/references/crash-and-console.md +68 -0
  29. package/skills/mcp-ai-dev-loop/references/live-debugging.md +129 -0
  30. package/skills/mcp-setup/SKILL.md +131 -0
  31. package/skills/project-setup/SKILL.md +210 -0
  32. package/skills/project-setup/references/roku-config.md +120 -0
  33. package/skills/screen-navigation/SKILL.md +166 -0
  34. package/skills/sdk-components/SKILL.md +213 -0
  35. package/skills/sdk-components/references/dialogs.md +148 -0
  36. package/skills/sdk-components/references/scenegraph-node-spelling.md +123 -0
  37. package/skills/sdk-installation/SKILL.md +129 -0
  38. package/skills/slideshow/SKILL.md +157 -0
  39. package/skills/slideshow/references/focus-and-handoff.md +187 -0
  40. package/skills/solid-on-roku/SKILL.md +181 -0
  41. package/skills/solid-on-roku/references/hermes-and-exports.md +130 -0
  42. package/skills/testing/SKILL.md +147 -0
  43. package/skills/testing/references/async-and-timing.md +97 -0
  44. package/skills/user-accounts-and-purchases/SKILL.md +164 -0
  45. package/skills-manifest.json +316 -0
  46. package/skills-manifest.schema.json +62 -0
  47. package/src/__tests__/manifest.test.ts +267 -0
  48. package/src/__tests__/snippets.test.ts +221 -0
  49. package/src/__tests__/trigger-routing.test.ts +77 -0
  50. package/src/index.ts +36 -0
  51. package/src/manifest.ts +215 -0
  52. package/src/router.ts +147 -0
  53. package/src/snippets.ts +212 -0
@@ -0,0 +1,213 @@
1
+ # SDK Components
2
+
3
+ ## Overview
4
+
5
+ `@roku-sdk/components` is the package an RSG SDK TypeScript app draws with: mostly thin typed
6
+ wrappers over SceneGraph nodes, plus a few helpers that wrap no node at all. Everything here is
7
+ imported in source at compile time. A partner shipping a player as a separate component library
8
+ from a URL at runtime is a Dynamic Component Library — the `app-packaging` skill routes those, and
9
+ the API is `@roku-sdk/runtime/component-library`, not a `Poster`, `Label` or any other export from
10
+ this package. This skill answers "which one renders this", not "what are its props" — the props
11
+ ship as TSDoc in the consumer's own `node_modules`, and the last section says where.
12
+
13
+ ## Read the reference first for these tasks
14
+
15
+ | If the request is | Read now, before answering |
16
+ |---|---|
17
+ | A dialog of any kind — a confirmation, a message, a prompt, anything modal | `references/dialogs.md` |
18
+
19
+ A dialog is composed from areas, each a separately named component, and the tables below do
20
+ not name them. Answer from the reference and quote it: a dialog assembled out of a `Group` and
21
+ some labels is the wrong answer even when it renders.
22
+
23
+ ## When to Use
24
+
25
+ - choosing the component for a job: a block of text, an image, a row of buttons, a spinner
26
+ - laying a screen out with `Group`, `LayoutGroup` or `MaskGroup`
27
+ - a component that compiles and renders nothing, or renders in the wrong place
28
+ - deciding whether a component is the encouraged one for new work
29
+
30
+ ### When not to use
31
+
32
+ | Situation | Use instead | Because |
33
+ |---|---|---|
34
+ | Focus, D-pad movement, or the state that dims an unfocused item | The `focus-management` skill | `Focusable`, `FocusableGroup`, `KeyHandler`, `ModalBoundary` and `useModal` are exports of this package that that skill owns outright. It also owns `FocusBoundary` and `useFocusable`, which live in `@roku-sdk/navigation` and are where new focus work goes |
35
+ | A virtualized shelf or poster grid in TypeScript | The `flex-grid` skill | That renderer owns its cells, its keys and its edges |
36
+ | Loading a component library from a URL at runtime, or referencing a node a partner shipped separately | The `app-packaging` skill, which routes to `documentation/core-concepts/dcl.md` and `@roku-sdk/runtime/component-library` | The words "component library" name a delivery mechanism. None of the exports below are fetched from a URL at runtime |
37
+ | Deciding *when* a dialog is up, what the caller learns when it closes, or a flow of several dialogs in sequence | The `dialogs-and-modals` skill | This skill owns what a dialog is made of. Which one is showing, and what its close means to the code that opened it, is app state above the component |
38
+ | Scaffolding, dependencies, `roku-config.ts`, where an image file lives | The `project-setup` skill | This skill starts once the app renders |
39
+ | Rounded corners, a border or a gradient on a `Rectangle` or `Poster` | The `effects` skill | An `Effect` is wired through a prop rather than rendered, and where it is created, how it is shared and animated, and which devices draw it are all there |
40
+
41
+ ## Quick Reference
42
+
43
+ | Job | Reach for | Give it |
44
+ |---|---|---|
45
+ | Position or group children | `Group` | Nothing — it draws nothing itself |
46
+ | Stack children in a row or column | `LayoutGroup` | `layoutDirection`, `itemSpacings` |
47
+ | A solid block of colour | `Rectangle` | `width`, `height` (`color` defaults to white) |
48
+ | Clip or fade children against a shape | `MaskGroup` | `maskUri` |
49
+ | Rounded corners, a border, or a gradient on a surface | `Effect`, whose wiring this row deliberately does not repeat | The `effects` skill — an effect is passed through a prop, and the rules for it live there |
50
+ | One line of text | `Label` | `text` |
51
+ | Text positioned by its own centre or edge | `SimpleLabel` | `text`, `horizOrigin`, `vertOrigin` |
52
+ | Text too long for its box that should march past | `ScrollingLabel` | `text` |
53
+ | A synopsis in a fixed box the user scrolls | `ScrollableText` | `text`, `width`, `height` |
54
+ | Text with inline styled runs | `MultiStyleLabel` | `text`, `drawingStyles` |
55
+ | A typeface | `Font`, whose props this row deliberately does not repeat | The `fonts-and-glyphs` skill — a typeface is wrong without its size and its fallback, and shipping one needs the import contract that lives there too |
56
+ | An image | `Poster` | `uri`, plus `loadDisplayMode` when it has to fit a fixed box |
57
+ | One button | `Button` | `text`, and `showFocusFootprint` when it is not inside a `ButtonGroup` |
58
+ | A row or column of buttons | `ButtonGroup` | `buttons` (strings), `onButtonSelected` (the index) |
59
+ | A loading indicator | `BusySpinner` | `uri`, e.g. `"theme:ActionHudSpinner"` |
60
+ | A dialog | The components named in `references/dialogs.md`, which this row deliberately does not repeat | `references/dialogs.md` — a dialog is wrong without its areas, and their props are only there |
61
+ | Animate a field to a target | `Tween` from `@roku-sdk/components` | `targetNode`, `field`, and `targetValue` or `targetVector` |
62
+ | Video or audio playback | `Video`, `Audio` | A `VideoContentNode` as `content` |
63
+ | Data for a SceneGraph-native list | `ContentNode` | The fields that list reads |
64
+
65
+ For the declarative `<tween>` primitive and the components `Tween` wrapper, read the Tween value-shape contract in `documentation/core-concepts/tween.md` in the SDK source tree.
66
+
67
+ ## Instructions
68
+
69
+ 1. **Assume nothing is required, because almost nothing is.** Every prop inherited from
70
+ `INodeProps` is optional, so a component with no props at all compiles. It just does not
71
+ render. The type system will not tell you that `Label` needs `text`, that `Rectangle` needs
72
+ `width` and `height`, that `Poster` needs `uri`, or that `MaskGroup` needs `maskUri`. Treat the
73
+ Quick Reference's third column as the real signature.
74
+
75
+ 2. **The handful of genuinely required props are worth knowing** because they are the only
76
+ compile errors this package gives you: `ButtonRow` needs `buttons`, `Dialog` needs `close`,
77
+ `RsgPalette` needs `colors`, `MarkupList` and `MarkupGrid` need `itemComponentName`, and
78
+ `KeyHandler` needs `onKeyEvent` plus exactly one of `keys` or `handleAllKeys`.
79
+
80
+ 3. **Write the lowercase intrinsic, not the capitalized wrapper, in new code.** Most of these
81
+ components are pure pass-throughs — `Rectangle` is exactly `props => <rectangle {...props} />`
82
+ — and the lowercase tag is already typed against the same props interface. So `<rectangle>`,
83
+ `<group>`, `<label>` and friends need no import and render identically. The wrappers are
84
+ supported and not deprecated, so existing code is fine as it stands and an app author may
85
+ use either; but samples, demos and generated code should be consistent about the intrinsic.
86
+ The exceptions are the components in step 4, which are not pass-throughs and must keep their
87
+ capitalized form. Full reasoning: `references/scenegraph-node-spelling.md`.
88
+
89
+ 4. **These are not pass-throughs — always write them capitalized.** `Audio` (renders `content`
90
+ as a child), `VideoContentNode`, the five keyboard components `CustomKeyboard`,
91
+ `DynamicKeyboard`, `DynamicKeyGrid`, `DynamicMiniKeyboard` and `DynamicPinPad` (they call
92
+ `retainAsset()` so the backing XML survives tree-shaking), the six dialogs `Dialog`,
93
+ `StandardDialog`, `StandardKeyboardDialog`, `StandardMessageDialog`, `StandardPinPadDialog`
94
+ and `StandardProgressDialog` (they wrap in `<Modal>`), `ListPanel` (forwards to a different
95
+ node), and the logic-bearing `Focusable`, `FocusableGroup`, `KeyHandler`, `Modal`,
96
+ `Container`, `ButtonRow`, `BlendedBusySpinner`, `BoundingTracker` and `ChannelStore`. A
97
+ lowercase tag here either fails to compile or silently drops the behaviour.
98
+
99
+ 5. **`<group>` now needs an import like everything else.** It used to be registered as a
100
+ capitalized JSX intrinsic, uniquely in the package, so a forgotten or misspelled import of
101
+ `Group` did not fail the build and a layout could misbehave with nothing to point at. That
102
+ alias has been removed: write `<group>` for the node, or import `Group` from
103
+ `@roku-sdk/components` if you want the wrapper.
104
+
105
+ 6. **If it is fed by a `content` ContentNode, it is a SceneGraph-native list.** Eleven are:
106
+ `RowList`, `ZoomRowList`, `MarkupList`, `MarkupGrid`, `PosterGrid`, `LabelList`, `CheckList`,
107
+ `RadioButtonList`, `TimeGrid`, `TargetGroup` and `TargetList`. Six of them also take an
108
+ `itemComponentName` — required on `MarkupList` and `MarkupGrid` — which names a compiled
109
+ BrightScript component by string, and that is what renders each item. A TypeScript component
110
+ therefore cannot be an item of one. Name them when a reader asks about them; for a scrolling
111
+ shelf or poster grid written in TypeScript, the answer is `@roku-sdk/flex-grid`. (`Video` and
112
+ `Audio` take a `content` node too, but they are players, not lists.)
113
+
114
+ 7. **Prefer these over the four that are still exported but not the current answer.**
115
+ `StandardDialog` over `Dialog` — Roku OS 10 introduced the newer node with palette support,
116
+ and `Dialog` remains only for backward compatibility. `RsgPalette` over `RSGPalette`, which
117
+ is the same value under a deprecated alias. `ButtonGroup` over `ButtonRow`, which is a
118
+ convenience wrapper built on the legacy `Focusable` / `FocusableGroup` focus model rather
119
+ than on `FocusBoundary`. And `Container` only for deliberate node pooling — it renders
120
+ nothing itself, and detaching a node through it on firmware 15.2.x destroys that node
121
+ (fixed in 15.3).
122
+
123
+ 8. **On an intrinsic, attribute order is behaviour: handlers first, `uri` last.** A capitalized
124
+ wrapper spreads its props, and the spread path hoists event handlers ahead of regular props
125
+ before any are applied. An intrinsic written with inline attributes does not go through that
126
+ path — the compiler emits one `setProp` per attribute in source order and nothing reorders
127
+ them. Two consequences. A property that can synchronously fire an event will fire it before a
128
+ handler written after it, and the event is lost: `<poster uri={u} onLoadStatus={h} />` drops
129
+ the event when the image is already cached, `<poster onLoadStatus={h} uri={u} />` does not.
130
+ (The spread path reorders handlers first; inline intrinsic attributes are not reordered.) And the `Poster` load starts when
131
+ `uri` lands, so a `loadDisplayMode` or a size written after it applies to a load already in
132
+ flight. Full reasoning: `references/scenegraph-node-spelling.md`.
133
+
134
+ 9. **Read the TSDoc for anything this skill does not answer.** Every export ships as raw
135
+ TypeScript with its documentation attached, at
136
+ `node_modules/@roku-sdk/components/src/sg-nodes/<node>.tsx` — `poster.tsx`, `label.tsx`,
137
+ `standard-dialog.tsx` and so on. The few components not backed by a single SceneGraph node
138
+ sit one level up in `src/`: `container.tsx`, `button-row.tsx`, `key-handler.tsx`.
139
+ `ModalBoundary` is re-exported from `@roku-sdk/navigation`. There is no bundled `.d.ts` and no
140
+ package README to consult instead.
141
+
142
+ ## Example
143
+
144
+ Intrinsics for the plain nodes — `poster` and `label` need no import, because each is a
145
+ pass-through with a lowercase tag.
146
+
147
+ The composite nodes are written here as the capitalized `LayoutGroup`, `ButtonGroup` and
148
+ `BusySpinner` components, which is the spelling that compiles on every version. Their intrinsic
149
+ tags are spelled `layoutGroup`, `buttonGroup` and `busySpinner` from SDK v2.15.0 onward, and were
150
+ `layoutgroup`, `buttongroup` and `busyspinner` on v2.14.0 and earlier; the components wrap
151
+ whichever the installed version provides, so prefer them in code meant to survive the rename.
152
+
153
+ ```tsx rsg-skill=complete
154
+ import { BusySpinner, ButtonGroup, LayoutGroup } from "@roku-sdk/components";
155
+ import { Show } from "@roku-sdk/solid";
156
+
157
+ interface DetailPanelProps {
158
+ title: string;
159
+ posterUri: string;
160
+ loading: boolean;
161
+ onAction: (index: number) => void;
162
+ }
163
+
164
+ export function DetailPanel(props: DetailPanelProps) {
165
+ return (
166
+ <layoutGroup layoutDirection="horiz" itemSpacings={[40]}>
167
+ {/* `uri` last: the load starts when it lands. */}
168
+ <poster width={300} height={400} loadDisplayMode="scaleToFit" uri={props.posterUri} />
169
+ <layoutGroup layoutDirection="vert" itemSpacings={[24]}>
170
+ <label text={props.title} width={900} />
171
+ <Show
172
+ when={props.loading}
173
+ fallback={
174
+ <buttonGroup
175
+ buttons={["Play", "More Info"]}
176
+ onButtonSelected={(index: number) => props.onAction(index)}
177
+ />
178
+ }
179
+ >
180
+ <busySpinner uri="theme:ActionHudSpinner" />
181
+ </Show>
182
+ </layoutGroup>
183
+ </layoutGroup>
184
+ );
185
+ }
186
+ ```
187
+
188
+ ## Common Mistakes
189
+
190
+ | Symptom | Cause | Fix |
191
+ |---|---|---|
192
+ | A component compiles but nothing appears | The prop that gives it content or size is optional and was left off | `Label` needs `text`; `Rectangle` and `ScrollableText` need `width` and `height`; `Poster` needs `uri`; `MaskGroup` needs `maskUri` |
193
+ | A `LayoutGroup` centres its children somewhere unexpected | Alignment is relative to the group's own `translation`, not to the screen | Position the group, then let it align within itself |
194
+ | A lone `Button` has no background until it is focused | `showFocusFootprint` defaults off outside a `ButtonGroup` | Set `showFocusFootprint={true}` |
195
+ | Text is set but renders at an unreadable size, or a symbol comes out as an empty box | The typeface, not the node | The `fonts-and-glyphs` skill |
196
+ | A `MaskGroup` masks nothing on some hardware | Masking is OpenGL-only; elsewhere children draw unmasked | Do not rely on it for correctness, only for polish |
197
+ | A TypeScript component cannot be used as a list item | That list renders items from BrightScript XML named by `itemComponentName` | Use `@roku-sdk/flex-grid` for a shelf or grid written in TypeScript |
198
+ | Rounded corners, a border or a gradient is missing, stutters, or will not animate | An `Effect` problem, not a component choice | The `effects` skill |
199
+ | The question names a component library, a URL at runtime, or a partner binary shipped separately | Those words describe a Dynamic Component Library, not a choice between `Poster` and `Label` | The `app-packaging` skill: it owns the routing, and names `@roku-sdk/runtime/component-library` for loading and referencing |
200
+
201
+ ## Tween
202
+
203
+ `Tween` is a logic-bearing wrapper and must remain capitalized. Use `targetNode` with a node
204
+ or a getter returning one. The old `targetRef` name is a deprecated alias for one minor release;
205
+ `targetNode` wins when both are supplied. The wrapper uses the native field types in `ITweenProps`,
206
+ except that its `onAnimationState` callback receives `(state, value)` rather than the intrinsic's
207
+ single `[state, value]` tuple. It binds the target before triggers and releases it on cleanup.
208
+ It defaults to `outCubic`; the native intrinsic defaults to `linear`.
209
+
210
+ The native node is selected when firmware supports the `tween` feature. Import `@roku-sdk/compat`
211
+ for `RsgTweenShim` on older firmware. Without compat, unsupported firmware throws a missing-feature
212
+ error rather than silently rendering an inert node. Axis and settle value shapes are described in
213
+ the linked value-shape contract.
@@ -0,0 +1,148 @@
1
+ # Composing a StandardDialog
2
+
3
+ A `StandardDialog` is assembled from areas, and each area holds items. Nothing about it is
4
+ positioned by hand: the dialog sizes and stacks its own areas, and `width` / `height` default
5
+ to the system size (1038 FHD, 692 HD) when left at 0.
6
+
7
+ ## The areas
8
+
9
+ | Area | Where it goes | Required |
10
+ |---|---|---|
11
+ | `StdDlgTitleArea` | Top, at most one | — |
12
+ | `StdDlgContentArea` | Between title and buttons, at most one | — |
13
+ | `StdDlgButtonArea` | Always the bottom, at most one | — |
14
+ | `StdDlgSideCardArea` | Left or right of the rest, at most one | `width` |
15
+ | `RsgPalette` | A child, or the `palette` prop | `colors` |
16
+
17
+ Ordering in the JSX does not decide layout — the dialog places each area by its type — but
18
+ writing them top-to-bottom keeps the source readable.
19
+
20
+ ## The items that go in the content area
21
+
22
+ `StdDlgTextItem` (`text`) and `StdDlgMultiStyleTextItem` (`drawingStyles`, `audioGuideText`)
23
+ for prose, `StdDlgBulletTextItem` for a list, `StdDlgGraphicItem` for an image,
24
+ `StdDlgProgressItem` (`text`) and `StdDlgDeterminateProgressItem` for progress,
25
+ `StdDlgKeyboardItem` for entry, and `StdDlgCustomItem` when nothing else fits.
26
+
27
+ Wrap a set of choices in a `StdDlgItemGroup`: it tightens the spacing and enforces
28
+ one-of-N selection across the `StdDlgActionCardItem`s inside it. An action card is focusable
29
+ and holds its own `StdDlgTextItem` as its label.
30
+
31
+ Buttons are `StdDlgButton` (`text`), and they belong directly inside `StdDlgButtonArea` —
32
+ nowhere else. The side card never takes key focus, so nothing interactive belongs in it.
33
+
34
+ ## A confirmation dialog
35
+
36
+ ```tsx rsg-skill=complete
37
+ import {
38
+ StandardDialog,
39
+ StdDlgButton,
40
+ StdDlgButtonArea,
41
+ StdDlgContentArea,
42
+ StdDlgTextItem,
43
+ StdDlgTitleArea
44
+ } from "@roku-sdk/components";
45
+
46
+ interface ConfirmDeleteProps {
47
+ onChoice: (index: number) => void;
48
+ onClosed: () => void;
49
+ }
50
+
51
+ export function ConfirmDelete(props: ConfirmDeleteProps) {
52
+ return (
53
+ <StandardDialog
54
+ initialFocusId="confirm-cancel"
55
+ onButtonSelected={index => props.onChoice(index)}
56
+ onWasClosed={() => props.onClosed()}
57
+ >
58
+ <stdDlgTitleArea primaryTitle="Delete recording?" />
59
+ <stdDlgContentArea>
60
+ <stdDlgTextItem text="This removes it from every device on your account." />
61
+ </stdDlgContentArea>
62
+ <stdDlgButtonArea>
63
+ <stdDlgButton id="confirm-cancel" text="Cancel" />
64
+ <stdDlgButton text="Delete" />
65
+ </stdDlgButtonArea>
66
+ </StandardDialog>
67
+ );
68
+ }
69
+ ```
70
+
71
+ `onButtonSelected` reports the index of the button in the order they are declared, so the
72
+ handler switches on 0 and 1 rather than on a label.
73
+
74
+ This reference stops at the dialog itself. Who decides it should be up, what the caller learns
75
+ when it goes down, and how one dialog leads to the next are app state — the
76
+ `dialogs-and-modals` skill owns that. `StandardDialog` normalizes native close notifications
77
+ to at most one `onWasClosed` per presented lifetime while mounted; button/action completion
78
+ remains app state. Unmount releases ownership and retires callbacks, so do not wait for a
79
+ close notification from a component you have already disposed.
80
+
81
+ ## Preparing before presentation
82
+
83
+ The lifecycle props in this section require SDK 2.17.0 (not available in 2.16.0).
84
+
85
+ Use `ready={false}` to prepare the dialog without presenting its content. An optional
86
+ `fallback` is a **raw native dialog element**, not a nested `StandardProgressDialog`
87
+ component (which would create a second modal owner):
88
+
89
+ ```tsx rsg-skill=complete
90
+ import { StandardDialog } from "@roku-sdk/components";
91
+
92
+ const DeferredDialog = (props: { open: boolean; ready: boolean; onClose: () => void }) => (
93
+ <StandardDialog
94
+ open={props.open}
95
+ ready={props.ready}
96
+ replaceExisting={false}
97
+ fallback={
98
+ <standardDialog optionsDialog>
99
+ <stdDlgContentArea><stdDlgProgressItem text="Loading" /></stdDlgContentArea>
100
+ </standardDialog>
101
+ }
102
+ onWasClosed={props.onClose}
103
+ onBlocked={props.onClose}
104
+ title="Preferences"
105
+ optionsDialog
106
+ >
107
+ {/* StdDlg areas and items */}
108
+ </StandardDialog>
109
+ );
110
+ ```
111
+
112
+ `open` defaults to true. Setting it false requests native closure; `onWasClosed` follows
113
+ the native event. Unmount requests closure of only the owned modal and retires its observers.
114
+ Native dismissal does not reopen on a late readiness update; toggle `open` after native
115
+ closure to start another lifetime. `replaceExisting` defaults to true for existing consumers;
116
+ false reports `onBlocked` rather than replacing an unrelated native dialog. The fallback
117
+ and ready content share that ownership, so their handoff does not report a terminal close.
118
+ `onOpened` confirms the main dialog's native attachment and initial focus, not bitmap
119
+ readiness or drawn pixels. `title` and `optionsDialog` are native dialog properties.
120
+
121
+ `close` also stays on the native dialog, without a Modal proxy. Native `close` is
122
+ always-notify: an actual field assignment closes regardless of the Boolean value.
123
+ Solid does not rewrite an unchanged prop. Clearing `scene.dialog` is not the native
124
+ close operation. Non-preemption is opt-in with `replaceExisting={false}`.
125
+
126
+ ## Focus is not yours to wire
127
+
128
+ `StandardDialog` renders a native SceneGraph modal, so SceneGraph moves focus into it when it
129
+ appears and back out when it closes. Point `initialFocusId` at the button that should be
130
+ focused first and stop there. An enclosing `FocusBoundary` goes inactive on its own while the
131
+ dialog is up; setting `focusActive` by hand fights the platform.
132
+
133
+ `ModalBoundary` and `useModal` are the other path, not an addition to this one. They exist for
134
+ an overlay you draw yourself out of ordinary nodes, which has no native modal to hand focus
135
+ to. Choose one: a `StandardDialog`, or a hand-drawn overlay inside a `ModalBoundary`. The
136
+ `focus-management` skill owns the second.
137
+
138
+ ## Ready-made variants
139
+
140
+ When the composition above is more than the job needs, four dialogs arrive pre-assembled and
141
+ take the same props as `StandardDialog`: `StandardMessageDialog` for a message and buttons,
142
+ `StandardKeyboardDialog` for text entry, `StandardPinPadDialog` for a PIN, and
143
+ `StandardProgressDialog` for a wait. They exist as separate modules so an app that uses one
144
+ does not pay for the XML of the others.
145
+
146
+ `Dialog` is the pre-OS-10 node. It is still exported and still works — note its required
147
+ `close` prop, which dismisses the dialog whenever it is *set*, whatever the value, so it is
148
+ normally passed as `close={false}` — but new work should use `StandardDialog`.
@@ -0,0 +1,123 @@
1
+ # SceneGraph Node Spelling: Intrinsics vs. Component Wrappers
2
+
3
+ Most SceneGraph nodes can be written two ways in an RSG SDK TypeScript app:
4
+
5
+ ```tsx
6
+ import { Rectangle } from "@roku-sdk/components";
7
+
8
+ <Rectangle width={200} height={100} color="0x333355FF" />;
9
+ ```
10
+
11
+ ```tsx
12
+ <rectangle width={200} height={100} color="0x333355FF" />;
13
+ ```
14
+
15
+ Both compile. Both type-check against the same `IRectangleProps`. Both produce the same node
16
+ at runtime. The capitalised form is a component whose entire body is
17
+ `props => <rectangle {...props} />`.
18
+
19
+ **Recommended for new work: the lowercase intrinsic.** The wrappers are supported and are not
20
+ deprecated — an existing app has no reason to churn, and either spelling is a legitimate
21
+ choice. This page explains the recommendation so the reasoning travels with it.
22
+
23
+ ## Why the intrinsic is recommended
24
+
25
+ The lowercase tag is the node. The capitalised export adds a component to the tree that exists
26
+ only to spread its props straight through, so the intrinsic is one less indirection between
27
+ what you write and what renders.
28
+
29
+ It also needs no import, which removes a class of mistake rather than merely discouraging one.
30
+ Capitalised names collide with names an app plausibly wants for its own components — `Button`,
31
+ `Panel`, `Video` and `ProgressBar` are all reasonable names for something in your own codebase,
32
+ and importing ours shadows yours.
33
+
34
+ The remaining reason is consistency. Two spellings with no stated preference means every author
35
+ and every code-generating agent picks arbitrarily, and a codebase ends up with both. Picking one
36
+ for new work is worth more than which one gets picked.
37
+
38
+ ## The `<Group>` alias, which used to be a trap
39
+
40
+ `Group` was once registered as a capitalised JSX intrinsic under `@roku-sdk/solid` — uniquely in
41
+ the package, nothing else was. A file could write `<Group>` with no import at all and still
42
+ compile, so a forgotten or misspelled `Group` import did not fail the build and a layout could
43
+ misbehave with nothing to point at.
44
+
45
+ That alias has been removed. `Group` is now an ordinary export like every other wrapper: import
46
+ it if you want it, or write `<group>` for the node. Both spell the same node, and the
47
+ recommendation above still applies — prefer `<group>`.
48
+
49
+ ## Put event handlers first in the attribute list
50
+
51
+ This one matters for correctness, not style, and it applies specifically to intrinsics.
52
+
53
+ A capitalised wrapper spreads its props (`props => <rectangle {...props} />`), and the spread
54
+ path reorders event handlers ahead of regular props before any of them are applied. An intrinsic
55
+ written with inline attributes does not go through that path: the compiler emits one
56
+ `setProp` call per attribute, in source order, and nothing reorders them.
57
+
58
+ So for an intrinsic, the order you write the attributes in is the order they are applied. If a
59
+ property assignment can synchronously fire an event, and its handler is written after that
60
+ property, the event fires before the handler exists and is lost:
61
+
62
+ ```tsx rsg-skill=no-check
63
+ // Wrong: if the image is already in the cache, the load completes during the
64
+ // uri assignment — before onLoadStatus is registered — and the event is dropped.
65
+ <poster uri={posterUri} onLoadStatus={handleLoad} />
66
+
67
+ // Right: the handler is registered before the property that can fire it.
68
+ <poster onLoadStatus={handleLoad} uri={posterUri} />
69
+ ```
70
+
71
+ This is the same defect that was fixed for the spread path only. Inline intrinsic attributes
72
+ are applied in source order, so write handlers first. It is a good habit regardless, since it
73
+ makes the ordering explicit rather than incidental.
74
+
75
+ ## Where the recommendation is binding
76
+
77
+ Samples, demos, documentation examples and the packaged AI skills should use intrinsics
78
+ consistently. This is the part that does the work — code-generating agents imitate surrounding
79
+ code far more reliably than they follow prose guidance, so example code that uses the
80
+ capitalised form teaches the capitalised form regardless of what any doc says.
81
+
82
+ Application code is the developer's call. Nothing here is enforced by lint, and no wrapper is
83
+ scheduled for removal.
84
+
85
+ ## Nodes with no intrinsic equivalent
86
+
87
+ Some exports are not pass-throughs. They add behaviour, and the capitalised component is the
88
+ only correct way to use them:
89
+
90
+ | Component | What it adds |
91
+ |---|---|
92
+ | `Audio` | Renders `content` as a child rather than spreading props alone |
93
+ | `VideoContentNode` | A distinct typed view over `<contentNode>` |
94
+ | `CustomKeyboard`, `DynamicKeyboard`, `DynamicKeyGrid`, `DynamicMiniKeyboard`, `DynamicPinPad` | Call `retainAsset()` so the backing XML survives tree-shaking |
95
+ | `Dialog`, `StandardDialog`, `StandardKeyboardDialog`, `StandardMessageDialog`, `StandardPinPadDialog`, `StandardProgressDialog` | Wrap their node in `<Modal>` |
96
+ | `ListPanel` | Forwards to `<managedListPanel>`, a different node |
97
+ | `Focusable`, `FocusableGroup`, `KeyHandler`, `Modal`, `Container`, `ButtonRow`, `BlendedBusySpinner`, `BoundingTracker`, `ChannelStore` | Real logic, no single backing node |
98
+
99
+ Writing a lowercase tag in place of one of these either fails to compile or silently drops the
100
+ behaviour the wrapper existed to provide.
101
+
102
+ ## Migrating (optional)
103
+
104
+ Props, types and rendered output are unchanged, so the edit is mechanical and the import can
105
+ usually be dropped:
106
+
107
+ ```tsx
108
+ // before
109
+ import { Group, Rectangle, Label } from "@roku-sdk/components";
110
+
111
+ <Group>
112
+ <Rectangle width={w} height={h} color="0x333355FF" />
113
+ <Label text="Hello" horizAlign="center" />
114
+ </Group>;
115
+
116
+ // after
117
+ <group>
118
+ <rectangle width={w} height={h} color="0x333355FF" />
119
+ <label text="Hello" horizAlign="center" />
120
+ </group>;
121
+ ```
122
+
123
+ Check the table above first — a `Dialog` or a `CustomKeyboard` must keep its wrapper.
@@ -0,0 +1,129 @@
1
+ # SDK Installation
2
+
3
+ ## Overview
4
+
5
+ Stable `@roku-sdk/*` packages install from registry.npmjs.org with ordinary pnpm
6
+ commands. No registry override or token is needed. Check the effective registry,
7
+ requested version and lockfile when an install fails; adding authentication is
8
+ not the fix for a public package.
9
+
10
+ ## When to Use
11
+
12
+ - a first install into a project that has none of the SDK yet
13
+ - pnpm cannot find an `@roku-sdk` package, or returns 401 or 404 for that scope
14
+ - an install succeeds locally but fails on another machine or in a pipeline
15
+ - moving SDK versions or staying on a package's older major line
16
+ - satisfying a peer dependency or avoiding duplicate reactive runtimes
17
+ - confirming where installed packages resolved
18
+
19
+ ### When not to use
20
+
21
+ | Situation | Use instead | Because |
22
+ | ------------------------------------------------------------------------------ | ----------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
23
+ | A package the app lists because its code imports it, or configuring a scaffold | The `project-setup` skill | That starts once packages resolve; a package's required peers stay here |
24
+ | A resolved package's API does not behave as expected | The skill for that area | Installation is finished |
25
+ | A different company's private registry | That registry's documentation | This skill covers public SDK consumption |
26
+ | Signing or publishing an app | The `app-packaging` skill | That ships an app outwards; this brings dependencies in |
27
+ | Releasing the SDK itself or obtaining a prerelease | The release process or preview access instructions supplied by the maintainer | npmjs.com carries stable releases; this is the stable consuming side |
28
+
29
+ ## Quick Reference
30
+
31
+ | Goal | Do |
32
+ | ---------------------------------- | -------------------------------------------------------------------------------- |
33
+ | Install stable packages | `pnpm add @roku-sdk/components` — registry.npmjs.org, no token |
34
+ | Check the effective package source | `pnpm config get registry` and `pnpm config get @roku-sdk:registry` |
35
+ | Check a published version or tag | `pnpm view @roku-sdk/flex-grid dist-tags` |
36
+ | Stay on an older major | Use `^2.0.0`; older patches may use `latest-2.16`, but confirm it exists |
37
+ | Verify installed versions | Inspect `pnpm-lock.yaml` and run `pnpm list` |
38
+ | Satisfy FlexGrid's peers | Declare all six in the app's `package.json`, including `@roku-sdk/runtime` |
39
+ | Install in CI | `pnpm install --frozen-lockfile`; public installs need no auth token |
40
+
41
+ ## Instructions
42
+
43
+ 1. **Install from the public registry.** Use Node 24 or later and pnpm 11.
44
+ If the SDK packages are already listed in `package.json`, run `pnpm install`;
45
+ otherwise use `pnpm add` to declare them. No project registry file or token
46
+ setup is required for stable SDK packages.
47
+
48
+ 2. **Check effective configuration before fixing a 401 or 404.** The default
49
+ registry should be `https://registry.npmjs.org/`. An unset `@roku-sdk:registry`
50
+ uses that default; an obsolete scope override still wins over it. Remove
51
+ stale overrides or authentication settings from the configuration that owns
52
+ them. Verify the requested package, version and dist-tag with `pnpm view`.
53
+ A public install does not need a new read token.
54
+
55
+ 3. **Check local and CI environments separately.** A runner can inherit
56
+ different user or environment registry configuration even with identical
57
+ repository files. Match Node/pnpm versions, confirm access to
58
+ registry.npmjs.org, and use the committed lockfile with `--frozen-lockfile`.
59
+ Do not add a CI authentication step for public packages.
60
+
61
+ 4. **Transitive packages install automatically; required peers are different.**
62
+ Which dependencies the app lists because it imports them is `project-setup`.
63
+ A package's peer contract is this skill's subject: inspect it with
64
+ `pnpm view @roku-sdk/flex-grid peerDependencies`.
65
+
66
+ 5. **Move packages released together together.** Keep a shared version range
67
+ across the coordinated SDK packages. A package on an independent major line,
68
+ such as FlexGrid, has its own version, but its peer ranges still require
69
+ compatible SDK versions.
70
+
71
+ 6. **Prefer a semver range over a moving dist-tag for a major line.** `^2.0.0`
72
+ receives compatible 2.x fixes and cannot select 3.x. A stable line can have
73
+ a tag such as `latest-2.16` for older patches; newest-line releases carry only
74
+ `latest`. Check the published tags before installing by one.
75
+ FlexGrid 3.x owns `latest`, while its 2.x maintenance tag is `maintenance-2x`.
76
+ Read the package's `BREAKING_CHANGES.md` before changing majors. Prerelease
77
+ channels are not available on npmjs.com.
78
+
79
+ 7. **Declare all of FlexGrid's peers in the app.** They are `@roku-sdk/common`,
80
+ `@roku-sdk/components`, `@roku-sdk/interfaces`, `@roku-sdk/navigation`,
81
+ `@roku-sdk/runtime` and `@roku-sdk/solid`. They let the app own one shared
82
+ reactive runtime. Satisfy the floors declared by the selected grid release
83
+ rather than upgrading the grid alone.
84
+
85
+ 8. **Verify the lockfile, not just the install's exit code.** Check the
86
+ `@roku-sdk/*` versions and integrity entries in `pnpm-lock.yaml` against the
87
+ installed tree. Packages should resolve from registry.npmjs.org. Modern
88
+ pnpm lockfiles usually omit download URLs and derive the registry from
89
+ configuration: no host string is not evidence of failure. If an older entry
90
+ explicitly points elsewhere, remove its obsolete registry override and run
91
+ `pnpm install --registry https://registry.npmjs.org`, then review the lockfile
92
+ diff. Do not hand-edit integrity hashes or introduce tarball URLs.
93
+
94
+ ## Example
95
+
96
+ In a consumer app with no SDK dependencies yet:
97
+
98
+ ```bash
99
+ pnpm add @roku-sdk/rsg-ts @roku-sdk/components @roku-sdk/solid @roku-sdk/tooling
100
+ pnpm config get registry
101
+ # Expected: https://registry.npmjs.org/
102
+ pnpm config get @roku-sdk:registry
103
+ # Unset is normal: the scope uses the default registry.
104
+ pnpm view @roku-sdk/rsg-ts version --registry https://registry.npmjs.org
105
+ pnpm list @roku-sdk/rsg-ts @roku-sdk/components
106
+ ```
107
+
108
+ Review the `@roku-sdk/*` entries in `pnpm-lock.yaml`, then commit it with
109
+ `package.json`. A CI runner installs the same tree with:
110
+
111
+ ```bash
112
+ pnpm install --frozen-lockfile
113
+ ```
114
+
115
+ For the exact exports and peer contract of an installed package, inspect its
116
+ `package.json` and typed source under `node_modules/@roku-sdk/`.
117
+
118
+ ## Common Mistakes
119
+
120
+ | Symptom | Cause | Fix |
121
+ | ---------------------------- | ----------------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
122
+ | 401 for public SDK packages | Unexpected registry or stale authentication configuration | Check default and scoped configuration on that machine; use registry.npmjs.org without a token |
123
+ | 404 or no matching version | Misspelled package or unpublished version/tag | Inspect `pnpm view`; use a published stable version |
124
+ | Works locally, fails in CI | Different configuration, network access or toolchain | Check the runner's effective registry and Node/pnpm versions; no public read token is needed |
125
+ | Frozen lockfile mismatch | Dependency declarations changed without the lockfile | Update locally and commit both files |
126
+ | Lockfile has no registry URL | Current pnpm stores integrity and versions rather than URLs | Check effective registry configuration and installed versions, not a grep's empty output |
127
+ | Unrequested major upgrade | A moving dist-tag was used instead of a range | Pin the intended major with a range; confirm tags before deliberate one-off installs |
128
+ | Duplicate reactive runtime | Required peers were omitted or incompatible | Declare all six FlexGrid peers and satisfy their ranges |
129
+ | `workspace:*` cannot resolve | The app is outside the SDK workspace | Replace workspace ranges with published versions |