@unified-product-graph/core 0.26.1 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/README.md +1 -1
- package/dist/index.cjs +403 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +423 -8
- package/dist/index.d.ts +423 -8
- package/dist/index.js +401 -9
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,22 @@ This project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## [0.27.0] - 2026-08-12
|
|
11
|
+
|
|
12
|
+
**A `surface` entity gives the UI a place: what occupies it, and who wins when two things want it.** The catalog could say who OWNS a thing (`feature_area`) and what ARCHITECTURE it belongs to (`bounded_context`), but nothing answered *what else occupies the same place?*. `screen` is route-level (route / viewport / access_level) and too coarse for a shell, a pane, a slot, a field gutter, an action bar, or an overlay. Those places are contested, and the arbitration rule is decided constantly and then rediscovered forever, because prose is where it lands. `surface` records the place, its guest list, and the rule. Additive; no breaking change.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
- **`surface` entity** (`proposed`, `ent_359`) — a place in the UI, in the `ux_design` domain beside `screen`. Reached through the screen that renders it and self-nesting from there (shell holds panes, panes hold regions, regions hold slots). Lifecycle `draft → in_design → built → shipped → deprecated`, shared verbatim with `screen` so a screen and the surfaces inside it report progress on one vocabulary. Eight properties: `surface_kind` (shell / tool / pane / region / slot / gutter / action_bar / overlay / ambient), `persistence`, `visibility_condition`, `capacity`, `arbitration_rule`, `extensibility`, `mutates_content`, `dimensional_constraint`.
|
|
16
|
+
- **Ten surface edges.** Outbound: `surface_contains_surface` (hierarchy, the nesting spine), `surface_serves_job` (cross-domain), `surface_governed_by_design_guideline` (cross-domain, cross-product-eligible), `surface_renders_design_component` (hierarchy, cross-product-eligible), `surface_measured_by_metric` (semantic, cross-product-eligible), `surface_supersedes_surface` (semantic). Inbound: `feature_occupies_surface` (cross-domain, the guest list), `screen_renders_surface` (hierarchy), `decision_affects_surface` (cross-domain), `journey_step_occurs_on_surface` (semantic, the finer-grained sibling of `journey_step_shown_on_screen`).
|
|
17
|
+
- **Three anti-patterns.** `contended-surface-without-arbitration` (medium) fires when features occupy surfaces and at least one carries no `arbitration_rule`. `surface-without-job` and `surface-without-measurement` (low, both thin-graph advisory) are its coverage companions.
|
|
18
|
+
- **Property-presence filter on `EntityCheck`** — `filter: { property, present }` counts entities that do or do not carry a value for a named property, backed by a new optional `countsByTypeAndPropertyPresence` input. The pre-existing value-keyed filter cannot express absence, because a collector only indexes values that exist; without this, a detector keyed on an unfilled field could never fire.
|
|
19
|
+
|
|
20
|
+
### Notes
|
|
21
|
+
- `surface_measured_by_metric` is `semantic`, not `hierarchy`. The `*_measured_by_metric` convention splits on ownership: `outcome` / `objective` / `strategic_pillar` are `hierarchy` because they own their metrics as children, and each carries a matching `UPG_VALID_CHILDREN` entry. A surface owns no metric, so it takes the `revenue_stream` / `cost_structure` half of the convention.
|
|
22
|
+
- `capacity` is a plain optional number whose ABSENCE means unbounded. `PropertyDefinition.type` is a single scalar kind with no union form, so a sentinel value was the alternative, and a sentinel every consumer must special-case is worse than a documented absence.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
10
26
|
## [0.20.1] - 2026-07-04
|
|
11
27
|
|
|
12
28
|
**Three edges close the alignment-sheet seam: an initiative can now reach directly into the OKR ladder and the roadmap, and a strategic pillar gets its own north-star metric.** Composed flat region surfaces spanning org/team/product altitudes surfaced two gaps in the strategic cascade: an initiative's only path to a key result or a roadmap theme ran through its parent `strategic_theme`, and `strategic_pillar` — durable, multi-year, org-wide — had no measuring metric of its own, unlike every other rung of the cascade (`objective_measured_by_metric`, `key_result_quantified_by_metric`). Additive; no breaking change.
|
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
The TypeScript core for the **Unified Product Graph (UPG)**, an open specification that gives product tools a shared vocabulary for entities, edges, and frameworks.
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
321 entity types across 37 domains. 1069 typed edges. 46 canonical framework definitions, with more added as each is reviewed.
|
|
11
11
|
|
|
12
12
|
## Install
|
|
13
13
|
|
package/dist/index.cjs
CHANGED
|
@@ -104,6 +104,7 @@ __export(index_exports, {
|
|
|
104
104
|
UPG_POLYMORPHIC_EDGE_KEYS: () => UPG_POLYMORPHIC_EDGE_KEYS,
|
|
105
105
|
UPG_PORTFOLIO_KINDS: () => UPG_PORTFOLIO_KINDS,
|
|
106
106
|
UPG_PORTFOLIO_SHARED_TYPES: () => UPG_PORTFOLIO_SHARED_TYPES,
|
|
107
|
+
UPG_PRESENCE_EXCEPT_SPECS: () => UPG_PRESENCE_EXCEPT_SPECS,
|
|
107
108
|
UPG_PRODUCT_STAGES: () => UPG_PRODUCT_STAGES,
|
|
108
109
|
UPG_PRODUCT_STAGE_COERCION_MAP: () => UPG_PRODUCT_STAGE_COERCION_MAP,
|
|
109
110
|
UPG_PROPERTY_MIGRATIONS: () => UPG_PROPERTY_MIGRATIONS,
|
|
@@ -240,6 +241,7 @@ __export(index_exports, {
|
|
|
240
241
|
normalizeDocument: () => normalizeDocument,
|
|
241
242
|
parseUpg: () => parseUpg,
|
|
242
243
|
pickCanonicalEdge: () => pickCanonicalEdge,
|
|
244
|
+
presenceExceptKey: () => presenceExceptKey,
|
|
243
245
|
resolveAllEdges: () => resolveAllEdges,
|
|
244
246
|
resolveContainmentEdge: () => resolveContainmentEdge,
|
|
245
247
|
resolveEntityType: () => resolveEntityType,
|
|
@@ -334,8 +336,8 @@ var UPG_DOMAINS = [
|
|
|
334
336
|
{
|
|
335
337
|
id: "ux_design",
|
|
336
338
|
label: "Experience Design",
|
|
337
|
-
description: "How users experience and interact with your product. User journeys map end-to-end experiences, broken into journey steps. User flows chart navigation paths. Screens and screen states define what users see. Design questions frame open problems. Design concepts explore possible solutions. Prototypes and wireframes make ideas tangible. Connects User (who) to Product Specification (what) through Design System (how).",
|
|
338
|
-
types: ["user_journey", "journey_step", "journey_phase", "journey_action", "user_flow", "screen", "screen_state", "design_question", "design_concept", "prototype", "wireframe"]
|
|
339
|
+
description: "How users experience and interact with your product. User journeys map end-to-end experiences, broken into journey steps. User flows chart navigation paths. Screens and screen states define what users see. Surfaces name the places inside a screen, who may occupy them, and the rule that settles contention. Design questions frame open problems. Design concepts explore possible solutions. Prototypes and wireframes make ideas tangible. Connects User (who) to Product Specification (what) through Design System (how).",
|
|
340
|
+
types: ["user_journey", "journey_step", "journey_phase", "journey_action", "user_flow", "screen", "screen_state", "surface", "design_question", "design_concept", "prototype", "wireframe"]
|
|
339
341
|
},
|
|
340
342
|
{
|
|
341
343
|
id: "design_system",
|
|
@@ -901,6 +903,11 @@ var UPG_ENTITY_META = [
|
|
|
901
903
|
{ name: "user_flow", type_id: "ent_068", maturity: "stable", since: "0.1.0" },
|
|
902
904
|
{ name: "screen", type_id: "ent_069", maturity: "stable", since: "0.1.0" },
|
|
903
905
|
{ name: "screen_state", type_id: "ent_070", maturity: "stable", since: "0.1.0" },
|
|
906
|
+
// surface (0.27.0): the place inside a screen. `screen` is route-level
|
|
907
|
+
// (route / viewport / access_level); `surface` is the contested slot within
|
|
908
|
+
// it, carrying its occupants and the rule that arbitrates between them.
|
|
909
|
+
// Enters `proposed` per the maturity rubric (precedent: planning_cycle 0.20.0).
|
|
910
|
+
{ name: "surface", type_id: "ent_359", maturity: "proposed", since: "0.27.0" },
|
|
904
911
|
{ name: "journey_phase", type_id: "ent_330", maturity: "proposed", since: "0.2.0" },
|
|
905
912
|
{ name: "journey_action", type_id: "ent_331", maturity: "proposed", since: "0.2.0" },
|
|
906
913
|
{ name: "design_decision", type_id: "ent_319", maturity: "deprecated", since: "0.2.0", deprecated_in: "0.2.0", replacement: "decision" },
|
|
@@ -1793,6 +1800,87 @@ var UPG_EDGE_CATALOG = {
|
|
|
1793
1800
|
// edge mirrors `journey_step_realised_by_feature` one level deeper.
|
|
1794
1801
|
journey_action_surfaces_need: { forward_verb: "surfaces", reverse_verb: "surfaced_in", classification: "cross-domain", source_type: "journey_action", target_type: "need" },
|
|
1795
1802
|
journey_action_realised_by_feature: { forward_verb: "realised_by", reverse_verb: "realises", classification: "cross-domain", source_type: "journey_action", target_type: "feature" },
|
|
1803
|
+
// ── surface (0.27.0): the place inside a screen ─────────────────────────
|
|
1804
|
+
// `screen` is route-level; `surface` is the contested place within it. The
|
|
1805
|
+
// eleven edges below give a surface its spine (nesting), its guest list, its
|
|
1806
|
+
// purpose, its governing rule, its rendering vocabulary, its measurement,
|
|
1807
|
+
// its replacement path, and (0.28.0) the gap between what it intends and
|
|
1808
|
+
// what it actually does.
|
|
1809
|
+
//
|
|
1810
|
+
// Classification adjudications, each anchored on the nearest precedent:
|
|
1811
|
+
// contains → hierarchy (feature_area_contains_feature_area, planning_cycle_contains_planning_cycle)
|
|
1812
|
+
// serves job → cross-domain (ux_design → users_needs; cf. user_journey_addresses_job).
|
|
1813
|
+
// Verb is `serves`, not `addresses`: a feature ADDRESSES a job
|
|
1814
|
+
// (it satisfies the struggle); a surface SERVES it (it is the
|
|
1815
|
+
// place where the job gets done). `serves`/`served_by` is an
|
|
1816
|
+
// established house pair (service_serves_api_endpoint,
|
|
1817
|
+
// api_endpoint_serves_feature, product_serves_account).
|
|
1818
|
+
// governed_by → cross-domain (ux_design → design_system). The verb precedent
|
|
1819
|
+
// design_component_governed_by_design_guideline is `hierarchy`,
|
|
1820
|
+
// but both of its endpoints sit inside design_system; once the
|
|
1821
|
+
// seam is crossed the catalog uses cross-domain
|
|
1822
|
+
// (insight_informs_design_guideline). cross_product_eligible:
|
|
1823
|
+
// the guideline usually lives in the shared design-system graph.
|
|
1824
|
+
// renders comp. → hierarchy + cross_product_eligible, matching its direct
|
|
1825
|
+
// sibling screen_renders_design_component verbatim.
|
|
1826
|
+
// measured_by → semantic + cross_product_eligible. The enumerated
|
|
1827
|
+
// *_measured_by_metric convention splits on OWNERSHIP, not on the
|
|
1828
|
+
// verb: outcome / objective / strategic_pillar are `hierarchy`
|
|
1829
|
+
// because those entities own their metrics as children (each has a
|
|
1830
|
+
// UPG_VALID_CHILDREN entry to match, which the hierarchy-integrity
|
|
1831
|
+
// guardrail enforces). A surface does not own a metric any more
|
|
1832
|
+
// than a revenue_stream does, so it takes the `semantic` half of
|
|
1833
|
+
// the convention (revenue_stream / cost_structure). It stays
|
|
1834
|
+
// cross_product_eligible with the rest: a metric DEFINITION is
|
|
1835
|
+
// portfolio_shared, so the reading it names commonly lives in the
|
|
1836
|
+
// registry rather than in the product graph.
|
|
1837
|
+
// supersedes → semantic (prompt_version_supersedes_prompt_version).
|
|
1838
|
+
surface_contains_surface: { forward_verb: "contains", reverse_verb: "belongs_to", classification: "hierarchy", source_type: "surface", target_type: "surface" },
|
|
1839
|
+
surface_serves_job: { forward_verb: "serves", reverse_verb: "served_by", classification: "cross-domain", source_type: "surface", target_type: "job" },
|
|
1840
|
+
surface_governed_by_design_guideline: { forward_verb: "governed_by", reverse_verb: "governs", classification: "cross-domain", source_type: "surface", target_type: "design_guideline", cross_product_eligible: true },
|
|
1841
|
+
surface_renders_design_component: { forward_verb: "renders", reverse_verb: "rendered_on", classification: "hierarchy", source_type: "surface", target_type: "design_component", cross_product_eligible: true },
|
|
1842
|
+
surface_measured_by_metric: { forward_verb: "measured_by", reverse_verb: "measures", classification: "semantic", source_type: "surface", target_type: "metric", cross_product_eligible: true },
|
|
1843
|
+
surface_supersedes_surface: { forward_verb: "supersedes", reverse_verb: "superseded_by", classification: "semantic", source_type: "surface", target_type: "surface" },
|
|
1844
|
+
// ── surface, 0.28.0 addition: intent versus reality ─────────────────────────
|
|
1845
|
+
// Every surface property records what the design INTENDS. `capacity: 1` on a
|
|
1846
|
+
// banner region is an assertion, and a real product will eventually be found
|
|
1847
|
+
// rendering four. Without somewhere to put that fact, the only ways to record
|
|
1848
|
+
// it are to edit `capacity` up to 4 (which destroys the intent, and with it
|
|
1849
|
+
// any evidence that a gap exists) or to leave the graph quietly wrong.
|
|
1850
|
+
//
|
|
1851
|
+
// This edge is the third option, and it is what lets `capacity` mean intent
|
|
1852
|
+
// unconditionally: the deviation hangs off a `technical_debt_item`, which is
|
|
1853
|
+
// already a trackable, assignable, prioritisable entity with an owner and a
|
|
1854
|
+
// remediation cost. The gap becomes work rather than a discrepancy.
|
|
1855
|
+
//
|
|
1856
|
+
// classification → cross-domain (ux_design → technical_debt). Crossing INTO
|
|
1857
|
+
// the debt domain from another ring is the established shape:
|
|
1858
|
+
// risk_manifests_as_technical_debt_item is cross-domain on exactly that
|
|
1859
|
+
// reasoning. NOT `causal`: `decision_incurs_technical_debt_item` is causal
|
|
1860
|
+
// because a decision BRINGS the debt into existence, whereas a surface
|
|
1861
|
+
// does not cause its own drift, it exhibits it. NOT `hierarchy`: that
|
|
1862
|
+
// would oblige a surface → technical_debt_item containment pair in
|
|
1863
|
+
// UPG_VALID_CHILDREN (guardrail G2b), and a surface does not own the debt
|
|
1864
|
+
// that afflicts it — service_carries_technical_debt_item can be hierarchy
|
|
1865
|
+
// because a service genuinely owns its backlog.
|
|
1866
|
+
// verbs → `deviates_via` / `causes_deviation_in`. The forward verb names
|
|
1867
|
+
// what the SOURCE does (the surface deviates), qualified by the debt that
|
|
1868
|
+
// accounts for it, so the edge key reads as the sentence it asserts. The
|
|
1869
|
+
// reverse says what the debt does to the place.
|
|
1870
|
+
// not cross_product_eligible → a deviation is a fact about one product's
|
|
1871
|
+
// code, and `surface` is not portfolio_shared in the first place.
|
|
1872
|
+
surface_deviates_via_technical_debt_item: { forward_verb: "deviates_via", reverse_verb: "causes_deviation_in", classification: "cross-domain", source_type: "surface", target_type: "technical_debt_item" },
|
|
1873
|
+
// Inbound. `feature_occupies_surface` is the guest list: the edge that makes
|
|
1874
|
+
// "what else lives here?" answerable, and the one the contention anti-pattern
|
|
1875
|
+
// counts. cross-domain (product_spec → ux_design), like screen_surfaces_feature
|
|
1876
|
+
// running the other way.
|
|
1877
|
+
feature_occupies_surface: { forward_verb: "occupies", reverse_verb: "occupied_by", classification: "cross-domain", source_type: "feature", target_type: "surface" },
|
|
1878
|
+
screen_renders_surface: { forward_verb: "renders", reverse_verb: "rendered_by", classification: "hierarchy", source_type: "screen", target_type: "surface" },
|
|
1879
|
+
decision_affects_surface: { forward_verb: "affects", reverse_verb: "affected_by", classification: "cross-domain", source_type: "decision", target_type: "surface" },
|
|
1880
|
+
// The finer-grained sibling of journey_step_shown_on_screen: a step happens on
|
|
1881
|
+
// a PLACE, not just a route. Same semantic classification as that edge (a step
|
|
1882
|
+
// is not contained by the surface; it references where it happens).
|
|
1883
|
+
journey_step_occurs_on_surface: { forward_verb: "occurs_on", reverse_verb: "hosts", classification: "semantic", source_type: "journey_step", target_type: "surface" },
|
|
1796
1884
|
// 2.5 UI System Domain
|
|
1797
1885
|
product_systematised_in_design_system: { forward_verb: "systematised_in", reverse_verb: "systematises", classification: "hierarchy", source_type: "product", target_type: "design_system" },
|
|
1798
1886
|
product_built_with_design_component: { forward_verb: "built_with", reverse_verb: "built_for", classification: "hierarchy", source_type: "product", target_type: "design_component" },
|
|
@@ -4314,8 +4402,24 @@ var UPG_VALID_CHILDREN = {
|
|
|
4314
4402
|
"user_flow",
|
|
4315
4403
|
"insight"
|
|
4316
4404
|
],
|
|
4317
|
-
screen: ["screen_state", "screen", "design_component", "wireframe"],
|
|
4318
|
-
// self-nesting + components + wireframes
|
|
4405
|
+
screen: ["screen_state", "screen", "surface", "design_component", "wireframe"],
|
|
4406
|
+
// self-nesting + surfaces + components + wireframes
|
|
4407
|
+
// surface (0.27.0): the place inside a screen. Self-nests, so a shell holds
|
|
4408
|
+
// panes, a pane holds regions, a region holds slots. A surface is reached
|
|
4409
|
+
// through the screen that renders it (`screen: [... 'surface']`), never as a
|
|
4410
|
+
// top-level product child: every UPG_VALID_CHILDREN pair must be backed by a
|
|
4411
|
+
// hierarchy-classified edge (guardrail G2b), and the ratified ten do not
|
|
4412
|
+
// include a product-to-surface verb. An app-shell surface therefore hangs off
|
|
4413
|
+
// the screen that mounts it.
|
|
4414
|
+
//
|
|
4415
|
+
// NOTE: `surface_kind` narrows this further than UPG_VALID_CHILDREN can
|
|
4416
|
+
// express. UPG_VALID_CHILDREN is keyed on entity TYPE, so it can only say
|
|
4417
|
+
// "a surface may contain a surface"; the kind ordering (shell > tool > pane >
|
|
4418
|
+
// region > slot / gutter / action_bar, with overlay and ambient parented by
|
|
4419
|
+
// whatever raises them) is a property-level rule documented on `SurfaceKind`
|
|
4420
|
+
// and enforced by validators, not by this table. Precedent: the same split
|
|
4421
|
+
// applies to `planning_cycle`, whose `cadence_kind` narrows its self-nesting.
|
|
4422
|
+
surface: ["surface", "design_component"],
|
|
4319
4423
|
// ── Marketing & Communications hierarchy ────────────────────────────────────
|
|
4320
4424
|
marketing_strategy: [
|
|
4321
4425
|
"marketing_channel",
|
|
@@ -5387,6 +5491,48 @@ var SCREEN_LIFECYCLE = {
|
|
|
5387
5491
|
}
|
|
5388
5492
|
]
|
|
5389
5493
|
};
|
|
5494
|
+
var SURFACE_LIFECYCLE = {
|
|
5495
|
+
entity_type: "surface",
|
|
5496
|
+
initial_phase: "draft",
|
|
5497
|
+
terminal_phases: ["deprecated"],
|
|
5498
|
+
phases: [
|
|
5499
|
+
{
|
|
5500
|
+
id: "draft",
|
|
5501
|
+
status_category: "started",
|
|
5502
|
+
label: "Draft",
|
|
5503
|
+
description: "The surface is a rough idea: named and scoped, but not yet worked in a design tool. Its occupants and arbitration rule are usually still undecided.",
|
|
5504
|
+
transitions_to: ["in_design"]
|
|
5505
|
+
},
|
|
5506
|
+
{
|
|
5507
|
+
id: "in_design",
|
|
5508
|
+
status_category: "started",
|
|
5509
|
+
label: "In Design",
|
|
5510
|
+
description: "The surface is being designed: its dimensions, occupants, and arbitration rule are being settled. May fall back to `draft` if the approach is rethought from scratch.",
|
|
5511
|
+
transitions_to: ["built", "draft"]
|
|
5512
|
+
},
|
|
5513
|
+
{
|
|
5514
|
+
id: "built",
|
|
5515
|
+
status_category: "started",
|
|
5516
|
+
label: "Built",
|
|
5517
|
+
description: "The surface has been implemented in code. It exists in a build but is not yet live for users. May return to `in_design` if implementation surfaces a layout or contention gap.",
|
|
5518
|
+
transitions_to: ["shipped", "in_design"]
|
|
5519
|
+
},
|
|
5520
|
+
{
|
|
5521
|
+
id: "shipped",
|
|
5522
|
+
status_category: "completed",
|
|
5523
|
+
label: "Shipped",
|
|
5524
|
+
description: "The surface is live in production and reachable by users. Its occupants are the real guest list.",
|
|
5525
|
+
transitions_to: ["deprecated"]
|
|
5526
|
+
},
|
|
5527
|
+
{
|
|
5528
|
+
id: "deprecated",
|
|
5529
|
+
status_category: "completed",
|
|
5530
|
+
label: "Deprecated",
|
|
5531
|
+
description: "The surface has been retired. Its occupants have moved elsewhere, or a replacement has taken the place; record the replacement with `surface_supersedes_surface`.",
|
|
5532
|
+
transitions_to: []
|
|
5533
|
+
}
|
|
5534
|
+
]
|
|
5535
|
+
};
|
|
5390
5536
|
var SERVICE_LIFECYCLE = {
|
|
5391
5537
|
entity_type: "service",
|
|
5392
5538
|
initial_phase: "development",
|
|
@@ -6588,6 +6734,7 @@ var UPG_LIFECYCLES = [
|
|
|
6588
6734
|
DESIGN_CONCEPT_LIFECYCLE,
|
|
6589
6735
|
BRAND_IDENTITY_LIFECYCLE,
|
|
6590
6736
|
SCREEN_LIFECYCLE,
|
|
6737
|
+
SURFACE_LIFECYCLE,
|
|
6591
6738
|
// Engineering & Operations
|
|
6592
6739
|
SERVICE_LIFECYCLE,
|
|
6593
6740
|
DEPLOYMENT_LIFECYCLE,
|
|
@@ -13317,6 +13464,21 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
13317
13464
|
signal_channel: { type: "string", description: "Channel through which the signal was received" },
|
|
13318
13465
|
signal_urgency: { type: "string", enum: ["low", "medium", "high", "critical"], description: "Perceived urgency of the customer's request" }
|
|
13319
13466
|
},
|
|
13467
|
+
// SurfaceProperties: A place in the UI, its occupants, and the rule that arbitrates between them.
|
|
13468
|
+
surface: {
|
|
13469
|
+
surface_kind: { type: "string", enum: ["shell", "tool", "pane", "region", "slot", "gutter", "action_bar", "overlay", "ambient"], description: "Structural kind. Determines what may legally nest inside this surface." },
|
|
13470
|
+
persistence: { type: "string", enum: ["always", "conditional", "on_demand", "transient"], description: "How reliably the surface is present." },
|
|
13471
|
+
visibility_condition: { type: "string", description: 'When the surface appears, in plain language. Pairs with `persistence: \'conditional\'`, which states *that* it is conditional; this states *what* the condition is. @example "A node is selected", "Only for workspace admins"' },
|
|
13472
|
+
capacity: { type: "number", description: 'How many occupants ONE INSTANCE of the surface holds at once, as a non-negative count. Count the instances themselves with `cardinality`. CAPACITY IS ALWAYS INTENT, NEVER OBSERVED BEHAVIOUR. It records the cap the design asserts, so a banner region declared `capacity: 1` keeps saying 1 even after someone finds it rendering four. Reality that has drifted from the declared intent is recorded as a trackable, assignable debt item on `surface_deviates_via_technical_debt_item`, not by quietly editing this number up to match the bug. Editing it to match would destroy the only record that a gap exists. ABSENT MEANS UNBOUNDED. UPG has no union-typed property primitive (`PropertyDefinition.type` is a single scalar kind), so the proposed `integer | unbounded` shape is expressed as an optional number whose absence carries the "no cap" reading, rather than a magic sentinel value that every consumer would have to special-case. `0` is a real cap meaning "nothing may occupy this surface" (a reserved place), not "unbounded".' },
|
|
13473
|
+
cardinality: { type: "string", enum: ["1", "0..1", "1..n", "0..n"], description: "How many instances of this surface exist. `capacity` counts occupants within one instance; this counts the instances." },
|
|
13474
|
+
instance_scope: { type: "string", enum: ["global", "per_parent"], description: 'What an instance is scoped to: one shared instance for the product, or one per containing surface. Decides whether "the product has this surface" is a true sentence or a per-parent one.' },
|
|
13475
|
+
composition_mode: { type: "string", enum: ["exclusive", "additive", "chained"], description: "How the occupants relate: one wins (`exclusive`), all coexist (`additive`), or each wraps the next (`chained`). Declaring `chained` exempts the surface from `contended-surface-without-arbitration`; leaving this unset does not." },
|
|
13476
|
+
arbitration_rule: { type: "string", description: 'Who wins when more occupants want the surface than `capacity` allows, and why. ABSENCE IS MEANINGFUL: a null or empty `arbitration_rule` on a contested surface means nobody decided, which is exactly what the `contended-surface-without-arbitration` anti-pattern detects. Do not fill this in with a placeholder to silence the check. @example "Highest priority wins; ties break to the most recently updated."' },
|
|
13477
|
+
arbitration_state: { type: "string", enum: ["enforced_documented", "enforced_undocumented", "safe_by_coincidence", "none"], description: 'Whether the arbitration answer is enforced, written down, both, or neither. Separates "enforced in code but never transcribed" (ten minutes of typing) from "never decided" (a design meeting) from "safe only because nothing has collided yet" (the dangerous one).' },
|
|
13478
|
+
extensibility: { type: "string", enum: ["closed", "plugin_registerable", "user_configurable"], description: "Who may add occupants to the surface." },
|
|
13479
|
+
mutates_content: { type: "boolean", description: "Whether occupying this surface can change the underlying content, as opposed to only selecting or revealing it. The selector-versus-mutator distinction: a gutter that toggles a value is a mutator, a gutter that jumps the cursor is not." },
|
|
13480
|
+
dimensional_constraint: { type: "string", description: 'The hard spatial budget the surface imposes on its occupants, in whatever unit the design system speaks. @example "292px wide", "25px per field", "two grid columns"' }
|
|
13481
|
+
},
|
|
13320
13482
|
// SurveyResponseProperties: Aggregated survey response data.
|
|
13321
13483
|
survey_response: {
|
|
13322
13484
|
response_count: { type: "number", description: "Total responses", modifier: "snapshot" },
|
|
@@ -20293,6 +20455,7 @@ var ENTITY_EMOJI = {
|
|
|
20293
20455
|
sales_motion: "\u{1F501}",
|
|
20294
20456
|
screen: "\u{1F5A5}\uFE0F",
|
|
20295
20457
|
screen_state: "\u{1F533}",
|
|
20458
|
+
surface: "\u{1FA9F}",
|
|
20296
20459
|
security_audit: "\u{1F6C3}",
|
|
20297
20460
|
security_control: "\u{1F39A}\uFE0F",
|
|
20298
20461
|
security_policy: "\u{1F510}",
|
|
@@ -20916,6 +21079,7 @@ var STANDARD_LABELS = {
|
|
|
20916
21079
|
design_system: { alt_labels: ["component library", "style system", "ui kit"] },
|
|
20917
21080
|
screen: { alt_labels: ["page", "view", "route", "ui state"] },
|
|
20918
21081
|
screen_state: { alt_labels: ["view state", "empty state", "loading state", "error state"] },
|
|
21082
|
+
surface: { alt_labels: ["slot", "pane", "region", "zone", "shell", "panel", "placement", "ui surface"] },
|
|
20919
21083
|
// Brand layer
|
|
20920
21084
|
brand_identity: { alt_labels: ["brand", "brand guidelines", "brand book"] },
|
|
20921
21085
|
brand_imagery: { alt_labels: ["brand image", "visual asset", "brand photo"] },
|
|
@@ -23497,22 +23661,30 @@ var UX_DESIGN_GUIDE = {
|
|
|
23497
23661
|
// of a journey_step, so the step must precede the action. The journey is the
|
|
23498
23662
|
// anchor; phases are the band overlay; steps are the timeline; actions
|
|
23499
23663
|
// decompose a step into service-blueprint rows.
|
|
23500
|
-
creation_sequence: ["user_journey", "journey_phase", "journey_step", "journey_action", "screen", "screen_state", "user_flow", "wireframe", "prototype", "design_question", "design_concept"],
|
|
23664
|
+
creation_sequence: ["user_journey", "journey_phase", "journey_step", "journey_action", "screen", "screen_state", "surface", "user_flow", "wireframe", "prototype", "design_question", "design_concept"],
|
|
23501
23665
|
patterns: [
|
|
23502
23666
|
{
|
|
23503
23667
|
name: "Journey to Screen Flow",
|
|
23504
23668
|
description: "Journeys map the experience, journey steps are shown on screens, prototypes simulate screens so the design can be tested",
|
|
23505
23669
|
entity_types: ["user_journey", "journey_step", "screen", "prototype"],
|
|
23506
23670
|
edge_chain: ["persona_experiences_user_journey", "user_journey_contains_journey_step", "journey_step_shown_on_screen", "prototype_simulates_screen"]
|
|
23671
|
+
},
|
|
23672
|
+
{
|
|
23673
|
+
name: "Surface Contention",
|
|
23674
|
+
description: "A screen renders surfaces, features occupy them, and the surface records the rule that settles who wins. Nest the place (shell, pane, region, slot), list the occupants, then write the arbitration rule down once so it is not re-argued at the next feature.",
|
|
23675
|
+
entity_types: ["screen", "surface", "feature"],
|
|
23676
|
+
edge_chain: ["screen_renders_surface", "surface_contains_surface", "feature_occupies_surface", "surface_serves_job"]
|
|
23507
23677
|
}
|
|
23508
23678
|
],
|
|
23509
23679
|
required_bridges: [
|
|
23510
23680
|
{ edge_type: "persona_experiences_user_journey", target_domain: "user", when: "Every journey must be anchored to a persona" },
|
|
23681
|
+
{ edge_type: "feature_occupies_surface", target_domain: "product_spec", when: "A surface should list the features that occupy it; the guest list is what makes contention visible" },
|
|
23511
23682
|
{ edge_type: "prototype_tests_hypothesis", target_domain: "validation", when: "Prototypes should test design hypotheses before building" },
|
|
23512
23683
|
{ edge_type: "screen_surfaces_feature", target_domain: "product_spec", when: "Screens should connect to the features they surface" }
|
|
23513
23684
|
],
|
|
23514
23685
|
anti_patterns: [
|
|
23515
23686
|
{ description: "Screens without journeys: isolated screens miss the experience context" },
|
|
23687
|
+
{ description: "Surfaces without an arbitration rule: a place two features both want, with no recorded answer for who wins, is a decision that gets made again every time it comes up. Fill `arbitration_rule`, or link the design guideline that already answers it via surface_governed_by_design_guideline." },
|
|
23516
23688
|
{ description: "Prototypes without testing: if nobody tests the prototype, it is just art" },
|
|
23517
23689
|
{ description: "Design questions left open: exploration status should progress to resolved or parked" },
|
|
23518
23690
|
{ description: "Touchpoints stuffed in the deprecated journey_step.touchpoint string (UPG-675). Touchpoints belong in one of two layers: journey_action is the in-product blueprint layer (the finest band of a journey_step), and the touchpoint entity is the cross-channel customer-success layer (touchpoint_occurs_in_journey_step). Pick the layer; do not duplicate the touch as a free-text string." }
|
|
@@ -27127,8 +27299,196 @@ var UPG_ANTI_PATTERNS = [
|
|
|
27127
27299
|
stages: ["concept", "validation", "build", "beta", "launch", "growth", "mature", "maintenance", "sunset"],
|
|
27128
27300
|
severity: "medium",
|
|
27129
27301
|
source: { kind: "fundamental" }
|
|
27302
|
+
},
|
|
27303
|
+
// ── surface (0.27.0): the place, its guest list, and its arbitration rule ──
|
|
27304
|
+
// Three patterns, calibrated so the contention one leads and the two coverage
|
|
27305
|
+
// companions sit a tier below it. Like every entry in this file, each detector
|
|
27306
|
+
// is a WHOLE-GRAPH approximation of a per-node rule (cf. personas-without-jobs,
|
|
27307
|
+
// which fires on "no persona anywhere links to a job", not per persona): the
|
|
27308
|
+
// `IntelligenceCondition` language composes aggregate counts, not per-node
|
|
27309
|
+
// predicates.
|
|
27310
|
+
{
|
|
27311
|
+
id: "contended-surface-without-arbitration",
|
|
27312
|
+
since: "0.27.0",
|
|
27313
|
+
name: "Contended surface without arbitration",
|
|
27314
|
+
description: 'Features occupy surfaces in this graph, and at least one surface has no recorded answer to "who wins here, and why". Either it carries no arbitration_rule and has not declared itself `chained`, or it admits through arbitration_state that the answer is absent (`none`) or accidental (`safe_by_coincidence`). Left unrecorded, that answer lives in whoever remembers the last argument.',
|
|
27315
|
+
// 0.28.0 (feedback 852a9721, from a 30-surface field audit). Three changes,
|
|
27316
|
+
// each earned by a reported false positive or a reported blind spot:
|
|
27317
|
+
//
|
|
27318
|
+
// (a) rule-absence, EXCEPT chained. `composition_mode: 'chained'` means each
|
|
27319
|
+
// occupant wraps the next rather than competing with it. Many occupants
|
|
27320
|
+
// and no arbitration rule is the designed shape, so the reporter's 14
|
|
27321
|
+
// chained slots were 14 standing false positives — and because
|
|
27322
|
+
// `get_anti_pattern_violations_for` attributes by TYPE, they kept the
|
|
27323
|
+
// whole surface roster lit no matter how diligently the genuinely
|
|
27324
|
+
// contended surfaces were documented. The check could never be silenced
|
|
27325
|
+
// by correct modelling, which is the property a check has to have.
|
|
27326
|
+
// Exemption is DECLARE-TO-EARN: an unset `composition_mode` still fires,
|
|
27327
|
+
// so the default posture stays suspicious and silence must be claimed.
|
|
27328
|
+
// (b) `safe_by_coincidence` fires on its own, with no chained exemption. It
|
|
27329
|
+
// is the one state that a written `arbitration_rule` can mask: the field
|
|
27330
|
+
// audit found surfaces whose prose described disjoint enum values doing
|
|
27331
|
+
// the arbitrating with nothing guarding them. Branch (a) cannot see
|
|
27332
|
+
// those, because the rule text is present.
|
|
27333
|
+
// (c) `none` fires on its own too. A graph that says "nobody decided" while
|
|
27334
|
+
// carrying rule text is contradicting itself, and the admission is the
|
|
27335
|
+
// half to believe.
|
|
27336
|
+
//
|
|
27337
|
+
// `enforced_undocumented` deliberately gets NO branch and NO suppression. It
|
|
27338
|
+
// needs none: a surface enforcing an untranscribed rule has no
|
|
27339
|
+
// `arbitration_rule`, so branch (a) already fires. Nor does it earn a
|
|
27340
|
+
// downgrade — severity is per-anti-pattern, not per-violation, so there is
|
|
27341
|
+
// nowhere to put one; and the harm this pattern names is that the settlement
|
|
27342
|
+
// is unrecorded, which is exactly what `enforced_undocumented` admits. The
|
|
27343
|
+
// graph is the record; code is not. What the state buys is triage, so the
|
|
27344
|
+
// remediation text below names the two costs separately.
|
|
27345
|
+
//
|
|
27346
|
+
// No new anti-pattern was minted for `safe_by_coincidence`. It is a value of
|
|
27347
|
+
// the arbitration question, not a different question, and a second detector
|
|
27348
|
+
// would fire on the same graphs and double-report through type-keyed
|
|
27349
|
+
// attribution — making this family noisier on its first real deployment,
|
|
27350
|
+
// which is the exact failure (a) exists to fix. If field evidence later shows
|
|
27351
|
+
// it needs its own severity, mint it then.
|
|
27352
|
+
structured_condition: {
|
|
27353
|
+
operator: "and",
|
|
27354
|
+
checks: [
|
|
27355
|
+
{ check: { type: "entity_count", entity_type: "surface", comparison: "nonzero" } },
|
|
27356
|
+
{
|
|
27357
|
+
check: {
|
|
27358
|
+
type: "relationship",
|
|
27359
|
+
source_type: "feature",
|
|
27360
|
+
edge_type: "feature_occupies_surface",
|
|
27361
|
+
target_type: "surface",
|
|
27362
|
+
comparison: "exists"
|
|
27363
|
+
}
|
|
27364
|
+
},
|
|
27365
|
+
{
|
|
27366
|
+
operator: "or",
|
|
27367
|
+
checks: [
|
|
27368
|
+
{
|
|
27369
|
+
check: {
|
|
27370
|
+
type: "entity_count",
|
|
27371
|
+
entity_type: "surface",
|
|
27372
|
+
filter: {
|
|
27373
|
+
property: "arbitration_rule",
|
|
27374
|
+
present: false,
|
|
27375
|
+
except_property: "composition_mode",
|
|
27376
|
+
except_value: "chained"
|
|
27377
|
+
},
|
|
27378
|
+
comparison: "nonzero"
|
|
27379
|
+
}
|
|
27380
|
+
},
|
|
27381
|
+
{
|
|
27382
|
+
check: {
|
|
27383
|
+
type: "entity_count",
|
|
27384
|
+
entity_type: "surface",
|
|
27385
|
+
filter: { property: "arbitration_state", value: "safe_by_coincidence" },
|
|
27386
|
+
comparison: "nonzero"
|
|
27387
|
+
}
|
|
27388
|
+
},
|
|
27389
|
+
{
|
|
27390
|
+
check: {
|
|
27391
|
+
type: "entity_count",
|
|
27392
|
+
entity_type: "surface",
|
|
27393
|
+
filter: { property: "arbitration_state", value: "none" },
|
|
27394
|
+
comparison: "nonzero"
|
|
27395
|
+
}
|
|
27396
|
+
}
|
|
27397
|
+
]
|
|
27398
|
+
}
|
|
27399
|
+
]
|
|
27400
|
+
},
|
|
27401
|
+
why_it_matters: "Contention over a UI place is settled every time it comes up, and an unrecorded settlement is re-argued at the next feature. The cost is paid in repeated decisions, not in a visible defect. A surface that is safe only by coincidence pays it all at once instead, the first time an occupant is added.",
|
|
27402
|
+
remediation: "Read `arbitration_state` first: it says what the work actually is. `enforced_undocumented` means the rule already exists in code and needs transcribing into `arbitration_rule`: ten minutes, no meeting. `none` means nobody has decided and someone has to. `safe_by_coincidence` means nothing is enforcing anything and the surface only looks settled; that one wants a guard in code, not just prose. Record the constraint the rule works within (`capacity`, `dimensional_constraint`), and where a design system already answers it, link the source with `surface_governed_by_design_guideline`. Surfaces whose occupants wrap one another rather than compete should declare `composition_mode: 'chained'`, which exempts them: that is a factual claim about how the code composes, not a way to quiet the check.",
|
|
27403
|
+
stages: ["build", "beta", "launch", "growth", "mature", "maintenance"],
|
|
27404
|
+
severity: "medium",
|
|
27405
|
+
source: { kind: "fundamental" }
|
|
27406
|
+
},
|
|
27407
|
+
{
|
|
27408
|
+
id: "surface-without-job",
|
|
27409
|
+
since: "0.27.0",
|
|
27410
|
+
name: "Surface without a job",
|
|
27411
|
+
description: "The graph has surfaces, but none links to the job it exists to serve. A surface with no job is a place that survives on precedent: it is there because it has always been there.",
|
|
27412
|
+
structured_condition: {
|
|
27413
|
+
operator: "and",
|
|
27414
|
+
checks: [
|
|
27415
|
+
{ check: { type: "entity_count", entity_type: "surface", comparison: "nonzero" } },
|
|
27416
|
+
{
|
|
27417
|
+
check: {
|
|
27418
|
+
type: "relationship",
|
|
27419
|
+
source_type: "surface",
|
|
27420
|
+
edge_type: "surface_serves_job",
|
|
27421
|
+
target_type: "job",
|
|
27422
|
+
comparison: "not_exists"
|
|
27423
|
+
}
|
|
27424
|
+
}
|
|
27425
|
+
]
|
|
27426
|
+
},
|
|
27427
|
+
why_it_matters: "Without a job, there is no test for whether a surface still earns its space. Places accumulate, and the layout argument becomes a matter of taste rather than of purpose.",
|
|
27428
|
+
remediation: "Link each surface to the job it serves with `surface_serves_job`. A surface that cannot name one is a candidate for removal or for merging into its parent.",
|
|
27429
|
+
stages: ["build", "beta", "launch", "growth", "mature"],
|
|
27430
|
+
severity: "low",
|
|
27431
|
+
source: { kind: "practitioner", attribution: "Clayton Christensen, Jobs to Be Done" }
|
|
27432
|
+
},
|
|
27433
|
+
{
|
|
27434
|
+
id: "surface-without-measurement",
|
|
27435
|
+
since: "0.27.0",
|
|
27436
|
+
name: "Surface without measurement",
|
|
27437
|
+
description: "The graph has surfaces, but none links to a metric. Nothing reports whether the place is used, ignored, or in the way.",
|
|
27438
|
+
structured_condition: {
|
|
27439
|
+
operator: "and",
|
|
27440
|
+
checks: [
|
|
27441
|
+
{ check: { type: "entity_count", entity_type: "surface", comparison: "nonzero" } },
|
|
27442
|
+
{
|
|
27443
|
+
check: {
|
|
27444
|
+
type: "relationship",
|
|
27445
|
+
source_type: "surface",
|
|
27446
|
+
edge_type: "surface_measured_by_metric",
|
|
27447
|
+
target_type: "metric",
|
|
27448
|
+
comparison: "not_exists"
|
|
27449
|
+
}
|
|
27450
|
+
}
|
|
27451
|
+
]
|
|
27452
|
+
},
|
|
27453
|
+
why_it_matters: "An unmeasured surface cannot be retired on evidence. It is defended by whoever built it and challenged by whoever wants its space, with no reading to settle the question.",
|
|
27454
|
+
remediation: "Attach the reading that would justify keeping the place with `surface_measured_by_metric`. Engagement, or the rate at which its occupants are actually invoked, is usually the honest one.",
|
|
27455
|
+
stages: ["build", "beta", "launch", "growth", "mature"],
|
|
27456
|
+
severity: "low",
|
|
27457
|
+
source: { kind: "fundamental" }
|
|
27130
27458
|
}
|
|
27131
27459
|
];
|
|
27460
|
+
function walkForExceptSpecs(cond, out) {
|
|
27461
|
+
if ("operator" in cond) {
|
|
27462
|
+
for (const child of cond.checks) walkForExceptSpecs(child, out);
|
|
27463
|
+
return;
|
|
27464
|
+
}
|
|
27465
|
+
const check = cond.check;
|
|
27466
|
+
if (check.type !== "entity_count" || !check.filter) return;
|
|
27467
|
+
const f = check.filter;
|
|
27468
|
+
if (typeof f.property !== "string" || typeof f.except_property !== "string" || typeof f.except_value !== "string") {
|
|
27469
|
+
return;
|
|
27470
|
+
}
|
|
27471
|
+
const spec = {
|
|
27472
|
+
entity_type: check.entity_type,
|
|
27473
|
+
property: f.property,
|
|
27474
|
+
except_property: f.except_property,
|
|
27475
|
+
except_value: f.except_value
|
|
27476
|
+
};
|
|
27477
|
+
const seen = out.some(
|
|
27478
|
+
(s) => s.entity_type === spec.entity_type && s.property === spec.property && s.except_property === spec.except_property && s.except_value === spec.except_value
|
|
27479
|
+
);
|
|
27480
|
+
if (!seen) out.push(spec);
|
|
27481
|
+
}
|
|
27482
|
+
var UPG_PRESENCE_EXCEPT_SPECS = (() => {
|
|
27483
|
+
const out = [];
|
|
27484
|
+
for (const ap of UPG_ANTI_PATTERNS) {
|
|
27485
|
+
if (ap.structured_condition) walkForExceptSpecs(ap.structured_condition, out);
|
|
27486
|
+
}
|
|
27487
|
+
return out;
|
|
27488
|
+
})();
|
|
27489
|
+
function presenceExceptKey(property, exceptProperty, exceptValue) {
|
|
27490
|
+
return `${property}!${exceptProperty}=${exceptValue}`;
|
|
27491
|
+
}
|
|
27132
27492
|
function getAntiPatternById(id) {
|
|
27133
27493
|
return UPG_ANTI_PATTERNS.find((ap) => ap.id === id);
|
|
27134
27494
|
}
|
|
@@ -27171,7 +27531,14 @@ function concernGatesFor(kind, concern) {
|
|
|
27171
27531
|
var COVERAGE_ANTI_PATTERNS = /* @__PURE__ */ new Set([
|
|
27172
27532
|
"single-domain-graph",
|
|
27173
27533
|
"persona-count-below-stage-benchmark",
|
|
27174
|
-
"competitors-missing-past-validation"
|
|
27534
|
+
"competitors-missing-past-validation",
|
|
27535
|
+
// surface companions (0.27.0): both presuppose the graph has grown past the
|
|
27536
|
+
// point where a place is worth justifying and measuring. A stub that has
|
|
27537
|
+
// sketched one surface has not yet drifted. `contended-surface-without-
|
|
27538
|
+
// arbitration` is deliberately NOT here: it only fires once features actually
|
|
27539
|
+
// occupy surfaces, which is itself the evidence that the graph has grown.
|
|
27540
|
+
"surface-without-job",
|
|
27541
|
+
"surface-without-measurement"
|
|
27175
27542
|
]);
|
|
27176
27543
|
var THIN_GRAPH_THRESHOLD = 8;
|
|
27177
27544
|
function isThinCoverageAdvisory(antiPatternId, totalEntityCount) {
|
|
@@ -27269,7 +27636,16 @@ function compareNumber(value, comparison, threshold) {
|
|
|
27269
27636
|
function evaluateEntityCount(check, inputs) {
|
|
27270
27637
|
const filter = check.filter;
|
|
27271
27638
|
let count = 0;
|
|
27272
|
-
if (filter && typeof filter.property === "string" && typeof filter.
|
|
27639
|
+
if (filter && typeof filter.property === "string" && typeof filter.present === "boolean" && typeof filter.except_property === "string" && typeof filter.except_value === "string") {
|
|
27640
|
+
const key = presenceExceptKey(filter.property, filter.except_property, filter.except_value);
|
|
27641
|
+
const exempt = inputs.countsByTypeAndProperty?.[check.entity_type]?.[filter.except_property]?.[filter.except_value] ?? 0;
|
|
27642
|
+
const eligible = Math.max(0, (inputs.countsByType[check.entity_type] ?? 0) - exempt);
|
|
27643
|
+
const withValue = inputs.countsByTypeAndPropertyPresenceExcept?.[check.entity_type]?.[key] ?? 0;
|
|
27644
|
+
count = filter.present ? Math.min(withValue, eligible) : Math.max(0, eligible - withValue);
|
|
27645
|
+
} else if (filter && typeof filter.property === "string" && typeof filter.present === "boolean") {
|
|
27646
|
+
const withValue = inputs.countsByTypeAndPropertyPresence?.[check.entity_type]?.[filter.property] ?? 0;
|
|
27647
|
+
count = filter.present ? withValue : Math.max(0, (inputs.countsByType[check.entity_type] ?? 0) - withValue);
|
|
27648
|
+
} else if (filter && typeof filter.property === "string" && typeof filter.value === "string") {
|
|
27273
27649
|
count = inputs.countsByTypeAndProperty?.[check.entity_type]?.[filter.property]?.[filter.value] ?? 0;
|
|
27274
27650
|
} else if (filter && typeof filter.status === "string") {
|
|
27275
27651
|
count = inputs.countsByTypeAndStatus?.[check.entity_type]?.[filter.status] ?? 0;
|
|
@@ -28174,6 +28550,10 @@ var UPG_REGIONS = [
|
|
|
28174
28550
|
type: "screen_state",
|
|
28175
28551
|
role: "leaf"
|
|
28176
28552
|
},
|
|
28553
|
+
{
|
|
28554
|
+
type: "surface",
|
|
28555
|
+
role: "container"
|
|
28556
|
+
},
|
|
28177
28557
|
{
|
|
28178
28558
|
type: "wireframe",
|
|
28179
28559
|
role: "leaf"
|
|
@@ -28282,7 +28662,9 @@ var UPG_REGIONS = [
|
|
|
28282
28662
|
"user_journey_contains_journey_step",
|
|
28283
28663
|
"design_system_contains_design_component",
|
|
28284
28664
|
"brand_identity_coloured_with_brand_colour",
|
|
28285
|
-
"design_component_composes_design_component"
|
|
28665
|
+
"design_component_composes_design_component",
|
|
28666
|
+
"screen_renders_surface",
|
|
28667
|
+
"surface_contains_surface"
|
|
28286
28668
|
],
|
|
28287
28669
|
boundary_edges: [
|
|
28288
28670
|
{
|
|
@@ -28290,6 +28672,16 @@ var UPG_REGIONS = [
|
|
|
28290
28672
|
edge_id: "need_reframed_as_design_question",
|
|
28291
28673
|
crosses_into: "users_needs"
|
|
28292
28674
|
},
|
|
28675
|
+
{
|
|
28676
|
+
direction: "import",
|
|
28677
|
+
edge_id: "feature_occupies_surface",
|
|
28678
|
+
crosses_into: "product_delivery"
|
|
28679
|
+
},
|
|
28680
|
+
{
|
|
28681
|
+
direction: "export",
|
|
28682
|
+
edge_id: "surface_serves_job",
|
|
28683
|
+
crosses_into: "users_needs"
|
|
28684
|
+
},
|
|
28293
28685
|
{
|
|
28294
28686
|
direction: "import",
|
|
28295
28687
|
edge_id: "opportunity_explores_via_design_concept",
|
|
@@ -30371,7 +30763,7 @@ function checkHeaderSealText(text) {
|
|
|
30371
30763
|
}
|
|
30372
30764
|
|
|
30373
30765
|
// src/index.ts
|
|
30374
|
-
var UPG_VERSION = "0.
|
|
30766
|
+
var UPG_VERSION = "0.28.0";
|
|
30375
30767
|
var UPG_FORMAT_VERSION = "0.4.0";
|
|
30376
30768
|
var MARKDOWN_FORMAT_VERSION = "0.1";
|
|
30377
30769
|
var UPG_TYPES = getTypes();
|
|
@@ -30510,6 +30902,7 @@ var UPG_META_COUNT = UPG_ENTITY_META.length;
|
|
|
30510
30902
|
UPG_POLYMORPHIC_EDGE_KEYS,
|
|
30511
30903
|
UPG_PORTFOLIO_KINDS,
|
|
30512
30904
|
UPG_PORTFOLIO_SHARED_TYPES,
|
|
30905
|
+
UPG_PRESENCE_EXCEPT_SPECS,
|
|
30513
30906
|
UPG_PRODUCT_STAGES,
|
|
30514
30907
|
UPG_PRODUCT_STAGE_COERCION_MAP,
|
|
30515
30908
|
UPG_PROPERTY_MIGRATIONS,
|
|
@@ -30646,6 +31039,7 @@ var UPG_META_COUNT = UPG_ENTITY_META.length;
|
|
|
30646
31039
|
normalizeDocument,
|
|
30647
31040
|
parseUpg,
|
|
30648
31041
|
pickCanonicalEdge,
|
|
31042
|
+
presenceExceptKey,
|
|
30649
31043
|
resolveAllEdges,
|
|
30650
31044
|
resolveContainmentEdge,
|
|
30651
31045
|
resolveEntityType,
|