9.7 KiB
Design: Search Match Indicators, Size Limit Increase, Skipped/Failed Files Overview
Date: 2026-03-24 Status: Approved
Overview
Three related improvements to the STL packages system:
- Search match indicators — Show which internal files matched a search query, with highlighted files in the drawer
- Size limit increase — Raise the ingestion limit from 4 GB to 200 GB so large multipart archives aren't skipped
- Skipped/failed files overview — Track and display archives that were skipped or failed, with retry capability
Feature 1: Size Limit Increase
Change
worker/src/util/config.ts line 6 — change default from "4096" to "204800".
One-line change. The split/upload pipeline already handles arbitrary sizes. The 2 GB per-part Telegram API limit is a separate hard-coded constant and stays as-is.
Impact
- Archives up to 200 GB will now be attempted
- Multipart archives where individual parts are under 2 GB (but total exceeds 4 GB) will no longer be skipped — these upload directly without any splitting
- Single files over 2 GB are automatically split into 2 GB parts (existing behavior)
- Temp disk usage during processing can now reach up to ~200 GB per archive
Feature 2: Search Match Indicators
Backend Changes
File: src/lib/telegram/queries.ts — searchPackages()
When searchIn is "files" or "both", change the PackageFile query from distinct to a grouped count:
// Current: findMany with select: { packageId }, distinct: ["packageId"]
// New: groupBy packageId with _count
const fileMatches = await prisma.packageFile.groupBy({
by: ["packageId"],
where: {
OR: [
{ fileName: { contains: q, mode: "insensitive" } },
{ path: { contains: q, mode: "insensitive" } },
],
},
_count: { _all: true },
});
This returns { packageId: string, _count: { _all: number } }[].
Note: PackageRow in package-columns.tsx mirrors PackageListItem and must also receive the two new fields.
File: src/lib/telegram/types.ts — PackageListItem
Add two fields:
matchedFileCount: number— how many files inside matched (0 if matched by package name only)matchedByContent: boolean— true if any files inside matched
Frontend Changes
File: src/app/(app)/stls/page.tsx
Pass the search term to StlTable as a new prop.
File: src/app/(app)/stls/_components/stl-table.tsx
Pass search term to columns via TanStack Table column meta.
File: src/app/(app)/stls/_components/package-columns.tsx
When search is active and matchedByContent is true, render a clickable badge below the filename: e.g., "3 file matches". Clicking opens the PackageFilesDrawer with a highlightTerm prop set to the search term.
File: src/app/(app)/stls/_components/package-files-drawer.tsx
- Accept optional
highlightTerm: stringprop - Render full file tree as normal (all files visible)
- Files whose
fileNameorpathcase-insensitively containshighlightTermget a subtle highlight (amber/yellow background on the row) - Auto-expand folders that contain highlighted files
- The drawer's own search input remains independent
Data Flow
- User types search term in STL table search input
- URL updates with
?search=value, page reloads page.tsxcallssearchPackages()withsearchIn: "both"- Query returns packages with
matchedFileCountandmatchedByContent - Table renders "N file matches" badge on content-matched rows
- User clicks badge -> drawer opens with full tree, matching files highlighted
- Folders containing matches auto-expanded
Feature 3: Skipped/Failed Files Overview
Database Schema
New model in prisma/schema.prisma:
enum SkipReason {
SIZE_LIMIT
DOWNLOAD_FAILED
EXTRACT_FAILED
UPLOAD_FAILED
}
model SkippedPackage {
id String @id @default(cuid())
fileName String
fileSize BigInt
reason SkipReason
errorMessage String?
sourceChannelId String
sourceChannel TelegramChannel @relation(fields: [sourceChannelId], references: [id], onDelete: Cascade)
sourceMessageId BigInt
sourceTopicId BigInt?
isMultipart Boolean @default(false)
partCount Int @default(1)
accountId String
account TelegramAccount @relation(fields: [accountId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now())
@@unique([sourceChannelId, sourceMessageId])
@@index([reason])
@@index([accountId])
@@map("skipped_packages")
}
Reverse relations must be added to TelegramChannel and TelegramAccount models:
// In TelegramChannel:
skippedPackages SkippedPackage[]
// In TelegramAccount:
skippedPackages SkippedPackage[]
Worker Changes
File: worker/src/worker.ts
Extend PipelineContext interface to include accountId (derived from the ingestion run's account).
At each skip/failure point, upsert a SkippedPackage record:
- Size limit skip (line 784): reason
SIZE_LIMIT, no error message - Download failure (catch in download loop): reason
DOWNLOAD_FAILED+ error text - Extract/metadata failure (catch in extract): reason
EXTRACT_FAILED+ error text - Upload failure (catch in upload): reason
UPLOAD_FAILED+ error text
On successful ingestion of a package, delete any existing SkippedPackage with the same (sourceChannelId, sourceMessageId) — so successful retries clean up after themselves.
File: worker/src/db/queries.ts
Add functions:
upsertSkippedPackage(data)— create or update skip recorddeleteSkippedPackage(sourceChannelId, sourceMessageId)— remove on success
Retry Mechanism
Retrying a skipped package:
- Delete the
SkippedPackagerecord - Find the
AccountChannelMaprecord using bothaccountIdandsourceChannelId, then reset itslastProcessedMessageIdtosourceMessageId - 1(only if less than current watermark) - If
sourceTopicIdis non-null, also reset the correspondingTopicProgress.lastProcessedMessageIdfor that topic - The next ingestion cycle picks up the message and re-attempts processing
For "Retry All" (e.g., all SIZE_LIMIT skips after raising the limit):
- Delete all matching
SkippedPackagerecords - For each affected (account, channel) pair, reset
AccountChannelMapwatermark to the minimumsourceMessageId - 1among deleted records - For each affected (account, channel, topic) triple, reset
TopicProgresswatermark similarly
Note on behavioral distinction: DOWNLOAD_FAILED, EXTRACT_FAILED, and UPLOAD_FAILED archives already naturally retry because the worker does not advance the watermark past failed sets. The SkippedPackage record provides visibility into these failures. The explicit retry/watermark reset is only strictly needed for SIZE_LIMIT skips (where the watermark does advance past the skipped message). The UI should present both types but the retry button is most impactful for SIZE_LIMIT skips.
Performance note: "Retry All" can cause the worker to re-scan large message ranges. The existing dedup logic (packageExistsBySourceMessage) ensures already-ingested packages are skipped quickly, but there is a scanning cost proportional to the number of messages between the reset watermark and the current position.
Frontend Changes
File: src/app/(app)/stls/_components/stl-table.tsx
Add a "Skipped / Failed" tab alongside the main packages table.
New file: src/app/(app)/stls/_components/skipped-packages-tab.tsx
Table columns:
- fileName — archive name
- fileSize — formatted size
- reason — color-coded badge:
SIZE_LIMIT(yellow),DOWNLOAD_FAILED(red),EXTRACT_FAILED(red),UPLOAD_FAILED(red) - errorMessage — truncated with expandable tooltip/popover for full text
- channel — source channel title
- createdAt — when the skip/failure was recorded
Actions:
- Retry button per row — server action that deletes record + resets watermark
- Retry All button in the header — bulk retry, filterable by reason
File: src/app/(app)/stls/page.tsx
Fetch skipped packages count (for tab badge) alongside existing queries.
File: src/data/ or src/lib/telegram/queries.ts
Add query functions:
listSkippedPackages(options)— paginated list with reason filtercountSkippedPackages()— for tab badgeretrySkippedPackage(id)— delete record + reset watermarkretryAllSkippedPackages(reason?)— bulk retry
File: src/app/(app)/stls/actions.ts
Add server actions:
retrySkippedPackageAction(id)retryAllSkippedPackagesAction(reason?)
Files to Create/Modify
Create
src/app/(app)/stls/_components/skipped-packages-tab.tsx— skipped packages table UI- Prisma migration for
SkippedPackagemodel
Modify
worker/src/util/config.ts— raise default max sizeworker/src/worker.ts— record skips/failures, clean up on successworker/src/db/queries.ts— add skip record CRUD functionsprisma/schema.prisma— addSkippedPackagemodel andSkipReasonenumsrc/lib/telegram/queries.ts— modifysearchPackages()for match counts, add skipped package queriessrc/lib/telegram/types.ts— addmatchedFileCount/matchedByContenttoPackageListItem, add skipped package typessrc/app/(app)/stls/page.tsx— pass search term, fetch skipped count, add tabsrc/app/(app)/stls/_components/stl-table.tsx— accept search prop, render tabssrc/app/(app)/stls/_components/package-columns.tsx— render match badgesrc/app/(app)/stls/_components/package-files-drawer.tsx— accept highlightTerm, highlight matching files, auto-expand matched folderssrc/app/(app)/stls/actions.ts— add retry server actions