diff --git a/apps/mobile/src/MobileApp.tsx b/apps/mobile/src/MobileApp.tsx index ecfc478a..afc03e68 100644 --- a/apps/mobile/src/MobileApp.tsx +++ b/apps/mobile/src/MobileApp.tsx @@ -18,6 +18,7 @@ import { SlidersHorizontal, } from 'phosphor-react-native' import { MobileNote, notes, sidebarSections } from './demoData' +import { MobileEditorAdapter } from './MobileEditorAdapter' import { createCompactNavigationState, transitionCompactNavigation, @@ -213,7 +214,6 @@ function EditorPanel({ onBack?: () => void onOpenProperties?: () => void }) { - const bodyLines = note.content.split('\n').filter((line) => line.trim() && !line.startsWith('---') && !line.includes(': ')) return ( @@ -222,19 +222,7 @@ function EditorPanel({ {onOpenProperties ? } onPress={onOpenProperties} /> : null} } /> - - - {note.type} - / - {note.id} - - {note.title} - {bodyLines.slice(1).map((line) => ( - - {line} - - ))} - + ) } diff --git a/apps/mobile/src/MobileEditorAdapter.tsx b/apps/mobile/src/MobileEditorAdapter.tsx new file mode 100644 index 00000000..2020f486 --- /dev/null +++ b/apps/mobile/src/MobileEditorAdapter.tsx @@ -0,0 +1,25 @@ +import { useMemo } from 'react' +import { ScrollView, Text, View } from 'react-native' +import type { MobileNote } from './mobileNoteProjection' +import { createMobileEditorDocument } from './mobileEditorDocument' +import { styles } from './styles' + +export function MobileEditorAdapter({ note }: { note: MobileNote }) { + const document = useMemo(() => createMobileEditorDocument(note), [note]) + + return ( + + + {note.type} + / + {note.id} + + {document.title} + {document.blocks.map((block) => ( + + {block.text} + + ))} + + ) +} diff --git a/apps/mobile/src/mobileEditorDocument.test.ts b/apps/mobile/src/mobileEditorDocument.test.ts new file mode 100644 index 00000000..a85bb633 --- /dev/null +++ b/apps/mobile/src/mobileEditorDocument.test.ts @@ -0,0 +1,65 @@ +import { describe, expect, it } from 'vitest' +import { createMobileEditorDocument } from './mobileEditorDocument' + +describe('mobile editor document', () => { + it('strips frontmatter and title heading from the displayed editor body', () => { + expect( + createMobileEditorDocument({ + title: 'Workflow Orchestration Essay', + content: [ + '---', + 'type: Essay', + '---', + '', + '# Workflow Orchestration Essay', + '', + 'The current narrative: everything routed through an LLM.', + ].join('\n'), + }), + ).toEqual({ + title: 'Workflow Orchestration Essay', + blocks: [ + { + id: '0:The current narrative: everything routed through an LLM.', + kind: 'paragraph', + text: 'The current narrative: everything routed through an LLM.', + }, + ], + }) + }) + + it('keeps colon paragraphs instead of treating them as frontmatter', () => { + const document = createMobileEditorDocument({ + title: 'Notes for Monday', + content: '# Notes for Monday\n\nBottom line up front: ship the smallest useful slice.', + }) + + expect(document.blocks).toEqual([ + { + id: '0:Bottom line up front: ship the smallest useful slice.', + kind: 'paragraph', + text: 'Bottom line up front: ship the smallest useful slice.', + }, + ]) + }) + + it('normalizes markdown bullets for the native placeholder surface', () => { + const document = createMobileEditorDocument({ + title: 'Plan', + content: '# Plan\n\n- Sidebar\n* Note list', + }) + + expect(document.blocks).toEqual([ + { + id: '0:- Sidebar', + kind: 'bullet', + text: 'Sidebar', + }, + { + id: '1:* Note list', + kind: 'bullet', + text: 'Note list', + }, + ]) + }) +}) diff --git a/apps/mobile/src/mobileEditorDocument.ts b/apps/mobile/src/mobileEditorDocument.ts new file mode 100644 index 00000000..1a2762eb --- /dev/null +++ b/apps/mobile/src/mobileEditorDocument.ts @@ -0,0 +1,53 @@ +import { splitFrontmatter } from '@tolaria/markdown' + +export type MobileEditorBlock = { + id: string + kind: 'bullet' | 'paragraph' + text: string +} + +export type MobileEditorDocument = { + title: string + blocks: MobileEditorBlock[] +} + +export type MobileEditorDocumentInput = { + title: string + content: string +} + +export function createMobileEditorDocument(input: MobileEditorDocumentInput): MobileEditorDocument { + const [, body] = splitFrontmatter(input.content) + + return { + title: input.title, + blocks: createBlocks(body, input.title), + } +} + +function createBlocks(body: string, title: string) { + return body + .split('\n') + .map((line) => line.trim()) + .filter((line) => line && !isTitleHeading(line, title)) + .map(createBlock) +} + +function createBlock(line: string, index: number): MobileEditorBlock { + const bulletText = bulletContent(line) + + return { + id: `${index}:${line}`, + kind: bulletText ? 'bullet' : 'paragraph', + text: bulletText ?? line, + } +} + +function bulletContent(line: string) { + const match = /^[-*]\s+(.+)$/.exec(line) + return match?.[1] ?? null +} + +function isTitleHeading(line: string, title: string) { + return line === `# ${title}` +} diff --git a/docs/MOBILE_PROGRESS.md b/docs/MOBILE_PROGRESS.md index 6d2cbfd2..9dcf1cc5 100644 --- a/docs/MOBILE_PROGRESS.md +++ b/docs/MOBILE_PROGRESS.md @@ -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: Connect parsed Git remotes to mobile vault configuration +- Active slice: Add mobile editor adapter boundary for TenTap - Push policy: commit locally; do not push unless explicitly requested - Validation target: iPad/iOS simulator first @@ -45,13 +45,14 @@ This file is the resumable working log for Tolaria mobile. The strategy and road - Re-tested the iPad simulator path; the app now launches in Expo Go on `iPad Pro 13-inch (M4)`. - Added a pure mobile Git remote parser that codifies the auth choice: GitHub remotes use the GitHub OAuth App path, arbitrary Git remotes use the SSH-key path. - Added a pure mobile vault configuration model that keeps vault storage app-local, distinguishes local-only vs remote-backed sync, and derives the required Git auth path from the parsed remote. +- Extracted the mobile editor surface behind `MobileEditorAdapter` with a tested document projection so TenTap can replace the placeholder surface without changing shell navigation. ## 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. Begin the TenTap editor spike behind a `MobileEditorAdapter` once the shell/storage boundary is stable. +2. Install and spike TenTap behind `MobileEditorAdapter`, keeping the adapter contract intact. 3. Add the first native storage adapter around the vault config/repository contracts after the editor spike confirms the app shape. ## Verification Log @@ -124,6 +125,10 @@ Continue Phase 2 with the next mobile shell slice: - `pnpm --filter @tolaria/mobile typecheck` passed after vault config extraction. - CodeScene after vault config extraction: `apps/mobile/src/mobileVaultConfig.ts` and `apps/mobile/src/mobileVaultConfig.test.ts` scored `10`. - `pnpm --filter @tolaria/mobile exec expo export --platform ios --output-dir /tmp/tolaria-mobile-export` passed after vault config extraction. +- `pnpm --filter @tolaria/mobile test -- src/mobileEditorDocument.test.ts` passed after editor adapter extraction: 8 files / 27 tests. +- `pnpm --filter @tolaria/mobile typecheck` passed after editor adapter extraction. +- CodeScene after editor adapter extraction: `apps/mobile/src/MobileApp.tsx`, `apps/mobile/src/MobileEditorAdapter.tsx`, `apps/mobile/src/mobileEditorDocument.ts`, and `apps/mobile/src/mobileEditorDocument.test.ts` scored `10`. +- `pnpm --filter @tolaria/mobile exec expo export --platform ios --output-dir /tmp/tolaria-mobile-export` passed after editor adapter extraction. ## Risks / Watch Items