Updates example to use theme colors and adds border radius

This commit is contained in:
martinalong 2025-12-12 22:15:50 -08:00
commit 2fa243ae8c
No known key found for this signature in database
4 changed files with 212 additions and 98 deletions

View file

@ -1,42 +1,45 @@
/* Default fallback values for host style variables */
:root {
--color-bg: #ffffff;
--color-text: #1f2937;
--color-text-muted: #6b7280;
--color-primary: #2563eb;
--color-primary-hover: #1d4ed8;
--color-card-bg: #f9fafb;
--color-border: #e5e7eb;
--color-enterprise: #1e40af;
--color-midmarket: #0d9488;
--color-smb: #059669;
--color-startup: #6366f1;
}
color-scheme: light dark;
@media (prefers-color-scheme: dark) {
:root {
--color-bg: #111827;
--color-text: #f9fafb;
--color-text-muted: #9ca3af;
--color-primary: #3b82f6;
--color-primary-hover: #60a5fa;
--color-card-bg: #1f2937;
--color-border: #374151;
--color-enterprise: #3b82f6;
--color-midmarket: #14b8a6;
--color-smb: #10b981;
--color-startup: #818cf8;
}
/* Background colors */
--color-background-primary: light-dark(#ffffff, #111827);
--color-background-secondary: light-dark(#f9fafb, #1f2937);
--color-background-tertiary: light-dark(#f3f4f6, #374151);
/* Text colors */
--color-text-primary: light-dark(#1f2937, #f9fafb);
--color-text-secondary: light-dark(#6b7280, #9ca3af);
--color-text-tertiary: light-dark(#9ca3af, #6b7280);
/* Border colors */
--color-border-primary: light-dark(#e5e7eb, #374151);
--color-border-secondary: light-dark(#d1d5db, #4b5563);
/* Accent colors */
--color-accent-info: light-dark(#2563eb, #3b82f6);
/* Border radius */
--border-radius-small: 4px;
--border-radius-medium: 8px;
/* App-specific colors (not from host) */
--color-enterprise: light-dark(#1e40af, #3b82f6);
--color-midmarket: light-dark(#0d9488, #14b8a6);
--color-smb: light-dark(#059669, #10b981);
--color-startup: light-dark(#6366f1, #818cf8);
}
html, body {
margin: 0;
padding: 0;
background: var(--color-bg);
color: var(--color-text);
color: var(--color-text-primary);
overflow: hidden;
}
.main {
border-radius: var(--border-radius-medium);
background: var(--color-background-primary);
width: 100%;
height: 600px;
margin: 0 auto;
@ -85,21 +88,21 @@ html, body {
gap: 6px;
font-size: 0.8125rem;
font-weight: 500;
color: var(--color-text-muted);
color: var(--color-text-secondary);
}
.select {
padding: 4px 8px;
font-size: 0.8125rem;
border: 1px solid var(--color-border);
border-radius: 4px;
background: var(--color-card-bg);
color: var(--color-text);
border: 1px solid var(--color-border-primary);
border-radius: var(--border-radius-small);
background: var(--color-background-secondary);
color: var(--color-text-primary);
cursor: pointer;
}
.select:focus {
outline: 2px solid var(--color-primary);
outline: 2px solid var(--color-accent-info);
outline-offset: 1px;
}
@ -107,10 +110,10 @@ html, body {
.chart-section {
flex: 1;
min-height: 0;
background: var(--color-card-bg);
border-radius: 8px;
background: var(--color-background-secondary);
border-radius: var(--border-radius-medium);
padding: 8px;
border: 1px solid var(--color-border);
border: 1px solid var(--color-border-primary);
}
.chart-container {
@ -141,13 +144,13 @@ html, body {
cursor: pointer;
padding: 4px 10px;
border-radius: 16px;
border: 1px solid var(--color-border);
background: var(--color-card-bg);
border: 1px solid var(--color-border-primary);
background: var(--color-background-secondary);
transition: all 0.15s ease;
}
.legend-item:hover {
border-color: var(--color-text-muted);
border-color: var(--color-text-secondary);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
@ -171,7 +174,7 @@ html, body {
}
.legend-count {
color: var(--color-text-muted);
color: var(--color-text-secondary);
}
/* Detail section - ~44px */
@ -186,15 +189,15 @@ html, body {
justify-content: center;
gap: 16px;
height: 100%;
background: var(--color-card-bg);
border-radius: 6px;
background: var(--color-background-secondary);
border-radius: var(--border-radius-small);
padding: 0 12px;
border: 1px solid var(--color-border);
border: 1px solid var(--color-border-primary);
font-size: 0.8125rem;
}
.detail-placeholder {
color: var(--color-text-muted);
color: var(--color-text-secondary);
}
.detail-name {
@ -203,7 +206,7 @@ html, body {
.detail-segment {
padding: 2px 8px;
border-radius: 4px;
border-radius: var(--border-radius-small);
font-size: 0.75rem;
font-weight: 500;
color: white;
@ -215,10 +218,10 @@ html, body {
.detail-segment.startup { background: var(--color-startup); }
.detail-metric {
color: var(--color-text-muted);
color: var(--color-text-secondary);
}
.detail-metric strong {
color: var(--color-text);
color: var(--color-text-primary);
font-weight: 600;
}

View file

@ -1,7 +1,12 @@
/**
* @file Customer Segmentation Explorer - interactive scatter/bubble visualization
*/
import { App, PostMessageTransport } from "@modelcontextprotocol/ext-apps";
import {
App,
PostMessageTransport,
applyHostStyles,
applyDocumentTheme,
} from "@modelcontextprotocol/ext-apps";
import { Chart, registerables } from "chart.js";
import "./global.css";
import "./mcp-app.css";
@ -138,11 +143,40 @@ function buildDatasets(): Chart["data"]["datasets"] {
});
}
// Hidden element for resolving CSS color values (reused to avoid DOM thrashing)
let colorResolver: HTMLDivElement | null = null;
// Resolve a CSS color value (handles light-dark() function)
function resolveColor(cssValue: string, fallback: string): string {
if (!cssValue) return fallback;
// If it's a simple color value, return it directly
if (!cssValue.includes("light-dark(")) return cssValue;
// Create resolver element once and keep it hidden
if (!colorResolver) {
colorResolver = document.createElement("div");
colorResolver.style.position = "absolute";
colorResolver.style.visibility = "hidden";
colorResolver.style.pointerEvents = "none";
document.body.appendChild(colorResolver);
}
colorResolver.style.color = cssValue;
return getComputedStyle(colorResolver).color || fallback;
}
// Get colors from CSS variables
function getChartColors(): { textColor: string; gridColor: string } {
const style = getComputedStyle(document.documentElement);
const rawTextColor = style.getPropertyValue("--color-text-secondary").trim();
const rawGridColor = style.getPropertyValue("--color-border-primary").trim();
return {
textColor: resolveColor(rawTextColor, "#6b7280"),
gridColor: resolveColor(rawGridColor, "#e5e7eb"),
};
}
// Initialize Chart.js
function initChart(): Chart {
const isDarkMode = window.matchMedia("(prefers-color-scheme: dark)").matches;
const textColor = isDarkMode ? "#9ca3af" : "#6b7280";
const gridColor = isDarkMode ? "#374151" : "#e5e7eb";
const { textColor, gridColor } = getChartColors();
return new Chart(chartCanvas, {
type: "bubble",
@ -243,8 +277,7 @@ function initChart(): Chart {
function updateChart(): void {
if (!state.chart) return;
const isDarkMode = window.matchMedia("(prefers-color-scheme: dark)").matches;
const textColor = isDarkMode ? "#9ca3af" : "#6b7280";
const { textColor, gridColor } = getChartColors();
state.chart.data.datasets = buildDatasets();
@ -252,11 +285,13 @@ function updateChart(): void {
const scales = state.chart.options.scales as {
x: {
title: { text: string; color: string };
ticks: { callback: (value: number) => string };
ticks: { color: string; callback: (value: number) => string };
grid: { color: string };
};
y: {
title: { text: string; color: string };
ticks: { callback: (value: number) => string };
ticks: { color: string; callback: (value: number) => string };
grid: { color: string };
};
};
@ -264,8 +299,12 @@ function updateChart(): void {
scales.y.title.text = METRIC_LABELS[state.yAxis];
scales.x.title.color = textColor;
scales.y.title.color = textColor;
scales.x.ticks.color = textColor;
scales.y.ticks.color = textColor;
scales.x.ticks.callback = (value: number) => formatValue(value, state.xAxis);
scales.y.ticks.callback = (value: number) => formatValue(value, state.yAxis);
scales.x.grid.color = gridColor;
scales.y.grid.color = gridColor;
state.chart.update();
}
@ -388,20 +427,52 @@ document.addEventListener("click", (e) => {
}
});
// Handle theme changes
// Handle system theme changes (fallback when host doesn't provide styles)
window
.matchMedia("(prefers-color-scheme: dark)")
.addEventListener("change", () => {
if (state.chart) {
state.chart.destroy();
state.chart = initChart();
.addEventListener("change", (e) => {
// Only apply if we haven't received host theme
if (!app.getHostContext()?.theme) {
applyDocumentTheme(e.matches ? "dark" : "light");
if (state.chart) {
state.chart.destroy();
state.chart = initChart();
}
}
});
// Apply initial theme based on system preference (before host context is available)
const systemDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
applyDocumentTheme(systemDark ? "dark" : "light");
// Register handlers and connect
app.onerror = log.error;
app.connect(new PostMessageTransport(window.parent));
// Handle host context changes (theme and styles from host)
app.onhostcontextchanged = (params) => {
if (params.theme) {
applyDocumentTheme(params.theme);
}
if (params.styles) {
applyHostStyles(params.styles);
}
// Recreate chart to pick up new colors
if (state.chart && (params.theme || params.styles)) {
state.chart.destroy();
state.chart = initChart();
}
};
app.connect(new PostMessageTransport(window.parent)).then(() => {
// Apply initial host context after connection
const ctx = app.getHostContext();
if (ctx?.theme) {
applyDocumentTheme(ctx.theme);
}
if (ctx?.styles) {
applyHostStyles(ctx.styles);
}
});
// Fetch data after connection
setTimeout(fetchData, 100);

View file

@ -154,11 +154,12 @@ interface UIResourceMeta {
/**
* Visual boundary preference
*
* Boolean indicating the UI prefers a visible border. Useful for widgets
* that might blend with host background.
* Boolean controlling whether a visible border and background is provided by the host. Specifying an
* explicit value for this is recommended because hosts' defaults may vary.
*
* - `true`: Request visible border (host decides styling)
* - `false` or omitted: No preference
* - `true`: Request visible border + background
* - `false`: Request no visible border + background
* - omitted: host decides border
*/
prefersBorder?: boolean,
}
@ -391,9 +392,8 @@ UI iframes can use the following subset of standard MCP protocol messages:
### Host Context in `McpUiInitializeResult`
When the Guest UI sends an `ui/initialize` request to the Host, the Host SHOULD include UI-specific context in the `McpUiInitializeResult`’s `hostContext` field:
When the Guest UI sends an `ui/initialize` request to the Host, the Host SHOULD include UI-specific context in the `McpUiInitializeResult`'s `hostContext` field:
```typescript
interface HostContext {
/** Metadata of the tool call that instantiated the App */
toolInfo?: {
@ -405,7 +405,7 @@ interface HostContext {
/** Current color theme preference */
theme?: "light" | "dark";
/** CSS variables for theming. See Theming section for standardized variable names. */
styles?: Record<string, string>;
styles?: Record<McpUiStyleVariableKey, string | undefined>;
/** How the UI is currently displayed */
displayMode?: "inline" | "fullscreen" | "pip";
/** Display modes the host supports */
@ -472,32 +472,72 @@ Hosts can optionally pass CSS custom properties via `HostContext.styles` for vis
#### Current Standardized Variables
| Category | Variables |
|----------|-----------|
| Background | `--color-background-primary`, `--color-background-secondary`, `--color-background-tertiary`, `--color-background-inverted` |
| Text | `--color-text-primary`, `--color-text-secondary`, `--color-text-tertiary`, `--color-text-inverted` |
| Icons | `--color-icon-primary`, `--color-icon-secondary`, `--color-icon-tertiary`, `--color-icon-inverted` |
| Borders | `--color-border-primary`, `--color-border-secondary` |
| Accents | `--color-accent-info`, `--color-accent-danger`, `--color-accent-success`, `--color-accent-warning` |
| Font Family | `--font-family-sans` |
| Font Sizes | `--font-size-heading`, `--font-size-body`, `--font-size-caption` |
| Font Weights | `--font-weight-regular`, `--font-weight-emphasized` |
| Line Heights | `--font-leading-regular`, `--font-leading-tight` |
| Composite Styles | `--font-style-heading`, `--font-style-body`, `--font-style-body-emphasized`, `--font-style-caption`, `--font-style-caption-emphasized` |
| Border Radius | `--border-radius-small`, `--border-radius-medium`, `--border-radius-large`, `--border-radius-full` |
| Border Width | `--border-width-regular` |
```typescript
/** CSS variable keys available to MCP apps for theming. */
type McpUiStyleVariableKey =
// Background colors
| "--color-background-primary"
| "--color-background-secondary"
| "--color-background-tertiary"
| "--color-background-inverted"
// Text colors
| "--color-text-primary"
| "--color-text-secondary"
| "--color-text-tertiary"
| "--color-text-inverted"
// Icon colors
| "--color-icon-primary"
| "--color-icon-secondary"
| "--color-icon-tertiary"
| "--color-icon-inverted"
// Border colors
| "--color-border-primary"
| "--color-border-secondary"
// Accent colors
| "--color-accent-info"
| "--color-accent-danger"
| "--color-accent-success"
| "--color-accent-warning"
// Typography - Family
| "--font-family-sans"
// Typography - Size
| "--font-size-heading"
| "--font-size-body"
| "--font-size-caption"
// Typography - Weight
| "--font-weight-regular"
| "--font-weight-emphasized"
// Typography - Line height
| "--font-leading-regular"
| "--font-leading-tight"
// Typography - Composite styles
| "--font-style-heading"
| "--font-style-body"
| "--font-style-body-emphasized"
| "--font-style-caption"
| "--font-style-caption-emphasized"
// Border radius
| "--border-radius-small"
| "--border-radius-medium"
| "--border-radius-large"
| "--border-radius-full"
// Border width
| "--border-width-regular";
#### Host Behavior
- Hosts can provide any subset of standardized variables, or not pass `styles` at all. However, passing all of the standardized properties is recommended for cohesiveness
- Hosts can use the CSS `light-dark()` function for theme-aware values
- Hosts can provide any subset of standardized variables, or not pass `styles` at all
- However, unexpected clashes may occur if hosts pass some color variables but not others for example, since apps are instructed to fallback on their own default values for unspecified style variables
- Hosts should use the CSS `light-dark()` function for theme-aware values (i.e. light mode and dark mode colors)
#### App Behavior
- Apps should set default fallback values for CSS variables, to account for hosts who don't pass some or all style variables. This ensures graceful degradation when hosts omit `styles` or specific variables:
- Apps should set default fallback values for the set of these variables that they use, to account for hosts who don't pass some or all style variables. This ensures graceful degradation when hosts omit `styles` or specific variables:
```
:root {
--colorTextPrimary: #171717;
--color-text-primary: light-dark(#171717, #000000);
--border-radius-small: 8px;
...
}
```
- Apps can use the `applyHostStyles` utility (or `useHostStyles` if they prefer a React hook) to easily populate the host-provided CSS variables into their style sheet

View file

@ -1698,7 +1698,7 @@
"lastModified": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-]\\d{2}:\\d{2})))$"
}
},
"additionalProperties": false
@ -1745,7 +1745,7 @@
"lastModified": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-]\\d{2}:\\d{2})))$"
}
},
"additionalProperties": false
@ -1792,7 +1792,7 @@
"lastModified": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-]\\d{2}:\\d{2})))$"
}
},
"additionalProperties": false
@ -1866,7 +1866,7 @@
"lastModified": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-]\\d{2}:\\d{2})))$"
}
},
"additionalProperties": false
@ -1959,7 +1959,7 @@
"lastModified": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-]\\d{2}:\\d{2})))$"
}
},
"additionalProperties": false
@ -2670,7 +2670,7 @@
"lastModified": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-]\\d{2}:\\d{2})))$"
}
},
"additionalProperties": false
@ -2717,7 +2717,7 @@
"lastModified": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-]\\d{2}:\\d{2})))$"
}
},
"additionalProperties": false
@ -2764,7 +2764,7 @@
"lastModified": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-]\\d{2}:\\d{2})))$"
}
},
"additionalProperties": false
@ -2838,7 +2838,7 @@
"lastModified": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-]\\d{2}:\\d{2})))$"
}
},
"additionalProperties": false
@ -2931,7 +2931,7 @@
"lastModified": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-]\\d{2}:\\d{2})))$"
}
},
"additionalProperties": false