# CURRENT.md — session state Last updated: 2026-09-16 This file tracks the work state. Read it first. Update it before you stop. ## Current focus (broad) The text layer is done. Voice actions and audio work. The local LLM harness (Veskslop agent, Go + Eino) is scaffolded and smoke-tested; the daemon-side task push is next. The web UI has a first mobile draft at 390px. ## In progress - Media archive project, branch `media-scrape-project` on `veskslop-t`. Design approved 2026-09-16 (docs: [media.md](docs/media.md), [media-scraper.md](docs/media-scraper.md), [slopbot.md](docs/slopbot.md), [media-mcp.md](docs/media-mcp.md)). Decisions: scrape the channel the command is sent in; slopbot (discord.js bot core) for guild channels and the puppet core bridge for group DMs; one self-contained SQLite + media dir per scrape under `media-scrape/db/` (gitignored); links indexed by default, downloads optional, third-party embeds never downloaded; 30 GiB default limit with a 90% warning and 5% no-delete overrun, usage bar and clean button in Settings → Bot; Media and Bot top-level tabs; MCP hooks designed for C8. The slopbot scaffold (config loader, invite helper) and its discord.js dependency were added 2026-09-16. C5 smoke test: `/api/media/status` and `/api/media` answer on a daemon with no core. Checkpoints, push after each: - [x] C1 design docs, AGENTS/INDEX pointers, `.gitignore` - [x] C2 core message shape: embeds, forwards, attachment metadata - [x] C3 media-scrape store and puppet walker, tests - [x] C4 slopbot backend, tests - [x] C5 daemon wiring: `scrape media` command, MediaService, `/api/media/*` - [x] C6 Media and Bot tabs in the Web UI - [x] C7a storage limits, retention, warning/confirm flow, storage and clean API - [x] C7b Settings group: limit field with info hover, usage bar, clean modal - [x] C8 MCP service hooks (`scrape_media`, scrape reads, storage, clean; reads in `run_commands`) - [x] C10 downloader: attachment and sticker bytes, hash-addressed, serial, size-capped, retries; embeds excluded in SQL; local files served at `/api/media///file` and preferred by the Media tab. Live run 2026-09-16: scrape `...-20260916083401` fetched the 784 KB attachment; storage counted actual bytes. - [x] C11 preview caching: Discord-hosted image/GIF previews (WebP 320px, hash-addressed, 5 MiB cap, one attempt) run after every walk in both modes. Live run `...-20260916090722`: `previewed: 2`; thumbs served over `/api/media///thumb` (200). - [x] C38 Filter layout swap. The common filters (filetype chips, source kind, server/DM origin, multi-select People popover) now sit in the always-visible bar; the Advanced search drawer holds aspect, resolution, channel, date range, cached-only, and the card-size slider. Apply/Reset stay in the bar. Fixed a dropped `defaultFilters` helper found by the Playwright smoke test, which passes on the new layout. - [x] C37 Advanced search drawer, adaptive paging, and zoom. Filters moved into an **Advanced search** drawer with Apply/Reset (no auto-update): filetype chips, source kind, origin (server/DM), multi-select People, and an Advanced section (aspect, resolution, channel, dates, **cachedOnly**). A **Card size** slider scales tiles from 100 px (2/3 of current) to one image per row; page size = full rows about 1.5 screens tall and follows zoom/resize. Infinite scroll preloads the next page's JSON and previews. Backend gained `authorIds`, `origin`, `cachedOnly` on `/api/media`, `/api/media/stats`, and MCP `query_media`. Verified: `origin=dm` and `cachedOnly=1` return the expected rows. - [x] C36 Daemon restart, preview diagnostics, browse-time caching, and rate-limit pauses. Settings → Server has a **Restart daemon** button (`POST /api/daemon/restart`: re-execs the daemon, keeps the client running). The preview pass logs every failure with scrape/item/kind/ provider/reason plus a per-pass summary, and `GET /api/media/preview-failures` lists them. Browsing no longer fetches Discord at all: the grid uses cached stills only (placeholders for missing ones), GIF hover plays the cached animated GIF (kept as `anim_hash`, served from `/anim`), and streaming only starts on explicit play. `429`s from previews/downloads raise a cooldown (`VESKSLOP_MEDIA_RATELIMIT_PAUSE_MS`, default 60 s, or `Retry-After`), leaving rows pending and pausing both walker and previews. - [x] C35 Walk/preview cycling. A live scrape now alternates: walk 5 pages, preview up to one batch (25, with the cooldown), walk on. Tiles fill in during a long walk instead of only at the end, and preview traffic is spread through the walk. Configurable with `VESKSLOP_MEDIA_WALK_CYCLE_PAGES` (0 defers previews to the end). The final pass still drains whatever remains. - [x] C34 Resume from cursor. A paused scrape can continue where it stopped instead of re-walking the top: `POST /api/media/scrape/resume`, MCP `resume_media_scrape`, and a Resume button in History. Previews never needed the walk — backfill fetches them straight from the stored URLs; the walk is only for discovery. Live: the paused `gernal_chut` scrape resumed at cursor `365256427247239181`, fetched its final page, and entered the preview pass. - [x] C33 Puppet history pagination. The core's `message.fetch` read the client store, which keeps only the newest messages plus the channel's first — every page was 10 messages and the walk stopped after one page (a 10-message, 4-media scrape of an ancient group with thousands of messages). It now fetches through the client's REST layer first (contiguous 100-message pages, `transport: "rest"`), store as fallback. Rebuilt and restarted the managed client; live re-run of channel `591000745164406784`: 21+ pages / 2,100+ messages and climbing, with hundreds of images/videos being indexed. - [x] C32 Group-chat clarity and puppet refresh. Group DM scrapes already worked (live-verified against the running daemon: `zıbb & fred` and a 1:1 DM, backend `puppet`, done, previews cached); the Group chat tab had shown an empty list while the core was offline. It now says so explicitly and offers Retry. Expired attachment URLs in DM/group-DM scrapes refresh through the core bridge (`message.fetch` with `before: id+1`, which returns exactly the target). - [x] C31 Batched previews. The preview pass now runs in batches (default 25) with a cooldown (default 60 s), configurable via `VESKSLOP_MEDIA_PREVIEW_BATCH` and `VESKSLOP_MEDIA_PREVIEW_BATCH_PAUSE_MS`; batching counts rows, not successes. Live log: `preview batch of 25 done, pausing 60000 ms`. - [x] C30 Preview completeness. Audio attachments and voice messages get an ffmpeg waveform strip; oversized animated GIFs fall back to an ffmpeg-read first frame instead of `too large`; transient network failures stay pending for the next backfill instead of becoming permanent. Live: a scrape with 58 audio items backfilled 13 waveforms in 40 s, serving `image/webp`. Remaining non-cached: Lottie stickers/custom emoji (not indexed as media) and embeds whose pages refuse bots (marked PRIVATE/GONE by their 403/404). - [x] C29 Attachment video previews. Discord's image proxy renders a frame for video files (`?width=320&height=320&format=webp`), so `previewUrlFor` now resizes attachment mp4/webm in links mode too and `pendingPreviews` includes non-embed videos. Live: paused-scrape videos backfilled to `image/webp` thumbs, pending queue drained to 0. - [x] C28 Media grid usability. Infinite scroll (240 px threshold) with a Load more fallback, `previewError` exposed so tiles show PRIVATE (403) or GONE (404/410) with the reason in the tooltip, and the scrape strip lists only queued/running jobs — paused scrapes move to History. - [x] C27 Non-media stream guard. Rows awaiting backfill fell back to first-frame `