transmission, extracted from the platform into its own repository

The sixth plugin out, immediately after photos. A sidecar, a proxy router and
29 web files; no db/ directory, because there are no tables.

── The one thing that is not self-contained ──

The connection lives in `service_connections`, a CORE table, reached through
getServiceConnection/saveServiceConnection/getServiceCredentials. This plugin
declares no schema.

Deliberate: that table is the app store's shared credential store, keyed on
(user_id, service), already encrypted at rest and already read by
app-store/effects.ts. A private copy of the same three columns would be a
second place the same fact could live, and the runbook is explicit that a move
plus a data-format change gives any failure two causes.

The cost, stated rather than hidden: this plugin depends on a core facility it
does not own, and would break if that table changed shape. Photos, by contrast,
owns photos_config outright.

── 38 palette classes paid down, mapped by meaning ──

  blue     downloading / download speed  -> info
  cyan     checking (verifying)          -> busy
  emerald  seeding / complete            -> success
  amber    queued / stalled / warnings   -> warning

Collapsing blue and cyan into one tone would have lost a real distinction in
the status dot, so `checking` went to `busy` — in-progress work, which is what
that tone is for.

── A near-miss worth recording ──

The first copy globbed *.tsx and *.ts and silently missed detail/ and dialogs/
— ten components. tsgo caught it as ten "cannot find module" errors. Worth
stating because it is quiet in the other direction: had those been imported
lazily, the typecheck would have passed and the panels would have broken at
runtime. Check the directory tree, not the file glob.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-18 19:19:30 +00:00
co-authored by Claude Opus 5
commit 864dd16355
40 changed files with 5656 additions and 0 deletions
+128
View File
@@ -0,0 +1,128 @@
import type { ReactNode } from 'react';
import type { Torrent } from '../shared';
import { Check, Copy } from 'lucide-react';
import { useState } from 'react';
import {
formatDate,
formatDuration,
formatEta,
formatPercent,
formatRatio,
formatSize,
formatSpeed,
PRIORITY_LABELS,
statusLabel,
} from '../format';
import { copyToClipboard } from 'helpers/clipboard';
// The "what is this torrent" tab: transfer numbers, then the immutable facts from the metainfo.
type GeneralTabProps = { torrent: Torrent };
export const GeneralTab = ({ torrent: t }: GeneralTabProps) => (
<div className="space-y-5 p-4">
{t.error !== 0 && t.errorString && (
<p className="rounded-lg bg-destructive/10 px-3 py-2 text-xs text-destructive">{t.errorString}</p>
)}
<Section title="Transfer">
<Field label="Status" value={statusLabel(t)} />
<Field label="Progress" value={formatPercent(t.percentDone)} />
<Field label="Downloaded" value={formatSize(t.downloadedEver)} />
<Field label="Uploaded" value={formatSize(t.uploadedEver)} />
<Field label="Ratio" value={formatRatio(t.uploadRatio)} />
<Field label="Remaining" value={formatSize(t.leftUntilDone)} />
<Field label="Down speed" value={formatSpeed(t.rateDownload) || '—'} />
<Field label="Up speed" value={formatSpeed(t.rateUpload) || '—'} />
<Field label="ETA" value={t.percentDone >= 1 ? '—' : formatEta(t.eta)} />
<Field label="Seeds / peers" value={`${t.peersSendingToUs} / ${t.peersGettingFromUs}`} />
<Field label="Seeding time" value={formatDuration(t.secondsSeeding)} />
{/* Corrupt bytes are re-downloaded silently, so this is the only place a failing disk or a bad peer
shows up before the ratio quietly goes wrong. */}
<Field label="Corrupt" value={t.corruptEver ? formatSize(t.corruptEver) : '—'} />
</Section>
<Section title="Dates">
<Field label="Added" value={formatDate(t.addedDate)} />
<Field label="Completed" value={formatDate(t.doneDate)} />
<Field label="Last active" value={formatDate(t.activityDate)} />
<Field label="Created" value={formatDate(t.dateCreated)} />
</Section>
<Section title="Torrent">
<Field label="Total size" value={formatSize(t.totalSize)} />
<Field label="Files" value={String(t['file-count'] ?? '—')} />
<Field label="Pieces" value={`${t.pieceCount} × ${formatSize(t.pieceSize)}`} />
<Field label="Privacy" value={t.isPrivate ? 'Private torrent' : 'Public torrent'} />
<Field label="Priority" value={PRIORITY_LABELS[t.bandwidthPriority] ?? '—'} />
<Field label="Queue position" value={String(t.queuePosition)} />
<Field label="Creator" value={t.creator || '—'} />
<Field label="Peer limit" value={String(t['peer-limit'] ?? '—')} />
</Section>
<Section title="Location" columns={1}>
<Field label="Download directory" value={t.downloadDir} mono />
<Field label="Hash" value={t.hashString ?? '—'} mono copy={t.hashString} />
<Field label="Magnet" value={t.magnetLink || '—'} mono truncate copy={t.magnetLink} />
{t.labels.length > 0 && <Field label="Labels" value={t.labels.join(', ')} />}
{t.comment && <Field label="Comment" value={t.comment} />}
</Section>
{t.peersFrom && (
<Section title="Peer sources">
<Field label="Tracker" value={String(t.peersFrom.fromTracker)} />
<Field label="DHT" value={String(t.peersFrom.fromDht)} />
<Field label="PEX" value={String(t.peersFrom.fromPex)} />
<Field label="Incoming" value={String(t.peersFrom.fromIncoming)} />
<Field label="Local discovery" value={String(t.peersFrom.fromLpd)} />
<Field label="Cache" value={String(t.peersFrom.fromCache)} />
</Section>
)}
</div>
);
const Section = ({ title, children, columns = 2 }: { title: string; children: ReactNode; columns?: number }) => (
<section>
<h3 className="mb-2 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground">{title}</h3>
<dl className={`grid gap-x-6 gap-y-1.5 ${columns === 1 ? 'grid-cols-1' : 'grid-cols-1 sm:grid-cols-2'}`}>
{children}
</dl>
</section>
);
type FieldProps = { label: string; value: string; mono?: boolean; truncate?: boolean; copy?: string };
const Field = ({ label, value, mono, truncate, copy }: FieldProps) => (
<div className="flex min-w-0 items-baseline gap-3 text-xs">
<dt className="w-32 shrink-0 text-muted-foreground">{label}</dt>
<dd className={`min-w-0 flex-1 ${mono ? 'font-mono' : ''} ${truncate ? 'truncate' : 'break-words'}`} title={value}>
{value}
</dd>
{copy && <CopyButton value={copy} />}
</div>
);
const CopyButton = ({ value }: { value: string }) => {
const [copied, setCopied] = useState(false);
return (
<button
type="button"
className="shrink-0 text-muted-foreground hover:text-foreground"
onClick={() => {
// copyToClipboard handles the insecure-origin case — navigator.clipboard does not exist over
// plain http, which Officer IS reached over on a tailnet. This comment used to say "Officer is
// always behind HTTPS"; it was not, and the button silently copied nothing.
void copyToClipboard(value).then(
() => {
setCopied(true);
setTimeout(() => setCopied(false), 1200);
},
() => undefined,
);
}}
aria-label="Copy"
>
{copied ? <Check className="h-3 w-3 text-success" /> : <Copy className="h-3 w-3" />}
</button>
);
};