From ae1beff69f207f2a2e76d12d859eeb3d476703c0 Mon Sep 17 00:00:00 2001 From: Sunny Karri Date: Tue, 19 May 2026 08:59:54 -0700 Subject: [PATCH] Improve validator coverage and add feature flag editor example --- README.md | 8 +- bloom-validator/README.md | 10 +- bloom-validator/package.json | 2 +- bloom-validator/src/rule-registry.ts | 6 + .../src/rules/no-empty-elements.ts | 31 ++ .../src/rules/no-inline-styles-except-root.ts | 29 ++ .../src/rules/responsive-images.ts | 34 +++ ...invalid-responsive-empty-inline-style.html | 24 ++ bloom-validator/tests/validator.test.ts | 17 ++ examples/design-system-example.html | 30 +- examples/feature-flag-editor-example.html | 283 ++++++++++++++++++ examples/pr-review-example.html | 9 +- templates/annotated-pr-review.html | 33 +- templates/status-report-v2.html | 10 +- templates/status-report.html | 12 +- 15 files changed, 496 insertions(+), 42 deletions(-) create mode 100644 bloom-validator/src/rules/no-empty-elements.ts create mode 100644 bloom-validator/src/rules/no-inline-styles-except-root.ts create mode 100644 bloom-validator/src/rules/responsive-images.ts create mode 100644 bloom-validator/tests/fixtures/invalid-responsive-empty-inline-style.html create mode 100644 examples/feature-flag-editor-example.html diff --git a/README.md b/README.md index a17f2fa..90c8baa 100644 --- a/README.md +++ b/README.md @@ -273,7 +273,8 @@ Bloom/ ├── examples/ # Fully worked artifacts │ ├── pr-review-example.html │ ├── incident-report-example.html -│ └── design-system-example.html +│ ├── design-system-example.html +│ └── feature-flag-editor-example.html └── .github/ └── workflows/ └── ci.yml # Validator tests + artifact validation @@ -368,7 +369,7 @@ cd bloom-validator && npm run validate-all Exit codes: `0` clean, `1` errors found, `2` invalid usage. -The validator currently enforces 10 rules: +The validator currently enforces 13 rules: - `no-external-deps` - `no-hardcoded-hex` @@ -379,6 +380,9 @@ The validator currently enforces 10 rules: - `no-dialog-apis` - `lang-attribute` - `focus-visible` +- `responsive-images` +- `no-empty-elements` +- `no-inline-styles-except-root` - `security-hardening` (bundles `no-eval`, `innerHTML-with-variable`, `no-network`, `no-inline-handlers`, `no-data-html-uri`, `no-javascript-uri`, and two more — see [`bloom-validator/README.md`](bloom-validator/README.md)) Every template under `templates/` and every artifact under `examples/` passes this validator on every push (see [`.github/workflows/ci.yml`](.github/workflows/ci.yml)). diff --git a/bloom-validator/README.md b/bloom-validator/README.md index 4a2ce3a..3b66eeb 100644 --- a/bloom-validator/README.md +++ b/bloom-validator/README.md @@ -18,7 +18,7 @@ Exit codes: ## Rules implemented -The validator currently enforces 10 rules. Each rule is a single file under [`src/rules/`](src/rules/) and is registered in [`src/rule-registry.ts`](src/rule-registry.ts). +The validator currently enforces 13 rules. Each rule is a single file under [`src/rules/`](src/rules/) and is registered in [`src/rule-registry.ts`](src/rule-registry.ts). | ID | Name | Severity | Description | |---|---|---|---| @@ -31,6 +31,9 @@ The validator currently enforces 10 rules. Each rule is a single file under [`sr | `rule-12` | `no-dialog-apis` | error | No `alert()`, `prompt()`, or `confirm()` calls in inline ` + + diff --git a/examples/pr-review-example.html b/examples/pr-review-example.html index b3d903d..b148a1d 100644 --- a/examples/pr-review-example.html +++ b/examples/pr-review-example.html @@ -45,6 +45,9 @@ .legend { margin-top: 12px; font-size: 12px; color: var(--gray-500); display: flex; flex-wrap: wrap; gap: 18px; } .legend span { display: inline-flex; align-items: center; gap: 6px; } .legend .dot { width: 8px; height: 8px; border-radius: 50%; } +.legend .dot.safe { background: var(--olive); } +.legend .dot.medium { background: var(--sand); } +.legend .dot.attention { background: var(--clay); } .file-card { border: 1.5px solid var(--gray-300); border-radius: 12px; background: var(--white); margin-bottom: 24px; overflow: hidden; scroll-margin-top: 20px; } .file-head { padding: 16px 20px; border-bottom: 1.5px solid var(--gray-100); display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; } .file-path { font-family: var(--mono); font-size: 13.5px; color: var(--slate); } @@ -115,9 +118,9 @@

