fix(photos): emit visibility and processing_status from the list mapper

The "hidden photo has no indicator on the admin grid" warning was not a
missing badge. The badge markup has existed since #172; the defect was in
GET /:eventId/photos, which hand-builds its response literal field by field
and never emitted `visibility` -- so the value was always undefined and
neither the grid tile nor the list row badge could render. Same omission class
as the view_count/download_count bug already commented in that file.

(The `visibility` line itself was swept into 4721bd83, whose message does not
mention it -- recording that here.)

Fixes the adjacent instance too: `processing_status` is missing from the same
mapper, so the grid's "Processing…" and "Failed"/Retry placeholders could
never render either.

On the card, reuses the existing EyeOff badge pattern from the list-view rows,
adds a tooltip on both layouts, and drops the category badge to top-9 so the
hidden badge can own the top-left corner.

Also fixes the Photo Limit spinbutton's aria-valuemax, which read 0 even with
a real cap set. Root cause: min={0} with no max -- for input[type=number]
Blink's MaxValueForRange returns DBL_MAX, fails isfinite and supplies no max,
so a11y tooling prints the default 0. Set to 2147483647, the events.photo_cap
column's real signed-32-bit ceiling (migration 074), which also stops an
out-of-range value failing only at INSERT. The sibling expires_in_days input
already had proper bounds.

Known: EventInformationCard carries the identical Photo Limit input with the
same defect; it is held by another concurrent change and follows next.

Refs testplan REPORT.md, hidden-photo and aria-valuemax warnings.
This commit is contained in:
Paul Nothaft
2026-09-02 09:43:10 +02:00
parent 3acb452090
commit fe5ac9162d
5 changed files with 186 additions and 6 deletions
@@ -0,0 +1,36 @@
/**
* `GET /api/admin/photos/:eventId/photos` hand-rolls its response object
* field by field instead of spreading the row, so any column the admin UI
* reads has to be listed explicitly. `visibility` (#172) was missing, which
* meant the grid/list "Hidden" badge could never render and a photo hidden
* from clients looked identical to a visible one (QA warning) — the same
* class of omission that previously hid view_count / download_count.
*
* Source inspection rather than an HTTP round-trip: the defect is purely
* "the key isn't in the literal", and this needs no database.
*/
const fs = require('fs');
const path = require('path');
const SOURCE = fs.readFileSync(path.join(__dirname, '..', 'adminPhotos.js'), 'utf8');
// The `res.json({ photos: photos.map(photo => ({ ... })) })` literal.
const LIST_MAPPER = /photos: photos\.map\(photo => \(\{([\s\S]*?)\n {6}\}\)\)/.exec(SOURCE);
describe('admin photo list mapper', () => {
it('has a recognisable photos.map() response literal', () => {
expect(LIST_MAPPER).not.toBeNull();
});
it.each([
'visibility',
'view_count',
'download_count',
])('exposes %s so the admin grid can render it', (field) => {
expect(LIST_MAPPER[1]).toContain(`${field}:`);
});
it('normalises visibility to the two values the UI switches on', () => {
expect(LIST_MAPPER[1]).toContain('visibility: photo.visibility === \'hidden\' ? \'hidden\' : \'visible\'');
});
});
+3
View File
@@ -1368,6 +1368,9 @@ router.get('/:eventId/photos', adminAuth, requirePermission('photos.view'), requ
// so the admin grid's "Hidden" badge could never render and a photo
// hidden from clients looked identical to a visible one (QA warning).
visibility: photo.visibility === 'hidden' ? 'hidden' : 'visible',
// Same omission: the grid's "Processing…" and "Failed"/Retry
// placeholders read this, so neither could ever render either.
processing_status: photo.processing_status || 'complete',
category_id: photo.category_id || photo.type,
category_name: photo.pc_name || (photo.type === 'individual' ? 'Individual Photos' : 'Collages'),
category_slug: photo.pc_slug || photo.type,