create-octane-xplat 0.7.3 → 0.10.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.
Files changed (30) hide show
  1. package/README.md +28 -8
  2. package/index.mjs +112 -14
  3. package/package.json +15 -8
  4. package/scaffold.mjs +531 -0
  5. package/template/.agents/skills/xplat/SKILL.md +109 -40
  6. package/template/.agents/skills/xplat/references/components.md +149 -96
  7. package/template/.agents/skills/xplat/references/navigation.md +81 -31
  8. package/template/.agents/skills/xplat/references/platform.md +74 -35
  9. package/template/.agents/skills/xplat/references/styling.md +80 -27
  10. package/template/AGENTS.md +1 -1
  11. package/template/package.json +11 -9
  12. package/template/pnpm-workspace.yaml +66 -33
  13. package/template/src/App.tsrx +17 -8
  14. package/template/src/process.d.ts +7 -0
  15. package/template/src/style.css +24 -6
  16. package/template/tsconfig.json +1 -1
  17. package/template/vite.config.native.mts +3 -2
  18. package/template/vite.config.ts +5 -3
  19. package/template/App_Resources/iOS/src/OctaneShaders.metal +0 -128
  20. package/template/App_Resources/iOS/src/ShaderEffectRegistration.swift +0 -31
  21. package/template/patches/@nativescript-community__octane@0.2.1.patch +0 -16
  22. package/template/patches/@nativescript-community__vite-octane@0.2.1.patch +0 -41
  23. package/template/patches/@nativescript__core@9.1.2.patch +0 -794
  24. package/template/patches/@nativescript__core@9.1.3-next.2.patch +0 -428
  25. package/template/patches/@nativescript__vite@8.0.11.patch +0 -569
  26. package/template/patches/@nativescript__vite@8.0.13.patch +0 -233
  27. package/template/patches/@tsrx__typescript-plugin@0.4.11.patch +0 -11
  28. package/template/patches/css@3.0.0.patch +0 -52
  29. package/template/patches/octane@0.6.3.patch +0 -1149
  30. /package/template/src/{index.ts → main.ts} +0 -0
@@ -1,45 +1,84 @@
1
- # Platform services + writing leaves
1
+ # Platform services and platform files
2
2
 
3
- ## `@octane-xplat/platform` — device services
3
+ Use `@octane-xplat/platform` for common device services such as storage,
4
+ clipboard, permissions, connectivity, app lifecycle, URLs, and screen size.
5
+ Clipboard operations can fail or be unavailable; show a useful result.
4
6
 
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`,
8
- `connectivity`, `useWindowSize`, `useAppState`, `useSafeAreaInsets`, deep
9
- links, clipboard, `secureStorage`, haptics, notifications, `announce`,
10
- `systemBars`, geolocation, permissions, media, files, share, `openUrl`,
11
- `openSettings`, biometrics.
7
+ ```tsrx
8
+ import { useState } from 'octane'
9
+ import { clipboard } from '@octane-xplat/platform'
10
+ import { Pressable, Text } from '@octane-xplat/ui'
12
11
 
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.
12
+ export function CopyTrip() {
13
+ const [message, setMessage] = useState('Copy trip link')
14
+ return <Pressable onPress={async () => {
15
+ const copied = await clipboard.writeText('https://example.com/trips/42')
16
+ setMessage(copied ? 'Copied' : 'Copy unavailable')
17
+ }}><Text>{message}</Text></Pressable>
18
+ }
19
+ ```
20
+
21
+ Features with additional native dependencies live in separate packages:
22
+ `@octane-xplat/share`, `files`, `media`, `biometrics`, `geolocation`,
23
+ `notifications`, `secure-storage`, and `haptics`. Install the package for the
24
+ feature you need. Biometrics, for example, reports `unsupported` on web;
25
+ check permission and availability before trying to verify someone.
16
26
 
17
- ## Leaf files — your own divergence
27
+ ```tsrx
28
+ import { useState } from 'octane'
29
+ import { biometrics } from '@octane-xplat/biometrics'
30
+ import { Pressable, Text } from '@octane-xplat/ui'
31
+
32
+ export function UnlockTrip() {
33
+ const [message, setMessage] = useState('Unlock trip')
34
+ return <Pressable onPress={async () => {
35
+ const status = await biometrics.ensure()
36
+ if (status !== 'granted' || !biometrics.impl) {
37
+ setMessage('Use your password instead')
38
+ return
39
+ }
40
+ const verified = await biometrics.impl.verify('Unlock your trip')
41
+ setMessage(verified ? 'Unlocked' : 'Try again')
42
+ }}><Text>{message}</Text></Pressable>
43
+ }
44
+ ```
18
45
 
19
- When a service doesn't exist yet or a visual behavior truly differs, split the
20
- file, not the JSX:
46
+ ## Write a platform file when behavior differs
47
+
48
+ Split the file when a feature needs browser or OS APIs. Import the component
49
+ without a suffix; the bundler selects the matching implementation.
21
50
 
22
51
  ```text
