docs(plan): v0.2 adds Pi subagents and scheduled-jobs board

Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
rootandClaude Fable 5.1 committed 2026-09-28 02:09:39 +08:00
1 parent b839ed4056
commit 16ab2a6c84
97 files changed
+592 -648

No files matched your search

+19 -26
View File
@@ -73,7 +73,6 @@ export default function SubscriptionsPage() {
<>
<PageHeader
title="订阅与收录规则"
description="订阅决定追什么,收录目标决定要哪种版本,策略决定默认怎么做;启用即授权范围内的持续操作。"
actions={
tab === "subscriptions" ? (
<Button onClick={() => setCreatingSub(true)}>
@@ -240,7 +239,7 @@ function SubscriptionsTab() {
empty={{
icon: CalendarClock,
title: filtered ? "没有符合条件的订阅" : "还没有订阅",
description: "订阅一部作品后,系统按收录目标持续补齐与升级;也可以只填标题,等作品建档后自动关联。",
description: filtered ? undefined : "订阅的作品会按版本目标补齐和升级。点「新建订阅」添加。",
}}
/>
<SubscriptionSheet sub={open} onClose={() => setOpenId(null)} />
@@ -256,7 +255,7 @@ function SubscriptionSheet({ sub, onClose }: { sub: Subscription | null; onClose
const runSearch = () =>
sub &&
search.mutate(sub.id, {
onSuccess: () => toast.success("已开始在已开启站点搜索,结果会按收录目标自动评估"),
onSuccess: () => toast.success("已在启用的站点开始搜索"),
onError: (e) => toast.error(errorMessage(e)),
});
const title = useEntityTitle(sub?.mediaEntityId);
@@ -284,20 +283,16 @@ function SubscriptionSheet({ sub, onClose }: { sub: Subscription | null; onClose
{sub.mediaEntityId ? (
<Button variant="outline" onClick={runSearch} disabled={search.isPending}>
<Search />
{search.isPending ? "搜索中…" : "立即搜索"}
{search.isPending ? "搜索中…" : "搜索资源"}
</Button>
) : null}
{sub.status === "active" || sub.status === "pending" ? (
<Button
variant="outline"
onClick={() => setStatus("paused", "已暂停,不再发起新下载")}
disabled={update.isPending}
>
<Button variant="outline" onClick={() => setStatus("paused", "已暂停")} disabled={update.isPending}>
<Pause />
暂停
</Button>
) : (
<Button variant="outline" onClick={() => setStatus("active", "已恢复追踪")} disabled={update.isPending}>
<Button variant="outline" onClick={() => setStatus("active", "已恢复")} disabled={update.isPending}>
<Play />
恢复
</Button>
@@ -310,7 +305,7 @@ function SubscriptionSheet({ sub, onClose }: { sub: Subscription | null; onClose
</Button>
}
title="取消这个订阅?"
description="不再追踪后续内容;已入库文件、已有收录目标与下载历史都保留。"
description="不再追踪后续内容。已入库文件、版本目标和下载记录保留。"
confirmLabel="取消订阅"
destructive
onConfirm={() => setStatus("cancelled", "订阅已取消")}
@@ -329,7 +324,7 @@ function SubscriptionSheet({ sub, onClose }: { sub: Subscription | null; onClose
label: "作品",
value: sub.mediaEntityId ? (
<Link className="underline-offset-4 hover:underline" to={consolePath("library", sub.mediaEntityId)}>
打开媒体详情
查看作品
</Link>
) : (
`按标题追踪:${sub.titleQuery ?? "—"}(尚未建档)`
@@ -356,11 +351,11 @@ function SubscriptionSheet({ sub, onClose }: { sub: Subscription | null; onClose
<DetailSection title="追踪方式">
<SubscriptionEditForm key={`${sub.id}-${sub.updatedAt}`} sub={sub} />
</DetailSection>
<DetailSection title="收录目标">
<DetailSection title="版本目标">
{sub.mediaEntityId ? (
<SlotsPanel mediaEntityId={sub.mediaEntityId} subscriptionId={sub.id} />
) : (
<p className="text-caption text-muted-foreground">作品建档并关联后才能设置收录目标。</p>
<p className="text-caption text-muted-foreground">作品建档后才能设置版本目标。</p>
)}
</DetailSection>
</>
@@ -396,7 +391,7 @@ function ProgressPanel({ subId, readOnly }: { subId: string; readOnly: boolean }
{ id: subId, seasonNumber: Number(season), total: Number(total), locked },
{
onSuccess: () => {
toast.success(locked ? "总集数已锁定" : "总集数已更新(只升)");
toast.success(locked ? "总集数已锁定" : "总集数已更新");
setTotalValue("");
},
onError: (e) => toast.error(errorMessage(e)),
@@ -404,10 +399,8 @@ function ProgressPanel({ subId, readOnly }: { subId: string; readOnly: boolean }
);
return (
<div className="space-y-3">
{!p.completable ? (
<p className="text-meta text-muted-foreground">总集数未知或元数据未就绪:继续追踪,不会自动结束。</p>
) : null}
{p.seasons.length === 0 ? <p className="text-caption text-muted-foreground">还没有季信息。</p> : null}
{!p.completable ? <p className="text-meta text-muted-foreground">总集数未知,订阅不会自动结束。</p> : null}
{p.seasons.length === 0 ? <p className="text-caption text-muted-foreground">没有季信息。</p> : null}
{p.seasons.map((s) => (
<div key={s.seasonNumber} className="space-y-1.5">
<p className="text-sm font-medium text-foreground">
@@ -439,7 +432,7 @@ function ProgressPanel({ subId, readOnly }: { subId: string; readOnly: boolean }
<ul className="space-y-0.5 text-meta text-muted-foreground">
{p.slots.map((x) => (
<li key={x.slotId}>
{x.name}:在途 {x.inflightPlans.length} 个计划,延后 {x.deferred} 个发布
{x.name}:在途 {x.inflightPlans.length} 个计划,延后 {x.deferred} 个资源
</li>
))}
</ul>
@@ -532,7 +525,7 @@ function SubscriptionEditForm({ sub }: { sub: Subscription }) {
</FormField>
</div>
{mode === "continuous" && scope === "following_seasons" && sub.mediaKind !== "movie" ? (
<p className="text-meta text-muted-foreground">特别篇、衍生节目和同名重启不直接视作下一季,按证据识别。</p>
<p className="text-meta text-muted-foreground">特别篇、衍生节目和同名重启不算作下一季。</p>
) : null}
<div className="flex justify-end">
<Button variant="outline" size="sm" onClick={save} disabled={!dirty || update.isPending}>
@@ -616,14 +609,14 @@ function CreateSubscriptionDialog({ onClose }: { onClose: () => void }) {
const v = SubscriptionCreate.safeParse(body);
if (!v.success) {
const errs = zodFieldErrors(v.error.issues);
if (errs[""]) errs.target = byTitle ? "请填写要追踪的标题" : "请选择一部作品";
if (errs[""]) errs.target = byTitle ? "填写标题" : "选择作品";
setErrors(errs);
return;
}
setErrors({});
create.mutate(v.data, {
onSuccess: () => {
toast.success("订阅已创建,将按收录目标开始检查");
toast.success("订阅已创建");
onClose();
},
onError: (e) => toast.error(errorMessage(e)),
@@ -635,7 +628,7 @@ function CreateSubscriptionDialog({ onClose }: { onClose: () => void }) {
<DialogContent className="max-h-[90dvh] overflow-y-auto sm:max-w-xl">
<DialogHeader>
<DialogTitle>新建订阅</DialogTitle>
<DialogDescription>选择库里的作品,或先按标题追踪,作品建档后自动关联。</DialogDescription>
<DialogDescription>选择库里的作品,或按标题追踪,建档后自动关联。</DialogDescription>
</DialogHeader>
<div className="min-w-0 space-y-3">
<fieldset className="flex gap-1" aria-label="订阅对象">
@@ -709,7 +702,7 @@ function CreateSubscriptionDialog({ onClose }: { onClose: () => void }) {
) : search.isError ? (
<p className="px-3 py-2 text-caption text-destructive">{errorMessage(search.error)}</p>
) : (search.data?.items ?? []).filter((e) => e.status !== "merged").length === 0 ? (
<p className="px-3 py-2 text-caption text-muted-foreground">没有匹配的作品,可以改用"按标题追踪"。</p>
<p className="px-3 py-2 text-caption text-muted-foreground">没有匹配的作品。可改用"按标题追踪"。</p>
) : (
<ul className="divide-y divide-border/60">
{(search.data?.items ?? [])
@@ -757,7 +750,7 @@ function CreateSubscriptionDialog({ onClose }: { onClose: () => void }) {
labels={SUB_MODE}
/>
</FormField>
<FormField label="季范围" htmlFor={`${id}-scope`} hint={mediaKind === "movie" ? "电影不适用" : "随时可改"}>
<FormField label="季范围" htmlFor={`${id}-scope`} hint={mediaKind === "movie" ? "电影不适用" : undefined}>
<EnumSelect
id={`${id}-scope`}
value={mediaKind === "movie" ? "current_season" : scope}