Welcome to Vanilla Breeze
This bell pulls live notifications from /go/notify/messages — the same contract documented at /docs/concepts/service-contracts/. Static articles like this one are the no-JS / no-backend fallback.
This bell pulls live notifications from /go/notify/messages — the same contract documented at /docs/concepts/service-contracts/. Static articles like this one are the no-JS / no-backend fallback.
11-ility prioritization decision surface that spends a numeric capacity budget supplied by
The <nfr-compass> component is the Planning Pack's quality-decision surface. It presents the eleven non-functional requirement (NFR) categories — performance, accessibility, security, reliability, maintainability, observability, compatibility, scalability, portability, internationalization, privacy — as one form, asks the team to mark each as Critical, Important, Acceptable, or Not relevant, and enforces scarcity by spending a numeric capacity budget against every Critical pick. Reach for it whenever a requirements document tries to flag everything as "must"; the compass replaces that anti-pattern with a forced-trade interface backed by real numbers from <iron-triangle>.
The output is a structured quality vector with required rationales — each Critical pick must include a one-sentence "why," and going over budget requires a written overrunRationale. Downstream tools (ADRs, validators, dashboards, CI guards) read the vector; the rationale is the protective shell that stops the choice from being relitigated every Monday.
Two siblings: an <iron-triangle id="shape"> supplies the capacity budget, and the compass binds to it via data-bind-to="shape". Mark a row as Critical to spend points; cross the budget and an over-budget rationale field appears below the rows.
<iron-triangle id="shape"></iron-triangle> <nfr-compass id="priorities" name="nfr-vector" data-bind-to="shape"> <h2 slot="title">Quality priorities for v1</h2> <fieldset name="performance"> <legend>Performance</legend> <label><input type="radio" name="performance" value="critical"> Critical</label> <label><input type="radio" name="performance" value="important"> Important</label> <label><input type="radio" name="performance" value="acceptable" checked> Acceptable</label> <label><input type="radio" name="performance" value="not-relevant"> Not relevant</label> <textarea name="performance-rationale" hidden minlength="10" maxlength="200"></textarea> </fieldset> <!-- Repeat for accessibility, security, reliability, maintainability, observability, compatibility, scalability, portability, internationalization, privacy. Or omit entirely to get the default 11. --> <textarea slot="overrun-rationale" name="overrunRationale" hidden minlength="10" maxlength="400"></textarea> <footer slot="footer"> <button type="submit">Save priorities</button> </footer></nfr-compass>
Two layers, both with shipped defaults, both override-friendly.
<iron-triangle>Capacity is resolved in this order on upgrade and on every iron-triangle:change:
<iron-triangle> matched by data-bind-to="id". The compass listens for iron-triangle:change and updates live.data-capacity-points attribute (compass-only usage with a fixed budget).capacityPoints property (programmatic).If none resolves, capacity is Infinity and the compass becomes informational — you can pick Criticals freely with no budget gate, and a console warning suggests pairing with <iron-triangle>.
Critical picks spend points; Important / Acceptable / Not-relevant cost 0. Default Critical cost weights (opinionated, easy to override):
| Ility | Default Critical cost |
|---|---|
| accessibility | 3 |
| performance | 5 |
| security | 5 |
| reliability | 4 |
| observability | 3 |
| internationalization | 4 |
| compatibility | 2 |
| portability | 3 |
| privacy | 4 |
| scalability | 5 |
| maintainability | 2 |
Sum at all-Critical = 40. A realistic mid-size project budget is 10–15, so the team is forced to choose. Override per project via attribute (the merged weights are frozen into the saved vector as costWeights so a future re-validation produces the same arithmetic):
<nfr-compass data-bind-to="shape" data-cost-weights='{"performance": 8, "scalability": 8}'></nfr-compass><!-- A perf-sensitive project: one performance-Critical now spends most of a small budget. -->
The compass allows going over budget, but captures the override so it can't quietly happen again. When criticalSum > capacityPoints:
overrunRationale textarea reveals below the row list with the prompt "You're over budget by N points — explain why."overrunRationale.length >= data-min-overrun-rationale (default 10).overrunRationale and the override is now durable.The companion uucd nfr check CLI rejects vectors where criticalSum > capacityPoints and overrunRationale is missing (only possible via hand-edit; the UI prevents it).
const compass = document.querySelector('nfr-compass'); compass.addEventListener('nfr-compass:over-budget', (e) => { console.log(`Over budget by ${e.detail.delta} points`);}); compass.addEventListener('nfr-compass:under-budget', (e) => { console.log(`Back within budget; ${e.detail.slack} points slack`);});
If you omit the <fieldset>s entirely, the compass injects the default eleven on upgrade. To opt out and ship a project-specific list (e.g. a docs site that only cares about six), author the <fieldset name="…"> rows yourself in the default slot and the component will use yours instead.
<nfr-compass name="docs-priorities" data-bind-to="shape"> <fieldset name="accessibility"><legend>Accessibility</legend>…</fieldset> <fieldset name="performance"><legend>Performance</legend>…</fieldset> <fieldset name="seo"><legend>SEO</legend>…</fieldset> <fieldset name="i18n"><legend>i18n</legend>…</fieldset> <fieldset name="maintainability"><legend>Maintainability</legend>…</fieldset> <fieldset name="cost"><legend>Cost</legend>…</fieldset></nfr-compass>
Add matching entries to data-cost-weights if your custom rows are absent from the default weight table.
If there's no project-shape surface to bind to, supply a literal capacity number. The over-budget gate still works; you just lose drift detection.
<nfr-compass name="v1-priorities" data-capacity-points="12"></nfr-compass>
| Attribute | Type | Default | Description |
|---|---|---|---|
name |
string | nfr-vector |
Form-association field name. |
data-bind-to |
string | — | ID of a sibling <iron-triangle> to read capacity from. |
data-capacity-points |
integer | — | Literal capacity fallback when no triangle is bound. |
data-cost-weights |
JSON object string | — | Per-ility cost-weight overrides; merged on top of the defaults. |
data-min-rationale |
integer | 10 |
Min characters for a Critical row's rationale. |
data-max-rationale |
integer | 200 |
Max characters for the rationale textarea. |
data-min-overrun-rationale |
integer | 10 |
Min characters for overrunRationale. |
data-max-overrun-rationale |
integer | 400 |
Max characters for overrunRationale. |
disabled |
boolean | absent | All inputs disabled. |
locked |
boolean | absent | Read-only mode for shipped vectors. |
| Slot | Expected content | Required |
|---|---|---|
title | Heading text shown above the rows | no (defaults to "NFR Compass") |
| (default slot) | <fieldset name="…"> rows; omit to get the default 11 ilities | no |
notes | Project-supplied advisory notes | no |
overrun-rationale | Pre-existing <textarea name="overrunRationale">; one is created when missing | no |
footer | Save / Submit area | no |
| Event | Detail | When |
|---|---|---|
nfr-compass:change |
{ vector, rationales, costWeights, capacityPoints, criticalSum, overrunRationale, source } |
Any pick or rationale change. |
nfr-compass:over-budget |
{ delta, criticalSum, capacityPoints } |
The selection just crossed into over-budget. |
nfr-compass:under-budget |
{ slack, criticalSum, capacityPoints } |
The selection just returned within budget. |
| State | When |
|---|---|
:state(over-budget) | Set when criticalSum > capacityPoints; CSS can highlight the readout. |
:state(missing-rationale) | Set during validation when any Critical lacks a complete rationale or overrunRationale is absent while over-budget. |
| Property | Type | Description |
|---|---|---|
.vector | Record<ility, 'critical' \| 'important' \| 'acceptable' \| 'not-relevant'> | Current picks. Setting it replaces the form state. |
.rationales | Record<ility, string> | Get / set rationale strings. Keys must match Criticals in the vector. |
.costWeights | Record<ility, integer> | Effective merged weights (defaults + overrides). |
.capacityPoints | integer | Current capacity (resolved from sibling, attribute, or property). |
.criticalSum | integer (readonly) | Live sum of weights for current Criticals. |
.overBudget | boolean (readonly) | criticalSum > capacityPoints. |
.overrunRationale | string | Required textarea contents when over budget. |
.value | object (readonly) | Full snapshot for serialization. |
const compass = document.querySelector('nfr-compass'); compass.vector = { performance: 'critical', security: 'critical', accessibility: 'important',};compass.rationales = { performance: 'Sub-200ms TTI is our differentiator vs Competitor X.', security: 'We handle PHI; SOC 2 audit pending Q2.',}; console.log(compass.criticalSum, compass.capacityPoints, compass.overBudget);// e.g. 10, 12, false — or 12, 10, true (with overrunRationale required)/code-block
| Token | Default | Purpose |
|---|---|---|
--nfr-compass-padding | var(--size-l) | Outer padding |
--nfr-compass-row-gap | var(--size-s) | Vertical gap between rows |
--nfr-compass-pill-bg | var(--color-surface-raised) | Segmented-control track |
--nfr-compass-critical-bg | var(--color-error-subtle) | Critical pip background |
--nfr-compass-important-bg | var(--color-warning-subtle) | Important pip background |
--nfr-compass-acceptable-bg | var(--color-success-subtle) | Acceptable pip background |
--nfr-compass-not-relevant-bg | var(--color-surface) | Not-relevant pip background |
--nfr-compass-overbudget-color | var(--color-error) | Over-budget chip + readout when over |
Without JavaScript, the compass is a plain <form> with one <fieldset> per ility, four-radio priority groups, and hidden rationale textareas. The form submits to its action with all picks intact, and a server can validate the budget against a stored capacity. After upgrade the component reveals rationale fields when Critical is selected, computes criticalSum live, surfaces the over-budget chip, and emits the events documented above.
<fieldset> / <legend> / radiogroup; no ARIA role overrides.<output aria-live="polite"> so screen readers hear "Now spending 12 of 10 points; rationale required" as picks change.<iron-triangle> — upstream capacity supplier. Compass reads capacityPoints from a paired triangle and stamps ironTriangleHash into the saved vector for drift detection.<adr-wc> — record an over-budget Critical pick as an ADR so the rationale survives the review cycle.