feat(cards): say what the linked material is — type, size, and PDF page count (#57) #75
No reviewers
Labels
No labels
bug
discussion
enhancement
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
edufeed/edufeed-app!75
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/issue-57-linked-materials-hover"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #57 slices 1 and 2. Slices 3 and 4 are deliberately out of scope and need a product call — see the bottom.
The hover badge over a resource cover read
📎 1 Materialien verlinkt— a count, with the wrong plural. It now reads📎 PDF · 12 Seiten · 2,4 MB.Slice 1 —
2d5eb5e3Type and size from data already on the event (
encoding:encodingFormat,encoding:contentSize). No format change, no migration, no network call. Fixes the1 Materialienplural while in there.One judgement worth flagging:
materialTypeFromMimereturnsnull, not'file', forapplication/octet-stream— the publish path defaults to octet-stream when the uploader did not know, so treating it as a known type would have every unlabelled upload claim to be a binary.Slice 2 —
88776df9AMB's
encoding:*has no page count, so it has to be read from the file. Nearly free in the common case:/api/pdf-thumbnailalready parses the document to render the cover, sorenderPdfThumbnailnow returnsnumPagesalongside the WebP and the endpoint writes apages.jsonsidecar./api/pdf-inforeads it. A card whose cover rendered costs no second fetch and no second parse; the fetch-and-parse path is only for files no thumbnail was ever rendered for, and it skips canvas and sharp entirely.Guardrails are shared, not reimplemented.
$lib/server/pdfSource.jsnow owns the fetch, size cap, redirect re-validation, content-type check and cache paths for both endpoints. That is the mistake #31 fixed in/api/reader— a second private copy of the private-IP check drifted from the first, and the weaker copy is what shipped.Rights model reused deliberately. Reading a page count means fetching the file, which is the same act
canDeriveThumbnailexists to authorise, so the card gates on it first. Attached files only — anrlink carries no attestation, so an external PDF gets no page count.Fetched on first
pointerenter, not on render: the badge is hover-only and a feed must not fire a request per card just to sit there. Memoised per URL including failures — a card must not retry a 404 on every hover.The cover div is given no ARIA role on purpose. It is not interactive; the handler is a cache warm-up and the badge renders type and size without it, so a user who never produces a pointer event loses nothing that
group-hoverwas not already hiding.Verification
Four full-suite runs at
88776df9, clean tree, ondeve0aa0aba:477 files / 5279 tests passgift-wrap-publish.test.js(#70)477 files / 5279 tests passFailed to resolve import "../runtime.js")Not one FAIL line in any run names a file this branch touches, and the new tests passed in all four. The failures are #70's known DM/inbox family plus a generated-output race under parallel load. Stating the spread rather than quoting the best run, because one reading of a flaky suite is not a baseline.
New tests confirmed load-bearing by reverting the code they cover:
The cross-endpoint claim is pinned too: render the cover, then ask for the count, assert exactly one upstream fetch. If the two endpoints ever disagree on the cache key that test fails rather than silently doubling third-party traffic.
Known risk, stated rather than discovered later
src/routes/api/pdf-info/__tests__/server.test.jsdoes a real pdf.js parse, and its cross-endpoint case does a real canvas + sharp render.pdf-thumbnail/__tests__/server.test.jsis already named in #70 as joining the timeout-prone set under heavier load, so this new file is a plausible future member of it. It did not fail in any of the four runs above.Deliberately not in this PR
rlinks — a bare URL with no type. Extension guess vs an HTTPHEADper card. TheHEADmeans an outbound request to a third party on every feed render, which is a privacy and rights question, not a perf one. Recommendation: extension guess, no network call.