Archilyzer · Source

archilyzer

Archilyzer
git clone https://archilyzer.pages.dev/source/archilyzer.git
Log | Files | Refs | README | LICENSE

commit 59259161cf146874e9cfc45e4f04cda7629d52d7
parent fe77950f12b11534632d16e7529bf1efc3ff0026
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Mon, 27 Apr 2026 23:31:52 -0400

feat(editor): show playlist and archive counts on channel list

The Playlist and Archive columns now report numeric entry counts (lines in
the playlist file, unique IDs in the archive file) instead of opaque ✓
booleans, so drift between the two — e.g. 7 in playlist but only 3
archived — is obvious at a glance. Cells render an em-dash when the file
doesn't exist yet.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

Diffstat:
Mcommon/controller/channels.ts | 29+++++++++++++++++++++++++----
Meditor/app/channels/page.tsx | 26+++++++++++++++++++++-----
Aeditor/cypress/e2e/channels-counts.cy.ts | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aeditor/cypress/fixtures/test-transcripts/channel-with-counts/channels/counted/archive | 3+++
Aeditor/cypress/fixtures/test-transcripts/channel-with-counts/channels/counted/config.json | 5+++++
Aeditor/cypress/fixtures/test-transcripts/channel-with-counts/channels/counted/playlist | 7+++++++
6 files changed, 127 insertions(+), 9 deletions(-)