23
- src/parts/Scanner.tsrx native default
24
- src/parts/Scanner.web.tsrx browser implementation (camera via getUserMedia)
52
+ src/parts/Scanner.web.tsrx browser implementation
25
53
  src/parts/Scanner.mobile.tsrx shared iOS/Android implementation
26
- src/parts/Scanner.ios.tsrx iOS-only override
54
+ src/parts/Scanner.ios.tsrx iOS override
55
+ src/parts/Scanner.tsrx native default
56
+ ```
57
+
58
+ ```tsrx
59
+ import { Scanner } from './parts/Scanner'
60
+
61
+ export function ScanScreen() { return <Scanner /> }
62
+ ```
63
+
64
+ Native JSX files start with the NativeScript pragma on line 1. Import native
65
+ APIs from `@nativescript/core`, and read app lifecycle state inside the call
66
+ rather than during module initialization. This example belongs in
67
+ `Status.mobile.tsrx`:
68
+
69
+ ```tsrx
70
+ /** @jsxImportSource @nativescript-community/octane */
71
+ import { Application } from '@nativescript/core'
72
+ import { Pressable, Text } from '@octane-xplat/ui'
73
+
74
+ export function Status() {
75
+ return <Pressable onPress={() => console.log(Application.hasLaunched())}>
76
+ <Text>Check app status</Text>
77
+ </Pressable>
78
+ }
27
79
  ```
28
80
 
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
-
33
- Leaf rules:
34
-
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
- - Never read app-level NativeScript globals (`Application.android.*`)
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
- `/** @jsxImportSource @nativescript-community/octane */` on line 1.
43
- - Deep imports under `@nativescript/core/ui/*` bundle as a second module
44
- instance — import from `@nativescript/core` only.
45
- - `console.debug` doesn't exist on device — use `console.log`.
81
+ Keep browser globals and DOM events in `.web.*` files. Do not import a second
82
+ renderer or deep-import NativeScript UI modules. Use `console.log` for device
83
+ logging. See the [platform services guide](https://octane-xplat.goddardai.org/platform-services)
84
+ when choosing a service or fallback.
@@ -1,42 +1,95 @@
1
1
  # Styling
2
2
 
3
- Two channels, chosen by _when the value is known_:
3
+ Use classes for static styling and inline style objects for values that change
4
+ while the app runs. Put the CSS in `src/style.css` and the component in a
5
+ `.tsrx` file.
4
6
 
5
- - `className` → CSS classes and design tokens. Use for anything static.
6
- - `style={{ ... }}` → inline object for runtime values: animated offsets,
7
- measured sizes, state-driven opacity. Don't put static styles here.
7
+ ```css
8
+ .trip-card {
9
+ padding: 16px;
10
+ border-radius: 12px;
11
+ }
12
+ ```
8
13
 
9
- ## The portable subset
14
+ ```tsrx
15
+ import { View, Text } from '@octane-xplat/ui'
10
16
 
11
- Shared CSS compiles for both targets. The native build rewrites `px` →
12
- `dip` and warns on declarations it can't honor — `position: fixed`/`sticky`,
13
- `margin-*: auto`, `float`, `box-shadow`, `white-space: pre-wrap`. A warning
14
- means the property does nothing on native: restructure (flex alignment,
15
- `Absolute`, transforms) instead of suppressing it.
17
+ export function TripCard(props: { opacity: number }) {
18
+ return <View className="trip-card" style={{ opacity: props.opacity }}>
19
+ <Text>Your next adventure</Text>
20
+ </View>
21
+ }
22
+ ```
16
23
 
17
- NativeScript limits worth knowing before you design around them:
24
+ ## Shared CSS
18
25
 
19
- - A scroll container can't clip children to a parent's rounded corners —
20
- inset separators/padding instead.
21
- - CSS transforms reach native views only through the `transform:`
22
- shorthand — `transform: translateX(16) scaleX(0.95)`. For motion, prefer
23
- `useAnimation`, which composes this per platform.
26
+ Shared CSS compiles for web and native. Prefer flex layouts, padding, and
27
+ supported transforms. The native build warns when it drops declarations such
28
+ as `position: fixed`, `margin: auto`, or `box-shadow`; resolve the warning
29
+ before depending on that style.
30
+
31
+ ```css
32
+ .trip-card {
33
+ display: flex;
34
+ flex-direction: column;
35
+ padding: 16px;
36
+ }
37
+ .trip-actions {
38
+ display: flex;
39
+ flex-direction: row;
40
+ justify-content: center;
41
+ }
42
+ ```
24
43
 
25
44
  ## Tokens and dark mode
26
45
 
27
- `import '@octane-xplat/ui/theme/tokens.css'` (already in `main.web.tsrx`)
28
- provides color/spacing tokens and `--font-sans`/`--font-mono` fallbacks.
29
- The shipped utility classes (`flex-1`, `items-center`, `gap-*`, `rounded`,
30
- `text-*`, …) mirror Tailwind v4 names and scales — write them as you would
31
- Tailwind utilities.
46
+ The starter imports the framework's tokens. They provide shared layout rules; the starter
47
+ defines its app palette in `src/style.css`. Use the palette tokens in your CSS.
48
+
49
+ ```ts
50
+ import '@octane-xplat/ui/theme/tokens.css'
51
+ ```
52
+
53
+ ```css
54
+ .trip-card {
55
+ background-color: var(--color-surface);
56
+ color: var(--color-text);
57
+ }
58
+ ```
59
+
60
+ Use `useColorScheme()` to follow the system theme. Apply the matching class to
61
+ the root so the token colors follow the theme; a user preference can override
62
+ this in the starter's `src/App.tsrx`.
32
63
 
33
- Dark mode: `useColorScheme()` reads the system; the app's override pattern
34
- is in `src/App.tsrx` — hold `boolean | null` state, apply a `dark`/`light`
35
- class on the root, and let tokens switch underneath.
64
+ ```tsrx
65
+ import { View, Text, useColorScheme } from '@octane-xplat/ui'
66
+
67
+ export function ThemePreview() {
68
+ const scheme = useColorScheme()
69
+ return <View className={scheme === 'dark' ? 'dark' : 'light'}>
70
+ <Text>Ready for your next trip</Text>
71
+ </View>
72
+ }
73
+ ```
36
74
 
37
75
  ## Fonts
38
76
 
39
- Family names differ per target (web face name, iOS PostScript name,
40
- Android filename). Ship the font file in the app, register per platform,
41
- and set the `--font-*` tokens in `style.css`. See the styling guide at
77
+ System fonts are the default. Add a custom font when your app needs one.
78
+
79
+ `pnpm xplat fonts add <file>` registers a font everywhere: stages a `.ttf`
80
+ into `src/fonts` (iOS/Android pick it up from there; `.woff`/`.woff2` inputs
81
+ are converted), writes the web `@font-face` into `src/fonts.css`, and wires
82
+ `--font-sans` (or another token via `--token mono|none|<name>`) in
83
+ `style.css`. Fontsource packages work too —
84
+ `pnpm xplat fonts add @fontsource-variable/inter --install` imports the
85
+ package's own CSS on web and stages its converted files natively (upright
86
+ faces only — italic files are skipped). Variable
87
+ fonts carry their weight range — `font-weight` on `Text` selects the matching
88
+ instance. Family names differ per target; the command reads them from the
89
+ file itself. See the styling guide at
42
90
  https://octane-xplat.goddardai.org/styling for the per-target table.
91
+
92
+ ```sh
93
+ pnpm xplat fonts add ./Inter.ttf
94
+ pnpm xplat fonts add @fontsource-variable/inter --install
95
+ ```
@@ -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.3",
24
- "@octane-xplat/ui": "^0.7.3",
23
+ "@octane-xplat/effects": "^0.10.0",
24
+ "@octane-xplat/platform": "^0.10.0",
25
+ "@octane-xplat/ui": "^0.10.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.3",
35
- "@octane-xplat/lint": "^0.7.3",
34
+ "@nativescript/vite": "8.0.17",
35
+ "@octane-xplat/cli": "^0.10.0",
36
+ "@octane-xplat/lint": "^0.10.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,25 @@ 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. Makes universal useImperativeHandle
29
+ # honor optional refs, ref replacement, nested ref arrays, and callback cleanup. Also
30
+ # allows async and generator helpers that are not components in universal compiler
31
+ # modules, while preserving errors when those functions are used as components
32
+ # (octanejs/octane#1469). Adds renderer-owned Children.toArray/map for transparent
33
+ # deferred JSX groups, plan ranges/slots and arrays, preserving keyed wrapper identity
34
+ # while leaving scoped owners intact.
35
+ octane@0.6.3: node_modules/.pnpm-config/@octane-xplat/patches/patches/octane@0.6.3.patch
25
36
  # FlexboxLayout getChildMeasureSpec subtracts the child's own padding where AOSP subtracts
26
37
  # parent padding + child margins — a column-stretched row re-measurement clamps padded
27
38
  # children (37→17 dip, squished controls). Adds squircle (continuous) corner clipping for
@@ -35,45 +46,67 @@ patchedDependencies:
35
46
  # frames whose ancestors present them (Frame.topmost stops returning hidden preloaded tab
36
47
  # frames). Also maps CSS font-weight to the OpenType wght axis for custom Android fonts on
37
48
  # API 26+, preserves explicit variation settings, and falls back to the prior file load
38
- # when the axis is unsupported.
39
- "@nativescript/core@9.1.2": patches/@nativescript__core@9.1.2.patch
49
+ # when the axis is unsupported. Carries NativeScript PR #11481: preserves percentage
50
+ # translate values through parsing and resolves each axis against the view's own
51
+ # dimensions, including native views and animations.
52
+ "@nativescript/core@9.1.2": node_modules/.pnpm-config/@octane-xplat/patches/patches/@nativescript__core@9.1.2.patch
53
+ # @nativescript/types@9.0.0 omits ambient declarations for the native URL and
54
+ # URLSearchParams globals, so projects without DOM libraries cannot typecheck valid native
55
+ # URL code. Adds runtime-aligned declarations that merge with DOM declarations when both
56
+ # libraries are present.
57
+ "@nativescript/types@9.0.0": node_modules/.pnpm-config/@octane-xplat/patches/patches/@nativescript__types@9.0.0.patch
40
58
  # @nativescript/vite serializes bundled CSS and dev app.css through reworkcss `css`@3.0.0,
41
59
  # which mis-parses @layer into garbage selectors — rules die before reaching
42
60
  # _populateRules. Adds atlayer() covering block and statement forms. Applies to a
43
61
  # transitive dep — no app declares it directly.
44
- css@3.0.0: patches/css@3.0.0.patch
45
- # Dev-server /ns/m fixes: resolve pnpm-isolated transitive deps and platform-suffixed
46
- # entries, inline workspace CSS through /@fs (including the node:path import required to
47
- # resolve workspace files), route vendor-excluded plugins through HTTP, decode bracketed
48
- # paths, and ignore comments during export-star expansion. Deps-bundle alias: the dev
49
- # payload's esbuild passes never see vite resolve.alias, so a dep importing bare `octane`
50
- # bundles the DOM runtime beside the universal one (hooks crash on boot, octane-xplat#3) —
51
- # an onResolve plugin redirects `octane` → `octane/universal/native`. tsconfig-paths
52
- # resolver also probes the .mobile suffix tier (ios/android) between the os qualifier and
53
- # unsuffixed default — .mobile-only modules behind paths-mapped imports failed bundle-mode
54
- # resolution.
55
- "@nativescript/vite@8.0.11": patches/@nativescript__vite@8.0.11.patch
62
+ css@3.0.0: node_modules/.pnpm-config/@octane-xplat/patches/patches/css@3.0.0.patch
63
+ # Dev-server fixes still missing after @nativescript/vite 8.0.17 added percent decoding
64
+ # for path-style /ns/m and Vue /ns/sfc requests (NativeScript#11483): collect re-export
65
+ # specifiers in the .vite/deps rewrite, route /@fs paths outside both roots through /ns/m
66
+ # (linked checkouts bypass the device transform upstream), and probe the non-active
67
+ # platform + `native` suffix tags in module candidates. The deps-bundle esbuild passes
68
+ # ignore vite resolve.alias, so a dep importing bare `octane` bundles the DOM runtime
69
+ # beside the universal one (hooks crash on boot, octane-xplat#3) — an onResolve plugin
70
+ # redirects `octane` → `octane/universal/native`. tsconfig-paths and the generated
71
+ # build-time checker moduleSuffixes also need the `.mobile` tier for iOS/Android/visionOS
72
+ # so `.mobile` leaves resolve under mapped imports and `ns build`. Served `/node_modules/`
73
+ # tails now also scan pnpm's `.pnpm` store and `link:`/`file:`/`portal:` dep roots — a
74
+ # linked package's transitive deps (e.g. @iconify/utils under @octane-xplat/icons) resolve
75
+ # nowhere else and 404 the dev session.
76
+ "@nativescript/vite@8.0.17": node_modules/.pnpm-config/@octane-xplat/patches/patches/@nativescript__vite@8.0.17.patch
56
77
  # Driver setProp skips `undefined` writes — NativeScript native setters coerce it (e.g.
57
78
  # editable → ios.userInteractionEnabled = NO), leaving dead UI that still has its JS
58
- # listeners.
59
- "@nativescript-community/octane@0.2.1": patches/@nativescript-community__octane@0.2.1.patch
79
+ # listeners. Aligns intrinsic ref types with Octane callback cleanup and nested arrays
80
+ # with optional entries. Advertises the implemented visibility capability so committed
81
+ # suspense bodies can be retained while pending.
82
+ "@nativescript-community/octane@0.2.4": node_modules/.pnpm-config/@octane-xplat/patches/patches/@nativescript-community__octane@0.2.4.patch
60
83
  # octaneServerStrategy's SCRIPT_FILE_RE only covers [jt]sx? — .tsrx edits never emit the
61
84
  # deferred ns:hmr-delta broadcast, so component HMR silently never reaches the device.
62
85
  # Also counts open /ns-hmr sockets so the update summary stops reporting recipients=0 for
63
86
  # delivered updates.
64
- "@nativescript-community/vite-octane@0.2.1": patches/@nativescript-community__vite-octane@0.2.1.patch
65
- # Same dev-server fixes as the 8.0.11 patch, ported to the @11468 pkg.pr.new preview the
66
- # windows target uses (apps/windows): /ns/m re-export records, vendor-manifest gating for
67
- # plugin-patterned packages, percent-decoded specifiers, and the .mobile-aware
68
- # tsconfig-paths probing.
69
- "@nativescript/vite@8.0.13": patches/@nativescript__vite@8.0.13.patch
70
- # Windows spike: the shared-file subset of the 9.1.2 patch — @layer cascade machinery
71
- # (css-tree parser, css-selector, style-scope), the #11446 frame/tab-view common fixes,
72
- # and the flexbox common measure-spec — applied to the @11468 preview. iOS/Android
73
- # impl-file hunks are dead code on windows and were dropped.
74
- "@nativescript/core@9.1.3-next.2": patches/@nativescript__core@9.1.3-next.2.patch
87
+ "@nativescript-community/vite-octane@0.2.1": node_modules/.pnpm-config/@octane-xplat/patches/patches/@nativescript-community__vite-octane@0.2.1.patch
75
88
  # Enables `resolveHiddenExtensions` on the language plugin's typescript facet so
76
89
  # extensionless specifiers (e.g. './Text') probe `.tsrx` after moduleSuffixes — required
77
90
  # for a single extensionless shared barrel to resolve per-platform leaves under tsrx-tsc
78
91
  # and the IDE plugin.
79
- "@tsrx/typescript-plugin@0.4.11": patches/@tsrx__typescript-plugin@0.4.11.patch
92
+ "@tsrx/typescript-plugin@0.4.11": node_modules/.pnpm-config/@octane-xplat/patches/patches/@tsrx__typescript-plugin@0.4.11.patch
93
+ # linkifyjs declares /\p{L}/u and /\p{Emoji}/u as regex literals at module scope;
94
+ # NativeScript's embedded V8 builds without ICU, so the literals throw SyntaxError at
95
+ # parse time and poison the whole bundle (any bundled app importing @tiptap/extension-link
96
+ # dies on boot). Rewrites them as try/catch new RegExp builds with ASCII-range fallbacks —
97
+ # same behavior where ICU exists, graceful degradation where it doesn't.
98
+ linkifyjs@4.3.3: node_modules/.pnpm-config/@octane-xplat/patches/patches/linkifyjs@4.3.3.patch
99
+ # Two bundler-visible issues: useEditor.ts reads bare process.env.NODE_ENV (no node global
100
+ # in the harness typecheck program) and './Context' resolves to Context.tsrx — a
101
+ # self-import — under suffix-first resolution, so EditorContext/useCurrentEditor were
102
+ # unreachable. Adds a module-local process declaration for typecheck and merges the
103
+ # Context.ts definitions into Context.tsrx.
104
+ "@octanejs/tiptap@0.0.51": node_modules/.pnpm-config/@octane-xplat/patches/patches/@octanejs__tiptap@0.0.51.patch
105
+ # @lexical/link declares a /\p{L}\p{N}/u URL matcher as a regex literal at module scope;
106
+ # NativeScript's embedded V8 builds without ICU, so the literal throws SyntaxError at
107
+ # parse time and poisons the whole bundle (any bundled app importing @lexical/link dies on
108
+ # boot). Same fix as linkifyjs: try/catch new RegExp with an ASCII-range fallback — the
109
+ # matcher only feeds autolink, which native doesn't use.
110
+ "@lexical/link@0.51.0": node_modules/.pnpm-config/@octane-xplat/patches/patches/@lexical__link@0.51.0.patch
111
+ configDependencies:
112
+ "@octane-xplat/patches": 0.10.0
@@ -1,5 +1,6 @@
1
1
  import { useState } from 'octane';
2
- import { View, Row, Text, Pressable, ScrollView, useColorScheme } from '@octane-xplat/ui';
2
+ import { View, HStack, Text, Pressable, ScrollableArea, 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
 
@@ -22,17 +27,21 @@ export function App() @{
22
27
  <Text className="title">octane-xplat</Text>
23
28
  <Text className="muted">one codebase — web · iOS · android</Text>
24
29
 
25
- <Row className="counter">
30
+ <HStack className="counter">
26
31
  <Pressable className="btn" onPress={() => setCount(count - 1)}><Text>−</Text></Pressable>
27
32
  <Text className="count">{count}</Text>
28
33
  <Pressable className="btn" onPress={() => setCount(count + 1)}><Text>+</Text></Pressable>
29
- </Row>
34
+ </HStack>
30
35
 
31
- <ScrollView>
36
+ <ScrollableArea>
32
37
  {FEATURES.map((f) => <Text className="chip">{f}</Text>)}
33
- </ScrollView>
38
+ </ScrollableArea>
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 } }
@@ -1,7 +1,21 @@
1
- /* App chrome — everything here is inside the web∩native CSS grammar
2
- * (see docs/css-support-matrix.md in the framework repo). Design tokens
3
- * (--color-*) come from @octane-xplat/ui/theme/tokens.css; .dark re-themes
4
- * the same vars — no JS color values anywhere. */
1
+ /* This starter leaves the optional UI chrome stylesheet out. Define this
2
+ * app's palette here; the required tokens.css supplies layout structure. */
3
+
4
+ :root,
5
+ .ns-root {
6
+ --color-primary: #171717;
7
+ --color-onprimary: #fafafa;
8
+ --color-surface: #ffffff;
9
+ --color-text: #0a0a0a;
10
+ }
11
+
12
+ .dark,
13
+ .ns-dark {
14
+ --color-primary: #ededed;
15
+ --color-onprimary: #171717;
16
+ --color-surface: #0a0a0a;
17
+ --color-text: #ededed;
18
+ }
5
19
 
