[object Object]

← back to Nineoh Guide

feat(mobile): fix scroll (flex ScrollView reveals full cast + all 114 episodes) + direct per-episode TVmaze link on each card

746a4e4f01a0f1a780c2994b39dab366211d1377 · 2026-07-27 12:06:11 -0700 · Steve Abrams

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit 746a4e4f01a0f1a780c2994b39dab366211d1377
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Mon Jul 27 12:06:11 2026 -0700

    feat(mobile): fix scroll (flex ScrollView reveals full cast + all 114 episodes) + direct per-episode TVmaze link on each card
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 apps/mobile/App.tsx | 164 ++++++++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 154 insertions(+), 10 deletions(-)

diff --git a/apps/mobile/App.tsx b/apps/mobile/App.tsx
index 7c56c02..2f82f50 100644
--- a/apps/mobile/App.tsx
+++ b/apps/mobile/App.tsx
@@ -32,14 +32,96 @@ const API_BASE =
 const WATCHLIST_KEY = "nineoh.watchlist.v1";
 const WATCHED_KEY = "nineoh.watchedThrough.v1";
 
-type TabKey = "episodes" | "cast" | "news" | "saved";
+type TabKey = "episodes" | "cast" | "media" | "news" | "saved";
 const TABS: { key: TabKey; icon: string; label: string }[] = [
   { key: "episodes", icon: "📺", label: "Episodes" },
   { key: "cast", icon: "🎭", label: "Cast" },
+  { key: "media", icon: "🎬", label: "Watch" },
   { key: "news", icon: "📰", label: "News" },
   { key: "saved", icon: "★", label: "Saved" },
 ];
 
+// Curated external media — YouTube, podcasts, streaming & reference.
+// Landing/search URLs (not fabricated deep links) so nothing 404s.
+type MediaLink = { label: string; sub: string; url: string };
+const MEDIA: { section: string; items: MediaLink[] }[] = [
+  {
+    section: "▶  YouTube",
+    items: [
+      {
+        label: "Official clips & iconic scenes",
+        sub: "YouTube",
+        url: "https://www.youtube.com/results?search_query=beverly+hills+90210+official+clip",
+      },
+      {
+        label: "Cast reunions & interviews",
+        sub: "YouTube",
+        url: "https://www.youtube.com/results?search_query=beverly+hills+90210+cast+reunion+interview",
+      },
+      {
+        label: "Opening theme & title sequences",
+        sub: "YouTube",
+        url: "https://www.youtube.com/results?search_query=beverly+hills+90210+opening+theme",
+      },
+      {
+        label: "Retrospectives & where-are-they-now",
+        sub: "YouTube",
+        url: "https://www.youtube.com/results?search_query=beverly+hills+90210+retrospective+where+are+they+now",
+      },
+    ],
+  },
+  {
+    section: "🎧  Podcasts",
+    items: [
+      {
+        label: "9021OMG — Jennie Garth & Tori Spelling",
+        sub: "The official rewatch podcast",
+        url: "https://podcasts.apple.com/us/search?term=9021OMG",
+      },
+      {
+        label: "All 90210 rewatch podcasts",
+        sub: "Apple Podcasts",
+        url: "https://podcasts.apple.com/us/search?term=beverly%20hills%2090210",
+      },
+      {
+        label: "Listen on Spotify",
+        sub: "Spotify",
+        url: "https://open.spotify.com/search/9021OMG",
+      },
+    ],
+  },
+  {
+    section: "📺  Where to watch",
+    items: [
+      {
+        label: "Streaming availability",
+        sub: "JustWatch",
+        url: "https://www.justwatch.com/us/search?q=Beverly%20Hills%2090210",
+      },
+      {
+        label: "Watch on Hulu",
+        sub: "hulu.com",
+        url: "https://www.hulu.com/search?q=beverly%20hills%2090210",
+      },
+    ],
+  },
+  {
+    section: "🔎  More",
+    items: [
+      {
+        label: "Series overview & history",
+        sub: "Wikipedia",
+        url: "https://en.wikipedia.org/wiki/Beverly_Hills,_90210",
+      },
+      {
+        label: "Full cast & episode guide",
+        sub: "IMDb",
+        url: "https://www.imdb.com/find/?q=Beverly%20Hills%2090210",
+      },
+    ],
+  },
+];
+
 type CastMember = {
   id: string;
   name: string;
@@ -66,7 +148,7 @@ function isAhead(
 }
 
 export default function App() {
-  const [tab, setTab] = useState<TabKey>("episodes");
+  const [tab, setTab] = useState<TabKey>("media");
 
   const [episodes, setEpisodes] = useState<Episode[]>([]);
   const [cast, setCast] = useState<CastMember[]>([]);
@@ -207,12 +289,26 @@ export default function App() {
         ) : (
           <Text style={styles.pending}>Recap coming soon</Text>
         )}
-        <Pressable
-          onPress={() => markWatched(ep.seasonNumber, ep.episodeNumber)}
-          hitSlop={6}
-        >
-          <Text style={styles.markWatched}>✓ watched to here</Text>
-        </Pressable>
+        <View style={styles.epActions}>
+          <Pressable
+            onPress={() => markWatched(ep.seasonNumber, ep.episodeNumber)}
+            hitSlop={6}
+          >
+            <Text style={styles.markWatched}>✓ watched to here</Text>
+          </Pressable>
+          {ep.externalIds?.tvmaze ? (
+            <Pressable
+              onPress={() =>
+                Linking.openURL(
+                  `https://www.tvmaze.com/episodes/${ep.externalIds.tvmaze}`
+                )
+              }
+              hitSlop={6}
+            >
+              <Text style={styles.epLink}>▶ View this episode ↗</Text>
+            </Pressable>
+          ) : null}
+        </View>
       </BlurView>
     );
   };
