fix(ota+mobile): strip expo-font from bundle, runtime back to 1.0
DIAGNOZA NA EMULATORZE (emulator-5554, goon-v0.1.9.apk):
Dwa błędne założenia z poprzednich sesji obalone empirycznie:
1. RUNTIME: APK ma EXPO_RUNTIME_VERSION="1.0" (NIE 0.1.9 — pomyliłem versionName
z runtime). App akceptuje TYLKO manifest runtime 1.0. Mój wcześniejszy
"fix" na 0.1.9 (c19da51) był wstecz — app go ignorował. Cofnięte: app.json
+ publish_update RUNTIME_DEFAULT z powrotem na "1.0".
2. CRASH: prawdziwa przyczyna "nic się nie pojawia" — OTA bundle z expo-font
crashował: "Cannot find native module 'ExpoFontLoader'" → expo-updates
ErrorRecovery rollback. APK (build 22-maja) nie ma natywnego ExpoFontLoader
(expo-font dodany 30-maja, PO buildzie APK). OTA NIE MOŻE dostarczyć native
modułu. Potwierdzone: embedded bundle + served bundle grep = 0 ExpoFontLoader;
stary font-bundle crashował, font-stripped NIE.
FIX: usunięto useFonts z App.tsx + expo-font import; theme.fonts → undefined
(system font); SceneTile/MoviePosterCard/navigation/GoonWordmark fontFamily →
fontWeight. Wszystko inne (2-col grid, oxblood, logo SVG-RNSVG-jest-w-APK)
zostaje. Custom fonty wrócą przy rebuildzie APK z expo-font (option B).
ZWERYFIKOWANE: bundle d5b87e5c (runtime 1.0, 0 ttf) — emulator launch:
`ReactNativeJS: Running "main"`, zero JS errors, brak ExpoFontLoader crash.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
05c0f6ef93
commit
64506690df
8 changed files with 29 additions and 44 deletions
|
|
@ -6,8 +6,6 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||||
import * as Sentry from '@sentry/react-native';
|
import * as Sentry from '@sentry/react-native';
|
||||||
import Constants from 'expo-constants';
|
import Constants from 'expo-constants';
|
||||||
import { registerRootComponent } from 'expo';
|
import { registerRootComponent } from 'expo';
|
||||||
import { useFonts } from 'expo-font';
|
|
||||||
import { Text as RNText } from 'react-native';
|
|
||||||
import * as ScreenCapture from 'expo-screen-capture';
|
import * as ScreenCapture from 'expo-screen-capture';
|
||||||
import { StatusBar } from 'expo-status-bar';
|
import { StatusBar } from 'expo-status-bar';
|
||||||
import * as Updates from 'expo-updates';
|
import * as Updates from 'expo-updates';
|
||||||
|
|
@ -77,30 +75,12 @@ const queryClient = new QueryClient({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Globalny default fontu dla całego <Text> — General Sans Regular jako body.
|
// NB: custom fonty (General Sans + Geist Mono) USUNIĘTE z bundla 2026-05-31 —
|
||||||
// Komponenty które chcą display/mono nadpisują fontFamily jawnie (bo RN nie
|
// `expo-font`/`ExpoFontLoader` natywny moduł NIE jest w APK 0.1.9 (build 22-maja,
|
||||||
// syntezuje weightów dla custom fontów). Bold-ale-bez-fontFamily tekst zostanie
|
// przed dodaniem expo-font) → useFonts crashował OTA bundle (ErrorRecovery rollback).
|
||||||
// Regular weightem General Sans — wciąż distinctive face, akceptowalne dla
|
// System font do czasu rebuildu APK z expo-font. Patrz [[reference-ota-runtime-version]].
|
||||||
// nietkniętych ekranów; high-traffic komponenty mają jawny Semibold.
|
|
||||||
let _textDefaultApplied = false;
|
|
||||||
function applyDefaultFont() {
|
|
||||||
if (_textDefaultApplied) return;
|
|
||||||
_textDefaultApplied = true;
|
|
||||||
const T = RNText as unknown as { defaultProps?: { style?: unknown } };
|
|
||||||
T.defaultProps = T.defaultProps || {};
|
|
||||||
const prev = T.defaultProps.style;
|
|
||||||
T.defaultProps.style = [{ fontFamily: 'GeneralSans-Regular' }, prev].filter(Boolean);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const [fontsLoaded] = useFonts({
|
|
||||||
'GeneralSans-Regular': require('./assets/fonts/GeneralSans-Regular.ttf'),
|
|
||||||
'GeneralSans-Medium': require('./assets/fonts/GeneralSans-Medium.ttf'),
|
|
||||||
'GeneralSans-Semibold': require('./assets/fonts/GeneralSans-Semibold.ttf'),
|
|
||||||
'GeistMono-Regular': require('./assets/fonts/GeistMono-Regular.ttf'),
|
|
||||||
});
|
|
||||||
if (fontsLoaded) applyDefaultFont();
|
|
||||||
|
|
||||||
const [hydrated, setHydrated] = useState(false);
|
const [hydrated, setHydrated] = useState(false);
|
||||||
const [ageAccepted, setAgeAccepted] = useState(false);
|
const [ageAccepted, setAgeAccepted] = useState(false);
|
||||||
const [client, setClient] = useState<GoonClient | null>(null);
|
const [client, setClient] = useState<GoonClient | null>(null);
|
||||||
|
|
@ -270,7 +250,7 @@ export default function App() {
|
||||||
return () => sub.remove();
|
return () => sub.remove();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (!fontsLoaded || !hydrated || !lockReady) {
|
if (!hydrated || !lockReady) {
|
||||||
return (
|
return (
|
||||||
<View style={{ flex: 1, backgroundColor: theme.bg, justifyContent: 'center' }}>
|
<View style={{ flex: 1, backgroundColor: theme.bg, justifyContent: 'center' }}>
|
||||||
<ActivityIndicator color={theme.fg} />
|
<ActivityIndicator color={theme.fg} />
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
"orientation": "portrait",
|
"orientation": "portrait",
|
||||||
"userInterfaceStyle": "automatic",
|
"userInterfaceStyle": "automatic",
|
||||||
"newArchEnabled": false,
|
"newArchEnabled": false,
|
||||||
"runtimeVersion": "0.1.9",
|
"runtimeVersion": "1.0",
|
||||||
"updates": {
|
"updates": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"url": "https://api.goon-foss.org/expo-updates/manifest",
|
"url": "https://api.goon-foss.org/expo-updates/manifest",
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ import React from 'react';
|
||||||
import { Text, View } from 'react-native';
|
import { Text, View } from 'react-native';
|
||||||
import Svg, { Circle } from 'react-native-svg';
|
import Svg, { Circle } from 'react-native-svg';
|
||||||
|
|
||||||
import { fonts, theme } from '../theme';
|
import { theme } from '../theme';
|
||||||
|
|
||||||
interface WordmarkProps {
|
interface WordmarkProps {
|
||||||
/** fontSize wordmarku w px. */
|
/** fontSize wordmarku w px. */
|
||||||
|
|
@ -29,8 +29,9 @@ interface WordmarkProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function GoonWordmark({ size = 26, color = theme.fg, mono = false }: WordmarkProps) {
|
export function GoonWordmark({ size = 26, color = theme.fg, mono = false }: WordmarkProps) {
|
||||||
|
// System bold (custom font usunięty z OTA bundla — ExpoFontLoader nie w APK).
|
||||||
const base = {
|
const base = {
|
||||||
fontFamily: fonts.display,
|
fontWeight: '800' as const,
|
||||||
fontSize: size,
|
fontSize: size,
|
||||||
letterSpacing: -size * 0.03,
|
letterSpacing: -size * 0.03,
|
||||||
includeFontPadding: false as const,
|
includeFontPadding: false as const,
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
import { Image } from 'expo-image';
|
import { Image } from 'expo-image';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Pressable, StyleSheet, Text, View } from 'react-native';
|
import { Pressable, StyleSheet, Text, View } from 'react-native';
|
||||||
import { fonts, theme } from '../theme';
|
import { theme } from '../theme';
|
||||||
import type { MovieOut } from '../types';
|
import type { MovieOut } from '../types';
|
||||||
|
|
||||||
export function MoviePosterCard({
|
export function MoviePosterCard({
|
||||||
|
|
@ -101,7 +101,7 @@ const styles = StyleSheet.create({
|
||||||
paddingHorizontal: 6,
|
paddingHorizontal: 6,
|
||||||
paddingVertical: 2,
|
paddingVertical: 2,
|
||||||
},
|
},
|
||||||
newBadgeText: { color: theme.fg, fontSize: 9, fontFamily: fonts.mono, fontWeight: '700', letterSpacing: 0.6 },
|
newBadgeText: { color: theme.fg, fontSize: 9, fontWeight: '800', letterSpacing: 0.6 },
|
||||||
posterDimmed: { opacity: 0.45 },
|
posterDimmed: { opacity: 0.45 },
|
||||||
watchedBadge: {
|
watchedBadge: {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
|
|
@ -124,7 +124,7 @@ const styles = StyleSheet.create({
|
||||||
backgroundColor: 'rgba(0,0,0,0.5)',
|
backgroundColor: 'rgba(0,0,0,0.5)',
|
||||||
},
|
},
|
||||||
progressFg: { height: 3, backgroundColor: theme.accent },
|
progressFg: { height: 3, backgroundColor: theme.accent },
|
||||||
title: { color: theme.fg, fontSize: 13, fontFamily: fonts.display, marginTop: 6, letterSpacing: -0.2 },
|
title: { color: theme.fg, fontSize: 13, fontWeight: '600', marginTop: 6, letterSpacing: -0.2 },
|
||||||
titleDimmed: { color: theme.muted },
|
titleDimmed: { color: theme.muted },
|
||||||
meta: { color: theme.muted, fontSize: 10, fontFamily: fonts.mono, marginTop: 2, letterSpacing: 0.5, textTransform: 'uppercase' },
|
meta: { color: theme.muted, fontSize: 10, fontWeight: '600', marginTop: 2, letterSpacing: 0.5, textTransform: 'uppercase' },
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ import React, { useState } from 'react';
|
||||||
import { Pressable, StyleSheet, Text, View } from 'react-native';
|
import { Pressable, StyleSheet, Text, View } from 'react-native';
|
||||||
|
|
||||||
import type { RootStackParamList } from '../navigation';
|
import type { RootStackParamList } from '../navigation';
|
||||||
import { fonts, theme } from '../theme';
|
import { theme } from '../theme';
|
||||||
import type { SceneOut } from '../types';
|
import type { SceneOut } from '../types';
|
||||||
import { Thumb } from './Thumb';
|
import { Thumb } from './Thumb';
|
||||||
|
|
||||||
|
|
@ -190,13 +190,13 @@ const styles = StyleSheet.create({
|
||||||
durText: {
|
durText: {
|
||||||
color: theme.fg,
|
color: theme.fg,
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
fontFamily: fonts.mono,
|
fontWeight: '600',
|
||||||
fontVariant: ['tabular-nums'],
|
fontVariant: ['tabular-nums'],
|
||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
color: theme.fg,
|
color: theme.fg,
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontFamily: fonts.display,
|
fontWeight: '600',
|
||||||
marginTop: 8,
|
marginTop: 8,
|
||||||
letterSpacing: -0.2,
|
letterSpacing: -0.2,
|
||||||
},
|
},
|
||||||
|
|
@ -204,7 +204,7 @@ const styles = StyleSheet.create({
|
||||||
meta: {
|
meta: {
|
||||||
color: theme.muted,
|
color: theme.muted,
|
||||||
fontSize: 10,
|
fontSize: 10,
|
||||||
fontFamily: fonts.mono,
|
fontWeight: '600',
|
||||||
marginTop: 3,
|
marginTop: 3,
|
||||||
letterSpacing: 0.5,
|
letterSpacing: 0.5,
|
||||||
textTransform: 'uppercase',
|
textTransform: 'uppercase',
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ import { SitesScreen } from './screens/SitesScreen';
|
||||||
import { StudioScenesScreen } from './screens/StudioScenesScreen';
|
import { StudioScenesScreen } from './screens/StudioScenesScreen';
|
||||||
import { TagScenesScreen } from './screens/TagScenesScreen';
|
import { TagScenesScreen } from './screens/TagScenesScreen';
|
||||||
import { TagsScreen } from './screens/TagsScreen';
|
import { TagsScreen } from './screens/TagsScreen';
|
||||||
import { fonts, theme } from './theme';
|
import { theme } from './theme';
|
||||||
|
|
||||||
export type RootStackParamList = {
|
export type RootStackParamList = {
|
||||||
Scenes: undefined;
|
Scenes: undefined;
|
||||||
|
|
@ -101,7 +101,7 @@ function TopTabs({
|
||||||
style={{
|
style={{
|
||||||
color: active ? theme.accent : theme.muted,
|
color: active ? theme.accent : theme.muted,
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
fontFamily: active ? fonts.display : fonts.medium,
|
fontWeight: active ? '700' : '500',
|
||||||
letterSpacing: 0.3,
|
letterSpacing: 0.3,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
@ -145,7 +145,7 @@ export function AppNavigator({ onLogout, client, appVersion }: AppNavigatorProps
|
||||||
<Stack.Navigator
|
<Stack.Navigator
|
||||||
screenOptions={{
|
screenOptions={{
|
||||||
headerStyle: { backgroundColor: theme.card },
|
headerStyle: { backgroundColor: theme.card },
|
||||||
headerTitleStyle: { color: theme.fg, fontFamily: fonts.display },
|
headerTitleStyle: { color: theme.fg },
|
||||||
headerTintColor: theme.accent,
|
headerTintColor: theme.accent,
|
||||||
contentStyle: { backgroundColor: theme.bg },
|
contentStyle: { backgroundColor: theme.bg },
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -56,11 +56,15 @@ export const theme = {
|
||||||
* Gdy useFonts jeszcze nie ready, App.tsx blokuje render (fonty z bundla
|
* Gdy useFonts jeszcze nie ready, App.tsx blokuje render (fonty z bundla
|
||||||
* ładują się <100ms), więc te stałe są zawsze valid przy pierwszym paint.
|
* ładują się <100ms), więc te stałe są zawsze valid przy pierwszym paint.
|
||||||
*/
|
*/
|
||||||
|
// Custom fonty USUNIĘTE z OTA bundla 2026-05-31 (ExpoFontLoader native nie ma w
|
||||||
|
// APK 0.1.9 → crash). `undefined` = system font (San Francisco/Roboto). Komponenty
|
||||||
|
// fallbackują na fontWeight dla hierarchii. Przywrócić do realnych rodzin gdy
|
||||||
|
// nowy APK z expo-font wejdzie do dystrybucji. Patrz [[reference-ota-runtime-version]].
|
||||||
export const fonts = {
|
export const fonts = {
|
||||||
body: 'GeneralSans-Regular',
|
body: undefined as string | undefined,
|
||||||
medium: 'GeneralSans-Medium',
|
medium: undefined as string | undefined,
|
||||||
display: 'GeneralSans-Semibold',
|
display: undefined as string | undefined,
|
||||||
mono: 'GeistMono-Regular',
|
mono: undefined as string | undefined,
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export function scoreColor(score: number): string {
|
export function scoreColor(score: number): string {
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ from pathlib import Path
|
||||||
ROOT = Path(__file__).resolve().parent.parent
|
ROOT = Path(__file__).resolve().parent.parent
|
||||||
MOBILE = ROOT / "mobile"
|
MOBILE = ROOT / "mobile"
|
||||||
DIST = MOBILE / "dist"
|
DIST = MOBILE / "dist"
|
||||||
RUNTIME_DEFAULT = "0.1.9" # MUSI == EXPO_RUNTIME_VERSION w APK (AndroidManifest). Zweryfikowane 2026-05-31.
|
RUNTIME_DEFAULT = "1.0" # == EXPO_RUNTIME_VERSION w APK (zweryfikowane emulatorem 2026-05-31: app akceptuje TYLKO runtime 1.0).
|
||||||
# Operator config — set in your shell / .env.local before running this script.
|
# Operator config — set in your shell / .env.local before running this script.
|
||||||
# Defaults are placeholders intended to fail loudly if you forgot to configure.
|
# Defaults are placeholders intended to fail loudly if you forgot to configure.
|
||||||
VPS = os.environ.get("GOON_VPS_SSH", "root@your-vps.example.com")
|
VPS = os.environ.get("GOON_VPS_SSH", "root@your-vps.example.com")
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue