A place in the UI, its occupants, and the rule that decides who wins.
A surface is a place in the user interface, together with the things that occupy it and the rule that decides who wins when two of them want it at once. It is the shell, the tool, the pane, the region, the slot, the gutter, the action bar, the overlay, or the ambient rail: the named place featuresFeatureProduct SpecificationA product capability or featureView reference → compete for, one level finer than the route that renders it.
Windowing systems gave interface work its vocabulary of places. Windows, panes, and regions were things a person could see, move, and resize, and designers learned to talk about layout in those terms long before anyone wrote the arrangement down as a rule. A place was drawn once, in one file, by whoever was designing the screen that week.
Extensibility changed the stakes. Once a product published named places that plugins, integrations, or other teams could register into, the place stopped being a drawing and became a contract that several parties depend on. Design systemsDesign SystemDesign SystemThe root design system entityView reference → documented what those places should look like and how their contents should behave, which settled the visual half of the question. The other half, who wins when two features want the same place, mostly stayed in prose: decided in a review, remembered by the people who were in the room, and re-argued the next time a feature needed somewhere to live. Recording the place as an entity is what makes the guest list queryable and the arbitration rule durable.
A team building a document editor has one inspector pane on the right, 292 pixels wide and visible only while something is selected. Three features want it: properties, comments, and version history. Nothing says which one shows, so the behaviour is whatever the last person to touch the code arranged, and every new feature reopens the argument. The team models the pane as a surface: kind pane, persistence conditional, capacity 1, dimensional constraintConstraintStrategyA limit, requirement, or ceiling the product must respect, whether a self-imposed principle or an externally imposed boundaryView reference → 292px wide. Then they write the rule that was never written down, that the most recently opened panel wins and a pinned panel outranks selection until it is unpinned, into arbitration_rule.
Nothing about the interface changes that day. What changes is that the fourth feature to want the pane now arrives at an answer instead of an argument, and the question of which features contest which places becomes a query rather than an act of recollection.
In the Unified Product Graph, surface is a container in the Experience Design domain of the Experience, Design & Brand region. It is reached through the screen that renders it via ScreenrendersSurfacehierarchy and self-nests from there via screen_renders_surfaceSurfacecontainsSurfacehierarchy, so a shell holds panes, a pane holds regions, and a region holds slots. It is never a top-level product child; a surface hangs off the screen that mounts it. The most important edge points inward: surface_contains_surfaceFeatureoccupiesSurfacecross-domain is the guest list, the edge that makes contention countable. Outbound, a surface names its purpose with feature_occupies_surfaceSurfaceservesJobcross-domain, the rule that governs it with surface_serves_jobSurfacegoverned byDesign Guidelinecross-domain, what it draws with through surface_governed_by_design_guidelineSurfacerendersDesign Componenthierarchy, how it is judged through surface_renders_design_componentSurfacemeasured byMetricsemantic, its replacement through surface_measured_by_metricSurfacesupersedesSurfacesemantic, and the distance between the capacity it declares and what it actually holds through surface_supersedes_surfaceSurfacedeviates viaTechnical Debt Itemcross-domain. DecisionsDecisionStrategyA recorded decision with context, rationale, and consequencesView reference → reach it through surface_deviates_via_technical_debt_itemDecisionaffectsSurfacecross-domain and journeys through decision_affects_surfaceJourney Stepoccurs onSurfacesemantic. Its lifecycle is shared verbatim with screen (draft, in design, built, shipped, deprecated), so a screen and the surfaces inside it report progress on one vocabulary.journey_step_occurs_on_surface
Type-specific fields on BaseNode
surface_kindenumStructural kind. Determines what may legally nest inside this surface.
persistenceenumHow reliably the surface is present.
visibility_conditionstringWhen 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"
capacitynumberHow many occupants ONE INSTANCE of the surface holds at once, as a non-negative count. Absent means unbounded, `0` means a reserved place nothing may occupy. Always the cap the design INTENDS, never what was observed rendering. Count the instances themselves with `cardinality`.
Intent, not observation: a banner region declared `capacity: 1` keeps saying 1 even after someone finds it rendering four. Reality that has drifted from the declared intent belongs on `surface_deviates_via_technical_debt_item` as a trackable, assignable debt item. Editing this number up to match the bug would destroy the only record that a gap exists. Why absence rather than a sentinel: UPG has no union-typed property primitive, so `integer | unbounded` is expressed as an optional number whose absence carries the "no cap" reading, instead of a magic value every consumer would have to special-case. Three states, all different. ABSENT is unbounded, no cap stated. `0` is a reserved place. `null` is neither, and nothing this field defines: it is what an explicit null write leaves behind, and no consumer reads it as a cap. To return the property to absent, remove the key with `update_node`'s or `batch_update_nodes`' `unset_properties`, since a property merge preserves anything omitted. Unbounded is not an exemption from scrutiny. The contention detector reads an absent capacity as a threshold of 1, because a surface that states no limit has stated no answer either, so several occupants is exactly the unrecorded decision worth naming. Declaring a real capacity is the way to quiet the check honestly, and the only way that also records something true.
cardinalityenumHow many instances of this surface exist. `capacity` counts occupants within one instance; this counts the instances.
instance_scopeenumWhat 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.
composition_modeenumHow 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.
arbitration_rulestringWho wins when more occupants want the surface than `capacity` allows, and why. Absence is meaningful: on a contested surface it means nobody decided. @example "Highest priority wins; ties break to the most recently updated."
An empty or absent rule on a contested surface is exactly what `contended-surface-without-arbitration` detects. Do not fill it in with a placeholder to silence the check: the check exists to find the unrecorded decision, and a placeholder hides it without settling anything. The field is overloaded across composition modes, which is worth knowing before writing one. On an `exclusive` surface it records DISPLACEMENT (who is not rendered). On an `additive` surface everyone fits, so what it records is ORDER. Only the displacement reading is what the contention detector reads.
arbitration_stateenumWhether the arbitration answer is enforced, written down, both, neither, or not owed at all. Leaving the field unset means unassessed, and always did.
The separations are the whole point of the field, because absence alone conflates four situations with four different remediations: "enforced in code but never transcribed" is ten minutes of typing, "never decided" is a design meeting, "safe only because nothing has collided yet" is the dangerous one, and "nothing to decide, the occupants never compete" is a chained surface, which owes no arbitration rule at all.
extensibilityenumWho may add occupants to the surface.
mutates_contentbooleanWhether 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.
dimensional_constraintstringThe 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"
idstringrequiredUnique identifier (UUID)
typeNodeTyperequiredDiscriminator for the entity type
titlestringrequiredDisplay name
descriptionstringOptional detailed description
statusstringLifecycle status
tagsstring[]Freeform tags for filtering
5 phases, initial: draft
16 edge types connected to this entity.
surface_contains_surfacesurface_renders_design_componentsurface_serves_jobsurface_governed_by_design_guidelinesurface_measured_by_metricsurface_supersedes_surfacesurface_supersedes_surfacesurface_deviates_via_technical_debt_itemsurface_varies_by_configuration_axissurface_alternates_with_surfacesurface_alternates_with_surfacefeature_occupies_surfacedecision_affects_surfacejourney_step_occurs_on_surface