-
Notifications
You must be signed in to change notification settings - Fork 1
chore: sync workflow templates #5645
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,99 @@ | ||
| # Presentation Patterns — the fleet design-system rollout standard | ||
|
|
||
| The cross-repo UX-Review baseline (2026-06-22, 8 apps) found that **every app's engine works; the | ||
| failures are presentation + deployment.** Deployment is a separate program. This doc covers the | ||
| **presentation** class: it collapses the ~12 recurring presentation findings into **6 reusable | ||
| patterns**, each with a rule, a design-system component (`components.css` / `tokens.css`), a | ||
| per-app-type application (Streamlit / web-SPA / static-PWA), and the fleet findings it closes. | ||
|
|
||
| **Principle:** apps should consume a *pattern*, not invent a bespoke fix. One pattern, applied | ||
| everywhere, beats N one-off PRs — and it's why this is a design-system rollout, not a bug list. | ||
|
|
||
| --- | ||
|
|
||
| ## The 6 patterns | ||
|
|
||
| ### P1 — Light, understated theme by default | ||
| **Rule:** never ship a default dark theme; default to `theme-air` (Ink & Air). Dark is opt-in only. | ||
| - **web:** load `tokens.css` + `components.css`; root element `class="ds theme-air"` (`theme-paper` for friendlier apps). | ||
| - **Streamlit:** `.streamlit/config.toml` `[theme] base="light"` + the shared `ds_streamlit.inject_theme()` (maps the `theme-air` tokens to Streamlit's theme). *Already prototyped for TMP.* | ||
| - **Fixes:** default-dark on Trend_Model_Project / Portable-Alpha / Manager-Database / Inv-Man-Intake. | ||
|
|
||
| ### P2 — Empty state = title + reason + next action | ||
| **Rule:** a "no data yet" surface ALWAYS shows a title, a one-line reason, and a **next-action CTA**. | ||
| NEVER a bare prompt sitting above already-rendered content, and NEVER an internal filename/path. | ||
| - **web:** `.ds .empty-state` (`.es-icon/.es-title/.es-desc/.es-cta`). | ||
| - **Streamlit:** `ds_streamlit.empty_state(title, desc, cta_label, on_click)`. | ||
| - **Fixes:** TMP #5628 (Run-analysis CTA above results), PA #2021 (Results "Outputs.xlsx not found"), | ||
| Manager-Database #1214 (empty default date / "Recent Activity"), LMS #351 (root `/` JSON 404). | ||
|
|
||
| ### P3 — Errors are human messages + remediation | ||
| **Rule:** NEVER surface a raw exception, pydantic/validation dump, internal field name, or stack to a | ||
| user. Translate at the boundary to a plain message + a recovery action. | ||
| - **web:** `.ds .notice--error` (`.ic` + `.body strong` headline + `.body .act` remediation). | ||
| - **Streamlit:** `ds_streamlit.error(message, remediation)` backed by `translate_error(exc)` (maps | ||
| known fields, e.g. `financing_mode` → "Financing mode is required"). | ||
| - **Fixes:** PA #2021 (raw `ModelConfig financing_mode` / margin pydantic errors), Inv-Man-Intake (item_id), | ||
| the broad "raw error" class across the fleet. | ||
|
|
||
| ### P4 — Dev/diagnostic notices go to logs, not the UI | ||
| **Rule:** auth-bypass / trace-sink / observability / "dev mode" notices NEVER render in the main | ||
| content. Use logging, or at most a collapsed "Diagnostics" expander. | ||
| - **web:** n/a in the main flow; use a `<details>`/debug panel. | ||
| - **Streamlit:** `ds_streamlit.dev_note(msg)` → `logging` (not `st.warning`/`st.write`); diagnostics behind `st.expander("Diagnostics")`. | ||
| - **Fixes:** Manager-Database #1215 (auth-bypass `st.warning`), Inv-Man-Intake #630 (trace-sink banner). | ||
|
|
||
| ### P5 — Feature-availability markers (no silent dead-ends) | ||
| **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. | ||
| - **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). | ||
|
|
||
| ### P6 — No raw internal identifiers in user surfaces | ||
| **Rule:** decode internal IDs / fixture filenames / record keys into human-readable labels before display. | ||
| - **web/Streamlit:** a display-name mapping; keep the raw id as secondary/`title=` metadata if useful. | ||
| - **Fixes:** Inv-Man-Intake #629 (opaque `item_id`), #630 (raw fixture filename in selector), PA Results (`Outputs.xlsx`). | ||
|
|
||
| --- | ||
|
|
||
| ## Finding → pattern map (presentation class) | ||
|
|
||
| | Finding | Pattern(s) | | ||
| |---|---| | ||
| | TMP #5628 empty-state CTA above results | P2 | | ||
| | TMP #5629 unmarked empty tabs *(fixed)* | P5 | | ||
| | Manager-Database #1214 empty default views | P2 | | ||
| | Manager-Database #1215 auth notice in UI | P4 | | ||
| | Inv-Man-Intake #629 non-actionable raw-JSON queue | P6 (+ a real action, app-specific) | | ||
| | Inv-Man-Intake #630 trace-sink + raw filenames | P4, P6 | | ||
| | Portable-Alpha #2021 raw errors / empty states | P2, P3 | | ||
| | Portable-Alpha #2026 upload-only, no sample | P5 | | ||
| | Pension-Data #594 *(deployment program — offline)* | — | | ||
| | LMS #351 root JSON 404 / empty surfaces | P2 (+ deployment) | | ||
| | 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`. | ||
| - `empty_state(title, desc, cta_label=None, on_click=None)` (P2) | ||
| - `notice(kind, title, body, action=None)` and `error(message, remediation=None)` + `translate_error(exc)` (P3) | ||
| - `dev_note(msg)` → logging; `diagnostics_expander()` (P4) | ||
| - `availability_badge(label)` (P5) | ||
| - `humanize_id(raw, mapping)` (P6) | ||
| Graduate this kit + the CSS into `Workflows/templates/consumer-repo/design-system/` so maint-68 syncs it fleet-wide. | ||
|
|
||
| ## Rollout sequence | ||
| 1. **Graduate** `tokens.css` + `components.css` (with the new patterns) + `ds_streamlit.py` into the Workflows consumer-repo design-system; let the existing sync (maint-68) distribute it. | ||
| 2. **Apply per app, highest-ROI first** — close each open finding by adopting its pattern (not a bespoke fix), one small PR per app with the pattern's named test gate: | ||
| - TMP #5628 → P2 (closest to a clean pass) | ||
| - Manager-Database #1214 → P2, #1215 → P4 | ||
| - Inv-Man-Intake #630 → P4/P6 (#629 also needs a real queue action) | ||
| - Portable-Alpha #2021 → P2/P3, #2026 → P5 | ||
| - Pension-Data / LMS web surfaces → P2/P3 via the CSS components | ||
| 3. **Theme pass (P1)** across the Streamlit apps once the kit is synced. | ||
|
|
||
| _Authored 2026-06-22 from the UX-Review fleet baseline. Components live in `components.css`/`tokens.css` | ||
| (this dir); see each repo's `docs/ux-review/REVIEW_LOG.md` for its findings + scores._ | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| # Shared Design System (staging) | ||
|
|
||
| Two themes on one token foundation, plus an orthogonal density axis. | ||
|
|
||
| - **`theme-air`** (Ink & Air) — default; work / outward-facing tools. | ||
| - **`theme-paper`** (Warm Paper) — friendlier apps (Reader, LMS). | ||
| - **`density-compact`** — tightens spacing for data-dense screens (e.g. Trend tables) **without** changing the theme. | ||
|
|
||
| ## Use | ||
|
|
||
| ```html | ||
| <link rel="stylesheet" href="tokens.css"> | ||
| <link rel="stylesheet" href="components.css"> | ||
|
|
||
| <body class="theme-air"> <!-- or theme-paper; add density-compact for dense screens --> | ||
| <div class="ds"> | ||
| ...components (.panel, .kpi, .appbar, table, .btns, .callout, ...)... | ||
| </div> | ||
| </body> | ||
| ``` | ||
|
|
||
| - Components are scoped under `.ds` so they never leak into a host app's styles. | ||
| - `components.css` is theme-agnostic — it only reads tokens. Don't fork it. | ||
|
|
||
| ## Per-app customization | ||
|
|
||
| Override any token in an app stylesheet loaded **after** `tokens.css`: | ||
|
|
||
| ```css | ||
| .theme-air { --accent: #0f6f6a; } /* this app wants a teal accent */ | ||
| ``` | ||
|
|
||
| That's the "default with per-app customization" model: the base is canonical; an app changes only the tokens it needs. | ||
|
|
||
| ## Files | ||
|
|
||
| | File | Role | | ||
| |---|---| | ||
| | `tokens.css` | Variables — themes + density. **Source of truth for the look.** | | ||
| | `components.css` | Component styles, token-driven and theme-agnostic. | | ||
| | `index.html` | Link-based reference page. Open locally, or import into Claude Design. | | ||
| | `preview.html` | Generated self-contained snapshot for sharing (regenerate with `build_preview.py`). | | ||
|
|
||
| ## Theme → app mapping (proposed) | ||
|
|
||
| - **Ink & Air:** Trend_Model_Project, Counter_Risk, Manager-Database, Inv-Man-Intake, Pension-Data, Portable-Alpha-Extension-Model, trip-planner, Travel-Plan-Permission. Use `density-compact` on Trend/Counter_Risk data-dense screens. | ||
| - **Warm Paper:** Reader, learning-management-system. | ||
|
|
||
| ## Status & next step | ||
|
|
||
| **STAGED** here in the tracker. Graduates to `Workflows/templates/consumer-repo/design-system/` with a `sync-manifest.yml` entry (`is_directory: true`; base tokens `template_sync: exact`) as a deliberate Workflows PR — see [`../PLAN.md`](../PLAN.md) §3.3. Claude Design imports from the Workflows path once graduated, enabling the prototype-in-Design → land-in-repo round-trip. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,95 @@ | ||
| /* components.css — component layer. Token-driven and theme-AGNOSTIC. | ||
| * Requires tokens.css + a theme class (.theme-air | .theme-paper) on an ancestor. | ||
| * Everything is scoped under .ds so it never leaks into a host app's own styles. | ||
| */ | ||
| .ds { color:var(--text); font-family:var(--font-body); font-size:var(--fs-base); line-height:1.5; background:var(--bg); } | ||
| .ds *, .ds *::before, .ds *::after { box-sizing:border-box; } | ||
| .ds h3 { font-family:var(--font-heading); font-weight:var(--heading-weight); margin:0 0 var(--space-3); font-size:15px; letter-spacing:-.005em; } | ||
| .ds .sub { color:var(--muted); font-size:var(--fs-sm); margin:calc(-1 * var(--space-2)) 0 var(--space-3); } | ||
|
|
||
| .ds .panel { background:var(--surface); border:var(--card-border); border-radius:var(--radius); box-shadow:var(--shadow); padding:var(--card-pad); margin-bottom:var(--space-4); } | ||
|
|
||
| .ds .appbar { display:flex; align-items:center; gap:var(--space-4); padding:var(--control-pad-y) var(--card-pad); background:var(--surface); border:var(--card-border); border-radius:var(--radius); box-shadow:var(--shadow); margin-bottom:var(--space-4); } | ||
| .ds .appbar .brand { font-family:var(--font-heading); font-weight:700; font-size:15px; } | ||
| .ds .appbar nav { display:flex; gap:var(--space-1); flex-wrap:wrap; } | ||
| .ds .appbar nav a { font-size:var(--fs-sm); text-decoration:none; color:var(--muted); padding:5px 10px; border-radius:var(--radius-sm); cursor:pointer; } | ||
| .ds .appbar nav a.active { color:var(--accent); background:var(--accent-weak); font-weight:600; } | ||
| .ds .appbar .spacer { flex:1; } | ||
|
|
||
| .ds .kpis { display:flex; flex-wrap:wrap; gap:var(--space-3); margin-bottom:var(--space-4); } | ||
| .ds .kpi { flex:1; min-width:150px; background:var(--surface); border:var(--card-border); border-radius:var(--radius); box-shadow:var(--shadow); padding:var(--space-3) var(--space-4); } | ||
| .ds .kpi .label { font-size:var(--fs-sm); color:var(--muted); } | ||
| .ds .kpi .val { font-size:var(--kpi-size); font-weight:700; letter-spacing:-.02em; margin-top:4px; font-family:var(--font-heading); } | ||
| .ds .kpi .delta { font-size:var(--fs-sm); font-weight:600; margin-top:2px; } | ||
| .ds .up { color:var(--pos); } .ds .down { color:var(--neg); } | ||
|
|
||
| .ds .grid2 { display:grid; grid-template-columns:1.3fr 1fr; gap:var(--space-4); } | ||
| @media (max-width:720px){ .ds .grid2 { grid-template-columns:1fr; } } | ||
|
|
||
| .ds table { width:100%; border-collapse:collapse; font-size:var(--fs-sm); } | ||
| .ds thead th { text-align:left; color:var(--muted); font-weight:600; font-size:var(--fs-xs); text-transform:uppercase; letter-spacing:.04em; padding:var(--row-pad-y) var(--row-pad-x); border-bottom:1px solid var(--border); } | ||
| .ds tbody td { padding:var(--row-pad-y) var(--row-pad-x); border-bottom:1px solid var(--row-border); } | ||
| .ds tbody tr:nth-child(even) { background:var(--zebra); } | ||
| .ds td.num, .ds th.num { text-align:right; font-variant-numeric:tabular-nums; } | ||
|
|
||
| .ds .field { margin-bottom:var(--space-3); } | ||
| .ds label { display:block; font-size:var(--fs-sm); font-weight:600; margin-bottom:5px; } | ||
| .ds label .help { font-weight:400; color:var(--muted); } | ||
| .ds .q { display:inline-flex; width:15px; height:15px; border-radius:50%; border:1px solid var(--border); color:var(--muted); font-size:10px; align-items:center; justify-content:center; vertical-align:1px; margin-left:5px; cursor:help; } | ||
| .ds input[type=text], .ds select { width:100%; font:inherit; font-size:var(--fs-base); padding:var(--control-pad-y) var(--control-pad-x); color:var(--text); background:var(--input-bg); border:1px solid var(--border); border-radius:var(--input-radius); } | ||
| .ds input[type=range] { width:100%; accent-color:var(--accent); } | ||
|
|
||
| .ds .btns { display:flex; flex-wrap:wrap; gap:var(--space-2); margin-top:var(--space-1); } | ||
| .ds button { font:inherit; font-size:var(--fs-sm); font-weight:600; padding:var(--control-pad-y) 15px; border-radius:var(--btn-radius); cursor:pointer; border:1px solid transparent; } | ||
| .ds .b-primary { background:var(--btn-primary-bg); color:var(--btn-primary-contrast); border-color:var(--btn-primary-bg); } | ||
| .ds .b-secondary { background:var(--surface); color:var(--text); border:1px solid var(--border); } | ||
| .ds .b-ghost { background:transparent; color:var(--accent); } | ||
| .ds .b-danger { background:transparent; color:var(--neg); border:1px solid var(--neg-border); } | ||
| .ds button[disabled] { opacity:.45; cursor:not-allowed; } | ||
|
|
||
| .ds .callout { display:flex; gap:10px; padding:11px 13px; border-radius:var(--radius); background:var(--accent-weak); border:1px solid var(--accent-line); font-size:var(--fs-sm); } | ||
| .ds .callout .ic { color:var(--accent); font-weight:700; } | ||
| .ds .empty { text-align:center; color:var(--muted); border:1px dashed var(--border); border-radius:var(--radius); padding:20px; font-size:var(--fs-sm); } | ||
| .ds .chart { color:var(--accent); display:block; } | ||
|
|
||
| /* ============================================================================ | ||
| * PRESENTATION-STATE PATTERNS (added 2026-06-22 from the cross-fleet UX-Review | ||
| * baseline). These standardize the recurring failure classes: empty/dead-end | ||
| * views, raw errors, dev/diagnostic leaks, and unavailable features. | ||
| * See PRESENTATION_PATTERNS.md for the rule behind each + per-app-type usage. | ||
| * ========================================================================== */ | ||
|
|
||
| /* notice — the ONE container for user-facing messages. Never render a raw | ||
| * exception / internal field name / filename / CLI flag; translate to a human | ||
| * message + remediation and put it here. Variants by semantics. */ | ||
| .ds .notice { display:flex; gap:10px; align-items:flex-start; padding:11px 13px; border-radius:var(--radius); font-size:var(--fs-sm); border:1px solid var(--border); background:var(--panel); } | ||
| .ds .notice .ic { font-weight:700; flex:none; line-height:1.4; } | ||
| .ds .notice .body { flex:1; } | ||
| .ds .notice .body strong { display:block; margin-bottom:2px; } | ||
| .ds .notice .body .act { margin-top:6px; } /* remediation link/button slot */ | ||
| .ds .notice--info { background:var(--info-weak); border-color:var(--accent-line); } | ||
| .ds .notice--info .ic { color:var(--info); } | ||
| .ds .notice--warn { background:var(--warn-weak); border-color:var(--warn); } | ||
| .ds .notice--warn .ic { color:var(--warn); } | ||
| .ds .notice--error { background:var(--neg-weak); border-color:var(--neg-border); } | ||
| .ds .notice--error .ic { color:var(--neg); } | ||
| .ds .notice--ok { background:var(--pos-weak); border-color:var(--pos); } | ||
| .ds .notice--ok .ic { color:var(--pos); } | ||
|
|
||
| /* empty-state — for "no data yet" surfaces. ALWAYS a title + one-line reason + | ||
| * a next-action; NEVER a bare prompt sitting above already-rendered results, | ||
| * and NEVER an internal filename/path. */ | ||
| .ds .empty-state { text-align:center; border:1px dashed var(--border); border-radius:var(--radius); padding:28px 20px; } | ||
| .ds .empty-state .es-icon { font-size:22px; opacity:.6; } | ||
| .ds .empty-state .es-title { font-family:var(--font-heading); font-weight:var(--heading-weight); font-size:15px; margin:8px 0 4px; } | ||
| .ds .empty-state .es-desc { color:var(--muted); font-size:var(--fs-sm); max-width:42ch; margin:0 auto 12px; } | ||
| .ds .empty-state .es-cta { display:inline-flex; gap:8px; } | ||
|
|
||
| /* badge — small availability/status marker (e.g. "multi-period only", | ||
| * "needs setup") so a tab/control states its applicability up front. */ | ||
| .ds .badge { display:inline-block; font-size:var(--fs-xs); font-weight:600; padding:1px 7px; border-radius:999px; background:var(--panel); color:var(--muted); border:1px solid var(--border); vertical-align:middle; } | ||
| .ds .badge--muted { opacity:.8; } | ||
|
|
||
| /* skeleton — loading placeholder so a pending surface never looks empty/broken. */ | ||
| .ds .skeleton { background:linear-gradient(90deg,var(--panel) 25%,var(--border) 37%,var(--panel) 63%); background-size:400% 100%; border-radius:var(--radius-sm); animation:ds-shimmer 1.3s ease infinite; min-height:14px; } | ||
| @keyframes ds-shimmer { 0%{background-position:100% 0} 100%{background-position:0 0} } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
P4 spec references
diagnostics_expander()function not in ds_streamlit.py.Line 83 states: "Streamlit:
ds_streamlit.dev_note(msg)→logging(notst.warning/st.write); diagnostics behindst.expander("Diagnostics")."However,
ds_streamlit.pyonly implementsdev_note()(line 153), which logs to Python's logger. There is nodiagnostics_expander()function to render a collapsible diagnostics panel in the Streamlit UI.Either:
ds_streamlit.py: a helper that wrapsst.expander()and logs/displays diagnostic info.The P4 pattern will be incomplete until this is resolved.
Would you like me to generate the missing
diagnostics_expander()function for ds_streamlit.py?🧰 Tools
🪛 markdownlint-cli2 (0.22.1)
[warning] 77-77: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
🤖 Prompt for AI Agents