@tastic/shell 0.3.1 → 0.5.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tastic/shell",
3
- "version": "0.3.1",
3
+ "version": "0.5.0",
4
4
  "description": "The app around a pack of React Native games, two-seat or for one player: Home, the loadout, the stats screen, Profiles and Settings, the pack's Redux store and its root providers, driven by one PackDefinition",
5
5
  "keywords": [
6
6
  "react-native",
@@ -66,7 +66,7 @@
66
66
  },
67
67
  "prettier": "@infinitetoken/eslint-config/prettier",
68
68
  "devDependencies": {
69
- "@babel/core": "^7.29.7",
69
+ "@babel/core": "^7.29.13",
70
70
  "@expo/vector-icons": "^15.1.1",
71
71
  "@infinitetoken/eslint-config": "^0.2.1",
72
72
  "@infinitetoken/jest-config": "^0.5.2",
@@ -74,7 +74,7 @@
74
74
  "@react-native-async-storage/async-storage": "2.2.0",
75
75
  "@react-native-masked-view/masked-view": "0.3.2",
76
76
  "@react-native/jest-preset": "^0.88.0",
77
- "@reduxjs/toolkit": "^2.13.0",
77
+ "@reduxjs/toolkit": "^2.14.0",
78
78
  "@rific/auto-paper": "^0.13.4",
79
79
  "@rific/core": "^0.5.0",
80
80
  "@rific/feedback-press": "^0.13.1",
@@ -84,7 +84,7 @@
84
84
  "@rific/toaster": "^0.12.0",
85
85
  "@rific/updater": "^0.8.0",
86
86
  "@tastic/achievements": "^0.6.0",
87
- "@tastic/core": "^0.14.0",
87
+ "@tastic/core": "^0.15.0",
88
88
  "@tastic/edge-guard": "^0.1.5",
89
89
  "@tastic/hud": "^0.27.1",
90
90
  "@tastic/profile": "^0.17.0",
@@ -95,34 +95,33 @@
95
95
  "eslint": "^9.39.5",
96
96
  "eslint-plugin-react-hooks": "^7.1.1",
97
97
  "eslint-plugin-react-native": "^5.0.0",
98
- "expo": "~58.0.19",
99
- "expo-audio": "~58.0.10",
98
+ "expo": "~58.0.23",
99
+ "expo-audio": "~58.0.11",
100
100
  "expo-blur": "~58.0.5",
101
- "expo-constants": "~58.0.13",
101
+ "expo-constants": "~58.0.15",
102
102
  "expo-font": "~58.0.10",
103
103
  "expo-haptics": "~58.0.7",
104
104
  "expo-keep-awake": "~58.0.3",
105
- "expo-router": "~58.0.26",
106
- "expo-sensors": "~58.0.4",
107
- "expo-splash-screen": "~58.0.11",
105
+ "expo-router": "~58.0.30",
106
+ "expo-splash-screen": "~58.0.13",
108
107
  "expo-system-ui": "~58.0.6",
109
- "expo-updates": "~58.0.18",
108
+ "expo-updates": "~58.0.21",
110
109
  "jest": "~30.5.2",
111
- "jest-expo": "~58.0.12",
110
+ "jest-expo": "~58.0.14",
112
111
  "prettier": "^3.8.3",
113
112
  "react": "19.3.0",
114
- "react-native": "0.88.0",
113
+ "react-native": "^0.88.1",
115
114
  "react-native-gesture-handler": "~3.3.0",
116
- "react-native-paper": "^5.15.3",
115
+ "react-native-paper": "^5.15.4",
117
116
  "react-native-reanimated": "4.7.1",
118
- "react-native-safe-area-context": "~5.10.1",
117
+ "react-native-safe-area-context": "~5.11.1",
119
118
  "react-native-screens": "~4.30.0",
120
119
  "react-native-worklets": "0.13.0",
121
120
  "react-redux": "^9.3.0",
122
121
  "redux": "^5.0.1",
123
122
  "redux-persist": "^6.0.0",
124
123
  "test-renderer": "^1.3.1",
125
- "tsup": "^8.0.0",
124
+ "tsup": "^9.0.0",
126
125
  "typescript": "~6.0.3"
127
126
  },
