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

+27 -30
View File
@@ -94,13 +94,13 @@ function PathsPanel({ detail }: { detail: LibraryDetail }) {
const rootOf = (id: string | null) => roots.data?.items.find((r) => r.id === id);
const bindings = detail.pathBindings.filter((b) => b.status !== "superseded");
return (
<Panel title="实际路径与库根" description="路径绑定一旦建立不随译名变化;执行进程可访问的绝对路径按库根拼接">
<Panel title="实际路径与库根" description="路径绑定建立后不随译名变化">
{bindings.length === 0 && detail.assets.length === 0 ? (
<EmptyState
plain
icon={FolderTree}
title="还没有入库文件"
description="收录目标满足后,文件位置会显示在这里。"
description="版本目标满足后,文件位置显示在这里。"
className="py-8"
/>
) : (
@@ -171,17 +171,23 @@ function IdentityPanel({ detail }: { detail: LibraryDetail }) {
const candidates = detail.identities.filter((i) => i.status === "proposed" || i.status === "conflicted");
return (
<Panel
title="外部身份映射"
description="本地 UUID 是主身份,TMDB 等外部 ID 是可修订的辅助映射"
title="外部身份"
description="以本地 ID 为准,外部身份可修改"
actions={
<Button variant="outline" size="sm" onClick={() => setOpen(true)}>
<PencilLine />
改映射
修改
</Button>
}
>
{list.length === 0 ? (
<EmptyState plain icon={SearchX} title="还没有外部映射" description="可以手动指定 TMDB id。" className="py-8" />
<EmptyState
plain
icon={SearchX}
title="还没有外部身份"
description="点「修改」手动指定 TMDB ID。"
className="py-8"
/>
) : (
<ul className="space-y-2">
{list.map((i) => (
@@ -244,7 +250,7 @@ function MetadataPanel({ detail, entity }: { detail: LibraryDetail; entity: Medi
return (
<Panel
title="元数据"
description={`字段级来源;锁定的字段刷新时不会被改写 · 最近刷新 ${formatDateTime(detail.entity.lastMetadataRefreshAt)}`}
description={`锁定的字段刷新时不变 · 最近刷新 ${formatDateTime(detail.entity.lastMetadataRefreshAt)}`}
actions={
<Button
variant="outline"
@@ -252,7 +258,7 @@ function MetadataPanel({ detail, entity }: { detail: LibraryDetail; entity: Medi
disabled={refresh.isPending}
onClick={() =>
refresh.mutate(undefined, {
onSuccess: () => toast.success("已提交元数据刷新(锁定字段保持不变)"),
onSuccess: () => toast.success("已提交刷新"),
onError: (err) => toast.error(errorMessage(err, "刷新失败")),
})
}
@@ -299,7 +305,7 @@ function MetadataPanel({ detail, entity }: { detail: LibraryDetail; entity: Medi
</Button>
}
title={`解锁「${ENTITY_FIELD_LABEL[field]}」?`}
description="取消人工覆盖与锁定,下次刷新会按来源优先级重新取值。"
description="取消人工修改,下次刷新按来源优先级取值。"
confirmLabel="解锁"
onConfirm={() =>
unlock.mutateAsync(field).then(
@@ -348,15 +354,15 @@ function MetadataPanel({ detail, entity }: { detail: LibraryDetail; entity: Medi
function SlotsPanel({ detail, units }: { detail: LibraryDetail; units: ContentUnit[] }) {
if (detail.slots.length === 0) {
return (
<Panel title="收录目标(Slot)">
<Panel title="版本目标">
<EmptyState
plain
icon={CalendarClock}
title="这部作品还没有收录目标"
description="在订阅与收录规则里新建订阅或 Slot,系统会按目标持续补齐与升级。"
title="还没有版本目标"
description="新建订阅或版本目标后,按目标补齐和升级。"
action={
<Button asChild variant="outline" size="sm">
<Link to={consolePath("subscriptions")}>去订阅与收录规则</Link>
<Link to={consolePath("subscriptions")}>去订阅</Link>
</Button>
}
className="py-8"
@@ -366,7 +372,7 @@ function SlotsPanel({ detail, units }: { detail: LibraryDetail; units: ContentUn
}
const assetOf = (id: string | null) => detail.assets.find((a) => a.asset.id === id);
return (
<Panel title="收录目标(Slot)" description="每个目标独立判断硬条件、软偏好与升级;状态均配文字">
<Panel title="版本目标" description="每个目标单独判断硬条件、软偏好和升级">
<div className="grid gap-3 xl:grid-cols-2">
{detail.slots.map((s) => {
const cur = assetOf(s.currentAssetId);
@@ -449,11 +455,7 @@ function UnitsPanel({ entity, detail }: { entity: ReturnType<typeof useEntityDet
a.segment.localeCompare(b.segment),
);
return (
<Panel
title="内容单元"
description="季 / 集 / 期与分段;临时单元是按解析结果先建的,确认或改编号后锁定"
bodyClassName="p-0 sm:p-0"
>
<Panel title="内容单元" description="临时单元按解析结果创建,确认或改编号后锁定" bodyClassName="p-0 sm:p-0">
<DataTable
plain
columns={columns}
@@ -463,9 +465,8 @@ function UnitsPanel({ entity, detail }: { entity: ReturnType<typeof useEntityDet
error={entity.error}
onRetry={() => entity.refetch()}
empty={{
title: detail.entity.kind === "movie" ? "电影没有分集单元" : "还没有内容单元",
description:
detail.entity.kind === "movie" ? "电影按内容版本核对完整性。" : "识别到新集或期后会自动建临时单元。",
title: detail.entity.kind === "movie" ? "电影没有季和集" : "还没有内容单元",
description: detail.entity.kind === "movie" ? "电影按内容版本核对完整性。" : "识别到新集后会建临时单元。",
}}
/>
</Panel>
@@ -548,11 +549,7 @@ function DownloadsPanel({ detail }: { detail: LibraryDetail }) {
},
];
return (
<Panel
title="下载历史与校验结果"
description="下载与入库串成一个业务过程;整理记录含逐文件核验"
bodyClassName="p-0 sm:p-0"
>
<Panel title="下载历史与校验结果" bodyClassName="p-0 sm:p-0">
<DataTable
plain
columns={columns}
@@ -595,7 +592,7 @@ export default function LibraryDetailPage() {
{back}
<ErrorState
title={notFound ? "作品不存在" : "作品加载失败"}
error={notFound ? "这个 ID 没有对应的作品,可能已被删除。" : detail.error}
error={notFound ? "找不到这个 ID 的作品。" : detail.error}
onRetry={notFound ? undefined : () => detail.refetch()}
/>
</>
@@ -638,13 +635,13 @@ export default function LibraryDetailPage() {
<Button asChild variant="outline" size="sm">
<Link to={`${consolePath("subscriptions")}?mediaEntityId=${e.id}`}>
<CalendarClock />
订阅与目标
查看订阅
</Link>
</Button>
{e.status !== "merged" ? (
<Button variant="outline" size="sm" onClick={() => setMerging(true)}>
<GitMerge />
合并到另一作品
合并作品
</Button>
) : null}
</div>