chore: post-review hardening + changelog link tooltip

Security / robustness / a11y polish on top of the color-search + tooltips work:

- URL opening: route through validated backend commands (open_map_location
  with lat/lon bounds-checking, open_changelog_url with a fixed URL) instead
  of the frontend opener:allow-open-url capability, which is now removed.
- EXIF GPS parsing: validate coordinate ranges and require the correct
  N/S/E/W hemisphere ref byte, then clamp.
- Guard double-submit on album create / add-to-album (Lightbox, BulkActionBar,
  Sidebar) and discard stale autocomplete responses in the bulk tag editor.
- Gallery tile: stop nesting <button>s — non-interactive tile div with an
  overlay button for open/toggle; checkbox and Similar promoted with z-index
  + focus rings.
- Accessibility: keyboard handlers, focus-visible rings, and aria on album
  rows, tag-manage actions, and tile controls; Tooltip uses a block <div>
  wrapper in block mode and aria-hidden when hidden.
- Show the destination URL in a tooltip on the "Full changelog" link so the
  user can see where it goes before clicking.
- Toolbar "All" filter also clears the color filter; color-filtered views no
  longer get unfiltered newly-indexed images injected.
- Sidebar reorder: bail if the album set changed mid-drag.
- Tooling: add cargo fmt scripts; alphabetize package.json scripts.
This commit is contained in:
2026-06-28 14:17:58 +01:00
parent 90dec3b212
commit bb0038e0a1
14 changed files with 211 additions and 78 deletions
+13 -6
View File
@@ -23,6 +23,7 @@ export function BulkActionBar() {
const [panel, setPanel] = useState<Panel>(null);
const [deleting, setDeleting] = useState(false);
const [creatingAlbum, setCreatingAlbum] = useState(false);
const [newAlbumName, setNewAlbumName] = useState("");
const barRef = useRef<HTMLDivElement>(null);
@@ -67,11 +68,16 @@ export function BulkActionBar() {
const handleCreateAlbum = async () => {
const name = newAlbumName.trim();
if (!name) return;
const album = await createAlbum(name);
await addToAlbum(album.id, ids);
setNewAlbumName("");
setPanel(null);
if (!name || creatingAlbum) return;
setCreatingAlbum(true);
try {
const album = await createAlbum(name);
await addToAlbum(album.id, ids);
setNewAlbumName("");
setPanel(null);
} finally {
setCreatingAlbum(false);
}
};
const btn = "rounded-md px-2.5 py-1.5 text-xs font-medium transition-colors";
@@ -187,11 +193,12 @@ export function BulkActionBar() {
placeholder="New album…"
value={newAlbumName}
onChange={(event) => setNewAlbumName(event.target.value)}
disabled={creatingAlbum}
/>
<button
type="submit"
className="rounded-md border border-white/10 bg-white/5 px-2 py-1 text-xs text-gray-300 transition-colors hover:bg-white/10 hover:text-white disabled:opacity-50"
disabled={!newAlbumName.trim()}
disabled={creatingAlbum || !newAlbumName.trim()}
>
Add
</button>