create-octane-xplat 0.6.0 → 0.7.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/README.md +37 -17
- package/package.json +6 -1
- package/template/.agents/skills/xplat/SKILL.md +7 -3
- package/template/.agents/skills/xplat/references/components.md +13 -8
- package/template/.agents/skills/xplat/references/navigation.md +8 -5
- package/template/.agents/skills/xplat/references/platform.md +21 -21
- package/template/App_Resources/iOS/src/ShaderEffectRegistration.swift +31 -0
- package/template/package.json +7 -8
- package/template/patches/@nativescript-community__octane@0.2.1.patch +16 -0
- package/template/patches/@nativescript__core@9.1.2.patch +752 -0
- package/template/patches/@nativescript__core@9.1.3-next.2.patch +428 -0
- package/template/patches/@nativescript__vite@8.0.11.patch +531 -261
- package/template/patches/@nativescript__vite@8.0.13.patch +233 -0
- package/template/patches/css@3.0.0.patch +52 -0
- package/template/patches/octane@0.6.3.patch +1149 -0
- package/template/pnpm-workspace.yaml +54 -12
- package/template/src/App.tsrx +2 -2
- package/template/tsconfig.json +1 -1
- package/template/tsconfig.native.json +1 -1
- package/template/vite.config.ts +3 -3
- /package/template/src/{index.native.ts → index.ts} +0 -0
package/README.md
CHANGED
|
@@ -1,30 +1,50 @@
|
|
|
1
1
|
# create-octane-xplat
|
|
2
2
|
|
|
3
|
-
>
|
|
4
|
-
>
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
> Get a TypeScript app running, then build its first useful flow with your
|
|
4
|
+
> coding agent.
|
|
5
|
+
|
|
6
|
+
xplat's target direction spans web, iOS, Android, macOS, and Windows. This
|
|
7
|
+
starter configures **web, iOS, and Android**. macOS is a separate experiment;
|
|
8
|
+
Windows has a separate experimental scaffold with runtime verification pending. See
|
|
9
|
+
[target support](https://octane-xplat.goddardai.org/spec#choose-your-targets).
|
|
10
|
+
The framework is `0.x` and its API surface is still changing.
|
|
11
|
+
|
|
12
|
+
With Node.js and pnpm installed:
|
|
8
13
|
|
|
9
14
|
```sh
|
|
10
15
|
pnpm create octane-xplat my-app
|
|
11
|
-
# or: npm create octane-xplat my-app
|
|
12
16
|
```
|
|
13
17
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
18
|
+
The creator copies the template, installs dependencies with pnpm, and starts
|
|
19
|
+
the web dev server. Open the printed URL and open `my-app` in your agent.
|
|
20
|
+
Ask it to read `AGENTS.md` and `.agents/skills/xplat/SKILL.md`, then build a
|
|
21
|
+
packing checklist with add, pack, and remove actions using in-memory state.
|
|
22
|
+
Check the result before asking for the next feature: add “Passport” and
|
|
23
|
+
“Charger,” pack Passport, and confirm that one item remains. Reloading should
|
|
24
|
+
clear the list in this in-memory version. The
|
|
25
|
+
[first-flow guide](https://octane-xplat.goddardai.org/toolchain#build-and-check-your-first-flow)
|
|
26
|
+
includes the full prompt and failure checks.
|
|
27
|
+
|
|
28
|
+
Leave the server running while you edit. After stopping it, restart from the
|
|
29
|
+
app directory:
|
|
17
30
|
|
|
18
31
|
```sh
|
|
32
|
+
cd my-app
|
|
19
33
|
pnpm dev # web
|
|
20
|
-
pnpm dev:ios # iOS simulator — needs the NativeScript toolchain
|
|
21
|
-
pnpm dev:android # Android — needs the NativeScript toolchain
|
|
22
|
-
pnpm typecheck # web + native tsconfigs
|
|
23
34
|
```
|
|
24
35
|
|
|
25
|
-
|
|
36
|
+
From that directory, these commands run other targets or check the app:
|
|
37
|
+
|
|
38
|
+
```sh
|
|
39
|
+
pnpm dev:ios # needs macOS, Xcode, and NativeScript setup
|
|
40
|
+
pnpm dev:android # needs Android SDK, compatible JDK, and NativeScript setup
|
|
41
|
+
pnpm lint
|
|
42
|
+
pnpm typecheck # web + native tsconfigs
|
|
43
|
+
pnpm build # web production bundle
|
|
44
|
+
```
|
|
26
45
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
46
|
+
[Get a working app and iterate](https://octane-xplat.goddardai.org/toolchain)
|
|
47
|
+
provides prerequisites, live-update boundaries, and version guidance. The
|
|
48
|
+
starter's xplat skill is included; NativeScript's official skills are optional
|
|
49
|
+
and installed separately. Ask your agent to report checks and targets actually
|
|
50
|
+
run. [llms.txt](https://octane-xplat.goddardai.org/llms.txt) indexes the docs.
|
package/package.json
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-octane-xplat",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Scaffold a minimal @octane-xplat/ui app — one Octane codebase for web + iOS + Android",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/octane-xplat/octane-xplat.git",
|
|
8
|
+
"directory": "packages/create"
|
|
9
|
+
},
|
|
5
10
|
"bin": {
|
|
6
11
|
"create-octane-xplat": "./index.mjs"
|
|
7
12
|
},
|
|
@@ -14,7 +14,7 @@ below prevent that; `pnpm lint` enforces most of them.
|
|
|
14
14
|
|
|
15
15
|
1. **One element vocabulary per file.** Shared code renders only
|
|
16
16
|
`@octane-xplat/ui` components. Platform divergence lives in whole files
|
|
17
|
-
— `
|
|
17
|
+
— `.web` for browser code, `.mobile` for shared iOS/Android variants, OS-specific suffixes, and the unsuffixed native default —
|
|
18
18
|
chosen by the bundler when something imports `./Foo`. Never branch on
|
|
19
19
|
platform inside JSX (`Platform.OS`, conditional imports, `typeof
|
|
20
20
|
document` checks). Platform-authentic widgets live behind
|
|
@@ -37,7 +37,11 @@ document` checks). Platform-authentic widgets live behind
|
|
|
37
37
|
for async data. Name signal variables with a `$` suffix (`count$`,
|
|
38
38
|
`user$`) — the compiler uses the suffix to preserve reactive reads on
|
|
39
39
|
native. Every module that reads or writes signals needs a runtime
|
|
40
|
-
`import 'octane/signals'` (or `octane/signals/client`).
|
|
40
|
+
`import 'octane/signals'` (or `octane/signals/client`). `query$` reads
|
|
41
|
+
suspend — put `.get()` under `@try`/`@pending`/`@catch`, or use
|
|
42
|
+
`.snapshot()`/`.latest()` for non-suspending reads. Never write to a
|
|
43
|
+
shared signal during render — screen-scoped data keyed by route params
|
|
44
|
+
gets a component-local `query$`, not a shared selector signal.
|
|
41
45
|
6. **One `octane` per app.** Don't add a second renderer or duplicate the
|
|
42
46
|
package — two copies break the reconciler without a helpful error.
|
|
43
47
|
|
|
@@ -52,7 +56,7 @@ document` checks). Platform-authentic widgets live behind
|
|
|
52
56
|
declare deps explicitly: `useLayoutEffect(fn, [props.x])`.
|
|
53
57
|
- Input handlers must be idempotent — `onInput`/`onChange` can dispatch
|
|
54
58
|
more than once per user action on web.
|
|
55
|
-
- `.
|
|
59
|
+
- `.mobile.tsrx`/`.ios.tsrx`/`.android.tsrx` files and unsuffixed native-default files containing JSX must
|
|
56
60
|
start with `/** @jsxImportSource @nativescript-community/octane */` on
|
|
57
61
|
line 1 — nothing may precede it.
|
|
58
62
|
|
|
@@ -6,7 +6,7 @@ OS chrome) live behind `@octane-xplat/ui/ios` and `@octane-xplat/ui/android`
|
|
|
6
6
|
under their official OS names — those subpaths resolve only in native
|
|
7
7
|
builds and are the honest opt-in for "I want the platform widget." A shared
|
|
8
8
|
`.tsrx` file that imports them fails the other platform's build on purpose;
|
|
9
|
-
import them inside `.ios.tsrx`/`.android.tsrx`/`.
|
|
9
|
+
import them inside `.ios.tsrx`/`.android.tsrx`/`.mobile.tsrx` leaves.
|
|
10
10
|
|
|
11
11
|
## Shared — `@octane-xplat/ui` (self-drawn, same pixels everywhere)
|
|
12
12
|
|
|
@@ -52,9 +52,12 @@ inert layout below it).
|
|
|
52
52
|
|
|
53
53
|
## Platform-only shared exports
|
|
54
54
|
|
|
55
|
-
- `KeyboardAvoiding` —
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
- `KeyboardAvoiding` — shared root export; web avoids via the
|
|
56
|
+
`visualViewport` inset, iOS/Android via OS keyboard events, macOS leaf
|
|
57
|
+
is an inert column.
|
|
58
|
+
- `Hoverable`, `Tooltip` — shared root exports; pointer platforms (web,
|
|
59
|
+
macOS) mount the hint layer, touch targets render only the trigger —
|
|
60
|
+
keep essential information out of `card`/`content`.
|
|
58
61
|
|
|
59
62
|
## Divergences that matter
|
|
60
63
|
|
|
@@ -77,10 +80,12 @@ inert layout below it).
|
|
|
77
80
|
`DrawerLayout`/`SideDrawer` in the platform subpaths.
|
|
78
81
|
- `RichTextSpan` children carry their own `className`/`style`/`onPress`;
|
|
79
82
|
native maps spans to `FormattedString` runs.
|
|
80
|
-
- `UITabBar`/`BottomNavigationView` give
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
(
|
|
83
|
+
- `UITabBar`/`BottomNavigationView` give platform-authentic tab chrome for
|
|
84
|
+
`TabSpec.stack`; pushed screens render inside the pane either way —
|
|
85
|
+
through a real per-pane `Frame` on iOS, and through the route store on
|
|
86
|
+
Android (Frame-in-TabViewItem bookkeeping is unreliable upstream,
|
|
87
|
+
NativeScript#11444). The shared `Tabs` uses the same route-store pane on
|
|
88
|
+
both.
|
|
84
89
|
- Accessibility props (`accessible`, label, hint, role, state, live
|
|
85
90
|
region) share names across targets; native translates roles such as
|
|
86
91
|
`heading` → `header`.
|
|
@@ -19,12 +19,15 @@ call sites drive both.
|
|
|
19
19
|
- **Back** — browser back, hardware back (Android), and swipe-back (iOS)
|
|
20
20
|
all map to `popRoute` on the current stack.
|
|
21
21
|
|
|
22
|
-
##
|
|
22
|
+
## Named stacks
|
|
23
23
|
|
|
24
|
-
Pushing into a
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
Pushing into a named stack (`{ into: 'stackName' }`) works on all three
|
|
25
|
+
targets: web pushes into the nested outlet, iOS navigates a real `Frame`
|
|
26
|
+
when the platform `UITabBar` registered one (and uses the route store
|
|
27
|
+
under the shared `Tabs`), and Android always uses the router-owned
|
|
28
|
+
swap-pane — pushed screens render inside the active tab pane.
|
|
29
|
+
`popRoute(stack)`/`useRoute(stack)` behave the same either way, so app
|
|
30
|
+
code never sees which mechanism ran.
|
|
28
31
|
|
|
29
32
|
For loaders, route config, and generated route types, see the navigation
|
|
30
33
|
guide: https://octane-xplat.goddardai.org/navigation
|
|
@@ -1,44 +1,44 @@
|
|
|
1
1
|
# Platform services + writing leaves
|
|
2
2
|
|
|
3
|
-
Two seams cover everything shared code can't reach:
|
|
4
|
-
|
|
5
3
|
## `@octane-xplat/platform` — device services
|
|
6
4
|
|
|
7
|
-
|
|
8
|
-
`.
|
|
5
|
+
Import a capability through the package barrel. The unsuffixed module is its
|
|
6
|
+
native default, `.web` is the browser implementation, and `.mobile` or an OS
|
|
7
|
+
suffix narrows behavior when needed. Surface: `device`, `appInfo`, `locale`,
|
|
9
8
|
`connectivity`, `useWindowSize`, `useAppState`, `useSafeAreaInsets`, deep
|
|
10
9
|
links, clipboard, `secureStorage`, haptics, notifications, `announce`,
|
|
11
10
|
`systemBars`, geolocation, permissions, media, files, share, `openUrl`,
|
|
12
11
|
`openSettings`, biometrics.
|
|
13
12
|
|
|
14
|
-
Services report honestly — `biometrics` returns `unsupported` on web
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
Services report honestly — `biometrics` returns `unsupported` on web rather
|
|
14
|
+
than faking a result. Handle the tier your app can fall back to instead of
|
|
15
|
+
assuming success.
|
|
17
16
|
|
|
18
17
|
## Leaf files — your own divergence
|
|
19
18
|
|
|
20
|
-
When a service doesn't exist yet or a visual behavior truly differs,
|
|
21
|
-
|
|
19
|
+
When a service doesn't exist yet or a visual behavior truly differs, split the
|
|
20
|
+
file, not the JSX:
|
|
22
21
|
|
|
23
22
|
```text
|
|
24
|
-
src/parts/Scanner.tsrx
|
|
25
|
-
src/parts/Scanner.
|
|
26
|
-
src/parts/Scanner.
|
|
23
|
+
src/parts/Scanner.tsrx native default
|
|
24
|
+
src/parts/Scanner.web.tsrx browser implementation (camera via getUserMedia)
|
|
25
|
+
src/parts/Scanner.mobile.tsrx shared iOS/Android implementation
|
|
26
|
+
src/parts/Scanner.ios.tsrx iOS-only override
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
Import the bare specifier (`./Scanner
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
Import the bare specifier (`./Scanner`); the bundler selects `.web` first on
|
|
30
|
+
web and the most specific native variant on each OS. The `.mobile` variant
|
|
31
|
+
overrides the unsuffixed native default on iOS and Android.
|
|
32
32
|
|
|
33
33
|
Leaf rules:
|
|
34
34
|
|
|
35
|
-
- Inside
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
- Inside `.web.*`, DOM APIs are expected; NativeScript imports belong in
|
|
36
|
+
`.mobile.*`, `.ios.*`, `.android.*`, or the unsuffixed native default. If
|
|
37
|
+
browser code imports that default, add a `.web.*` sibling.
|
|
38
38
|
- Never read app-level NativeScript globals (`Application.android.*`)
|
|
39
|
-
at module top — read them inside the call. Top-level reads crash cold
|
|
40
|
-
|
|
41
|
-
- `.
|
|
39
|
+
at module top — read them inside the call. Top-level reads crash cold start
|
|
40
|
+
before the app exists.
|
|
41
|
+
- Native-specific `.tsrx` files containing JSX start with
|
|
42
42
|
`/** @jsxImportSource @nativescript-community/octane */` on line 1.
|
|
43
43
|
- Deep imports under `@nativescript/core/ui/*` bundle as a second module
|
|
44
44
|
instance — import from `@nativescript/core` only.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import SwiftUI
|
|
2
|
+
|
|
3
|
+
// Registers the app's stitchable shaders (OctaneShaders.metal) by name for
|
|
4
|
+
// @octane-xplat/effects' <ShaderEffect>. Names are runtime strings because
|
|
5
|
+
// ShaderLibrary lookups are compile-time — registration is the dispatch
|
|
6
|
+
// table. Call `OctaneShaderEffects.install()` from JS before rendering a
|
|
7
|
+
// ShaderEffect (`declare const OctaneShaderEffects: any`).
|
|
8
|
+
//
|
|
9
|
+
// Pair-bound: this file + OctaneShaders.metal + the @octane-xplat/effects
|
|
10
|
+
// dependency go together — delete all three if you don't want shader
|
|
11
|
+
// effects.
|
|
12
|
+
@objc(OctaneShaderEffects)
|
|
13
|
+
public final class OctaneShaderEffects: NSObject {
|
|
14
|
+
@objc public static func install() {
|
|
15
|
+
XplatShaderEffectRegistry.register("heatHaze", kind: .distortion) { args in
|
|
16
|
+
ShaderLibrary.octaneHeatHaze(
|
|
17
|
+
.float(args.f("time")), .float2(args.size), .float(args.f("intensity"))
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
XplatShaderEffectRegistry.register("sheen", kind: .color) { args in
|
|
21
|
+
ShaderLibrary.octaneSheen(
|
|
22
|
+
.float(args.f("time")), .float2(args.size), .float(args.f("intensity"))
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
XplatShaderEffectRegistry.register("shatter", kind: .layer) { args in
|
|
26
|
+
ShaderLibrary.octaneShatter(
|
|
27
|
+
.float2(args.size), .float(args.f("progress"))
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
package/template/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "octane-xplat-app",
|
|
3
3
|
"private": true,
|
|
4
4
|
"type": "module",
|
|
5
|
-
"main": "src/index.
|
|
5
|
+
"main": "src/index.ts",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"xplat": "xplat",
|
|
8
8
|
"dev": "vite",
|
|
@@ -18,13 +18,12 @@
|
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"@nativescript-community/gesturehandler": "2.0.45",
|
|
20
20
|
"@nativescript-community/octane": "0.2.1",
|
|
21
|
-
"@nativescript-community/ui-canvas": "4.6.52",
|
|
22
21
|
"@nativescript-community/ui-drawer": "0.1.32",
|
|
23
|
-
"@nativescript-community/ui-svg": "0.2.24",
|
|
24
22
|
"@nativescript/core": "9.1.2",
|
|
25
|
-
"@octane-xplat/
|
|
23
|
+
"@octane-xplat/effects": "^0.7.0",
|
|
24
|
+
"@octane-xplat/ui": "^0.7.0",
|
|
26
25
|
"@valor/nativescript-websockets": "2.0.3",
|
|
27
|
-
"octane": "0.
|
|
26
|
+
"octane": "0.6.3"
|
|
28
27
|
},
|
|
29
28
|
"devDependencies": {
|
|
30
29
|
"@nativescript-community/vite-octane": "0.2.1",
|
|
@@ -32,9 +31,9 @@
|
|
|
32
31
|
"@nativescript/ios": "9.1.0",
|
|
33
32
|
"@nativescript/types": "9.0.0",
|
|
34
33
|
"@nativescript/vite": "8.0.11",
|
|
35
|
-
"@octane-xplat/cli": "^0.
|
|
36
|
-
"@octane-xplat/lint": "^0.
|
|
37
|
-
"@octanejs/vite-plugin": "0.1.
|
|
34
|
+
"@octane-xplat/cli": "^0.7.0",
|
|
35
|
+
"@octane-xplat/lint": "^0.7.0",
|
|
36
|
+
"@octanejs/vite-plugin": "0.1.61",
|
|
38
37
|
"@tsrx/typescript-plugin": "0.4.7",
|
|
39
38
|
"nativescript": "9.1.1",
|
|
40
39
|
"oxlint": "^1.74.0",
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
diff --git a/dist/driver.js b/dist/driver.js
|
|
2
|
+
index f485f992282c05c58fed810458303be3141f51c0..9b5a654cf0c32be5fc6da4ae5fec04a58560925f 100644
|
|
3
|
+
--- a/dist/driver.js
|
|
4
|
+
+++ b/dist/driver.js
|
|
5
|
+
@@ -83,6 +83,11 @@ function setProp(node, view, name, value) {
|
|
6
|
+
setRenderItem(view, value ?? null, createNativeScriptRoot);
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
+ // `undefined` means "prop not supplied" — skip the write. NativeScript
|
|
10
|
+
+ // native setters coerce it (e.g. editable → ios.userInteractionEnabled =
|
|
11
|
+
+ // NO) leaving dead UI that still has its JS listeners.
|
|
12
|
+
+ if (value === undefined)
|
|
13
|
+
+ return;
|
|
14
|
+
// Core raises `<name>Change` for a script write exactly as for a user
|
|
15
|
+
// edit; a controlled input must not see its own value come back as one.
|
|
16
|
+
const echo = `${name}Change`;
|