refactor: add mobile editor adapter
This commit is contained in:
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
25
apps/mobile/src/MobileEditorAdapter.tsx
Normal file
25
apps/mobile/src/MobileEditorAdapter.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
65
apps/mobile/src/mobileEditorDocument.test.ts
Normal file
65
apps/mobile/src/mobileEditorDocument.test.ts
Normal 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',
|
||||
},
|
||||
])
|
||||
})
|
||||
})
|
||||
53
apps/mobile/src/mobileEditorDocument.ts
Normal file
53
apps/mobile/src/mobileEditorDocument.ts
Normal 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}`
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user