diff --git a/.github/workflows/agents-guard.yml b/.github/workflows/agents-guard.yml index 9367516d7..395f3482b 100644 --- a/.github/workflows/agents-guard.yml +++ b/.github/workflows/agents-guard.yml @@ -100,7 +100,7 @@ jobs: github.event_name == 'pull_request_target' && steps.eligibility.outputs.should-run == 'true' && steps.api_client_base.outputs.available != 'true' - uses: "stranske/Workflows/.github/actions/setup-api-client@44965d8d784573c119fb63828c05c89256c5f3e1" + uses: "stranske/Workflows/.github/actions/setup-api-client@a335f1af2c35b8f35d2278f56e9af78792a09bf1" with: secrets: ${{ toJSON(secrets) }} github_token: ${{ github.token }} @@ -170,7 +170,7 @@ jobs: steps.eligibility.outputs.should-run == 'true' && github.event_name == 'pull_request' && steps.api_client_head.outputs.available != 'true' - uses: "stranske/Workflows/.github/actions/setup-api-client@44965d8d784573c119fb63828c05c89256c5f3e1" + uses: "stranske/Workflows/.github/actions/setup-api-client@a335f1af2c35b8f35d2278f56e9af78792a09bf1" with: secrets: ${{ toJSON(secrets) }} github_token: ${{ github.token }} diff --git a/config/template-drift-allowlist.txt b/config/template-drift-allowlist.txt index 43795de6f..a1a4b16d5 100644 --- a/config/template-drift-allowlist.txt +++ b/config/template-drift-allowlist.txt @@ -81,9 +81,9 @@ reason = Intentional divergence (re-baselined 2026-06-14): consumer template SHA [pair.10] main = .github/workflows/agents-guard.yml template = templates/consumer-repo/.github/workflows/agents-guard.yml -main_sha256 = 96704f934339d1bf1ac455512f62b5cf280926891437821410090ec74d03b426 -template_sha256 = ff6f4a50f764c3e9d39a0de1601e066c975119193758768a2aae1f5c9332710b -reason = Intentional divergence re-baselined 2026-06-23: root and consumer guard workflows differ for pinned consumer actions/App-token setup; root and consumer setup-api-client pins were refreshed to the current Workflows main digest 44965d8. +main_sha256 = 5206c4d37688d2b92017ed9c782cd4b5171f7df4bd661f816f2d21046f1daa50 +template_sha256 = 90fa9141f6739767eb9b48303a9aa61b09646964e7837d2e499deae9eb815c98 +reason = Intentional divergence re-baselined 2026-06-23: root and consumer guard workflows differ for pinned consumer actions/App-token setup; root and consumer setup-api-client pins were refreshed to the current Workflows main digest a335f1a. [pair.11] main = .github/workflows/agents-issue-optimizer.yml diff --git a/templates/consumer-repo/.github/workflows/agents-guard.yml b/templates/consumer-repo/.github/workflows/agents-guard.yml index 8d1e67406..81ac75cc1 100644 --- a/templates/consumer-repo/.github/workflows/agents-guard.yml +++ b/templates/consumer-repo/.github/workflows/agents-guard.yml @@ -111,7 +111,7 @@ jobs: github.event_name == 'pull_request_target' && steps.eligibility.outputs.should-run == 'true' && steps.api_client_base.outputs.available != 'true' - uses: "stranske/Workflows/.github/actions/setup-api-client@44965d8d784573c119fb63828c05c89256c5f3e1" # v1 + uses: "stranske/Workflows/.github/actions/setup-api-client@a335f1af2c35b8f35d2278f56e9af78792a09bf1" # v1 with: secrets: ${{ toJSON(secrets) }} github_token: ${{ github.token }} @@ -180,7 +180,7 @@ jobs: steps.eligibility.outputs.should-run == 'true' && github.event_name == 'pull_request' && steps.api_client_head.outputs.available != 'true' - uses: "stranske/Workflows/.github/actions/setup-api-client@44965d8d784573c119fb63828c05c89256c5f3e1" # v1 + uses: "stranske/Workflows/.github/actions/setup-api-client@a335f1af2c35b8f35d2278f56e9af78792a09bf1" # v1 with: secrets: ${{ toJSON(secrets) }} github_token: ${{ github.token }} diff --git a/templates/consumer-repo/design-system/PRESENTATION_PATTERNS.md b/templates/consumer-repo/design-system/PRESENTATION_PATTERNS.md index 87ad74108..521f79a5f 100644 --- a/templates/consumer-repo/design-system/PRESENTATION_PATTERNS.md +++ b/templates/consumer-repo/design-system/PRESENTATION_PATTERNS.md @@ -52,7 +52,7 @@ content. Use logging, or at most a collapsed "Diagnostics" expander. **Rule:** a tab/control that isn't applicable in the current mode states so up front (a badge/label), rather than opening into a silent empty/disabled surface. - **web:** `.ds .badge` on the tab/control (e.g. "multi-period only", "needs setup"). -- **Streamlit:** `ds_streamlit.availability_badge(label)` in the tab title / disabled control caption. +- **Streamlit:** `ds_streamlit.availability_badge(label)` in the tab title / disabled control caption; use `plain=False` only in trusted HTML containers. - **Fixes:** TMP #5629 (4/6 Results tabs empty — fixed by labelling, the canonical example), PA #2026 (upload-only pages with no sample path → mark/offer the sample). @@ -81,6 +81,7 @@ rather than opening into a silent empty/disabled surface. | Default dark theme (TMP/PA/MD/IMI) | P1 | ## Streamlit design kit (most of the fleet is Streamlit) + The CSS components above cover the web apps (Pension-Data, trip-planner, LMS). The four Streamlit Tier-A apps need a Streamlit-native equivalent — ship a shared `ds_streamlit.py` alongside the CSS: - `inject_theme()` — applies the `theme-air` palette (P1); pairs with `.streamlit/config.toml`. diff --git a/templates/consumer-repo/design-system/README.md b/templates/consumer-repo/design-system/README.md index 29a86d59f..7f8ee7c57 100644 --- a/templates/consumer-repo/design-system/README.md +++ b/templates/consumer-repo/design-system/README.md @@ -47,4 +47,4 @@ That's the "default with per-app customization" model: the base is canonical; an ## Status & next step -This kit is managed from `Workflows/templates/consumer-repo/design-system/` and distributed by Maint 68 through `.github/sync-manifest.yml`. Update it here first, then let the sync workflow replace consumer copies. +This kit is managed from `Workflows/templates/consumer-repo/design-system/` and distributed by the Maint 68 Sync Consumer Repos GitHub Actions workflow through `.github/sync-manifest.yml`. Update it here first, then let the sync workflow replace consumer copies. diff --git a/templates/consumer-repo/design-system/ds_streamlit.py b/templates/consumer-repo/design-system/ds_streamlit.py index 60a6df2a7..6d3e9bd29 100644 --- a/templates/consumer-repo/design-system/ds_streamlit.py +++ b/templates/consumer-repo/design-system/ds_streamlit.py @@ -108,7 +108,7 @@ def empty_state( def notice(kind: str, title: str = "", body: str = "", action: str | None = None) -> None: """P3/P4 — the one container for user-facing messages. kind in - {error,warn,info,ok}. `action` is optional remediation (markdown).""" + {error,warn,info,ok}. `action` is optional literal remediation text.""" import streamlit as st color, bg, ic = _NOTICE_STYLE.get(kind, _NOTICE_STYLE["info"]) @@ -171,16 +171,41 @@ def diagnostics_expander(label: str = "Diagnostics", *, expanded: bool = False): yield -def availability_badge(label: str) -> str: - """P5 — plain Streamlit-safe availability marker for tab titles/captions, - e.g. tab label f"Export {availability_badge('multi-period only')}".""" - return f" · {str(label).strip()}" +def availability_badge(label: str, *, plain: bool = True) -> str: + """P5 — availability marker for tabs/captions. + + The default is safe for Streamlit surfaces that render labels as literal + text. Use `plain=False` only inside containers rendered as trusted HTML. + """ + text = str(label).strip() + if plain: + return f" · {text}" + return f"{escape(text)}" def humanize_id(raw: str, mapping: Mapping[str, str] | None = None) -> str: """P6 — decode an internal id to a human label; never show raw keys.""" if mapping and raw in mapping: return mapping[raw] - # Best-effort: take a trailing human-ish segment, strip hashes. - tail = str(raw).replace("_", " ").split(":")[-1].strip() - return tail or "item" + # Best-effort: prefer the most specific non-opaque namespace segment. + segments = str(raw).replace("/", ":").split(":") + for segment in reversed(segments): + label = _human_label_segment(segment) + if label: + return label + return "item" + + +def _human_label_segment(segment: str) -> str: + words = [part for part in segment.replace("-", "_").split("_") if part] + meaningful = [] + for word in words: + lowered = word.lower() + if lowered.isdigit(): + continue + if len(lowered) >= 8 and all(ch in "0123456789abcdef" for ch in lowered): + continue + meaningful.append(word) + if not meaningful: + return "" + return " ".join(meaningful).strip()