Week 10 finally wired up the Share and Bookmark buttons that have been sitting on the detail screen as inert UI since Week 3, and built the Saved tab behind them.
Bookmarks: A small Zustand store persisted to MMKV, so saved items survive restarts with no backend and no user accounts. The shape took some thinking. The obvious version is one list of ids, and it’s wrong here: highlight ids (185-214) fall inside the tablet id range (29-782), so a single list would treat tablet 187 and highlight 187 as the same bookmark. I considered tagging each entry ({ type, id }) and instead went with two separate lists, tablets and highlights. The list an id lives in is its type, so a collision is structurally impossible, lookups stay a plain .includes(), and no comparison helper is needed. That works because the Saved screen shows the two kinds in separate sections, so there’s never a need for a single chronological order across both.
Adding prepends rather than appends, so each section reads newest-saved-first.
The Saved screen: Two sections with headings. FlashList doesn’t do sections natively, and the obvious workaround - two lists inside a ScrollView - means nesting vertical lists, which is exactly what you’re told not to do. Instead I flatten everything into one array of tagged rows (header | tablet | highlight) and render a single FlashList over it. Section headings, and virtualisation intact.
Saved ids are resolved against the already-cached feeds rather than copying tablet data into storage. That keeps storage tiny and means a bookmark reflects the current content if a curator edits an entry.
Share: React Native ships a Share API, so no dependency - this was already the plan in my proposal. Tablets share their title, theme and link; highlights share title and link. One platform detail: Share.share() accepts a separate url field, but it’s iOS-only and Android drops it silently, so the URL goes inside message instead.
What to link to. Both content types already have a public page on cdli.earth, keyed on the same id the app uses, so there was no guesswork and no fallback needed:
cdli.earth/cdli-tablet/{id}cdli.earth/postings/{id}On deep links: the ideal is a link that opens the app when it’s installed and the website when it isn’t. That needs apple-app-site-association and assetlinks.json hosted on cdli.earth, carrying CDLI’s Apple team id and the Android signing fingerprint - so it’s blocked on infrastructure and app-store identity rather than on code. Sharing a normal web link now is the right move regardless: it works for every recipient today, and the same links start opening the app the day those files are deployed. Nothing shared now ever breaks.
I also tidied the detail screen while I was in it - the body had both a Share button and a View on CDLI button, and with share available in the header the duplicate came out and View on CDLI took the full width.
| Area | What I did | MR |
|---|---|---|
| Bookmarks | Zustand + MMKV store, two id lists to avoid tablet/highlight id collisions | !10 |
| Saved tab | Sectioned list via a flattened row union, so headings keep virtualisation | !10 |
| Share | Built-in RN Share, URL in message (the url field is iOS-only) |
!10 |
| Links | Canonical per-item web URLs for both content types; “View on CDLI” repointed | !10 |