diff --git a/src/app/(app)/stls/_components/stl-table.tsx b/src/app/(app)/stls/_components/stl-table.tsx
index d914bb0..8814c55 100644
--- a/src/app/(app)/stls/_components/stl-table.tsx
+++ b/src/app/(app)/stls/_components/stl-table.tsx
@@ -500,14 +500,9 @@ export function StlTable({
)}
-
- Ungrouped
- {ungroupedTotalCount > 0 && (
-
- {ungroupedTotalCount}
-
- )}
-
+ {/* "Ungrouped" tab hidden: the STL list is now flat and grouping is
+ no longer surfaced here. The tab content below is kept (unreachable)
+ to avoid churn; remove it and its data fetch if grouping is dropped. */}
diff --git a/src/lib/telegram/queries.ts b/src/lib/telegram/queries.ts
index b667101..a1262bd 100644
--- a/src/lib/telegram/queries.ts
+++ b/src/lib/telegram/queries.ts
@@ -135,31 +135,31 @@ export async function listDisplayItems(options: {
const sortCol = sortBy === "fileName" ? `"fileName"` : sortBy === "fileSize" ? `"fileSize"` : `"indexedAt"`;
const sortDir = order === "asc" ? "ASC" : "DESC";
- // Step 1: Count display items
+ // NOTE: The STL list is intentionally FLAT — every package is its own display
+ // row regardless of packageGroupId. Grouping is no longer surfaced in this
+ // view (the creator column/filter organizes the list instead). PackageGroup
+ // rows and the manual grouping actions still exist in the DB/UI; they just
+ // don't drive this list's layout anymore.
+
+ // Step 1: Count display items (one per package)
const countResult = await prisma.$queryRawUnsafe<[{ count: bigint }]>(
- `SELECT COUNT(*) AS count FROM (
- SELECT DISTINCT COALESCE(p."packageGroupId", p."id") AS display_id
- FROM packages p
- ${whereClause}
- ) AS display_items`,
+ `SELECT COUNT(*) AS count FROM packages p ${whereClause}`,
...params
);
const total = Number(countResult[0].count);
- // Step 2: Get display item IDs for this page
+ // Step 2: Get package IDs for this page
const limitParam = paramIdx++;
const offsetParam = paramIdx++;
const displayRows = await prisma.$queryRawUnsafe<
{ display_id: string; display_type: string }[]
>(
`SELECT
- COALESCE(p."packageGroupId", p."id") AS display_id,
- CASE WHEN p."packageGroupId" IS NOT NULL THEN 'group' ELSE 'package' END AS display_type,
- MAX(p.${sortCol}) AS sort_value
+ p."id" AS display_id,
+ 'package' AS display_type,
+ p.${sortCol} AS sort_value
FROM packages p
${whereClause}
- GROUP BY COALESCE(p."packageGroupId", p."id"),
- CASE WHEN p."packageGroupId" IS NOT NULL THEN 'group' ELSE 'package' END
ORDER BY sort_value ${sortDir}
LIMIT $${limitParam} OFFSET $${offsetParam}`,
...params, limit, (page - 1) * limit
diff --git a/worker/src/worker.ts b/worker/src/worker.ts
index 793ba0b..3c3971f 100644
--- a/worker/src/worker.ts
+++ b/worker/src/worker.ts
@@ -65,7 +65,7 @@ import { readRarContents } from "./archive/rar-reader.js";
import { read7zContents } from "./archive/sevenz-reader.js";
import { byteLevelSplit, concatenateFiles } from "./archive/split.js";
import { uploadToChannel, UploadStallError } from "./upload/channel.js";
-import { processAlbumGroups, processRuleBasedGroups, processTimeWindowGroups, processPatternGroups, processCreatorGroups, processZipPathGroups, processReplyChainGroups, processCaptionGroups, detectGroupingConflicts, type IndexedPackageRef } from "./grouping.js";
+import { processAlbumGroups, detectGroupingConflicts, type IndexedPackageRef } from "./grouping.js";
import { db } from "./db/client.js";
import type { TelegramAccount, TelegramChannel } from "@prisma/client";
import type { Client } from "tdl";
@@ -1479,34 +1479,13 @@ async function processArchiveSets(
scanResult.photos
);
- // Auto-grouping passes (gated by per-channel flag)
- const channelRecord = await db.telegramChannel.findUnique({
- where: { id: channel.id },
- select: { autoGroupEnabled: true },
- });
-
- if (channelRecord?.autoGroupEnabled !== false) {
- // Learned rule-based grouping (from manual overrides)
- await processRuleBasedGroups(channel.id, indexedPackageRefs);
-
- // Time-window grouping for remaining ungrouped packages
- await processTimeWindowGroups(channel.id, indexedPackageRefs);
-
- // Pattern-based grouping (date patterns, project slugs)
- await processPatternGroups(channel.id, indexedPackageRefs);
-
- // Creator-based grouping (3+ files from same creator)
- await processCreatorGroups(channel.id, indexedPackageRefs);
-
- // ZIP path prefix grouping (shared root folder inside archives)
- await processZipPathGroups(channel.id, indexedPackageRefs);
-
- // Reply chain grouping (messages replying to same root)
- await processReplyChainGroups(channel.id, indexedPackageRefs);
-
- // Caption fuzzy match grouping
- await processCaptionGroups(channel.id, indexedPackageRefs);
- }
+ // Heuristic auto-grouping passes (rule/time/pattern/creator/zip-path/
+ // reply-chain/caption) were removed: the STL view is now a flat list
+ // organized by the creator filter, so automatically inventing groups at
+ // ingestion is no longer wanted. Album grouping above is kept because it
+ // reflects real upload structure (files posted together as one Telegram
+ // album), not a heuristic guess. Existing groups and the manual grouping
+ // actions in the UI are unaffected.
// Check for potential grouping conflicts
await detectGroupingConflicts(channel.id, indexedPackageRefs);