@stacksjs/defaults 0.70.370 → 0.70.375

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.
@@ -0,0 +1,154 @@
1
+ ---
2
+ name: stacks-mobile
3
+ description: Use when building native iOS or Android applications from a Stacks and STX codebase with Craft, including mobile configuration, native capabilities, safe areas, haptics, sharing, and mobile build output.
4
+ license: MIT
5
+ compatibility: Bun >= 1.3.0, TypeScript, Xcode for iOS project generation
6
+ allowed-tools: Read Edit Write Bash Grep Glob
7
+ ---
8
+
9
+ # Stacks Mobile
10
+
11
+ Stacks mobile applications reuse the same STX views, components, routes, and
12
+ API as the web application. Craft owns the platform project and native bridge;
13
+ Stacks owns application configuration and build orchestration.
14
+
15
+ ## Key paths
16
+
17
+ - App configuration: `config/mobile.ts`
18
+ - Mobile runtime: `storage/framework/core/mobile/src/`
19
+ - Platform build actions: `storage/framework/core/actions/src/build/ios.ts` and `build/android.ts`
20
+ - Reusable STX components: `storage/framework/defaults/resources/components/Native*.stx`
21
+ - Generated iOS project: `storage/framework/mobile/ios/` (ignored build output)
22
+
23
+ ## Build
24
+
25
+ ```bash
26
+ buddy build:ios
27
+ buddy build:android
28
+ ```
29
+
30
+ The build validates `config/mobile.ts`, initializes a Craft iOS project,
31
+ selects either a remote application URL or bundled web assets, generates the
32
+ Xcode project with xcodegen, and records source, capability, and Craft builder
33
+ revision provenance in `stacks-mobile.json`.
34
+
35
+ For local Craft development, point Stacks at Craft's builder source:
36
+
37
+ ```bash
38
+ CRAFT_IOS_SRC=/absolute/path/to/craft/packages/ios/src/index.ts buddy build:ios
39
+ CRAFT_ANDROID_SRC=/absolute/path/to/craft/packages/android/src/index.ts buddy build:android
40
+ ```
41
+
42
+ `STACKS_IOS_SKIP_XCODEGEN=1` and `STACKS_ANDROID_SKIP_GRADLE=1` are only for
43
+ source-level CI and tests. Shippable projects must be generated and compiled
44
+ with Xcode or Gradle respectively.
45
+
46
+ ## Configuration
47
+
48
+ ```ts
49
+ import type { MobileConfig } from '@stacksjs/types'
50
+
51
+ export default {
52
+ ios: {
53
+ appName: 'My App',
54
+ bundleId: 'com.example.app',
55
+ url: 'https://example.com',
56
+ fallbackWebAssets: 'dist',
57
+ deploymentTarget: '16.0',
58
+ orientations: ['portrait'],
59
+ urlSchemes: ['myapp'],
60
+ capabilities: {
61
+ haptics: true,
62
+ share: true,
63
+ geolocation: true,
64
+ secureStorage: true,
65
+ },
66
+ },
67
+ android: {
68
+ appName: 'My App',
69
+ packageName: 'com.example.app',
70
+ url: 'https://example.com',
71
+ fallbackWebAssets: 'dist',
72
+ capabilities: {
73
+ haptics: true,
74
+ share: true,
75
+ geolocation: true,
76
+ secureStorage: true,
77
+ },
78
+ },
79
+ } satisfies MobileConfig
80
+ ```
81
+
82
+ Choose exactly one primary content source:
83
+
84
+ - `url`: load the deployed Stacks application and keep server-rendered routes.
85
+ - `webAssets`: bundle a static distribution containing `index.html` and every
86
+ referenced asset.
87
+ - `fallbackWebAssets`: with `url`, bundle a static distribution that Craft
88
+ loads when the remote application is unreachable on cold start.
89
+
90
+ Only enable capabilities the product uses. Craft turns enabled capabilities
91
+ into native bridge availability and required iOS privacy descriptions.
92
+
93
+ ## Runtime API
94
+
95
+ ```ts
96
+ import { haptics, keepAwake, location, pushNotifications, share, withNativeFeedback } from '@stacksjs/mobile'
97
+
98
+ await haptics.selection()
99
+ const position = await location.getCurrentPosition({ enableHighAccuracy: true })
100
+ await location.startRecording({ enableHighAccuracy: true })
101
+ await keepAwake.enable()
102
+ const pushToken = await pushNotifications.register()
103
+ await share.share({ title: 'Route', url: 'https://example.com/routes/1' })
104
+ await withNativeFeedback(() => saveActivity())
105
+ ```
106
+
107
+ The runtime is browser-safe. Craft-backed operations use the native bridge;
108
+ supported web APIs provide fallback behavior outside a native host.
109
+
110
+ ## STX components
111
+
112
+ - `<NativeAppShell>` applies iOS safe-area insets and reserves tab-bar space.
113
+ - `<NativeTabBar>` provides the accessible navigation shell and selection haptics.
114
+ - `<NativeTabItem>` provides each route, active state, label, and Iconify icon.
115
+ - `<NativeShareButton>` opens the native share sheet and reports feedback.
116
+ - `<NativeNetworkBanner>` reflects native connectivity changes and announces offline state accessibly.
117
+ - `<NativePermissionButton>` wraps permission status, requests, haptics, and the native Settings escape hatch.
118
+ - `<NativeHealthButton>` requests the minimal Apple Health or Android Health Connect grants.
119
+
120
+ Use Iconify classes for tab icons. Keep native operations inside reusable
121
+ components or TypeScript composables, never through `window.*` in an STX
122
+ script.
123
+
124
+ ## Health and watch surfaces
125
+
126
+ Enable `healthKit` on iOS or `healthConnect` on Android, then use the shared
127
+ `health` service to request only the record types the product needs. Completed
128
+ recordings can be written back with `health.saveWorkout(...)`; treat permission
129
+ revocation as a normal runtime state and never block saving the application's
130
+ own activity when a health write fails.
131
+
132
+ Enable `watchApp` to generate and embed the SwiftUI watchOS companion. The
133
+ shared `watchConnectivity` service exchanges commands and the latest recording
134
+ context without exposing `WCSession` to STX templates. Set
135
+ `ios.watchDeploymentTarget` when the default watchOS 9.0 target is not suitable.
136
+
137
+ ## Validation
138
+
139
+ Before finishing mobile work:
140
+
141
+ ```bash
142
+ buddy lint
143
+ bun run typecheck:app
144
+ buddy test
145
+ buddy build:ios
146
+ buddy build:android
147
+ ```
148
+
149
+ On a Mac with full Xcode selected, compile the generated project for an iOS
150
+ Simulator (including embedded extensions and watchOS dependencies) and a
151
+ physical-device archive. Compile the Android project with Gradle when Android is
152
+ configured. Verify permission prompts, safe-area
153
+ layout, deep links, offline/error states, background transitions, and native
154
+ feedback on device.
@@ -50,7 +50,7 @@ export default defineModel({
50
50
  title: {
51
51
  fillable: true,
52
52
  required: true,
53
- validation: { rule: schema.string().maxLength(200) },
53
+ validation: { rule: schema.string().max(200) },
54
54
  factory: (faker) => faker.lorem.sentence()
55
55
  },
56
56
  content: {
@@ -104,8 +104,8 @@ export default defineModel({
104
104
  required: true,
105
105
  unique: false,
106
106
  validation: {
107
- rule: schema.string().maxLength(100),
108
- message: { maxLength: 'Name is too long' }
107
+ rule: schema.string().max(100),
108
+ message: { max: 'Name is too long' }
109
109
  },
110
110
  factory: (faker) => faker.lorem.word()
111
111
  },
@@ -121,8 +121,8 @@ import { schema } from '@stacksjs/validation'
121
121
  ```typescript
122
122
  // String types
123
123
  schema.string() // StringValidatorType
124
- schema.string().minLength(2) // chain: min length
125
- schema.string().maxLength(100) // chain: max length
124
+ schema.string().min(2) // chain: min length
125
+ schema.string().max(100) // chain: max length
126
126
  schema.string().email() // chain: must be email
127
127
  schema.string().url() // chain: must be URL
128
128
  schema.string().matches(/pattern/) // chain: regex match
@@ -189,10 +189,10 @@ export default defineModel({
189
189
  attributes: {
190
190
  name: {
191
191
  validation: {
192
- rule: schema.string().minLength(2).maxLength(100),
192
+ rule: schema.string().min(2).max(100),
193
193
  message: {
194
- minLength: 'Name must be at least 2 characters',
195
- maxLength: 'Name cannot exceed 100 characters',
194
+ min: 'Name must be at least 2 characters',
195
+ max: 'Name cannot exceed 100 characters',
196
196
  }
197
197
  }
198
198
  },
@@ -226,9 +226,9 @@ export default defineModel({
226
226
 
227
227
  bio: {
228
228
  validation: {
229
- rule: schema.string().maxLength(500),
229
+ rule: schema.string().max(500),
230
230
  message: {
231
- maxLength: 'Bio cannot exceed 500 characters',
231
+ max: 'Bio cannot exceed 500 characters',
232
232
  }
233
233
  }
234
234
  },
@@ -2,7 +2,7 @@
2
2
  "publisher": "Stacks",
3
3
  "name": "vscode-stacks",
4
4
  "displayName": "Stacks",
5
- "version": "0.70.370",
5
+ "version": "0.70.375",
6
6
  "description": "A modern Stacks development environment.",
7
7
  "license": "MIT",
8
8
  "funding": "https://github.com/sponsors/chrisbbreuer",
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/defaults",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.70.370",
5
+ "version": "0.70.375",
6
6
  "description": "The complete managed Stacks application scaffold, including runtime defaults, AI guidance, editor metadata, and npm-backed project support files.",
7
7
  "author": "Chris Breuer",
8
8
  "license": "MIT",
@@ -46,6 +46,7 @@
46
46
  "dependencies": {
47
47
  "@iconify-json/f7": "^1.2.2",
48
48
  "@iconify-json/hugeicons": "^1.2.27",
49
+ "@stacksjs/mobile": "^0.70.375",
49
50
  "@stacksjs/sanitizer": "^0.2.113"
50
51
  },
51
52
  "scripts": {
@@ -0,0 +1,75 @@
1
+ <script>
2
+ interface NativeAppShellProps {
3
+ class?: string
4
+ tabBar?: boolean
5
+ }
6
+
7
+ const { tabBar = false } = defineProps<NativeAppShellProps>()
8
+ </script>
9
+
10
+ <script client>
11
+ const className = useReactiveProp<string>('class', '')
12
+ </script>
13
+
14
+ <div data-native-app-shell class="native-app-shell" :class="className()">
15
+ <main class="native-app-content">
16
+ <slot />
17
+ </main>
18
+ @if(tabBar)
19
+ <footer class="native-app-tab-slot">
20
+ <slot name="tab-bar" />
21
+ </footer>
22
+ @endif
23
+ </div>
24
+
25
+ <style>
26
+ .native-app-shell {
27
+ --native-safe-area-top: env(safe-area-inset-top, 0px);
28
+ --native-safe-area-right: env(safe-area-inset-right, 0px);
29
+ --native-safe-area-bottom: env(safe-area-inset-bottom, 0px);
30
+ --native-safe-area-left: env(safe-area-inset-left, 0px);
31
+
32
+ min-height: 100dvh;
33
+ color: inherit;
34
+ background: inherit;
35
+ padding-top: var(--native-safe-area-top);
36
+ padding-right: var(--native-safe-area-right);
37
+ padding-left: var(--native-safe-area-left);
38
+ }
39
+
40
+ .native-app-shell::before {
41
+ content: '';
42
+ position: fixed;
43
+ top: 0;
44
+ right: 0;
45
+ left: 0;
46
+ z-index: 35;
47
+ height: var(--native-safe-area-top);
48
+ pointer-events: none;
49
+ background: var(--native-safe-area-background, Canvas);
50
+ }
51
+
52
+ .native-app-content {
53
+ min-height: calc(100dvh - var(--native-safe-area-top));
54
+ padding-bottom: var(--native-safe-area-bottom);
55
+ }
56
+
57
+ .native-app-shell:has(.native-app-tab-slot) .native-app-content {
58
+ padding-bottom: calc(5rem + var(--native-safe-area-bottom));
59
+ }
60
+
61
+ .native-app-sticky-top {
62
+ top: var(--native-safe-area-top);
63
+ }
64
+
65
+ .native-app-tab-slot {
66
+ position: fixed;
67
+ right: 0;
68
+ bottom: 0;
69
+ left: 0;
70
+ z-index: 30;
71
+ padding-right: var(--native-safe-area-right);
72
+ padding-bottom: var(--native-safe-area-bottom);
73
+ padding-left: var(--native-safe-area-left);
74
+ }
75
+ </style>
@@ -0,0 +1,49 @@
1
+ <script client>
2
+ import type { HealthDataType } from '@stacksjs/mobile'
3
+ import { device, haptics, health, isNativeMobile } from '@stacksjs/mobile'
4
+
5
+ const types = useReactiveProp<HealthDataType[]>('types', ['steps', 'distance', 'activeEnergy', 'heartRate', 'workouts'])
6
+ const className = useReactiveProp<string>('class', 'inline-flex min-h-11 items-center justify-center gap-2 rounded-xl px-4 text-sm font-semibold')
7
+ const status = state<'idle' | 'requesting' | 'connected' | 'denied' | 'unavailable' | 'error'>('idle')
8
+ const emit = defineEmits()
9
+
10
+ const provider = derived(() => device.isIOS() ? 'Apple Health' : device.isAndroid() ? 'Health Connect' : 'Health')
11
+ const buttonLabel = derived(() => {
12
+ if (status() === 'requesting') return `Connecting ${provider()}`
13
+ if (status() === 'connected') return `${provider()} connected`
14
+ if (status() === 'unavailable') return `${provider()} unavailable`
15
+ return `Connect ${provider()}`
16
+ })
17
+
18
+ async function connectHealth(): Promise<void> {
19
+ if (!isNativeMobile()) {
20
+ status.set('unavailable')
21
+ return
22
+ }
23
+ if (status() === 'requesting' || status() === 'connected') return
24
+ status.set('requesting')
25
+ await haptics.selection()
26
+ try {
27
+ const allowed = await health.requestAuthorization(types())
28
+ status.set(allowed ? 'connected' : 'denied')
29
+ emit('change', { connected: allowed, provider: provider() })
30
+ await haptics.notification(allowed ? 'success' : 'warning')
31
+ }
32
+ catch (error) {
33
+ status.set('error')
34
+ emit('error', error)
35
+ await haptics.notification('error')
36
+ }
37
+ }
38
+ </script>
39
+
40
+ <button
41
+ type="button"
42
+ :class="className()"
43
+ :disabled="status() === 'requesting' || status() === 'connected' || status() === 'unavailable'"
44
+ :aria-busy="status() === 'requesting' ? 'true' : 'false'"
45
+ @click="connectHealth()"
46
+ >
47
+ <i aria-hidden="true" class="h-5 w-5 i-hugeicons-health"></i>
48
+ <span>{{ buttonLabel() }}</span>
49
+ </button>
@@ -0,0 +1,53 @@
1
+ <script client>
2
+ import { network } from '@stacksjs/mobile'
3
+ import { onDestroy, onMount } from 'stx'
4
+
5
+ const className = useReactiveProp<string>('class', '')
6
+ const offlineLabel = useReactiveProp<string>('offlineLabel', 'You are offline. Changes will sync automatically.')
7
+ const connected = state(true)
8
+ let removeListener: (() => void) | null = null
9
+
10
+ function applyStatus(status: { isConnected: boolean }): void {
11
+ connected.set(status.isConnected)
12
+ }
13
+
14
+ onMount(() => {
15
+ void network.getStatus().then(applyStatus).catch(() => connected.set(false))
16
+ removeListener = network.onChange(applyStatus)
17
+ })
18
+
19
+ onDestroy(() => removeListener?.())
20
+ </script>
21
+
22
+ <div
23
+ :if="!connected()"
24
+ role="status"
25
+ aria-live="polite"
26
+ class="native-network-banner"
27
+ :class="className()"
28
+ >
29
+ <i aria-hidden="true" class="h-4 w-4 i-hugeicons-wifi-disconnected-01"></i>
30
+ <span>{{ offlineLabel() }}</span>
31
+ </div>
32
+
33
+ <style>
34
+ .native-network-banner {
35
+ position: sticky;
36
+ top: env(safe-area-inset-top, 0px);
37
+ z-index: 50;
38
+ display: flex;
39
+ gap: 0.5rem;
40
+ align-items: center;
41
+ justify-content: center;
42
+ min-height: 2.75rem;
43
+ padding: 0.625rem 1rem;
44
+ color: rgb(120 53 15);
45
+ background: rgb(254 243 199 / 0.96);
46
+ font-size: 0.8125rem;
47
+ font-weight: 600;
48
+ }
49
+
50
+ @media (prefers-color-scheme: dark) {
51
+ .native-network-banner { color: rgb(254 243 199); background: rgb(120 53 15 / 0.96); }
52
+ }
53
+ </style>
@@ -0,0 +1,61 @@
1
+ <script client>
2
+ import type { PermissionStatus, PermissionType } from '@stacksjs/mobile'
3
+ import { haptics, permissions } from '@stacksjs/mobile'
4
+ import { onMount } from 'stx'
5
+
6
+ const permission = useReactiveProp<PermissionType>('permission', 'location')
7
+ const label = useReactiveProp<string>('label', 'Allow access')
8
+ const grantedLabel = useReactiveProp<string>('grantedLabel', 'Access allowed')
9
+ const className = useReactiveProp<string>('class', 'inline-flex min-h-11 items-center justify-center gap-2 rounded-xl px-4 text-sm font-semibold')
10
+ const status = state<PermissionStatus>('undetermined')
11
+ const busy = state(false)
12
+ const emit = defineEmits()
13
+
14
+ onMount(() => {
15
+ void permissions.check(permission()).then(status.set).catch(() => status.set('undetermined'))
16
+ })
17
+
18
+ async function requestAccess(): Promise<void> {
19
+ if (busy() || status() === 'granted') return
20
+ busy.set(true)
21
+ await haptics.selection()
22
+ try {
23
+ const next = await permissions.request(permission())
24
+ status.set(next)
25
+ emit('change', next)
26
+ await haptics.notification(next === 'granted' ? 'success' : 'warning')
27
+ }
28
+ catch (error) {
29
+ emit('error', error)
30
+ await haptics.notification('error')
31
+ }
32
+ finally {
33
+ busy.set(false)
34
+ }
35
+ }
36
+
37
+ async function openNativeSettings(): Promise<void> {
38
+ await permissions.openSettings()
39
+ }
40
+ </script>
41
+
42
+ <div class="inline-flex flex-col gap-2">
43
+ <button
44
+ type="button"
45
+ :class="className()"
46
+ :disabled="busy() || status() === 'granted'"
47
+ :aria-busy="busy() ? 'true' : 'false'"
48
+ @click="requestAccess()"
49
+ >
50
+ <i aria-hidden="true" :class="status() === 'granted' ? 'i-hugeicons-checkmark-circle-02' : 'i-hugeicons-shield-01'" class="h-5 w-5"></i>
51
+ <span>{{ busy() ? 'Requesting access' : status() === 'granted' ? grantedLabel() : label() }}</span>
52
+ </button>
53
+ <button
54
+ :if="status() === 'denied' || status() === 'restricted'"
55
+ type="button"
56
+ class="min-h-11 text-sm underline underline-offset-4"
57
+ @click="openNativeSettings()"
58
+ >
59
+ Open device settings
60
+ </button>
61
+ </div>
@@ -0,0 +1,35 @@
1
+ <script client>
2
+ import { haptics, share } from '@stacksjs/mobile'
3
+
4
+ const title = useReactiveProp<string | undefined>('title', undefined)
5
+ const text = useReactiveProp<string | undefined>('text', undefined)
6
+ const url = useReactiveProp<string | undefined>('url', undefined)
7
+ const className = useReactiveProp<string>('class', 'inline-flex gap-2 items-center justify-center min-h-11 px-4 font-semibold text-sm rounded-xl')
8
+ const label = useReactiveProp<string>('label', 'Share')
9
+ const status = state<'idle' | 'sharing' | 'shared' | 'error'>('idle')
10
+
11
+ async function openShareSheet(): Promise<void> {
12
+ if (status() === 'sharing') return
13
+ status.set('sharing')
14
+ await haptics.impact('light')
15
+ try {
16
+ await share.share({ title: title(), text: text(), url: url() })
17
+ status.set('shared')
18
+ await haptics.notification('success')
19
+ }
20
+ catch {
21
+ status.set('error')
22
+ await haptics.notification('error')
23
+ }
24
+ }
25
+ </script>
26
+
27
+ <button
28
+ type="button"
29
+ :class="className()"
30
+ :aria-busy="status() === 'sharing' ? 'true' : 'false'"
31
+ @click="openShareSheet()"
32
+ >
33
+ <i aria-hidden="true" class="h-5 w-5 i-hugeicons-share-08"></i>
34
+ <span>{{ status() === 'sharing' ? 'Sharing' : label() }}</span>
35
+ </button>
@@ -0,0 +1,74 @@
1
+ <script>
2
+ interface NativeTabBarProps {
3
+ label?: string
4
+ }
5
+
6
+ const { label = 'App navigation' } = defineProps<NativeTabBarProps>()
7
+ </script>
8
+
9
+ <script client>
10
+ import { haptics } from '@stacksjs/mobile'
11
+
12
+ function selectTab(): void {
13
+ void haptics.selection()
14
+ }
15
+ </script>
16
+
17
+ <nav aria-label="{{ label }}" class="native-tab-bar" @click="selectTab()">
18
+ <slot />
19
+ </nav>
20
+
21
+ <style>
22
+ .native-tab-bar {
23
+ display: grid;
24
+ grid-auto-flow: column;
25
+ grid-auto-columns: 1fr;
26
+ min-height: 4.5rem;
27
+ border-top: 1px solid rgb(148 163 184 / 0.22);
28
+ background: rgb(255 255 255 / 0.9);
29
+ backdrop-filter: blur(24px) saturate(160%);
30
+ -webkit-backdrop-filter: blur(24px) saturate(160%);
31
+ }
32
+
33
+ .native-tab-item {
34
+ display: flex;
35
+ flex-direction: column;
36
+ gap: 0.2rem;
37
+ align-items: center;
38
+ justify-content: center;
39
+ min-width: 2.75rem;
40
+ color: rgb(100 116 139);
41
+ font-size: 0.6875rem;
42
+ font-weight: 600;
43
+ line-height: 1;
44
+ text-decoration: none;
45
+ transition: color 150ms ease, transform 150ms ease;
46
+ -webkit-tap-highlight-color: transparent;
47
+ }
48
+
49
+ .native-tab-item:active { transform: scale(0.96); }
50
+ .native-tab-item.is-active { color: rgb(5 150 105); }
51
+ .native-tab-icon { width: 1.35rem; height: 1.35rem; }
52
+
53
+ .dark .native-tab-bar {
54
+ border-color: rgb(71 85 105 / 0.55);
55
+ background: rgb(15 23 42 / 0.9);
56
+ }
57
+
58
+ .dark .native-tab-item { color: rgb(148 163 184); }
59
+ .dark .native-tab-item.is-active { color: rgb(52 211 153); }
60
+
61
+ @media (prefers-reduced-transparency: reduce) {
62
+ .native-tab-bar {
63
+ background: rgb(255 255 255);
64
+ backdrop-filter: none;
65
+ -webkit-backdrop-filter: none;
66
+ }
67
+
68
+ .dark .native-tab-bar { background: rgb(15 23 42); }
69
+ }
70
+
71
+ @media (prefers-reduced-motion: reduce) {
72
+ .native-tab-item { transition: none; }
73
+ }
74
+ </style>
@@ -0,0 +1,19 @@
1
+ <script>
2
+ interface NativeTabItemProps {
3
+ label: string
4
+ href: string
5
+ icon: string
6
+ }
7
+
8
+ const { href, icon, label } = defineProps<NativeTabItemProps>()
9
+ </script>
10
+
11
+ <StxLink
12
+ to="{{ href }}"
13
+ aria-label="{{ label }}"
14
+ activeClass="is-active"
15
+ class="native-tab-item"
16
+ >
17
+ <i aria-hidden="true" class="{{ icon }} native-tab-icon"></i>
18
+ <span>{{ label }}</span>
19
+ </StxLink>
@@ -12,4 +12,11 @@ export { default as DashboardNavbar } from './Dashboard/Navbar.stx'
12
12
  export * from './Dashboard'
13
13
  export { default as Audio } from './Audio.stx'
14
14
  export { default as Image } from './Image.stx'
15
+ export { default as NativeAppShell } from './NativeAppShell.stx'
16
+ export { default as NativeHealthButton } from './NativeHealthButton.stx'
17
+ export { default as NativeNetworkBanner } from './NativeNetworkBanner.stx'
18
+ export { default as NativePermissionButton } from './NativePermissionButton.stx'
19
+ export { default as NativeShareButton } from './NativeShareButton.stx'
20
+ export { default as NativeTabBar } from './NativeTabBar.stx'
21
+ export { default as NativeTabItem } from './NativeTabItem.stx'
15
22
  export { default as Video } from './Video.stx'
@@ -0,0 +1,150 @@
1
+ # actions/labeler@v6 format (structured match rules).
2
+ # Each label maps to changed-files > any-glob-to-any-file.
3
+ #
4
+ # Scoped to the paths an application actually owns. The framework's own
5
+ # labeler carries a label per internal package; in an app those are installed
6
+ # dependencies, not code under review.
7
+
8
+ app:
9
+ - changed-files:
10
+ - any-glob-to-any-file:
11
+ - 'app/**'
12
+
13
+ actions:
14
+ - changed-files:
15
+ - any-glob-to-any-file:
16
+ - 'app/Actions/**'
17
+
18
+ commands:
19
+ - changed-files:
20
+ - any-glob-to-any-file:
21
+ - 'app/Commands/**'
22
+
23
+ events:
24
+ - changed-files:
25
+ - any-glob-to-any-file:
26
+ - 'app/Events/**'
27
+ - 'app/Listeners/**'
28
+ - 'app/Events.ts'
29
+ - 'app/Listener.ts'
30
+
31
+ jobs:
32
+ - changed-files:
33
+ - any-glob-to-any-file:
34
+ - 'app/Jobs/**'
35
+
36
+ mail:
37
+ - changed-files:
38
+ - any-glob-to-any-file:
39
+ - 'app/Mail/**'
40
+
41
+ middleware:
42
+ - changed-files:
43
+ - any-glob-to-any-file:
44
+ - 'app/Middleware/**'
45
+ - 'app/Middleware.ts'
46
+
47
+ models:
48
+ - changed-files:
49
+ - any-glob-to-any-file:
50
+ - 'app/Models/**'
51
+
52
+ notifications:
53
+ - changed-files:
54
+ - any-glob-to-any-file:
55
+ - 'app/Notifications/**'
56
+
57
+ config:
58
+ - changed-files:
59
+ - any-glob-to-any-file:
60
+ - 'config/**'
61
+
62
+ database:
63
+ - changed-files:
64
+ - any-glob-to-any-file:
65
+ - 'database/**'
66
+
67
+ migrations:
68
+ - changed-files:
69
+ - any-glob-to-any-file:
70
+ - 'database/migrations/**'
71
+
72
+ resources:
73
+ - changed-files:
74
+ - any-glob-to-any-file:
75
+ - 'resources/**'
76
+
77
+ components:
78
+ - changed-files:
79
+ - any-glob-to-any-file:
80
+ - 'resources/components/**'
81
+
82
+ views:
83
+ - changed-files:
84
+ - any-glob-to-any-file:
85
+ - 'resources/views/**'
86
+ - 'resources/layouts/**'
87
+ - 'resources/partials/**'
88
+
89
+ functions:
90
+ - changed-files:
91
+ - any-glob-to-any-file:
92
+ - 'resources/functions/**'
93
+
94
+ stores:
95
+ - changed-files:
96
+ - any-glob-to-any-file:
97
+ - 'resources/stores/**'
98
+
99
+ lang:
100
+ - changed-files:
101
+ - any-glob-to-any-file:
102
+ - 'resources/lang/**'
103
+ - 'locales/**'
104
+
105
+ routes:
106
+ - changed-files:
107
+ - any-glob-to-any-file:
108
+ - 'routes/**'
109
+ - 'app/Routes.ts'
110
+
111
+ tests:
112
+ - changed-files:
113
+ - any-glob-to-any-file:
114
+ - 'tests/**'
115
+
116
+ docs:
117
+ - changed-files:
118
+ - any-glob-to-any-file:
119
+ - 'docs/**'
120
+ - 'config/docs.ts'
121
+ - '**/*.md'
122
+
123
+ content:
124
+ - changed-files:
125
+ - any-glob-to-any-file:
126
+ - 'content/**'
127
+
128
+ public:
129
+ - changed-files:
130
+ - any-glob-to-any-file:
131
+ - 'public/**'
132
+
133
+ cloud:
134
+ - changed-files:
135
+ - any-glob-to-any-file:
136
+ - 'cloud/**'
137
+ - 'config/cloud.ts'
138
+
139
+ deps:
140
+ - changed-files:
141
+ - any-glob-to-any-file:
142
+ - 'package.json'
143
+ - 'bun.lock'
144
+ - 'deps.yml'
145
+ - 'pantry.lock'
146
+
147
+ ci:
148
+ - changed-files:
149
+ - any-glob-to-any-file:
150
+ - '.github/**'
@@ -110,5 +110,12 @@ jobs:
110
110
  - name: Set Application Key
111
111
  run: bun buddy key:generate
112
112
 
113
+ # Feature tests query real tables, and a fresh checkout has no database
114
+ # file at all: it is gitignored, being machine-local state. Without this
115
+ # the suite fails in beforeAll with "no such table", which reads like a
116
+ # broken test rather than a missing schema.
117
+ - name: Migrate the database
118
+ run: bun buddy migrate
119
+
113
120
  - name: Test Suite
114
121
  run: bun run test
@@ -10,4 +10,4 @@ jobs:
10
10
  pull-requests: write
11
11
  runs-on: ubuntu-latest
12
12
  steps:
13
- - uses: actions/labeler@v4
13
+ - uses: actions/labeler@v6