create-octane-xplat 0.5.0 → 0.6.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
@@ -23,3 +23,8 @@ pnpm typecheck # web + native tsconfigs
23
23
  ```
24
24
 
25
25
  Docs: [Running and checking an app](https://octane-xplat.goddardai.org/toolchain)
26
+
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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-octane-xplat",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Scaffold a minimal @octane-xplat/ui app — one Octane codebase for web + iOS + Android",
5
5
  "bin": {
6
6
  "create-octane-xplat": "./index.mjs"
@@ -17,7 +17,10 @@ below prevent that; `pnpm lint` enforces most of them.
17
17
  — `Foo.web.tsrx`, `Foo.native.tsrx`, `Foo.ios.tsrx`, `Foo.android.tsrx` —
18
18
  chosen by the bundler when something imports `./Foo`. Never branch on
19
19
  platform inside JSX (`Platform.OS`, conditional imports, `typeof
20
- document` checks).
20
+ document` checks). Platform-authentic widgets live behind
21
+ `@octane-xplat/ui/ios`, `/android`, and `/web` — importing a subpath
22
+ outside a matching suffix file fails the other platform's build (the
23
+ `xplat/platform-subpath-import` lint enforces it).
21
24
  2. **No DOM globals in shared code.** `document`, `window`, `localStorage`,
22
25
  DOM events — all web-only. Device capabilities (clipboard, storage,
23
26
  permissions, connectivity, geolocation, …) come from
@@ -55,12 +58,12 @@ below prevent that; `pnpm lint` enforces most of them.
55
58
 
56
59
  ## Verify
57
60
 
58
- | Command | Catches |
59
- | --- | --- |
60
- | `pnpm lint` | vocabulary / DOM-global / style violations — its output is authoritative |
61
- | `pnpm typecheck` | both web and native TS configs |
62
- | `pnpm dev` | web behavior |
63
- | `pnpm dev:ios` / `pnpm dev:android` | the platform where behavior actually differs |
61
+ | Command | Catches |
62
+ | ----------------------------------- | ------------------------------------------------------------------------ |
63
+ | `pnpm lint` | vocabulary / DOM-global / style violations — its output is authoritative |
64
+ | `pnpm typecheck` | both web and native TS configs |
65
+ | `pnpm dev` | web behavior |
66
+ | `pnpm dev:ios` / `pnpm dev:android` | the platform where behavior actually differs |
64
67
 
65
68
  ## Read next (only when needed)
66
69
 
@@ -74,4 +77,4 @@ below prevent that; `pnpm lint` enforces most of them.
74
77
  - Full framework docs, one file:
75
78
  https://octane-xplat.goddardai.org/llms-full.txt (index:
76
79
  https://octane-xplat.goddardai.org/llms.txt). The `/notes/*` design
77
- record explains *why* a constraint exists — rarely needed for app work.
80
+ record explains _why_ a constraint exists — rarely needed for app work.
@@ -1,49 +1,93 @@
1
- # Components — the shared vocabulary
2
-
3
- Import everything from `@octane-xplat/ui`. Each component has a web leaf
4
- (DOM) and a native leaf (NativeScript view) — the JSX is identical on both.
5
-
6
- | Need | Component(s) |
7
- | --- | --- |
8
- | Layout | `View` (aliased `Column`), `Row`, `Grid`, `Stack`, `Absolute`, `Spacer`, `SafeArea`, `KeyboardAvoiding` |
9
- | Text | `Text`, `Heading`, `RichText` + `RichTextSpan` |
10
- | Tap/hover | `Pressable`, `Link`, `NavLink`, `Hoverable` |
11
- | Input | `TextInput`, `TextArea`, `Switch`, `Slider` |
12
- | Collections | `List` |
13
- | Scrolling | `ScrollView`, `ScrollBox` |
14
- | Media | `Image`, `Icon` (`registerIcon`/`registerIcons` for glyphs) |
15
- | Feedback | `ActivityIndicator`, `Meter`, `showToast` |
16
- | Overlays | `Modal`/`openModal`, `Sheet`/`openSheet`/`closeSheet`, `Overlay`, `Popover`, `Drawer`, `openWindow` |
17
- | Shells | `Screen`, `Tabs` (+ `TabSpec`) |
18
- | Animation | `useAnimation` → `AnimatedValue` |
1
+ # Components — the vocabulary
2
+
3
+ Every shared component imports from `@octane-xplat/ui` and owns its pixels —
4
+ same props, same pixels on every target. Platform-authentic widgets (real
5
+ OS chrome) live behind `@octane-xplat/ui/ios` and `@octane-xplat/ui/android`
6
+ under their official OS names — those subpaths resolve only in native
7
+ builds and are the honest opt-in for "I want the platform widget." A shared
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.
10
+
11
+ ## Shared — `@octane-xplat/ui` (self-drawn, same pixels everywhere)
12
+
13
+ | Need | Component(s) |
14
+ | ------------- | ----------------------------------------------------------------------------------------------- |
15
+ | Layout | `View` (aliased `Column`), `Row`, `Grid`, `Stack`, `Absolute`, `Spacer`, `SafeArea` |
16
+ | Text | `Text`, `Heading`, `RichText` + `RichTextSpan` |
17
+ | Tap | `Pressable`, `Link`, `NavLink` |
18
+ | Input | `TextInput`, `TextArea`, `Switch`, `Slider` |
19
+ | Scrolling | `ScrollView`, `ScrollBox` |
20
+ | Media | `Image`, `Icon` (`registerIcon`/`registerIcons` for glyphs) |
21
+ | Feedback | `ActivityIndicator`, `Meter`, `showToast` |
22
+ | Overlays | `Sheet`/`openSheet`/`closeSheet`, `Overlay`, `Popover`, `Drawer`, `openWindow` |
23
+ | Shells | `Screen`, `Tabs` (+ `TabSpec`) |
24
+ | Animation | `useAnimation` → `AnimatedValue` |
19
25
  | Theme/measure | `useColorScheme`/`getColorScheme`, `useSafeAreaInsets`, `useMeasure`, `styled()`, `createStore` |
