From 061dc393c5c24eb8dc941d841a329afbb82a9398 Mon Sep 17 00:00:00 2001 From: yibo Date: Wed, 2 Sep 2026 22:10:49 +0800 Subject: [PATCH] Add T31: surface characters' totalSegments/remainingSegments/status --- admin-characters.html | 11 +++++++++++ dressup-crud.test.js | 29 +++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 0 deletions(-) diff --git a/admin-characters.html b/admin-characters.html index 3489951..95df689 100644 --- a/admin-characters.html +++ b/admin-characters.html @@ -69,6 +69,7 @@ 默认 来源 状态 + 片段配额 创建时间 操作 @@ -97,6 +98,14 @@ function escapeHtml(s) { return div.innerHTML; } +// T31: totalSegments/remainingSegments are only ever non-null for +// source:"user_paid" characters (mode 3, T35) — admin_seed characters like +// the default one stay null forever (unlimited plays, not a purchased quota). +function segmentsLabel(c) { + if (c.totalSegments == null) return "-"; + return `${c.remainingSegments ?? 0}/${c.totalSegments}`; +} + async function uploadImage(file) { const fd = new FormData(); fd.append("file", file); @@ -134,6 +143,7 @@ function render() { ${c.source} ${c.status} + ${segmentsLabel(c)} ${new Date(c.createdAt).toLocaleDateString()} @@ -150,6 +160,7 @@ function render() { ${c.isDefault ? '默认' : ""} ${c.source} ${c.status} + ${segmentsLabel(c)} ${new Date(c.createdAt).toLocaleDateString()} diff --git a/dressup-crud.test.js b/dressup-crud.test.js index 9648a94..f8361d6 100644 --- a/dressup-crud.test.js +++ b/dressup-crud.test.js @@ -122,6 +122,35 @@ test("characters: setting isDefault on a second character clears it on the first ); }); +// T31 (dev-plan-5.md): totalSegments/remainingSegments/status were already +// part of the schema (T1) but never actually read/written until mode 3 +// (T35) exists. This just proves the plumbing (PATCH, admin.html's +// segmentsLabel()) has something real to render — admin_seed characters +// stay null/active by default (unaffected). +test("characters: totalSegments/remainingSegments/status round-trip via PATCH (mode 3's shape, before T35 exists)", async () => { + const created = await fetch( + `${BASE}/api/admin/characters`, + authed({ method: "POST", body: JSON.stringify({ name: "付费人物占位", imageUrl: "/x.png", appearanceDescriptor: "d" }) }) + ).then((r) => r.json()); + assert.equal(created.totalSegments, null); + assert.equal(created.remainingSegments, null); + assert.equal(created.status, "active"); + + const patched = await fetch( + `${BASE}/api/admin/characters/${created.id}`, + authed({ method: "PATCH", body: JSON.stringify({ totalSegments: 3, remainingSegments: 3, status: "active" }) }) + ).then((r) => r.json()); + assert.equal(patched.totalSegments, 3); + assert.equal(patched.remainingSegments, 3); + + const expired = await fetch( + `${BASE}/api/admin/characters/${created.id}`, + authed({ method: "PATCH", body: JSON.stringify({ remainingSegments: 0, status: "expired" }) }) + ).then((r) => r.json()); + assert.equal(expired.status, "expired"); + assert.equal(expired.totalSegments, 3, "totalSegments is history, not reset by going expired"); +}); + test("garments: create/list round-trip with defaults", async () => { const res = await fetch( `${BASE}/api/admin/garments`, -- libgit2 0.21.4