feat: show mobile vault load retry
This commit is contained in:
@@ -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[],
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
32
apps/mobile/src/styles/vaultLoadStyles.ts
Normal file
32
apps/mobile/src/styles/vaultLoadStyles.ts
Normal 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',
|
||||
},
|
||||
})
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user