@roku-sdk/ai 0.0.0-stage → 2.18.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 +181 -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 +205 -0
- package/skills/focus-management/references/focus-recipes.md +282 -0
- package/skills/fonts-and-glyphs/SKILL.md +176 -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,210 @@
|
|
|
1
|
+
# Project Setup
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
`npx rk create <app-name>` scaffolds an RSG SDK TypeScript app that already builds and
|
|
6
|
+
runs. Five things then own the project: `roku-config.ts` (app config and manifest entries),
|
|
7
|
+
`tsconfig.json` (one `extends` line), `package.json` (the `@roku-sdk/*` dependencies),
|
|
8
|
+
`src/` (TypeScript and imported images) and `pkg/` (static assets copied verbatim).
|
|
9
|
+
|
|
10
|
+
## Read the reference first for these tasks
|
|
11
|
+
|
|
12
|
+
| If the request is | Read now, before answering |
|
|
13
|
+
|---|---|
|
|
14
|
+
| Any `roku-config.ts` field this body does not itself show | `references/roku-config.md` |
|
|
15
|
+
|
|
16
|
+
This body shows `app.entryPoint` and `manifestEntries`, plus the `images` and `logger` blocks in
|
|
17
|
+
the Example. For any other field, open the reference and name the field from it. If it is not
|
|
18
|
+
there either, the authority is `RokuConfig` — the type already annotating the app's own
|
|
19
|
+
`roku-config.ts` — because the schema gains fields faster than either page. This table routes;
|
|
20
|
+
there is nothing in it to answer from.
|
|
21
|
+
|
|
22
|
+
## When to Use
|
|
23
|
+
|
|
24
|
+
- creating a new app, or adding one to an existing repo
|
|
25
|
+
- the app title, version, splash screen or supported resolutions have to change
|
|
26
|
+
- a new image, font or static asset has to be added and referenced from code
|
|
27
|
+
- an `@roku-sdk/*` package the app imports is missing from its `package.json`
|
|
28
|
+
- the app's TypeScript setup is wrong: JSX or SDK types not resolving
|
|
29
|
+
- declaration generation stops on an XML component base the SDK does not map, or whether built-in mappings still apply alongside yours in `dtsOptions`
|
|
30
|
+
- `rk` cannot find the config, or a first run on a device is not working yet
|
|
31
|
+
|
|
32
|
+
### When not to use
|
|
33
|
+
|
|
34
|
+
| Situation | Use instead | Because |
|
|
35
|
+
|---|---|---|
|
|
36
|
+
| Building, deploying, sideloading, or reading logs from an app that already runs, inside an agent session | The `mcp-ai-dev-loop` skill | Those are build and deploy operations, not project configuration |
|
|
37
|
+
| The same operations from a shell script or a CI job | The `cli-dev-loop` skill | Same reason, different surface — and the CLI's directories and flags are not the ones the tools expose |
|
|
38
|
+
| An `@roku-sdk` package that will not resolve at all, moving the versions of ones that do, a peer one of them requires the app to declare, or a `workspace:*` range that will not resolve | The `sdk-installation` skill | Reaching the registry, and which version line arrives from it, comes before any of this |
|
|
39
|
+
| Registering a Roku, storing its password, or choosing which one a command reaches | The `device-management` skill | That store lives outside the repository and is not project configuration |
|
|
40
|
+
| A legacy BrightScript channel with XML components | Roku's BrightScript documentation | This SDK's project shape does not apply there |
|
|
41
|
+
| How the app behaves at runtime — focus, layout, data | The skill for that area | Setup ends where the app's own code begins |
|
|
42
|
+
| A convention that holds only in one repo | That repo's `AGENTS.md` | Repo-specific facts do not belong in a portable skill |
|
|
43
|
+
|
|
44
|
+
## Quick Reference
|
|
45
|
+
|
|
46
|
+
| Task | Do this |
|
|
47
|
+
|---|---|
|
|
48
|
+
| Create an app | `npx rk create my-app` — name lowercase, letters, digits, hyphens |
|
|
49
|
+
| Change title, version, splash, resolutions | `manifestEntries` in `roku-config.ts` |
|
|
50
|
+
| An XML base declaration generation does not recognise | `dtsOptions.componentMapping` in `roku-config.ts`; keep sibling `includeDefaults` on so the SDK's own mappings still merge |
|
|
51
|
+
| Configure anything else in `roku-config.ts` | `references/roku-config.md` — the field names are only there |
|
|
52
|
+
| Register a Roku, store its password, or pick which one commands reach | The `device-management` skill |
|
|
53
|
+
| Keep the device developer password out of git | it lands in `.roku/roku-dev-config.json`, which the scaffold already covers by ignoring `.roku/` — that entry is also the only one it writes, not even build output |
|
|
54
|
+
| Build the app, install it, drive it or read its logs from a shell | The `cli-dev-loop` skill |
|
|
55
|
+
| Find any flag | `npx rk <command> --help` — the help text is the contract |
|
|
56
|
+
|
|
57
|
+
## Instructions
|
|
58
|
+
|
|
59
|
+
1. **Scaffold with `npx rk create my-app`**, then `cd my-app && npx rk dev`. It
|
|
60
|
+
installs dependencies, generates the app icons and drops the SDK's agent skills into the
|
|
61
|
+
new repo. Needs Node >= 24 and pnpm. Do **not** assemble a project by hand, and do
|
|
62
|
+
**not** reach for `brighterscript`, `roku-deploy` or `ropm` — a different toolchain that
|
|
63
|
+
does not apply to this SDK.
|
|
64
|
+
|
|
65
|
+
A standalone app also gets a `pnpm-workspace.yaml` whose `allowBuilds` block allows
|
|
66
|
+
`@roku-sdk/tooling`'s install script and denies `esbuild`, `unrs-resolver` and
|
|
67
|
+
`@parcel/watcher`. pnpm 11 fails every install and every `pnpm run` without a decision
|
|
68
|
+
for each, so keep the file. Created inside an existing pnpm workspace, the app gets no
|
|
69
|
+
file and the root's `allowBuilds` has to cover those four.
|
|
70
|
+
|
|
71
|
+
The `.gitignore` it writes holds a single entry, `.roku/`. That is the entry that matters —
|
|
72
|
+
the device developer password lands in `.roku/roku-dev-config.json` in clear text, so without
|
|
73
|
+
it `git add -A` would commit a credential — but it is also the only one, so build output and
|
|
74
|
+
`node_modules` still want adding before the first commit.
|
|
75
|
+
|
|
76
|
+
2. **Configure the app in `roku-config.ts`** at the app root — see the Example. Only
|
|
77
|
+
`app.entryPoint` is required. Let `manifestEntries` write the manifest, and never hand-write
|
|
78
|
+
one alongside it. The build does read a `manifest` file when the app ships one, keeping its
|
|
79
|
+
comments, its line order and any key `manifestEntries` leaves alone — but every key set in
|
|
80
|
+
both is overwritten from `roku-config.ts`, with a warning. Two sources for one file that
|
|
81
|
+
only ever resolves one way is a file that drifts. `rk` finds `roku-config.ts` by walking up
|
|
82
|
+
from the current directory, so run commands from inside the app or pass `--config <path>`.
|
|
83
|
+
|
|
84
|
+
3. **Know the chain that starts the app**, because nothing under `src/` runs until all of it is
|
|
85
|
+
in place. BrightScript's `Main()` creates the SceneGraph screen and instantiates a Scene
|
|
86
|
+
**by name**; that Scene is the root node, and the app's anchor node is a child of it, not
|
|
87
|
+
the root itself. The app's entry point then calls `main()` and registers the anchor node
|
|
88
|
+
with `registerAnchorNode` **inside** that callback. The Scene's generated XML has to be
|
|
89
|
+
imported for its side effect — `import "./compiled-components/<Scene>.xml";` — or the name
|
|
90
|
+
`Main()` asks for does not exist at runtime and the app comes up as an empty screen with no
|
|
91
|
+
build error.
|
|
92
|
+
|
|
93
|
+
4. **Keep `tsconfig.json` to one `extends` line**, by package alias:
|
|
94
|
+
|
|
95
|
+
```json
|
|
96
|
+
{
|
|
97
|
+
"extends": "@roku-sdk/rsg-ts/tsconfig.json",
|
|
98
|
+
"compilerOptions": { "rootDir": "src", "baseUrl": "." },
|
|
99
|
+
"include": ["src/**/*"]
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Never extend by a relative path — it breaks as soon as the app moves. `jsx`,
|
|
104
|
+
`jsxImportSource: "@roku-sdk/solid"` and the SDK's `types` all arrive from that config,
|
|
105
|
+
so do not re-declare them. If you must add to `types`, re-list
|
|
106
|
+
`@roku-sdk/interfaces/global` and `@roku-sdk/components/jsx` beside your addition,
|
|
107
|
+
because the array **replaces** the inherited one.
|
|
108
|
+
|
|
109
|
+
5. **Declare every package the app imports** in its own `package.json`, including one you
|
|
110
|
+
only take a type from — pnpm does not hoist. Use `"workspace:*"` inside the SDK's pnpm
|
|
111
|
+
workspace and one shared caret version for a standalone app, since `@roku-sdk/*`
|
|
112
|
+
packages are released together; which line that is, and any peer a package requires on top,
|
|
113
|
+
belong to the `sdk-installation` skill. Into a standalone app, `rk create` writes
|
|
114
|
+
`@roku-sdk/common`, `@roku-sdk/components`, `@roku-sdk/interfaces`, `@roku-sdk/native`,
|
|
115
|
+
`@roku-sdk/rsg-ts`, `@roku-sdk/sdk-config`, `@roku-sdk/solid`, `@roku-sdk/testing-library`,
|
|
116
|
+
`@roku-sdk/tooling` and `@roku-sdk/ts-core`, plus `@roku-sdk/dev-tools` as a dev
|
|
117
|
+
dependency. The `rk` launcher loads `tsx` from tooling itself; do not add `tsx`
|
|
118
|
+
merely to run `npx rk`. Run inside the SDK's own workspace it scaffolds from a different app and the
|
|
119
|
+
set differs, so read the `package.json` it generated before concluding anything is missing.
|
|
120
|
+
What is not there is on you to add: `@roku-sdk/navigation` the moment the screen needs
|
|
121
|
+
focus, `@roku-sdk/flex-grid` for a shelf or poster grid. `solid-js` is deliberately
|
|
122
|
+
not in that set and must not be added: SolidJS reaches the app through `@roku-sdk/solid`,
|
|
123
|
+
which re-exports the part of it that works under the SceneGraph renderer. Import every
|
|
124
|
+
SolidJS primitive from there, never from `solid-js`. Declaring a package is not the same as
|
|
125
|
+
being able to install it — if one of these will not resolve at all, that is the
|
|
126
|
+
`sdk-installation` skill.
|
|
127
|
+
|
|
128
|
+
6. **Put app images in `src/` and import them by relative path.** The import gives you
|
|
129
|
+
`uri`, plus `dimensions` when the intrinsic size is needed:
|
|
130
|
+
|
|
131
|
+
```tsx
|
|
132
|
+
import { Poster } from "@roku-sdk/components";
|
|
133
|
+
|
|
134
|
+
import { dimensions, uri } from "../images/logo-fhd.png";
|
|
135
|
+
|
|
136
|
+
<poster uri={uri} width={dimensions.fhd.width} height={dimensions.fhd.height} />;
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Alias them — `import { uri as logoUri }` — in a module that imports more than one image.
|
|
140
|
+
Never hardcode a `pkg:/` URI for an app image: that opts out of the path rewrite, the
|
|
141
|
+
webp conversion and the copy into the package. Importable extensions are `.png`, `.jpg`,
|
|
142
|
+
`.jpeg` and `.webp`.
|
|
143
|
+
|
|
144
|
+
7. **For one image per resolution, put the placeholder in the specifier** —
|
|
145
|
+
`import { uri } from "../images/logo-*.png"`. The `*` is **not** a glob: it is the
|
|
146
|
+
`uri_resolution_autosub` placeholder and is substituted literally, so the files must be
|
|
147
|
+
named exactly `logo-fhd.png` and `logo-hd.png`. A 9-patch (`logo-*.9.png`) fails the
|
|
148
|
+
build outright without a hand-authored `logo-hd.9.png`.
|
|
149
|
+
|
|
150
|
+
8. **Put the icons and splash named in `manifestEntries` under `pkg/images/`** and reference
|
|
151
|
+
them as `pkg:/images/...`. Everything under `pkg/` is copied into the package verbatim,
|
|
152
|
+
which is what makes it the one place a `pkg:/` URI is written by hand.
|
|
153
|
+
|
|
154
|
+
## Example
|
|
155
|
+
|
|
156
|
+
```ts rsg-skill=complete
|
|
157
|
+
import { getBuildType, type RokuConfig } from "@roku-sdk/rsg-ts/roku-config";
|
|
158
|
+
|
|
159
|
+
const buildType = getBuildType();
|
|
160
|
+
|
|
161
|
+
const configuration: RokuConfig = {
|
|
162
|
+
app: {
|
|
163
|
+
entryPoint: "src/index.ts",
|
|
164
|
+
images: { convertToWebp: true, generateHd: true },
|
|
165
|
+
logger: { baseLogLevel: "INFO", currentLogLevel: "ERROR" },
|
|
166
|
+
manifestEntries: {
|
|
167
|
+
title: buildType === "production" ? "My App" : `My App (${buildType})`,
|
|
168
|
+
major_version: 1,
|
|
169
|
+
minor_version: 0,
|
|
170
|
+
build_version: 0,
|
|
171
|
+
ui_resolutions: "fhd",
|
|
172
|
+
uri_resolution_autosub: "*,sd,hd,fhd",
|
|
173
|
+
mm_icon_focus_hd: "pkg:/images/app-icon-hd.png",
|
|
174
|
+
mm_icon_focus_fhd: "pkg:/images/app-icon-fhd.png",
|
|
175
|
+
splash_screen_hd: "pkg:/images/splash_hd.jpg",
|
|
176
|
+
splash_color: "0x000000FF",
|
|
177
|
+
splash_min_time: 1,
|
|
178
|
+
rsg_version: "2.0"
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
export default configuration;
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
`ui_resolutions` also fixes the space the app is **authored** in. With `"fhd"`, every
|
|
187
|
+
coordinate, width and height in the code is in a 1920×1080 design space whatever the device
|
|
188
|
+
turns out to be, and an HD box scales the whole tree down as it renders. So write one set of
|
|
189
|
+
numbers and never branch on the device's resolution. Reading a measurement back *off* a device
|
|
190
|
+
runs the other way — a 720p screenshot's pixels are two-thirds of the number the code used —
|
|
191
|
+
and that direction belongs to the `mcp-ai-dev-loop` skill.
|
|
192
|
+
|
|
193
|
+
## Common Mistakes
|
|
194
|
+
|
|
195
|
+
| Symptom | Cause | Fix |
|
|
196
|
+
|---|---|---|
|
|
197
|
+
| An image import resolves to the wrong file, or to nothing | `*` was read as a glob | Name the files `-fhd` and `-hd` exactly |
|
|
198
|
+
| `HD image not found for 9-patch image` | A `.9.png` has no HD variant | Author `-hd.9.png` by hand; it is not generated |
|
|
199
|
+
| SDK types vanished after editing `types` in `tsconfig.json` | The array replaced the inherited one | Re-list `@roku-sdk/interfaces/global` and `@roku-sdk/components/jsx` |
|
|
200
|
+
| Builds fine, but no icon on the device | `mm_icon_focus_hd` / `mm_icon_focus_fhd` name files that are not there | Put them under `pkg/images/` |
|
|
201
|
+
| `git status` offers to commit `.roku/roku-dev-config.json`, which holds the password | The project was not scaffolded by `rk create`, so nothing ignores `.roku/` | Ignore `.roku/`, and `git rm --cached` it if it was already committed |
|
|
202
|
+
| `.roku/` is git-ignored but `rk` still warns that it is not | The check recognises `.roku`, `/.roku`, `.roku/` and `/.roku/`, and no other pattern | Use one of those four forms rather than a glob such as `**/.roku` |
|
|
203
|
+
| The build stops on an XML component whose base the declaration generation does not recognise, and no SDK version ever mapped it | The base is the app's own, so only the app can declare it | Add it under `dtsOptions.componentMapping` in `roku-config.ts`, and leave the sibling `includeDefaults` on so the SDK's own mappings still merge in |
|
|
204
|
+
| `rk` cannot find the config | It walks up from the current directory | Run from inside the app, or pass `--config` |
|
|
205
|
+
| The build fails on something the installed version handles — a base component the SDK maps itself, a define it sets itself — or otherwise behaves like an older SDK | `rk` prefers a running `rk server`, which executes the build with the tooling it loaded at startup, from whatever project it was started in. Reinstalling the app's packages cannot change code already resident in that process | `rk server status` to see it, `rk server stop` then start it again from this app, or pass `--local` to skip server discovery for one command |
|
|
206
|
+
| A package you import is missing at build time | pnpm does not hoist it in | Declare it in the app's own `package.json` |
|
|
207
|
+
| An import of `solid-js` does not resolve, or its API does nothing on the device | `solid-js` belongs to `@roku-sdk/solid`, not to the app, and only the re-exported part drives the SceneGraph renderer | Import from `@roku-sdk/solid` |
|
|
208
|
+
|
|
209
|
+
Every config field is typed with TSDoc in `@roku-sdk/sdk-config` — `UserSdkConfig`,
|
|
210
|
+
`UserSdkAppConfig`, `RokuManifestEntries`. Read a field there rather than guessing its name.
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# `roku-config.ts` beyond the manifest
|
|
2
|
+
|
|
3
|
+
The skill body covers the part of this file every app touches: `app.entryPoint`, and the
|
|
4
|
+
`manifestEntries` block that *is* the manifest. This file covers the rest — the blocks that
|
|
5
|
+
exist for a specific job, and the rules for which value wins when more than one place sets it.
|
|
6
|
+
|
|
7
|
+
Every field is typed with TSDoc in `@roku-sdk/sdk-config` (`UserSdkConfig`, `UserSdkAppConfig`,
|
|
8
|
+
`RokuManifestEntries`). Read a field's type there; read the behaviour here.
|
|
9
|
+
|
|
10
|
+
## Code that must not reach production
|
|
11
|
+
|
|
12
|
+
`app.buildVariables` injects build-time constants, and two names are supplied whether or not
|
|
13
|
+
you declare them.
|
|
14
|
+
|
|
15
|
+
`__DEV__` is `true` under `rk dev` and `false` under `rk build`, except for
|
|
16
|
+
`rk build --debug`. It is **reserved**: declaring it in `buildVariables` does not override it.
|
|
17
|
+
Guarding with it is what removes code from a production build, because the branch is
|
|
18
|
+
tree-shaken away rather than merely skipped.
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
if (__DEV__) {
|
|
22
|
+
// dev-only logic, absent from a production bundle
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Naming reactive work is a different switch, and it is not a build variable. `solidDevMode`
|
|
27
|
+
sits at the top level beside `app` (or comes from `--solid-dev-mode`) and resolves solid through
|
|
28
|
+
its development build, which is what gives `createSignal`, `createMemo` and `createEffect`
|
|
29
|
+
their names in traces and Solid devtools — without it reactive work is anonymous, which is
|
|
30
|
+
usually discovered halfway through reading a trace. The config field takes effect only on
|
|
31
|
+
`rk dev --debug`; `--solid-dev-mode` works on any JavaScript build. Plain `rk dev` and
|
|
32
|
+
`rk build` ignore the config field, and bytecode builds skip it, so a production trace stays
|
|
33
|
+
anonymous however this is set.
|
|
34
|
+
|
|
35
|
+
`buildVariables: { __SIGNAL__: true }` is the legacy spelling of the same switch. Reach for
|
|
36
|
+
`solidDevMode` instead: values are JSON-stringified on the way through, so the bare `true`
|
|
37
|
+
reaches the switch as `true` and matches, while `"true"` reaches it still quoted and turns
|
|
38
|
+
nothing on. Both spellings are legal build variables, so the compiler stays silent on the
|
|
39
|
+
one that does nothing.
|
|
40
|
+
|
|
41
|
+
```ts rsg-skill=complete
|
|
42
|
+
import { type RokuConfig } from "@roku-sdk/rsg-ts/roku-config";
|
|
43
|
+
|
|
44
|
+
const configuration: RokuConfig = {
|
|
45
|
+
solidDevMode: true,
|
|
46
|
+
app: {
|
|
47
|
+
entryPoint: "src/index.ts",
|
|
48
|
+
// Not a build variable: the two log-level keys are written by the build
|
|
49
|
+
// itself, so setting them in `buildVariables` is silently discarded.
|
|
50
|
+
logger: { baseLogLevel: "INFO" }
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The log levels are the one pair that does not belong in `buildVariables`. `__BASE_LOG_LEVEL__`
|
|
56
|
+
and `__CURRENT_LOG_LEVEL__` are set by the build from `app.logger` and overwrite whatever you put
|
|
57
|
+
there, with no warning — see the `logging-and-errors` skill for what each level actually does.
|
|
58
|
+
|
|
59
|
+
## Seeing inside a function without editing it
|
|
60
|
+
|
|
61
|
+
`app.perfetto` wraps eligible named functions in trace slices at build time.
|
|
62
|
+
|
|
63
|
+
| Field | Effect |
|
|
64
|
+
|---|---|
|
|
65
|
+
| `instrumentPaths` | Folder prefixes to instrument, matched as **substrings** of the source path. Empty or absent disables instrumentation |
|
|
66
|
+
| `exclude` | Babel visitor shapes to skip: `"FunctionDeclaration"`, `"VariableDeclarator"`, `"FunctionExpression"`, `"ObjectMethod"` |
|
|
67
|
+
| `filter` | Trace-name substrings to keep. Absent instruments every eligible name; an **empty array instruments none** |
|
|
68
|
+
|
|
69
|
+
The substring match is the trap: `"src"` also matches `resource/data` and `other-src/utils`,
|
|
70
|
+
so give a prefix specific enough to mean what you intended.
|
|
71
|
+
|
|
72
|
+
`--perfetto-paths`, `--perfetto-exclude` and `--perfetto-filter` override the corresponding
|
|
73
|
+
field for one run. Each is repeatable and each value may be comma-separated. Passing an empty
|
|
74
|
+
value disables that dimension rather than falling back to the config.
|
|
75
|
+
|
|
76
|
+
## An XML component whose base the SDK does not know
|
|
77
|
+
|
|
78
|
+
A build that fails asking for a missing base-component mapping is asking for `dtsOptions`.
|
|
79
|
+
Declaration generation maps every base an XML component extends; a base outside the merged
|
|
80
|
+
mapping — the SDK's defaults plus yours — stops the build rather than emitting a `.d.ts` with a
|
|
81
|
+
hole in it.
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
import { type RokuConfig } from "@roku-sdk/rsg-ts/roku-config";
|
|
85
|
+
|
|
86
|
+
const configuration: RokuConfig = {
|
|
87
|
+
app: { entryPoint: "src/index.ts" },
|
|
88
|
+
dtsOptions: {
|
|
89
|
+
includeDefaults: true,
|
|
90
|
+
augmentJSX: true,
|
|
91
|
+
componentMapping: {
|
|
92
|
+
MyCustomBase: {
|
|
93
|
+
interface: "IMyCustomBaseProps",
|
|
94
|
+
module: "./src/components/MyCustomBase.xml"
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Keep `includeDefaults` on unless you intend to replace the SDK's mappings wholesale; the
|
|
102
|
+
`componentMapping` object merges with them rather than shadowing the file.
|
|
103
|
+
|
|
104
|
+
## Signing, and the one value that must not be here
|
|
105
|
+
|
|
106
|
+
`package.devId` is the developer ID expected after a re-key, and `package.outDir` is where a
|
|
107
|
+
signed package lands. The signing password is **not** a config field: pass
|
|
108
|
+
`--signing-password`, or set `ROKU_SIGNING_PASSWORD`. A password written into `roku-config.ts`
|
|
109
|
+
is a credential in the repository.
|
|
110
|
+
|
|
111
|
+
## Publishing
|
|
112
|
+
|
|
113
|
+
`roku-config.ts` accepts a `publishing` block, but `rk` does not currently read it, so editing
|
|
114
|
+
it changes nothing a command does. A version reaches a channel by uploading the signed `.pkg` in
|
|
115
|
+
the Roku Developer Dashboard; getting that `.pkg` is the `app-packaging` skill's subject.
|
|
116
|
+
|
|
117
|
+
## Which value wins
|
|
118
|
+
|
|
119
|
+
Perfetto instrumentation takes the CLI flag first, then the `app.perfetto` field. There is no
|
|
120
|
+
environment layer, so a stale environment variable cannot outrank a value you just edited.
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# Moving between screens
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
`@roku-sdk/navigation`'s screen controller owns which whole view is on screen. It offers two
|
|
6
|
+
styles that mix freely: a **stack**, where a pushed view layers over the previous one and the
|
|
7
|
+
one below stays mounted, and a **replace**, where the outgoing view is torn down.
|
|
8
|
+
|
|
9
|
+
Two things about it are not guessable from the API and cause most of the surprises. It renders
|
|
10
|
+
**no background of its own**, so a pushed view does not hide what is under it. And it exposes
|
|
11
|
+
**no hook** — a screen reads the context directly.
|
|
12
|
+
|
|
13
|
+
## When to Use
|
|
14
|
+
|
|
15
|
+
| If the request is | Then |
|
|
16
|
+
|---|---|
|
|
17
|
+
| Opening a whole view over another, and getting back to the first with its state intact | Here — the stack |
|
|
18
|
+
| Swapping the current view for a sibling, with the old one torn down | Here — replace |
|
|
19
|
+
| Dropping everything a viewer had open and returning to the app's starting view | Here — reset |
|
|
20
|
+
| Releasing work while a view is covered and resuming when it is on top again | Here — the cover callbacks |
|
|
21
|
+
| A pushed view through which the previous one is still visible | Here — the controller draws no background |
|
|
22
|
+
| Handing a value to the view being opened | Here — props on the factory's element |
|
|
23
|
+
|
|
24
|
+
### When not to use
|
|
25
|
+
|
|
26
|
+
| Situation | Use instead | Because |
|
|
27
|
+
|---|---|---|
|
|
28
|
+
| Focus or D-pad movement inside a view, or handing focus between regions of one | The `focus-management` skill | Screen routing is not a focus concern. Focus work starts once the view is up |
|
|
29
|
+
| A view that comes up blank with no crash and no stack | The `logging-and-errors` skill | That is a component that threw, not a navigation problem. The controller will happily mount a screen that renders nothing |
|
|
30
|
+
| A dialog or overlay over the current view | The `dialogs-and-modals` skill for its lifecycle, `sdk-components` for what it is made of | A dialog leaves the view underneath mounted *and* claims focus through a native modal. Pushing a screen does neither |
|
|
31
|
+
| The app's directory layout, or which packages it declares | The `project-setup` skill | The scaffold already lists `@roku-sdk/navigation`, so there is nothing to add before using this |
|
|
32
|
+
| Moving along a shelf or grid, or off its edge onto something else | The `flex-grid` skill | A grid owns its own movement keys and its own edges |
|
|
33
|
+
|
|
34
|
+
## Quick Reference
|
|
35
|
+
|
|
36
|
+
Read the context to get these. Every one is a member of the controller.
|
|
37
|
+
|
|
38
|
+
| Member | Does |
|
|
39
|
+
|---|---|
|
|
40
|
+
| `pushScreen(factory)` | Layers a view on top. The one below stays mounted, so its state survives |
|
|
41
|
+
| `popScreen()` | Drops the top one; the one below reappears untouched |
|
|
42
|
+
| `setScreen(factory)` | Replaces the top entry. **The outgoing instance is destroyed and its local state is gone** |
|
|
43
|
+
| `resetScreen(factory?)` | Clears the whole history. With no argument, lands on the provider's fallback |
|
|
44
|
+
| `getScreen()` | The element for the current stack, which the root scene renders |
|
|
45
|
+
| `onScreenCovered(callback)` | Fires when something is pushed over this view |
|
|
46
|
+
| `onScreenUncovered(callback)` | Fires when the thing above it is popped and it is top again |
|
|
47
|
+
|
|
48
|
+
`ScreenControllerProvider` takes `children` and a required `fallback` — the factory used when
|
|
49
|
+
the stack is empty, when an entry's factory is `null`, and when `resetScreen()` is called bare.
|
|
50
|
+
|
|
51
|
+
## Instructions
|
|
52
|
+
|
|
53
|
+
1. **Mount `ScreenControllerProvider` once, near the root, and give it a `fallback`.** The
|
|
54
|
+
fallback is not an error path — it is the app's starting view, and three different calls
|
|
55
|
+
land on it.
|
|
56
|
+
|
|
57
|
+
2. **Reach the controller with `useContext(ScreenControllerContext)`.** That is the whole
|
|
58
|
+
access pattern. Its default value throws if it is read outside the provider, so a missing
|
|
59
|
+
provider fails loudly rather than silently doing nothing.
|
|
60
|
+
|
|
61
|
+
3. **Choose the style by what should survive.** `pushScreen` / `popScreen` for drilling in and
|
|
62
|
+
coming back, because the view underneath stays mounted and Solid keeps its state for free.
|
|
63
|
+
`setScreen` for siblings — tabs, wizard steps — because only one should be alive.
|
|
64
|
+
`resetScreen` to clear history.
|
|
65
|
+
|
|
66
|
+
4. **A pushed view does not hide the one below it.** The controller paints nothing, so unless
|
|
67
|
+
the new view covers the screen with something opaque, the old one shows through. Either give
|
|
68
|
+
the pushed view an opaque background, or set the lower one invisible from its
|
|
69
|
+
`onScreenCovered`.
|
|
70
|
+
|
|
71
|
+
5. **Pass data as props on the element the factory returns.** The factory closes over whatever
|
|
72
|
+
is in scope, so no parameter mechanism is needed. But those props are a **snapshot taken at
|
|
73
|
+
navigation time** — mutating the source afterwards does not reach the mounted view. Pass an
|
|
74
|
+
accessor when the view has to track a value that keeps changing.
|
|
75
|
+
|
|
76
|
+
6. **Register the cover callbacks from `onMount`.** They unregister themselves when the calling
|
|
77
|
+
owner is disposed, so there is no matching teardown to write. What belongs in them is work
|
|
78
|
+
that costs something while nobody is looking: polling, timers, heavy textures.
|
|
79
|
+
|
|
80
|
+
7. **Return to the start with `setScreen(null)` or a bare `resetScreen()`.** Both render the
|
|
81
|
+
provider's fallback; `resetScreen()` also empties the history so there is nothing left to
|
|
82
|
+
pop back to.
|
|
83
|
+
|
|
84
|
+
## Example
|
|
85
|
+
|
|
86
|
+
A provider, a list that drills in, and a details view that releases work while it is covered.
|
|
87
|
+
|
|
88
|
+
```tsx rsg-skill=complete
|
|
89
|
+
import { ScreenControllerContext, ScreenControllerProvider } from "@roku-sdk/navigation";
|
|
90
|
+
import { Component, createSignal, JSX, onMount, useContext } from "@roku-sdk/solid";
|
|
91
|
+
|
|
92
|
+
interface DetailsProps {
|
|
93
|
+
itemId: string;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const DetailsScreen: Component<DetailsProps> = props => {
|
|
97
|
+
const screens = useContext(ScreenControllerContext);
|
|
98
|
+
const [live, setLive] = createSignal(true);
|
|
99
|
+
|
|
100
|
+
onMount(() => {
|
|
101
|
+
// Both callbacks unregister with this owner — nothing to undo by hand.
|
|
102
|
+
screens.onScreenCovered(() => setLive(false));
|
|
103
|
+
screens.onScreenUncovered(() => setLive(true));
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
return (
|
|
107
|
+
<group>
|
|
108
|
+
{/* Opaque, because the controller paints no background of its own. */}
|
|
109
|
+
<rectangle width={1920} height={1080} color="0x101018FF" />
|
|
110
|
+
<label text={`Details for ${props.itemId}`} translation={[96, 96]} />
|
|
111
|
+
<label text={live() ? "streaming" : "paused"} translation={[96, 160]} />
|
|
112
|
+
</group>
|
|
113
|
+
);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const HomeScreen: Component = () => <label text="Home" translation={[96, 96]} />;
|
|
117
|
+
|
|
118
|
+
/** Renders whichever entries are on the stack. One of these, under the provider. */
|
|
119
|
+
const ScreenHost: Component = () => {
|
|
120
|
+
const screens = useContext(ScreenControllerContext);
|
|
121
|
+
return <group>{screens.getScreen()}</group>;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
export const RootScene: Component = (): JSX.Element => (
|
|
125
|
+
<ScreenControllerProvider fallback={() => <HomeScreen />}>
|
|
126
|
+
<ScreenHost />
|
|
127
|
+
</ScreenControllerProvider>
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
export { DetailsScreen };
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Drilling in and coming back, from anywhere under the provider:
|
|
134
|
+
|
|
135
|
+
```tsx
|
|
136
|
+
import { ScreenControllerContext } from "@roku-sdk/navigation";
|
|
137
|
+
import { useContext } from "@roku-sdk/solid";
|
|
138
|
+
|
|
139
|
+
import { DetailsScreen } from "./details-screen";
|
|
140
|
+
|
|
141
|
+
const screens = useContext(ScreenControllerContext);
|
|
142
|
+
|
|
143
|
+
// `itemId` is captured here; changing it afterwards does not reach the mounted view.
|
|
144
|
+
const openItem = (itemId: string) => screens.pushScreen(() => <DetailsScreen itemId={itemId} />);
|
|
145
|
+
|
|
146
|
+
const goBack = () => screens.popScreen();
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Going back is `popScreen()`, and the list returns with its position and focus intact because
|
|
150
|
+
it was never unmounted.
|
|
151
|
+
|
|
152
|
+
## Common Mistakes
|
|
153
|
+
|
|
154
|
+
| Symptom | Cause | Fix |
|
|
155
|
+
|---|---|---|
|
|
156
|
+
| The previous view is readable through the one just opened | The controller renders no background, and the new view does not cover the screen | Give the pushed view an opaque background, or hide the lower one from its `onScreenCovered` |
|
|
157
|
+
| Coming back from a details view resets the list | `setScreen` was used where a drill-down was meant, so the list was destroyed | `pushScreen` to go in, `popScreen` to come back |
|
|
158
|
+
| A view shows a stale value that has since changed | Props on the factory's element are a snapshot at navigation time | Pass an accessor rather than the value |
|
|
159
|
+
| Memory and network keep growing as a viewer drills deeper | Every pushed view stays mounted and keeps working | Release what is expensive from `onScreenCovered` and restore it from `onScreenUncovered` |
|
|
160
|
+
| Reading the controller throws on mount | The component is outside the provider | Mount `ScreenControllerProvider` above it. The throw is deliberate — a silent no-op would be worse |
|
|
161
|
+
| Back leaves the app instead of returning | Nothing was on the stack to pop, so the key reached the platform | Push before expecting to pop, and treat the fallback as the floor |
|
|
162
|
+
| Sibling tabs slow down as the viewer cycles them | Each tab was pushed, so they accumulate | `setScreen` for siblings — only one should be alive |
|
|
163
|
+
|
|
164
|
+
Provider and context types are exported from `@roku-sdk/navigation` and documented with TSDoc
|
|
165
|
+
there — `IScreenControllerContext` and `IScreenControllerProviderProps`. Read a member's exact
|
|
166
|
+
signature there rather than inferring it from here.
|