@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,131 @@
|
|
|
1
|
+
# MCP Setup
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The SDK reaches an agent through an MCP server, and there are two ways to run it: the editor
|
|
6
|
+
spawns it, or it runs as a persistent background process. They are not alternatives to choose
|
|
7
|
+
between once. The spawned entry looks for a running server on every request and forwards to it,
|
|
8
|
+
so the config decides how the client reaches the tools, and whether a server is running decides
|
|
9
|
+
which tools answer. Write the spawned entry, then start a server if the task needs one.
|
|
10
|
+
|
|
11
|
+
## When to Use
|
|
12
|
+
|
|
13
|
+
- an agent session with no Roku tools in it, or an editor that has never been configured
|
|
14
|
+
- a client that will not connect, or connects and lists nothing
|
|
15
|
+
- deciding between the spawned entry and a persistent server, and how to move between them
|
|
16
|
+
- finding the port a running server chose, or fixing it so it can be written down
|
|
17
|
+
- a call refused with a message about requiring server mode
|
|
18
|
+
|
|
19
|
+
### When not to use
|
|
20
|
+
|
|
21
|
+
| Situation | Use instead | Because |
|
|
22
|
+
|---|---|---|
|
|
23
|
+
| Tools answer, and one call fails over targeting, a device, or the app | The `mcp-ai-dev-loop` skill | Setup finished when the client listed tools. A refusal that names server mode is the exception and stays here, because the fix is to start a server |
|
|
24
|
+
| Which call reports whether the server is up, from inside a working session | The `mcp-ai-dev-loop` skill | Reading the session's own state is that skill's loop; starting what is missing is this one |
|
|
25
|
+
| Deploying, screenshotting, driving the app or reading its logs | The `mcp-ai-dev-loop` skill | This skill ends at the connection |
|
|
26
|
+
| Configuring an MCP server that is not the SDK's | That server's own documentation | Nothing here generalises past `rk` |
|
|
27
|
+
| A shell script, a CI step, or anything with no agent in it | The `cli-dev-loop` skill | MCP exists to give an agent the tools; a runner calls `rk` itself, and needs none of this |
|
|
28
|
+
| `rk` itself does not resolve yet | The `sdk-installation` skill | There is nothing to configure until the package is installed |
|
|
29
|
+
|
|
30
|
+
## Quick Reference
|
|
31
|
+
|
|
32
|
+
| Goal | Do |
|
|
33
|
+
|---|---|
|
|
34
|
+
| Let the editor run the server | A config entry invoking `rk mcp` through the project's own runner |
|
|
35
|
+
| Run one persistently | `rk server start --daemon` |
|
|
36
|
+
| Find the port, and confirm it is up | `rk server status` |
|
|
37
|
+
| Fix the port so it can be written down | `--port <n>` on `rk server start` |
|
|
38
|
+
| Stop it | `rk server stop` |
|
|
39
|
+
| Test the install without the editor | Run `rk mcp` in a terminal; it should sit waiting on stdin |
|
|
40
|
+
|
|
41
|
+
## Instructions
|
|
42
|
+
|
|
43
|
+
1. **Write the spawned entry first.** It is the whole of setup for most work, and it is the
|
|
44
|
+
half that survives every later change. What matters is that the entry reaches the *installed*
|
|
45
|
+
binary in `node_modules/.bin`: `npx rk mcp` and `pnpm exec rk mcp` both do that in a project
|
|
46
|
+
that depends on `@roku-sdk/tooling`, so use whichever runner the project already uses. What
|
|
47
|
+
fails is an entry that can miss it — a bare `rk` that is not on `PATH`, or a runner invoked
|
|
48
|
+
outside the project directory, which sends it to the registry for a package it could have
|
|
49
|
+
read from disk.
|
|
50
|
+
|
|
51
|
+
2. **The key and the fields differ by editor, and a wrong key fails silently.** VS Code reads
|
|
52
|
+
`.vscode/mcp.json` under a `servers` key and wants a `type`. Cursor reads `.cursor/mcp.json`
|
|
53
|
+
under `mcpServers` and does not. A client handed the other shape finds no entry, so it
|
|
54
|
+
reports no error and lists no tools — which looks exactly like a broken install.
|
|
55
|
+
|
|
56
|
+
3. **Start a server only when the task needs one.** Most tools run in process. `logs`,
|
|
57
|
+
`get_server_health`, `stop_dev_session`, the three Perfetto tools (`perfetto_enable`,
|
|
58
|
+
`perfetto_start`, `perfetto_stop`) and the CDP and DAP debugger families require a server
|
|
59
|
+
and say so when there is none, naming `rk server` in the failure.
|
|
60
|
+
|
|
61
|
+
4. **Never hardcode the port.** It is derived from a hash of the workspace root path, so it
|
|
62
|
+
differs per checkout and even between two clones of the same repo, landing somewhere in
|
|
63
|
+
3737–4236 (Fetch-blocked ports such as 4045 and 4190 are skipped). `rk server status`
|
|
64
|
+
prints the one in use. If a port has to be written into a config file, make it true by
|
|
65
|
+
starting the server with `--port` rather than by guessing.
|
|
66
|
+
|
|
67
|
+
5. **Moving from spawned to persistent is starting a server, not rewriting the config.**
|
|
68
|
+
Because `rk mcp` forwards to a live server when it finds one, a stdio entry plus a running
|
|
69
|
+
server already has the full tool surface. Point the client at an HTTP URL only when
|
|
70
|
+
something other than that editor needs the same backend.
|
|
71
|
+
|
|
72
|
+
6. **`--daemon` is what makes it outlive the terminal.** Without it the server stops with the
|
|
73
|
+
shell that started it. It also exits after 240 idle minutes, which `--timeout` changes; a
|
|
74
|
+
morning's server being gone after lunch is that, not a crash.
|
|
75
|
+
|
|
76
|
+
7. **When no tools appear, test the install before touching the config again.** Run `rk mcp`
|
|
77
|
+
from the workspace root, through the same runner the config entry uses: it should start and
|
|
78
|
+
sit waiting on stdin. That separates the two failures — if it runs, the config is wrong; if it
|
|
79
|
+
reports that `rk` was not found, the tooling is not installed and no config would have helped.
|
|
80
|
+
Read the message rather than the exit status: a runner reports a missing binary with its own
|
|
81
|
+
code, and only a bare shell returns the 127 people expect.
|
|
82
|
+
|
|
83
|
+
8. **In spawned mode nothing may write to stdout.** That stream carries JSON-RPC and nothing
|
|
84
|
+
else. A wrapper script that echoes a line, or a shell profile that prints a banner, corrupts
|
|
85
|
+
the first message and the client drops the connection — usually reported as the server
|
|
86
|
+
exiting immediately. Invoke the binary directly rather than through a wrapper.
|
|
87
|
+
|
|
88
|
+
## Example
|
|
89
|
+
|
|
90
|
+
A Cursor setup that starts spawned and gains the server-only tools without the config changing.
|
|
91
|
+
|
|
92
|
+
`.cursor/mcp.json` — written once:
|
|
93
|
+
|
|
94
|
+
```json
|
|
95
|
+
{
|
|
96
|
+
"mcpServers": {
|
|
97
|
+
"roku-sdk-tools": {
|
|
98
|
+
"command": "pnpm",
|
|
99
|
+
"args": ["exec", "rk", "mcp"]
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
`"command": "npx"` with `["rk", "mcp"]` is the same entry for a project on npm; the runner has
|
|
106
|
+
to match the project, not this page.
|
|
107
|
+
|
|
108
|
+
Later, when the task needs log collection or a trace:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
npx rk server start --daemon
|
|
112
|
+
npx rk server status
|
|
113
|
+
# rk server running at http://localhost:3941/api (pid 12345, rkRoot: /Users/you/workspace)
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
The config above is unchanged and now reaches that server. The printed port matters only if
|
|
117
|
+
another client has to be pointed at the same backend; for this editor, nothing needs it.
|
|
118
|
+
|
|
119
|
+
## Common Mistakes
|
|
120
|
+
|
|
121
|
+
| Symptom | Cause | Fix |
|
|
122
|
+
|---|---|---|
|
|
123
|
+
| No tools at all, and no error anywhere | The config uses the other editor's key | `servers` for VS Code, `mcpServers` for Cursor |
|
|
124
|
+
| Tools appear, but every connection is slow | The runner is reaching the registry because it cannot see the project's `node_modules` | Give the entry the project directory, so `rk` resolves from disk |
|
|
125
|
+
| The server exits the moment the client starts it | Something wrote to stdout ahead of the protocol | Drop the wrapper script; invoke the binary |
|
|
126
|
+
| Connection refused on the URL that worked yesterday | The port was hardcoded and is derived per workspace | `rk server status`, or pin it with `--port` |
|
|
127
|
+
| Every request answers `403` with `HOST_NOT_ALLOWED` or `ORIGIN_NOT_ALLOWED` | The URL names the server by something other than a loopback name (`host.docker.internal`, the machine's LAN address), or a browser-based client sends an `Origin` from another port | Use `localhost` or `127.0.0.1`, through a port forward if the client runs elsewhere (`ssh -L`, an editor's port forwarding, `docker -p` all work, since only the `Host` name is checked); a browser client has to be served from the server's own port |
|
|
128
|
+
| An HTTP client gets `404` with `MCP_SESSION_UNKNOWN` | Its `Mcp-Session-Id` was not issued on this machine: made up or altered, from another machine, or signed with a session key file (`roku-rce-mcp-session-secret`, beside the RCE token) that was since deleted or replaced. A restart alone does not cause it | Reconnect the client so it sends `initialize` again; `rk mcp` does this on its own |
|
|
129
|
+
| A call refuses, naming server mode, while others work | No server is running | `rk server start --daemon`; the config needs no change |
|
|
130
|
+
| The server is gone after a few hours | The idle timeout, or it was started without `--daemon` | Restart with `--daemon`, and raise `--timeout` if the gap is expected |
|
|
131
|
+
| Running it by hand reports that `rk` was not found | The tooling is not installed in this workspace | Install dependencies, and confirm `node_modules/.bin/rk` exists |
|
|
@@ -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.
|