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 CHANGED
@@ -1,30 +1,50 @@
1
1
  # create-octane-xplat
2
2
 
3
- > Scaffolds an Octane xplat app — one codebase targeting web, iOS, and
4
- > Android via NativeScript.
5
- >
6
- > Status: `0.x` — the API surface is still moving. iOS/Android targets need
7
- > the NativeScript toolchain (Xcode/JDK + `ns`).
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
- Copies the starter template (vite + nativescript configs, shared `src/`,
15
- `App_Resources`, pinned workspace deps), installs dependencies with pnpm,
16
- and starts the web dev server. From there:
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
- Docs: [Running and checking an app](https://octane-xplat.goddardai.org/toolchain)
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
- The scaffolded app ships `.agents/skills/xplat/` — a skill with the
28
- invariants agents need before writing code (element vocabulary, DOM-global
29
- rules, signal naming, compiler surprises) plus per-domain references.
30
- Agents: [llms.txt](https://octane-xplat.goddardai.org/llms.txt) indexes the docs.
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.6.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
- — `Foo.web.tsrx`, `Foo.native.tsrx`, `Foo.ios.tsrx`, `Foo.android.tsrx` —
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
- - `.native.tsrx`/`.ios.tsrx`/`.android.tsrx` files containing JSX must
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`/`.native.tsrx` leaves.
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` — native root export only (inert on web).
56
- - `Hoverable` — `@octane-xplat/ui/web` only. Hover has no touch-platform
57
- semantic; the old native leaf's long-press stand-in was fake parity.
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 real per-pane navigation stacks
81
- for `TabSpec.stack`; the shared `Tabs` keeps stack history in the route
82
- store instead. On Android, Frame-in-TabViewItem is unreliable upstream
83
- (NativeScript#11444) — prefer the shared `Tabs` there for stack panes.
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
- ## Known gap
22
+ ## Named stacks
23
23
 
24
- Pushing into a _named_ stack is iOS-only until
25
- [NativeScript#11444](https://github.com/NativeScript/NativeScript/issues/11444)
26
- lands — `pushRoute` there warns loudly on Android instead of dropping
27
- silently. Root-stack navigation works on both platforms.
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
- One import per capability; each service resolves internally to a `.web` or
8
- `.native` implementation. Surface: `device`, `appInfo`, `locale`,
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
- rather than faking a result. Handle the tier your app can fall back to
16
- instead of assuming success.
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
- split the file, not the JSX:
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 shared part — imports './Scanner.impl'
25
- src/parts/Scanner.impl.web.ts DOM implementation (camera via getUserMedia)
26
- src/parts/Scanner.impl.native.ts NativeScript implementation
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.impl`); the bundler picks the leaf
30
- via the suffix chain `.ios` → `.android` → `.native` → shared / `.web` →
31
- shared.
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 a leaf, platform APIs are _expected_: DOM globals in `.web.*`,
36
- `@nativescript/*` imports in `.native.*`. The lint rules key off the
37
- suffix — don't fight them with suppressions.
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
- start before the app exists.
41
- - `.native.tsrx` files with JSX start with
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
+ }
@@ -2,7 +2,7 @@
2
2
  "name": "octane-xplat-app",
3
3
  "private": true,
4
4
  "type": "module",
5
- "main": "src/index.native.ts",
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/ui": "^0.5.0",
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.4.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.5.0",
36
- "@octane-xplat/lint": "^0.1.0",
37
- "@octanejs/vite-plugin": "0.1.58",
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`;