From e19fb6347f254933bccc9c1d3ca3591176a7d57b Mon Sep 17 00:00:00 2001 From: sergio Date: Fri, 17 Jul 2026 11:34:02 -0400 Subject: [PATCH] =?UTF-8?q?estado:=20vista=20humana=20del=20grafo=20(build?= =?UTF-8?q?-state-view.py=20=E2=86=92=20build-state.html)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dashboard self-contained (sin recursos externos, tema claro/oscuro) que rinde build-state.json para VER y SEGUIR: barra de avance del corpus, tiles por estado, reparto de deuda por clase, y los huecos en ORDEN DE ATAQUE. Ese último es el uso accionable del grafo: cada receta en deuda muestra sus deps coloreadas por estado; una fila 'lista' tiene todas sus deps al día (construible ya), una 'espera N' depende de N recetas también en deuda. Foto actual: 117 en deuda, 71 listas ahora. Publicado como Artifact para verlo en el navegador; el HTML también queda en el repo (abrir con file://). Regenerar ambos: scripts/build-state.py && scripts/build-state-view.py. Co-Authored-By: Claude Opus 4.8 --- docs/state/build-state.html | 815 ++++++++++++++++++++++++++++++++++++ scripts/build-state-view.py | 311 ++++++++++++++ 2 files changed, 1126 insertions(+) create mode 100644 docs/state/build-state.html create mode 100755 scripts/build-state-view.py diff --git a/docs/state/build-state.html b/docs/state/build-state.html new file mode 100644 index 00000000..83cf3a3f --- /dev/null +++ b/docs/state/build-state.html @@ -0,0 +1,815 @@ + +
+
+

estado del build · hammer

+
764 recetas · grafo cierra sin ciclos · grafo cerrado · 2026-07-17 15:33 UTC
+
+ +
+

avance del corpus

+
+
+ 647 sellado al día + 109 deuda de rebuild + 8 nunca construida + +
+
+ +
+
+
647
+
sellado al día
+
84.7%
+
+
109
+
deuda de rebuild
+
14.3%
+
+
8
+
nunca construida
+
1.0%
+
+
0
+
ilegible
+
0.0%
+
+
+ +
+

dónde está la deuda

+
+
Go + 362
+
+
357 al día + 5 en deuda
+
+
C + 322
+
+
248 al día + 74 en deuda
+
+
Rust + 45
+
+
40 al día + 5 en deuda
+
+
GUI + 30
+
+
1 al día + 29 en deuda
+
+
kernel + 5
+
+
1 al día + 4 en deuda
+
+
+ +
+

huecos, en orden de ataque

+

Cada receta en deuda con sus deps de build coloreadas por estado. Una fila + lista tiene todas sus deps al día ⇒ se puede construir ya; + una espera N depende de N recetas que también están en deuda. + 71 listas ahora.

+
+ + + + 117 en deuda +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
recetaclaseestadodeps de build
amp 0.7.1Clistazlib
bob-nvim 4.1.7Clistasin deps de build
broot 1.57.0Clistazlib
bwrap 0.11.0Clistalibcap
cargo-audit 0.22.1Clistazlib
delta 0.19.2Clistazlib
dhcpcd 10.3.2Clistalinux-headersmakepkgconf
doas 6.8.2Clistabisonmake
dosfstools 4.2Clistalinux-headersmake
dust 1.2.4Clistasin deps de build
dwarves 1.30Clistabusyboxmakecmakeelfutilszlib
e2fsprogs 1.47.4Clistalinux-headerspkgconf
gawk 5.3.2Clistamake
git-absorb 0.9.0Clistazlib
gitui 0.28.1Clistazlib
gzip 1.14Clistamake
htop 3.5.1Clistancurseslinux-headersmake
iproute2 7.1.0Clistalinux-headersbisonflexmakepkgconf
iputils 20250605Clistalibcaplinux-headersmesonsamuraipython3pkgconf
jq 1.8.1Clistaonigurumabusyboxmake
kbd 2.8.0Clistaflexlinux-headersmakepkgconf
less 704Clistancurses
libassuan 3.0.2Clistalibgpg-errorlinux-headerspkgconfmake
libevdev 1.13.4Clistapkgconf
libevent 2.1.12Clistamake
libksba 1.7.0Clistalibgpg-errorlinux-headerspkgconfmake
libsodium 1.0.22Clistamake
libudev-zero 1.0.4Clistapkgconfmake
libusb 1.0.30Clistalinux-headersmake
libuv 1.52.1Clistasin deps de build
llimphi-counter 0.1.0Clistasin deps de build
lz4 1.10.0Clistamake
mandoc 1.14.6Clistazlibmake
mtdev 1.1.6Clistapkgconfmake
musl 1.2.5Clistamake
nano 9.0Clistancurseslinux-headersmake
npth 1.8Clistamake
parted 3.7Clistautil-linuxlinux-headerspkgconfmake
pciutils 3.15.0Clistalinux-headersmake
perl 5.40.2Clistapkgconf
procps-ng 4.0.6Clistancurseslinux-headerspkgconfmake
rsync 3.4.4Clistazliblinux-headers
scdoc 1.11.5Clistamake
sed 4.10Clistalinux-headersmake
shadow 4.18.0Clistalinux-headersmake
strace 6.19Clistalinux-headersmake
tar 1.35Clistamake
tig 2.6.1Clistancursespcre2
tree 2.3.2Clistamake
tzdata 2026bClistamake
vim 9.2.0747Clistancurseslinux-headerspkgconfmake
when 1.1.45Clistamake
wpa_supplicant 2.11Clistalibnlmake
xorriso 1.5.8Clistamake
xz 5.8.3Clistamake
zellij 0.44.3Clistazlib
zstd 1.5.7Clistasin deps de build
cvemap 2.0.1Golistago
prometheus-blackbox-exporter 0.28.0Golistago
prometheus-node-exporter 1.11.1Golistago
regctl 0.11.3Golistago
fontconfig 2.18.1GUIlistafreetypeexpatpkgconfgperfpython3libpngzlibmake
fribidi 1.0.16GUIlistamesonsamuraipython3pkgconf
glib 2.88.1GUIlistamesonsamuraipython3pkgconflibffipcre2zlib
libdrm 2.4.134GUIlistamesonsamuraipython3pkgconf
libxkbcommon 1.7.0GUIlistamesonsamuraipython3pkgconfbison
pixman 0.44.2GUIlistamesonsamuraipython3pkgconf
seatd 0.9.3GUIlistamesonsamuraipython3pkgconf
tllist 1.1.0GUIlistamesonsamuraipython3pkgconf
wayland 1.25.0GUIlistamesonsamuraipython3pkgconflibffiexpat
linux-pam 1.6.1kernellistaflexpkgconfmake
monolith 2.10.1Cespera 1openssl
nu 0.104.1Cespera 1openssl
openssl 3.5.4Cespera 1perl
rink 0.9.0Cespera 1openssl
tmux 3.6bCespera 1libeventncursesbisonmake
gocryptfs 2.6.1Goespera 1goopensslpkgconf
gdk-pixbuf 2.44.6GUIespera 1mesonsamuraipython3pkgconfgliblibpnglibffipcre2zlib
graphene 1.10.8GUIespera 1mesonsamuraipython3pkgconfgliblibffipcre2zlib
harfbuzz 14.2.1GUIespera 1mesonsamuraipython3pkgconffreetypeglibpcre2libffizliblibpng
libepoxy 1.5.10GUIespera 1mesonsamuraipython3pkgconfmesa
wayland-protocols 1.48GUIespera 1mesonsamuraipython3pkgconfwayland
linux 6.16.12kernelespera 1flexbisonm4opensslelfutilsbusyboxmake
linux-generic 7.1.2kernelespera 1flexbisonm4opensslelfutilsmake
linux-metal 6.16.12kernelespera 1flexbisonm4opensslelfutilsmake
cargo-cache 0.8.3Rustespera 1opensslzlib
cargo-generate 0.23.9Rustespera 1openssl
cargo-outdated 0.19.0Rustespera 1openssl
cargo-udeps 0.1.61Rustespera 1openssl
ca-certificates 20260611Cespera 2opensslperl
curl 8.20.0Cespera 2opensslzlibperl
hurl 8.0.1Cespera 2curlopensslzliblibxml2
libssh2 1.11.1Cespera 2opensslzlibperl
libxmlb 0.3.22Cespera 2mesonsamuraipython3pkgconfglibzstdpcre2libffizlib
openssh 10.3p1Cespera 2zlibopensslperl
socat 1.8.1.2Cespera 2opensslreadlinencursesperl
usbutils 019Cespera 2libusblibudev-zerolinux-headerspkgconfmesonsamuraipython3
wget 1.25.0Cespera 2opensslzlibpcre2perl
git 2.54.0Cespera 3zlibopensslcurlpcre2linux-headersperl
gnupg 2.4.9Cespera 3libgpg-errorlibgcryptlibassuanlibksbanpthzliblinux-headerspkgconfmake
cairo 1.18.4GUIespera 3mesonsamuraipython3pkgconfpixmanfreetypefontconfiglibpngzlibglibexpatlibffipcre2
libinput 1.27.1GUIespera 3mesonsamuraipython3pkgconflibudev-zerolibevdevmtdev
fcft 3.3.3GUIespera 4tllistutf8procfreetypefontconfigharfbuzzpixmanlibpngzlibexpatmesonsamuraipython3pkgconflinux-headers
mesa 24.0.9GUIespera 4mesonsamuraipython3pkgconfflexbisonmakomarkupsafepackaginglibdrmwaylandwayland-protocolsexpatzlibzstdlibffi
mesa-swrast 24.0.9GUIespera 4mesonsamuraipython3pkgconfflexbisonmakomarkupsafepackaginglibdrmwaylandwayland-protocolsexpatzlibzstdlibffi
appstream 1.0.5GUIespera 5mesonsamuraipython3pkgconfgperfgettext-tinyitstoolgliblibxml2libyamlcurlopenssllibxmlbzstdpcre2libffizlib
pango 1.57.1GUIespera 6mesonsamuraipython3pkgconfcairoharfbuzzfribidiglibfreetypefontconfigpixmanlibpngexpatlibffipcre2zlib
mirada-greeter 0.0.1Rustespera 6mesawaylandwayland-protocolslibdrmexpatzlibzstdlibffipkgconflinux-pam
foot 1.27.0GUIespera 8fcfttllistutf8procfontconfigfreetypeharfbuzzlibxkbcommonncursespixmanwaylandwayland-protocolslibffilibpngzlibexpatmesonsamuraipython3pkgconflinux-headers
mirada-compositor 0.0.1GUIespera 13mesawaylandwayland-protocolslibdrmseatdlinux-pamlibinputlibudev-zerolibevdevmtdevlibxkbcommonpixmanexpatzlibzstdlibffipkgconf
gtk4 4.18.6GUIespera 15mesonsamuraipython3pkgconfglibcairopangogdk-pixbufgraphenelibepoxywaylandwayland-protocolslibxkbcommonlibdrmfontconfigharfbuzzfribidilibpngpixmanfreetypeexpatlibffipcre2zliblibjpeg-turbolibtiffmesa
gtk4-hello 4.18.6GUIespera 15pkgconfgtk4pangogdk-pixbufcairographeneglibharfbuzzfribidifontconfigfreetypepixmanlibpngexpatlibjpeg-turbolibtifflibepoxywaylandlibxkbcommonlibdrmpcre2libffizlibmesa
gtksourceview 5.16.0GUIespera 16mesonsamuraipython3pkgconfgettext-tinygtk4pangogdk-pixbufcairographeneglibharfbuzzfribidifontconfigfreetypepixmanlibpngexpatlibxml2libjpeg-turbolibtifflibepoxywaylandwayland-protocolslibxkbcommonlibdrmpcre2libffizlibmesa
adwaita-hello 1.7.6GUIespera 21pkgconflibadwaitaappstreamlibyamlcurlopenssllibxmlblibxml2zstdgtk4pangogdk-pixbufcairographeneglibharfbuzzfribidifontconfigfreetypepixmanlibpngexpatlibjpeg-turbolibtifflibepoxywaylandlibxkbcommonlibdrmpcre2libffizlibmesa
libadwaita 1.7.6GUIespera 21mesonsamuraipython3pkgconfsasscappstreamlibyamlcurlopenssllibxmlblibxml2zstdgtk4pangogdk-pixbufcairographeneglibharfbuzzfribidifontconfigfreetypepixmanlibpngexpatlibjpeg-turbolibtifflibepoxywaylandwayland-protocolslibxkbcommonlibdrmpcre2libffizlibmesa
hammer-edit 1.0.0Cespera 22pkgconfgtksourceviewlibadwaitaappstreamlibyamlcurlopenssllibxmlblibxml2zstdgtk4pangogdk-pixbufcairographeneglibharfbuzzfribidifontconfigfreetypepixmanlibpngexpatlibjpeg-turbolibtifflibepoxywaylandlibxkbcommonlibdrmpcre2libffizlibmesa
sourceview-hello 5.16.0GUIespera 22pkgconfgtksourceviewlibadwaitaappstreamlibyamlcurlopenssllibxmlblibxml2zstdgtk4pangogdk-pixbufcairographeneglibharfbuzzfribidifontconfigfreetypepixmanlibpngexpatlibjpeg-turbolibtifflibepoxywaylandlibxkbcommonlibdrmpcre2libffizlibmesa
+
+
+ +
+ Derivado de recipes/*.toml + hammer hash + el store, + por scripts/build-state.py. La fuente firme es + docs/state/build-state.json (versionada: su git diff es el avance). + Regenerar: scripts/build-state.py && scripts/build-state-view.py. +
+
+ diff --git a/scripts/build-state-view.py b/scripts/build-state-view.py new file mode 100755 index 00000000..c47609a2 --- /dev/null +++ b/scripts/build-state-view.py @@ -0,0 +1,311 @@ +#!/usr/bin/env python3 +# build-state-view.py — vista HUMANA del grafo de estado. Lee docs/state/build-state.json (la fuente +# firme que emite build-state.py) y emite docs/state/build-state.html: un dashboard self-contained +# (sin recursos externos) para VER y SEGUIR el avance, los huecos y los fallos. +# +# El dato clave que añade sobre el JSON crudo: para cada receta en deuda, computa quién la BLOQUEA +# —sus deps que también están en deuda— usando el grafo. Una receta en deuda con TODAS sus deps +# selladas está "lista para construir ya"; una con deps en deuda debe esperar. Ese es el uso +# accionable del orden topológico: qué atacar primero. +# +# Uso: scripts/build-state-view.py # regenera docs/state/build-state.html desde el JSON +import json, html +from pathlib import Path +from datetime import datetime, timezone + +ROOT = Path(__file__).resolve().parent.parent +DATA = ROOT / "docs/state/build-state.json" +OUT = ROOT / "docs/state/build-state.html" + +doc = json.loads(DATA.read_text()) +nodes = doc["nodes"] + +STATE_ORDER = ["sealed", "debt", "never", "unhashable"] +STATE_ES = {"sealed": "sellado al día", "debt": "deuda de rebuild", + "never": "nunca construida", "unhashable": "ilegible"} +CLASS_ES = {"c": "C", "go": "Go", "rust": "Rust", "gui": "GUI", "kernel": "kernel"} + +# Para cada nodo, sus deps que están en deuda (lo que lo bloquea) — el sub-grafo accionable. +def blockers(name): + r = nodes[name] + return [d for d in r.get("deps", []) if d in nodes and nodes[d]["state"] in ("debt", "never", "unhashable")] + +debt = {n: r for n, r in nodes.items() if r["state"] in ("debt", "never", "unhashable")} +# Orden: primero las "listas" (sin bloqueadores), luego por nº de bloqueadores, luego alfabético. +def debt_key(n): + b = blockers(n) + return (len(b), nodes[n]["cls"], n) +debt_sorted = sorted(debt, key=debt_key) +ready = [n for n in debt_sorted if not blockers(n)] + +totals = doc["totals"] +n_total = totals["recipes"] + +# Reparto por clase (para las barras por-clase). +classes = {} +for n, r in nodes.items(): + c = r.get("cls", "c") + classes.setdefault(c, {s: 0 for s in STATE_ORDER}) + classes[c][r["state"]] += 1 + +def pct(x): return f"{100*x/n_total:.1f}%" + +E = html.escape + +def chip(dep): + st = nodes[dep]["state"] if dep in nodes else "unhashable" + return f'{E(dep)}' + +# ---- barra de progreso apilada (sealed|debt|never|unhashable) ---- +seg = "" +for s in STATE_ORDER: + v = totals.get(s, 0) + if v: + seg += f'' + +# ---- tiles de estado ---- +tiles = "" +for s in STATE_ORDER: + v = totals.get(s, 0) + tiles += f'''
+
{v}
+
{E(STATE_ES[s])}
+
{pct(v)}
+
''' + +# ---- grid por clase ---- +cls_cards = "" +for c in sorted(classes, key=lambda c: -sum(classes[c].values())): + cc = classes[c]; tot = sum(cc.values()) + bar = "" + for s in STATE_ORDER: + if cc[s]: + bar += f'' + deuda = cc["debt"] + cc["never"] + cc["unhashable"] + cls_cards += f'''
+
{E(CLASS_ES.get(c, c))} + {tot}
+
{bar}
+
{cc['sealed']} al día + {deuda} en deuda
+
''' + +# ---- filas de deuda (agrupadas visualmente por "listas" vs "bloqueadas") ---- +def debt_row(n): + r = nodes[n]; b = blockers(n) + deps = r.get("deps", []) + chips = "".join(chip(d) for d in deps) if deps else 'sin deps de build' + badge = ('lista' if not b + else f'espera {len(b)}') + ver = f'{E(str(r.get("version") or ""))}' if r.get("version") else "" + return f''' + {E(n)} {ver} + {E(CLASS_ES.get(r['cls'], r['cls']))} + {badge} + {chips} + ''' + +rows = "".join(debt_row(n) for n in debt_sorted) +gen_time = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC") +topo = "cierra sin ciclos" if doc.get("topo_ok") else "⚠ CICLO detectado" +orphan_note = ("grafo cerrado" if not doc.get("orphan_deps") + else f"⚠ {len(doc['orphan_deps'])} deps huérfanas") + +CSS = """ +:root{ + --bg:#eef0f3; --panel:#ffffff; --panel-2:#f6f7f9; --ink:#1b1e24; --ink-2:#5b636f; + --line:#dce0e6; --steel:#4a7291; --ember:#dd6b3d; + --sealed:#3f8f61; --debt:#bf922f; --never:#8f6234; --unhashable:#bb443c; + --sealed-bg:#e7f1eb; --debt-bg:#f6efdc; --never-bg:#efe6da; --unhashable-bg:#f4e2e0; +} +@media (prefers-color-scheme:dark){:root{ + --bg:#12151a; --panel:#1a1e25; --panel-2:#20252e; --ink:#e7eaef; --ink-2:#98a1af; + --line:#2a303a; --steel:#7aa6c8; --ember:#e97d4f; + --sealed:#59b483; --debt:#d8b054; --never:#b98a54; --unhashable:#d76258; + --sealed-bg:#182a20; --debt-bg:#2a2617; --never-bg:#271f16; --unhashable-bg:#2c1a19; +}} +:root[data-theme="light"]{ + --bg:#eef0f3; --panel:#ffffff; --panel-2:#f6f7f9; --ink:#1b1e24; --ink-2:#5b636f; + --line:#dce0e6; --steel:#4a7291; --ember:#dd6b3d; + --sealed:#3f8f61; --debt:#bf922f; --never:#8f6234; --unhashable:#bb443c; + --sealed-bg:#e7f1eb; --debt-bg:#f6efdc; --never-bg:#efe6da; --unhashable-bg:#f4e2e0; +} +:root[data-theme="dark"]{ + --bg:#12151a; --panel:#1a1e25; --panel-2:#20252e; --ink:#e7eaef; --ink-2:#98a1af; + --line:#2a303a; --steel:#7aa6c8; --ember:#e97d4f; + --sealed:#59b483; --debt:#d8b054; --never:#b98a54; --unhashable:#d76258; + --sealed-bg:#182a20; --debt-bg:#2a2617; --never-bg:#271f16; --unhashable-bg:#2c1a19; +} +*{box-sizing:border-box} +body{margin:0;background:var(--bg);color:var(--ink); + font-family:system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;line-height:1.5; + -webkit-font-smoothing:antialiased} +.mono{font-family:ui-monospace,"SF Mono","JetBrains Mono","Cascadia Code",Menlo,monospace} +.wrap{max-width:1180px;margin:0 auto;padding:clamp(20px,4vw,48px)} +header{display:flex;flex-wrap:wrap;align-items:baseline;gap:12px 20px;margin-bottom:28px} +h1{font-size:clamp(1.5rem,3vw,2.1rem);margin:0;font-weight:680;letter-spacing:-.02em} +h1 .mark{color:var(--ember)} +.sub{color:var(--ink-2);font-size:.9rem} +.sub .mono{color:var(--ink)} +.eyebrow{text-transform:uppercase;letter-spacing:.12em;font-size:.7rem;font-weight:650; + color:var(--ink-2);margin:0 0 10px} +section{margin-bottom:34px} +/* progress */ +.progress{height:34px;display:flex;border-radius:8px;overflow:hidden;border:1px solid var(--line); + background:var(--panel)} +.seg{display:block;height:100%} +.s-sealed{background:var(--sealed)} .s-debt{background:var(--debt)} +.s-never{background:var(--never)} .s-unhashable{background:var(--unhashable)} +.legend{display:flex;flex-wrap:wrap;gap:16px;margin-top:12px;font-size:.82rem;color:var(--ink-2)} +.legend b{color:var(--ink);font-weight:600} +.legend .k{display:inline-block;width:10px;height:10px;border-radius:3px;margin-right:6px; + vertical-align:baseline} +/* tiles */ +.tiles{display:grid;grid-template-columns:repeat(4,1fr);gap:14px} +@media(max-width:640px){.tiles{grid-template-columns:repeat(2,1fr)}} +.tile{background:var(--panel);border:1px solid var(--line);border-radius:10px;padding:16px 18px; + border-top:3px solid var(--line)} +.tile.s-sealed{border-top-color:var(--sealed)} .tile.s-debt{border-top-color:var(--debt)} +.tile.s-never{border-top-color:var(--never)} .tile.s-unhashable{border-top-color:var(--unhashable)} +.tile-n{font-size:2rem;font-weight:680;font-family:ui-monospace,monospace;line-height:1; + font-variant-numeric:tabular-nums} +.tile-l{font-size:.82rem;color:var(--ink-2);margin-top:6px} +.tile-p{font-size:.78rem;color:var(--ink-2);margin-top:2px;font-family:ui-monospace,monospace} +/* class grid */ +.cls-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:14px} +.cls{background:var(--panel);border:1px solid var(--line);border-radius:10px;padding:15px 16px} +.cls-head{display:flex;justify-content:space-between;align-items:baseline;margin-bottom:10px} +.cls-name{font-weight:620} .cls-tot{font-family:ui-monospace,monospace;color:var(--ink-2); + font-variant-numeric:tabular-nums} +.bar{height:9px;display:flex;border-radius:5px;overflow:hidden;background:var(--panel-2)} +.cls-foot{display:flex;justify-content:space-between;margin-top:9px;font-size:.78rem} +.cls-foot .ok{color:var(--sealed)} .cls-foot .warn{color:var(--debt)} +.cls-foot .muted{color:var(--ink-2)} +/* debt table */ +.controls{display:flex;flex-wrap:wrap;gap:10px;align-items:center;margin-bottom:14px} +.controls input{background:var(--panel);border:1px solid var(--line);color:var(--ink); + padding:8px 12px;border-radius:8px;font-size:.9rem;min-width:200px;font-family:ui-monospace,monospace} +.controls input:focus{outline:2px solid var(--steel);outline-offset:1px} +.fbtn{background:var(--panel);border:1px solid var(--line);color:var(--ink-2);padding:7px 13px; + border-radius:999px;font-size:.8rem;cursor:pointer;font-weight:550} +.fbtn[aria-pressed="true"]{background:var(--steel);border-color:var(--steel);color:#fff} +.tbl-wrap{overflow-x:auto;border:1px solid var(--line);border-radius:10px;background:var(--panel)} +table{border-collapse:collapse;width:100%;font-size:.88rem} +th{text-align:left;font-size:.7rem;text-transform:uppercase;letter-spacing:.08em;color:var(--ink-2); + font-weight:600;padding:11px 14px;border-bottom:1px solid var(--line);background:var(--panel-2); + position:sticky;top:0} +td{padding:10px 14px;border-bottom:1px solid var(--line);vertical-align:top} +.drow:last-child td{border-bottom:none} +.c-name{font-family:ui-monospace,monospace;font-weight:550;white-space:nowrap} +.c-name .ver{color:var(--ink-2);font-weight:400;font-size:.82em;margin-left:5px} +.s-dot{display:inline-block;width:8px;height:8px;border-radius:50%;margin-right:8px; + vertical-align:middle} +.tag{font-size:.72rem;padding:2px 8px;border-radius:999px;font-weight:600;white-space:nowrap} +.t-c{background:var(--panel-2);color:var(--ink-2)} .t-go{background:#1f3a4d33;color:var(--steel)} +.t-rust{background:#dd6b3d22;color:var(--ember)} .t-gui{background:#8f623422;color:var(--never)} +.t-kernel{background:#bb443c22;color:var(--unhashable)} +.ready{color:var(--sealed);font-weight:600;font-size:.8rem} +.blocked{color:var(--debt);font-weight:600;font-size:.8rem;font-family:ui-monospace,monospace} +.c-deps{max-width:560px} +.chip{display:inline-block;font-family:ui-monospace,monospace;font-size:.74rem;padding:2px 7px; + border-radius:5px;margin:2px 3px 2px 0;border:1px solid transparent} +.chip.s-sealed{background:var(--sealed-bg);color:var(--sealed)} +.chip.s-debt{background:var(--debt-bg);color:var(--debt);border-color:var(--debt)} +.chip.s-never{background:var(--never-bg);color:var(--never);border-color:var(--never)} +.chip.s-unhashable{background:var(--unhashable-bg);color:var(--unhashable);border-color:var(--unhashable)} +.chip.empty{color:var(--ink-2);font-style:italic} +.hint{color:var(--ink-2);font-size:.82rem;margin:0 0 14px} +.count{color:var(--ink-2);font-size:.82rem;margin-left:auto;font-family:ui-monospace,monospace} +footer{color:var(--ink-2);font-size:.78rem;margin-top:36px;padding-top:16px; + border-top:1px solid var(--line);line-height:1.7} +footer .mono{color:var(--ink)} +@media(prefers-reduced-motion:no-preference){.seg,.fbtn{transition:.15s}} +""" + +JS = """ +const rows=[...document.querySelectorAll('.drow')]; +const q=document.getElementById('q'); +let cls='all',onlyReady=false; +function apply(){ + const t=(q.value||'').toLowerCase();let shown=0; + for(const r of rows){ + const okC=cls==='all'||r.dataset.cls===cls; + const okR=!onlyReady||r.dataset.ready==='1'; + const okT=!t||r.dataset.name.includes(t); + const v=okC&&okR&&okT;r.style.display=v?'':'none';if(v)shown++; + } + document.getElementById('count').textContent=shown+' en deuda'; +} +document.querySelectorAll('.fbtn[data-cls]').forEach(b=>b.onclick=()=>{ + document.querySelectorAll('.fbtn[data-cls]').forEach(x=>x.setAttribute('aria-pressed','false')); + b.setAttribute('aria-pressed','true');cls=b.dataset.cls;apply();}); +const rb=document.getElementById('ready'); +rb.onclick=()=>{onlyReady=!onlyReady;rb.setAttribute('aria-pressed',onlyReady);apply();}; +q.oninput=apply; +""" + +cls_btns = '' +for c in sorted(classes, key=lambda c: -sum(classes[c].values())): + deuda = classes[c]["debt"] + classes[c]["never"] + classes[c]["unhashable"] + if deuda: + cls_btns += f'' + +HTML = f''' +
+
+

estado del build · hammer

+
{n_total} recetas · grafo {E(topo)} · {E(orphan_note)} · {E(gen_time)}
+
+ +
+

avance del corpus

+
{seg}
+
+ {totals.get('sealed',0)} sellado al día + {totals.get('debt',0)} deuda de rebuild + {totals.get('never',0)} nunca construida + {''+str(totals.get('unhashable',0))+' ilegible' if totals.get('unhashable') else ''} +
+
+ +
+
{tiles}
+
+ +
+

dónde está la deuda

+
{cls_cards}
+
+ +
+

huecos, en orden de ataque

+

Cada receta en deuda con sus deps de build coloreadas por estado. Una fila + lista tiene todas sus deps al día ⇒ se puede construir ya; + una espera N depende de N recetas que también están en deuda. + {len(ready)} listas ahora.

+
+ {cls_btns} + + + {len(debt_sorted)} en deuda +
+
+ + + {rows} +
recetaclaseestadodeps de build
+
+
+ +
+ Derivado de recipes/*.toml + hammer hash + el store, + por scripts/build-state.py. La fuente firme es + docs/state/build-state.json (versionada: su git diff es el avance). + Regenerar: scripts/build-state.py && scripts/build-state-view.py. +
+
+ +''' + +OUT.write_text(HTML) +print(f"→ {OUT.relative_to(ROOT)} ({len(HTML)//1024} KB) · {len(debt_sorted)} en deuda, {len(ready)} listas")