Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
28 changes: 16 additions & 12 deletions _tools/NEXT_SESSION_PROMPT.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,13 @@ Prophecy chains (50 chains, 283 links, browse + detail screens) · Enhanced note
|-------|-------------|--------|
| 0 | Chapter panel button categorization (3 groups) | **Complete** |
| 1 | TabbedPanelRenderer composite infrastructure | **Complete** |
| 2 | Composite Context Panel (hist → tabbed) | Next |
| 3 | Composite Connections Panel (cross → tabbed) | Next |
| 4-23 | See `_tools/DEEP_STUDY_FEATURES_PLAN.md` | Planned |
| 2 | Context Hub (hist+ctx merge → tabbed composite) | **Complete** |
| 3 | Connections Hub (cross → tabbed with echoes slot) | **Complete** |
| 4 | Literary Structure upgrade (chiasm view) | **Complete** |
| 5 | Genre Guidance Banner | **Complete** |
| 6 | Depth Indicator Dots | **Complete** |
| 7 | Study Coach Mode | **Complete** |
| 8-23 | See `_tools/DEEP_STUDY_FEATURES_PLAN.md` | Planned |

### Content Remediation

Expand All @@ -53,14 +57,14 @@ Batches 0–5 complete (word study bugfix, scholar bios, ghost panels, people en

## What's Next

1. **Deep Study Features Phase 2 + 3** — Composite Context Panel (hist → tabbed with historical/audience/ANE tabs) + Composite Connections Panel (cross → tabbed with refs/echoes tabs)
2. **Thin Panel Enrichment** (~259 panels) — 138 section panels (mostly thin cross-refs in Chronicles/Nehemiah/Esther) + 121 chapter panels (mostly thin ppl/rec in prophets)
3. **Inline Style Migration** (Arch Batch 7) — 318 inline `style={{ }}` objects, migrate to `StyleSheet.create()`
4. **Cross-reference thread expansion** (Batch 13)
5. **Map story enhancements** (Batch 14)
6. **Isaiah 23-66 enrichment debt** (44 chapters, thin panels)
7. **Kings/Chronicles MacArthur enrichment debt** (112 chapters)
8. **Test foundation → CI/CD → branch protection** (Arch 8A-C)
1. **Deep Study Features Phase 8+** — See `_tools/DEEP_STUDY_FEATURES_PLAN.md` for next phases
3. **Thin Panel Enrichment** (~259 panels) — 138 section panels (mostly thin cross-refs in Chronicles/Nehemiah/Esther) + 121 chapter panels (mostly thin ppl/rec in prophets)
4. **Inline Style Migration** (Arch Batch 7) — 318 inline `style={{ }}` objects, migrate to `StyleSheet.create()`
5. **Cross-reference thread expansion** (Batch 13)
6. **Map story enhancements** (Batch 14)
7. **Isaiah 23-66 enrichment debt** (44 chapters, thin panels)
8. **Kings/Chronicles MacArthur enrichment debt** (112 chapters)
9. **Test foundation → CI/CD → branch protection** (Arch 8A-C)

---

Expand Down Expand Up @@ -93,4 +97,4 @@ git add -A && git commit -m "..." && git push
cd app && eas update --branch production
```

DB version: 0.20 · 54 scholars
DB version: 0.22 · 54 scholars
25 changes: 17 additions & 8 deletions _tools/build_sqlite.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,10 @@ def bump_db_version():
testament TEXT NOT NULL,
total_chapters INTEGER NOT NULL,
book_order INTEGER NOT NULL,
is_live BOOLEAN DEFAULT 0
is_live BOOLEAN DEFAULT 0,
genre TEXT,
genre_label TEXT,
genre_guidance TEXT
);

CREATE TABLE chapters (
Expand All @@ -103,7 +106,8 @@ def bump_db_version():
timeline_link_event TEXT,
timeline_link_text TEXT,
map_story_link_id TEXT,
map_story_link_text TEXT
map_story_link_text TEXT,
coaching_json TEXT
);

CREATE TABLE sections (
Expand Down Expand Up @@ -336,10 +340,10 @@ def populate_books(cur):
books = _load_json(META / 'books.json')
for b in books:
cur.execute(
'INSERT INTO books (id, name, testament, total_chapters, book_order, is_live) '
'VALUES (?, ?, ?, ?, ?, ?)',
'INSERT INTO books (id, name, testament, total_chapters, book_order, is_live, genre, genre_label, genre_guidance) '
'VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)',
(b['id'], b['name'], b['testament'], b['total_chapters'],
b['book_order'], b['is_live'])
b['book_order'], b['is_live'], b.get('genre'), b.get('genre_label'), b.get('genre_guidance'))
)
return len(books)

Expand Down Expand Up @@ -378,17 +382,22 @@ def populate_chapters(cur):
tl = data.get('timeline_link')
ms = data.get('map_story_link')

# Coaching tips (optional)
coaching = data.get('coaching')
coaching_str = _json_str(coaching) if coaching else None

cur.execute(
'INSERT INTO chapters (id, book_id, chapter_num, title, subtitle, '
'timeline_link_event, timeline_link_text, '
'map_story_link_id, map_story_link_text) '
'VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)',
'map_story_link_id, map_story_link_text, coaching_json) '
'VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
(chapter_id, book_id, ch_num,
data.get('title'), data.get('subtitle'),
tl['event_id'] if tl else None,
tl['text'] if tl else None,
ms['story_id'] if ms else None,
ms['text'] if ms else None)
ms['text'] if ms else None,
coaching_str)
)
chapter_count += 1

Expand Down
2 changes: 1 addition & 1 deletion _tools/db_version.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"version": "0.21"
"version": "0.24"
}
9 changes: 5 additions & 4 deletions _tools/validate_sqlite.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ def main():

# Meta tables — these counts drift as content is enriched. Update after changes.
check("282 people", q1(cur, "SELECT COUNT(*) FROM people") == 282)
check("51 scholars", q1(cur, "SELECT COUNT(*) FROM scholars") == 51)
check("54 scholars", q1(cur, "SELECT COUNT(*) FROM scholars") == 54)
check("71+ places", q1(cur, "SELECT COUNT(*) FROM places") >= 60)
check("28+ map stories", q1(cur, "SELECT COUNT(*) FROM map_stories") >= 15)
check("14+ word studies", q1(cur, "SELECT COUNT(*) FROM word_studies") >= 14)
Expand Down Expand Up @@ -355,11 +355,12 @@ def main():
gen1_secs = q1(cur, "SELECT COUNT(*) FROM sections WHERE chapter_id='genesis_1'")
check("Genesis 1 has 5 sections", gen1_secs == 5, f"got {gen1_secs}")

# Genesis 1 S1: 9 panel types (heb, hist, ctx, cross, mac, sarna, alter, calvin, net)
# Genesis 1 S1: 8 panel types (heb, hist, cross, mac, sarna, alter, calvin, net)
# ctx merged into hist as composite object in Phase 2
gen1_s1_types = [r[0] for r in q(cur,
"SELECT panel_type FROM section_panels WHERE section_id='genesis_1_s1' ORDER BY panel_type")]
expected = ['alter', 'calvin', 'cross', 'ctx', 'heb', 'hist', 'mac', 'net', 'sarna']
check("Genesis 1 S1 has 9 panel types", gen1_s1_types == expected,
expected = ['alter', 'calvin', 'cross', 'heb', 'hist', 'mac', 'net', 'sarna']
check("Genesis 1 S1 has 8 panel types", gen1_s1_types == expected,
f"got {gen1_s1_types}")

# Psalms 23: verify it exists and has sections
Expand Down
53 changes: 53 additions & 0 deletions app/src/components/DepthDots.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/**
* DepthDots — Tiny dot indicator showing panel exploration progress.
*
* Row of 4px circles: filled gold = opened, unfilled = not yet opened.
*/

import React from 'react';
import { View, StyleSheet } from 'react-native';
import { base } from '../theme';

interface Props {
explored: number;
total: number;
}

export function DepthDots({ explored, total }: Props) {
if (total === 0) return null;

const dots: boolean[] = [];
for (let i = 0; i < total; i++) {
dots.push(i < explored);
}

return (
<View style={styles.container}>
{dots.map((filled, i) => (
<View
key={i}
style={[styles.dot, filled ? styles.filled : styles.unfilled]}
/>
))}
</View>
);
}

const styles = StyleSheet.create({
container: {
flexDirection: 'row',
alignItems: 'center',
gap: 3,
},
dot: {
width: 4,
height: 4,
borderRadius: 2,
},
filled: {
backgroundColor: base.gold,
},
unfilled: {
backgroundColor: '#444',
},
});
70 changes: 70 additions & 0 deletions app/src/components/GenreBanner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/**
* GenreBanner — Dismissible genre guidance shown at top of chapter.
*
* Displays genre label + reading guidance. Dismissible per-session
* (state resets on app restart). Hidden when no genre data exists.
*/

import React, { useState } from 'react';
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
import { base, spacing, fontFamily } from '../theme';

interface Props {
genreLabel: string;
genreGuidance: string;
}

export function GenreBanner({ genreLabel, genreGuidance }: Props) {
const [dismissed, setDismissed] = useState(false);

if (dismissed) return null;

return (
<View style={styles.container}>
<View style={styles.header}>
<Text style={styles.label}>{genreLabel.toUpperCase()}</Text>
<TouchableOpacity
onPress={() => setDismissed(true)}
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
>
<Text style={styles.dismiss}>✕</Text>
</TouchableOpacity>
</View>
<Text style={styles.guidance}>{genreGuidance}</Text>
</View>
);
}

const styles = StyleSheet.create({
container: {
backgroundColor: base.gold + '0A',
borderWidth: 1,
borderColor: base.gold + '25',
borderRadius: 8,
padding: spacing.sm,
marginBottom: spacing.md,
},
header: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 4,
},
label: {
fontFamily: fontFamily.uiSemiBold,
fontSize: 10,
color: base.gold,
letterSpacing: 0.5,
},
dismiss: {
fontFamily: fontFamily.ui,
fontSize: 14,
color: base.textMuted,
},
guidance: {
fontFamily: fontFamily.body,
fontSize: 13,
color: base.textMuted,
lineHeight: 18,
},
});
8 changes: 7 additions & 1 deletion app/src/components/SectionBlock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,18 @@ interface Props {
renderButtonRow?: (panels: SectionPanel[], sectionId: string) => React.ReactNode;
/** Render prop for active panel content */
renderPanel?: (panel: SectionPanel) => React.ReactNode;
/** Study depth tracking */
depthExplored?: number;
depthTotal?: number;
onDepthRecord?: (sectionId: string, panelType: string) => void;
}

export function SectionBlock({
section, panels, verses, vhlGroups, activeVhlGroups,
notedVerses, activePanel, fontSize,
onPanelToggle, onNotePress, onRefPress,
renderButtonRow, renderPanel,
depthExplored, depthTotal, onDepthRecord,
}: Props) {
// Filter verses for this section
const sectionVerses = verses.filter(
Expand All @@ -49,6 +54,7 @@ export function SectionBlock({
const matchType = panelTypes.find((pt) => availableTypes.has(pt));
if (matchType) {
onPanelToggle(sectionId, matchType);
onDepthRecord?.(sectionId, matchType);
}
},
[panels, onPanelToggle]
Expand All @@ -62,7 +68,7 @@ export function SectionBlock({

return (
<View style={styles.container}>
<SectionHeader header={section.header} />
<SectionHeader header={section.header} explored={depthExplored} total={depthTotal} />

<VerseBlock
verses={sectionVerses}
Expand Down
12 changes: 11 additions & 1 deletion app/src/components/SectionHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,35 @@
/**
* SectionHeader — Cinzel heading with gold accent and bottom border.
* Optional DepthDots indicator right-aligned when depth data provided.
*/

import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { DepthDots } from './DepthDots';
import { base, spacing, fontFamily } from '../theme';

interface Props {
header: string;
explored?: number;
total?: number;
}

export function SectionHeader({ header }: Props) {
export function SectionHeader({ header, explored, total }: Props) {
return (
<View style={styles.container} accessibilityRole="header">
<Text style={styles.text}>{header}</Text>
{total != null && total > 0 ? (
<DepthDots explored={explored ?? 0} total={total} />
) : null}
</View>
);
}

const styles = StyleSheet.create({
container: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
borderBottomWidth: 1,
borderBottomColor: base.border,
paddingHorizontal: spacing.md,
Expand Down
Loading