128
127
  "peerDependencies": {
@@ -136,7 +135,7 @@
136
135
  "@rific/splash-gate": ">=0.2.3",
137
136
  "@rific/toaster": ">=0.10.1",
138
137
  "@tastic/achievements": ">=0.6.0",
139
- "@tastic/core": ">=0.14.0",
138
+ "@tastic/core": ">=0.15.0",
140
139
  "@tastic/edge-guard": ">=0.1.5",
141
140
  "@tastic/hud": ">=0.27.1",
142
141
  "@tastic/profile": ">=0.16.2",
@@ -147,7 +146,6 @@
147
146
  "expo-haptics": ">=58.0.7",
148
147
  "expo-keep-awake": ">=58.0.3",
149
148
  "expo-router": ">=58.0.23",
150
- "expo-sensors": ">=58.0.4",
151
149
  "expo-splash-screen": ">=58.0.10",
152
150
  "react": ">=19.3.0",
153
151
  "react-native": ">=0.88.0",
@@ -1,12 +1,11 @@
1
1
  import { useAutoPaperTheme } from '@rific/auto-paper'
2
2
  import { useHapticSettings } from '@rific/feedback-press'
3
3
  import { Toaster, ToastProvider } from '@rific/toaster'
4
- import { OrientationProvider, useRotationAwareStatusBar, useThemedRootBackground } from '@tastic/core'
4
+ import { useThemedRootBackground } from '@tastic/core'
5
5
  import { RotatedFrame } from '@tastic/hud/chrome'
6
6
  import { ProfileColorProvider } from '@tastic/profile'
7
7
  import * as Haptics from 'expo-haptics'
8
8
  import { DarkTheme, DefaultTheme, Stack, ThemeProvider as NavigationThemeProvider } from 'expo-router'
9
- import { DeviceMotion } from 'expo-sensors'
10
9
  import { ReactNode, useCallback, useMemo } from 'react'
11
10
  import { Platform, StyleSheet, View } from 'react-native'
12
11
  import { GestureHandlerRootView } from 'react-native-gesture-handler'
@@ -19,7 +18,6 @@ import { GuideHost } from './components/GuideHost'
19
18
  import { Providers } from './components/Providers'
20
19
  import { Theme } from './components/Theme'
21
20
  import { UpdateDialog } from './components/UpdateDialog'
22
- import { useAppSettings } from './hooks/useAppSettings'
23
21
  import { LastActiveGameProvider } from './hooks/useLastActiveGame'
24
22
  import { ProfilesProvider, useProfiles } from './hooks/useProfiles'
25
23
  import { PackProvider, usePack } from './PackContext'
@@ -38,15 +36,13 @@ import type { PackDefinition, PackGameDefinition } from './types'
38
36
  // a play screen and wins (confirmed on-device in LightCycles, where it kicked the player back to the title
39
37
  // screen mid-round instead of turning their cycle). Web has no such OS-level gesture.
40
38
  //
41
- // useRotationAwareStatusBar (@tastic/core) hides the OS status bar whenever the content is rotated
42
- // off portrait. It does it through screen options because that is the only way left: an app linked
43
- // against the iOS 27 SDK can't set the status bar app-wide, which is all React Native's StatusBar
44
- // (and expo-status-bar) does on iOS, and with @tastic/core's config plugin on, every mounted
45
- // StatusBar logs an error. So nothing in the app mounts one (Theme passes auto-paper's Provider
46
- // statusBar={false}). No `locked` argument: OrientationBridge already freezes the reading it
47
- // follows. A play screen hides the bar for the whole round, whatever the angle (a screen's own
48
- // options win over screenOptions); index is listed first so it stays the stack's first route.
49
- // A game with keepStatusBar (Solitaire's) keeps the screen options' rotation-aware bar instead.
39
+ // The status bar is set through screen options because that is the only way left: an app linked
40
+ // against the iOS 27 SDK can't set the status bar app-wide, which is all React Native's StatusBar (and
41
+ // expo-status-bar) does on iOS, and with UIViewControllerBasedStatusBarAppearance on (each app's
42
+ // ios.infoPlist), every mounted StatusBar logs an error. So nothing in the app mounts one (Theme passes
43
+ // auto-paper's Provider statusBar={false}). A play screen hides the bar for the whole round (a screen's
44
+ // own options win over screenOptions); index is listed first so it stays the stack's first route. A
45
+ // game with keepStatusBar (Solitaire's) keeps the stack's bar instead.
50
46
  const PLAY_OPTIONS = { statusBarHidden: true }
51
47
  const KEEP_STATUS_BAR_OPTIONS = {}
52
48
 
@@ -76,7 +72,7 @@ function AppStack() {
76
72
  const rootBackground = useThemedRootBackground(darkRoot, false)
77
73
  // The status bar's text follows the app's theme, not the phone's appearance (react-native-screens'
78
74
  // default `auto`), which drew dark text on a dark theme's black Home with the phone set to light.
79
- const statusBar = useRotationAwareStatusBar({ style: darkRoot ? 'light' : 'dark' })
75
+ const statusBarStyle = darkRoot ? ('light' as const) : ('dark' as const)
80
76
  const packOptions = (pack.useScreenOptions ?? noScreenOptions)()
81
77
  // Over a backdrop every screen is transparent, and Android crossfades: its default push and pop hold
82
78
  // the outgoing screen opaque for ~450ms while the new one slides over it, which would show Home
@@ -96,7 +92,7 @@ function AppStack() {
96
92
  }, [darkRoot, navigationBackground])
97
93
  const stack = (
98
94
  <NavigationThemeProvider value={navigationTheme}>
99
- <Stack screenOptions={{ ...rootBackground, ...statusBar, ...backdropOptions, ...packOptions }}>
95
+ <Stack screenOptions={{ ...rootBackground, statusBarStyle, ...backdropOptions, ...packOptions }}>
100
96
  <Stack.Screen name='index' />
101
97
  <Stack.Screen name='play/[game]' options={playOptions} />
102
98
  </Stack>
@@ -111,38 +107,6 @@ function AppStack() {
111
107
  )
112
108
  }
113
109
 
114
- // Wires the persisted Lock Orientation setting into @tastic/core's OrientationProvider. Its own
115
- // component because RootLayout renders the Redux store (inside Providers) and so can't call
116
- // useAppSettings() itself. Providers' PersistGate holds rendering until the store has rehydrated, so
117
- // lockInitialValue carries the saved lock from the very first render.
118
- //
119
- // freezeWhileLocked makes the lock app-wide: while appSettings.lockOrientation is on, the ONE shared
120
- // orientation reading freezes for every consumer (every screen, every board, hud dialogs, the
121
- // status bar), including anything that mounts after the lock, so no call site ever takes a `locked`
122
- // argument of its own (one would freeze on the pre-sensor default at mount). The Settings dialog
123
- // toggle only writes appSettings.lockOrientation, which reaches the provider as a changed
124
- // lockInitialValue; the provider reports the angle it froze on through onLockChange (`rotation`,
125
- // cleared on unlock), persisted as appSettings.lockedRotation and handed back here so a relaunch reopens
126
- // in the locked orientation instead of portrait.
127
- //
128
- // Mounted once, here, rather than per screen: one app-lifetime sensor subscription is what lets the
129
- // committed orientation survive navigation. deviceMotion is the app's own expo-sensors import, handed
130
- // in because @tastic/core never imports expo-sensors itself (apps without tilt tracking would
131
- // otherwise need it installed just to bundle).
132
- //
133
- // portraitWhileKeyboard (PackDefinition.portraitWhileKeyboard): the software keyboard can't turn with a
134
- // faked rotation, so a pack that has the player type sideways (a profile's name) reads portrait while
135
- // it's up.
136
- function OrientationBridge({ children }: { children: ReactNode }) {
137
- const { settings, setSettings } = useAppSettings()
138
- const { portraitWhileKeyboard } = usePack()
139
- return (
140
- <OrientationProvider deviceMotion={DeviceMotion} freezeWhileLocked lockInitialValue={{ locked: settings.lockOrientation, rotation: settings.lockedRotation }} onLockChange={(next) => setSettings({ lockOrientation: next.locked, lockedRotation: next.rotation })} portraitWhileKeyboard={portraitWhileKeyboard}>
141
- {children}
142
- </OrientationProvider>
143
- )
144
- }
145
-
146
110
  // ProfilesProvider reconciles the local roster with the shared App Group one before `loaded` flips,
147
111
  // so a screen mounting earlier would read (and could persist) the wrong profiles. This holds
148
112
  // everything nested inside it, and the splash screen, until that's done, while ProfilesProvider sits
@@ -163,7 +127,7 @@ function GatedApp({ children }: { children: ReactNode }) {
163
127
  // either is ever shown. Must render inside Providers, whose FeedbackBridge provides the setting.
164
128
  //
165
129
  // A pack with its own Toaster gets no Paper here: with Paper the toaster draws through a Paper Portal,
166
- // which would take its toasts out of the turned frame PackToaster lays them in.
130
+ // which would take its toasts out of the frame PackToaster lays them in.
167
131
  function HapticGatedToastProvider({ children }: { children: ReactNode }) {
168
132
  const { settings } = useHapticSettings()
169
133
  const { Toaster: PackToasterComponent } = usePack()
@@ -186,10 +150,10 @@ function RotatingToaster() {
186
150
  return <Toaster historyButton={null} clearButton={null} limit={3} renderFrame={renderFrame} />
187
151
  }
188
152
 
189
- // The pack's own toaster (PackDefinition.Toaster), laid over the whole app in a frame turned with the
190
- // phone: passThrough, so only a toast claims a touch over the stack, and rotatedSafeArea, so the
191
- // toaster's own safe-area reading lands on the frame's edges. The absoluteFill wrapper takes the frame
192
- // out of the layout beside the stack.
153
+ // The pack's own toaster (PackDefinition.Toaster), laid over the whole app in hud's RotatedFrame, which
154
+ // nothing turns since @tastic/core 0.15: passThrough, so only a toast claims a touch over the stack, and
155
+ // rotatedSafeArea, so the toaster reads the safe area. The absoluteFill wrapper takes the frame out of
156
+ // the layout beside the stack.
193
157
  function PackToaster({ Toaster: PackToasterComponent }: { Toaster: NonNullable<PackDefinition['Toaster']> }) {
194
158
  return (
195
159
  <View style={StyleSheet.absoluteFill} pointerEvents='box-none'>
@@ -243,24 +207,21 @@ export function RootLayout({ pack, store, persistor }: RootLayoutProps) {
243
207
  <GameProviders games={pack.games}>
244
208
  <PackRoot pack={pack}>
245
209
  <GatedApp>
246
- <OrientationBridge>
247
- <HapticGatedToastProvider>
248
- {/* Wraps the stack (rather than sitting beside it like UpdateDialog)
210
+ <HapticGatedToastProvider>
211
+ {/* Wraps the stack (rather than sitting beside it like UpdateDialog)
249
212
  because Home's tiles and the loadout's How to Play row reach it through
250
- context (see GuideHost). Inside the toast provider and OrientationBridge
251
- because the card renders through a Portal and rotates with the shared
252
- reading, and inside the gate so it mounts together with Home rather than
253
- before it. */}
254
- <GuideHost>
255
- <AppStack />
256
- </GuideHost>
257
- {/* historyButton/clearButton off: there's no toast history to open, and a
213
+ context (see GuideHost). Inside the toast provider because the card renders
214
+ through a Portal, and inside the gate so it mounts together with Home rather
215
+ than before it. */}
216
+ <GuideHost>
217
+ <AppStack />
218
+ </GuideHost>
219
+ {/* historyButton/clearButton off: there's no toast history to open, and a
258
220
  control row for it above the toast stack would go nowhere. limit pins the
259
221
  package's current default (3) so a future toaster release can't change it. */}
260
- {pack.Toaster ? <PackToaster Toaster={pack.Toaster} /> : <RotatingToaster />}
261
- <UpdateDialog />
262
- </HapticGatedToastProvider>
263
- </OrientationBridge>
222
+ {pack.Toaster ? <PackToaster Toaster={pack.Toaster} /> : <RotatingToaster />}
223
+ <UpdateDialog />
224
+ </HapticGatedToastProvider>
264
225
  </GatedApp>
265
226
  </PackRoot>
266
227
  </GameProviders>
@@ -1,4 +1,4 @@
1
- import { useIsTouchPrimaryDevice, useRotation } from '@tastic/core'
1
+ import { useIsTouchPrimaryDevice } from '@tastic/core'
2
2
  import { GuideProvider, useGuide } from '@tastic/hud/guide'
3
3
  import { createContext, ReactNode, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'
4
4
  import { shallowEqual, useDispatch, useSelector, useStore } from 'react-redux'
@@ -38,9 +38,8 @@ interface Props {
38
38
  // the pack's own (PackDefinition.guide, cards that teach every game at once), or a game's. Each
39
39
  // game's cards come from its own registry entry (GameDefinition.guide), built from the live settings
40
40
  // and seat 2 so a replay after any loadout change describes the next match. Mounted around the stack
41
- // in RootLayout, inside the toast provider and OrientationBridge because the card renders through a
42
- // Portal and turns with the shared reading (`rotation`, the same one Home hands its Settings dialog),
43
- // and inside the profiles gate so it mounts together with Home.
41
+ // in RootLayout, inside the toast provider because the card renders through a Portal, and inside the
42
+ // profiles gate so it mounts together with Home.
44
43
  export function GuideHost({ children }: Props) {
45
44
  const pack = usePack()
46
45
  const [subject, setSubject] = useState(() => (pack.guide ? PACK_GUIDE : (pack.games.find((g) => g.guide)?.id ?? null)))
@@ -48,7 +47,6 @@ export function GuideHost({ children }: Props) {
48
47
  const game = subject === PACK_GUIDE ? undefined : pack.games.find((g) => g.id === subject)
49
48
  const gameGuide = game?.guide
50
49
 
51
- const rotation = useRotation()
52
50
  const touch = useIsTouchPrimaryDevice()
53
51
  const seatTwo = useSelector((state: ShellState) => state.opponent.seatTwo)
54
52
  const input = useSelector((state: ShellState) => (gameGuide ? gameGuide.selectInput(state) : NO_INPUT), shallowEqual)
@@ -68,7 +66,7 @@ export function GuideHost({ children }: Props) {
68
66
  )
69
67
 
70
68
  return (
71
- <GuideProvider steps={steps} currentVersion={(packGuide ?? gameGuide)?.version ?? 0} seenVersion={seenVersion} onSeen={onSeen} rotation={rotation}>
69
+ <GuideProvider steps={steps} currentVersion={(packGuide ?? gameGuide)?.version ?? 0} seenVersion={seenVersion} onSeen={onSeen}>
72
70
  <GameGuideBridge onPick={setSubject}>{children}</GameGuideBridge>
73
71
  </GuideProvider>
74
72
  )
@@ -1,5 +1,4 @@
1
1
  import { useUpdateErrorToast } from '@rific/toaster'
2
- import { ViewRotation } from '@tastic/core'
3
2
  import { BaseSettingsDialog } from '@tastic/hud/chrome'
4
3
  import type { ReactNode } from 'react'
5
4
 
@@ -9,11 +8,6 @@ import { usePack } from '../PackContext'
9
8
  export interface SettingsDialogProps {
10
9
  visible: boolean
11
10
  onDismiss: () => void
12
- // Live physical-hold rotation (see @tastic/core's useRotation). BaseSettingsDialog lays the sheet
13
- // out in its own frame turned by this (see @tastic/hud's RotatedFrame), so it reads right-side-up
14
- // and fits the short edge however the phone is held. Defaults to 0 for a call site that doesn't
15
- // track one.
16
- rotation?: ViewRotation
17
11
  // Shows the How to Play row, which opens this game's cards (a loadout passes its game's opener).
18
12
  // Left out on Home, where each tile's rules button does it, and in a match, where opening
19
13
  // Settings pauses play.
@@ -23,12 +17,12 @@ export interface SettingsDialogProps {
23
17
  children?: ReactNode
24
18
  }
25
19
 
26
- // The pack-wide Settings dialog: everything the shell puts in it applies to every game (Lock
27
- // Orientation, Edge Guard, theme, sound, haptics, Keep Awake), so it reads and writes the app
20
+ // The pack-wide Settings dialog: everything the shell puts in it applies to every game (Edge Guard,
21
+ // theme, sound, haptics, Keep Awake), so it reads and writes the app
28
22
  // settings itself. Under those, a play screen's own rows and then the pack's own sections
29
23
  // (PackDefinition.SettingsSections), in every Settings dialog the pack opens. A pack with edgeGuard
30
24
  // false gets no Edge Guard row: hud draws it on iOS whenever its props are passed.
31
- export function SettingsDialog({ visible, onDismiss, rotation = 0, onShowHowToPlay, children }: SettingsDialogProps) {
25
+ export function SettingsDialog({ visible, onDismiss, onShowHowToPlay, children }: SettingsDialogProps) {
32
26
  const onUpdateError = useUpdateErrorToast()
33
27
  const { settings, setSettings } = useAppSettings()
34
28
  // The pack's otaVersion (src/constants/release.ts, which the OTA tooling bumps).
@@ -36,7 +30,7 @@ export function SettingsDialog({ visible, onDismiss, rotation = 0, onShowHowToPl
36
30
  const edgeGuardProps = edgeGuard ? { deferBottomEdgeGestures: settings.deferBottomEdgeGestures, onDeferBottomEdgeGestures: (value: boolean) => setSettings({ deferBottomEdgeGestures: value }) } : {}
37
31
 
38
32
  return (
39
- <BaseSettingsDialog visible={visible} onDismiss={onDismiss} rotation={rotation} version={otaVersion} lockOrientation={settings.lockOrientation} onLockOrientationChange={(value) => setSettings({ lockOrientation: value })} {...edgeGuardProps} onUpdateError={onUpdateError} onShowHowToPlay={onShowHowToPlay}>
33
+ <BaseSettingsDialog visible={visible} onDismiss={onDismiss} version={otaVersion} {...edgeGuardProps} onUpdateError={onUpdateError} onShowHowToPlay={onShowHowToPlay}>
40
34
  {children}
41
35
  {SettingsSections && <SettingsSections />}
42
36
  </BaseSettingsDialog>
@@ -61,8 +61,9 @@ export function Theme({ children }: Props) {
61
61
  // iOS/Android (the Settings sheet's and Rules' rows above Done, @tastic/profile's dropdown lists
62
62
  // above Manage); without it they just scroll with a hard edge. Web needs nothing.
63
63
  // statusBar={false}: auto-paper renders no React Native StatusBar (here or in Appbar.Header). The
64
- // status bar is set per screen (_layout.tsx's useRotationAwareStatusBar), and with @tastic/core's
65
- // config plugin on, a mounted StatusBar logs an error (a red box in a dev build).
64
+ // status bar is set through the stack's screen options (RootLayout's AppStack), and with
65
+ // UIViewControllerBasedStatusBarAppearance on (each app's ios.infoPlist), a mounted StatusBar logs an
66
+ // error (a red box in a dev build).
66
67
  // reanimated is what makes Dialog's own animatedStyle prop do anything (Metro won't pick up a
67
68
  // require()-in-try/catch from this package's ESM build, so the module is handed in); nothing else in
68
69
  // auto-paper reads it. fontFamily is the pack's (PackDefinition.fontFamily): set, it goes on every
@@ -1,5 +1,4 @@
1
1
  import { useUpdateErrorToast } from '@rific/toaster'
2
- import { useRotation } from '@tastic/core'
3
2
  import { UpdateDialog as SharedUpdateDialog } from '@tastic/hud/chrome'
4
3
 
5
4
  // Thin bridge onto the fleet-shared @tastic/hud UpdateDialog, which now owns the useUpdater()
@@ -8,6 +7,5 @@ import { UpdateDialog as SharedUpdateDialog } from '@tastic/hud/chrome'
8
7
  // directly in _layout.tsx) purely so this app's own onError -> toast wiring has one place to live.
9
8
  export function UpdateDialog() {
10
9
  const onError = useUpdateErrorToast()
11
- const rotation = useRotation()
12
- return <SharedUpdateDialog onError={onError} rotation={rotation} />
10
+ return <SharedUpdateDialog onError={onError} />
13
11
  }
@@ -5,9 +5,9 @@ import { useState } from 'react'
5
5
  // Solitaire's components/useFrozenWhileHidden.ts as it was.
6
6
  //
7
7
  // Home holds its layout with it while another screen covers it (screens/HomeScreen.tsx): Home stays
8
- // mounted beneath every screen the stack pushes and keeps following the rotation, and crossing the
8
+ // mounted beneath every screen the stack pushes and keeps following the window, and crossing the
9
9
  // upright/wide threshold swaps its whole tree (every tile, the hero, seat 1's button), invisible work
10
- // landing in the same commit as the covering screen's own rotation update. While covered the layout is
10
+ // landing in the same commit as the covering screen's own update for the new window. While covered the layout is
11
11
  // held where it was; it catches up (one swap, visible) when Home is shown again.
12
12
  export function useFrozenWhileHidden<T>(value: T, hidden: boolean): T {
13
13
  // React's own sanctioned "adjust state during render" shape (see react.dev's guide of the same
package/src/index.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  // and store, and every screen, game and shared piece reads the pack through usePack().
3
3
  export { PackProvider, usePack, usePackGame } from './PackContext'
4
4
  export { RootLayout } from './RootLayout'
5
- export { type FeedbackSounds, type GameDefinition, type GameGuide, type GameIconProps, type GameLoadout, type GamePreviewProps, type GameRulesProps, GUEST_PLAYER_ID, type GuidelessGameDefinition, type HeroTitleProps, humanSeatsFor, type LoadoutOptionsProps, type PackDefinition, type PackGameDefinition, type PackGuide, type PackStatsReset, type PackStatsSectionsProps, type PackStatsTab, type PickerAlign, type RoundMode, roundModeFor, type Seat, type SeatControlLook, type SeatTwo, seatTwoFor, type StackScreenOptions } from './types'
5
+ export { type FeedbackSounds, type GameDefinition, type GameGuide, type GameIconProps, type GameLoadout, type GamePreviewProps, type GameRulesProps, GUEST_PLAYER_ID, type GuidelessGameDefinition, type HeroTitleProps, type HomeTab, humanSeatsFor, type LoadoutOptionsProps, type PackDefinition, type PackGameDefinition, type PackGuide, type PackStatsReset, type PackStatsSectionsProps, type PackStatsTab, type PickerAlign, type RoundMode, roundModeFor, type Seat, type SeatControlLook, type SeatTwo, seatTwoFor, type StackScreenOptions } from './types'
6
6
 
7
7
  // The screens behind an app's routes (src/app/ stays in the app: expo-router only reads routes there).
8
8
  export { LoadoutRoute, PlayRoute } from './screens/GameRoute'
@@ -1,24 +1,13 @@
1
1
  import { createSettingsSlice } from '@rific/core'
2
- import type { ViewRotation } from '@tastic/core'
3
2
 
4
3
  // Settings that apply to the whole pack rather than one game. A game's own rules (board, score to
5
4
  // win, controls) live in that game's own settings slice.
6
5
  export interface AppSettings {
7
- // Lock Orientation: RootLayout's OrientationBridge hands it to @tastic/core's OrientationProvider
8
- // (freezeWhileLocked), which freezes the one shared orientation reading for every screen, every
9
- // board, dialogs and the status bar alike. Off by default: all of those just follow however the
10
- // device is actually being held.
11
- lockOrientation: boolean
12
- // The angle the lock is holding. Written by OrientationProvider's onLockChange (recorded on lock,
13
- // cleared on unlock) and handed back as lockInitialValue, so a relaunch reopens in the locked
14
- // orientation instead of portrait. Never set by the UI directly; absent while unlocked.
15
- lockedRotation?: ViewRotation
16
6
  // Edge Guard's opt-in (see Providers.tsx's EdgeGuardBridge).
17
7
  deferBottomEdgeGestures: boolean
18
8
  }
19
9
 
20
10
  export const DEFAULT_APP_SETTINGS: AppSettings = {
21
- lockOrientation: false,
22
11
  deferBottomEdgeGestures: false
23
12
  }
24
13