Bilingual (Farsi / English), RTL-first archive and community app for Shir ya Khat (شیر یا خط, “Heads or Tails”), a non-profit Persian blockchain podcast recorded as live panels since 2017.
content/ bundle| Area | What it does |
|---|---|
| Episodes | 41 episodes grouped by season, bilingual cards, Jalali dates in Farsi, continue-listening card |
| Player | Video (YouTube) / audio (podcast RSS) toggle, background audio with lock-screen controls, resume per episode, chapter markers, speed control |
| Transcript | Bilingual, synced to playback, tap a line to seek, long-press to bookmark / share / copy |
| Search | Full-archive search over every transcript (FA + EN) returning episode + timestamp hits |
| Glossary | 358 crypto terms with FA + EN definitions; tap-to-define inline in transcripts |
| Bookmarks | Per episode and per timestamp, local-first |
| Downloads | Resumable offline audio downloads with storage management |
| Share | On-device image card (art + quote + QR) and plain quote + link sharing |
| Community | Supabase schema, GitHub sign-in, minute comments, questions, bookmark sync — flagged off |
npm install
npm start # then press i (iOS simulator), a (Android) or scan the QR with Expo Go
No .env is needed. Everything the app shows is read from content/.
npm run check # lint + typecheck + tests (what CI runs)
npm run format # prettier
Deep links (scheme shiryakhat://):
| Link | Opens |
|---|---|
shiryakhat://episode/s07e07?t=600 |
An episode at 10:00 |
shiryakhat://episode/s07e07?pane=transcript |
An episode on its transcript (about, chapters, transcript) |
shiryakhat://?tab=search&q=tornado |
Search with a query (tab = episodes, search, glossary, library, settings) |
content/ committed static bundle the app reads (generated by the pipeline)
episodes.json canonical episode list
transcripts/ one bilingual cue list per episode
enrichment/ Claude output per episode + manifest.json (resume state)
glossary.json merged glossary
search-index.json token -> episodes inverted index
types.ts shapes shared by the pipeline and the app
pipeline/ Node/TS scripts that build content/ (own package.json)
src/
app/ expo-router routes (home, episode/[id], share, questions)
screens/ the five home panes
components/ feature components and components/ui primitives
player/ unified player (expo-audio + YouTube iframe)
content/ typed access to the bundle, search, glossary matching
state/ local-first stores (settings, progress, bookmarks, downloads)
community/ Supabase client, auth and API (only used when the flag is on)
theme/, i18n/ design tokens, FA/EN dictionaries
supabase/ SQL migrations, RLS policies, local config
docs/release.md tag -> TestFlight / Play internal flow
Nothing heavy is re-hosted.
The corpus holds 49 videos for 40 episodes plus one special, because seasons 4 to 6 were uploaded
twice. The pipeline de-duplicates by episode code, prefers the upload that has chapters and then the
original upload, and keeps the others in altYoutubeIds.
Direction is set with the direction style on every screen root (components/ui/Screen), driven by
the persisted locale, so the whole UI mirrors instantly when the language changes.
Text takes logical alignment (start / end) and a per-line dir, so an English line inside the
Farsi UI keeps its own paragraph direction.S07E07 · ۱۹ مرداد ۱۴۰۴ is joined with Unicode isolates.src/__tests__/rtl.test.tsx pins these rules down.cd pipeline && npm install
| Step | Command | Needs | Output |
|---|---|---|---|
| Fetch sources | npm run fetch-sources |
network | pipeline/.cache/website, pipeline/.cache/rss.xml |
| Episodes | npm run episodes |
corpus, cache | content/episodes.json |
| Transcripts | npm run transcripts |
corpus | content/transcripts/*.json |
| Enrichment | npm run enrich |
ANTHROPIC_API_KEY |
content/enrichment/*.json |
| Finalize | npm run finalize |
nothing | glossary, corrected transcripts, search index, index.generated.ts |
npm run build runs episodes, transcripts and finalize. The raw corpus
(/Volumes/Beezhan/data/podcasts, override with SYK_CORPUS_DIR) is read-only and never copied.
Enrichment sends each episode’s bilingual transcript to Claude (claude-sonnet-4-6, override
with SYK_ENRICH_MODEL) and gets back a bilingual summary, 3 to 5 timestamped key quotes, glossary
terms, and a list of mis-transcribed names and terms. It is resumable: manifest.json records
finished episodes and re-running skips them.
npm run enrich # everything still pending, 3 at a time
npm run enrich -- --only s07e07,s07e06 # specific episodes
npm run enrich -- --batch # Message Batches API (cheaper, asynchronous, resumable)
npm run enrich -- --force # redo finished episodes
Finalize is offline and idempotent. It merges glossaries, applies the corrections to the transcripts (for example the mis-heard show name “Shenakht”), rebuilds the search index and regenerates the require map Metro uses to load transcripts lazily.
Transcripts are raw Whisper output with automatic name fixes only. They are not proofread, and the app says so under each transcript.
Set these in .env once a Supabase project exists (see supabase/README.md):
EXPO_PUBLIC_COMMUNITY_ENABLED=true
EXPO_PUBLIC_SUPABASE_URL=...
EXPO_PUBLIC_SUPABASE_ANON_KEY=...
With the flag off the Supabase SDK is never initialised and the app shows a short note in Settings.
npx eas-cli build --profile development --platform ios # dev client for the simulator
npx eas-cli build --profile preview # internal builds
Pushing a v* tag runs .github/workflows/release.yml: lint, typecheck and test, then EAS Build
and EAS Submit to TestFlight and the Play internal track. The app version comes from
package.json (app.config.js), so npm version patch && git push --follow-tags is the whole
release command. One-time setup, the secrets table and troubleshooting are in
docs/release.md.
Background audio and lock-screen controls need a development or store build to behave as they will in production; Expo Go is fine for everything else.