create-octane-xplat 0.16.1 → 0.17.1
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/README.md +1 -1
- package/compatibility.json +7 -6
- package/dist/scaffold.js +24 -42
- package/library-template/package.json +2 -2
- package/library-template/pnpm-workspace.yaml +68 -77
- package/package.json +1 -1
- package/scaffold.ts +42 -59
- package/template/.agents/skills/xplat/SKILL.md +1 -1
- package/template/.agents/skills/xplat/references/components.md +6 -0
- package/template/.agents/skills/xplat/references/navigation.md +108 -47
- package/template/AGENTS.md +3 -3
- package/template/index.html +1 -1
- package/template/package.json +1 -1
- package/template/pnpm-workspace.yaml +71 -80
- package/template/src/App.tsrx +22 -20
- package/template/src/main.ts +5 -5
package/README.md
CHANGED
|
@@ -99,7 +99,7 @@ your app as you replace the starter screen.
|
|
|
99
99
|
|
|
100
100
|
Platforms added later also receive their test setup. The library template
|
|
101
101
|
includes its component tests and publishing checks.
|
|
102
|
-
See [checking an app](../../docs/
|
|
102
|
+
See [checking an app](../../docs/testing/testing.md#add-automated-tests) for the
|
|
103
103
|
commands, file conventions, and checks that still need a browser or phone.
|
|
104
104
|
|
|
105
105
|
## Keep compatible versions
|
package/compatibility.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$comment": "Release compatibility set, shared by create-octane-xplat and xplat doctor/upgrade. Edit external pins here, then run pnpm sync:compatibility. Framework package names are generated from the lockstep release set. Optional packages are managed only when declared; library consumer peer ranges remain app-owned.",
|
|
3
3
|
"schemaVersion": 1,
|
|
4
|
-
"frameworkVersion": "0.
|
|
4
|
+
"frameworkVersion": "0.17.1",
|
|
5
5
|
"tools": {
|
|
6
6
|
"nodeMinimum": "22.18.0",
|
|
7
7
|
"pnpm": "11.24.0"
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"@octane-xplat/image-crop",
|
|
30
30
|
"@octane-xplat/intersection-observer",
|
|
31
31
|
"@octane-xplat/jsx",
|
|
32
|
+
"@octane-xplat/keyboard",
|
|
32
33
|
"@octane-xplat/lexical",
|
|
33
34
|
"@octane-xplat/lib",
|
|
34
35
|
"@octane-xplat/lingui",
|
|
@@ -65,7 +66,7 @@
|
|
|
65
66
|
],
|
|
66
67
|
"packages": {
|
|
67
68
|
"octane": {
|
|
68
|
-
"version": "0.
|
|
69
|
+
"version": "0.14.0",
|
|
69
70
|
"group": "runtime",
|
|
70
71
|
"singleton": true
|
|
71
72
|
},
|
|
@@ -108,7 +109,7 @@
|
|
|
108
109
|
"group": "bundler"
|
|
109
110
|
},
|
|
110
111
|
"@nativescript/vite": {
|
|
111
|
-
"version": "8.0.
|
|
112
|
+
"version": "8.0.20",
|
|
112
113
|
"group": "bundler"
|
|
113
114
|
},
|
|
114
115
|
"nativescript": {
|
|
@@ -160,15 +161,15 @@
|
|
|
160
161
|
"group": "plugin"
|
|
161
162
|
},
|
|
162
163
|
"@nativescript-community/ui-image": {
|
|
163
|
-
"version": "5.0.
|
|
164
|
+
"version": "5.0.18",
|
|
164
165
|
"group": "plugin"
|
|
165
166
|
},
|
|
166
167
|
"@valor/nativescript-websockets": {
|
|
167
168
|
"version": "2.0.3",
|
|
168
169
|
"group": "plugin"
|
|
169
170
|
},
|
|
170
|
-
"@
|
|
171
|
-
"version": "1.0.0-beta.
|
|
171
|
+
"@octane-xplat/nativescript-masonkit": {
|
|
172
|
+
"version": "1.0.0-beta.126",
|
|
172
173
|
"group": "runtime"
|
|
173
174
|
},
|
|
174
175
|
"jsdom": {
|
package/dist/scaffold.js
CHANGED
|
@@ -170,29 +170,6 @@ function composeManifest(ids) {
|
|
|
170
170
|
}
|
|
171
171
|
var PATCH_ENTRY = /^\s+"?(@?[\w./-]+@[\w.-]+)"?:\s+\S+\s*$/;
|
|
172
172
|
var patchPkg = (key) => key.replace(/@[^@]+$/, "");
|
|
173
|
-
function parsePatchEntries(lines) {
|
|
174
|
-
const entries = [];
|
|
175
|
-
let inPatches = false;
|
|
176
|
-
let pending = [];
|
|
177
|
-
for (const line of lines) {
|
|
178
|
-
if (/^\S/.test(line)) {
|
|
179
|
-
inPatches = line.startsWith("patchedDependencies");
|
|
180
|
-
pending = [];
|
|
181
|
-
continue;
|
|
182
|
-
}
|
|
183
|
-
if (!inPatches) continue;
|
|
184
|
-
const m = line.match(PATCH_ENTRY);
|
|
185
|
-
if (m) {
|
|
186
|
-
entries.push({
|
|
187
|
-
name: patchPkg(m[1]),
|
|
188
|
-
comments: pending,
|
|
189
|
-
line
|
|
190
|
-
});
|
|
191
|
-
pending = [];
|
|
192
|
-
} else pending.push(line);
|
|
193
|
-
}
|
|
194
|
-
return entries;
|
|
195
|
-
}
|
|
196
173
|
/** The pnpm-workspace.yaml a given target set produces. */
|
|
197
174
|
function composeWorkspaceYaml(ids) {
|
|
198
175
|
const resolved = new Set(resolveTargets(ids));
|
|
@@ -237,7 +214,7 @@ function composeWorkspaceYaml(ids) {
|
|
|
237
214
|
return String(doc);
|
|
238
215
|
}
|
|
239
216
|
/**
|
|
240
|
-
* Add the patch entries that `enabledIds` owns to the
|
|
217
|
+
* Add the patch entries that `enabledIds` owns to the owning workspace's
|
|
241
218
|
* pnpm-workspace.yaml. Entries already present (by package name, any
|
|
242
219
|
* version) are kept. Returns true if the file changed.
|
|
243
220
|
*/
|
|
@@ -245,31 +222,36 @@ function ensurePatchEntries(appRoot, enabledIds) {
|
|
|
245
222
|
const needed = /* @__PURE__ */ new Set();
|
|
246
223
|
for (const id of enabledIds) for (const name of targets[id].patches ?? []) needed.add(name);
|
|
247
224
|
if (!needed.size) return false;
|
|
248
|
-
|
|
225
|
+
let workspace = resolve(appRoot);
|
|
226
|
+
while (!existsSync(join(workspace, "pnpm-workspace.yaml")) && dirname(workspace) !== workspace) workspace = dirname(workspace);
|
|
227
|
+
if (!existsSync(join(workspace, "pnpm-workspace.yaml"))) workspace = appRoot;
|
|
228
|
+
const yamlPath = join(workspace, "pnpm-workspace.yaml");
|
|
249
229
|
const appText = existsSync(yamlPath) ? readFileSync(yamlPath, "utf8") : "";
|
|
250
|
-
const present = new Set(parsePatchEntries(appText.split("\n")).map((e) => e.name));
|
|
251
|
-
const missing = [...needed].filter((name) => !present.has(name));
|
|
252
|
-
if (!missing.length) return false;
|
|
253
|
-
const blocks = parsePatchEntries(readFileSync(join(TEMPLATE_DIR, "pnpm-workspace.yaml"), "utf8").split("\n")).filter((e) => missing.includes(e.name)).map((e) => [...e.comments, e.line].join("\n"));
|
|
254
230
|
if (!appText.trim()) {
|
|
255
231
|
writeFileSync(yamlPath, composeWorkspaceYaml(enabledIds));
|
|
256
232
|
return true;
|
|
257
233
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
234
|
+
const doc = parseDocument(appText);
|
|
235
|
+
if (doc.errors.length) throw new Error(`${yamlPath}: ${doc.errors[0].message}`);
|
|
236
|
+
const present = new Set(Object.keys(doc.toJS()?.patchedDependencies ?? {}).map(patchPkg));
|
|
237
|
+
const template = parseDocument(readFileSync(join(TEMPLATE_DIR, "pnpm-workspace.yaml"), "utf8")).toJS();
|
|
238
|
+
let changed = false;
|
|
239
|
+
for (const [key, path] of Object.entries(template.patchedDependencies)) {
|
|
240
|
+
const name = patchPkg(key);
|
|
241
|
+
if (needed.has(name) && !present.has(name)) {
|
|
242
|
+
doc.setIn(["patchedDependencies", key], path);
|
|
243
|
+
changed = true;
|
|
267
244
|
}
|
|
268
|
-
lines.splice(insertAt, 0, ...blocks.join("\n").split("\n"));
|
|
269
|
-
next = lines.join("\n");
|
|
270
245
|
}
|
|
271
|
-
|
|
272
|
-
|
|
246
|
+
for (const [name, spec] of Object.entries(template.configDependencies)) {
|
|
247
|
+
const prefix = `node_modules/.pnpm-config/${name}/`;
|
|
248
|
+
if (Object.values(doc.toJS()?.patchedDependencies ?? {}).some((path) => typeof path === "string" && path.startsWith(prefix)) && !doc.hasIn(["configDependencies", name])) {
|
|
249
|
+
doc.setIn(["configDependencies", name], spec);
|
|
250
|
+
changed = true;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
if (changed) writeFileSync(yamlPath, String(doc));
|
|
254
|
+
return changed;
|
|
273
255
|
}
|
|
274
256
|
/**
|
|
275
257
|
* The tsconfig.json a given target set produces. One program covers every
|
|
@@ -73,8 +73,8 @@
|
|
|
73
73
|
"peerDependencies": {
|
|
74
74
|
"@nativescript-community/octane": ">=0.2.1 <1",
|
|
75
75
|
"@nativescript/core": ">=9.1.0 <10",
|
|
76
|
-
"@octane-xplat/jsx": "^0.
|
|
77
|
-
"@octane-xplat/ui": "^0.
|
|
76
|
+
"@octane-xplat/jsx": "^0.17.1",
|
|
77
|
+
"@octane-xplat/ui": "^0.17.1",
|
|
78
78
|
"octane": ">=0.6.3 <1"
|
|
79
79
|
},
|
|
80
80
|
"peerDependenciesMeta": {
|
|
@@ -9,13 +9,13 @@ catalogs:
|
|
|
9
9
|
"@nativescript-community/octane": 0.2.5
|
|
10
10
|
"@nativescript/core": 9.1.3
|
|
11
11
|
"@nativescript/types": 9.1.4
|
|
12
|
-
"@octane-xplat/cli": 0.
|
|
13
|
-
"@octane-xplat/jsx": 0.
|
|
14
|
-
"@octane-xplat/ui": 0.
|
|
12
|
+
"@octane-xplat/cli": 0.17.1
|
|
13
|
+
"@octane-xplat/jsx": 0.17.1
|
|
14
|
+
"@octane-xplat/ui": 0.17.1
|
|
15
15
|
"@octanejs/vite-plugin": 0.1.61
|
|
16
16
|
"@tsrx/oxc": 0.20.0
|
|
17
17
|
"@tsrx/typescript-plugin": 0.4.11
|
|
18
|
-
octane: 0.
|
|
18
|
+
octane: 0.14.0
|
|
19
19
|
tsrx-typegen: 0.2.2
|
|
20
20
|
typescript: 5.9.3
|
|
21
21
|
vite: 8.3.0
|
|
@@ -25,23 +25,25 @@ overrides:
|
|
|
25
25
|
"@nativescript-community/vite-octane>esbuild": 0.27.7
|
|
26
26
|
vite>esbuild: 0.27.7
|
|
27
27
|
configDependencies:
|
|
28
|
-
"@octane-xplat/patches": 0.
|
|
28
|
+
"@octane-xplat/patches": 0.17.1
|
|
29
29
|
allowUnusedPatches: true
|
|
30
30
|
# Framework patches are supplied by the @octane-xplat/patches config dependency and
|
|
31
31
|
# applied via pnpm patchedDependencies. Managed upstream by @octane-xplat/cli (`xplat
|
|
32
32
|
# patches apply` refreshes, `xplat patches check` verifies); do not edit by hand.
|
|
33
33
|
patchedDependencies:
|
|
34
|
-
# Published 0.
|
|
35
|
-
#
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
#
|
|
40
|
-
#
|
|
41
|
-
#
|
|
42
|
-
#
|
|
43
|
-
#
|
|
44
|
-
|
|
34
|
+
# Published 0.14.0 still requires renderer-owned Children.toArray/map with explicit
|
|
35
|
+
# universal-native re-exports for older runtimes, universal signal-read tracking and local
|
|
36
|
+
# hooks, root event-error forwarding, the complete universal imperative-ref lifecycle, the
|
|
37
|
+
# Descriptor declaring-owner reader fallback, and the signal-owner/enclosingOwner chain so
|
|
38
|
+
# child reads reuse instance declarations. Rebased against the registry 0.14.0 tarball,
|
|
39
|
+
# including local signal-hook registration in the split runtime/hooks modules. Provider
|
|
40
|
+
# null/undefined descendant cleanup is now upstream and no longer patched. Marks generic
|
|
41
|
+
# binding bags from bagOf so fragment commit rehomes their DOM parent references,
|
|
42
|
+
# preventing stale fragment parents, namespace recursion, and invalid insertion during
|
|
43
|
+
# typeahead updates. Preserves keyed component ownership through compiler-generated
|
|
44
|
+
# transparent root slots so native map reorders retain host identity, state, and portal
|
|
45
|
+
# content.
|
|
46
|
+
octane@0.14.0: node_modules/.pnpm-config/@octane-xplat/patches/patches/octane@0.14.0.patch
|
|
45
47
|
# Published 9.1.3 ships the #11446 nested-tab-frame lifecycle fix and iOS explicit-size
|
|
46
48
|
# flex alignment, so those hunks are removed. Retains the unshipped parent-padding
|
|
47
49
|
# measure-spec fix and Android explicit-size flex alignment, iOS continuous corner
|
|
@@ -55,58 +57,53 @@ patchedDependencies:
|
|
|
55
57
|
# _populateRules. Adds atlayer() covering block and statement forms. Applies to a
|
|
56
58
|
# transitive dep — no app declares it directly.
|
|
57
59
|
css@3.0.0: node_modules/.pnpm-config/@octane-xplat/patches/patches/css@3.0.0.patch
|
|
58
|
-
#
|
|
59
|
-
#
|
|
60
|
-
#
|
|
61
|
-
#
|
|
62
|
-
#
|
|
63
|
-
#
|
|
64
|
-
#
|
|
65
|
-
#
|
|
66
|
-
#
|
|
67
|
-
#
|
|
68
|
-
#
|
|
69
|
-
#
|
|
70
|
-
#
|
|
71
|
-
#
|
|
72
|
-
#
|
|
73
|
-
|
|
74
|
-
# Linked dependency fallbacks also discover each linked package's enclosing workspace and
|
|
75
|
-
# reuse the cached, bounded workspace node_modules scan so transitive workspace siblings
|
|
76
|
-
# (auth to secure-storage to a private native plugin) resolve without the consumer
|
|
77
|
-
# supplying that workspace root. Transform candidates now keep root-relative URLs only
|
|
78
|
-
# when they identify the discovered file; nested linked-dependency and pnpm-store matches
|
|
79
|
-
# inside the app root use their actual filesystem paths, preserving query suffixes. The
|
|
80
|
-
# /ns/m import rewriter now resolves `#`-prefixed package `imports` specifiers through the
|
|
81
|
-
# importer's own package.json — raw-read node_modules files (e.g. octane's
|
|
82
|
-
# dist/signals/graph.js importing `#octane/dist/signal-actions/graph`) otherwise reach the
|
|
83
|
-
# device verbatim and the HTTP loader throws `Cannot find module`, deadlocking dev boot
|
|
84
|
-
# before any boot recording can bundle the dep. NativeScript#11551: preserve user virtual
|
|
85
|
-
# stylesheet imports while retaining browser-helper filtering, route virtual CSS through
|
|
86
|
-
# the native inline compiler/registration path before the generic virtual-module shortcut,
|
|
87
|
-
# and relay Vite virtual stylesheet updates as tagged native CSS updates through the same
|
|
88
|
-
# adapted-text endpoint. Empty sheets clear previous rules; CSS compilation failures
|
|
89
|
-
# return HTTP 500 instead of raw CSS.
|
|
90
|
-
"@nativescript/vite@8.0.17": node_modules/.pnpm-config/@octane-xplat/patches/patches/@nativescript__vite@8.0.17.patch
|
|
60
|
+
# Remaining dev-server fixes on @nativescript/vite 8.0.20: collect re-export specifiers in
|
|
61
|
+
# the .vite/deps rewrite; route outside-roots /@fs paths through /ns/m; probe non-active
|
|
62
|
+
# platform and native suffixes; redirect bare octane to octane/universal/native in
|
|
63
|
+
# deps-bundle esbuild passes; add .mobile to tsconfig-paths and generated checker
|
|
64
|
+
# suffixes; scan the app-local pnpm virtual store for served node_modules URLs; ignore
|
|
65
|
+
# per-package turbo.json as workspace markers; resolve # package-imports specifiers in
|
|
66
|
+
# served-module rewrites; preserve, compile, and hot-update virtual stylesheets
|
|
67
|
+
# (NativeScript#11551); and inspect ESM export declarations/specifiers for native
|
|
68
|
+
# default-import validation instead of matching comments, strings, or a narrow export
|
|
69
|
+
# syntax (NativeScript#11566). Shared-source graph and hot-context IDs now use the same
|
|
70
|
+
# app/workspace/@fs paths as served imports, preserving distinct roots
|
|
71
|
+
# (NativeScript#11569); extensionless workspace requests probe filesystem-anchored
|
|
72
|
+
# extension/platform candidates so hot reimports can fetch shared modules
|
|
73
|
+
# (NativeScript#11570). Linked-checkout discovery and candidate anchoring shipped in
|
|
74
|
+
# 8.0.20 (NativeScript#11559), so those local hunks are removed.
|
|
75
|
+
"@nativescript/vite@8.0.20": node_modules/.pnpm-config/@octane-xplat/patches/patches/@nativescript__vite@8.0.20.patch
|
|
91
76
|
# Retains the workspace's NativeScript driver fixes when upgrading to 0.2.5 for portal
|
|
92
77
|
# support: a prop dropped from an update's complete prop bag — or set to
|
|
93
78
|
# `undefined`/`null` — is a removal that restores the documented default
|
|
94
79
|
# (Property/CssProperty setters get `unsetValue`, plain fields the captured pre-prop
|
|
95
80
|
# value, `style` keys diff per key); a never-applied `undefined` still skips the write.
|
|
96
|
-
#
|
|
97
|
-
#
|
|
98
|
-
#
|
|
99
|
-
#
|
|
100
|
-
# the
|
|
101
|
-
#
|
|
102
|
-
#
|
|
103
|
-
#
|
|
104
|
-
#
|
|
81
|
+
# Synced with PR #14: registered setters are identified before resetting (ordinary fields
|
|
82
|
+
# never receive the sentinel), inline removal refreshes matching class CSS through the
|
|
83
|
+
# isolated core 9.1 `_cssState.onChange` hook, CSS variables use public removal, strings
|
|
84
|
+
# use the core CSS parser, and replacement views capture fresh defaults. Also advertises
|
|
85
|
+
# the implemented visibility capability, carries Octane callback ref types, and preserves
|
|
86
|
+
# authored Grid row/col/span metadata. Hosts custom-layout containers (e.g. MasonKit
|
|
87
|
+
# ViewBase) that own child lists via insertChild/removeChild/getChildrenCount without
|
|
88
|
+
# subclassing LayoutBase. Additionally, the masonry cutover: masonview resolves to
|
|
89
|
+
# MasonKit View and scrollview to MasonKit Scroll as the only layout containers, with zero
|
|
90
|
+
# layout props — every former prop name warns-and-drops and all CSS semantics arrive
|
|
91
|
+
# through the style bag; foreign native leaves mirror resolved class and inline layout
|
|
92
|
+
# styles through Style.fromView, observe longhand changes and native recreation, and
|
|
93
|
+
# explicitly clear removed placement — removal restores the CSS cascade, so a later
|
|
94
|
+
# sibling attachment cannot resurrect removed tracks. Container alignment uses the core
|
|
95
|
+
# stretch default without a local style override, allowing class-authored centering.
|
|
96
|
+
# Inline style resets reapply the matching CSS cascade. Re-exports Octane's ClassValue
|
|
97
|
+
# unchanged and stringifies truthy numeric class values while omitting 0 and NaN, matching
|
|
98
|
+
# Octane 0.11.
|
|
105
99
|
"@nativescript-community/octane@0.2.5": node_modules/.pnpm-config/@octane-xplat/patches/patches/@nativescript-community__octane@0.2.5.patch
|
|
106
100
|
# octaneServerStrategy's SCRIPT_FILE_RE only covers [jt]sx? — .tsrx edits never emit the
|
|
107
101
|
# deferred ns:hmr-delta broadcast, so component HMR silently never reaches the device.
|
|
108
102
|
# Also counts open /ns-hmr sockets so the update summary stops reporting recipients=0 for
|
|
109
|
-
# delivered updates.
|
|
103
|
+
# delivered updates. Includes shared script sources in transaction ordering while
|
|
104
|
+
# excluding dependencies, tests and declarations. External-source edits use the absolute
|
|
105
|
+
# source for graph identity and a Vite /@fs transform URL instead of treating
|
|
106
|
+
# parent-relative paths as app URLs.
|
|
110
107
|
"@nativescript-community/vite-octane@0.2.1": node_modules/.pnpm-config/@octane-xplat/patches/patches/@nativescript-community__vite-octane@0.2.1.patch
|
|
111
108
|
# Enables `resolveHiddenExtensions` on the language plugin's typescript facet so
|
|
112
109
|
# extensionless specifiers (e.g. './Text') probe `.tsrx` after moduleSuffixes — required
|
|
@@ -130,27 +127,21 @@ patchedDependencies:
|
|
|
130
127
|
# boot). Same fix as linkifyjs: try/catch new RegExp with an ASCII-range fallback — the
|
|
131
128
|
# matcher only feeds autolink, which native doesn't use.
|
|
132
129
|
"@lexical/link@0.51.0": node_modules/.pnpm-config/@octane-xplat/patches/patches/@lexical__link@0.51.0.patch
|
|
133
|
-
# ui-image 5.x regressed two iOS prefetch behaviors the 4.x line shipped (still present in
|
|
134
|
-
# 4.6.20): prefetchToCacheType ignores its cacheType argument — SDWebImagePrefetcher's
|
|
135
|
-
# default context stores to disk+memory either way, so a 'disk' prefetch also decodes and
|
|
136
|
-
# warms memory, diverging from Android's bytes-only Glide downloadOnly — and
|
|
137
|
-
# getContextFromOptions drops PrefetchOptions.headers because it never sets
|
|
138
|
-
# SDWebImageContextDownloadRequestModifier, so prefetch({headers}) silently sends no
|
|
139
|
-
# headers on iOS while Android's CustomGlideUrl forwards them. Ports upstream's own 4.x
|
|
140
|
-
# implementation — SDWebImageContextStoreCacheType on the prefetch context plus the
|
|
141
|
-
# request-modifier branch in getContextFromOptions — and additionally sets
|
|
142
|
-
# SDWebImageContextOriginalStoreCacheType so a memory prefetch does not spill original
|
|
143
|
-
# bytes to disk.
|
|
144
|
-
"@nativescript-community/ui-image@5.0.17": node_modules/.pnpm-config/@octane-xplat/patches/patches/@nativescript-community__ui-image@5.0.17.patch
|
|
145
130
|
# @nativescript/doctor's Android runtime check reads the app's raw devDependencies
|
|
146
131
|
# specifier and npm dist-tag resolves anything that isn't a semver range — a pnpm
|
|
147
132
|
# `catalog:` pin throws 'The determined Android runtime version ... is not valid' and
|
|
148
133
|
# kills `ns build android` before toolchain checks. Resolve catalog specifiers through the
|
|
149
134
|
# installed package's manifest, reusing the existing tgz-branch mechanism.
|
|
150
135
|
"@nativescript/doctor@2.0.18": node_modules/.pnpm-config/@octane-xplat/patches/patches/@nativescript__doctor@2.0.18.patch
|
|
151
|
-
#
|
|
152
|
-
#
|
|
153
|
-
#
|
|
154
|
-
#
|
|
155
|
-
#
|
|
156
|
-
|
|
136
|
+
# shaders@4.0.0 inlines TypeGPU 0.12.3 into dist/core/std-ClftfWpF.js, which declares
|
|
137
|
+
# /\p{XID_Start}/ and /\p{XID_Continue}/ regex literals at module scope (WGSL identifier
|
|
138
|
+
# validation, external prop-chain matching, minify separator check). NativeScript's
|
|
139
|
+
# embedded V8 on iOS (@nativescript/ios 9.1.0) and Android (@nativescript/android 9.1.1,
|
|
140
|
+
# V8 14.9.207.39) is built without ICU — every \p{…} escape is a SyntaxError, even in a
|
|
141
|
+
# never-called function, so a literal fails the whole module at parse time and any bundled
|
|
142
|
+
# app importing `shaders/js` dies on boot. Rewriting to \p{ID_Start}/\p{L} would not help
|
|
143
|
+
# — all property escapes are rejected. The patch builds the regexes via try/catch new
|
|
144
|
+
# RegExp with ASCII/non-ASCII-range fallbacks — byte-identical .source where ICU exists.
|
|
145
|
+
# dist/js/bundle.js, react/bundle.js, and cli.js carry the same escapes but are
|
|
146
|
+
# unreachable from `shaders/js` and left unpatched.
|
|
147
|
+
shaders@4.0.0: node_modules/.pnpm-config/@octane-xplat/patches/patches/shaders@4.0.0.patch
|
package/package.json
CHANGED
package/scaffold.ts
CHANGED
|
@@ -252,35 +252,6 @@ const PATCH_ENTRY = /^\s+"?(@?[\w./-]+@[\w.-]+)"?:\s+\S+\s*$/
|
|
|
252
252
|
|
|
253
253
|
const patchPkg = (key) => key.replace(/@[^@]+$/, '')
|
|
254
254
|
|
|
255
|
-
// Split a pnpm-workspace.yaml into per-package patch blocks (leading comment
|
|
256
|
-
// lines included) so compose can drop entries and apply can transplant them.
|
|
257
|
-
function parsePatchEntries(lines) {
|
|
258
|
-
const entries: any[] = []
|
|
259
|
-
let inPatches = false
|
|
260
|
-
let pending: string[] = []
|
|
261
|
-
for (const line of lines) {
|
|
262
|
-
if (/^\S/.test(line)) {
|
|
263
|
-
inPatches = line.startsWith('patchedDependencies')
|
|
264
|
-
pending = []
|
|
265
|
-
continue
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
if (!inPatches) {
|
|
269
|
-
continue
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
const m = line.match(PATCH_ENTRY)
|
|
273
|
-
if (m) {
|
|
274
|
-
entries.push({ name: patchPkg(m[1]), comments: pending, line })
|
|
275
|
-
pending = []
|
|
276
|
-
} else {
|
|
277
|
-
pending.push(line)
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
return entries
|
|
282
|
-
}
|
|
283
|
-
|
|
284
255
|
/** The pnpm-workspace.yaml a given target set produces. */
|
|
285
256
|
export function composeWorkspaceYaml(ids) {
|
|
286
257
|
const resolved = new Set(resolveTargets(ids))
|
|
@@ -341,7 +312,7 @@ export function composeWorkspaceYaml(ids) {
|
|
|
341
312
|
}
|
|
342
313
|
|
|
343
314
|
/**
|
|
344
|
-
* Add the patch entries that `enabledIds` owns to the
|
|
315
|
+
* Add the patch entries that `enabledIds` owns to the owning workspace's
|
|
345
316
|
* pnpm-workspace.yaml. Entries already present (by package name, any
|
|
346
317
|
* version) are kept. Returns true if the file changed.
|
|
347
318
|
*/
|
|
@@ -357,45 +328,57 @@ function ensurePatchEntries(appRoot, enabledIds) {
|
|
|
357
328
|
return false
|
|
358
329
|
}
|
|
359
330
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
return false
|
|
331
|
+
// Match catalog ownership: adding a target must not split an existing
|
|
332
|
+
// monorepo into a new, nested workspace.
|
|
333
|
+
let workspace = resolve(appRoot)
|
|
334
|
+
while (!existsSync(join(workspace, 'pnpm-workspace.yaml')) && dirname(workspace) !== workspace) {
|
|
335
|
+
workspace = dirname(workspace)
|
|
366
336
|
}
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
337
|
+
if (!existsSync(join(workspace, 'pnpm-workspace.yaml'))) {
|
|
338
|
+
workspace = appRoot
|
|
339
|
+
}
|
|
340
|
+
const yamlPath = join(workspace, 'pnpm-workspace.yaml')
|
|
341
|
+
const appText = existsSync(yamlPath) ? readFileSync(yamlPath, 'utf8') : ''
|
|
373
342
|
if (!appText.trim()) {
|
|
374
343
|
writeFileSync(yamlPath, composeWorkspaceYaml(enabledIds))
|
|
375
344
|
return true
|
|
376
345
|
}
|
|
377
346
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
347
|
+
const doc = parseDocument(appText)
|
|
348
|
+
if (doc.errors.length) {
|
|
349
|
+
throw new Error(`${yamlPath}: ${doc.errors[0].message}`)
|
|
350
|
+
}
|
|
351
|
+
const present = new Set(Object.keys(doc.toJS()?.patchedDependencies ?? {}).map(patchPkg))
|
|
352
|
+
const template = parseDocument(
|
|
353
|
+
readFileSync(join(TEMPLATE_DIR, 'pnpm-workspace.yaml'), 'utf8'),
|
|
354
|
+
).toJS()
|
|
355
|
+
let changed = false
|
|
356
|
+
for (const [key, path] of Object.entries(template.patchedDependencies)) {
|
|
357
|
+
const name = patchPkg(key)
|
|
358
|
+
if (needed.has(name) && !present.has(name)) {
|
|
359
|
+
doc.setIn(['patchedDependencies', key], path)
|
|
360
|
+
changed = true
|
|
391
361
|
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Template paths are relative to the owning workspace. pnpm installs the
|
|
365
|
+
// config package there before resolving its patch files on the first install.
|
|
366
|
+
for (const [name, spec] of Object.entries(template.configDependencies)) {
|
|
367
|
+
const prefix = `node_modules/.pnpm-config/${name}/`
|
|
368
|
+
const usesPackage = Object.values(doc.toJS()?.patchedDependencies ?? {}).some(
|
|
369
|
+
(path) => typeof path === 'string' && path.startsWith(prefix),
|
|
370
|
+
)
|
|
392
371
|
|
|
393
|
-
|
|
394
|
-
|
|
372
|
+
if (usesPackage && !doc.hasIn(['configDependencies', name])) {
|
|
373
|
+
doc.setIn(['configDependencies', name], spec)
|
|
374
|
+
changed = true
|
|
375
|
+
}
|
|
395
376
|
}
|
|
396
377
|
|
|
397
|
-
|
|
398
|
-
|
|
378
|
+
if (changed) {
|
|
379
|
+
writeFileSync(yamlPath, String(doc))
|
|
380
|
+
}
|
|
381
|
+
return changed
|
|
399
382
|
}
|
|
400
383
|
|
|
401
384
|
/**
|
|
@@ -151,7 +151,7 @@ export function BrowserGreeting() { return <p>Hello</p> }
|
|
|
151
151
|
- `references/components.md` — the component vocabulary plus per-platform
|
|
152
152
|
divergences and name traps.
|
|
153
153
|
- `references/styling.md` — tokens, the portable CSS subset, dark mode.
|
|
154
|
-
- `references/navigation.md` —
|
|
154
|
+
- `references/navigation.md` — file routes, links, params, stacks, modals.
|
|
155
155
|
- `references/platform.md` — device services and writing your own leaf.
|
|
156
156
|
- Current limits and platform boundaries:
|
|
157
157
|
https://octane-xplat.goddardai.org/known-limits
|
|
@@ -31,6 +31,12 @@ export function Welcome() {
|
|
|
31
31
|
| Shells | `Screen`, `Tabs` |
|
|
32
32
|
| Theme and measurement | `useColorScheme`, `useSafeAreaInsets`, `useMeasure` |
|
|
33
33
|
|
|
34
|
+
Native pages start full-screen: app content extends under the status bar
|
|
35
|
+
and home indicator, and `SafeArea` is the only source of safe-area padding —
|
|
36
|
+
wrap screen content that must stay clear of the system bars. A custom
|
|
37
|
+
NativeScript page needs `prepareFullScreenPage(page)` from
|
|
38
|
+
`@octane-xplat/ui/nativescript` before its content renders.
|
|
39
|
+
|
|
34
40
|
For images, prefer the `Image` from `@octane-xplat/image` — it has the exact
|
|
35
41
|
same props as `@octane-xplat/ui`'s `Image` but loads through a real image
|
|
36
42
|
engine on native (Glide on Android, SDWebImage on iOS) with a sized memory +
|
|
@@ -1,87 +1,148 @@
|
|
|
1
1
|
# Navigation
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
Routes are files under `src/app`, not registered lists. A `[param]` segment
|
|
4
|
+
becomes a typed `:param`. Write screens in `.tsrx` files and type callbacks
|
|
5
|
+
with the generated `routes.gen.contracts` module:
|
|
6
6
|
|
|
7
7
|
```tsrx
|
|
8
|
+
// src/app/items/[id].tsrx
|
|
8
9
|
import { Text } from '@octane-xplat/ui'
|
|
9
|
-
import {
|
|
10
|
+
import type { RouteProps, RouteLoaderArgs } from '../../routes.gen.contracts'
|
|
10
11
|
|
|
11
|
-
function
|
|
12
|
-
|
|
12
|
+
export function loader({ params }: RouteLoaderArgs<'/items/:id'>) {
|
|
13
|
+
return { title: 'Item ' + params.id }
|
|
14
|
+
}
|
|
13
15
|
|
|
14
|
-
|
|
15
|
-
{
|
|
16
|
-
|
|
17
|
-
]))
|
|
16
|
+
export function screen(props: RouteProps<'/items/:id'>) {
|
|
17
|
+
return <Text>{props.data.title}</Text>
|
|
18
|
+
}
|
|
18
19
|
```
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
Regenerate the catalog after adding or renaming route files (the dev and
|
|
22
|
+
build commands also run it):
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
pnpm exec xplat routes --dir src/app
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Import the generated client once in your entry module and place `RouteOutlet`
|
|
29
|
+
around the content that forms the home screen — the starter's `App` keeps its
|
|
30
|
+
own content and wraps it:
|
|
22
31
|
|
|
23
32
|
```tsrx
|
|
24
|
-
|
|
25
|
-
import
|
|
33
|
+
// src/App.tsrx
|
|
34
|
+
import './routes.gen'
|
|
35
|
+
import { NavLink, RouteOutlet, Text } from '@octane-xplat/ui'
|
|
36
|
+
|
|
37
|
+
export function App() {
|
|
38
|
+
return <RouteOutlet>
|
|
39
|
+
<Text>My collection</Text>
|
|
40
|
+
<NavLink to="/items/:id" params={{ id: 'coat' }}><Text>Open coat</Text></NavLink>
|
|
41
|
+
</RouteOutlet>
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Use `NavLink` for a screen link or `useRouter()` in an event handler.
|
|
46
|
+
Destinations are objects: a declared `to` pattern plus `params`. `Link`
|
|
47
|
+
accepts a URL, including an external website.
|
|
48
|
+
|
|
49
|
+
```tsrx
|
|
50
|
+
// src/SettingsLinks.tsrx
|
|
51
|
+
import { HStack, Link, Pressable, Text } from '@octane-xplat/ui'
|
|
52
|
+
import { useRouter } from '@octane-xplat/ui/routing'
|
|
26
53
|
|
|
27
54
|
export function SettingsLinks() {
|
|
28
|
-
const
|
|
55
|
+
const navigation = useRouter()
|
|
29
56
|
return <HStack>
|
|
30
|
-
<
|
|
31
|
-
|
|
57
|
+
<Pressable onPress={() => navigation.push({ to: '/items/:id', params: { id: '42' } })}>
|
|
58
|
+
<Text>Open item</Text>
|
|
59
|
+
</Pressable>
|
|
32
60
|
<Link href="https://octane-xplat.goddardai.org"><Text>Documentation</Text></Link>
|
|
33
61
|
</HStack>
|
|
34
62
|
}
|
|
35
63
|
```
|
|
36
64
|
|
|
37
|
-
|
|
38
|
-
|
|
65
|
+
`router.push` returns a result, not void: check `status === 'committed'`,
|
|
66
|
+
then `await result.finished` only when animation settlement matters.
|
|
67
|
+
`router.back()` pops one entry, `router.dismiss()` closes a modal,
|
|
68
|
+
`useCanGoBack` gates a Back control, and `useRouterState()` reads the current
|
|
69
|
+
route's declared `to` pattern. `router.navigateUrl(url)` admits an incoming
|
|
70
|
+
absolute or relative URL.
|
|
39
71
|
|
|
40
72
|
```tsrx
|
|
73
|
+
// src/NavigationStatus.tsrx
|
|
41
74
|
import { Pressable, Text } from '@octane-xplat/ui'
|
|
42
|
-
import {
|
|
75
|
+
import { useCanGoBack, useRouter, useRouterState } from '@octane-xplat/ui/routing'
|
|
43
76
|
|
|
44
77
|
export function NavigationStatus() {
|
|
45
|
-
const
|
|
46
|
-
const
|
|
78
|
+
const navigation = useRouter()
|
|
79
|
+
const route = useRouterState().route
|
|
80
|
+
const canGoBack = useCanGoBack()
|
|
47
81
|
return <>
|
|
48
|
-
<Text>{route?.
|
|
49
|
-
<Pressable disabled={!canGoBack} onPress={() =>
|
|
82
|
+
<Text>{route?.to ?? 'Home'}</Text>
|
|
83
|
+
<Pressable disabled={!canGoBack} onPress={() => navigation.back()}>
|
|
50
84
|
<Text>Back</Text>
|
|
51
85
|
</Pressable>
|
|
52
86
|
</>
|
|
53
87
|
}
|
|
54
88
|
```
|
|
55
89
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
registered `/projects/:id` screen, navigate using the project ID:
|
|
90
|
+
A guard runs in `beforeLoad` before the attempted route commits: return context
|
|
91
|
+
values, `redirect({ to, params, search })`, or throw `RouteForbidden`. Type the
|
|
92
|
+
args with the route's generated `RouteGuardArgs`:
|
|
60
93
|
|
|
61
|
-
```
|
|
62
|
-
|
|
94
|
+
```tsrx
|
|
95
|
+
// src/app/items/[id].tsrx
|
|
96
|
+
import { redirect } from '@octane-xplat/ui/routing'
|
|
97
|
+
import type { RouteGuardArgs } from '../../routes.gen.contracts'
|
|
63
98
|
|
|
64
|
-
|
|
99
|
+
export function beforeLoad({ params }: RouteGuardArgs<'/items/:id'>) {
|
|
100
|
+
if (params.id === 'old-coat') redirect({ to: '/items/:id', params: { id: 'coat' } })
|
|
101
|
+
return { checked: true }
|
|
102
|
+
}
|
|
65
103
|
```
|
|
66
104
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
105
|
+
Prefer string IDs in params: generated contracts type params as strings and
|
|
106
|
+
IDs make URLs shareable. Search is validated per route with `validateSearch`
|
|
107
|
+
(return `as const`); screens update it with `useUpdateSearch()` inside a
|
|
108
|
+
component via the route's `getRouteApi` accessor.
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
// src/app/items/[id].tsrx — search schema for the same route file
|
|
112
|
+
export function validateSearch(raw: { tab?: unknown }) {
|
|
113
|
+
const tab = raw.tab ?? 'details'
|
|
114
|
+
if (tab !== 'details' && tab !== 'activity') {
|
|
115
|
+
throw new Error('Invalid tab')
|
|
116
|
+
}
|
|
117
|
+
return { tab } as const
|
|
118
|
+
}
|
|
119
|
+
```
|
|
70
120
|
|
|
71
121
|
```tsrx
|
|
72
|
-
|
|
73
|
-
import {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
122
|
+
// src/ActivityFilter.tsrx
|
|
123
|
+
import { Button } from '@octane-xplat/ui'
|
|
124
|
+
import { getRouteApi } from '@octane-xplat/ui/routing'
|
|
125
|
+
|
|
126
|
+
const item = getRouteApi('/items/:id')
|
|
127
|
+
|
|
128
|
+
export function ActivityFilter() {
|
|
129
|
+
const updateSearch = item.useUpdateSearch()
|
|
130
|
+
return <Button label="Show activity" clickAction={() => {
|
|
131
|
+
void updateSearch(previous => ({ ...previous, tab: 'activity' }))
|
|
132
|
+
}} />
|
|
83
133
|
}
|
|
84
134
|
```
|
|
85
135
|
|
|
86
|
-
|
|
87
|
-
|
|
136
|
+
Named stacks keep each tab's history separate: declare `"stacks": ["catalog",
|
|
137
|
+
"account"]` in `xplat.routes.json`, render `Tabs`, and address a stack with
|
|
138
|
+
`stack:` in the destination or `router.forStack(name)`:
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
await router.forStack('catalog').push({ to: '/items/:id', params: { id: 'coat' } })
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
`presentation: 'modal'` on a destination (or a `+modal` route-file suffix)
|
|
145
|
+
opens an owned modal flow the host dismisses as one surface.
|
|
146
|
+
|
|
147
|
+
For deep links, transitions, file-route details, and generated contracts, see
|
|
148
|
+
the [navigation guide](https://octane-xplat.goddardai.org/navigation).
|
package/template/AGENTS.md
CHANGED
|
@@ -53,7 +53,7 @@ pnpm test:android -- --device DEVICE_SERIAL
|
|
|
53
53
|
Only commands for enabled platforms are added. Web tests use jsdom; phone
|
|
54
54
|
tests need a selected simulator or device. Report targets actually run and
|
|
55
55
|
distinguish document checks from browser layout or OS input checks. See the
|
|
56
|
-
[testing guide](https://octane-xplat.goddardai.org/
|
|
56
|
+
[testing guide](https://octane-xplat.goddardai.org/testing#add-automated-tests).
|
|
57
57
|
|
|
58
58
|
## Stable automation targets
|
|
59
59
|
|
|
@@ -73,11 +73,11 @@ than array positions. Preserve `id` values, useful accessibility labels and
|
|
|
73
73
|
roles. Do not put IDs on every decorative child or expose personal data.
|
|
74
74
|
Inputs target their editable host; overlays target mounted content. Verify
|
|
75
75
|
forwarding when composing another component; inherited types alone do not
|
|
76
|
-
promise platform support. See [qualified hosts and mappings](https://octane-xplat.goddardai.org/
|
|
76
|
+
promise platform support. See [qualified hosts and mappings](https://octane-xplat.goddardai.org/test-identifiers).
|
|
77
77
|
|
|
78
78
|
Argent is optional AI-agent device/browser control through MCP, with no app
|
|
79
79
|
runtime dependency. Use [official setup](https://docs.swmansion.com/argent/docs/fundamentals/installation/)
|
|
80
|
-
and the [qualified local recipe](https://octane-xplat.goddardai.org/
|
|
80
|
+
and the [qualified local recipe](https://octane-xplat.goddardai.org/argent)
|
|
81
81
|
(version 0.27.0). Prefer stable IDs for reliable targeting while keeping
|
|
82
82
|
spoken accessibility semantics. Discovery and replay have different native
|
|
83
83
|
prerequisites. Chromium prefers DOM `id` over `data-testid` when both exist.
|
package/template/index.html
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<html lang="en">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="utf-8" />
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
|
6
6
|
<title>octane-xplat app</title>
|
|
7
7
|
</head>
|
|
8
8
|
<body>
|
package/template/package.json
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"@octane-xplat/jsx": "catalog:xplat",
|
|
27
27
|
"@octane-xplat/platform": "catalog:xplat",
|
|
28
28
|
"@octane-xplat/ui": "catalog:xplat",
|
|
29
|
-
"@
|
|
29
|
+
"@octane-xplat/nativescript-masonkit": "catalog:xplat",
|
|
30
30
|
"@valor/nativescript-websockets": "catalog:xplat",
|
|
31
31
|
"octane": "catalog:xplat"
|
|
32
32
|
},
|
|
@@ -21,17 +21,19 @@ allowUnusedPatches: true
|
|
|
21
21
|
# applied via pnpm patchedDependencies. Managed upstream by @octane-xplat/cli (`xplat
|
|
22
22
|
# patches apply` refreshes, `xplat patches check` verifies); do not edit by hand.
|
|
23
23
|
patchedDependencies:
|
|
24
|
-
# Published 0.
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
#
|
|
28
|
-
#
|
|
29
|
-
#
|
|
30
|
-
#
|
|
31
|
-
#
|
|
32
|
-
#
|
|
33
|
-
#
|
|
34
|
-
|
|
24
|
+
# Published 0.14.0 still requires renderer-owned Children.toArray/map with explicit
|
|
25
|
+
# universal-native re-exports for older runtimes, universal signal-read tracking and local
|
|
26
|
+
# hooks, root event-error forwarding, the complete universal imperative-ref lifecycle, the
|
|
27
|
+
# Descriptor declaring-owner reader fallback, and the signal-owner/enclosingOwner chain so
|
|
28
|
+
# child reads reuse instance declarations. Rebased against the registry 0.14.0 tarball,
|
|
29
|
+
# including local signal-hook registration in the split runtime/hooks modules. Provider
|
|
30
|
+
# null/undefined descendant cleanup is now upstream and no longer patched. Marks generic
|
|
31
|
+
# binding bags from bagOf so fragment commit rehomes their DOM parent references,
|
|
32
|
+
# preventing stale fragment parents, namespace recursion, and invalid insertion during
|
|
33
|
+
# typeahead updates. Preserves keyed component ownership through compiler-generated
|
|
34
|
+
# transparent root slots so native map reorders retain host identity, state, and portal
|
|
35
|
+
# content.
|
|
36
|
+
octane@0.14.0: node_modules/.pnpm-config/@octane-xplat/patches/patches/octane@0.14.0.patch
|
|
35
37
|
# Published 9.1.3 ships the #11446 nested-tab-frame lifecycle fix and iOS explicit-size
|
|
36
38
|
# flex alignment, so those hunks are removed. Retains the unshipped parent-padding
|
|
37
39
|
# measure-spec fix and Android explicit-size flex alignment, iOS continuous corner
|
|
@@ -45,58 +47,53 @@ patchedDependencies:
|
|
|
45
47
|
# _populateRules. Adds atlayer() covering block and statement forms. Applies to a
|
|
46
48
|
# transitive dep — no app declares it directly.
|
|
47
49
|
css@3.0.0: node_modules/.pnpm-config/@octane-xplat/patches/patches/css@3.0.0.patch
|
|
48
|
-
#
|
|
49
|
-
#
|
|
50
|
-
#
|
|
51
|
-
#
|
|
52
|
-
#
|
|
53
|
-
#
|
|
54
|
-
#
|
|
55
|
-
#
|
|
56
|
-
#
|
|
57
|
-
#
|
|
58
|
-
#
|
|
59
|
-
#
|
|
60
|
-
#
|
|
61
|
-
#
|
|
62
|
-
#
|
|
63
|
-
|
|
64
|
-
# Linked dependency fallbacks also discover each linked package's enclosing workspace and
|
|
65
|
-
# reuse the cached, bounded workspace node_modules scan so transitive workspace siblings
|
|
66
|
-
# (auth to secure-storage to a private native plugin) resolve without the consumer
|
|
67
|
-
# supplying that workspace root. Transform candidates now keep root-relative URLs only
|
|
68
|
-
# when they identify the discovered file; nested linked-dependency and pnpm-store matches
|
|
69
|
-
# inside the app root use their actual filesystem paths, preserving query suffixes. The
|
|
70
|
-
# /ns/m import rewriter now resolves `#`-prefixed package `imports` specifiers through the
|
|
71
|
-
# importer's own package.json — raw-read node_modules files (e.g. octane's
|
|
72
|
-
# dist/signals/graph.js importing `#octane/dist/signal-actions/graph`) otherwise reach the
|
|
73
|
-
# device verbatim and the HTTP loader throws `Cannot find module`, deadlocking dev boot
|
|
74
|
-
# before any boot recording can bundle the dep. NativeScript#11551: preserve user virtual
|
|
75
|
-
# stylesheet imports while retaining browser-helper filtering, route virtual CSS through
|
|
76
|
-
# the native inline compiler/registration path before the generic virtual-module shortcut,
|
|
77
|
-
# and relay Vite virtual stylesheet updates as tagged native CSS updates through the same
|
|
78
|
-
# adapted-text endpoint. Empty sheets clear previous rules; CSS compilation failures
|
|
79
|
-
# return HTTP 500 instead of raw CSS.
|
|
80
|
-
"@nativescript/vite@8.0.17": node_modules/.pnpm-config/@octane-xplat/patches/patches/@nativescript__vite@8.0.17.patch
|
|
50
|
+
# Remaining dev-server fixes on @nativescript/vite 8.0.20: collect re-export specifiers in
|
|
51
|
+
# the .vite/deps rewrite; route outside-roots /@fs paths through /ns/m; probe non-active
|
|
52
|
+
# platform and native suffixes; redirect bare octane to octane/universal/native in
|
|
53
|
+
# deps-bundle esbuild passes; add .mobile to tsconfig-paths and generated checker
|
|
54
|
+
# suffixes; scan the app-local pnpm virtual store for served node_modules URLs; ignore
|
|
55
|
+
# per-package turbo.json as workspace markers; resolve # package-imports specifiers in
|
|
56
|
+
# served-module rewrites; preserve, compile, and hot-update virtual stylesheets
|
|
57
|
+
# (NativeScript#11551); and inspect ESM export declarations/specifiers for native
|
|
58
|
+
# default-import validation instead of matching comments, strings, or a narrow export
|
|
59
|
+
# syntax (NativeScript#11566). Shared-source graph and hot-context IDs now use the same
|
|
60
|
+
# app/workspace/@fs paths as served imports, preserving distinct roots
|
|
61
|
+
# (NativeScript#11569); extensionless workspace requests probe filesystem-anchored
|
|
62
|
+
# extension/platform candidates so hot reimports can fetch shared modules
|
|
63
|
+
# (NativeScript#11570). Linked-checkout discovery and candidate anchoring shipped in
|
|
64
|
+
# 8.0.20 (NativeScript#11559), so those local hunks are removed.
|
|
65
|
+
"@nativescript/vite@8.0.20": node_modules/.pnpm-config/@octane-xplat/patches/patches/@nativescript__vite@8.0.20.patch
|
|
81
66
|
# Retains the workspace's NativeScript driver fixes when upgrading to 0.2.5 for portal
|
|
82
67
|
# support: a prop dropped from an update's complete prop bag — or set to
|
|
83
68
|
# `undefined`/`null` — is a removal that restores the documented default
|
|
84
69
|
# (Property/CssProperty setters get `unsetValue`, plain fields the captured pre-prop
|
|
85
70
|
# value, `style` keys diff per key); a never-applied `undefined` still skips the write.
|
|
86
|
-
#
|
|
87
|
-
#
|
|
88
|
-
#
|
|
89
|
-
#
|
|
90
|
-
# the
|
|
91
|
-
#
|
|
92
|
-
#
|
|
93
|
-
#
|
|
94
|
-
#
|
|
71
|
+
# Synced with PR #14: registered setters are identified before resetting (ordinary fields
|
|
72
|
+
# never receive the sentinel), inline removal refreshes matching class CSS through the
|
|
73
|
+
# isolated core 9.1 `_cssState.onChange` hook, CSS variables use public removal, strings
|
|
74
|
+
# use the core CSS parser, and replacement views capture fresh defaults. Also advertises
|
|
75
|
+
# the implemented visibility capability, carries Octane callback ref types, and preserves
|
|
76
|
+
# authored Grid row/col/span metadata. Hosts custom-layout containers (e.g. MasonKit
|
|
77
|
+
# ViewBase) that own child lists via insertChild/removeChild/getChildrenCount without
|
|
78
|
+
# subclassing LayoutBase. Additionally, the masonry cutover: masonview resolves to
|
|
79
|
+
# MasonKit View and scrollview to MasonKit Scroll as the only layout containers, with zero
|
|
80
|
+
# layout props — every former prop name warns-and-drops and all CSS semantics arrive
|
|
81
|
+
# through the style bag; foreign native leaves mirror resolved class and inline layout
|
|
82
|
+
# styles through Style.fromView, observe longhand changes and native recreation, and
|
|
83
|
+
# explicitly clear removed placement — removal restores the CSS cascade, so a later
|
|
84
|
+
# sibling attachment cannot resurrect removed tracks. Container alignment uses the core
|
|
85
|
+
# stretch default without a local style override, allowing class-authored centering.
|
|
86
|
+
# Inline style resets reapply the matching CSS cascade. Re-exports Octane's ClassValue
|
|
87
|
+
# unchanged and stringifies truthy numeric class values while omitting 0 and NaN, matching
|
|
88
|
+
# Octane 0.11.
|
|
95
89
|
"@nativescript-community/octane@0.2.5": node_modules/.pnpm-config/@octane-xplat/patches/patches/@nativescript-community__octane@0.2.5.patch
|
|
96
90
|
# octaneServerStrategy's SCRIPT_FILE_RE only covers [jt]sx? — .tsrx edits never emit the
|
|
97
91
|
# deferred ns:hmr-delta broadcast, so component HMR silently never reaches the device.
|
|
98
92
|
# Also counts open /ns-hmr sockets so the update summary stops reporting recipients=0 for
|
|
99
|
-
# delivered updates.
|
|
93
|
+
# delivered updates. Includes shared script sources in transaction ordering while
|
|
94
|
+
# excluding dependencies, tests and declarations. External-source edits use the absolute
|
|
95
|
+
# source for graph identity and a Vite /@fs transform URL instead of treating
|
|
96
|
+
# parent-relative paths as app URLs.
|
|
100
97
|
"@nativescript-community/vite-octane@0.2.1": node_modules/.pnpm-config/@octane-xplat/patches/patches/@nativescript-community__vite-octane@0.2.1.patch
|
|
101
98
|
# Enables `resolveHiddenExtensions` on the language plugin's typescript facet so
|
|
102
99
|
# extensionless specifiers (e.g. './Text') probe `.tsrx` after moduleSuffixes — required
|
|
@@ -120,32 +117,26 @@ patchedDependencies:
|
|
|
120
117
|
# boot). Same fix as linkifyjs: try/catch new RegExp with an ASCII-range fallback — the
|
|
121
118
|
# matcher only feeds autolink, which native doesn't use.
|
|
122
119
|
"@lexical/link@0.51.0": node_modules/.pnpm-config/@octane-xplat/patches/patches/@lexical__link@0.51.0.patch
|
|
123
|
-
# ui-image 5.x regressed two iOS prefetch behaviors the 4.x line shipped (still present in
|
|
124
|
-
# 4.6.20): prefetchToCacheType ignores its cacheType argument — SDWebImagePrefetcher's
|
|
125
|
-
# default context stores to disk+memory either way, so a 'disk' prefetch also decodes and
|
|
126
|
-
# warms memory, diverging from Android's bytes-only Glide downloadOnly — and
|
|
127
|
-
# getContextFromOptions drops PrefetchOptions.headers because it never sets
|
|
128
|
-
# SDWebImageContextDownloadRequestModifier, so prefetch({headers}) silently sends no
|
|
129
|
-
# headers on iOS while Android's CustomGlideUrl forwards them. Ports upstream's own 4.x
|
|
130
|
-
# implementation — SDWebImageContextStoreCacheType on the prefetch context plus the
|
|
131
|
-
# request-modifier branch in getContextFromOptions — and additionally sets
|
|
132
|
-
# SDWebImageContextOriginalStoreCacheType so a memory prefetch does not spill original
|
|
133
|
-
# bytes to disk.
|
|
134
|
-
"@nativescript-community/ui-image@5.0.17": node_modules/.pnpm-config/@octane-xplat/patches/patches/@nativescript-community__ui-image@5.0.17.patch
|
|
135
120
|
# @nativescript/doctor's Android runtime check reads the app's raw devDependencies
|
|
136
121
|
# specifier and npm dist-tag resolves anything that isn't a semver range — a pnpm
|
|
137
122
|
# `catalog:` pin throws 'The determined Android runtime version ... is not valid' and
|
|
138
123
|
# kills `ns build android` before toolchain checks. Resolve catalog specifiers through the
|
|
139
124
|
# installed package's manifest, reusing the existing tgz-branch mechanism.
|
|
140
125
|
"@nativescript/doctor@2.0.18": node_modules/.pnpm-config/@octane-xplat/patches/patches/@nativescript__doctor@2.0.18.patch
|
|
141
|
-
#
|
|
142
|
-
#
|
|
143
|
-
#
|
|
144
|
-
#
|
|
145
|
-
#
|
|
146
|
-
|
|
126
|
+
# shaders@4.0.0 inlines TypeGPU 0.12.3 into dist/core/std-ClftfWpF.js, which declares
|
|
127
|
+
# /\p{XID_Start}/ and /\p{XID_Continue}/ regex literals at module scope (WGSL identifier
|
|
128
|
+
# validation, external prop-chain matching, minify separator check). NativeScript's
|
|
129
|
+
# embedded V8 on iOS (@nativescript/ios 9.1.0) and Android (@nativescript/android 9.1.1,
|
|
130
|
+
# V8 14.9.207.39) is built without ICU — every \p{…} escape is a SyntaxError, even in a
|
|
131
|
+
# never-called function, so a literal fails the whole module at parse time and any bundled
|
|
132
|
+
# app importing `shaders/js` dies on boot. Rewriting to \p{ID_Start}/\p{L} would not help
|
|
133
|
+
# — all property escapes are rejected. The patch builds the regexes via try/catch new
|
|
134
|
+
# RegExp with ASCII/non-ASCII-range fallbacks — byte-identical .source where ICU exists.
|
|
135
|
+
# dist/js/bundle.js, react/bundle.js, and cli.js carry the same escapes but are
|
|
136
|
+
# unreachable from `shaders/js` and left unpatched.
|
|
137
|
+
shaders@4.0.0: node_modules/.pnpm-config/@octane-xplat/patches/patches/shaders@4.0.0.patch
|
|
147
138
|
configDependencies:
|
|
148
|
-
"@octane-xplat/patches": 0.
|
|
139
|
+
"@octane-xplat/patches": 0.17.1
|
|
149
140
|
catalogs:
|
|
150
141
|
xplat:
|
|
151
142
|
"@nativescript-community/octane": 0.2.5
|
|
@@ -155,19 +146,19 @@ catalogs:
|
|
|
155
146
|
"@nativescript/ios": 9.1.0
|
|
156
147
|
"@nativescript/types": 9.1.4
|
|
157
148
|
"@nativescript/unit-test-runner": 5.0.0
|
|
158
|
-
"@nativescript/vite": 8.0.
|
|
159
|
-
"@octane-xplat/cli": 0.
|
|
160
|
-
"@octane-xplat/jsx": 0.
|
|
161
|
-
"@octane-xplat/
|
|
162
|
-
"@octane-xplat/
|
|
149
|
+
"@nativescript/vite": 8.0.20
|
|
150
|
+
"@octane-xplat/cli": 0.17.1
|
|
151
|
+
"@octane-xplat/jsx": 0.17.1
|
|
152
|
+
"@octane-xplat/nativescript-masonkit": 1.0.0-beta.126
|
|
153
|
+
"@octane-xplat/platform": 0.17.1
|
|
154
|
+
"@octane-xplat/ui": 0.17.1
|
|
163
155
|
"@octanejs/vite-plugin": 0.1.61
|
|
164
|
-
"@triniwiz/nativescript-masonkit": 1.0.0-beta.110
|
|
165
156
|
"@tsrx/oxc": 0.20.0
|
|
166
157
|
"@tsrx/typescript-plugin": 0.4.11
|
|
167
158
|
"@valor/nativescript-websockets": 2.0.3
|
|
168
159
|
jsdom: 30.1.1
|
|
169
160
|
nativescript: 9.1.1
|
|
170
|
-
octane: 0.
|
|
161
|
+
octane: 0.14.0
|
|
171
162
|
typescript: 5.9.3
|
|
172
163
|
vite: 8.3.0
|
|
173
164
|
vitest: 4.1.11
|
package/template/src/App.tsrx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useState } from 'octane';
|
|
2
|
-
import { View, HStack, Text, Pressable, ScrollableArea, useColorScheme } from '@octane-xplat/ui';
|
|
2
|
+
import { View, HStack, Text, Pressable, ScrollableArea, SafeArea, useColorScheme } from '@octane-xplat/ui';
|
|
3
3
|
import { storage } from '@octane-xplat/platform';
|
|
4
4
|
|
|
5
5
|
const FEATURES = [
|
|
@@ -23,26 +23,28 @@ export function App() @{
|
|
|
23
23
|
const scheme = useColorScheme();
|
|
24
24
|
const dark = override ?? scheme === 'dark';
|
|
25
25
|
|
|
26
|
-
<
|
|
27
|
-
<
|
|
28
|
-
|
|
26
|
+
<SafeArea>
|
|
27
|
+
<View className={['app', dark ? 'dark' : 'light']}>
|
|
28
|
+
<Text className="title">octane-xplat</Text>
|
|
29
|
+
<Text className="muted">one codebase — web · iOS · android</Text>
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
<HStack className="counter">
|
|
32
|
+
<Pressable testID="counter.decrement" accessibilityLabel="Decrease count" className="btn" onPress={() => setCount(count - 1)}><Text>−</Text></Pressable>
|
|
33
|
+
<Text testID="counter.value" className="count">{count}</Text>
|
|
34
|
+
<Pressable testID="counter.increment" accessibilityLabel="Increase count" className="btn" onPress={() => setCount(count + 1)}><Text>+</Text></Pressable>
|
|
35
|
+
</HStack>
|
|
35
36
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
<ScrollableArea>
|
|
38
|
+
{FEATURES.map((f) => <Text className="chip">{f}</Text>)}
|
|
39
|
+
</ScrollableArea>
|
|
39
40
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
41
|
+
<Pressable testID="settings.toggle-theme" className="link" onPress={() => {
|
|
42
|
+
const next = !dark;
|
|
43
|
+
storage.setString('theme-preference', next ? 'dark' : 'light');
|
|
44
|
+
setOverride(next);
|
|
45
|
+
}}>
|
|
46
|
+
<Text>{dark ? 'light' : 'dark'} mode (system: {scheme})</Text>
|
|
47
|
+
</Pressable>
|
|
48
|
+
</View>
|
|
49
|
+
</SafeArea>
|
|
48
50
|
}
|
package/template/src/main.ts
CHANGED
|
@@ -2,18 +2,18 @@
|
|
|
2
2
|
// different shell: a Frame hosting a Page that renders the universal root.
|
|
3
3
|
import { Application, Frame, Page } from '@nativescript/core'
|
|
4
4
|
import { renderNativeScriptApp } from '@nativescript-community/octane'
|
|
5
|
+
import { prepareFullScreenPage } from '@octane-xplat/ui/nativescript'
|
|
5
6
|
import { App } from './App.tsrx'
|
|
6
7
|
import '@octane-xplat/ui/theme/tokens.css'
|
|
7
8
|
import './style.css'
|
|
8
9
|
|
|
9
10
|
const entry = {
|
|
10
11
|
create: () => {
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
// named stacks come from TabSpec.stack or registerStack. Route
|
|
14
|
-
// pushes also need registerScreens({name: Component}) at boot.
|
|
12
|
+
// A Frame hosts the root Page. RouteOutlet binds canonical routing
|
|
13
|
+
// to this Frame when the app adds generated routes.
|
|
15
14
|
const frame = new Frame()
|
|
16
|
-
|
|
15
|
+
// Full-screen page: SafeArea owns safe-area padding.
|
|
16
|
+
const page = prepareFullScreenPage(new Page())
|
|
17
17
|
page.actionBarHidden = true
|
|
18
18
|
renderNativeScriptApp(page, App)
|
|
19
19
|
frame.navigate({ create: () => page })
|