diff --git a/CLAUDE.md b/CLAUDE.md index ea033e2..c5fd10d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -76,7 +76,11 @@ Images over 1MiB are downscaled in a Web Worker via `OffscreenCanvas`. The queue ### Profile tabs (`src/lib/post-tabs.ts`) -The grid holds **everything**, reels included, and the Reels tab is a *filtered view* of that same set — as on Instagram. Only the Reels tab filters. The tabs were mutually exclusive until v1.7.0, which hid a lot: 1100 of `groupfandom`'s 3813 posts and 533 of `for.member`'s 1225 never appeared in the grid at all. +The grid holds **everything**, reels included, and the Reels tab is a *filtered view* of that same set. Only the Reels tab filters. The tabs were mutually exclusive until v1.7.0, which hid a lot: 1100 of `groupfandom`'s 3813 posts and 533 of `for.member`'s 1225 never appeared in the grid at all. + +This *approximates* Instagram rather than matching it. Instagram's grid includes a reel only if the creator shared it to feed — a per-post choice, measured live on 2026-08-16: `official_band` had 21 reels in its first 34 grid tiles, `4utumn07` just 1 in 214. That flag appears nowhere in an archive (JD2 stores no metadata, and Instaloader's `product_type` says what a post *is*, not whether it was shared to feed), so showing everything is the closest reachable behaviour. Instagram's "N posts" counter equals its grid, which is why the header counts `postsForTab(allPosts, 'posts')` and not `allPosts` — the raw list still holds both copies of a double-fetched post. + +When checking the live site, note that grid reels link to `/reel//` while ordinary posts link to `//p//`. Matching only `/p/` silently drops every reel, which once produced a confident and completely wrong conclusion that Instagram never shows reels in the grid. Deciding *what is a reel* has no good answer for most archives. Instagram's own marker is `product_type` on the post's GraphQL node (`clips` = reel, `feed` = ordinary feed video, `igtv`, `story`) — `__typename` is `GraphVideo` for all three, and aspect ratio does not separate them either. But: diff --git a/package-lock.json b/package-lock.json index 3ffc189..721b94a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "instaarchive-viewer", - "version": "1.7.0", + "version": "1.7.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "instaarchive-viewer", - "version": "1.7.0", + "version": "1.7.1", "dependencies": { "@tailwindcss/vite": "^4.1.14", "@vitejs/plugin-react": "^5.0.4", diff --git a/package.json b/package.json index 65b74e1..be6d47a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "instaarchive-viewer", "private": true, - "version": "1.7.0", + "version": "1.7.1", "type": "module", "scripts": { "dev": "vite --port=3000 --host=0.0.0.0", diff --git a/src/App.tsx b/src/App.tsx index 290aac4..fc0eb9b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -179,6 +179,13 @@ export default function App() { */ const filteredPosts = useMemo(() => postsForTab(allPosts, activeTab), [allPosts, activeTab]); + /** + * Instagram's "N posts" counter equals what its grid holds, so count the grid + * rather than `allPosts` — the raw list still holds both copies of any post + * fetched into two directories. + */ + const totalPosts = useMemo(() => postsForTab(allPosts, 'posts').length, [allPosts]); + /** Story highlights, grouped into the circles shown under the bio. */ const highlightGroups = useMemo(() => { const groups = new Map(); @@ -519,7 +526,7 @@ export default function App() { {allProfilePics.length > 1 && } -
{allPosts.length} posts
{(followerCount || 0).toLocaleString()} followers
{(followingCount || 0).toLocaleString()} following
+
{totalPosts.toLocaleString()} posts
{(followerCount || 0).toLocaleString()} followers
{(followingCount || 0).toLocaleString()} following
{fullName || `@${username}`}
{bio || 'Archived profile viewer for local files.'}
{externalUrl && {externalUrl.replace(/^https?:\/\/(www\.)?/, '')}}