refactor: add mobile editor adapter

This commit is contained in:
lucaronin
2026-05-04 09:46:22 +02:00
parent 165ec6f2b0
commit 3b94faf034
5 changed files with 152 additions and 16 deletions

View File

@@ -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 (
<View style={styles.editor}>
<Toolbar>
@@ -222,19 +222,7 @@ function EditorPanel({
{onOpenProperties ? <IconButton icon={<Info size={23} color={colors.textSoft} />} onPress={onOpenProperties} /> : null}
<IconButton icon={<DotsThreeVertical size={23} color={colors.textSoft} />} />
</Toolbar>
<ScrollView contentContainerStyle={styles.editorContent}>
<View style={styles.breadcrumbRow}>
<Text style={styles.breadcrumbText}>{note.type}</Text>
<Text style={styles.breadcrumbDivider}>/</Text>
<Text style={styles.breadcrumbText}>{note.id}</Text>
</View>
<Text style={styles.editorTitle}>{note.title}</Text>
{bodyLines.slice(1).map((line) => (
<Text key={line} style={line.startsWith('-') ? styles.editorBullet : styles.editorParagraph}>
{line}
</Text>
))}
</ScrollView>
<MobileEditorAdapter note={note} />
</View>
)
}

View File

@@ -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 (
<ScrollView contentContainerStyle={styles.editorContent}>
<View style={styles.breadcrumbRow}>
<Text style={styles.breadcrumbText}>{note.type}</Text>
<Text style={styles.breadcrumbDivider}>/</Text>
<Text style={styles.breadcrumbText}>{note.id}</Text>
</View>
<Text style={styles.editorTitle}>{document.title}</Text>
{document.blocks.map((block) => (
<Text key={block.id} style={block.kind === 'bullet' ? styles.editorBullet : styles.editorParagraph}>
{block.text}
</Text>
))}
</ScrollView>
)
}

View File

@@ -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',
},
])
})
})

View File

@@ -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}`
}

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: 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