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:
jtrzupek 2026-05-31 11:41:29 +02:00
parent 05c0f6ef93
commit 64506690df
8 changed files with 29 additions and 44 deletions

View file

@ -6,8 +6,6 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import * as Sentry from '@sentry/react-native';
import Constants from 'expo-constants';
import { registerRootComponent } from 'expo';
import { useFonts } from 'expo-font';
import { Text as RNText } from 'react-native';
import * as ScreenCapture from 'expo-screen-capture';
import { StatusBar } from 'expo-status-bar';
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.
// Komponenty które chcą display/mono nadpisują fontFamily jawnie (bo RN nie
// syntezuje weightów dla custom fontów). Bold-ale-bez-fontFamily tekst zostanie
// Regular weightem General Sans — wciąż distinctive face, akceptowalne dla
// 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);
}
// NB: custom fonty (General Sans + Geist Mono) USUNIĘTE z bundla 2026-05-31 —
// `expo-font`/`ExpoFontLoader` natywny moduł NIE jest w APK 0.1.9 (build 22-maja,
// przed dodaniem expo-font) → useFonts crashował OTA bundle (ErrorRecovery rollback).
// System font do czasu rebuildu APK z expo-font. Patrz [[reference-ota-runtime-version]].
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 [ageAccepted, setAgeAccepted] = useState(false);
const [client, setClient] = useState<GoonClient | null>(null);
@ -270,7 +250,7 @@ export default function App() {
return () => sub.remove();
}, []);
if (!fontsLoaded || !hydrated || !lockReady) {
if (!hydrated || !lockReady) {
return (
<View style={{ flex: 1, backgroundColor: theme.bg, justifyContent: 'center' }}>
<ActivityIndicator color={theme.fg} />

View file

@ -6,7 +6,7 @@
"orientation": "portrait",
"userInterfaceStyle": "automatic",
"newArchEnabled": false,
"runtimeVersion": "0.1.9",
"runtimeVersion": "1.0",
"updates": {
"enabled": true,
"url": "https://api.goon-foss.org/expo-updates/manifest",

View file

@ -17,7 +17,7 @@ import React from 'react';
import { Text, View } from 'react-native';
import Svg, { Circle } from 'react-native-svg';
import { fonts, theme } from '../theme';
import { theme } from '../theme';
interface WordmarkProps {
/** fontSize wordmarku w px. */
@ -29,8 +29,9 @@ interface 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 = {
fontFamily: fonts.display,
fontWeight: '800' as const,
fontSize: size,
letterSpacing: -size * 0.03,
includeFontPadding: false as const,

View file

@ -3,7 +3,7 @@
import { Image } from 'expo-image';
import React from 'react';
import { Pressable, StyleSheet, Text, View } from 'react-native';
import { fonts, theme } from '../theme';
import { theme } from '../theme';
import type { MovieOut } from '../types';
export function MoviePosterCard({
@ -101,7 +101,7 @@ const styles = StyleSheet.create({
paddingHorizontal: 6,
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 },
watchedBadge: {
position: 'absolute',
@ -124,7 +124,7 @@ const styles = StyleSheet.create({
backgroundColor: 'rgba(0,0,0,0.5)',
},
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 },
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' },
});

View file

@ -27,7 +27,7 @@ import React, { useState } from 'react';
import { Pressable, StyleSheet, Text, View } from 'react-native';
import type { RootStackParamList } from '../navigation';
import { fonts, theme } from '../theme';
import { theme } from '../theme';
import type { SceneOut } from '../types';
import { Thumb } from './Thumb';
@ -190,13 +190,13 @@ const styles = StyleSheet.create({
durText: {
color: theme.fg,
fontSize: 11,
fontFamily: fonts.mono,
fontWeight: '600',
fontVariant: ['tabular-nums'],
},
title: {
color: theme.fg,
fontSize: 14,
fontFamily: fonts.display,
fontWeight: '600',
marginTop: 8,
letterSpacing: -0.2,
},
@ -204,7 +204,7 @@ const styles = StyleSheet.create({
meta: {
color: theme.muted,
fontSize: 10,
fontFamily: fonts.mono,
fontWeight: '600',
marginTop: 3,
letterSpacing: 0.5,
textTransform: 'uppercase',

View file

@ -24,7 +24,7 @@ import { SitesScreen } from './screens/SitesScreen';
import { StudioScenesScreen } from './screens/StudioScenesScreen';
import { TagScenesScreen } from './screens/TagScenesScreen';
import { TagsScreen } from './screens/TagsScreen';
import { fonts, theme } from './theme';
import { theme } from './theme';
export type RootStackParamList = {
Scenes: undefined;
@ -101,7 +101,7 @@ function TopTabs({
style={{
color: active ? theme.accent : theme.muted,
fontSize: 13,
fontFamily: active ? fonts.display : fonts.medium,
fontWeight: active ? '700' : '500',
letterSpacing: 0.3,
}}
>
@ -145,7 +145,7 @@ export function AppNavigator({ onLogout, client, appVersion }: AppNavigatorProps
<Stack.Navigator
screenOptions={{
headerStyle: { backgroundColor: theme.card },
headerTitleStyle: { color: theme.fg, fontFamily: fonts.display },
headerTitleStyle: { color: theme.fg },
headerTintColor: theme.accent,
contentStyle: { backgroundColor: theme.bg },
}}

View file

@ -56,11 +56,15 @@ export const theme = {
* Gdy useFonts jeszcze nie ready, App.tsx blokuje render (fonty z bundla
* ładują się <100ms), więc te stałe 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 = {
body: 'GeneralSans-Regular',
medium: 'GeneralSans-Medium',
display: 'GeneralSans-Semibold',
mono: 'GeistMono-Regular',
body: undefined as string | undefined,
medium: undefined as string | undefined,
display: undefined as string | undefined,
mono: undefined as string | undefined,
} as const;
export function scoreColor(score: number): string {

View file

@ -29,7 +29,7 @@ from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
MOBILE = ROOT / "mobile"
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.
# Defaults are placeholders intended to fail loudly if you forgot to configure.
VPS = os.environ.get("GOON_VPS_SSH", "root@your-vps.example.com")