feat: show mobile note create state

This commit is contained in:
lucaronin
2026-05-04 19:05:13 +02:00
parent 5da79cac74
commit 1f66fbda59
4 changed files with 71 additions and 5 deletions

View File

@@ -44,6 +44,8 @@ export function MobileApp() {
const showsProperties = width >= 1120
const [availableNotes, setAvailableNotes] = useState(fallbackNotes)
const [compactNavigation, setCompactNavigation] = useState(() => createCompactNavigationState(fallbackNotes[0].id))
const [createNoteFailed, setCreateNoteFailed] = useState(false)
const [isCreatingNote, setIsCreatingNote] = useState(false)
const [saveStateByNoteId, setSaveStateByNoteId] = useState<Record<string, MobileEditorSaveState>>({})
const selectedNote = useMemo(
() => availableNotes.find((note) => note.id === compactNavigation.selectedNoteId) ?? availableNotes[0],
@@ -89,6 +91,12 @@ export function MobileApp() {
}
const saveDraft = useCallback((draft: MobileEditorDraft) => autosaveQueue.enqueue(draft), [autosaveQueue])
const createNote = useCallback(() => {
if (isCreatingNote) {
return
}
setCreateNoteFailed(false)
setIsCreatingNote(true)
void createDemoVaultNote()
.then((note) => {
if (!note) {
@@ -98,8 +106,13 @@ export function MobileApp() {
setAvailableNotes((notes) => [note, ...notes.filter((item) => item.id !== note.id)])
setCompactNavigation((state) => transitionCompactNavigation(state, { type: 'selectNote', noteId: note.id }))
})
.catch(() => {})
}, [])
.catch(() => {
setCreateNoteFailed(true)
})
.finally(() => {
setIsCreatingNote(false)
})
}, [isCreatingNote])
return (
<SafeAreaProvider>
@@ -110,6 +123,8 @@ export function MobileApp() {
<NoteListPanel
notes={availableNotes}
selectedNoteId={compactNavigation.selectedNoteId}
createNoteFailed={createNoteFailed}
isCreatingNote={isCreatingNote}
onCreateNote={createNote}
onSelectNote={selectNote}
/>
@@ -125,6 +140,8 @@ export function MobileApp() {
selectedNoteId={compactNavigation.selectedNoteId}
onNavigate={(event) => setCompactNavigation((state) => transitionCompactNavigation(state, event))}
onDraftChange={saveDraft}
createNoteFailed={createNoteFailed}
isCreatingNote={isCreatingNote}
onCreateNote={createNote}
onSelectNote={selectNote}
/>
@@ -141,6 +158,8 @@ function CompactShell({
saveState,
onNavigate,
onDraftChange,
createNoteFailed,
isCreatingNote,
onCreateNote,
onSelectNote,
selectedNoteId,
@@ -149,6 +168,8 @@ function CompactShell({
note: MobileNote
notes: MobileNote[]
saveState: MobileEditorSaveState
createNoteFailed: boolean
isCreatingNote: boolean
onNavigate: (event: CompactNavigationEvent) => void
onDraftChange: (draft: MobileEditorDraft) => void
onCreateNote: () => void
@@ -190,6 +211,8 @@ function CompactShell({
<NoteListPanel
notes={notes}
selectedNoteId={selectedNoteId}
createNoteFailed={createNoteFailed}
isCreatingNote={isCreatingNote}
onCreateNote={onCreateNote}
onOpenSidebar={() => onNavigate({ type: 'openSidebar' })}
onSelectNote={onSelectNote}
@@ -233,12 +256,16 @@ function SidebarPanel({ onClose }: { onClose?: () => void }) {
function NoteListPanel({
notes,
createNoteFailed,
isCreatingNote,
onCreateNote,
onOpenSidebar,
onSelectNote,
selectedNoteId,
}: {
notes: MobileNote[]
createNoteFailed: boolean
isCreatingNote: boolean
onCreateNote: () => void
onOpenSidebar?: () => void
onSelectNote: (note: MobileNote) => void
@@ -280,7 +307,17 @@ function NoteListPanel({
</Pressable>
)}
/>
<Pressable onPress={onCreateNote} style={styles.composeButton}>
{createNoteFailed ? <Text style={styles.createNoteError}>Could not create note</Text> : null}
<Pressable
accessibilityLabel="Create note"
disabled={isCreatingNote}
onPress={onCreateNote}
style={({ pressed }) => [
styles.composeButton,
isCreatingNote ? styles.composeButtonDisabled : null,
pressed ? styles.pressed : null,
]}
>
<PencilSimple size={28} color="#ffffff" />
</Pressable>
</View>

View File

@@ -1,6 +1,7 @@
import { commonStyles } from './styles/commonStyles'
import { editorSaveStateStyles } from './styles/editorSaveStateStyles'
import { editorStyles } from './styles/editorStyles'
import { noteCreateStyles } from './styles/noteCreateStyles'
import { noteListStyles } from './styles/noteListStyles'
import { propertiesStyles } from './styles/propertiesStyles'
import { sidebarStyles } from './styles/sidebarStyles'
@@ -9,6 +10,7 @@ export const styles = {
...commonStyles,
...editorStyles,
...editorSaveStateStyles,
...noteCreateStyles,
...noteListStyles,
...propertiesStyles,
...sidebarStyles,

View File

@@ -0,0 +1,21 @@
import { StyleSheet } from 'react-native'
import { colors, spacing } from '../theme'
export const noteCreateStyles = StyleSheet.create({
composeButtonDisabled: {
opacity: 0.55,
},
createNoteError: {
position: 'absolute',
right: spacing.xl,
bottom: spacing.xl + 76,
maxWidth: 220,
borderRadius: 14,
paddingHorizontal: spacing.md,
paddingVertical: spacing.sm,
color: colors.textSoft,
backgroundColor: colors.chipRed,
fontSize: 13,
fontWeight: '600',
},
})

View File

@@ -8,7 +8,7 @@ This file is the resumable working log for Tolaria mobile. The strategy and road
- Branch: `codex/mobile`
- Active phase: Phase 2 - Mobile Shell
- Active slice: Create local mobile note
- Active slice: Add create-note UX state
- Push policy: commit locally; do not push unless explicitly requested
- Validation target: iPad/iOS simulator first
@@ -63,6 +63,7 @@ This file is the resumable working log for Tolaria mobile. The strategy and road
- Added a debounced mobile autosave queue that coalesces rapid TenTap draft changes, marks edited drafts as queued, and ignores stale save results when newer drafts supersede them.
- Refreshed the in-memory mobile note projection after the latest successful editor save so the note list, editor source, properties words, and snippets update from canonical Markdown.
- Added the first app-local mobile note creation path and wired the compose button to write a new Markdown note, prepend it to the current list, and select it through the shared compact navigation reducer.
- Added create-note UX state so the compose button disables during app-local creation and displays a compact failure message if storage creation fails.
## Next Action
@@ -70,7 +71,7 @@ Continue Phase 2 with the next mobile shell slice:
1. Dismiss or suppress Expo Go's first-run tools modal during simulator QA so screenshots capture the app without the overlay.
2. Add a focused simulator interaction path for editor typing/autosave once Expo Go's overlay no longer blocks clean screenshots.
3. Add a durable note-create UX state: disabled/pressed state while creating, create failure feedback, and eventual title-entry flow.
3. Add an eventual title-entry flow for new mobile notes instead of creating `Untitled` immediately.
## Verification Log
@@ -209,6 +210,11 @@ Continue Phase 2 with the next mobile shell slice:
- CodeScene after local note creation: `apps/mobile/src/MobileApp.tsx`, `apps/mobile/src/mobileDemoVault.ts`, and `apps/mobile/src/mobileNoteCreate.test.ts` scored `10`; `apps/mobile/src/mobileNoteCreate.ts` returned no scorable code and no findings.
- `pnpm --filter @tolaria/mobile test` passed after local note creation: 17 files / 53 tests.
- `pnpm --filter @tolaria/mobile exec expo export --platform ios --output-dir /tmp/tolaria-mobile-export` passed after local note creation.
- `pnpm --filter @tolaria/mobile test -- src/mobileNoteCreate.test.ts` passed after create-note UX state: 17 files / 53 tests.
- `pnpm --filter @tolaria/mobile typecheck` passed after create-note UX state.
- CodeScene after create-note UX state: `apps/mobile/src/MobileApp.tsx`, `apps/mobile/src/styles/noteListStyles.ts`, and `apps/mobile/src/styles/noteCreateStyles.ts` scored `10`.
- `pnpm --filter @tolaria/mobile test` passed after create-note UX state: 17 files / 53 tests.
- `pnpm --filter @tolaria/mobile exec expo export --platform ios --output-dir /tmp/tolaria-mobile-export` passed after create-note UX state.
## Risks / Watch Items