fix: review round 1 — row press after a rename, related reset, dead items on removal, honest Prev/Next

- A press that begins while a field in the row is edited only ends the editing (rowOpen latches it
  at pointerdown); the card layout's row press is now tested.
- A related video reset in the player forgets the session's stored position.
- Removing the playing video passes over known-dead items and steps the way it went.
- Prev/Next off only because of dead items stay focusable and say why (HU/EN).
- The docked bar's scroller leaves room for focus rings.
- A focus-opened About card is not closed by its own scroll into view.
- EN "Clear watch progress" for the video reset, distinct from a page's "Reset view".
- The signed-out title read goes to the page yt-dlp read; the retitle writes are conditional.
- Tests: the cap of three errors, the bar's own Fullscreen, the group glyph, the backfill's reads.
This commit is contained in:
2026-11-01 05:36:59 +01:00
parent 245c0a2019
commit c910cf2f20
25 changed files with 411 additions and 63 deletions
+86 -4
View File
@@ -6,11 +6,18 @@ no post text to fall back on, and the download name copied the placeholder too.
the same reel (yt-dlp 2026.08.19, as on prod) still carries the post's text in its description.
"""
import threading
from sqlalchemy import update
from app.config import settings
from app.db import SessionLocal
from app.downloads import adapters, backfill, pipeline
from app.downloads.models import DownloadJob, MediaAsset
_URL = "https://www.facebook.com/reel/1860682468422402/"
# The page yt-dlp says it read for it (`webpage_url`); a group post's link resolves the same way.
_PAGE = "https://www.facebook.com/watch/?v=1860682468422402"
# The measured signed-out read of that reel.
_PUBLIC = {
"title": "163K reactions · 14K shares | sorry not helping #workhumor #officelife | Work dot exe",
@@ -20,12 +27,13 @@ _PUBLIC = {
def _fake_ydl(monkeypatch, outcome):
"""yt-dlp faked at its `YoutubeDL` boundary: records each read's options, answers `outcome`."""
"""yt-dlp faked at its `YoutubeDL` boundary: records each read (its options and its `url`),
answers `outcome`."""
seen: list[dict] = []
class FakeYDL:
def __init__(self, opts):
seen.append(opts)
self.opts = opts
def __enter__(self):
return self
@@ -34,6 +42,7 @@ def _fake_ydl(monkeypatch, outcome):
return False
def extract_info(self, url, download):
seen.append({**self.opts, "url": url})
return outcome
monkeypatch.setattr(backfill.yt_dlp, "YoutubeDL", FakeYDL)
@@ -61,6 +70,14 @@ def test_a_real_title_reads_nothing_more(monkeypatch):
assert seen == []
def test_a_placeholder_with_the_posts_text_behind_it_reads_nothing_more(monkeypatch):
seen = _fake_ydl(monkeypatch, _PUBLIC)
info = {"title": "Video", "description": "Office jokes, part two"}
assert backfill.public_title_info(info, _URL, adapters.FacebookAdapter(), 7) is info
assert seen == []
def test_a_placeholder_the_signed_out_page_cannot_better_stays(monkeypatch):
_fake_ydl(monkeypatch, {"title": "Facebook", "description": None})
fb = adapters.FacebookAdapter()
@@ -81,7 +98,7 @@ def test_another_source_is_never_read_again(monkeypatch):
def test_the_backfill_retitles_a_placeholder_download_and_the_names_that_repeated_it(
db, make_user, monkeypatch
):
_fake_ydl(monkeypatch, _PUBLIC)
seen = _fake_ydl(monkeypatch, _PUBLIC)
owner, other = make_user(), make_user()
asset = MediaAsset(
source_kind="url",
@@ -120,6 +137,7 @@ def test_the_backfill_retitles_a_placeholder_download_and_the_names_that_repeate
db.commit()
assert backfill.run()["retitled"] == 1
assert len(seen) == 1
db.expire_all()
assert db.get(MediaAsset, asset.id).title == "sorry not helping"
@@ -127,6 +145,66 @@ def test_the_backfill_retitles_a_placeholder_download_and_the_names_that_repeate
assert db.get(DownloadJob, renamed.id).display_name == "Office jokes"
# Run again: nothing left to retitle, and no page read for it.
assert backfill.run()["retitled"] == 0
assert len(seen) == 1
def test_a_name_given_while_the_backfill_runs_stays(db, make_user, monkeypatch):
# The owner renames the download between the backfill's retitle and its commit (the poster step
# sits between them): the owner's name wins, it is not overwritten by what was read before it.
_fake_ydl(monkeypatch, _PUBLIC)
owner = make_user()
asset = MediaAsset(
source_kind="url",
source_ref=_URL,
format_sig="fb",
status="ready",
rel_path="facebook/x.mp4",
uploader_url="https://www.facebook.com/1",
title="Facebook",
ref_count=1,
)
db.add(asset)
db.flush()
job = DownloadJob(
user_id=owner.id,
asset_id=asset.id,
source_kind="url",
source_ref=_URL,
status="done",
queue_pos=0,
profile_snapshot={},
display_name="Facebook",
)
db.add(job)
db.commit()
def rename():
with SessionLocal() as other:
other.execute(
update(DownloadJob)
.where(DownloadJob.id == job.id)
.values(display_name="Office jokes")
)
other.commit()
def poster_while_renamed(a, root):
# A rename that must wait for the backfill's write waits here, and lands after its commit.
t = threading.Thread(target=rename)
t.start()
t.join(timeout=0.5)
renamers.append(t)
return None
renamers: list[threading.Thread] = []
monkeypatch.setattr(backfill.editmod, "ensure_poster", poster_while_renamed)
assert backfill.run()["retitled"] == 1
for t in renamers:
t.join(timeout=10)
assert not t.is_alive()
db.expire_all()
assert db.get(DownloadJob, job.id).display_name == "Office jokes"
def test_a_download_titled_facebook_is_named_from_the_signed_out_page(
@@ -134,7 +212,7 @@ def test_a_download_titled_facebook_is_named_from_the_signed_out_page(
):
# The whole download path with yt-dlp and the media tools faked: the signed-in read gives only
# the placeholder, and the file and the download's name get the post's text.
_fake_ydl(monkeypatch, _PUBLIC)
seen = _fake_ydl(monkeypatch, _PUBLIC)
monkeypatch.setattr(settings, "files_root", str(tmp_path))
monkeypatch.setattr(pipeline.urlguard, "check_url", lambda url: None)
monkeypatch.setattr(pipeline, "_ensure_phone_safe", lambda _j, produced, *_a: produced)
@@ -168,12 +246,16 @@ def test_a_download_titled_facebook_is_named_from_the_signed_out_page(
"id": "1860682468422402",
"title": "Facebook",
"uploader": "Work dot exe",
"webpage_url": _PAGE,
"requested_downloads": [{"filepath": str(f)}],
}
monkeypatch.setattr(pipeline, "_extract_with_fallback", signed_in_read)
pipeline._download(job.id, asset.id, "url", _URL, {}, owner.id)
# The signed-out read goes to the page yt-dlp read, not the link queued: a group post's link
# resolves to its video, and signed out the post itself is the login wall.
assert [r["url"] for r in seen] == [_PAGE]
db.expire_all()
assert db.get(DownloadJob, job.id).status == "done"
assert db.get(MediaAsset, asset.id).title == "sorry not helping"
+1 -1
View File
@@ -579,7 +579,7 @@ def _display_write_sites() -> dict[tuple[str, str], set[str]]:
# enqueue_clip slices the source's title to the same width.
# files.ingest_spooled passes through `spool_upload`'s value, bounded there.
# routes enqueue_clip the `display_name=` keyword passes on the name bound above it.
# pipeline._download / _process_clip / worker._finish_from_cache
# pipeline._download / _process_clip / worker._finish_from_cache / backfill._retitle
# slice a fetched or stored title to the column's width.
#
# Held as EXACT EQUALITY rather than as a list of offenders. The offender form could only fail