Archilyzer · Source

archilyzer

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

commit fb18dd2ded1ba3fa5de3e44dd5c18e58692d687a
parent e5994b34c7951a2f33c3f808b18326034caf767e
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Thu, 24 Sep 2026 13:01:44 -0400

channelWork: the server shell draws the shared channel table

ChannelWorkTable keeps the section, heading, description and `<id> empty`
paragraph, and hands its rows to the shared ChannelsTable with WORK_COLUMNS
(`extra` dropped when the section has none), `rowLabelPrefix={id}` and
`labels={{count, extra, report: "Last report"}}`. The section's functions
stay on the server: each row's count, extra figure and actions (the primary
action + refresh report) cross as a number, a string and an element. The
private `Row` is deleted, and with it `isStaleOrMissing` (its last caller);
the report cell is the rack's (`report age for`, stale / missing), dates are
`formatStamp`. Never `role="status"`.

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

Diffstat:
Meditor/app/components/channelWork/ChannelWorkTable.tsx | 177+++++++++++++++++++++++++++++++------------------------------------------------
Meditor/app/lib/actionable/loadActionable.ts | 4----
2 files changed, 69 insertions(+), 112 deletions(-)

diff --git a/editor/app/components/channelWork/ChannelWorkTable.tsx b/editor/app/components/channelWork/ChannelWorkTable.tsx @@ -1,22 +1,35 @@ -import Link from "next/link"; -import { - isStaleOrMissing, - type ActionableRow, - type ActionableSummary, +import type { + ActionableRow, + ActionableSummary, } from "../../lib/actionable/loadActionable"; +import { + buildChannelRowView, + neutralChannelPriority, + type ChannelRowView, +} from "yt-dlp-transcript-common/views/channelRow"; import { InlineActionButton } from "../actions/InlineActionButton"; +import { ChannelsTable } from "../../channels/components/ChannelsTable"; +import { + WORK_COLUMNS, + type ChannelRowExtras, +} from "../../channels/components/channelColumns"; import type { SectionConfig } from "./sections"; // A server component on purpose: `SectionConfig.primaryAction` is a function // that returns an element, and a function cannot cross the server/client -// boundary as a prop. +// boundary as a prop. So this is the SHELL — section, heading, description, +// empty paragraph — and the rows are the shared channel table +// (channels/components/ChannelsTable.tsx) with the WORK_COLUMNS preset, fed +// each row's count, extra figure and actions as data and elements. // // THE ARIA CONTRACT BELOW IS LOAD-BEARING. `channel-work.spec`, // `cleanup-actionable.spec`, `incomplete-transcript.spec` and // `download-format-guard.spec` all locate by these exact strings: // <section aria-label="<id>">, <h2>{title}</h2>, // <p aria-label="<id> empty">, <tr aria-label="<id> row <slug>">, -// columns Slug / countLabel / [extra] / Last report / Last sync / Actions, +// columns Slug / countLabel / [extra] / Last report / Last sync / Actions +// (the report cell is the rack's: `report age for <slug>`, "stale" / +// "missing" in the warning colour), // and a `refresh report <slug>` InlineActionButton on every row. // // NEVER add `role="status"` in here. This renders on the RUNNER operation @@ -51,6 +64,27 @@ function Section({ config: SectionConfig; rows: ActionableRow[]; }) { + // The functions stay HERE, on the server: each row's count, extra figure and + // actions are computed now and cross to the shared (client) table as a + // number, a string and an element. + const rowExtras: Record<string, ChannelRowExtras> = {}; + for (const row of rows) { + rowExtras[row.channel.slug] = { + count: config.getCount(row), + extra: config.extraColumn?.getValue(row), + actions: ( + <span className="inline-flex items-center gap-3 flex-wrap"> + {config.primaryAction(row)} + <InlineActionButton + variant={{ kind: "refreshReport", slug: row.channel.slug }} + /> + </span> + ), + }; + } + const columns = config.extraColumn + ? WORK_COLUMNS + : WORK_COLUMNS.filter((id) => id !== "extra"); return ( <section aria-label={config.id} @@ -69,110 +103,37 @@ function Section({ {config.emptyLabel} </p> ) : ( - <div className="overflow-x-auto -mx-4 md:mx-0 md:overflow-visible"> - <table className="text-sm border-y md:border md:border-border border-border md:rounded-md md:overflow-hidden w-full"> - <thead className="bg-muted"> - <tr> - <th className="text-left font-medium px-3 py-2">Slug</th> - <th className="text-right font-medium px-3 py-2"> - {config.countLabel} - </th> - {config.extraColumn && ( - <th className="text-right font-medium px-3 py-2 whitespace-nowrap"> - {config.extraColumn.label} - </th> - )} - <th className="text-left font-medium px-3 py-2 whitespace-nowrap"> - Last report - </th> - <th className="text-left font-medium px-3 py-2 whitespace-nowrap"> - Last sync - </th> - <th className="text-right font-medium px-3 py-2">Actions</th> - </tr> - </thead> - <tbody> - {rows.map((row) => ( - <Row - key={row.channel.slug} - row={row} - count={config.getCount(row)} - extraValue={config.extraColumn?.getValue(row) ?? null} - primaryAction={config.primaryAction(row)} - sectionId={config.id} - /> - ))} - </tbody> - </table> - </div> + <ChannelsTable + rows={rows.map(workRowOf)} + columns={columns} + rowLabelPrefix={config.id} + rowExtras={rowExtras} + labels={{ + count: config.countLabel, + extra: config.extraColumn?.label, + report: "Last report", + }} + sort={false} + selection={false} + /> )} </section> ); } -function Row({ - row, - count, - extraValue, - primaryAction, - sectionId, -}: { - row: ActionableRow; - count: number; - extraValue: string | null; - primaryAction: React.ReactNode; - sectionId: string; -}) { - const { channel, snapshot } = row; - const lastSync = channel.config.lastSyncedAt; - const lastReport = snapshot?.generatedAt ?? null; - // The loader's own definition, which this used to carry a copy of. - const isStale = isStaleOrMissing(row); - return ( - <tr - aria-label={`${sectionId} row ${channel.slug}`} - className="border-t border-border" - > - <td className="px-3 py-2 font-mono"> - <Link - href={`/channels/${channel.slug}`} - className="underline hover:text-foreground" - > - {channel.slug} - </Link> - </td> - <td className="px-3 py-2 text-right tabular-nums">{count}</td> - {extraValue !== null && ( - <td className="px-3 py-2 text-right tabular-nums whitespace-nowrap text-muted-foreground"> - {extraValue} - </td> - )} - <td - className={`px-3 py-2 text-xs whitespace-nowrap ${ - isStale - ? "text-warning" - : "text-muted-foreground" - }`} - > - {lastReport ? ( - <time dateTime={lastReport}> - {new Date(lastReport).toLocaleString()} - </time> - ) : ( - "never" - )} - </td> - <td className="px-3 py-2 text-xs text-muted-foreground whitespace-nowrap"> - {lastSync ? new Date(lastSync).toLocaleString() : "never"} - </td> - <td className="px-3 py-2 text-right"> - <span className="inline-flex items-center justify-end gap-3 flex-wrap"> - {primaryAction} - <InlineActionButton - variant={{ kind: "refreshReport", slug: channel.slug }} - /> - </span> - </td> - </tr> - ); +// The shared row, for a table that draws no bands, no tier, no media badge and +// no Location column: those inputs are neutral rather than read. The report +// state is the builder's `reportStateOf` — the loader's own definition, which +// this table used to carry a copy of. +function workRowOf(row: ActionableRow): ChannelRowView { + return buildChannelRowView({ + slug: row.channel.slug, + config: row.channel.config, + snapshot: row.snapshot, + playlistCount: null, + bands: [], + priority: neutralChannelPriority(), + media: null, + volume: { id: "", label: "" }, + }); } diff --git a/editor/app/lib/actionable/loadActionable.ts b/editor/app/lib/actionable/loadActionable.ts @@ -44,10 +44,6 @@ export type ActionableSummary = { // builder that reads it; re-exported so every importer keeps its path. export { reportStateOf }; -export function isStaleOrMissing(row: ActionableRow): boolean { - return reportStateOf(row.channel) !== "current"; -} - // THE COUNT HELPERS, as wrappers. The counting itself is // common/views/actionableCounts.ts — pure functions over one snapshot — which // the channel-row builder calls too, so a dashboard cell and this census cannot