feat: create local mobile notes

This commit is contained in:
lucaronin
2026-05-04 19:00:09 +02:00
parent 0efad2211a
commit 5da79cac74
5 changed files with 98 additions and 5 deletions

View File

@@ -18,7 +18,7 @@ import {
SlidersHorizontal,
} from 'phosphor-react-native'
import { MobileNote, notes as fallbackNotes, sidebarSections } from './demoData'
import { loadDemoVaultNotes, saveDemoVaultDraft } from './mobileDemoVault'
import { createDemoVaultNote, loadDemoVaultNotes, saveDemoVaultDraft } from './mobileDemoVault'
import { createMobileAutosaveQueue } from './mobileAutosaveQueue'
import type { MobileEditorDraft } from './mobileEditorDraft'
import {
@@ -88,6 +88,18 @@ export function MobileApp() {
setCompactNavigation((state) => transitionCompactNavigation(state, { type: 'selectNote', noteId: note.id }))
}
const saveDraft = useCallback((draft: MobileEditorDraft) => autosaveQueue.enqueue(draft), [autosaveQueue])
const createNote = useCallback(() => {
void createDemoVaultNote()
.then((note) => {
if (!note) {
return
}
setAvailableNotes((notes) => [note, ...notes.filter((item) => item.id !== note.id)])
setCompactNavigation((state) => transitionCompactNavigation(state, { type: 'selectNote', noteId: note.id }))
})
.catch(() => {})
}, [])
return (
<SafeAreaProvider>
@@ -98,6 +110,7 @@ export function MobileApp() {
<NoteListPanel
notes={availableNotes}
selectedNoteId={compactNavigation.selectedNoteId}
onCreateNote={createNote}
onSelectNote={selectNote}
/>
<EditorPanel note={selectedNote} saveState={selectedSaveState} onDraftChange={saveDraft} />
@@ -112,6 +125,7 @@ export function MobileApp() {
selectedNoteId={compactNavigation.selectedNoteId}
onNavigate={(event) => setCompactNavigation((state) => transitionCompactNavigation(state, event))}
onDraftChange={saveDraft}
onCreateNote={createNote}
onSelectNote={selectNote}
/>
)}
@@ -127,6 +141,7 @@ function CompactShell({
saveState,
onNavigate,
onDraftChange,
onCreateNote,
onSelectNote,
selectedNoteId,
}: {
@@ -136,6 +151,7 @@ function CompactShell({
saveState: MobileEditorSaveState
onNavigate: (event: CompactNavigationEvent) => void
onDraftChange: (draft: MobileEditorDraft) => void
onCreateNote: () => void
onSelectNote: (note: MobileNote) => void
selectedNoteId: string
}) {
@@ -174,6 +190,7 @@ function CompactShell({
<NoteListPanel
notes={notes}
selectedNoteId={selectedNoteId}
onCreateNote={onCreateNote}
onOpenSidebar={() => onNavigate({ type: 'openSidebar' })}
onSelectNote={onSelectNote}
/>
@@ -216,11 +233,13 @@ function SidebarPanel({ onClose }: { onClose?: () => void }) {
function NoteListPanel({
notes,
onCreateNote,
onOpenSidebar,
onSelectNote,
selectedNoteId,
}: {
notes: MobileNote[]
onCreateNote: () => void
onOpenSidebar?: () => void
onSelectNote: (note: MobileNote) => void
selectedNoteId: string
@@ -261,7 +280,7 @@ function NoteListPanel({
</Pressable>
)}
/>
<Pressable style={styles.composeButton}>
<Pressable onPress={onCreateNote} style={styles.composeButton}>
<PencilSimple size={28} color="#ffffff" />
</Pressable>
</View>

View File

@@ -1,6 +1,7 @@
import { demoNoteSources } from './demoData'
import type { MobileEditorDraft } from './mobileEditorDraft'
import { saveMobileEditorDraft } from './mobileEditorDraftSave'
import { createMobileNoteFile } from './mobileNoteCreate'
import { createMobileVaultConfig } from './mobileVaultConfig'
import { createNativeMobileVaultStorage } from './mobileNativeVaultStorage'
import { createStoredMobileVaultRepository } from './mobileVaultRepository'
@@ -24,6 +25,14 @@ export function saveDemoVaultDraft(draft: MobileEditorDraft) {
})
}
export async function createDemoVaultNote() {
const storage = createNativeMobileVaultStorage()
const file = createMobileNoteFile()
await storage.writeMarkdownFile(demoVault, file.path, file.content)
return createStoredMobileVaultRepository({ storage, vault: demoVault }).readNote(file.path.replace(/\.md$/, ''))
}
function demoVaultFiles(): MobileVaultFile[] {
return demoNoteSources.map((source) => ({
path: source.filename,

View File

@@ -0,0 +1,34 @@
import { describe, expect, it } from 'vitest'
import { createMobileNoteFile } from './mobileNoteCreate'
describe('mobile note create', () => {
it('creates a deterministic markdown file from the current time', () => {
const file = createMobileNoteFile({
now: new Date('2026-05-04T12:34:56.000Z'),
})
expect(file).toEqual({
path: 'note-mor6mem8.md',
content: [
'---',
'title: Untitled',
'type: Note',
'created: 2026-05-04T12:34:56.000Z',
'---',
'',
'# Untitled',
'',
].join('\n'),
})
})
it('supports a custom title for future create flows', () => {
const file = createMobileNoteFile({
now: new Date('2026-05-04T12:34:56.000Z'),
title: 'Meeting notes',
})
expect(file.content).toContain('title: Meeting notes')
expect(file.content).toContain('# Meeting notes')
})
})

View File

@@ -0,0 +1,25 @@
import type { MobileVaultFile } from './mobileVaultStorage'
export function createMobileNoteFile({
now = new Date(),
title = 'Untitled',
}: {
now?: Date
title?: string
} = {}): MobileVaultFile {
const id = `note-${now.getTime().toString(36)}`
return {
path: `${id}.md`,
content: [
'---',
`title: ${title}`,
'type: Note',
`created: ${now.toISOString()}`,
'---',
'',
`# ${title}`,
'',
].join('\n'),
}
}

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: Refresh saved mobile note projection
- Active slice: Create local mobile note
- Push policy: commit locally; do not push unless explicitly requested
- Validation target: iPad/iOS simulator first
@@ -62,14 +62,15 @@ This file is the resumable working log for Tolaria mobile. The strategy and road
- Split editor save-state styles into a separate style module to keep mobile style files at CodeScene `10`.
- 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.
## Next Action
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 the first local note create path against app-local vault storage, then wire it into the compact phone and iPad shells.
3. Add a focused simulator interaction path for editor typing/autosave once Expo Go's overlay no longer blocks clean screenshots.
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.
## Verification Log
@@ -203,6 +204,11 @@ Continue Phase 2 with the next mobile shell slice:
- CodeScene after saved note projection refresh: `apps/mobile/src/MobileApp.tsx`, `apps/mobile/src/mobileAutosaveQueue.ts`, `apps/mobile/src/mobileAutosaveQueue.test.ts`, `apps/mobile/src/mobileSavedDraftProjection.ts`, and `apps/mobile/src/mobileSavedDraftProjection.test.ts` scored `10`.
- `pnpm --filter @tolaria/mobile test` passed after saved note projection refresh: 16 files / 51 tests.
- `pnpm --filter @tolaria/mobile exec expo export --platform ios --output-dir /tmp/tolaria-mobile-export` passed after saved note projection refresh.
- `pnpm --filter @tolaria/mobile test -- src/mobileNoteCreate.test.ts src/mobileVaultRepository.test.ts` passed after local note creation: 17 files / 53 tests.
- `pnpm --filter @tolaria/mobile typecheck` passed after local note creation.
- 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.
## Risks / Watch Items