@@ -272,6 +272,30 @@ def root():
272272
273273 has_comparator = Config .comparator is not None
274274
275+ def collect_one_sided (existing : Path , root : Path , message : str ) -> list [dict ]:
276+ """Walk a directory present on only one side.
277+
278+ Emits one row per comparable file inside it so each file stays viewable
279+ (on the present side) and copyable to the reference, just like a missing
280+ file. Recurses into nested directories.
281+ """
282+ entries = []
283+
284+ for child in sorted (existing .iterdir (), key = lambda p : p .name ):
285+ if child .is_dir ():
286+ entries .extend (collect_one_sided (child , root , message ))
287+ elif child .is_file () and comparable_file (child ):
288+ entries .append (
289+ {
290+ "path" : str (child .relative_to (root )),
291+ "comparable" : True ,
292+ "message" : message ,
293+ "result" : "different" ,
294+ }
295+ )
296+
297+ return entries
298+
275299 def collect (a : Path , b : Path ) -> list [dict ]:
276300 """Single O(n) walk producing flat leaf rows.
277301
@@ -311,7 +335,7 @@ def collect(a: Path, b: Path) -> list[dict]:
311335 entries .append (
312336 {
313337 "path" : str (rel ),
314- "comparable" : False ,
338+ "comparable" : True ,
315339 "message" : "missing in reference (A)" ,
316340 "result" : "different" ,
317341 }
@@ -320,23 +344,25 @@ def collect(a: Path, b: Path) -> list[dict]:
320344 entries .append (
321345 {
322346 "path" : str (rel ),
323- "comparable" : False ,
347+ "comparable" : True ,
324348 "message" : "missing in monitored (B)" ,
325349 "result" : "different" ,
326350 }
327351 )
328352
329353 for name in sorted (left_dirs ^ right_dirs ):
330- rel = common_path / name
331- where = "reference (A)" if name in right_dirs else "monitored (B)"
332- entries .append (
333- {
334- "path" : str (rel ) + "/" ,
335- "comparable" : False ,
336- "message" : f"directory missing in { where } " ,
337- "result" : "different" ,
338- }
339- )
354+ if name in left_dirs :
355+ entries .extend (
356+ collect_one_sided (
357+ a / name , Config .path_a , "missing in monitored (B)"
358+ )
359+ )
360+ else :
361+ entries .extend (
362+ collect_one_sided (
363+ b / name , Config .path_b , "missing in reference (A)"
364+ )
365+ )
340366
341367 for name in sorted (left_dirs & right_dirs ):
342368 entries .extend (collect (a / name , b / name ))
@@ -638,6 +664,9 @@ def image_diff(path: str):
638664 if Config .driver is None :
639665 return "Image diff not available without browser driver" , 404
640666
667+ if not (Config .path_a / path ).is_file () or not (Config .path_b / path ).is_file ():
668+ return "Image diff not available: file missing on one side" , 404
669+
641670 diff , _ = html_render_diff (
642671 Config .path_a / path ,
643672 Config .path_b / path ,
@@ -659,6 +688,16 @@ def file(variant: str, path: str):
659688 raise ValueError ("Variant must be 'a' or 'b'" )
660689
661690 variant_root = Config .path_a if variant == "a" else Config .path_b
691+
692+ if not (variant_root / path ).is_file ():
693+ side = "reference (A)" if variant == "a" else "monitored (B)"
694+ return (
695+ "<!DOCTYPE html><html><body style='margin:0;display:flex;"
696+ "align-items:center;justify-content:center;height:100vh;"
697+ "font-family:sans-serif;color:#5f6368;background:#fafafa;'>"
698+ f"<div>file missing in { side } </div></body></html>"
699+ )
700+
662701 return send_from_directory (variant_root , path )
663702
664703
0 commit comments