Skip to content

Show the uploader outside the main grid - #730

Merged
ShaneIsrael merged 3 commits into
mainfrom
develop
Sep 14, 2026
Merged

ShaneIsrael merged 3 commits into
mainfrom
develop

Conversation

@ShaneIsrael

@ShaneIsrael ShaneIsrael commented Sep 14, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #727.

Attribution was only visible on the feed cards. Checking who owned a clip meant going back to the grid and searching for it by name, and the File Manager could set an uploader but never showed one — which made reattributing an existing library tedious in exactly the way the issue describes.

File Manager

  • /api/admin/files and /api/admin/image-files now include uploader, in the same shape the feeds already use. Video.uploader is lazy="joined", so this costs no extra query.
  • Sortable Uploader column on both tabs. Unattributed media sorts as greater than every name, so it gathers at the end ascending and at the top descending — finding it is the whole point of the column, so one click has to reach it rather than leaving it scattered.
  • Uploader filter, a chip showing how much of the library is still unattributed that jumps straight to it, and search over uploader name and username.
  • The filter options and the count are derived from the loaded rows rather than a second request, so they cannot disagree with what the table is showing.

The migration flow the issue asks for is now: click the unattributed chip → select all → Uploader.

Clip views

The byline appears on the watch page, the video modal and the image view. The details payload already carried it, so these needed no API change.

Editing

An Uploader field on the video and image edit modals, for administrators only.

It writes through the existing bulk-set-uploader route rather than the details PUT. That whitelist was deliberately narrowed, and the route is reachable by non-admin owners via can_modify — reassigning ownership is an administrator's call, not part of editing a caption. The field hides itself on the 403 from /api/admin/uploaders, so nothing offers a control whose save would be rejected.

A shared UploaderPicker replaces the picker that had been copied into both File Manager dialogs.

Share cards

/w/ and /i/ lead their og:description with the uploader and set author and profile:username. An unfurl in chat is often the only place the recipient looks, and descriptions get truncated there, so the byline goes first.

Also: the watch page's meta tags (abf24e9)

Verifying the byline turned up two problems in the Helmet block, fixed in their own commit.

The three og:video tags were grouped behind a Fragment. react-helmet reads each child's type as a tag name and matches it against a list, which a Fragment never matches, so those three tags were silently dropped from the rendered head.

The remaining tags carried their values in value rather than content. react-helmet only copies over the attributes a meta tag actually has, so every one of them rendered empty.

Neither affected link unfurls — crawlers get the server-rendered /w/ page from templates/metadata.html, which was always correct.

The Fragment also crashes react-helmet outright (Cannot convert a Symbol value to a string) and, with no error boundary above it, blanks the whole view — but only in development: that code path sits inside react-helmet's warnOnInvalidChildren, which is guarded by process.env.NODE_ENV !== "production" and is dead-code-eliminated from the production bundle. Verified by serving a production build of the unfixed file, which renders normally. So this was a local-development annoyance, never a user-facing outage.

Testing

Verified against a running instance with a seeded library:

  • Uploader column renders with avatars and an "Unattributed" placeholder; sorting toggles correctly in both directions and carries the folder grouping with it
  • The chip and filter narrow to unattributed; search matches both mira and Zoe Quinn
  • Editing a clip's uploader persisted and updated the card byline immediately; clearing an image's uploader (the explicit-null path) worked
  • Signed in as a non-admin with edit_own: the Uploader field is absent from the edit modal, and /api/admin/uploaders and bulk-set-uploader both return 403
  • /watch renders again — byline present and linking to the profile, absent with no dangling separator when unattributed, console clean
  • Share cards checked through the real routes across all four uploader/description combinations, including a hostile display name to confirm Jinja autoescaping holds

Media with no uploader is unchanged throughout: no byline, no author tag, no separator left dangling.

The Helmet block grouped the three og:video tags behind a Fragment, and
react-helmet reads each child's `type` as a tag name. A Fragment's type is
Symbol(react.fragment), so it threw "Cannot convert a Symbol value to a
string" and, with no error boundary above it, unmounted the whole view: a
blank screen for every video that is not password protected. Repeat the
condition per tag instead of grouping them.

The tags also carried their values in `value` rather than `content`.
react-helmet only copies over the attributes a meta tag actually has, so
every one of these rendered empty. Unfurls were unaffected either way —
crawlers get the server-rendered /w/ page from templates/metadata.html —
but the tags should still say what they claim to.

Broken since dc99d48, which introduced the password gate.
Attribution was only visible on the feed cards, so checking or changing who
owns a clip meant going back to the grid and searching for it by name. The
File Manager could set an uploader but never showed one.

File Manager
- /api/admin/files and /api/admin/image-files now include the uploader, in the
  same shape the feeds use. Video.uploader is lazy="joined", so it costs no
  extra query.
- Sortable Uploader column on both tabs. Unattributed media sorts as greater
  than every name, so it gathers at the end ascending and at the top
  descending — finding it is the point, so one click has to reach it.
- Uploader filter, a chip showing how much of the library is still
  unattributed that jumps straight to it, and search over uploader name and
  username. Both are derived from the loaded rows rather than a second
  request, so the count cannot disagree with the table.

Clip views
- The byline now appears on the watch page, the video modal and the image
  view. The details payload already carried it.

Editing
- Uploader field on the video and image edit modals, for administrators only.
  It writes through the existing bulk-set-uploader route rather than the
  details PUT, whose whitelist was deliberately narrowed and which a
  non-admin owner can reach: reassigning ownership is an administrator's
  call, not part of editing a caption. The field hides itself on the 403 from
  /api/admin/uploaders, so nothing offers a control whose save would fail.
- Shared UploaderPicker replaces the picker that had been copied into both
  File Manager dialogs.

Share cards
- /w/ and /i/ lead their og:description with the uploader and set author and
  profile:username. An unfurl in chat is often the only place the recipient
  looks, and descriptions get truncated there, so the byline goes first.

Media with no uploader is unchanged throughout: no byline, no author tag, no
separator left dangling.
@ShaneIsrael
ShaneIsrael merged commit a49d7b5 into main Sep 14, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Show Uploader in more places

1 participant