Parent epic: #1446 (Amicus — AI Study Partner v1)
Phase: 2 · Size: S · Depends on: #1455 (citation pills)
Tapping a citation pill in an Amicus message navigates the user to the source — the exact panel, debate topic, word study, or lexicon entry that backed the citation. The navigation is deep-link style and must respect the existing tab structure.
Files to create
app/src/services/amicus/citationNav.ts — resolves a chunk_id to a navigation action
app/src/services/amicus/__tests__/citationNav.test.ts — unit tests
Files to modify
app/src/components/amicus/CitationPill.tsx — wire onTap to call the resolver
Resolver API
// citationNav.ts
import type { NavigationProp } from '@react-navigation/native';
export interface CitationTarget {
chunk_id: string;
source_type: string;
source_id: string;
metadata?: Record<string, unknown>;
}
/**
* Resolve a citation target into a navigation action and execute it.
* Handles cross-stack navigation (e.g., Amicus → Read tab → Chapter screen).
*/
export async function navigateToCitation(
target: CitationTarget,
navigation: NavigationProp<ParamListBase>
): Promise<void>;
The resolver switches on source_type and builds the correct navigation action:
| source_type |
Destination |
section_panel |
ReadTab → Chapter screen with openPanel param set to { sectionNum, panelType } (reuses existing OpenPanelParam from nav types) |
chapter_panel |
ReadTab → Chapter screen with openPanel set to { panelType } (chapter-level) |
word_study |
ExploreTab → WordStudyDetail with wordId |
lexicon_entry |
ExploreTab → DictionaryDetail with entryId |
debate_topic |
ExploreTab → DebateDetail with topicId |
cross_ref_thread_note |
ExploreTab → ParallelDetail with entryId |
journey_stop |
HomeTab → JourneyDetail stop (verify existing route) |
meta_faq |
In-app modal showing the FAQ content (no dedicated screen — meta-FAQ is reference-only content) |
Cross-stack navigation
To jump from the Amicus tab to e.g. the Read tab's Chapter screen:
navigation.getParent()?.navigate('ReadTab', {
screen: 'Chapter',
params: { bookId, chapterNum, openPanel: { sectionNum, panelType } },
});
This uses the existing root-level tab → stack → screen nesting pattern. Verify against TabNavigator.tsx + ReadStack.tsx when implementing.
source_id parsing
Chunk_ids are deterministic per #1447: {source_type}:{source_id}. Example: section_panel:romans-9-s1-calvin.
For section_panel, the source_id format is {book_id}-{chapter_num}-s{section_num}-{panel_type}. Parse via a simple regex; do not invent fields. Add a parser in citationNav.ts:
export function parseSectionPanelId(sourceId: string):
| { bookId: string; chapterNum: number; sectionNum: number; panelType: string }
| null;
If parsing fails, log via logger.warn and show a toast "Source unavailable" — never crash.
Resolution from DB when needed
Some source types need a lookup to resolve display info (e.g., a lexicon_entry chunk_id maps to an entry_id field that isn't in the chunk_id string). The resolver queries scripture.db for these cases. Keep queries scoped and fast (<50ms).
Meta-FAQ modal
Since meta-FAQ content doesn't have a destination screen, citation taps open a modal with:
- Title
- Full article body (scrollable)
- Close button
- Renders via a new lightweight
MetaFaqModal.tsx component
Conventions to follow
- Navigation via
useNavigation<NavigationProp<RootParamList>>() hook in CitationPill
- Cross-stack nav pattern:
navigation.getParent()?.navigate('TabName', { screen, params }) — match existing deep-link entry points in the codebase
- Errors via logger + user-visible toast (reuse existing toast component if present; otherwise subtle bottom banner)
- No
any; strict TS
Acceptance criteria
Out of scope
- Visual feedback on citation hover (mobile has no hover)
- Preview-on-long-press (future UX enhancement, not v1)
Parent epic: #1446 (Amicus — AI Study Partner v1)
Phase: 2 · Size: S · Depends on: #1455 (citation pills)
Tapping a citation pill in an Amicus message navigates the user to the source — the exact panel, debate topic, word study, or lexicon entry that backed the citation. The navigation is deep-link style and must respect the existing tab structure.
Files to create
app/src/services/amicus/citationNav.ts— resolves a chunk_id to a navigation actionapp/src/services/amicus/__tests__/citationNav.test.ts— unit testsFiles to modify
app/src/components/amicus/CitationPill.tsx— wireonTapto call the resolverResolver API
The resolver switches on
source_typeand builds the correct navigation action:section_panelopenPanelparam set to{ sectionNum, panelType }(reuses existingOpenPanelParamfrom nav types)chapter_panelopenPanelset to{ panelType }(chapter-level)word_studywordIdlexicon_entryentryIddebate_topictopicIdcross_ref_thread_noteentryIdjourney_stopmeta_faqCross-stack navigation
To jump from the Amicus tab to e.g. the Read tab's Chapter screen:
This uses the existing root-level tab → stack → screen nesting pattern. Verify against
TabNavigator.tsx+ReadStack.tsxwhen implementing.source_id parsing
Chunk_ids are deterministic per #1447:
{source_type}:{source_id}. Example:section_panel:romans-9-s1-calvin.For
section_panel, the source_id format is{book_id}-{chapter_num}-s{section_num}-{panel_type}. Parse via a simple regex; do not invent fields. Add a parser incitationNav.ts:If parsing fails, log via
logger.warnand show a toast "Source unavailable" — never crash.Resolution from DB when needed
Some source types need a lookup to resolve display info (e.g., a lexicon_entry chunk_id maps to an
entry_idfield that isn't in the chunk_id string). The resolver queries scripture.db for these cases. Keep queries scoped and fast (<50ms).Meta-FAQ modal
Since meta-FAQ content doesn't have a destination screen, citation taps open a modal with:
MetaFaqModal.tsxcomponentConventions to follow
useNavigation<NavigationProp<RootParamList>>()hook in CitationPillnavigation.getParent()?.navigate('TabName', { screen, params })— match existing deep-link entry points in the codebaseany; strict TSAcceptance criteria
section_panelcitation navigates to Read → Chapter withopenPanelparam; panel opensword_studynavigates to Explore → WordStudyDetaillexicon_entrynavigates to Explore → DictionaryDetaildebate_topicnavigates to Explore → DebateDetailmeta_faqopens modal with FAQ bodyanytypes; strict TS; lint cleanOut of scope