<quality-target>
Polygon-as-UI quality prioritization surface. Click any axis to set its level + rationale; the radar visualizes the chosen vector against the project's capacity envelope. Pairs with iron-triangle via data-bind-to.
Overview
The <quality-target> component is the planning surface's quality decision. The polygon IS the picker — each of the 11 ility axes is a clickable hit-target that opens a per-ility <dialog> with a level radio group (Critical / Important / Acceptable / Not relevant) plus a required rationale when Critical. The same SVG visualizes the chosen vector against the project's capacity envelope, so picking and reading happen on the same surface.
Pair it with <iron-triangle> via data-bind-to and the envelope reads from project shape. "Quality" is the word the surface uses; the older "NFR" jargon and its separate compass + radar pair collapse into this one component, two roles.
<iron-triangle id="shape" data-quality-href="#quality"></iron-triangle> <quality-target id="quality" name="quality" data-bind-to="shape"></quality-target>
Per-axis editors
Click an axis (or focus it and press Enter / Space) to open its dialog. Each editor has a radio group for the four priority levels, a <form-field>-wrapped rationale textarea (required when Critical, ≥10 chars by default), and Cancel + Save buttons; the dialog uses method="dialog" so submit closes it. The marker color follows the level: red Critical, amber Important, green Acceptable, muted Not relevant. The polygon is the closed shape across all picked axes; unpicked ilities don't pull the shape toward the centroid.
Capacity envelope
When paired with <iron-triangle> via data-bind-to, the soft green polygon shows the budget envelope: radius × min(1, capacityPoints / sum(costWeights)) on each axis. Going outside the envelope is allowed but reveals an inline overrun rationale field below the polygon — the form's checkValidity() returns false until that field is filled. The component fires quality-target:over-budget and quality-target:under-budget as the selection crosses the capacity line.
const target = document.querySelector('quality-target'); target.addEventListener('quality-target:over-budget', (e) => { console.log(`Over by ${e.detail.delta} pts (${e.detail.criticalSum}/${e.detail.capacityPoints})`);});target.addEventListener('quality-target:under-budget', (e) => { console.log(`Back within budget; ${e.detail.slack} pts of slack`);});
Default ility list and cost weights
The 11 default ilities (cost dropped — that's <iron-triangle>'s job): performance, accessibility, security, reliability, maintainability, observability, compatibility, scalability, portability, internationalization, privacy. Default cost-when-Critical weights sum to 40 — against a realistic budget of 10–15 that forces real trade-offs. Override with data-cost-weights='{"performance": 8, "scalability": 8}' or set the ilities property to a custom array.
Attributes
| Attribute | Values | Default | Description |
|---|---|---|---|
name | string | quality | Form-association field name |
data-bind-to | string | — | ID of a sibling <iron-triangle> to read capacityPoints from |
data-capacity-points | number | — | Literal capacity fallback when no triangle is bound |
data-cost-weights | string | — | JSON object overriding default per-ility cost weights |
data-radius | number | 100 | Base radius in SVG units |
data-show-envelope | boolean | — | Show the capacity envelope polygon (default true; set to "false" to hide) |
data-min-rationale | number | 10 | Min characters for a Critical row's rationale |
data-max-rationale | number | 200 | Max characters for the rationale textarea |
data-min-overrun-rationale | number | 10 | Min characters for overrunRationale |
data-max-overrun-rationale | number | 400 | Max characters for overrunRationale |
disabled | boolean | — | All inputs disabled |
locked | boolean | — | Read-only mode for shipped vectors |
Properties & events
| Member | Type | Description |
|---|---|---|
vector | R/W Record<ility, level> | Current picks. |
rationales | R/W Record<ility, string> | Per-Critical rationales. |
costWeights | R | Effective merged weights. |
capacityPoints | R/W | Resolved capacity (triangle > attribute > property > Infinity). |
criticalSum | R | Live sum of Critical weights. |
overBudget | R boolean | criticalSum > capacityPoints. |
overrunRationale | R/W string | Required when over-budget. |
ilities | R/W string[] | Override the default 11. |
openEditor(ility) | method | Imperatively open one axis's dialog. |
checkValidity() | method | Validate vector + over-budget rationale; sets :state(missing-rationale). |
quality-target:change | event | Any pick or rationale change. |
quality-target:over-budget | event | Selection just crossed into over-budget. |
quality-target:under-budget | event | Selection just returned within budget. |
Accessibility
Each axis is a real focusable hit-target activatable by Enter / Space, and its dialog is a native <dialog> with proper focus management and an escape-to-close path. Priority levels are conveyed by the radio-group label text, not by marker color alone, so the red/amber/green/muted coding is reinforcement rather than the sole signal. The over-budget state surfaces as a required, labelled rationale field (and a :state(missing-rationale) hook) instead of a color-only warning.
Related
<iron-triangle>— suppliescapacityPoints; the target reads it viadata-bind-to.<capacity-plan>— readscriticalSumfrom the target and reconciles it against capacity plus slotted feature costs.<requirement-card>— renders a single ility's row outside the target for status dashboards and ADR appendices.