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

View file

@ -1,7 +1,12 @@
/** /**
* @file Customer Segmentation Explorer - interactive scatter/bubble visualization * @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 { Chart, registerables } from "chart.js";
import "./global.css"; import "./global.css";
import "./mcp-app.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 // Initialize Chart.js
function initChart(): Chart { function initChart(): Chart {
const isDarkMode = window.matchMedia("(prefers-color-scheme: dark)").matches; const { textColor, gridColor } = getChartColors();
const textColor = isDarkMode ? "#9ca3af" : "#6b7280";
const gridColor = isDarkMode ? "#374151" : "#e5e7eb";
return new Chart(chartCanvas, { return new Chart(chartCanvas, {
type: "bubble", type: "bubble",
@ -243,8 +277,7 @@ function initChart(): Chart {
function updateChart(): void { function updateChart(): void {
if (!state.chart) return; if (!state.chart) return;
const isDarkMode = window.matchMedia("(prefers-color-scheme: dark)").matches; const { textColor, gridColor } = getChartColors();
const textColor = isDarkMode ? "#9ca3af" : "#6b7280";
state.chart.data.datasets = buildDatasets(); state.chart.data.datasets = buildDatasets();
@ -252,11 +285,13 @@ function updateChart(): void {
const scales = state.chart.options.scales as { const scales = state.chart.options.scales as {
x: { x: {
title: { text: string; color: string }; title: { text: string; color: string };
ticks: { callback: (value: number) => string }; ticks: { color: string; callback: (value: number) => string };
grid: { color: string };
}; };
y: { y: {
title: { text: string; color: string }; 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.y.title.text = METRIC_LABELS[state.yAxis];
scales.x.title.color = textColor; scales.x.title.color = textColor;
scales.y.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.x.ticks.callback = (value: number) => formatValue(value, state.xAxis);
scales.y.ticks.callback = (value: number) => formatValue(value, state.yAxis); scales.y.ticks.callback = (value: number) => formatValue(value, state.yAxis);
scales.x.grid.color = gridColor;
scales.y.grid.color = gridColor;
state.chart.update(); 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 window
.matchMedia("(prefers-color-scheme: dark)") .matchMedia("(prefers-color-scheme: dark)")
.addEventListener("change", () => { .addEventListener("change", (e) => {
if (state.chart) { // Only apply if we haven't received host theme
state.chart.destroy(); if (!app.getHostContext()?.theme) {
state.chart = initChart(); 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 // Register handlers and connect
app.onerror = log.error; 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 // Fetch data after connection
setTimeout(fetchData, 100); setTimeout(fetchData, 100);

View file

@ -154,11 +154,12 @@ interface UIResourceMeta {
/** /**
* Visual boundary preference * Visual boundary preference
* *
* Boolean indicating the UI prefers a visible border. Useful for widgets * Boolean controlling whether a visible border and background is provided by the host. Specifying an
* that might blend with host background. * explicit value for this is recommended because hosts' defaults may vary.
* *
* - `true`: Request visible border (host decides styling) * - `true`: Request visible border + background
* - `false` or omitted: No preference * - `false`: Request no visible border + background
* - omitted: host decides border
*/ */
prefersBorder?: boolean, prefersBorder?: boolean,
} }
@ -391,9 +392,8 @@ UI iframes can use the following subset of standard MCP protocol messages:
### Host Context in `McpUiInitializeResult` ### 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 { interface HostContext {
/** Metadata of the tool call that instantiated the App */ /** Metadata of the tool call that instantiated the App */
toolInfo?: { toolInfo?: {
@ -405,7 +405,7 @@ interface HostContext {
/** Current color theme preference */ /** Current color theme preference */
theme?: "light" | "dark"; theme?: "light" | "dark";
/** CSS variables for theming. See Theming section for standardized variable names. */ /** 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 */ /** How the UI is currently displayed */
displayMode?: "inline" | "fullscreen" | "pip"; displayMode?: "inline" | "fullscreen" | "pip";
/** Display modes the host supports */ /** Display modes the host supports */
@ -472,32 +472,72 @@ Hosts can optionally pass CSS custom properties via `HostContext.styles` for vis
#### Current Standardized Variables #### Current Standardized Variables
| Category | Variables | ```typescript
|----------|-----------| /** CSS variable keys available to MCP apps for theming. */
| Background | `--color-background-primary`, `--color-background-secondary`, `--color-background-tertiary`, `--color-background-inverted` | type McpUiStyleVariableKey =
| Text | `--color-text-primary`, `--color-text-secondary`, `--color-text-tertiary`, `--color-text-inverted` | // Background colors
| Icons | `--color-icon-primary`, `--color-icon-secondary`, `--color-icon-tertiary`, `--color-icon-inverted` | | "--color-background-primary"
| Borders | `--color-border-primary`, `--color-border-secondary` | | "--color-background-secondary"
| Accents | `--color-accent-info`, `--color-accent-danger`, `--color-accent-success`, `--color-accent-warning` | | "--color-background-tertiary"
| Font Family | `--font-family-sans` | | "--color-background-inverted"
| Font Sizes | `--font-size-heading`, `--font-size-body`, `--font-size-caption` | // Text colors
| Font Weights | `--font-weight-regular`, `--font-weight-emphasized` | | "--color-text-primary"
| Line Heights | `--font-leading-regular`, `--font-leading-tight` | | "--color-text-secondary"
| Composite Styles | `--font-style-heading`, `--font-style-body`, `--font-style-body-emphasized`, `--font-style-caption`, `--font-style-caption-emphasized` | | "--color-text-tertiary"
| Border Radius | `--border-radius-small`, `--border-radius-medium`, `--border-radius-large`, `--border-radius-full` | | "--color-text-inverted"
| Border Width | `--border-width-regular` | // 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 #### 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 provide any subset of standardized variables, or not pass `styles` at all
- Hosts can use the CSS `light-dark()` function for theme-aware values - 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 #### 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 { :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 - 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": { "lastModified": {
"type": "string", "type": "string",
"format": "date-time", "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 "additionalProperties": false
@ -1745,7 +1745,7 @@
"lastModified": { "lastModified": {
"type": "string", "type": "string",
"format": "date-time", "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 "additionalProperties": false
@ -1792,7 +1792,7 @@
"lastModified": { "lastModified": {
"type": "string", "type": "string",
"format": "date-time", "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 "additionalProperties": false
@ -1866,7 +1866,7 @@
"lastModified": { "lastModified": {
"type": "string", "type": "string",
"format": "date-time", "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 "additionalProperties": false
@ -1959,7 +1959,7 @@
"lastModified": { "lastModified": {
"type": "string", "type": "string",
"format": "date-time", "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 "additionalProperties": false
@ -2670,7 +2670,7 @@
"lastModified": { "lastModified": {
"type": "string", "type": "string",
"format": "date-time", "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 "additionalProperties": false
@ -2717,7 +2717,7 @@
"lastModified": { "lastModified": {
"type": "string", "type": "string",
"format": "date-time", "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 "additionalProperties": false
@ -2764,7 +2764,7 @@
"lastModified": { "lastModified": {
"type": "string", "type": "string",
"format": "date-time", "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 "additionalProperties": false
@ -2838,7 +2838,7 @@
"lastModified": { "lastModified": {
"type": "string", "type": "string",
"format": "date-time", "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 "additionalProperties": false
@ -2931,7 +2931,7 @@
"lastModified": { "lastModified": {
"type": "string", "type": "string",
"format": "date-time", "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 "additionalProperties": false