Technical Architecture

Technical Architecture

Normalized state with useReducer, localStorage persistence layer, synchronous return contract, and all key engineering decisions

Part 1 — State Architecture
ANormalized BookmarkStore
Data structure
Three maps managed atomically by a pure reducer
Instead of three parallel useState calls that could desync, a single BookmarkStore is managed by useReducer. Every mutation touches all three maps atomically in one dispatch.
byId: Record<number, Tweet>
The source of truth for all tweet data. Every tweet object including bookmarkMeta, confidence, and matched keywords lives here. O(1) lookup by tweet ID.
tweetToFolder: Record<number, string>
Maps every tweet ID to its folder ID. O(1) lookup to find which folder a tweet is in. Used by removeBookmark and moveBookmark.
folderToTweets: Record<string, number[]>
Maps every folder ID to an array of tweet IDs. O(1) lookup to get all tweets in a folder. Auto-deletes the key when the array becomes empty.
Why not useState for each?
Three separate useState calls require manually syncing all three on every mutation — a bug magnet. The reducer keeps them consistent. One dispatch, one atomic update, no desync possible.
Four reducer actions
Complete mutation coverage — every state change in one place
ADD
Insert tweet into byId. Map tweetId to categoryId in tweetToFolder. Append tweetId to folderToTweets array. Used on every bookmark tap.
REMOVE
Delete from byId. Delete from tweetToFolder. Filter from folderToTweets. Auto-delete the folder key if the array is now empty.
MOVE
Update tweetToFolder to new categoryId. Remove tweetId from old folder array. Append to new folder array. Set autoAssigned to false on the tweet object.
DELETE_FOLDER
Loop all tweet IDs in the folder. Delete each from byId and tweetToFolder. Delete the folder key from folderToTweets. Used when a folder is deleted from the UI.
Part 2 — localStorage Persistence Layer
BThree keys, one useEffect
xrecall_store
BookmarkStore JSON
Full normalized store: byId, tweetToFolder, folderToTweets. Updated on every ADD, REMOVE, MOVE, and DELETE_FOLDER dispatch. Largest of the three keys.
xrecall_categories
Category[] JSON
Array of category objects with id, name, color, createdAt. Updated on folder creation, rename, delete, and reorder. Loaded as lazy useState initializer.
xrecall_corrections
CorrectionRecord[] JSON
Array of correction records with tweetId, oldCategory, newCategory, keywordPattern, timestamp. The AI learning memory. Updated on every manual folder move.
Write strategy
Single useEffect persists all three keys on every change
One useEffect with store, categories, and correctionHistory as dependencies. Fires after every state change and writes all three keys. lsSet is try-catch wrapped — fails silently on storage full or unavailable.
Read strategy
buildInitialStore checks localStorage first via lsGet. If saved store exists with at least one bookmark, returns immediately — seed skipped. Categories and corrections use lazy useState initializers that read localStorage before first render.
lsGet and lsSet helpers
Both are try-catch wrapped. lsGet returns null on parse failure or missing key. lsSet swallows errors silently. This prevents storage errors from crashing the app in environments where localStorage is restricted.
Part 3 — Key Engineering Decisions
CSynchronous return contract
Race condition fix
addBookmark returns meta before the render cycle
Original bug
The original code called addBookmark, dispatched the ADD action, then used setTimeout(10ms) to read bookmarkMeta back from state after the dispatch. React state does not flush synchronously — the setTimeout read stale data or undefined, causing the toast to show incorrect confidence and folder information.
Fix applied
addBookmark now constructs the BookmarkMeta object locally before dispatching to the reducer, and returns it directly: return {"{"}categoryId, categoryName, meta{"}"}. The toast receives accurate data on the same synchronous call frame. No setTimeout, no state readback, no race condition.
DActiveSheet union type
Type-level safety
Single discriminated union replaces three boolean flags
Original problem
LibraryScreen used showTweetMenu, showMoveFolder, and showExplanation as three separate boolean states. Any invalid combination — two true simultaneously — would render two stacked bottom sheets. This was a silent bug waiting to happen.
Fix applied
Replaced with a single ActiveSheet type: null | folder-actions | folder-rename | folder-delete | tweet-menu | tweet-move | tweet-explain. Only one sheet can be active at any time. Invalid combinations are impossible at the TypeScript type level, not just at runtime.
EPerformance decisions
useMemo on feed
Feed tweet list memoized
getDisplayTweets was previously called on every render — rebuilding the interleaved tweet+ad array on every state change including toast show, like tap, and scroll. Replaced with useMemo with an empty dependency array. Computed once on mount.
useCallback on handlers
All handlers wrapped in useCallback
Every handler passed as a prop — addBookmark, removeBookmark, moveBookmark, renameCategory, deleteCategory, getTweetsForCategory — is wrapped in useCallback with correct dependencies. Prevents unnecessary child re-renders on unrelated state changes.
forwardRef removed
TweetComponent simplified
The original TweetComponent used forwardRef but no parent ever passed a ref to it. forwardRef adds a wrapper and a React.memo-equivalent re-render check for no benefit when refs are not used. Removed entirely.
View count
Deterministic non-obvious multiplier
Original code used likes x 12 — obvious to any developer who opened DevTools. Replaced with (7 + (id % 19)) as a multiplier per tweet. Still deterministic and stable but not immediately obvious as a formula from the rendered numbers.
Part 4 — Component Architecture
FComponent responsibilities
App.tsx
Root state and orchestration
Owns all state: BookmarkStore via useReducer, categories, correctionHistory, trustScore. Handles all bookmark mutations. Passes data and callbacks down as props. Manages screen navigation.
FeedScreen.tsx
Feed display and interaction
Renders the tweet feed and handles all feed interactions: bookmark tap, like, retweet, context menu. Manages toast state. Shows profile drawer. Does not own any bookmark state.
LibraryScreen.tsx
Library display and management
Renders the bookmark library. Manages all bottom sheets via ActiveSheet union. Handles folder rename, delete, tweet move, AI explanation. Passes actions up to App via callbacks.
TweetComponent.tsx
Single tweet renderer
Context-aware: feed vs library mode. Stateful like and retweet with optional callbacks. Deterministic view count. Custom play button overlay for video tweets. No forwardRef.
ProfileDrawer.tsx
Profile navigation drawer
Spring-animated left drawer. Verified badge, follower stats, nav items. Bookmarks item navigates to Library. Created from scratch — was missing from the original codebase.
FeedTweetsData.tsx
Static feed data
25 tweets with unique Unsplash avatars per author. No two authors share the same photo ID. Verified across both FeedTweetsData and SEED_DATA to prevent the duplicate avatar bug.
Section legend
A — Normalized state architecture
B — localStorage persistence
C — Synchronous return contract
D — ActiveSheet union type
E — Performance decisions
F — Component responsibilities