20
26
 
27
+ `Switch`, `Slider`, `ActivityIndicator`, `Tabs`, and `Drawer` are drawn by
28
+ the framework — the OS widget is deliberately not what you get. For a list,
29
+ compose `ScrollView` + mapped children; there is no shared recycled list.
30
+
31
+ ## Normalized OS widgets (shared, unavoidable platform controls)
32
+
33
+ `TextInput`, `TextArea`, `ScrollView`/`ScrollBox` render through the real
34
+ OS text/scroll controls — self-drawing them would lose IME, secure entry,
35
+ selection, and autofill. The framework resets their chrome (no platform
36
+ underline/border/padding, normalized font/color/placeholder/focus) so the
37
+ same `className`/`style` lands on the same box. What stays platform-native
38
+ by design: caret and selection UI, autocorrect/IME toolbars, keyboard
39
+ types, scroll physics and overscroll.
40
+
41
+ ## Platform-authentic — `@octane-xplat/ui/ios`
42
+
43
+ `UISwitch`, `UISlider`, `UIActivityIndicatorView`, `UITableView`,
44
+ `UITabBar`, `UIModal` (+ `openModal`), `SideDrawer` (ui-drawer edge-gesture
45
+ drawer), `LiquidGlass` + `LiquidGlassContainer` (iOS 26+ UIGlassEffect,
46
+ inert layout below it).
47
+
48
+ ## Platform-authentic — `@octane-xplat/ui/android`
49
+
50
+ `MaterialSwitch`, `SeekBar`, `CircularProgressIndicator`, `RecyclerView`,
51
+ `BottomNavigationView`, `MaterialDialog` (+ `openModal`), `DrawerLayout`.
52
+
53
+ ## Platform-only shared exports
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.
58
+
21
59
  ## Divergences that matter
22
60
 
23
61
  - `ScrollBox` is a **name trap**: on native it's a plain, non-scrolling
24
- container (a `List` inside it owns scrolling). Use `ScrollView` when you
25
- actually want a scroller.
26
- - `List` is not virtualized on web. On native it must not sit inside a
27
- `ScrollView` — two scrollers fight and the framework throws.
62
+ container (a platform list inside it owns scrolling). Use `ScrollView`
63
+ when you actually want a scroller.
64
+ - A `UITableView`/`RecyclerView` must not sit inside a `ScrollView` on
65
+ native — two scrollers fight and the framework throws. Wrap it in
66
+ `ScrollBox` instead.
28
67
  - `TextArea` `onSubmit`: web fires on Cmd/Ctrl+Enter; native fires only
29
68
  when `returnKeyType` is `"done"` or `"send"` — otherwise every newline
30
69
  reports as a submit.
31
- - `Modal` on Android: a non-fullscreen modal shows as a centered dialog,
32
- not a bottom sheet. Context and theme don't cross overlay roots — read
33
- them inside the overlay or pass values down.
70
+ - `MaterialDialog` is a centered dialog when non-fullscreen on Android;
71
+ `UIModal` is a form sheet there. Context and theme don't cross modal
72
+ roots — read them inside the modal or pass values down.
34
73
  - `Sheet`/`openSheet` is a bottom-anchored sheet on both platforms; on web