Risk map

internal/oauth/token_store_test.go
- safe — tests or trivial change - worth a look — verify edge cases - needs attention — could regress prod + safe — tests or trivial change + worth a look — verify edge cases + needs attention — could regress prod
diff --git a/templates/annotated-pr-review.html b/templates/annotated-pr-review.html index b9521a8..e9168ab 100644 --- a/templates/annotated-pr-review.html +++ b/templates/annotated-pr-review.html @@ -24,9 +24,12 @@ .avatar { width: 36px; height: 36px; border-radius: 50%; background: var(--oat); color: var(--slate); display: flex; align-items: center; justify-content: center; font-weight: 600; font-size: 13px; border: 1.5px solid var(--gray-300); } .branch { font-family: var(--mono); font-size: 12.5px; color: var(--gray-700); background: var(--gray-100); border: 1.5px solid var(--gray-300); border-radius: 8px; padding: 6px 10px; } .branch .arrow { color: var(--gray-500); margin: 0 6px; } -.stat { font-family: var(--mono); font-size: 13px; } -.stat .add { color: var(--olive); font-weight: 600; } -.stat .del { color: var(--rust); font-weight: 600; } + .stat { font-family: var(--mono); font-size: 13px; } + .stat .add { color: var(--olive); font-weight: 600; } + .stat .del { color: var(--rust); font-weight: 600; } + .byline-meta, .stat-files { color: var(--gray-500); } + .byline-meta { font-size: 12px; } + .stat-files { margin-left: 10px; } section { margin-bottom: 40px; } h2 { font-family: var(--serif); font-weight: 500; font-size: 21px; color: var(--slate); margin-bottom: 14px; } .risk-map { display: flex; flex-wrap: wrap; gap: 10px; } @@ -39,11 +42,15 @@ .chip.medium .dot { background: var(--sand); } .chip.attention { background: rgba(217,119,87,0.12); border-color: rgba(217,119,87,0.55); } .chip.attention .dot { background: var(--clay); } -.legend { margin-top: 12px; font-size: 12px; color: var(--gray-500); display: flex; gap: 18px; } -.legend span { display: inline-flex; align-items: center; gap: 6px; } -.legend .dot { width: 8px; height: 8px; border-radius: 50%; } + .legend { margin-top: 12px; font-size: 12px; color: var(--gray-500); display: flex; gap: 18px; } + .legend span { display: inline-flex; align-items: center; gap: 6px; } + .legend .dot { width: 8px; height: 8px; border-radius: 50%; } + .legend .dot.safe { background: var(--olive); } + .legend .dot.medium { background: var(--sand); } + .legend .dot.attention { background: var(--clay); } .file-card { border: 1.5px solid var(--gray-300); border-radius: 12px; background: var(--white); margin-bottom: 24px; overflow: hidden; scroll-margin-top: 20px; } -.file-head { padding: 16px 20px; border-bottom: 1.5px solid var(--gray-100); display: flex; align-items: center; justify-content: space-between; gap: 12px; } + .file-head { padding: 16px 20px; border-bottom: 1.5px solid var(--gray-100); display: flex; align-items: center; justify-content: space-between; gap: 12px; } + .file-head-actions { display: flex; align-items: center; gap: 12px; } .file-path { font-family: var(--mono); font-size: 13.5px; color: var(--slate); } .risk-tag { font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; padding: 3px 8px; border-radius: 6px; font-weight: 600; } .risk-tag.safe { background: rgba(120,140,93,0.15); color: var(--olive); } @@ -101,9 +108,9 @@