@@ -267,7 +363,11 @@ export default function App() {
         </View>
       ) : null}
 
-      <ScrollView contentContainerStyle={styles.body}>
+      <ScrollView
+        style={styles.scroll}
+        contentContainerStyle={styles.body}
+        showsVerticalScrollIndicator={false}
+      >
         {tab === "episodes" ? (
           <>
             <Text style={styles.disclaimer}>{APP.disclaimerShort}</Text>
@@ -382,6 +482,34 @@ export default function App() {
             )}
           </>
         ) : null}
+
+        {tab === "media" ? (
+          <>
+            <Text style={styles.sectionTitle}>🎬 Watch, listen & explore</Text>
+            <Text style={styles.disclaimer}>
+              Curated links out to YouTube, podcast apps and streaming guides. An
+              unofficial fan guide — not affiliated with or endorsed by the show.
+            </Text>
+            {MEDIA.map((g) => (
+              <View key={g.section}>
+                <Text style={styles.mediaGroup}>{g.section}</Text>
+                {g.items.map((m) => (
+                  <Pressable
+                    key={m.url}
+                    onPress={() => Linking.openURL(m.url)}
+                    style={styles.newsCard}
+                  >
+                    <Text style={styles.newsHeadline}>{m.label}</Text>
+                    <Text style={styles.newsMeta}>
+                      {m.sub}
+                      {"  ↗"}
+                    </Text>
+                  </Pressable>
+                ))}
+              </View>
+            ))}
+          </>
+        ) : null}
       </ScrollView>
 
       {/* Bottom tab bar */}
@@ -437,6 +565,7 @@ const styles = StyleSheet.create({
     paddingHorizontal: 12,
     paddingVertical: 5,
   },
+  scroll: { flex: 1 },
   body: { padding: 20, paddingTop: 12, paddingBottom: 28 },
   sectionTitle: {
     fontSize: 17,
@@ -445,6 +574,13 @@ const styles = StyleSheet.create({
     marginBottom: 12,
   },
   disclaimer: { fontSize: 12, color: "#666", marginBottom: 16 },
+  mediaGroup: {
+    fontSize: 14,
+    fontWeight: "700",
+    color: "#7b3f6e",
+    marginTop: 16,
+    marginBottom: 8,
+  },
   empty: { fontSize: 14, color: "#8a1c1c", lineHeight: 20 },
   card: {
     backgroundColor: "rgba(255,255,255,0.5)",
@@ -471,7 +607,15 @@ const styles = StyleSheet.create({
     borderRadius: 4,
     padding: 6,
   },
-  markWatched: { fontSize: 11, color: "#888", marginTop: 6 },
+  epActions: {
+    flexDirection: "row",
+    justifyContent: "space-between",
+    alignItems: "center",
+    marginTop: 8,
+    gap: 12,
+  },
+  markWatched: { fontSize: 11, color: "#888" },
+  epLink: { fontSize: 12, color: "#7b3f6e", fontWeight: "700" },
   chipGuard: { backgroundColor: "#f3e9e9", borderColor: "#d8b4b4" },
   chipGuardText: { fontSize: 13, color: "#8a1c1c" },
   // cast

← 96bb1e7 docs(mobile): EAS ad-hoc build recipe — press 'a' to select  ·  back to Nineoh Guide  ·  feat(mobile): add Watch tab — scrollable YouTube / podcasts 70d1038 →