mirror of
https://github.com/vinta/awesome-python.git
synced 2026-11-08 02:15:12 -06:00
fix: give each review page verdict a single meaning
Drop meant three things on the review page: remove a listed entry, leave a candidate out, or park a structure question with no record. Verdicts are now per row kind: keep/remove for listed entries, add/reject for candidates, restructure/park/reject for structure questions, and the feedback JSON carries the same words. The page also gains an optional Source column naming where each row came from. Star scans now judge an unlisted project a reject names as the better pick, seed judgment-call rows as park, and keep any parked row unrecorded. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -27,7 +27,7 @@ Done when every entry in scope has downloads (or a stated no-signal reason), rep
|
||||
|
||||
## 3. Verdicts
|
||||
|
||||
Draft a verdict and reason for every entry, restructure before cap: decide Splits, mints, and re-homes first — cutting before restructuring destroys entries — then apply the cap tier by tier, including promotions and demotions between obvious choice and challenger. Ground every reason in the fetched evidence; label anything unverifiable as a judgment call. Where fresh evidence contradicts the standing verdict, say so in the reason instead of silently keeping the seed. A proposed addition under `docs/audit-logs.md` "Rejected Additions" seeds as drop from that line.
|
||||
Draft a verdict and reason for every entry, restructure before cap: decide Splits, mints, and re-homes first — cutting before restructuring destroys entries — then apply the cap tier by tier, including promotions and demotions between obvious choice and challenger. Ground every reason in the fetched evidence; label anything unverifiable as a judgment call. Where fresh evidence contradicts the standing verdict, say so in the reason instead of silently keeping the seed. A proposed addition under `docs/audit-logs.md` "Rejected Additions" seeds as reject from that line.
|
||||
|
||||
## 4. Review and go
|
||||
|
||||
|
||||
@@ -1,24 +1,29 @@
|
||||
---
|
||||
name: preview-verdicts
|
||||
description: Generate the interactive keep/drop/add verdict preview (HTML page with per-row feedback controls) whenever a prune sweep, batch entry edit, or restructure needs maintainer review before touching README.md — and process the feedback JSON the maintainer pastes back.
|
||||
description: Generate the interactive keep/remove/add/reject verdict preview (HTML page with per-row feedback controls) whenever a prune sweep, batch entry edit, or restructure needs maintainer review before touching README.md — and process the feedback JSON the maintainer pastes back.
|
||||
---
|
||||
|
||||
# Verdict preview
|
||||
|
||||
Maintainer review happens through an interactive HTML page: one row per entry with your seeded verdict and reason, Keep/Drop buttons (Add/Drop on proposed new entries) and a reason field for the maintainer, and a **Copy feedback** button that exports only changed or commented rows as JSON. Generate the page, wait for the pasted JSON, then apply it. Entry changes land in README.md only after the review — and only on an explicit go.
|
||||
Maintainer review happens through an interactive HTML page: one row per entry with your seeded verdict and reason, verdict buttons and a reason field for the maintainer, and a **Copy feedback** button that exports only changed or commented rows as JSON. Generate the page, wait for the pasted JSON, then apply it. Entry changes land in README.md only after the review — and only on an explicit go.
|
||||
|
||||
## Generate the preview
|
||||
|
||||
1. Build the `DATA` array. A group is `[section, subcategory, rows]`; a row is `[entry, url, downloads, verdict, reason]`.
|
||||
1. Build the `DATA` array. A group is `[section, subcategory, rows]`; a row is `[entry, url, downloads, verdict, reason]`, plus an optional sixth `source` field.
|
||||
- `source` says where a row came from when that is not obvious, such as the starred repo behind a scan candidate or an incumbent pulled in during the audit. The page shows a Source column only when a row has one.
|
||||
- `subcategory` may carry a note after ` — ` (rendered muted): use it for proposed splits, re-homes, or anything the maintainer should weigh for the whole group.
|
||||
- `downloads` is PyPI last-month as a comma-formatted string; use `—` when no signal exists (e.g. agent skill packs), `stdlib` for standard-library modules, `fetch failed` when the lookup failed. State the fetch date in the sub-header.
|
||||
- `verdict` is `keep`, `drop`, or `add`, seeded from the current adjudication or dry-run. `add` marks a proposed new entry; its row gets Add/Drop buttons, where Drop means not added. Show a replacement as two rows: the old entry `drop`, the new entry `add`.
|
||||
- `verdict` is seeded from the current adjudication or dry-run, and its row kind sets the buttons, so each verdict word names one outcome:
|
||||
- Listed entry: `keep` or `remove` (taken out of README.md).
|
||||
- Proposed new entry: `add` or `reject` (not added, logged under "Rejected Additions").
|
||||
- Structure question: `restructure` (take the change the reason or group note describes), `park` (no record, so it resurfaces), or `reject` (logged under "Rejected Additions"); seed it `restructure` or `park`.
|
||||
- Show a replacement as two rows: the old entry `remove`, the new entry `add`.
|
||||
- `reason` is plain language the maintainer reads cold — no invented shorthand. When fresh evidence contradicts the seeded verdict (a big download count on a drop, a dead repo on a keep), say so in that row's reason instead of silently changing the seed.
|
||||
2. Copy `template.html` (sibling of this file) and replace the placeholders: `__TITLE__` (page title), `__SUB__` (sub-header: scope, seed provenance, fetch date, and the standing instruction to flip/comment then Copy feedback), `__KEY__` (localStorage key), `__DATA__` (the array). `__KEY__` must be unique per review — slug plus date, e.g. `awesome-python-science-2026-09-01` — because saved state under a reused key bleeds a previous review's flips into rows with the same section and entry name.
|
||||
3. Write the page to `local/awesome-python-<slug>-preview.html` in the repo root (git-ignored; create the directory if needed), `open` it, and tell the maintainer the path and the return path: flip or comment rows (they highlight yellow), press **Copy feedback**, paste the JSON into the chat. Done when the page is open and the return path is stated.
|
||||
|
||||
## Process the pasted feedback
|
||||
|
||||
Each JSON row is `{section, subcategory, entry, my_verdict, your_verdict, reason}`. On a row seeded `add`, `your_verdict: "drop"` means do not add it. The maintainer's verdict is final — apply it, never re-argue it. An empty reason means the verdict stands unexplained; that is enough.
|
||||
Each JSON row is `{section, subcategory, entry, my_verdict, your_verdict, reason}`, with verdict words as defined above. The maintainer's verdict is final — apply it, never re-argue it. An empty reason means the verdict stands unexplained; that is enough.
|
||||
|
||||
Before executing, surface anything the flips imply that the maintainer has not decided: a use case pushed past its cap, an entry left homeless by a proposed split, a request that is already satisfied (a no-op). Ask, then execute on their go. Done when every pasted row is either applied or surfaced back — none silently dropped.
|
||||
|
||||
@@ -27,20 +27,24 @@
|
||||
td.name { white-space: nowrap; font-weight: 600; }
|
||||
td.name a { color: var(--ink); text-decoration: none; border-bottom: 1px solid #c8c4c0; }
|
||||
td.name a:hover { border-bottom-color: var(--ink); }
|
||||
td.source { font-size: .95rem; color: var(--muted); max-width: 14rem; }
|
||||
td.dl { white-space: nowrap; font-variant-numeric: tabular-nums; color: var(--muted); text-align: right; }
|
||||
td.mine { white-space: nowrap; }
|
||||
.badge { display: inline-block; font-size: .78rem; font-weight: 700; letter-spacing: .03em; padding: .12em .5em; border-radius: 4px; }
|
||||
.badge.keep { color: var(--keep); background: var(--keep-bg); }
|
||||
.badge.drop { color: var(--drop); background: var(--drop-bg); }
|
||||
.badge.add { color: var(--add); background: var(--add-bg); }
|
||||
.badge.remove, .badge.reject { color: var(--drop); background: var(--drop-bg); }
|
||||
.badge.add, .badge.restructure { color: var(--add); background: var(--add-bg); }
|
||||
.badge.park { color: var(--muted); background: var(--line); }
|
||||
td.reason { color: #44403c; font-size: .95rem; max-width: 34rem; }
|
||||
td.yours { white-space: nowrap; }
|
||||
.seg { display: inline-flex; border: 1px solid var(--line); border-radius: 6px; overflow: hidden; }
|
||||
.seg button { border: 0; background: #fff; padding: .3rem .8rem; font: inherit; font-size: .88rem; cursor: pointer; color: var(--muted); }
|
||||
.seg button + button { border-left: 1px solid var(--line); }
|
||||
.seg button.on.k { background: var(--keep); color: #fff; font-weight: 700; }
|
||||
.seg button.on.a { background: var(--add); color: #fff; font-weight: 700; }
|
||||
.seg button.on.d { background: var(--drop); color: #fff; font-weight: 700; }
|
||||
.seg button.on { color: #fff; font-weight: 700; }
|
||||
.seg button.on.keep { background: var(--keep); }
|
||||
.seg button.on.add, .seg button.on.restructure { background: var(--add); }
|
||||
.seg button.on.remove, .seg button.on.reject { background: var(--drop); }
|
||||
.seg button.on.park { background: var(--muted); }
|
||||
td.fb input { width: 100%; min-width: 14rem; font: inherit; font-size: .92rem; padding: .3rem .5rem; border: 1px solid var(--line); border-radius: 6px; background: #fff; }
|
||||
td.fb input:focus { outline: 2px solid var(--keep); outline-offset: 0; }
|
||||
.bar { position: fixed; bottom: 0; left: 0; right: 0; background: var(--ink); color: #fafaf9; display: flex; align-items: center; gap: 1.4rem; padding: .7rem 2.5rem; font-size: .95rem; }
|
||||
@@ -70,31 +74,34 @@ const KEY = "__KEY__";
|
||||
let state = {};
|
||||
try { state = JSON.parse(localStorage.getItem(KEY) || "{}"); } catch (e) {}
|
||||
|
||||
// A row's seeded verdict picks its button set, so each verdict word names one outcome.
|
||||
const CHOICES = { keep: ["keep", "remove"], remove: ["keep", "remove"], add: ["add", "reject"], reject: ["add", "reject"], restructure: ["restructure", "park", "reject"], park: ["restructure", "park", "reject"] };
|
||||
const cap = v => v[0].toUpperCase() + v.slice(1);
|
||||
|
||||
function rowId(section, name) { return section + "::" + name; }
|
||||
|
||||
function render() {
|
||||
const app = document.getElementById("app");
|
||||
let html = "", lastSection = "";
|
||||
const hasSource = DATA.some(g => g[2].some(r => r.length > 5));
|
||||
for (const [section, subcat, rows] of DATA) {
|
||||
if (section !== lastSection) { html += `<h2>${section}</h2>`; lastSection = section; }
|
||||
const parts = subcat.split(" — ");
|
||||
html += `<h3>${parts[0]}${parts[1] ? ` <span class="note">— ${parts[1]}</span>` : ""}</h3>`;
|
||||
html += `<table><tr><th>Entry</th><th style="text-align:right">Downloads/mo</th><th>My verdict</th><th>My reason</th><th>Yours</th><th>Your reason (optional)</th></tr>`;
|
||||
for (const [name, url, dl, mine, reason] of rows) {
|
||||
html += `<table><tr><th>Entry</th>${hasSource ? "<th>Source</th>" : ""}<th style="text-align:right">Downloads/mo</th><th>My verdict</th><th>My reason</th><th>Yours</th><th>Your reason (optional)</th></tr>`;
|
||||
for (const [name, url, dl, mine, reason, source] of rows) {
|
||||
const id = rowId(section, name);
|
||||
const s = state[id] || {};
|
||||
const yours = s.verdict || mine;
|
||||
const changed = yours !== mine || (s.feedback || "").trim() !== "";
|
||||
html += `<tr id="${cssId(id)}" class="${changed ? "changed" : ""}">
|
||||
<td class="name"><a href="${url}" target="_blank" rel="noopener">${name}</a></td>
|
||||
${hasSource ? `<td class="source">${source || ""}</td>` : ""}
|
||||
<td class="dl">${dl}</td>
|
||||
<td class="mine"><span class="badge ${mine}">${mine.toUpperCase()}</span></td>
|
||||
<td class="reason">${reason}</td>
|
||||
<td class="yours"><span class="seg">
|
||||
${mine === "add"
|
||||
? `<button class="a ${yours === "add" ? "on" : ""}" onclick="setV('${esc(id)}','add')">Add</button>`
|
||||
: `<button class="k ${yours === "keep" ? "on" : ""}" onclick="setV('${esc(id)}','keep')">Keep</button>`}
|
||||
<button class="d ${yours === "drop" ? "on" : ""}" onclick="setV('${esc(id)}','drop')">Drop</button>
|
||||
${CHOICES[mine].map(v => `<button class="${v} ${yours === v ? "on" : ""}" onclick="setV('${esc(id)}','${v}')">${cap(v)}</button>`).join("")}
|
||||
</span></td>
|
||||
<td class="fb"><input value="${(s.feedback || "").replace(/"/g, """)}" oninput="setF('${esc(id)}', this.value)" placeholder="why…"></td>
|
||||
</tr>`;
|
||||
@@ -137,14 +144,16 @@ function collect() {
|
||||
}
|
||||
|
||||
function updateCounts() {
|
||||
let keeps = 0, adds = 0, drops = 0, changed = 0;
|
||||
const tally = {};
|
||||
let changed = 0;
|
||||
for (const [section, , rows] of DATA) for (const [name, , , mine] of rows) {
|
||||
const s = state[rowId(section, name)] || {};
|
||||
const yours = s.verdict || mine;
|
||||
if (yours === "keep") keeps++; else if (yours === "add") adds++; else drops++;
|
||||
tally[yours] = (tally[yours] || 0) + 1;
|
||||
if (yours !== mine) changed++;
|
||||
}
|
||||
document.getElementById("counts").innerHTML = `<b>${keeps}</b> keep · <b>${drops}</b> drop · <b>${adds}</b> add | <b>${changed}</b> verdicts flipped`;
|
||||
const counts = Object.entries(tally).map(([v, n]) => `<b>${n}</b> ${v}`).join(" · ");
|
||||
document.getElementById("counts").innerHTML = `${counts} | <b>${changed}</b> verdicts flipped`;
|
||||
}
|
||||
|
||||
function copyFeedback() {
|
||||
|
||||
@@ -21,16 +21,16 @@ Append each screen-out to `local/starred-scan.tsv` as it is decided, one `repo<T
|
||||
|
||||
## 3. Judge
|
||||
|
||||
Group survivors by target use case. Per group, invoke `audit-the-list` with: "Judge proposed entries <names, each with its GitHub repo> for the <section — subcategory> use case. Evidence and Verdicts steps only; report the verdicts back. No preview page, no README changes, no commits." Done when every survivor holds an add or drop verdict with a reason, and each add at cap names the entry it displaces.
|
||||
Group survivors by target use case. Per group, invoke `audit-the-list` with: "Judge proposed entries <names, each with its GitHub repo> for the <section — subcategory> use case. Evidence and Verdicts steps only; report the verdicts back. No preview page, no README changes, no commits." A reject whose reason names an unlisted project as the better pick turns that project into a candidate too: judge it in the same group and mark its source as found in the audit, not starred. Done when every survivor holds an add or reject verdict with a reason, and each add at cap names the entry it displaces.
|
||||
|
||||
## 4. Review
|
||||
|
||||
Run `preview-verdicts` over all groups: survivors as `add` or `drop` rows, displaced entries as `drop`, structure questions noted on their group, and any `prior` verdict quoted in the row's reason. Done when the maintainer's feedback is processed and they gave the go.
|
||||
Run `preview-verdicts` over all groups: survivors as `add` or `reject` rows, displaced entries as `remove`, structure questions as `park` or `restructure` rows, and any `prior` verdict quoted in the row's reason. Seed `park` on any row whose verdict rests on a judgment call, except an add where only the tier is in doubt, so the page holds only settled calls. Done when the maintainer's feedback is processed and they gave the go.
|
||||
|
||||
## 5. Record
|
||||
|
||||
- **Added**: follow `audit-the-list` step 5 for the README change, one commit per entry.
|
||||
- **Rejected**: a candidate that reached review and was not added gets one line under "Rejected Additions": `- owner/repo -- yyyy-mm-dd, <downloads>/month (<source>): <reason>.`, replacing its earlier line.
|
||||
- **Parked**: a structure question the maintainer leaves open gets no record, so it resurfaces next run.
|
||||
- **Parked**: a row the maintainer parks gets no record, so it resurfaces next run.
|
||||
|
||||
Done when every candidate from step 1 is listed in README.md, a ledger row, a Rejected Additions line, or parked by the maintainer.
|
||||
|
||||
Reference in New Issue
Block a user