fix(uploads): make the drift guards fail for the reasons they claim
Deep-review round 3, targeted at the surfaces round 2's fix commit changed. Five of the eight findings are the same shape: the guards written to stop half-done sweeps were themselves half-done. test_upload.py's display-field guard walked only `ast.Assign`, so the one writer with an unbounded REQUEST HEADER behind it — `spool_upload`'s `display_name=display_text(...)` keyword — was invisible to the guard meant to protect it. It also stopped at `app/routes/`, missing the four writers in `downloads/` that actually construct the row, and asserted `not offenders`, which enforces nothing once a refactor moves the writers out of its view. It now walks assignments AND keywords across `app/`, and holds exact set equality against a registry of all twelve write sites with each one's value. test_body_limit.py's release guard used `any(... ast.walk(fn))`, which accepts a `release_read_connection` that runs AFTER the await it exists to precede — satisfying the letter of the rule while pinning a connection for exactly the transfer it was supposed to free. Position is checked now, and a late release is its own, louder failure. The registry keyed sites by file basename while scanning recursively, and `admin.py`, `discover.py` and `playlists.py` each exist both at the top level and under `plex/` — the same entry-collapse the registry was widened to close. Keyed by relative path now. And all three guards recognised a body read only by the `capped_` naming convention, so a route spelling `await request.body()` inline declared no ceiling, released no connection, and was invisible to every one of them. `release_read_connection`'s precondition claimed the caller "must have NOTHING PENDING, and that is checked rather than described" while checking only UNFLUSHED ORM work. Measured: after `add`+`flush` all three collections are empty and the rollback loses the row, and Core DML never enters them at all — both of them this codebase's canonical write shapes. It now also asks the transaction directly via `pg_current_xact_id_if_assigned()`. `errors.tooLarge` was reworded to "try a smaller file", but that key answers every route's body cap: the issue composer's over-size JSON draft got advice about a file it does not have. Back to payload-neutral; the file-specific sentence keeps its own key. The upload dialog's new danger border outranked `dragOver`, so a user replacing an over-cap file got no sign the drop zone was live — and with a file picked the label shows its name rather than "drop it here", so the border was the only feedback left. Four further findings were refuted 3/3 and are not fixed: `aria-invalid` on the drop zone (measured in Chromium — the AX node does carry it on a native button), a bidi-override spoof through `display_text` (the served filename already strips those categories, and the proposed filter would have broken every ZWJ emoji), and `capped_body` not draining (the screenshot route's edge ceiling equals its own cap, so that refusal is unreachable, and the log sink is sent by beacon with nobody reading the answer).
This commit is contained in:
@@ -125,14 +125,22 @@ export default function UploadDialog({ onClose }: { onClose: () => void }) {
|
||||
disabled={busy}
|
||||
// The refusal is tied to the control it invalidates, not left to a one-shot announcement:
|
||||
// the drop zone reads as invalid and points at the sentence, so a reader who tabs back to
|
||||
// it hears WHY, and the border says the same thing without colour being the only signal.
|
||||
// it hears WHY. (Measured, because `aria-invalid` is not in `button`'s ARIA-supported
|
||||
// list: Chromium's accessibility node for a native button does carry `invalid` from this
|
||||
// attribute.) The signal that is NOT colour is the sentence below, visible to everyone —
|
||||
// the border only reinforces it.
|
||||
aria-invalid={tooBig}
|
||||
aria-describedby={tooBig ? refusalId : undefined}
|
||||
// `dragOver` outranks `tooBig`: a drag is transient and the refusal repaints the instant
|
||||
// the drop lands, whereas a zone that stays red under the pointer tells the user their
|
||||
// drop target is dead — in the one state where they are trying to replace the file. With
|
||||
// a file picked, the label below shows its name rather than `dropActive`, so this border
|
||||
// is the ONLY feedback that the zone will accept the drop.
|
||||
className={`w-full flex flex-col items-center justify-center gap-2 rounded-xl border-2 border-dashed px-4 py-8 text-center transition disabled:opacity-60 ${
|
||||
tooBig
|
||||
? "border-danger"
|
||||
: dragOver
|
||||
? "border-accent bg-accent/5"
|
||||
dragOver
|
||||
? "border-accent bg-accent/5"
|
||||
: tooBig
|
||||
? "border-danger"
|
||||
: "border-border hover:border-fg/40"
|
||||
}`}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user