[PR TITLE]

[INITIALS]
-
[AUTHOR]
[OPENED WHEN]
+
[AUTHOR]
[BRANCH] main
-
+[NUM] / −[NUM] [NUM] files changed
+
+[NUM] / −[NUM] [NUM] files changed
@@ -114,9 +121,9 @@

Risk map

- safe - worth a look - needs attention + safe + worth a look + needs attention
@@ -126,7 +133,7 @@

Files

[PATH]
-
+
[RISK LEVEL]
diff --git a/templates/status-report-v2.html b/templates/status-report-v2.html index 2536696..a064041 100644 --- a/templates/status-report-v2.html +++ b/templates/status-report-v2.html @@ -103,6 +103,10 @@ margin: 0 0 6px; } hr.rule { border: none; border-top: 1px solid var(--gray-300); margin: 0 0 22px; } +.col-pr { width: 90px; } +.col-author, .col-owner, .col-blocked { width: 140px; } +.col-risk, .col-stage { width: 100px; } +.col-action { width: 60px; } .summary-band { display: grid; grid-template-columns: repeat(4, 1fr); @@ -378,7 +382,7 @@

Work breakdown

- + @@ -404,7 +408,7 @@

Work breakdown

PRTitleAuthorRisk
PRTitleAuthorRisk
#2841
- + @@ -424,7 +428,7 @@

Work breakdown

PRTitleAuthorStage
PRTitleAuthorStage
#2848
- + diff --git a/templates/status-report.html b/templates/status-report.html index 6cf520d..af712b7 100644 --- a/templates/status-report.html +++ b/templates/status-report.html @@ -31,11 +31,13 @@ .stat-delta{font-family:var(--mono);font-size:11px;margin-top:6px} .stat-delta.up{color:var(--olive)} table{width:100%;border-collapse:separate;border-spacing:0;background:var(--white);border:1.5px solid var(--gray-300);border-radius:12px;overflow:hidden} -thead th{text-align:left;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:0.06em;color:var(--gray-500);background:var(--gray-100);padding:12px 16px;border-bottom:1px solid var(--gray-300)} -tbody td{padding:13px 16px;border-bottom:1px solid var(--gray-100);font-size:14px;vertical-align:middle} + thead th{text-align:left;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:0.06em;color:var(--gray-500);background:var(--gray-100);padding:12px 16px;border-bottom:1px solid var(--gray-300)} + .col-pr{width:90px}.col-author{width:140px}.col-risk{width:100px} + tbody td{padding:13px 16px;border-bottom:1px solid var(--gray-100);font-size:14px;vertical-align:middle} tbody tr:last-child td{border-bottom:none} tbody tr:hover{background:var(--ivory)} -.risk{display:inline-flex;align-items:center;gap:7px;font-size:12px;color:var(--gray-500)} + .risk{display:inline-flex;align-items:center;gap:7px;font-size:12px;color:var(--gray-500)} + .pr-link{font-family:var(--mono);font-size:13px;color:var(--clay);text-decoration:none}.author-cell{font-size:13px;color:var(--gray-700)} .risk-dot{width:9px;height:9px;border-radius:50%;flex-shrink:0} .risk-dot.low{background:var(--olive)}.risk-dot.med{background:var(--sand)}.risk-dot.high{background:var(--clay)} .highlights{list-style:none;padding:0} @@ -83,9 +85,9 @@

Highlights

Shipped


IssueOwnerBlocked on
IssueOwnerBlocked on
OAuth webhook timeout (95p 1.8s over SLA)
- + - +
PRTitleAuthorRisk
PRTitleAuthorRisk
#[NUM][TITLE][AUTHOR]Med
#[NUM][TITLE][AUTHOR]Med