diff --git a/common/controller/channels.ts b/common/controller/channels.ts @@ -1,14 +1,15 @@ import path from "node:path"; import { readdir, readFile, writeFile, rename, rm, stat, mkdir } from "node:fs/promises"; import type { Dirent } from "node:fs"; +import { readArchive } from "../lib/archive"; import { parseChannelConfig, type ChannelConfig } from "../lib/channelConfig"; import type { Paths } from "../lib/paths"; export type ChannelStat = { slug: string; config: ChannelConfig; - hasArchive: boolean; - hasPlaylist: boolean; + archiveCount: number | null; + playlistCount: number | null; videoCount: number; }; @@ -21,6 +22,26 @@ async function exists(p: string): Promise<boolean> { } } +async function countArchive(p: string): Promise<number | null> { + if (!(await exists(p))) return null; + const parsed = await readArchive(p); + return parsed.ids.size; +} + +async function countPlaylist(p: string): Promise<number | null> { + let raw: string; + try { + raw = await readFile(p, "utf8"); + } catch { + return null; + } + let count = 0; + for (const line of raw.split("\n")) { + if (line.trim().length > 0) count++; + } + return count; +} + export async function readChannelConfig( paths: Paths, slug: string, @@ -68,8 +89,8 @@ export async function listChannels(paths: Paths): Promise<ChannelStat[]> { out.push({ slug, config, - hasArchive: await exists(path.join(channelDir, "archive")), - hasPlaylist: await exists(path.join(channelDir, "playlist")), + archiveCount: await countArchive(path.join(channelDir, "archive")), + playlistCount: await countPlaylist(path.join(channelDir, "playlist")), videoCount, }); } diff --git a/editor/app/channels/page.tsx b/editor/app/channels/page.tsx @@ -35,8 +35,8 @@ export default async function ChannelsPage() { <Th>Name</Th> <Th>Handling</Th> <Th className="text-right">Videos</Th> - <Th>Playlist</Th> - <Th>Archive</Th> + <Th className="text-right">Playlist</Th> + <Th className="text-right">Archive</Th> <Th>Last sync</Th> </tr> </thead> @@ -58,8 +58,18 @@ export default async function ChannelsPage() { <Td>{c.config.name ?? ""}</Td> <Td>{c.config.handling}</Td> <Td className="text-right">{c.videoCount}</Td> - <Td>{c.hasPlaylist ? "✓" : ""}</Td> - <Td>{c.hasArchive ? "✓" : ""}</Td> + <Td + className="text-right" + testId={`channel-row-playlist-${c.slug}`} + > + {c.playlistCount ?? "—"} + </Td> + <Td + className="text-right" + testId={`channel-row-archive-${c.slug}`} + > + {c.archiveCount ?? "—"} + </Td> <Td className="text-xs text-zinc-500"> {c.config.lastSyncedAt ? new Date(c.config.lastSyncedAt).toLocaleString() @@ -91,9 +101,15 @@ function Th({ function Td({ children, className = "", + testId, }: { children: React.ReactNode; className?: string; + testId?: string; }) { - return <td className={`px-3 py-2 ${className}`}>{children}</td>; + return ( + <td className={`px-3 py-2 ${className}`} data-testid={testId}> + {children} + </td> + ); } diff --git a/editor/cypress/e2e/channels-counts.cy.ts b/editor/cypress/e2e/channels-counts.cy.ts @@ -0,0 +1,66 @@ +describe("Channel list playlist/archive counts", () => { + it("shows em-dash when neither file exists", () => { + cy.resetData("one-youtube-channel"); + cy.visit("/channels"); + cy.findByTestId("channel-row-playlist-test-youtube").should( + "contain.text", + "—", + ); + cy.findByTestId("channel-row-archive-test-youtube").should( + "contain.text", + "—", + ); + }); + + it("shows entry counts when files exist", () => { + cy.resetData("channel-with-counts"); + cy.visit("/channels"); + cy.findByTestId("channel-row-playlist-counted").should( + "have.text", + "7", + ); + cy.findByTestId("channel-row-archive-counted").should( + "have.text", + "3", + ); + }); + + it("counts update after store-playlist and download", () => { + cy.resetData("test-pipeline"); + cy.visit("/channels"); + cy.findByTestId("channel-row-playlist-test-pipeline").should( + "contain.text", + "—", + ); + + cy.visit("/channels/test-pipeline"); + cy.findByTestId("pipeline-store").find("button").click(); + cy.findByTestId("pipeline-store-log", { timeout: 20_000 }).should( + "contain.text", + "Wrote 5 URLs", + ); + + cy.visit("/channels"); + cy.findByTestId("channel-row-playlist-test-pipeline").should( + "have.text", + "5", + ); + cy.findByTestId("channel-row-archive-test-pipeline").should( + "contain.text", + "—", + ); + + cy.visit("/channels/test-pipeline"); + cy.findByTestId("pipeline-download").find("button").click(); + cy.findByTestId("pipeline-download-log", { timeout: 30_000 }).should( + "contain.text", + "download complete", + ); + + cy.visit("/channels"); + cy.findByTestId("channel-row-archive-test-pipeline").should( + "have.text", + "5", + ); + }); +}); diff --git a/editor/cypress/fixtures/test-transcripts/channel-with-counts/channels/counted/archive b/editor/cypress/fixtures/test-transcripts/channel-with-counts/channels/counted/archive @@ -0,0 +1,3 @@ +youtube arvid000001 +youtube arvid000002 +youtube arvid000003 diff --git a/editor/cypress/fixtures/test-transcripts/channel-with-counts/channels/counted/config.json b/editor/cypress/fixtures/test-transcripts/channel-with-counts/channels/counted/config.json @@ -0,0 +1,5 @@ +{ + "handling": "youtube", + "name": "Counted Channel", + "url": "https://www.youtube.com/@counted/videos" +} diff --git a/editor/cypress/fixtures/test-transcripts/channel-with-counts/channels/counted/playlist b/editor/cypress/fixtures/test-transcripts/channel-with-counts/channels/counted/playlist @@ -0,0 +1,7 @@ +https://www.youtube.com/watch?v=plvid000001 +https://www.youtube.com/watch?v=plvid000002 +https://www.youtube.com/watch?v=plvid000003 +https://www.youtube.com/watch?v=plvid000004 +https://www.youtube.com/watch?v=plvid000005 +https://www.youtube.com/watch?v=plvid000006 +https://www.youtube.com/watch?v=plvid000007