Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/facett

py/facett_demo/panes/errcode_pane.py

"""ERROR-CODES pane — the Python/Streamlit mirror of facett-demo's ``Errors`` tab.

LAW #2: a facett component that lands in facett-demo lands HERE too, in lockstep.
This pane is the Streamlit counterpart of the native error-code showcase:

  * the ``facet-<component>-<n>`` catalog (component → code → message → source
    source → status → the Phase-2 consumer REACTION),
  * the Phase-2 **reaction matrix** — every ``(code × mount)`` pair the consumer
    robot tests assert,
  * the triggered example errors rendered with their code in **CLEAR PINK**, exactly
    like the native demo's debug build.

ONE SOURCE OF TRUTH: the catalog is NOT re-authored here. It is read from
``errcodes.json``, generated from the compiled Rust ``facett_core::errcode::REGISTRY``
(``cargo run -p facett-core --bin errcode_registry_json``), so the two demos can never
disagree about which codes exist or how a consumer must react.

Both matrices are mounted as the REAL facett ``table`` component (via
``facet-wrapped``), not drawn with ``st.dataframe``/pandas — the same rule the map,
graph and population panes follow. A demo whose *error* tab is a Streamlit lookalike
contradicts the very tab it is showing.
"""

from __future__ import annotations

import json
import os
from functools import lru_cache
from typing import Any

import streamlit as st

from ..facet_host import facett_component, is_data_driven

#: The clear pink the native demo paints an error CODE in (facett_core::look::PINK).
PINK = "#FF40B4"

#: The warm red facett paints a failure MESSAGE in (facett_core::look::RED).
RED = "#E05A5A"

_REGISTRY_PATH = os.path.join(os.path.dirname(os.path.dirname(__file__)), "errcodes.json")

#: The facett component both matrices are mounted as (registry name in `facet-wrapped`).
TABLE_COMPONENT = "table"


@lru_cache(maxsize=1)
def registry() -> dict[str, Any]:
    """The generated facett error-code catalog (cached)."""
    with open(_REGISTRY_PATH, encoding="utf-8") as fh:
        return json.load(fh)


def code_chip(code: str) -> str:
    """A CLEAR-PINK code chip — the same diagnostic stripe the native demo paints."""
    return (
        f'<code style="background:{PINK};color:#fff;padding:1px 6px;'
        f'border-radius:4px;font-weight:600">{code}</code>'
    )


#: (column header, how to read it off one registry row) for the error-code matrix.
_CODE_COLUMNS: list[tuple[str, Any]] = [
    ("component", lambda c: c["component"]),
    ("code", lambda c: c["code"]),
    ("message", lambda c: c["message"]),
    ("source", lambda c: f'{c["crate"]}/{c["src_file"]}'),
    ("status", lambda c: c["status"]),
    ("reaction (Phase 2)", lambda c: c["reaction"]),
    ("who reacts", lambda c: c["consumer"] or "— (none today)"),
]

#: The same, for the Phase-2 (code × mount) reaction matrix.
_MATRIX_COLUMNS: list[tuple[str, Any]] = [
    ("mounted code", lambda r: r["mounted_code"]),
    ("base code", lambda r: r["code"]),
    ("mount", lambda r: r["mount"]),
    ("expected reaction", lambda r: r["reaction"]),
]


def _payload(title: str, columns: list[tuple[str, Any]], records: list[dict[str, Any]]) -> dict[str, Any]:
    """The facett `table` payload contract: {title, columns, rows} of strings."""
    return {
        "title": title,
        "columns": [name for name, _ in columns],
        "rows": [[str(get(rec)) for _, get in columns] for rec in records],
    }


def _mount_table(title: str, columns: list[tuple[str, Any]], records: list[dict[str, Any]],
                 *, key: str, height: int) -> None:
    """Mount the REAL facett table, or refuse — never fall back to a Python renderer."""
    if not records:
        st.info(f"`{title}` has no rows (honest empty).")
        return
    if not is_data_driven(TABLE_COMPONENT):
        st.error(
            f"`{TABLE_COMPONENT}` is not available as a data-driven facett component — the "
            "`facet-wrapped` wasm bundle is missing or stale. Rebuild it with "
            "`cargo xtask wrap-wasm`. Refusing to render this matrix in Python."
        )
        return
    facett_component(TABLE_COMPONENT, data=_payload(title, columns, records), height=height, key=key)


def render(client=None) -> None:  # noqa: ARG001 — signature parity with the other panes
    """Render the error-code showcase. Takes no live RPC — the catalog is static."""
    reg = registry()

    st.subheader("facett UI error codes")
    st.caption(
        "Every facett UI part carries a STABLE, UNIQUE code `facet-<component>-<n>` so "
        "tests + consumers react to the CODE, not a matched string."
    )

    c1, c2, c3, c4 = st.columns(4)
    c1.metric("components", reg["component_count"])
    c2.metric("codes", reg["code_count"])
    c3.metric("wired", reg["wired_count"])
    c4.metric("reaction pairs", reg["reaction_pairs"])

    # ── Triggered examples: message + CLEAR-PINK code (mirrors the native tab) ──
    st.markdown("**Triggered examples (message + pink code):**")
    for c in reg["codes"]:
        if c["component"] != "demo":
            continue
        st.markdown(
            f'<span style="color:{RED}">{c["message"]}</span> &nbsp; {code_chip(c["code"])}',
            unsafe_allow_html=True,
        )

    # ── Consumer mount-prefix (Phase 2) ──
    st.markdown("**Consumer mount-prefix (Phase 2 — same facet, two mount points):**")
    st.code("korp/cases:facet-map-2\nkorp/analysis:facet-map-2", language=None)

    st.divider()

    # ── The error-code MATRIX slide ──
    st.markdown(
        f"**Error-code MATRIX — {reg['code_count']} codes across "
        f"{reg['component_count']} components:**"
    )
    _mount_table("Error codes", _CODE_COLUMNS, reg["codes"], key="err_codes", height=460)

    # ── The Phase-2 REACTION matrix slide ──
    st.markdown(
        f"**Reaction MATRIX (Phase 2) — {reg['reaction_pairs']} (code × mount) pairs "
        "the consumer tests assert:**"
    )
    _mount_table("Reaction matrix", _MATRIX_COLUMNS, reg["reaction_matrix"],
                 key="err_matrix", height=460)

    # ── The reaction vocabulary ──
    with st.expander("Reaction vocabulary — what a consumer must DO"):
        for r in reg["reactions"]:
            st.markdown(f"- `{r['token']}` — {r['describes']}")

    # ── Source per facet-component ──
    with st.expander("Source per facet-component"):
        seen: set[str] = set()
        for c in reg["codes"]:
            if c["component"] in seen:
                continue
            seen.add(c["component"])
            st.markdown(f"- `facet-{c['component']}` → [{c['crate']}/{c['src_file']}]({c['source_url']})")