Save source descriptions, add "Why it's #N", fix IGDB studio names

Descriptions
- Items keep the source's description (TMDB/TheTVDB overview, IGDB
  summary, Wikipedia intro) in a new items.description column
  (migration 3); editable in the item form
- Deezer/MusicBrainz track counts and album names are now display-only
  hints, not saved as descriptions

Why it's #N
- The owner's note is presented as "Why it's #N" in the editor and the
  public detail view, above an "About" section with the description

IGDB developers
- IGDB credits internal divisions ("Nintendo EAD", "Nintendo EPD
  Production Group No. 8"). A developer whose name shares its first word
  with its parent company is credited to the parent ("Nintendo");
  independently named studios keep their name (Retro Studios, Naughty
  Dog, Landfall Games)
- Fall back to the publisher when IGDB lists no developer

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
cdnutterandClaude Opus 5.5 committed 2026-09-24 18:05:20 -07:00
1 parent 3923444b3b
commit 7320e3f707
16 files changed
+207 -44

No files matched your search

+9 -4
View File
@@ -271,7 +271,7 @@ function renderResults(res) {
h("div", { style: { minWidth: 0 } },
h("div", { class: "result-title" }, r.title),
sub && h("div", { class: "result-sub" }, sub),
r.overview && h("div", { class: "result-overview" }, r.overview),
(r.overview || r.hint) && h("div", { class: "result-overview" }, r.overview || r.hint),
),
action,
);
@@ -294,6 +294,7 @@ function fromResult(r) {
return {
title: r.title, subtitle: r.subtitle, year: r.year, externalUrl: r.externalUrl,
imageUrl: r.imageUrl, provider: r.provider, providerId: r.providerId, extra: { ...(r.extra || {}) },
description: r.overview || "",
};
}
@@ -344,7 +345,10 @@ function openItemForm({ item, result }) {
const title = h("input", { required: true, maxlength: 500, value: src.title || "" });
const subtitle = h("input", { maxlength: 500, value: src.subtitle || "" });
const year = h("input", { maxlength: 20, value: src.year || "", inputmode: "numeric", style: { maxWidth: "140px" } });
const note = h("textarea", { rows: 3, maxlength: 10000, value: item?.note || "", placeholder: "Why it's here. Shown to visitors." });
const description = h("textarea", { rows: 4, maxlength: 10000, value: src.description || "", placeholder: "What it is, in a few sentences." });
const note = h("textarea", { rows: 3, maxlength: 10000, value: item?.note || "", placeholder: "What earns it this spot? Shown to visitors." });
const source = { tmdb: "TMDB", tvdb: "TheTVDB", igdb: "IGDB", wikipedia: "Wikipedia" }[src.provider];
const rankLabel = editing ? `#${item.rank}` : `#${list.itemCount + 1}`;
const link = h("input", { type: "url", maxlength: 2000, value: src.externalUrl || "", placeholder: "https://" });
// Theater production gets top billing with suggestions.
@@ -438,7 +442,8 @@ function openItemForm({ item, result }) {
field(cat.key === "theater" ? "Year (of this production)" : "Year", year),
),
extraInputs.length > 0 && h("div", { class: "row" }, extraInputs.map((x) => field(x.label, x.input))),
field("Your note", note),
field("Description", description, source ? `From ${source}. Edit it if you like.` : "Optional. Shown to visitors."),
field(`Why it's ${rankLabel}`, note, "Your take on why it earns its place in the ranking."),
field("Link", link, "Where the title links to on your page."),
error,
),
@@ -461,7 +466,7 @@ function openItemForm({ item, result }) {
if (production) newExtra.production = production.value.trim();
for (const x of extraInputs) newExtra[x.key] = x.input.value.trim();
const body = {
title: title.value, subtitle: subtitle.value, year: year.value, note: note.value,
title: title.value, subtitle: subtitle.value, year: year.value, note: note.value, description: description.value,
externalUrl: link.value.trim(), extra: newExtra,
imageUrl: clearImage ? "" : imageUrl.value.trim(), clearImage,
};