feat: show mobile vault load retry

This commit is contained in:
lucaronin
2026-05-05 00:48:09 +02:00
parent a41be6f58d
commit 428097f35d
5 changed files with 95 additions and 6 deletions

View File

@@ -83,7 +83,7 @@ export function MobileApp() {
setCompactNavigation((state) => selectLoadedNote(state, notes, selectedNoteId))
}, [])
useMobileVaultRuntimeLoader({
const runtimeLoader = useMobileVaultRuntimeLoader({
appStateStorage,
loadNotes: loadDemoVaultNotes,
metadataStorage: vaultMetadataStorage,
@@ -127,9 +127,11 @@ export function MobileApp() {
createNoteTitle={createFlow.title}
isCreatePromptOpen={createFlow.isPromptOpen}
isCreatingNote={createFlow.isCreating}
runtimeLoadFailed={runtimeLoader.failed}
onCancelCreateNote={createFlow.cancel}
onChangeCreateNoteTitle={createFlow.setTitle}
onOpenCreateNote={createFlow.open}
onRetryRuntimeLoad={runtimeLoader.retry}
onSubmitCreateNote={createFlow.submit}
onSelectNote={selectNote}
/>
@@ -155,9 +157,11 @@ export function MobileApp() {
createNoteTitle={createFlow.title}
isCreatePromptOpen={createFlow.isPromptOpen}
isCreatingNote={createFlow.isCreating}
runtimeLoadFailed={runtimeLoader.failed}
onCancelCreateNote={createFlow.cancel}
onChangeCreateNoteTitle={createFlow.setTitle}
onOpenCreateNote={createFlow.open}
onRetryRuntimeLoad={runtimeLoader.retry}
onSubmitCreateNote={createFlow.submit}
onSelectNote={selectNote}
/>
@@ -179,9 +183,11 @@ function CompactShell({
createNoteTitle,
isCreatePromptOpen,
isCreatingNote,
runtimeLoadFailed,
onCancelCreateNote,
onChangeCreateNoteTitle,
onOpenCreateNote,
onRetryRuntimeLoad,
onSubmitCreateNote,
onSelectNote,
selectedNoteId,
@@ -194,12 +200,14 @@ function CompactShell({
createNoteTitle: string
isCreatePromptOpen: boolean
isCreatingNote: boolean
runtimeLoadFailed: boolean
onNavigate: (event: CompactNavigationEvent) => void
onDeleteNote?: () => void
onDraftChange: (draft: MobileEditorDraft) => void
onCancelCreateNote: () => void
onChangeCreateNoteTitle: (title: string) => void
onOpenCreateNote: () => void
onRetryRuntimeLoad: () => void
onSubmitCreateNote: () => void
onSelectNote: (note: MobileNote) => void
selectedNoteId: string
@@ -244,10 +252,12 @@ function CompactShell({
createNoteTitle={createNoteTitle}
isCreatePromptOpen={isCreatePromptOpen}
isCreatingNote={isCreatingNote}
runtimeLoadFailed={runtimeLoadFailed}
onCancelCreateNote={onCancelCreateNote}
onChangeCreateNoteTitle={onChangeCreateNoteTitle}
onOpenCreateNote={onOpenCreateNote}
onOpenSidebar={() => onNavigate({ type: 'openSidebar' })}
onRetryRuntimeLoad={onRetryRuntimeLoad}
onSelectNote={onSelectNote}
onSubmitCreateNote={onSubmitCreateNote}
/>
@@ -294,10 +304,12 @@ function NoteListPanel({
createNoteTitle,
isCreatePromptOpen,
isCreatingNote,
runtimeLoadFailed,
onCancelCreateNote,
onChangeCreateNoteTitle,
onOpenCreateNote,
onOpenSidebar,
onRetryRuntimeLoad,
onSelectNote,
onSubmitCreateNote,
selectedNoteId,
@@ -307,10 +319,12 @@ function NoteListPanel({
createNoteTitle: string
isCreatePromptOpen: boolean
isCreatingNote: boolean
runtimeLoadFailed: boolean
onCancelCreateNote: () => void
onChangeCreateNoteTitle: (title: string) => void
onOpenCreateNote: () => void
onOpenSidebar?: () => void
onRetryRuntimeLoad: () => void
onSelectNote: (note: MobileNote) => void
onSubmitCreateNote: () => void
selectedNoteId: string
@@ -323,6 +337,7 @@ function NoteListPanel({
<View style={styles.toolbarSpacer} />
<IconButton icon={<MagnifyingGlass size={23} color={colors.textSoft} />} />
</Toolbar>
{runtimeLoadFailed ? <VaultLoadErrorNotice onRetry={onRetryRuntimeLoad} /> : null}
<FlatList
data={notes}
keyExtractor={(item) => item.id}
@@ -377,6 +392,17 @@ function NoteListPanel({
)
}
function VaultLoadErrorNotice({ onRetry }: { onRetry: () => void }) {
return (
<View style={styles.vaultLoadError}>
<Text style={styles.vaultLoadErrorText}>Could not load vault notes.</Text>
<Pressable onPress={onRetry} style={({ pressed }) => [styles.vaultLoadRetry, pressed ? styles.pressed : null]}>
<Text style={styles.vaultLoadRetryText}>Retry</Text>
</Pressable>
</View>
)
}
function selectLoadedNote(
state: ReturnType<typeof createCompactNavigationState>,
loadedNotes: MobileNote[],

View File

@@ -5,6 +5,7 @@ import { noteCreateStyles } from './styles/noteCreateStyles'
import { noteListStyles } from './styles/noteListStyles'
import { propertiesStyles } from './styles/propertiesStyles'
import { sidebarStyles } from './styles/sidebarStyles'
import { vaultLoadStyles } from './styles/vaultLoadStyles'
export const styles = {
...commonStyles,
@@ -14,4 +15,5 @@ export const styles = {
...noteListStyles,
...propertiesStyles,
...sidebarStyles,
...vaultLoadStyles,
}

View File

@@ -0,0 +1,32 @@
import { StyleSheet } from 'react-native'
import { colors, spacing } from '../theme'
export const vaultLoadStyles = StyleSheet.create({
vaultLoadError: {
marginHorizontal: spacing.lg,
marginBottom: spacing.sm,
padding: spacing.md,
borderColor: colors.chipRed,
borderWidth: StyleSheet.hairlineWidth,
borderRadius: 8,
backgroundColor: '#fff8f7',
},
vaultLoadErrorText: {
color: colors.text,
fontSize: 14,
fontWeight: '600',
},
vaultLoadRetry: {
alignSelf: 'flex-start',
marginTop: spacing.sm,
paddingHorizontal: spacing.md,
paddingVertical: spacing.xs,
borderRadius: 6,
backgroundColor: colors.primarySoft,
},
vaultLoadRetryText: {
color: colors.primary,
fontSize: 13,
fontWeight: '700',
},
})

View File

@@ -1,4 +1,4 @@
import { useEffect } from 'react'
import { useCallback, useEffect, useState } from 'react'
import type { MobileAppStateStorage } from './mobileAppStateStorage'
import type { MobileNote } from './demoData'
import type { MobileVaultMetadata } from './mobileVaultMetadata'
@@ -20,19 +20,42 @@ export function useMobileVaultRuntimeLoader({
selectedNoteId: string | null
}) => void
}) {
const [loadState, setLoadState] = useState<'loading' | 'ready' | 'failed'>('loading')
const [reloadKey, setReloadKey] = useState(0)
const retry = useCallback(() => {
setLoadState('loading')
setReloadKey((key) => key + 1)
}, [])
useEffect(() => {
let isActive = true
void loadMobileVaultRuntime({ appStateStorage, loadNotes, metadataStorage })
.then((runtime) => {
if (isActive && runtime.notes.length > 0) {
if (!isActive) {
return
}
if (runtime.notes.length > 0) {
onLoaded(runtime)
}
setLoadState('ready')
})
.catch(() => {
if (isActive) {
setLoadState('failed')
}
})
.catch(() => {})
return () => {
isActive = false
}
}, [appStateStorage, loadNotes, metadataStorage, onLoaded])
}, [appStateStorage, loadNotes, metadataStorage, onLoaded, reloadKey])
return {
failed: loadState === 'failed',
isLoading: loadState === 'loading',
retry,
}
}

View File

@@ -72,6 +72,7 @@ This file is the resumable working log for Tolaria mobile. The strategy and road
- Replaced the demo vault's hardcoded identity with the shared default vault metadata boundary.
- Wired mobile runtime loading through the persisted vault metadata catalog boundary, so app state, note loading, autosave, note creation, and deletion all operate against the active vault metadata.
- Extracted runtime loading into a hook plus tested pure loader to keep `MobileApp.tsx` at CodeScene `10.0`.
- Added first-class runtime vault load failure state with a visible retry notice in the note list, replacing the previous silent failure path.
## Next Action
@@ -79,7 +80,7 @@ Continue Phase 3 with app-managed vault storage hardening:
1. Add a focused simulator interaction path for create/open/edit/autosave/delete once Expo Go's overlay no longer blocks clean screenshots.
2. Decide whether archive should be modeled as a first-class note state or deferred until the mobile vault schema exists.
3. Add first-class error/retry state for vault runtime load failures instead of silently falling back to fixture notes.
3. Add a small runtime empty-vault state once user-created blank vaults exist.
## Verification Log
@@ -248,6 +249,11 @@ Continue Phase 3 with app-managed vault storage hardening:
- CodeScene after runtime vault metadata wiring: `apps/mobile/src/MobileApp.tsx`, `apps/mobile/src/mobileDemoVault.ts`, `apps/mobile/src/mobileVaultRuntime.ts`, and `apps/mobile/src/mobileVaultRuntime.test.ts` scored `10`; `apps/mobile/src/useMobileVaultRuntimeLoader.ts` returned no scorable code and no findings.
- `pnpm --filter @tolaria/mobile test` passed after runtime vault metadata wiring: 22 files / 70 tests.
- `pnpm --filter @tolaria/mobile exec expo export --platform ios --output-dir /tmp/tolaria-mobile-export` passed after runtime vault metadata wiring.
- `pnpm --filter @tolaria/mobile test -- src/mobileVaultRuntime.test.ts` passed after runtime load retry UI: 22 files / 70 tests.
- `pnpm --filter @tolaria/mobile typecheck` passed after runtime load retry UI.
- CodeScene after runtime load retry UI: `apps/mobile/src/MobileApp.tsx`, `apps/mobile/src/styles/noteListStyles.ts`, and `apps/mobile/src/styles/vaultLoadStyles.ts` scored `10`; `apps/mobile/src/useMobileVaultRuntimeLoader.ts` returned no scorable code and no findings.
- `pnpm --filter @tolaria/mobile test` passed after runtime load retry UI: 22 files / 70 tests.
- `pnpm --filter @tolaria/mobile exec expo export --platform ios --output-dir /tmp/tolaria-mobile-export` passed after runtime load retry UI.
## Risks / Watch Items