ergosteurandClaude Opus 5 0dc6a8372c
Docker Build and Publish / build-and-push (push) Failing after 10s
feat: mobile opens posts as a scrolling feed instead of a modal
Tapping a post on a phone now opens a real feed page — header, media, actions,
caption, next post peeking in below — scrolled with the browser's own vertical
scrolling rather than swipe gestures. Desktop keeps the modal, where a centred
sheet with side arrows suits a pointer.

Only a window of posts is mounted: a profile here holds up to 1129 posts and
mounting them all would mean as many full-size images. The window grows in both
directions as you scroll. Growing upwards shifts everything below it, so the
scroll offset is corrected in the same frame, before paint — measured against
the real archive, an anchored post moves exactly one screen per scroll with no
jump.

Only the post crossing the viewport centre plays its video; the rest stay
paused, so a feed of reels doesn't play ten at once. The URL tracks that same
post, so scrolling updates /<archive>/p/<shortcode>/ the way Instagram does,
and the back button returns to the grid with its scroll position intact.

Feed video sizes to the container width rather than its intrinsic size: a
<video> reports 300x150 until metadata loads, which made it render narrow and
then jump to full width. It also gets a taller height ceiling than the modal so
ordinary portrait media fills the width instead of sitting in side bars.

The carousel is extracted into a shared MediaCarousel used by both surfaces, so
horizontal paging behaves identically; touch-action keeps vertical scrolling
passing through to the feed. PostModal loses its now-dead mobile swipe branch.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011uBWhwV3wFQ5MBCcMHHem7
2026-08-14 10:23:33 -04:00

InstaArchive Viewer

A high-performance React PWA for browsing archived Instagram data with a native-feeling interface. Supports both official Instagram exports and Instaloader archives.

Features

  • Advanced Carousel: Seamless, zero-latency transitions between slides with intelligent preloading. Navigating between different posts is now near-instant thanks to inter-post background preloading.
  • High-Res Performance: Handles 50MP+ images effortlessly using a background Web Worker and a memory-safe serial processing queue.
  • Persistent Local Caching: Uses IndexedDB to store parsed archives and generated thumbnails. Local folders now load instantly from cache on return visits without needing to re-upload files.
  • Permalinks: State is synchronized with the URL, allowing you to share direct links to archives, tabs, or specific posts. Navigating back to the explorer cleans up URL parameters automatically.
  • Glassy Scanning UI: A refined, translucent white terminal experience with flicker-free, double-buffered dynamic blurred backgrounds.
  • PWA with Auto-Update: Fully offline-capable and installable. Clients automatically receive updates when a new version is deployed to the server.
  • Local Privacy: All processing is done client-side. Even when using the self-hosted version, your media is processed locally in your browser and never uploaded.
  • Smart Fallbacks: Automatically detects usernames from folder names and uses the oldest archive image as a profile picture if one is missing.
  • Customizable Grid: 1:1 or 3:4 aspect ratios with adjustable "bumps" for aesthetic alignment.
  • Story Viewer: Native-like story experience with segmented progress bars, auto-playback, and audio controls.
  • Navigation Protection: Intercepts accidental browser "Back" or "Refresh" actions to protect your current session.

Deployment

The easiest way to run InstaArchive is using Docker.

docker run -d \
  -p 3000:3000 \
  -v /path/to/your/archives:/archives:ro \
  ghcr.io/ergosteur/instaarchive-viewer:latest

Note for Linux/SELinux users: If you see "Permission Denied" in the logs, append ,z to your volume mount: -v /path/to/archives:/archives:ro,z

Docker Compose

Create a compose.yml file:

services:
  instaarchive:
    image: ghcr.io/ergosteur/instaarchive-viewer:latest
    ports:
      - "3000:3000"
    volumes:
      - ./archives:/archives:ro,z # ,z handles SELinux permissions

Troubleshooting Permissions

If the app shows "No Archives Found" and logs EACCES: permission denied:

  1. Check Directory Permissions: Ensure the archive folder is world-readable:
    chmod -R 755 /path/to/archives
    
  2. SELinux (Fedora/RHEL/CentOS): Use the :z flag in your volume mount as shown above.
  3. User Mapping: The container runs as the non-root node user (UID 1000). If your archives are readable only by another account, run as that user instead — the container needs to list the archive directory, so --x (traverse-only) permissions are not enough:
    docker run --user $(stat -c '%u:%g' /path/to/archives) ...
    
    In Compose:
    services:
      instaarchive:
        user: "1234:1234"   # a UID that can read your archives
    

Archive Index

On first start the server walks the archive root once and caches the result, keyed by directory mtime. This matters on network storage: for a 110k-file archive root, listing went from ~52s per request to ~0.1s. Mount a volume at /cache (or set CACHE_DIR) so the index survives restarts, otherwise it is rebuilt on every start.

Supported Archive Structure

Place your archive folders inside the mounted /archives directory. The directory name will be used as the account username.

Example Structure:

archives/
├── wanderlust_explorer/          # Instaloader format
│   ├── 2024-01-01_12-00-00_UTC.jpg
│   ├── 2024-01-01_12-00-00_UTC.json.xz
│   └── wanderlust_explorer_profile_pic.jpg
└── pixel_architect/             # Instagram Export format
    ├── 2023-12-25_pixel_architect - post_123.jpg
    ├── 2023-12-25_pixel_architect - post_123.json
    └── pixel_architect.jpg

Local Development

Prerequisites: Node.js (LTS recommended)

  1. Install dependencies: npm install
  2. Start dev server: npm run dev (Frontend on port 3000)
  3. Start local backend: npm run server (Optional, serves ./_sample-archives on port 3001)
  4. Build production: npm run build (Generates ./dist for frontend and ./dist-server for the API)
S
Description
No description provided
Readme
1.5 MiB
Languages
TypeScript 97.8%
CSS 1.6%
Dockerfile 0.5%
HTML 0.1%