feat(foundry): close the operational map data loop

This commit is contained in:
Codex
2026-07-20 20:45:05 +03:00
parent aac44d057f
commit a02c3ff3dd
44 changed files with 4158 additions and 811 deletions
-10
View File
@@ -72,16 +72,6 @@
"requiredBehavior": ["selected state", "drag state", "assignees", "dates", "quick actions"],
"reasonNotBaseline": "The visual pattern is mature, but task-domain fields must be separated from the reusable card shell."
},
{
"id": "toast",
"classification": "missing-primitive",
"priority": "P2",
"referenceSources": ["task-manager"],
"expectedPackage": "@nodedc/ui-react",
"dependsOn": ["status-badge", "button"],
"requiredBehavior": ["stack", "timeout", "persistent error", "action", "screen-reader announcement"],
"reasonNotBaseline": "No cross-application contract has been audited yet."
},
{
"id": "tooltip",
"classification": "missing-primitive",
+22 -6
View File
@@ -5,16 +5,17 @@
"id": "glass-surface",
"status": "baseline",
"package": "@nodedc/ui-react",
"exports": ["GlassSurface", "GlassMaterialSurface"],
"domContract": ["nodedc-glass", "nodedc-glass-material", "nodedc-material-rim"],
"summary": "Matte application surfaces plus the canonical Engine V4 glass material for modal and Inspector shells.",
"anatomy": ["theme-provided surface", "modal tint", "gradient rim", "backdrop blur/saturation/brightness", "content"],
"variants": ["default", "strong", "soft"],
"exports": ["GlassSurface", "GlassMaterialSurface", "MapGlassSurface"],
"domContract": ["nodedc-glass", "nodedc-glass-material", "nodedc-map-glass", "nodedc-material-rim"],
"summary": "Matte application surfaces, the Engine V4 modal material and the light translucent surface used over Cesium imagery.",
"anatomy": ["theme-provided surface", "modal tint", "map-only light translucency", "gradient rim", "backdrop blur/saturation/brightness", "content"],
"variants": ["default", "strong", "soft", "map"],
"rules": [
"Geometry is invariant across application themes.",
"A material rim is a glass highlight, not a hard product-colored border.",
"Nested surfaces must use a softer tone to avoid card-inside-card noise.",
"GlassyMaterialSurface is restricted to modal Windows and the draggable Inspector."
"GlassyMaterialSurface is restricted to modal Windows and the draggable Inspector.",
"MapGlassSurface is restricted to overlays over Cesium/map imagery and must match the map Toolbar translucency."
]
},
{
@@ -337,6 +338,21 @@
"domContract": ["nodedc-status"],
"summary": "Semantic status pill whose tone is independent from the application accent."
},
{
"id": "toast",
"status": "baseline",
"package": "@nodedc/ui-react",
"exports": ["ToastCard", "ToastStack", "ToastItem", "ToastTone"],
"domContract": ["nodedc-toast-viewport", "nodedc-toast", "nodedc-toast__icon", "nodedc-toast__copy"],
"summary": "Tasker-derived non-blocking status notification with a bottom-right glass stack.",
"variants": ["success", "error", "warning", "info", "loading"],
"behavior": ["controlled items", "optional auto-dismiss", "manual dismiss", "portal viewport", "polite live region"],
"rules": [
"Applications own operation state and message copy; ToastStack owns viewport geometry and dismissal timing.",
"Loading notifications remain until the operation updates or dismisses them.",
"Status notifications never replace a blocking confirmation modal."
]
},
{
"id": "drag-drop",
"status": "baseline",
@@ -14,6 +14,39 @@
"no_position"
],
"removeMode": "canonical-tombstone-or-snapshot-rebase"
},
{
"id": "map-moving-object-current-v2",
"version": "2.0.0",
"dataProductId": "fleet.positions.current.v2",
"productVersion": "2.0.0",
"staleAfterMs": 60000,
"terminalStatuses": [
"inactive",
"no-position",
"no_position"
],
"removeMode": "canonical-tombstone-or-snapshot-rebase"
},
{
"id": "map-moving-object-current-v4",
"version": "4.0.0",
"dataProductId": "fleet.positions.current.v4",
"productVersion": "4.0.0",
"staleAfterMs": null,
"terminalStatuses": [
"inactive"
],
"statusContract": {
"attribute": "signal_state",
"allowedValues": [
"active",
"inactive"
],
"missing": "reject",
"freshness": "none"
},
"removeMode": "canonical-tombstone-or-snapshot-rebase"
}
]
}
@@ -1,148 +0,0 @@
{
"schemaVersion": "0.1.0",
"fixtureId": "map.operational",
"template": { "id": "map", "version": "0.1.0" },
"title": "Operational spatial scene",
"description": "Minimal provider-neutral scene covering the reusable spatial entities already proven in NODE.DC Engine.",
"state": "ready",
"viewport": {
"center": [37.6176, 55.7558, 250],
"heading": 15,
"pitch": -42,
"range": 18000
},
"capabilities": ["camera", "base-layer", "buildings", "grid", "labels", "pins", "moving-objects", "traces", "routes", "tracks", "stations", "zones", "selection"],
"styleProfiles": [
{ "id": "layer.base.neutral", "kind": "layer", "variant": "neutral-dark", "opacity": 1 },
{ "id": "layer.buildings.default", "kind": "layer", "variant": "extruded-neutral", "opacity": 0.86 },
{ "id": "grid.planetary.major", "kind": "grid", "variant": "major", "color": "#8f72dc", "opacity": 0.48, "width": 1 },
{ "id": "label.place.large", "kind": "label", "variant": "place-large", "color": "#ffffff", "opacity": 1, "size": 28 },
{ "id": "label.entity.compact", "kind": "label", "variant": "entity-compact", "color": "#ffffff", "opacity": 0.92, "size": 13 },
{ "id": "label.station.medium", "kind": "label", "variant": "station-medium", "color": "#ffffff", "opacity": 0.95, "size": 16 },
{
"id": "pin.transport.active",
"kind": "pin",
"variant": "transport-active",
"color": "#ff2f92",
"opacity": 1,
"size": 8,
"pinPresentation": {
"variant": "elevated-spike",
"stemHeightMeters": 120,
"headSizePx": 8,
"stemWidthPx": 2,
"outlineColor": "#0c0d12",
"outlineOpacity": 0.6,
"outlineWidthPx": 1,
"labelOffsetX": 10,
"labelOffsetY": 0,
"pinHideCameraHeightMeters": 120000,
"labelHideCameraHeightMeters": 85000
}
},
{ "id": "pin.station.metro", "kind": "pin", "variant": "station-metro", "color": "#8f72dc", "opacity": 1, "size": 26 },
{ "id": "pin.station.rail", "kind": "pin", "variant": "station-rail", "color": "#ffffff", "opacity": 1, "size": 22 },
{ "id": "line.route.primary", "kind": "line", "variant": "route-primary", "color": "#ff2f92", "opacity": 0.9, "width": 4 },
{ "id": "line.track.rail", "kind": "line", "variant": "track-rail", "color": "#8f72dc", "opacity": 0.8, "width": 2 },
{ "id": "fill.zone.operational", "kind": "fill", "variant": "operational-zone", "color": "#ff2f92", "opacity": 0.22 }
],
"scene": {
"layers": [
{ "id": "base.default", "type": "base", "visible": true, "mode": "surface", "styleProfileId": "layer.base.neutral" },
{ "id": "buildings.default", "type": "buildings", "visible": true, "mode": "volume", "styleProfileId": "layer.buildings.default", "visibility": { "minRange": 0, "maxRange": 70000 } },
{
"id": "grid.planetary",
"type": "grid",
"visible": true,
"mode": "graticule",
"styleProfileId": "grid.planetary.major",
"lodLevels": [
{ "level": 0, "minRange": 3000000, "maxRange": 50000000 },
{ "level": 1, "minRange": 700000, "maxRange": 3000000 },
{ "level": 2, "minRange": 180000, "maxRange": 700000 },
{ "level": 3, "minRange": 40000, "maxRange": 180000 },
{ "level": 4, "minRange": 0, "maxRange": 40000 }
]
}
],
"places": [
{
"id": "place.moscow",
"name": "Москва",
"position": [37.6176, 55.7558, 220],
"label": { "text": "МОСКВА", "styleProfileId": "label.place.large" },
"visibility": { "minRange": 100000, "maxRange": 50000000 }
}
],
"movingObjects": [
{
"id": "vehicle.tram.017",
"objectType": "tram",
"status": "active",
"position": [37.6242, 55.7604, 4],
"heading": 124,
"pinStyleProfileId": "pin.transport.active",
"label": { "text": "ТМ 17", "styleProfileId": "label.entity.compact" },
"trace": [[37.615, 55.756, 4], [37.619, 55.758, 4], [37.6242, 55.7604, 4]]
},
{
"id": "vehicle.rail.mcd2",
"objectType": "rail",
"status": "delayed",
"position": [37.579, 55.769, 4],
"heading": 82,
"pinStyleProfileId": "pin.transport.active",
"label": { "text": "МЦД-2 · +3 мин", "styleProfileId": "label.entity.compact" }
}
],
"stations": [
{
"id": "station.metro.tverskaya",
"stationType": "metro",
"position": [37.604, 55.7647, -20],
"pinStyleProfileId": "pin.station.metro",
"label": { "text": "Тверская", "styleProfileId": "label.station.medium" }
},
{
"id": "station.rail.rizhskaya",
"stationType": "rail",
"position": [37.6328, 55.7927, 4],
"pinStyleProfileId": "pin.station.rail",
"label": { "text": "Рижская", "styleProfileId": "label.station.medium" }
}
],
"routes": [
{
"id": "route.tram.017",
"coordinates": [[37.595, 55.748, 4], [37.607, 55.754, 4], [37.6242, 55.7604, 4], [37.64, 55.772, 4]],
"styleProfileId": "line.route.primary",
"label": { "text": "Маршрут 17", "styleProfileId": "label.entity.compact" }
}
],
"tracks": [
{
"id": "track.rail.north-east",
"coordinates": [[37.579, 55.769, 4], [37.606, 55.781, 4], [37.6328, 55.7927, 4]],
"styleProfileId": "line.track.rail"
}
],
"zones": [
{
"id": "zone.depot.control",
"geometry": {
"type": "Polygon",
"coordinates": [[[37.61, 55.752], [37.632, 55.752], [37.632, 55.765], [37.61, 55.765], [37.61, 55.752]]]
},
"styleProfileId": "fill.zone.operational",
"label": { "text": "Операционная зона", "styleProfileId": "label.entity.compact" }
}
]
},
"selection": { "entityId": "vehicle.tram.017" },
"acceptance": [
{ "id": "scene.provider-neutral", "expect": ["Scene contains no renderer credentials or provider-specific object types."] },
{ "id": "scene.shared-label-language", "expect": ["Places, vehicles, stations, routes and zones resolve labels through shared style profiles."] },
{ "id": "scene.grid-lod", "expect": ["Grid changes density through five non-overlapping range bands without disappearing at a boundary."] },
{ "id": "scene.selection", "expect": ["Selection resolves to an existing scene entity and can open the canonical Inspector."] }
]
}
+107
View File
@@ -0,0 +1,107 @@
{
"schemaVersion": "nodedc.map-presentation-profiles/v1",
"profiles": [
{
"id": "map.moving-object.operational.default",
"version": "1.3.0",
"title": "Флот",
"semanticTypes": ["map.moving_object"],
"label": {
"mode": "subject_id",
"fields": ["display_name", "label", "name", "title"],
"fontWeight": 600,
"sizePx": 18,
"color": "#f5f5f5",
"outlineColor": "#0c0d12",
"outlineWidthPx": 1,
"backgroundColor": "#0c0d12",
"backgroundOpacity": 0.86,
"paddingX": 8,
"paddingY": 5,
"maxLength": 48,
"offsetX": 15,
"offsetY": 10,
"hideCameraHeightMeters": 70000
},
"target": {
"variant": "elevated-spike",
"stemHeightMeters": 1500,
"headSizePx": 9,
"stemWidthPx": 5,
"outlineColor": "#0c0d12",
"outlineOpacity": 0.6,
"outlineWidthPx": 1,
"hideCameraHeightMeters": 100000
},
"facets": [
{
"id": "availability",
"field": "availability_state",
"label": "Связь",
"filterable": true,
"counter": true,
"values": [
{ "value": "online", "label": "Онлайн", "order": 0 },
{ "value": "offline", "label": "Офлайн", "order": 1 }
]
},
{
"id": "motion",
"field": "motion_state",
"label": "Движение",
"filterable": true,
"counter": true,
"values": [
{ "value": "moving", "label": "В движении", "order": 0 },
{ "value": "stationary", "label": "Стоят", "order": 1 }
]
}
],
"styles": [
{ "id": "online", "color": "#86fdb8", "opacity": 0.9 },
{ "id": "offline", "color": "#c2c2c2", "opacity": 0.7 },
{ "id": "moving", "color": "#6fb5fb", "opacity": 0.9 },
{ "id": "stationary", "color": "#ffd485", "opacity": 0.9 }
],
"classes": [
{
"id": "availability-offline",
"label": "Офлайн",
"priority": 400,
"match": [{ "field": "availability_state", "equals": "offline" }],
"styleId": "offline",
"renderable": true
},
{
"id": "motion-moving",
"label": "В движении",
"priority": 300,
"match": [{ "field": "motion_state", "equals": "moving" }],
"styleId": "moving",
"renderable": true
},
{
"id": "motion-stationary",
"label": "Стоят",
"priority": 200,
"match": [{ "field": "motion_state", "equals": "stationary" }],
"styleId": "stationary",
"renderable": true
},
{
"id": "availability-online",
"label": "Онлайн",
"priority": 100,
"match": [{ "field": "availability_state", "equals": "online" }],
"styleId": "online",
"renderable": true
}
],
"defaultClassId": "motion-stationary",
"sort": [
{ "field": "availability_state", "order": ["online", "offline"] },
{ "field": "motion_state", "order": ["moving", "stationary"] }
]
}
]
}
-1
View File
@@ -36,7 +36,6 @@
"contracts": {
"fixtureSchema": "registry/schemas/map-scene-fixture-v0.1.schema.json",
"acceptanceFixtures": [
"registry/fixtures/map/map-operational-v0.1.json",
"registry/fixtures/map/map-empty-offline-v0.1.json"
],
"lodPolicySchema": "registry/schemas/map-lod-policy-v0.1.schema.json",
+1
View File
@@ -54,5 +54,6 @@
"designProfileSchema": "schemas/design-profile-v0.1.schema.json",
"mapSceneFixtureSchema": "schemas/map-scene-fixture-v0.1.schema.json",
"mapLodPolicySchema": "schemas/map-lod-policy-v0.1.schema.json",
"mapPresentationProfileRegistry": "map-presentation-profiles.json",
"operatingRule": "Check this registry before creating a NODE.DC interface element. Reuse or extend the canonical export instead of creating an application-local copy."
}
@@ -64,6 +64,91 @@
"features": {
"type": "object",
"additionalProperties": { "type": "boolean" }
},
"layout": {
"type": "object",
"additionalProperties": false,
"properties": {
"map": {
"type": "object",
"properties": {
"dataProductBindings": {
"type": "array",
"maxItems": 64,
"items": {
"type": "object",
"properties": {
"id": { "type": "string", "minLength": 1 },
"displayName": { "type": "string", "minLength": 1, "maxLength": 120 },
"order": { "type": "integer", "minimum": 0, "maximum": 10000 }
}
}
},
"subjectStates": {
"type": "array",
"maxItems": 64,
"items": {
"type": "object",
"additionalProperties": false,
"required": ["bindingId", "visible", "filters", "window"],
"properties": {
"bindingId": { "type": "string", "minLength": 1 },
"visible": { "type": "boolean" },
"filters": {
"type": "object",
"additionalProperties": {
"type": "array",
"uniqueItems": true,
"items": { "type": "string" }
}
},
"window": {
"type": "object",
"additionalProperties": false,
"required": ["open", "rect", "maximized", "zIndex"],
"properties": {
"open": { "type": "boolean" },
"rect": {
"type": "object",
"additionalProperties": false,
"required": ["x", "y", "width", "height"],
"properties": {
"x": { "type": "number" },
"y": { "type": "number" },
"width": { "type": "number", "minimum": 200 },
"height": { "type": "number", "minimum": 160 }
}
},
"maximized": { "type": "boolean" },
"zIndex": { "type": "integer", "minimum": 1 }
}
}
}
}
}
}
}
}
},
"designOverrides": {
"type": "object",
"additionalProperties": false,
"properties": {
"map": {
"type": "object",
"additionalProperties": false,
"properties": {
"settings": {
"type": "object",
"additionalProperties": { "type": ["string", "number", "boolean"] }
},
"presentationProfiles": {
"type": "array",
"items": { "type": "object" }
}
}
}
}
}
}
}
@@ -101,6 +101,12 @@
"lensCount": { "type": "integer", "minimum": 1, "maximum": 12 },
"autoHide": { "type": "boolean" }
}
},
"pageTypes": {
"type": "object",
"maxProperties": 64,
"propertyNames": { "pattern": "^[a-z0-9-]+@[0-9]+\\.[0-9]+\\.[0-9]+$" },
"additionalProperties": { "$ref": "#/$defs/mapPageFragment" }
}
}
},
@@ -129,6 +135,25 @@
"fieldOpacity": { "$ref": "#/$defs/percent" },
"nestedHex": { "$ref": "#/$defs/hex" }
}
},
"mapPageFragment": {
"type": "object",
"additionalProperties": false,
"required": ["schemaVersion", "templateId", "templateVersion", "settings", "presentationProfiles"],
"properties": {
"schemaVersion": { "const": 1 },
"templateId": { "const": "map" },
"templateVersion": { "type": "string", "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$" },
"settings": {
"type": "object",
"minProperties": 1,
"additionalProperties": { "type": ["string", "number", "boolean"] }
},
"presentationProfiles": {
"type": "array",
"items": { "type": "object" }
}
}
}
}
}