v3: reworked posting — three panels, drafts, duplicate detection, upload progress
All checks were successful
Build and Publish Docker Image / build-and-push (push) Successful in 42s

Posting a dump was a single form where the important choices were the easiest
to miss. It is now three panels: link or file, why & where, playlists.

Composition:
- No more URL/File toggle. An empty panel offers both ways in at once and the
  kind follows what you actually did; a file dropped anywhere in the modal is
  accepted, not just on the zone.
- Categories and visibility get their own panel instead of a disclosure that
  read as optional, and the primary button stays "Next" until they've been
  seen. Visibility carries a real label now.
- The draft (link, title, why, categories, visibility) is mirrored to
  localStorage on every change and restored on reopen, so Escape or a stray
  backdrop click costs nothing. Only an attached file can't be restored, so
  that is the one case that asks before closing.
- URL dumps can carry a poster-supplied title instead of being stuck with
  whatever the page scraped, editable right under the preview.
- Multipart uploads go through XHR so there is a real progress bar and a
  percentage on the button, rather than 50 MB of silence.

Duplicates:
- New dumps.url_canonical column (+ index, backfilled by 0013) holding a lossy
  key that ignores scheme, www., trailing slashes, tracking parameters and
  YouTube share shapes. GET /api/dumps/by-url reads it, and the create form
  warns "already dumped by X" while it fetches the preview. Never blocking.

Fixes:
- /api/preview now reports whether the page was actually reached: a failed
  fetch still yields a hostname-only stub, so a dead link and a page without
  metadata used to render identically.
- The Web Share Target never worked. The manifest posts to "/", but the index
  redirect dropped the query string, so every Android share landed on the feed
  with nothing pre-filled.
- File dumps no longer take the extension into their title.
- The link field no longer autofocuses on touch, where it raised a keyboard
  over the modal.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TiAPtJZeCYYk8rKehtLUQU
This commit is contained in:
khannurien
2026-09-08 14:40:37 +00:00
parent d76154d15d
commit fb8364e24d
21 changed files with 1688 additions and 304 deletions

View File

@@ -4,6 +4,7 @@ import {
Navigate,
Route,
Routes,
useLocation,
useParams,
} from "react-router";
@@ -85,14 +86,21 @@ function useResolvedDefaultTab() {
return preferredTab === "followed" && !user ? "hot" : preferredTab;
}
// The query string has to survive the hop: the Web Share Target posts to "/",
// so an Android share arrives here as `/?share_url=…` and the feed below is the
// only thing that can act on it.
function IndexRedirect() {
return <Navigate to={`/~/${useResolvedDefaultTab()}`} replace />;
const { search } = useLocation();
return <Navigate to={`/~/${useResolvedDefaultTab()}${search}`} replace />;
}
// Bare `/<slug>` lands on that category's default feed tab.
function CategoryRedirect() {
const { categorySlug } = useParams();
return <Navigate to={`/${categorySlug}/${useResolvedDefaultTab()}`} replace />;
const { search } = useLocation();
return (
<Navigate to={`/${categorySlug}/${useResolvedDefaultTab()}${search}`} replace />
);
}
// Both `/~/:feedTab` (all) and `/:categorySlug/:feedTab` render the same feed.