6
20
  body {
7
21
  margin: 0;
@@ -9,10 +23,12 @@ body {
9
23
  background-color: var(--color-surface);
10
24
  }
11
25
 
12
- /* System-dark before JS mounts — no white flash. Same values tokens.css
13
- * sets under .dark; .app.light below is the manual override back. */
26
+ /* System-dark before JS mounts — no white flash. .app.light below is the
27
+ * manual override back. */
14
28
  @media (prefers-color-scheme: dark) {
15
29
  :root {
30
+ --color-primary: #ededed;
31
+ --color-onprimary: #171717;
16
32
  --color-surface: #0f172a;
17
33
  --color-text: #f8fafc;
18
34
  }
@@ -23,6 +39,8 @@ body {
23
39
 
24
40
  /* Manual light override when the system is dark (JS adds .light). */
25
41
  .app.light {
42
+ --color-primary: #171717;
43
+ --color-onprimary: #fafafa;
26
44
  --color-surface: #ffffff;
27
45
  --color-text: #111827;
28
46
  }
@@ -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
  }
@@ -3,6 +3,7 @@ import { xplatNative } from '@octane-xplat/cli/vite'
3
3
 
4
4
  // The shared preset owns the renderer rules, octane→universal/native alias,
5
5
  // suffix extension chain, deps-bundle plugin exclusions, the HMR watchdog,
6
- // and the px→dip CSS rewrite. App-specific additions go through the second
7
- // argument's `extra` (or mergeConfig).
6
+ // the px→dip CSS rewrite, and fs.allow widening for link:/file: dep roots.
7
+ // App-specific additions go through the second argument's `extra` (or
8
+ // mergeConfig).
8
9
  export default defineConfig(({ mode }) => xplatNative(mode))
@@ -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
+ }))