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 +5 -0
- package/package.json +1 -1
- package/template/.agents/skills/xplat/SKILL.md +11 -8
- package/template/.agents/skills/xplat/references/components.md +73 -29
- package/template/.agents/skills/xplat/references/navigation.md +1 -1
- package/template/.agents/skills/xplat/references/platform.md +1 -1
- package/template/.agents/skills/xplat/references/styling.md +1 -1
- package/template/AGENTS.md +2 -1
- package/template/package.json +1 -1
- package/template/src/App.tsrx +4 -2
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
|
@@ -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
|
-
|
|
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
|
|
59
|
-
|
|
|
60
|
-
| `pnpm lint`
|
|
61
|
-
| `pnpm typecheck`
|
|
62
|
-
| `pnpm dev`
|
|
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
|
|
80
|
+
record explains _why_ a constraint exists — rarely needed for app work.
|
|
@@ -1,49 +1,93 @@
|
|
|
1
|
-
# Components — the
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
|
14
|
-
|
|
|
15
|
-
|
|
|
16
|
-
|
|
|
17
|
-
|
|
|
18
|
-
|
|
|
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
|
|
25
|
-
actually want a scroller.
|
|
26
|
-
- `
|
|
27
|
-
|
|
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
|
-
- `
|
|
32
|
-
|
|
33
|
-
them inside the
|
|
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`
|
|
37
|
-
|
|
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
|
-
-
|
|
47
|
-
|
|
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
|
|
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
|
|
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
|
|
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,
|
package/template/AGENTS.md
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# AGENTS.md
|
|
2
2
|
|
|
3
3
|
Octane xplat app — one TypeScript codebase for web + iOS + Android (Octane
|
|
4
|
-
|
|
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
|
package/template/package.json
CHANGED
package/template/src/App.tsrx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useState } from 'octane';
|
|
2
|
-
import { View, Row, Text, Pressable,
|
|
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
|
-
<
|
|
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>
|