Lexicon.missing_media() resolves <media> and <illustration> hrefs with a plain is_file(), so a folder authored on Windows reports files as missing when it is read on a case-sensitive filesystem.
Dict.lift <illustration href="pictures\SDD.PNG"/>
pictures/sdd.png
Loads clean on Windows; on Linux sil-lift validate warns missing-media, and --strict fails the run. The image is right there.
The lookup is the two-candidate probe in missing_media() (src/sil_lift/_model.py):
candidates = [base / relative, base / subfolder[ref.kind] / relative]
if not any(candidate.is_file() for candidate in candidates):
This is the same Windows-authored/Linux-read mismatch that #19 fixed for .lift-ranges companions, in the one other place the library touches the filesystem.
Why this is not just "call _existing_file here too"
For a companion, folding the name is unambiguously right: resolve it or silently lose every range it defines. For media the opposite argument has real force. If an illustration href says SDD.PNG and the file is sdd.png, a web export, an APK build, or anything else serving that folder from a case-sensitive host will 404. A validator that silently resolves the mismatch reports a clean bill for a dictionary that is genuinely broken downstream — hiding the defect the check exists to surface.
Options:
- A. Fold silently. ~6 source lines. Consistent with companion resolution, kills the false positives, hides genuine portability defects.
- B. Fold, but report the mismatch as its own finding (
media-case-mismatch, warning). missing-media goes back to meaning "no such file under any spelling"; the new code says "found, but only case-insensitively — this will not survive a case-sensitive host." Costs a new problem code, which is a SemVer-covered interface here.
- C. Leave it, document it. The status quo is at least honest about the portability problem, just unhelpfully worded and noisy.
B looks right, but it is a real interface decision and wants a deliberate call rather than a drive-by.
Two things to settle either way
- Only the final path component folds.
_existing_file searches one directory listing for a name; it does not fold directory components. Media hrefs routinely include pictures/ and audio/, so Pictures\x.png against an on-disk pictures/ would still misreport. Whether to fold directory components is more pressing here than it was for companions, where hrefs are basenames or same-folder relatives.
- Cost.
missing_media() iterates every media ref in the document — thousands in a real dictionary — and media folders are large. _existing_file's listings cache has to be threaded across the whole loop so it is one directory read per folder per call, not one per reference.
Rough size
Option A: ~50 lines over 3 files. Option B: ~200-250 lines over ~10 — _model.py, _validate.py, _cli.py, docs/en/guides/{validate,cli,lift-export-interop}.md, a negative corpus fixture plus its PROVENANCE.md entry, test_validate.py, test_cli.py, and CHANGELOG.md.
Split out of review discussion on #19, which deliberately kept its scope to companion resolution.
Lexicon.missing_media()resolves<media>and<illustration>hrefs with a plainis_file(), so a folder authored on Windows reports files as missing when it is read on a case-sensitive filesystem.Loads clean on Windows; on Linux
sil-lift validatewarnsmissing-media, and--strictfails the run. The image is right there.The lookup is the two-candidate probe in
missing_media()(src/sil_lift/_model.py):This is the same Windows-authored/Linux-read mismatch that #19 fixed for
.lift-rangescompanions, in the one other place the library touches the filesystem.Why this is not just "call
_existing_filehere too"For a companion, folding the name is unambiguously right: resolve it or silently lose every range it defines. For media the opposite argument has real force. If an illustration href says
SDD.PNGand the file issdd.png, a web export, an APK build, or anything else serving that folder from a case-sensitive host will 404. A validator that silently resolves the mismatch reports a clean bill for a dictionary that is genuinely broken downstream — hiding the defect the check exists to surface.Options:
media-case-mismatch, warning).missing-mediagoes back to meaning "no such file under any spelling"; the new code says "found, but only case-insensitively — this will not survive a case-sensitive host." Costs a new problem code, which is a SemVer-covered interface here.B looks right, but it is a real interface decision and wants a deliberate call rather than a drive-by.
Two things to settle either way
_existing_filesearches one directory listing for a name; it does not fold directory components. Media hrefs routinely includepictures/andaudio/, soPictures\x.pngagainst an on-diskpictures/would still misreport. Whether to fold directory components is more pressing here than it was for companions, where hrefs are basenames or same-folder relatives.missing_media()iterates every media ref in the document — thousands in a real dictionary — and media folders are large._existing_file'slistingscache has to be threaded across the whole loop so it is one directory read per folder per call, not one per reference.Rough size
Option A: ~50 lines over 3 files. Option B: ~200-250 lines over ~10 —
_model.py,_validate.py,_cli.py,docs/en/guides/{validate,cli,lift-export-interop}.md, a negative corpus fixture plus itsPROVENANCE.mdentry,test_validate.py,test_cli.py, andCHANGELOG.md.Split out of review discussion on #19, which deliberately kept its scope to companion resolution.