Sync working tree before initial Gitea push

Includes:
- cli.py path fix (parents[1]) for config/catalog resolution
- Library cleanup feature design docs (TODO.md, mockup)
- Audit + bug-queue markdowns from May 2026 reliability pass
- .gitignore expanded for transient artifacts
This commit is contained in:
admin
2026-05-26 22:35:42 +02:00
parent 8d6bdb81af
commit f7fc15b17c
24 changed files with 2938 additions and 41 deletions
+603
View File
@@ -0,0 +1,603 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>rclone-jav — Library Cleanup mockup (preview-first, no resolution probing)</title>
<style>
:root {
color-scheme: dark;
--bg: #0c0e10;
--shell: #14171a;
--panel: #181b1e;
--surface: #1f2327;
--line: #292e33;
--line-2: #3a4148;
--text: #e1e6eb;
--muted: #8a949d;
--blue: #6ec5ff;
--green: #7de4a0;
--yellow: #ffd36c;
--red: #ff9097;
--purple: #c5a9ff;
--orange: #ffb072;
}
* { box-sizing: border-box; }
body { margin:0; background:var(--bg); color:var(--text); font:13px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif; }
main { padding:24px; max-width:1320px; margin:0 auto; }
h1 { margin:0 0 4px; font-size:24px; }
h2 { margin:28px 0 8px; font-size:17px; color:#f4f7fa; }
h3 { margin:0 0 6px; font-size:11px; text-transform:uppercase; color:#9ba6af; letter-spacing:0.04em; }
p { margin:0 0 10px; color:var(--muted); }
.intro { color:var(--muted); max-width:960px; margin:6px 0 18px; font-size:13px; }
code { font-family:Consolas,monospace; background:#1a1f24; padding:1px 5px; border-radius:3px; color:#cfdde5; font-size:11px; }
.meta-banner { display:flex; align-items:center; gap:10px; padding:10px 14px; background:#11181f; border:1px solid #1f2b35; border-radius:6px; margin-bottom:18px; font-size:12px; color:var(--muted); }
.meta-banner .dot { width:8px; height:8px; border-radius:50%; background:var(--green); box-shadow:0 0 0 3px rgba(125,228,160,0.15); }
.meta-banner b { color:#cfdde5; }
.status-grid { display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:12px; margin-bottom:24px; }
.status-card { background:#13171b; border:1px solid #232a30; border-radius:6px; padding:12px; }
.status-card h3 { color:#dce5ed; text-transform:none; letter-spacing:0; font-size:13px; margin-bottom:8px; }
.status-card.todo { border-left:3px solid var(--yellow); }
.status-card.work { border-left:3px solid var(--blue); }
.status-card.done { border-left:3px solid var(--green); }
.status-card ul { margin:0; padding-left:16px; color:var(--muted); font-size:12px; }
.status-card ul li { margin:2px 0; }
.status-card .num { color:var(--text); font-weight:700; font-size:18px; }
.legend { display:flex; gap:6px; flex-wrap:wrap; margin-bottom:14px; }
.pill { border-radius:12px; padding:3px 9px; font-size:11px; border:1px solid var(--line-2); background:#22272b; color:var(--text); }
.pill.green { color:var(--green); background:#143020; border-color:#245036; }
.pill.blue { color:var(--blue); background:#132837; border-color:#284b66; }
.pill.red { color:var(--red); background:#321618; border-color:#5b2228; }
.pill.yellow { color:var(--yellow); background:#332b16; border-color:#645228; }
.pill.orange { color:var(--orange); background:#3a2818; border-color:#7a4b25; }
.pill.purple { color:var(--purple); background:#241d35; border-color:#453363; }
.pill.muted { color:#9aa4ac; background:#1a1f24; border-color:#2c333a; }
.mock { border:1px solid #2c333a; border-radius:8px; background:var(--shell); overflow:hidden; margin-bottom:14px; }
.mock-head { padding:9px 14px; border-bottom:1px solid var(--line); background:#0f1214; display:flex; align-items:center; justify-content:space-between; }
.mock-head .title { color:#fff; font-weight:600; font-size:13px; }
.mock-head .sub { color:var(--muted); font-size:11px; }
.mock-body { padding:14px 16px; }
button { border:1px solid var(--line-2); border-radius:4px; padding:5px 10px; background:#252a2e; color:var(--text); font:inherit; cursor:default; font-size:11px; }
button.primary { background:#163923; color:#aaf3bf; border-color:#285b3a; }
button.live { background:#143247; color:#9fd9ff; border-color:#2e607f; }
button.danger { background:#3a191d; color:#ffb2b7; border-color:#722c33; }
button.ghost { background:transparent; color:#9aa4ac; border-color:#3a4148; }
button.warn { background:#3a3017; color:#ffd784; border-color:#645228; }
/* Filter chips */
.chip-row { display:flex; gap:6px; flex-wrap:wrap; padding:10px 14px; background:#0d1013; border-bottom:1px solid var(--line); font-size:11px; align-items:center; }
.chip { display:inline-flex; align-items:center; gap:8px; padding:4px 10px; border:1px solid #2a3138; border-radius:12px; background:#1a1f24; color:#9aa4ac; cursor:default; min-width:auto; }
.chip .cnt { font-variant-numeric:tabular-nums; min-width:22px; padding:0 6px; text-align:right; background:rgba(255,255,255,0.05); border-radius:9px; font-size:10px; font-weight:600; color:#888; }
.chip.active { background:#27313a; color:#fff; border-color:#36526a; }
.chip.active .cnt { background:rgba(157,204,255,0.12); color:#9dccff; }
/* outcome-tinted active chips */
.chip.t-cleanup.active { background:#143020; border-color:#245036; color:#9be3b3; }
.chip.t-cleanup.active .cnt { background:rgba(155,227,179,0.12); color:#9be3b3; }
.chip.t-strip.active { background:#332b16; border-color:#645228; color:#ffd784; }
.chip.t-strip.active .cnt { background:rgba(255,215,132,0.12); color:#ffd784; }
.chip.t-conflict.active { background:#321618; border-color:#5b2228; color:#ff9097; }
.chip.t-conflict.active .cnt { background:rgba(255,144,151,0.12); color:#ff9097; }
.chip.t-optional.active { background:#241d35; border-color:#453363; color:#c5a9ff; }
.chip.t-optional.active .cnt { background:rgba(197,169,255,0.12); color:#c5a9ff; }
.chip-row .sep { color:#3a4148; padding:0 2px; }
.chip-row .right { margin-left:auto; display:flex; gap:6px; }
.row { display:grid; grid-template-columns: 22px 1fr 22px 1fr 130px; gap:10px; padding:8px 10px; background:#101418; border:1px solid #1d2429; border-radius:4px; margin-top:5px; align-items:center; font-size:12px; }
.row.conflict { background:#231410; border-color:#43251c; }
.row.skip-default { opacity:0.65; }
.row .box { width:14px; height:14px; border:1px solid #4a5560; border-radius:3px; background:#0a0c0e; position:relative; }
.row .box.checked { background:#163923; border-color:#285b3a; }
.row .box.checked::after { content:"✓"; color:#aaf3bf; font-size:11px; position:absolute; top:-3px; left:1px; }
.row .arrow { color:#5d6772; text-align:center; }
.row .name { font-family:Consolas,monospace; font-size:11px; color:#cdd6dd; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.row .name.old { color:#a8b3bb; }
.row .name.new { color:#9be3b3; }
.row .name.new.conflict { color:var(--red); }
.row .meta { font-size:10px; color:var(--muted); display:flex; flex-direction:column; gap:2px; align-items:flex-end; }
.row .meta .tag { padding:1px 6px; border-radius:8px; font-size:9px; background:#1a1f24; color:#9aa4ac; border:1px solid #2a3138; }
.row .meta .tag.strip { background:#3a2818; color:#ffb072; border-color:#7a4b25; }
.row .meta .tag.transform { background:#132837; color:#9fd9ff; border-color:#2e607f; }
.row .meta .tag.conflict { background:#321618; color:#ff9097; border-color:#5b2228; }
.row .meta .tag.still-bare { background:#332b16; color:#ffd784; border-color:#645228; }
.row .name-stack { display:flex; flex-direction:column; gap:2px; min-width:0; }
.row .name-stack .secondary { font-family:Consolas,monospace; font-size:10px; color:#5d6772; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.reason { font-size:10px; color:#6b757d; margin-top:2px; padding-left:32px; font-family:Consolas,monospace; }
/* Plan summary footer */
.plan-footer { margin-top:18px; padding:12px; background:#0f1518; border:1px solid #1d2a30; border-radius:5px; display:flex; align-items:center; justify-content:space-between; }
.plan-footer .counts { font-size:12px; color:var(--muted); display:flex; gap:14px; }
.plan-footer .counts b { color:#fff; }
/* Decision table */
table.spec { width:100%; border-collapse:collapse; margin-top:10px; font-size:12px; }
table.spec th { text-align:left; padding:8px 10px; background:#181d22; color:#cfdde5; font-weight:600; border-bottom:1px solid #2a3138; font-size:11px; }
table.spec td { padding:8px 10px; border-bottom:1px solid #1c2126; color:var(--text); vertical-align:top; font-size:12px; }
table.spec tr:nth-child(even) td { background:#10141a; }
table.spec td.opt { font-family:Consolas,monospace; color:var(--blue); font-weight:600; }
table.spec td.small { color:var(--muted); font-size:11px; }
/* Option compare cards */
.opt-grid { display:grid; grid-template-columns: repeat(3, minmax(0,1fr)); gap:12px; margin-top:12px; }
.opt-card { background:#13171b; border:1px solid #232a30; border-radius:6px; padding:12px; }
.opt-card h3 { color:#dce5ed; text-transform:none; font-size:13px; letter-spacing:0; margin-bottom:6px; }
.opt-card .verdict { font-size:11px; margin-top:8px; }
.opt-card.rec { border-color:#285b3a; }
.opt-card .verdict b { color:#dce5ed; }
/* Plan modal frame */
.modal-shell { background:#181b1e; border:1px solid var(--line-2); border-radius:6px; box-shadow:0 8px 24px rgba(0,0,0,.55); overflow:hidden; }
.modal-head { display:flex; align-items:center; justify-content:space-between; background:#0f1214; padding:10px 14px; border-bottom:1px solid var(--line); }
.modal-head .title { color:#fff; font-weight:600; font-size:13px; }
.modal-head .sub { color:var(--muted); font-size:11px; margin-top:2px; }
.modal-head .x { color:#7a838c; font-size:14px; cursor:default; }
.modal-toolbar { display:flex; align-items:center; gap:8px; padding:8px 14px; background:#10141a; border-bottom:1px solid var(--line); font-size:11px; color:var(--muted); }
/* Apply progress */
.progress { background:#0a0c0e; border:1px solid var(--line-2); border-radius:4px; padding:10px 12px; }
.progress-bar { height:6px; background:#1a1f24; border-radius:3px; overflow:hidden; margin-top:6px; }
.progress-fill { height:100%; background:linear-gradient(90deg, var(--blue), var(--green)); width:42%; transition:width .3s; }
.progress-meta { display:flex; justify-content:space-between; font-size:11px; color:var(--muted); margin-top:6px; }
.progress-meta b { color:var(--text); }
/* Settings card for ignore-list */
.setting-card { background:#13171b; border:1px solid #232a30; border-radius:6px; padding:12px; margin-top:10px; }
.setting-card label { display:flex; align-items:center; gap:10px; color:var(--text); font-size:12px; }
.setting-card label .sublabel { display:block; color:var(--muted); font-size:11px; margin-left:24px; margin-top:2px; }
@media (max-width: 1000px) {
.opt-grid, .status-grid { grid-template-columns: 1fr; }
}
</style>
</head>
<body>
<main>
<h1>Library Cleanup — preview-first mockup</h1>
<p class="intro">Phase 1 only: deterministic transforms + junk-strip on names that already have resolution data, or have garbage trailing tokens. <b>No ffprobe</b>. No resolution-adding work. Real numbers from the 2026-05-26 Library Issues export.</p>
<div class="meta-banner">
<span class="dot"></span>
<span><b>Scope locked:</b> Phase 1 cleanup only. Phase 2 resolution probing is a separate session. Goal here: 85 cleanup-tier renames + ~21 junk-strips on missing-resolution names. <b>Total in scope: ~106 files.</b> Preview is mandatory before any rclone moveto runs.</span>
</div>
<!-- ============================================== -->
<h2>1 — Volume picture</h2>
<div class="status-grid">
<div class="status-card done">
<h3>Already clean</h3>
<p style="color:var(--muted); font-size:11px;">No work needed.</p>
<ul>
<li><b>0</b> bracket_id</li>
<li><b>0</b> nohyphen_id</li>
</ul>
</div>
<div class="status-card work">
<h3>Cleanup-tier (Phase 1a)</h3>
<p style="color:var(--muted); font-size:11px;">Already have resolution data. Just reshape.</p>
<ul>
<li><span class="num">64</span> <b>resolution_part_suffix</b><code>RBD-394 [1080p].2of2.wmv</code></li>
<li><span class="num">18</span> <b>resolution_copy_suffix</b><code>PIYO-005 [1080p] (1).mp4</code></li>
<li><span class="num">3</span> <b>resolution_bare_suffix</b><code>REAL-487.450p.wmv</code></li>
<li style="margin-top:6px;color:#9be3b3;"><b>85</b> become fully canonical after Phase 1a</li>
</ul>
</div>
<div class="status-card todo">
<h3>Junk-strip (Phase 1b)</h3>
<p style="color:var(--muted); font-size:11px;">Strip leftover tags. Still missing resolution after.</p>
<ul>
<li><span class="num">2</span> empty brackets <code>[]</code></li>
<li><span class="num">5</span> <code>.HD</code> suffix (failed auto-label)</li>
<li><span class="num">6</span> <code>[396m]</code> bracket (bitrate, not resolution)</li>
<li><span class="num">~9</span> <code>_PARTN</code> → optional normalize</li>
<li style="margin-top:6px;color:#ffd784;"><b>~21</b> renamed, still need Phase 2 ffprobe later</li>
</ul>
</div>
</div>
<p style="color:var(--muted); font-size:12px;">The other 775 missing_resolution files (bare names like <code>ROYD-109.mp4</code>) need resolution data we don't have on hand. Out of scope for this cleanup session.</p>
<!-- ============================================== -->
<h2>2 — Preview flow options (P1 / P2 / P3)</h2>
<div class="opt-grid">
<div class="opt-card">
<h3>P1 — Inline rows in existing Library Issues modal</h3>
<p style="font-size:11px; color:var(--muted);">Add Old → New column to existing rows. Same modal. New "Cleanup Plan" filter chip on top of existing All / Found / Missing chips.</p>
<div class="verdict"><b>Trade:</b> no new surface, but modal is busy. 779 missing_resolution rows are already cramped; adding 106 cleanup rows compounds it.</div>
</div>
<div class="opt-card rec">
<h3>P2 — Dedicated Cleanup Plan modal <span class="pill green" style="margin-left:6px;">recommended</span></h3>
<p style="font-size:11px; color:var(--muted);">Click "Generate Cleanup Plan" in Library Review. Opens its own modal with only the ~106 affected files, grouped by transform kind. Per-group select-all, per-row toggle, Apply N button at bottom.</p>
<div class="verdict"><b>Trade:</b> focused UX, but a new modal to maintain. Mockup below uses this.</div>
</div>
<div class="opt-card">
<h3>P3 — External JSON plan + reimport</h3>
<p style="font-size:11px; color:var(--muted);">Export cleanup-plan-{ts}.json. User edits in text editor (deletes lines to skip). Re-import to apply.</p>
<div class="verdict"><b>Trade:</b> full audit trail + offline review, but high friction. Worth offering as a secondary "Export plan" button alongside P2.</div>
</div>
</div>
<!-- ============================================== -->
<h2>3 — Mockup: P2 Cleanup Plan modal</h2>
<p>Below shows what the dedicated modal looks like for your actual library state. Grouped by transform kind. Per-row checkbox. Conflict rows default-unchecked + flagged.</p>
<div class="modal-shell">
<div class="modal-head">
<div>
<div class="title">Cleanup Plan</div>
<div class="sub">Phase 1 deterministic transforms + junk-strip. No resolution probing.</div>
</div>
<div class="x"></div>
</div>
<div class="modal-toolbar">
<span><b style="color:#fff;">106</b> files in plan</span>
<span>·</span>
<span><b style="color:#9be3b3;">85</b> cleanup</span>
<span><b style="color:#ffd784;">21</b> junk-strip</span>
<span><b style="color:#ff9097;">2</b> conflicts</span>
<span style="margin-left:auto;">
<button class="ghost">Export plan (JSON)</button>
</span>
</div>
<!-- Filter chips replace the long stacked group headers -->
<div class="chip-row">
<span class="chip active"><span>All</span><span class="cnt">106</span></span>
<span class="sep">|</span>
<span class="chip t-cleanup"><span>part suffix</span><span class="cnt">64</span></span>
<span class="chip t-cleanup"><span>copy (N)</span><span class="cnt">18</span></span>
<span class="chip t-cleanup"><span>bare res</span><span class="cnt">3</span></span>
<span class="sep">|</span>
<span class="chip t-strip"><span>empty []</span><span class="cnt">2</span></span>
<span class="chip t-strip"><span>strip .HD</span><span class="cnt">5</span></span>
<span class="chip t-strip"><span>strip [Nm]</span><span class="cnt">6</span></span>
<span class="sep">|</span>
<span class="chip t-optional"><span>_PARTN</span><span class="cnt">9</span></span>
<span class="sep">|</span>
<span class="chip t-conflict"><span>conflicts</span><span class="cnt">2</span></span>
<span class="right">
<button class="ghost" style="font-size:10px;">Select all visible</button>
<button class="ghost" style="font-size:10px;">Deselect all visible</button>
</span>
</div>
<div style="padding:12px 16px;">
<div style="font-size:11px; color:var(--muted); margin-bottom:8px;">
Showing <b style="color:#fff;">All</b> · 106 rows (5 displayed below, rest virtualized in real impl). Click a chip to filter. Multi-select not supported — chips are single-choice radio-style.
</div>
<div class="row">
<div class="box checked"></div>
<div class="name-stack">
<span class="name old">RBD-394 [1080p].2of2.wmv</span>
<span class="secondary">cq:JAV/Q-U/R/RBD/ · part suffix</span>
</div>
<div class="arrow"></div>
<div class="name new">RBD-394 #part2 [1080p].wmv</div>
<div class="meta">
<span class="tag transform">transform</span>
<span>2.63 GiB</span>
</div>
</div>
<div class="row">
<div class="box checked"></div>
<div class="name-stack">
<span class="name old">PIYO-005 [1080p] (1).mp4</span>
<span class="secondary">cq:JAV/... · copy (N) · no conflict</span>
</div>
<div class="arrow"></div>
<div class="name new">PIYO-005 [1080p].mp4</div>
<div class="meta">
<span class="tag transform">drop (N)</span>
<span>5.84 GiB</span>
</div>
</div>
<div class="row conflict skip-default">
<div class="box"></div>
<div class="name-stack">
<span class="name old">HFD-197 [720p] (1).mp4</span>
<span class="secondary" style="color:#ff9097;">CONFLICT — HFD-197 [720p].mp4 already in cache</span>
</div>
<div class="arrow"></div>
<div class="name new conflict">HFD-197 [720p].mp4 ✗</div>
<div class="meta">
<span class="tag conflict">conflict</span>
<span>2.91 GiB</span>
</div>
</div>
<div class="row">
<div class="box checked"></div>
<div class="name-stack">
<span class="name old">REAL-487.450p.wmv</span>
<span class="secondary">cq:JAV/Q-U/R/REAL/ · bare res</span>
</div>
<div class="arrow"></div>
<div class="name new">REAL-487 [450p].wmv</div>
<div class="meta">
<span class="tag transform">wrap</span>
<span>2.52 GiB</span>
</div>
</div>
<div class="row">
<div class="box checked"></div>
<div class="name-stack">
<span class="name old">TYOD-232 [].wmv</span>
<span class="secondary">empty [] · auto-labeler leftover</span>
</div>
<div class="arrow"></div>
<div class="name new">TYOD-232.wmv</div>
<div class="meta">
<span class="tag strip">strip</span>
<span class="tag still-bare">still missing res</span>
</div>
</div>
<div class="row">
<div class="box checked"></div>
<div class="name-stack">
<span class="name old">MXGS-672 [396m].avi</span>
<span class="secondary">[Nm] interpreted as bitrate, not resolution</span>
</div>
<div class="arrow"></div>
<div class="name new">MXGS-672.avi</div>
<div class="meta">
<span class="tag strip">strip bracket</span>
<span class="tag still-bare">still missing res</span>
</div>
</div>
<div class="row skip-default">
<div class="box"></div>
<div class="name-stack">
<span class="name old">KV-118 - Aiba Reika_PART1.mp4</span>
<span class="secondary" style="color:#c5a9ff;">_PARTN — OPTIONAL, default unchecked (cosmetic; extract_id already handles)</span>
</div>
<div class="arrow"></div>
<div class="name new">KV-118 - Aiba Reika #part1.mp4</div>
<div class="meta">
<span class="tag transform">cosmetic</span>
<span>3.40 GiB</span>
</div>
</div>
<div style="text-align:center; color:var(--muted); font-size:11px; padding:10px 0;">… 99 more rows in real plan (virtualized scrolling). Use chips above to narrow down.</div>
</div>
<div class="plan-footer">
<div class="counts">
<span><b>104</b> selected</span>
<span><b style="color:#ff9097;">2</b> skipped (conflicts)</span>
<span>est. <b>~2 min</b> apply time</span>
</div>
<div style="display:flex; gap:8px;">
<button class="ghost">Cancel</button>
<button class="warn">Save plan to disk (no apply)</button>
<button class="primary">Apply 104 renames</button>
</div>
</div>
</div>
<!-- ============================================== -->
<h2>4 — Per-row anatomy</h2>
<div class="mock">
<div class="mock-head">
<div>
<div class="title">One row, annotated</div>
<div class="sub">Five columns: checkbox · old name + folder context · arrow · new name · transform tag + size</div>
</div>
</div>
<div class="mock-body" style="padding:18px 22px;">
<div class="row">
<div class="box checked"></div>
<div class="name-stack">
<span class="name old">RBD-394 [1080p].2of2.wmv</span>
<span class="secondary">cq:JAV/Q-U/R/RBD/</span>
</div>
<div class="arrow"></div>
<div class="name new">RBD-394 #part2 [1080p].wmv</div>
<div class="meta">
<span class="tag transform">transform</span>
<span>2.63 GiB</span>
</div>
</div>
<div style="margin-top:10px; font-size:11px; color:var(--muted); line-height:1.7;">
<div><b style="color:#fff;">checkbox</b> — default checked unless conflict detected; per-row toggle, per-group select-all</div>
<div><b style="color:#fff;">old name + remote folder</b> — folder context is muted so it stays scannable; full <code>full_path</code> in tooltip</div>
<div><b style="color:#fff;">arrow</b> — separator only, no interactivity</div>
<div><b style="color:#fff;">new name</b> — green text on safe transforms, red on conflicts</div>
<div><b style="color:#fff;">meta</b> — transform-kind tag + file size; flags like <code>conflict</code> or <code>still missing res</code> stack vertically</div>
</div>
</div>
</div>
<!-- ============================================== -->
<h2>5 — Conflict cases</h2>
<p>Cache-based conflict detection runs synchronously when building the plan. Real rclone-side recheck runs at apply time as belt-and-suspenders.</p>
<table class="spec">
<thead>
<tr><th style="width:30%;">Conflict case</th><th>Example</th><th>Plan default</th></tr>
</thead>
<tbody>
<tr>
<td><b>Target exists in cache</b></td>
<td><code>PIYO-005 [1080p] (1).mp4</code><code>PIYO-005 [1080p].mp4</code><br><span class="small">stripped form already in cache.json</span></td>
<td><span class="pill red">skip</span> Default-unchecked. Reason text: "Use Duplicate Review to decide which to keep."</td>
</tr>
<tr>
<td><b>Two plan rows target same new name</b></td>
<td>If <code>ABC-001 (1).mp4</code> AND <code>ABC-001 (2).mp4</code> both want <code>ABC-001.mp4</code></td>
<td><span class="pill red">skip both</span> Plan generator detects in-plan collision; flags both rows with "conflict-with-plan-row N."</td>
</tr>
<tr>
<td><b>Target appears at apply time</b></td>
<td>File renamed externally between plan generation and Apply click</td>
<td><span class="pill yellow">apply-time skip</span> rclone lsf check fails; row reported as <code>skipped: target appeared</code> in summary modal.</td>
</tr>
<tr>
<td><b>rclone moveto error</b></td>
<td>Network glitch, permission, rclone bug</td>
<td><span class="pill yellow">apply-time fail</span> Row marked failed in summary. Other renames continue. User can re-run plan to retry.</td>
</tr>
</tbody>
</table>
<!-- ============================================== -->
<h2>6 — Apply progress + summary</h2>
<p>Bulk apply on ~104 files is roughly 1-2 minutes at typical rclone moveto latency. Progress + cancel needed.</p>
<div class="mock">
<div class="mock-head">
<div>
<div class="title">Applying renames…</div>
<div class="sub">Progress channel mirrors the existing scan-progress pattern</div>
</div>
<button class="danger">Cancel</button>
</div>
<div class="mock-body">
<div class="progress">
<div style="display:flex; justify-content:space-between; font-size:12px;">
<span style="color:#dce5ed;">Renaming <b style="font-family:Consolas,monospace;">NFDM-247 [720p].1of2.wmv</b></span>
<span style="color:var(--muted);">43 / 104</span>
</div>
<div class="progress-bar"><div class="progress-fill"></div></div>
<div class="progress-meta">
<span><b>41</b> succeeded · <b style="color:var(--red);">2</b> conflicts (skipped) · <b style="color:var(--yellow);">0</b> failed</span>
<span>elapsed 22s · est <b>32s</b> remaining</span>
</div>
</div>
<p style="margin-top:10px; font-size:11px; color:var(--muted);">Cancel waits for the current rclone moveto to complete before stopping. Partial application is safe — cache is patched per-rename, batch <code>save_cache</code> still fires at the end of the cancelled run.</p>
</div>
</div>
<h3 style="margin-top:18px;">Result summary modal (after apply)</h3>
<div class="mock" style="margin-top:6px;">
<div class="mock-head">
<div>
<div class="title">Cleanup complete</div>
<div class="sub">104 of 106 renames attempted · 102 succeeded · 2 conflicts auto-skipped</div>
</div>
<div class="x"></div>
</div>
<div class="mock-body">
<div style="display:grid; grid-template-columns:repeat(4,minmax(0,1fr)); gap:8px; font-size:12px;">
<div style="background:#143020;border:1px solid #245036;border-radius:4px;padding:10px;">
<div style="color:#9be3b3;font-size:18px;font-weight:700;">102</div>
<div style="color:#9be3b3;font-size:11px;">succeeded</div>
</div>
<div style="background:#321618;border:1px solid #5b2228;border-radius:4px;padding:10px;">
<div style="color:#ff9097;font-size:18px;font-weight:700;">2</div>
<div style="color:#ff9097;font-size:11px;">conflicts (in-plan skip)</div>
</div>
<div style="background:#332b16;border:1px solid #645228;border-radius:4px;padding:10px;">
<div style="color:#ffd784;font-size:18px;font-weight:700;">0</div>
<div style="color:#ffd784;font-size:11px;">apply-time failures</div>
</div>
<div style="background:#132837;border:1px solid #2e607f;border-radius:4px;padding:10px;">
<div style="color:#9fd9ff;font-size:18px;font-weight:700;">~21</div>
<div style="color:#9fd9ff;font-size:11px;">still need resolution (Phase 2)</div>
</div>
</div>
<div style="margin-top:14px; display:flex; gap:8px; align-items:center;">
<button class="ghost">Save revert plan (cleanup-revert-{ts}.json)</button>
<button class="ghost">Re-scan Library Issues</button>
<span style="margin-left:auto; font-size:11px; color:var(--muted);">Cache patched + saved · scan re-suggests cleanup pass if any items remain</span>
</div>
</div>
</div>
<!-- ============================================== -->
<h2>7 — Ignore list (optional, per-file)</h2>
<p>After applying, some files might intentionally stay non-canonical. Tracking them prevents Library Issues from re-flagging the same file next scan. Per-file flag in cache, no UI editor needed beyond a "Mark as intentional" checkbox per row in the modal.</p>
<div class="setting-card">
<label>
<input type="checkbox" checked disabled style="cursor:default;">
<span>Persist "ignore" decisions per file</span>
</label>
<div class="sublabel">Adds <code>filename_hygiene_ignore: true</code> to cache entry when row is unchecked + marked Intentional. Library Issues scan skips these files going forward. Cleared on cache rebuild.</div>
</div>
<!-- ============================================== -->
<h2>8 — Decisions to lock before any code</h2>
<table class="spec">
<thead>
<tr><th>Decision</th><th>Options</th><th>Suggested default</th></tr>
</thead>
<tbody>
<tr>
<td><b>Preview flow</b></td>
<td>P1 inline · P2 dedicated modal · P3 JSON-only</td>
<td class="opt">P2 + P3 export as side button</td>
</tr>
<tr>
<td><b>Part-suffix canonical shape</b></td>
<td><code>#part2 [1080p]</code> · <code>[1080p] #part2</code> · <code>.2of2 [1080p]</code> · leave alone</td>
<td class="opt"><code>#part2 [1080p]</code><br><span class="small">resolution at end matches the canonical regex; <code>#partN</code> matches existing extract_id convention</span></td>
</tr>
<tr>
<td><b>_PARTN normalization (9 files)</b></td>
<td>Convert <code>_PART1</code><code>#part1</code> · Leave as-is</td>
<td class="opt">Optional group, default deselected<br><span class="small">extract_id already handles both; cosmetic only</span></td>
</tr>
<tr>
<td><b>copy_suffix conflict policy</b></td>
<td>Auto-skip + report · Auto-include + warn · User decides per-row</td>
<td class="opt">Auto-skip + default-uncheck<br><span class="small">prevents clobbering real dupes; user can override</span></td>
</tr>
<tr>
<td><b>Multi-pattern transforms</b></td>
<td>Composite single row · Sequential per pattern</td>
<td class="opt">Composite<br><span class="small">simpler review; reason field lists all applied transforms</span></td>
</tr>
<tr>
<td><b>Revert plan artifact</b></td>
<td>None · Auto-save JSON · Save on user opt-in</td>
<td class="opt">Auto-save to disk on apply<br><span class="small">cheap safety net; user can ignore if not needed</span></td>
</tr>
<tr>
<td><b>Progress UI during apply</b></td>
<td>None · Spinner · Full progress bar + ETA</td>
<td class="opt">Full bar + ETA + cancel<br><span class="small">apply takes 1-2 min; user needs visibility</span></td>
</tr>
<tr>
<td><b>Placement in extension</b></td>
<td>Library Review pane (add button) · New Setup card · Detached window</td>
<td class="opt">Library Review pane<br><span class="small">already where users go for library issues</span></td>
</tr>
<tr>
<td><b>Persistent ignore list</b></td>
<td>None · Per-file flag in cache · Pattern-based regex</td>
<td class="opt">Per-file flag<br><span class="small">simplest; cleared on cache rebuild is acceptable</span></td>
</tr>
</tbody>
</table>
<!-- ============================================== -->
<h2>9 — What's NOT in this mockup (scope-fenced)</h2>
<ul style="color:var(--muted); font-size:12px; line-height:1.8; margin:0 0 30px 18px;">
<li><b style="color:#dce5ed;">ffprobe / resolution probing</b> — Phase 2, separate mockup if/when needed</li>
<li><b style="color:#dce5ed;">Bare-name renames</b> (~775 missing_resolution files) — out of scope without resolution data</li>
<li><b style="color:#dce5ed;">Quality-mapping editor</b> (HD → 1080p config) — only 4 files affected; not worth own UI</li>
<li><b style="color:#dce5ed;">Bulk cancellation that aborts mid-rclone-call</b> — would risk corrupt remote state; not supported</li>
<li><b style="color:#dce5ed;">Cross-remote moves</b> — cleanup keeps files in same folder; only rename within remote</li>
<li><b style="color:#dce5ed;">Pattern-rule editor</b> — extends Library Issues custom_rules; future enhancement</li>
</ul>
</main>
</body>
</html>