Files
ditto.site/compiler/scripts/diag-flex.py
T
2026-06-29 15:11:48 -07:00

98 lines
5.2 KiB
Python

#!/usr/bin/env python3
"""Replicate the (updated) reconstructFlexRow and report success + bail reasons + band impact."""
import json, glob, os, sys, collections
run = sys.argv[1] if len(sys.argv) > 1 else "compiler/output-dense/sample/.clone"
caps = {}
for f in sorted(glob.glob(os.path.join(run, "source/capture/dom-*.json"))):
caps[int(os.path.basename(f)[4:-5])] = json.load(open(f))["root"]
VPS = sorted(caps); BAND_VPS = [v for v in (375, 768, 1280, 1920) if v in VPS]
acc = []
def walkidx(n):
n["_i"] = len(acc); acc.append(n); n["_kids"] = []
for c in (n.get("children") or []):
n["_kids"].append(len(acc)); walkidx(c)
walkidx(caps[VPS[0]])
N = len(acc)
# per-vp flat lists aligned by index
F = {vp: [] for vp in VPS}
def flat(n, a):
a.append(n)
for c in (n.get("children") or []): flat(c, a)
for vp in VPS: flat(caps[vp], F[vp])
def pf(v):
try: return float(str(v).replace("px", ""))
except: return 0.0
def CS(i, vp): return F[vp][i].get("computed") or {}
def BB(i, vp): return F[vp][i].get("bbox") or {}
def VIS(i, vp): return F[vp][i].get("visible")
reasons = collections.Counter(); succ = 0; succ_items = 0; bands_removed = 0
for i in range(N):
c0 = CS(i, VPS[0]); disp = c0.get("display", "")
if "flex" not in disp: continue
if c0.get("flexDirection", "row") not in ("row", "row-reverse"): reasons["not_row"] += 1; continue
if (c0.get("flexWrap") or "nowrap") != "nowrap": reasons["wrap"] += 1; continue
info = []; bail = None
for k in acc[i]["_kids"]:
if not acc[k].get("tag"): continue
ck = CS(k, VPS[0]); pos = ck.get("position", "static")
if pos in ("absolute", "fixed"): continue
if (ck.get("float") or "none") != "none": continue
if ck.get("marginLeft") == "auto" or ck.get("marginRight") == "auto": bail = "auto_margin"; break
if (ck.get("boxSizing") or "border-box") == "content-box": bail = "content_box"; break
if pf(ck.get("flexGrow")) > 0: bail = "grow"; break
basis = ck.get("flexBasis") or "auto"
if basis != "auto" and not basis.endswith("px"): bail = "basis_pct"; break
info.append((k, pf(ck.get("flexShrink")), pf(basis) if basis.endswith("px") else None,
pf(ck.get("minWidth")) if (ck.get("minWidth") or "").endswith("px") else 0,
pf(ck.get("maxWidth")) if (ck.get("maxWidth") or "").endswith("px") else float("inf")))
if bail: reasons[bail] += 1; continue
if not info: continue
used = {}; margin = {}; cw = {}; gap = {}
for vp in VPS:
pcs = CS(i, vp); pb = BB(i, vp)
cw[vp] = pb.get("width", 0) - pf(pcs.get("paddingLeft")) - pf(pcs.get("paddingRight"))
gap[vp] = pf(pcs.get("columnGap") if pcs.get("columnGap") not in (None, "normal") else pcs.get("gap"))
u = []; m = []
for (k, sh, bp, mn, mx) in info:
ck = CS(k, vp); hidden = (not VIS(k, vp)) or ck.get("display") == "none"
u.append(None if hidden else BB(k, vp).get("width", 0)); m.append(0 if hidden else pf(ck.get("marginLeft")) + pf(ck.get("marginRight")))
used[vp] = u; margin[vp] = m
def slack(vp):
vis = [j for j in range(len(info)) if used[vp][j] is not None]
return cw[vp] - sum(used[vp][j] for j in vis) - (gap[vp] * max(0, len(vis) - 1) + sum(margin[vp][j] for j in vis))
bases = []; ok = True
for j, (k, sh, bp, mn, mx) in enumerate(info):
if bp is not None: bases.append(bp); continue
base = None
for v in range(len(VPS) - 1, -1, -1):
vp = VPS[v]
if used[vp][j] is None: continue
if slack(vp) >= -0.5: base = used[vp][j]; break
if base is None: ok = False; break
bases.append(base)
if not ok: reasons["no_base"] += 1; continue
bad = False
for vp in VPS:
vis = [j for j in range(len(info)) if used[vp][j] is not None]
if not vis: continue
hyp = [min(max(bases[j], info[j][3]), info[j][4]) for j in vis]
gaps = gap[vp] * max(0, len(vis) - 1) + sum(margin[vp][j] for j in vis)
free = cw[vp] - sum(hyp) - gaps
sim = list(hyp)
if free < -0.5:
ts = sum(info[vis[k]][1] * hyp[k] for k in range(len(vis)))
if ts > 0:
for k in range(len(vis)): sim[k] = max(hyp[k] - abs(free) * (info[vis[k]][1] * hyp[k]) / ts, info[vis[k]][3])
for k in range(len(vis)):
if abs(sim[k] - used[vp][vis[k]]) > 1: bad = True
if bad: reasons["sim_mismatch"] += 1; continue
varies = [j for j in range(len(info)) if len([used[vp][j] for vp in VPS if used[vp][j] is not None]) >= 2 and max(x for vp in VPS if (x:=used[vp][j]) is not None) - min(x for vp in VPS if (x:=used[vp][j]) is not None) > 8]
if not varies: reasons["no_variation"] += 1; continue
succ += 1; succ_items += len(info)
# estimate band variants removed: for each varying item, count distinct band-vp widths != base-vp
for j in varies:
bandw = set(round(used[vp][j]) for vp in BAND_VPS if used[vp][j] is not None)
bands_removed += max(0, len(bandw) - 1)
print(f"SUCCESS lines: {succ} items: {succ_items} est. width-band variants removed: ~{bands_removed}")
for k, v in reasons.most_common(): print(f" bail {k:16} {v}")