@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,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 |
|