create-octane-xplat 0.7.2 → 0.9.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
@@ -3,13 +3,14 @@
3
3
  > Get a TypeScript app running, then build its first useful flow with your
4
4
  > coding agent.
5
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
6
+ Xplat's target direction spans web, iOS, Android, macOS, Windows, and Linux.
7
+ This starter configures **web, iOS, and Android**. macOS, Windows, and Linux
8
+ are separate experiments — an AppKit host, a WinUI 3 scaffold with runtime
9
+ verification pending, and a WebKitGTK webview host. See
9
10
  [target support](https://octane-xplat.goddardai.org/spec#choose-your-targets).
10
11
  The framework is `0.x` and its API surface is still changing.
11
12
 
12
- With Node.js and pnpm installed:
13
+ With Node.js and pnpm 11.24.0 installed:
13
14
 
14
15
  ```sh
15
16
  pnpm create octane-xplat my-app
@@ -25,6 +26,10 @@ clear the list in this in-memory version. The
25
26
  [first-flow guide](https://octane-xplat.goddardai.org/toolchain#build-and-check-your-first-flow)
26
27
  includes the full prompt and failure checks.
27
28
 
29
+ The generated app pins pnpm 11.24.0. Its `pnpm-workspace.yaml` loads
30
+ `@octane-xplat/patches` as a `configDependencies` package, making the
31
+ framework patches available during the first install.
32
+
28
33
  Leave the server running while you edit. After stopping it, restart from the
29
34
  app directory:
30
35
 
@@ -45,6 +50,6 @@ pnpm build # web production bundle
45
50
 
46
51
  [Get a working app and iterate](https://octane-xplat.goddardai.org/toolchain)
47
52
  provides prerequisites, live-update boundaries, and version guidance. The
48
- starter's xplat skill is included; NativeScript's official skills are optional
53
+ starter's Xplat skill is included; NativeScript's official skills are optional
49
54
  and installed separately. Ask your agent to report checks and targets actually
50
55
  run. [llms.txt](https://octane-xplat.goddardai.org/llms.txt) indexes the docs.
package/index.mjs CHANGED
@@ -4,7 +4,9 @@ import { dirname, join, resolve } from 'node:path'
4
4
  import { fileURLToPath } from 'node:url'
5
5
  import { spawnSync } from 'node:child_process'
6
6
 
7
- const dir = resolve(process.argv[2] ?? 'octane-xplat-app')
7
+ const positional = process.argv.slice(2).filter((arg) => !arg.startsWith('-'))
8
+ const noInstall = process.argv.includes('--no-install')
9
+ const dir = resolve(positional[0] ?? 'octane-xplat-app')
8
10
 
9
11
  if (existsSync(dir) && readdirSync(dir).length > 0) {
10
12
  console.error(`✗ ${dir} exists and is not empty`)
@@ -20,6 +22,12 @@ try {
20
22
 
21
23
  console.log(`\n✓ scaffolded ${dir}`)
22
24
 
25
+ if (noInstall) {
26
+ console.log('\nSkipped install (--no-install). To run manually:')
27
+ console.log(` cd ${dir}\n pnpm install\n pnpm dev`)
28
+ process.exit(0)
29
+ }
30
+
23
31
  const install = spawnSync('pnpm', ['install'], { cwd: dir, stdio: 'inherit' })
24
32
  if (install.status !== 0) {
25
33
  console.log(`\nInstall didn't finish (pnpm missing?). To run manually:`)
package/package.json CHANGED
@@ -1,22 +1,22 @@
1
1
  {
2
2
  "name": "create-octane-xplat",
3
- "version": "0.7.2",
3
+ "version": "0.9.0",
4
4
  "description": "Scaffold a minimal @octane-xplat/ui app — one Octane codebase for web + iOS + Android",
5
5
  "keywords": [
6
- "octane",
7
- "xplat",
8
- "cross-platform",
9
6
  "create",
7
+ "cross-platform",
8
+ "init",
9
+ "octane",
10
+ "project-generator",
10
11
  "scaffolding",
11
12
  "starter-template",
12
- "project-generator",
13
- "init"
13
+ "xplat"
14
14
  ],
15
- "license": "MIT",
16
15
  "homepage": "https://github.com/octane-xplat/octane-xplat/tree/main/packages/create",
16
+ "license": "MIT",
17
17
  "repository": {
18
18
  "type": "git",
19
- "url": "git+https://github.com/octane-xplat/octane-xplat.git",
19
+ "url": "https://github.com/octane-xplat/octane-xplat",
20
20
  "directory": "packages/create"
21
21
  },
22
22
  "bin": {
@@ -1,9 +1,9 @@
1
1
  ---
2
2
  name: xplat
3
- description: Build and review code in an Octane xplat app — one TypeScript codebase targeting web (DOM) and iOS/Android (NativeScript). Use when writing components, styles, routes, platform leaves, or device-service calls, and when a feature works on one target but not another.
3
+ description: Build and review code in an Octane Xplat app — one TypeScript codebase targeting web (DOM) and iOS/Android (NativeScript). Use when writing components, styles, routes, platform leaves, or device-service calls, and when a feature works on one target but not another.
4
4
  ---
5
5
 
6
- # Octane xplat app
6
+ # Octane Xplat app
7
7
 
8
8
  Every `src/*.tsrx` file compiles twice: once to the DOM renderer, once to
9
9
  NativeScript views. The common failure mode is **silent divergence** — code
@@ -7,8 +7,12 @@ native default, `.web` is the browser implementation, and `.mobile` or an OS
7
7
  suffix narrows behavior when needed. Surface: `device`, `appInfo`, `locale`,
8
8
  `connectivity`, `useWindowSize`, `useAppState`, `useSafeAreaInsets`, deep
9
9
  links, clipboard, `secureStorage`, haptics, notifications, `announce`,
10
- `systemBars`, geolocation, permissions, media, files, share, `openUrl`,
11
- `openSettings`, biometrics.
10
+ `systemBars`, permissions, `openUrl`, `openSettings`. Services that need a
11
+ NativeScript plugin ship as leaf packages: `@octane-xplat/share`,
12
+ `@octane-xplat/files`, `@octane-xplat/media`, `@octane-xplat/biometrics`,
13
+ `@octane-xplat/geolocation`, `@octane-xplat/notifications`,
14
+ `@octane-xplat/secure-storage`, and `haptics` in `@octane-xplat/haptics` —
15
+ each leaf owns its plugin as a real dependency.
12
16
 
13
17
  Services report honestly — `biometrics` returns `unsupported` on web rather
14
18
  than faking a result. Handle the tier your app can fall back to instead of
@@ -1,6 +1,6 @@
1
1
  # AGENTS.md
2
2
 
3
- Octane xplat app — one TypeScript codebase for web + iOS + Android (Octane
3
+ Octane Xplat app — one TypeScript codebase for web + iOS + Android (Octane
4
4
 
5
5
  - NativeScript).
6
6
 
@@ -2,7 +2,7 @@
2
2
  "name": "octane-xplat-app",
3
3
  "private": true,
4
4
  "type": "module",
5
- "main": "src/index.ts",
5
+ "main": "src/main.ts",
6
6
  "scripts": {
7
7
  "xplat": "xplat",
8
8
  "dev": "vite",
@@ -17,11 +17,12 @@
17
17
  },
18
18
  "dependencies": {
19
19
  "@nativescript-community/gesturehandler": "2.0.45",
20
- "@nativescript-community/octane": "0.2.1",
20
+ "@nativescript-community/octane": "0.2.4",
21
21
  "@nativescript-community/ui-drawer": "0.1.32",
22
22
  "@nativescript/core": "9.1.2",
23
- "@octane-xplat/effects": "^0.7.2",
24
- "@octane-xplat/ui": "^0.7.2",
23
+ "@octane-xplat/effects": "^0.9.0",
24
+ "@octane-xplat/platform": "^0.9.0",
25
+ "@octane-xplat/ui": "^0.9.0",
25
26
  "@valor/nativescript-websockets": "2.0.3",
26
27
  "octane": "0.6.3"
27
28
  },
@@ -30,14 +31,15 @@
30
31
  "@nativescript/android": "9.1.1",
31
32
  "@nativescript/ios": "9.1.0",
32
33
  "@nativescript/types": "9.0.0",
33
- "@nativescript/vite": "8.0.11",
34
- "@octane-xplat/cli": "^0.7.2",
35
- "@octane-xplat/lint": "^0.7.2",
34
+ "@nativescript/vite": "8.0.17",
35
+ "@octane-xplat/cli": "^0.9.0",
36
+ "@octane-xplat/lint": "^0.9.0",
36
37
  "@octanejs/vite-plugin": "0.1.61",
37
- "@tsrx/typescript-plugin": "0.4.7",
38
+ "@tsrx/typescript-plugin": "0.4.11",
38
39
  "nativescript": "9.1.1",
39
40
  "oxlint": "^1.74.0",
40
41
  "typescript": "5.9.3",
41
42
  "vite": "8.3.0"
42
- }
43
+ },
44
+ "packageManager": "pnpm@11.24.0"
43
45
  }
@@ -14,14 +14,21 @@ allowBuilds:
14
14
  '@parcel/watcher': true
15
15
  nativescript: true
16
16
  '@nativescript/core': false
17
- # Framework patch set required by the octane-xplat toolchain — applied via pnpm
18
- # patchedDependencies. Managed upstream by @octane-xplat/cli (`xplat patches apply`
19
- # refreshes, `xplat patches check` verifies); do not edit by hand.
17
+ # The patch set covers optional leaf deps (tiptap, linkifyjs, …) the starter
18
+ # doesn't install — tolerate entries that apply to nothing.
19
+ allowUnusedPatches: true
20
+ # Framework patches are supplied by the @octane-xplat/patches config dependency and
21
+ # applied via pnpm patchedDependencies. Managed upstream by @octane-xplat/cli (`xplat
22
+ # patches apply` refreshes, `xplat patches check` verifies); do not edit by hand.
20
23
  patchedDependencies:
21
24
  # Published octane 0.6.3 lacks live signal reads under universal renderers. Adds signal
22
25
  # ownership, read tracking, and local signal hooks; JSX prop lowering and __methodDep
23
- # already ship upstream.
24
- octane@0.6.3: patches/octane@0.6.3.patch
26
+ # already ship upstream. Routes uncaught universal event-handler errors to onUncaughtError
27
+ # when configured. Pins instance signal/query declarations to their declaring owner so
28
+ # child reads and event handlers share one request. Also allows async and generator
29
+ # helpers that are not components in universal compiler modules, while preserving errors
30
+ # when those functions are used as components (octanejs/octane#1469).
31
+ octane@0.6.3: node_modules/.pnpm-config/@octane-xplat/patches/patches/octane@0.6.3.patch
25
32
  # FlexboxLayout getChildMeasureSpec subtracts the child's own padding where AOSP subtracts
26
33
  # parent padding + child margins — a column-stretched row re-measurement clamps padded
27
34
  # children (37→17 dip, squished controls). Adds squircle (continuous) corner clipping for
@@ -33,44 +40,59 @@ patchedDependencies:
33
40
  # on iOS and detached from their tab fragment on Android), iOS items writes load the
34
41
  # selected item's view once controllers exist, and Frame._pushInFrameStack only promotes
35
42
  # frames whose ancestors present them (Frame.topmost stops returning hidden preloaded tab
36
- # frames).
37
- "@nativescript/core@9.1.2": patches/@nativescript__core@9.1.2.patch
43
+ # frames). Also maps CSS font-weight to the OpenType wght axis for custom Android fonts on
44
+ # API 26+, preserves explicit variation settings, and falls back to the prior file load
45
+ # when the axis is unsupported. Carries NativeScript PR #11481: preserves percentage
46
+ # translate values through parsing and resolves each axis against the view's own
47
+ # dimensions, including native views and animations.
48
+ "@nativescript/core@9.1.2": node_modules/.pnpm-config/@octane-xplat/patches/patches/@nativescript__core@9.1.2.patch
49
+ # @nativescript/types@9.0.0 omits ambient declarations for the native URL and
50
+ # URLSearchParams globals, so projects without DOM libraries cannot typecheck valid native
51
+ # URL code. Adds runtime-aligned declarations that merge with DOM declarations when both
52
+ # libraries are present.
53
+ "@nativescript/types@9.0.0": node_modules/.pnpm-config/@octane-xplat/patches/patches/@nativescript__types@9.0.0.patch
38
54
  # @nativescript/vite serializes bundled CSS and dev app.css through reworkcss `css`@3.0.0,
39
55
  # which mis-parses @layer into garbage selectors — rules die before reaching
40
56
  # _populateRules. Adds atlayer() covering block and statement forms. Applies to a
41
57
  # transitive dep — no app declares it directly.
42
- css@3.0.0: patches/css@3.0.0.patch
43
- # Dev-server /ns/m fixes: resolve pnpm-isolated transitive deps and platform-suffixed
44
- # entries, inline workspace CSS through /@fs, route vendor-excluded plugins through HTTP,
45
- # decode bracketed paths, and ignore comments during export-star expansion. Deps-bundle
46
- # alias: the dev payload's esbuild passes never see vite resolve.alias, so a dep importing
47
- # bare `octane` bundles the DOM runtime beside the universal one (hooks crash on boot,
48
- # octane-xplat#3) — an onResolve plugin redirects `octane` → `octane/universal/native`.
49
- # tsconfig-paths resolver also probes the .mobile suffix tier (ios/android) between the os
50
- # qualifier and unsuffixed default — .mobile-only modules behind paths-mapped imports
51
- # failed bundle-mode resolution.
52
- "@nativescript/vite@8.0.11": patches/@nativescript__vite@8.0.11.patch
58
+ css@3.0.0: node_modules/.pnpm-config/@octane-xplat/patches/patches/css@3.0.0.patch
59
+ # Dev-server fixes still missing after @nativescript/vite 8.0.17 added percent decoding
60
+ # for path-style /ns/m and Vue /ns/sfc requests (NativeScript#11483): collect re-export
61
+ # specifiers in the .vite/deps rewrite, route /@fs paths outside both roots through /ns/m
62
+ # (linked checkouts bypass the device transform upstream), and probe the non-active
63
+ # platform + `native` suffix tags in module candidates. The deps-bundle esbuild passes
64
+ # ignore vite resolve.alias, so a dep importing bare `octane` bundles the DOM runtime
65
+ # beside the universal one (hooks crash on boot, octane-xplat#3) — an onResolve plugin
66
+ # redirects `octane` → `octane/universal/native`. tsconfig-paths and the generated
67
+ # build-time checker moduleSuffixes also need the `.mobile` tier for iOS/Android/visionOS
68
+ # so `.mobile` leaves resolve under mapped imports and `ns build`.
69
+ "@nativescript/vite@8.0.17": node_modules/.pnpm-config/@octane-xplat/patches/patches/@nativescript__vite@8.0.17.patch
53
70
  # Driver setProp skips `undefined` writes — NativeScript native setters coerce it (e.g.
54
71
  # editable → ios.userInteractionEnabled = NO), leaving dead UI that still has its JS
55
- # listeners.
56
- "@nativescript-community/octane@0.2.1": patches/@nativescript-community__octane@0.2.1.patch
72
+ # listeners. Advertises the implemented visibility capability so committed suspense bodies
73
+ # can be retained while pending.
74
+ "@nativescript-community/octane@0.2.4": node_modules/.pnpm-config/@octane-xplat/patches/patches/@nativescript-community__octane@0.2.4.patch
57
75
  # octaneServerStrategy's SCRIPT_FILE_RE only covers [jt]sx? — .tsrx edits never emit the
58
76
  # deferred ns:hmr-delta broadcast, so component HMR silently never reaches the device.
59
77
  # Also counts open /ns-hmr sockets so the update summary stops reporting recipients=0 for
60
78
  # delivered updates.
61
- "@nativescript-community/vite-octane@0.2.1": patches/@nativescript-community__vite-octane@0.2.1.patch
62
- # Same dev-server fixes as the 8.0.11 patch, ported to the @11468 pkg.pr.new preview the
63
- # windows target uses (apps/windows): /ns/m re-export records, vendor-manifest gating for
64
- # plugin-patterned packages, percent-decoded specifiers, and the .mobile-aware
65
- # tsconfig-paths probing.
66
- "@nativescript/vite@8.0.13": patches/@nativescript__vite@8.0.13.patch
67
- # Windows spike: the shared-file subset of the 9.1.2 patch — @layer cascade machinery
68
- # (css-tree parser, css-selector, style-scope), the #11446 frame/tab-view common fixes,
69
- # and the flexbox common measure-spec — applied to the @11468 preview. iOS/Android
70
- # impl-file hunks are dead code on windows and were dropped.
71
- "@nativescript/core@9.1.3-next.2": patches/@nativescript__core@9.1.3-next.2.patch
79
+ "@nativescript-community/vite-octane@0.2.1": node_modules/.pnpm-config/@octane-xplat/patches/patches/@nativescript-community__vite-octane@0.2.1.patch
72
80
  # Enables `resolveHiddenExtensions` on the language plugin's typescript facet so
73
81
  # extensionless specifiers (e.g. './Text') probe `.tsrx` after moduleSuffixes — required
74
82
  # for a single extensionless shared barrel to resolve per-platform leaves under tsrx-tsc
75
83
  # and the IDE plugin.
76
- "@tsrx/typescript-plugin@0.4.11": patches/@tsrx__typescript-plugin@0.4.11.patch
84
+ "@tsrx/typescript-plugin@0.4.11": node_modules/.pnpm-config/@octane-xplat/patches/patches/@tsrx__typescript-plugin@0.4.11.patch
85
+ # linkifyjs declares /\p{L}/u and /\p{Emoji}/u as regex literals at module scope;
86
+ # NativeScript's embedded V8 builds without ICU, so the literals throw SyntaxError at
87
+ # parse time and poison the whole bundle (any bundled app importing @tiptap/extension-link
88
+ # dies on boot). Rewrites them as try/catch new RegExp builds with ASCII-range fallbacks —
89
+ # same behavior where ICU exists, graceful degradation where it doesn't.
90
+ linkifyjs@4.3.3: node_modules/.pnpm-config/@octane-xplat/patches/patches/linkifyjs@4.3.3.patch
91
+ # Two bundler-visible issues: useEditor.ts reads bare process.env.NODE_ENV (no node global
92
+ # in the harness typecheck program) and './Context' resolves to Context.tsrx — a
93
+ # self-import — under suffix-first resolution, so EditorContext/useCurrentEditor were
94
+ # unreachable. Adds a module-local process declaration for typecheck and merges the
95
+ # Context.ts definitions into Context.tsrx.
96
+ "@octanejs/tiptap@0.0.51": node_modules/.pnpm-config/@octane-xplat/patches/patches/@octanejs__tiptap@0.0.51.patch
97
+ configDependencies:
98
+ "@octane-xplat/patches": 0.9.0
@@ -1,5 +1,6 @@
1
1
  import { useState } from 'octane';
2
2
  import { View, Row, Text, Pressable, ScrollView, useColorScheme } from '@octane-xplat/ui';
3
+ import { storage } from '@octane-xplat/platform';
3
4
 
4
5
  const FEATURES = [
5
6
  'shared components',
@@ -13,8 +14,12 @@ const FEATURES = [
13
14
  * (.web.tsrx / .mobile.tsrx), never inside a component. */
14
15
  export function App() @{
15
16
  const [count, setCount] = useState(0);
16
- // null = follow the system; the toggle overrides it for this session.
17
- const [override, setOverride] = useState<boolean | null>(null);
17
+ // null = follow the system until the user chooses a mode.
18
+ const [override, setOverride] = useState<boolean | null>(() => {
19
+ const saved = storage.getString('theme-preference');
20
+ return saved === 'light' ? false : saved === 'dark' ? true : null;
21
+ });
22
+
18
23
  const scheme = useColorScheme();
19
24
  const dark = override ?? scheme === 'dark';
20
25
 
@@ -32,7 +37,11 @@ export function App() @{
32
37
  {FEATURES.map((f) => <Text className="chip">{f}</Text>)}
33
38
  </ScrollView>
34
39
 
35
- <Pressable className="link" onPress={() => setOverride(!dark)}>
40
+ <Pressable className="link" onPress={() => {
41
+ const next = !dark;
42
+ storage.setString('theme-preference', next ? 'dark' : 'light');
43
+ setOverride(next);
44
+ }}>
36
45
  <Text>{dark ? 'light' : 'dark'} mode (system: {scheme})</Text>
37
46
  </Pressable>
38
47
  </View>
@@ -0,0 +1,7 @@
1
+ // npm deps (upstream octane packages included) read `process.env.NODE_ENV`
2
+ // bare — the npm convention where the consumer bundler statically replaces
3
+ // the member expression (vite `define`; see xplatNodeEnvDefine). tsc still
4
+ // needs the name declared in a program with no Node types. Kept narrow on
5
+ // purpose: `process.env.FOO` reads stay type errors because the bundler
6
+ // define cannot rewrite them either.
7
+ declare const process: { env: { NODE_ENV?: string } }
@@ -14,5 +14,5 @@
14
14
  "skipLibCheck": true
15
15
  },
16
16
  "include": ["src/**/*"],
17
- "exclude": ["node_modules", "src/index.ts"]
17
+ "exclude": ["node_modules", "src/main.ts"]
18
18
  }
@@ -1,12 +1,14 @@
1
1
  import { defineConfig } from 'vite'
2
2
  import { octane } from '@octanejs/vite-plugin'
3
+ import { xplatBoundary, xplatNodeEnvDefine } from '@octane-xplat/cli/vite'
3
4
 
4
5
  // The web build. For native (iOS/Android), the same source runs through
5
6
  // @nativescript-community/vite-octane with `conditions: ['native']`, the
6
7
  // .ios/.android/.mobile suffix chain, and the unsuffixed native default — see
7
8
  // the octane-xplat repo's apps/mobile/vite.config.mts.
8
- export default defineConfig({
9
- plugins: [...octane()],
9
+ export default defineConfig(({ mode }) => ({
10
+ plugins: [...octane(), xplatBoundary('web')],
11
+ define: xplatNodeEnvDefine(mode),
10
12
  server: { port: 5200 },
11
13
  resolve: {
12
14
  conditions: ['web'],
@@ -25,4 +27,4 @@ export default defineConfig({
25
27
  '.json',
26
28
  ],
27
29
  },
28
- })
30
+ }))
@@ -1,128 +0,0 @@
1
- #include <metal_stdlib>
2
- #include <SwiftUI/SwiftUI_Metal.h>
3
- using namespace metal;
4
-
5
- // Stitchable shaders for SwiftUI's `.distortionEffect` / `.colorEffect` (iOS 17+).
6
- // SwiftUI supplies the leading parameters — `position` for a distortion, plus
7
- // `color` for a colour effect — and appends the arguments passed at the call
8
- // site in `OctaneLogo.swift`.
9
-
10
- /// Rising heat haze.
11
- ///
12
- /// A distortion shader maps a destination pixel back to the source pixel it
13
- /// should sample, so the returned offset reads as the image bending. Amplitude
14
- /// is weighted by `base^2` (1 at the bottom, 0 at the top) so the mark shimmers
15
- /// like air over hot metal instead of wobbling as a rigid block, and the two
16
- /// waves use incommensurate frequencies so the pattern never visibly repeats.
17
- [[ stitchable ]] float2 octaneHeatHaze(float2 position, float time, float2 size, float intensity) {
18
- float2 uv = position / size;
19
- float base = 1.0 - uv.y;
20
- float wave = sin(uv.y * 26.0 - time * 3.1) * 0.6
21
- + sin(uv.y * 11.0 - time * 1.9 + uv.x * 4.0) * 0.4;
22
- float amp = 2.4 * base * base * intensity;
23
- return position + float2(wave * amp, 0.0);
24
- }
25
-
26
- /// Ember sheen sweeping diagonally across the ink.
27
- ///
28
- /// SwiftUI hands colour effects premultiplied alpha, so the colour has to be
29
- /// un-premultiplied before mixing and re-premultiplied on the way out —
30
- /// blending straight into premultiplied RGB would bleed the highlight into the
31
- /// transparent surround and halo the glyph edges.
32
- [[ stitchable ]] half4 octaneSheen(float2 position, half4 color, float time, float2 size, float intensity) {
33
- if (color.a < 0.004h) {
34
- return color;
35
- }
36
- half3 rgb = color.rgb / color.a;
37
-
38
- float2 uv = position / size;
39
- float sweep = fract(time * 0.22);
40
- float d = (uv.x * 0.85 + (1.0 - uv.y) * 0.35) - (sweep * 1.8 - 0.4);
41
- float band = exp(-(d * d) * 42.0);
42
-
43
- half3 ember = half3(1.0h, 0.255h, 0.353h); // the logo's #FF415A accent
44
- half3 gold = half3(1.0h, 0.72h, 0.36h);
45
- half3 hot = mix(ember, gold, half(band));
46
-
47
- half glow = half(band) * half(intensity);
48
- rgb = mix(rgb, hot, glow * 0.9h);
49
- rgb += half3(glow * 0.25h);
50
-
51
- return half4(saturate(rgb) * color.a, color.a);
52
- }
53
-
54
- /// Per-shard pseudo-random pair. Deterministic in the cell index, so a shard
55
- /// keeps the same trajectory for the whole burst instead of jittering per frame.
56
- static inline float2 shardNoise(float2 cell) {
57
- float3 a = fract(float3(cell.xyx) * float3(0.1031, 0.1030, 0.0973));
58
- a += dot(a, a.yzx + 33.33);
59
- return fract(float2((a.x + a.y) * a.z, (a.x + a.z) * a.y));
60
- }
61
-
62
- /// Ignition burst: the mark shatters outward and reassembles.
63
- ///
64
- /// A layer effect (rather than a distortion) because each shard has to read
65
- /// from somewhere else in the image: sampling `position - offset` with an
66
- /// offset that is constant across a grid cell translates that whole cell as a
67
- /// rigid chip, which is what reads as breaking apart. A distortion shader can
68
- /// only nudge its own pixel, so it smears instead of shattering.
69
- ///
70
- /// `progress` runs 0 -> 1 once per burst and the displacement rides
71
- /// `sin(p * pi)`, so the shards fly out and land back exactly where they
72
- /// started — no separate reassembly pass, and the mark cannot get stranded
73
- /// mid-explosion if a frame is dropped.
74
- [[ stitchable ]] half4 octaneShatter(float2 position, SwiftUI::Layer layer, float2 size, float progress) {
75
- if (progress <= 0.0001) {
76
- return layer.sample(position);
77
- }
78
-
79
- float p = clamp(progress, 0.0, 1.0);
80
- float fly = sin(p * M_PI_F);
81
- float ease = fly * fly;
82
-
83
- float2 cells = float2(22.0, 5.0);
84
- float2 cellSize = size / cells;
85
- float2 rnd = shardNoise(floor(position / cellSize));
86
-
87
- float2 centre = size * 0.5;
88
- float2 radial = (position - centre) / max(size.x * 0.5, 1.0);
89
- float2 dir = radial + (rnd - 0.5) * 0.9;
90
- float len = max(length(dir), 0.0001);
91
- dir /= len;
92
-
93
- // Capped so the outermost shards stay inside the host view; anything
94
- // travelling further is clipped by its bounds rather than fading out.
95
- float2 offset = dir * mix(22.0, 70.0, rnd.x) * ease;
96
- offset.y += (rnd.y - 0.5) * 42.0 * ease;
97
- float2 src = position - offset;
98
-
99
- // Chromatic split along the flight path — the shards read as moving fast
100
- // rather than merely being displaced.
101
- float split = ease * 3.5;
102
- half4 cr = layer.sample(src + dir * split);
103
- half4 cg = layer.sample(src);
104
- half4 cb = layer.sample(src - dir * split);
105
- half alpha = max(max(cr.a, cg.a), cb.a);
106
- if (alpha < 0.004h) {
107
- return half4(0.0h);
108
- }
109
- half3 rgb = half3(cr.r, cg.g, cb.b) / alpha;
110
-
111
- // Expanding shockwave ring, brightest just after the tap.
112
- float ring = exp(-pow((length(radial) - p * 1.5) * 5.0, 2.0)) * (1.0 - p);
113
-
114
- half3 ember = half3(1.0h, 0.255h, 0.353h);
115
- half3 gold = half3(1.0h, 0.78h, 0.35h);
116
- half heat = half(ease);
117
- // Ember-leaning, and only a partial mix: pushing all the way to `hot` turns
118
- // every shard the same flat gold and the mark stops being recognisable
119
- // mid-flight. Holding some ink back keeps them reading as burning pieces
120
- // of the logo.
121
- half3 hot = mix(ember, gold, heat * 0.75h);
122
-
123
- rgb = mix(rgb, hot, heat * 0.6h);
124
- rgb += half3(heat * 0.18h * half(rnd.x) + half(ring) * 0.45h);
125
-
126
- alpha *= 1.0h - heat * 0.2h;
127
- return half4(saturate(rgb) * alpha, alpha);
128
- }
@@ -1,31 +0,0 @@
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
- }
@@ -1,16 +0,0 @@
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`;
@@ -1,41 +0,0 @@
1
- diff --git a/dist/server/strategy.js b/dist/server/strategy.js
2
- index 7d84b37e2ed3a026da5b31c8a11027e0580877b2..f6594a913ba64837135e994cb183add13c6a0530 100644
3
- --- a/dist/server/strategy.js
4
- +++ b/dist/server/strategy.js
5
- @@ -14,7 +14,9 @@ import { purgeTransformCachesForHotUpdate, runHotUpdatePrologue, typescriptServe
6
- * otherwise the re-fetch can be served from the previous save's transform and
7
- * the screen ends up one save behind.
8
- */
9
- -const SCRIPT_FILE_RE = /\.(?:[mc]?[jt]sx?)$/i;
10
- +// `.tsrx` is Octane's component extension — without it the deferred delta is
11
- +// never emitted and component edits silently never reach the device.
12
- +const SCRIPT_FILE_RE = /\.(?:[mc]?[jt]sx?|tsrx)$/i;
13
- export const octaneServerStrategy = {
14
- ...typescriptServerStrategy,
15
- flavor: 'octane',
16
- @@ -24,7 +26,7 @@ export const octaneServerStrategy = {
17
- if (!state)
18
- return;
19
- const { root, metrics, emitSummary } = state;
20
- - const { moduleGraph, verbose, sharedTransformRequest } = deps;
21
- + const { moduleGraph, verbose, sharedTransformRequest, wss, isSocketClientOpen } = deps;
22
- const { file, server } = ctx;
23
- if (!SCRIPT_FILE_RE.test(file)) {
24
- emitSummary();
25
- @@ -71,6 +73,16 @@ export const octaneServerStrategy = {
26
- const gm = moduleGraph.get(normalizedId);
27
- if (gm)
28
- moduleGraph.emitDelta([gm], []);
29
- + // The ns:hmr-delta broadcast IS the update delivery for this flavor —
30
- + // count open sockets so the always-on summary reflects reality
31
- + // instead of reporting recipients=0 for a delivered update.
32
- + try {
33
- + wss?.clients?.forEach((client) => {
34
- + if (isSocketClientOpen?.(client))
35
- + metrics.recipients += 1;
36
- + });
37
- + }
38
- + catch { }
39
- }
40
- catch (e) {
41
- if (verbose)