35
74
  it renders an in-window portal layer over the app.
36
- - `Drawer` has an edge-swipe gesture on native only — give web users a
37
- visible toggle.
75
+ - The shared `Drawer` is self-drawn: a left panel + backdrop on every
76
+ platform (backdrop fires `onDismiss`). The edge-gesture plugin widget is
77
+ `DrawerLayout`/`SideDrawer` in the platform subpaths.
38
78
  - `RichTextSpan` children carry their own `className`/`style`/`onPress`;
39
79
  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.
40
84
  - Accessibility props (`accessible`, label, hint, role, state, live
41
85
  region) share names across targets; native translates roles such as
42
86
  `heading` → `header`.
43
87
 
44
88
  ## Things that look right but aren't
45
89
 
46
- - `PlatformBadge` is an internal probe, not app vocabulary — don't use it.
47
- - `keyFor` on `List` is web-only.
90
+ - There is no shared `Modal`/`openModal` or `List` — import the platform
91
+ names from the subpaths, or compose `Sheet`/`ScrollView`.
48
92
  - Route `params` must be scalar (see navigation reference) — objects work
49
93
  on native but are silently dropped on web.
@@ -21,7 +21,7 @@ call sites drive both.
21
21
 
22
22
  ## Known gap
23
23
 
24
- Pushing into a *named* stack is iOS-only until
24
+ Pushing into a _named_ stack is iOS-only until
25
25
  [NativeScript#11444](https://github.com/NativeScript/NativeScript/issues/11444)
26
26
  lands — `pushRoute` there warns loudly on Android instead of dropping
27
27
  silently. Root-stack navigation works on both platforms.
@@ -32,7 +32,7 @@ shared.
32
32
 
33
33
  Leaf rules:
34
34
 
35
- - Inside a leaf, platform APIs are *expected*: DOM globals in `.web.*`,
35
+ - Inside a leaf, platform APIs are _expected_: DOM globals in `.web.*`,
36
36
  `@nativescript/*` imports in `.native.*`. The lint rules key off the
37
37
  suffix — don't fight them with suppressions.
38
38
  - Never read app-level NativeScript globals (`Application.android.*`)
@@ -1,6 +1,6 @@
1
1
  # Styling
2
2
 
3
- Two channels, chosen by *when the value is known*:
3
+ Two channels, chosen by _when the value is known_:
4
4
 
5
5
  - `className` → CSS classes and design tokens. Use for anything static.
6
6
  - `style={{ ... }}` → inline object for runtime values: animated offsets,
@@ -1,7 +1,8 @@
1
1
  # AGENTS.md
2
2
 
3
3
  Octane xplat app — one TypeScript codebase for web + iOS + Android (Octane
4
- + NativeScript).
4
+
5
+ - NativeScript).
5
6
 
6
7
  Before writing code, read `.agents/skills/xplat/SKILL.md` (or invoke the
7
8
  `xplat` skill). Its invariants are enforced by `pnpm lint` — violations
@@ -36,8 +36,8 @@
36
36
  "@octane-xplat/lint": "^0.1.0",
37
37
  "@octanejs/vite-plugin": "0.1.58",
38
38
  "@tsrx/typescript-plugin": "0.4.7",
39
- "oxlint": "^1.74.0",
40
39
  "nativescript": "9.1.1",
40
+ "oxlint": "^1.74.0",
41
41
  "typescript": "5.9.3",
42
42
  "vite": "8.3.0"
43
43
  }
@@ -1,5 +1,5 @@
1
1
  import { useState } from 'octane';
2
- import { View, Row, Text, Pressable, List, useColorScheme } from '@octane-xplat/ui';
2
+ import { View, Row, Text, Pressable, ScrollView, useColorScheme } from '@octane-xplat/ui';
3
3
 
4
4
  const FEATURES = [
5
5
  'shared components',
@@ -28,7 +28,9 @@ export function App() @{
28
28
  <Pressable className="btn" onPress={() => setCount(count + 1)}><Text>+</Text></Pressable>
29
29
  </Row>
30
30
 
31
- <List items={FEATURES} renderItem={(f) => <Text className="chip">{f}</Text>} />
31
+ <ScrollView>
32
+ {FEATURES.map((f) => <Text className="chip">{f}</Text>)}
33
+ </ScrollView>
32
34
 
33
35
  <Pressable className="link" onPress={() => setOverride(!dark)}>
34
36
  <Text>{dark ? 'light' : 'dark'} mode (system: {scheme})</Text>