bmc_hub/output/min-koe-forslag-preview.html
Christian 1fb5b4a191 Add comprehensive tests for email and drift functionalities
- Introduced tests for drift operations UI and backend to ensure proper handling of ignore rules and UI elements.
- Added tests for email identity API to validate email linking and identity retrieval without updates.
- Implemented tests for email purchase link service to verify correct handling of purchase mail links and conflict scenarios.
- Created tests for email reply automation to ensure proper routing and processing of replies based on classification.
- Developed tests for email sender creation to validate suggestions and handling of existing contacts and companies.
- Added tests for email sender identity to ensure correct matching of contacts and domains.
- Implemented tests for work desk functionalities to validate task management, meeting registrations, and timer functionalities.
2026-11-03 01:16:12 +01:00

3016 lines
108 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html lang="en" data-visualize-standalone>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="referrer" content="no-referrer">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'unsafe-inline' 'unsafe-eval' 'wasm-unsafe-eval' blob: data: https://cdnjs.cloudflare.com https://cdn.jsdelivr.net https://esm.sh https://fonts.bunny.net https://fonts.googleapis.com https://fonts.gstatic.com https://unpkg.com; style-src 'unsafe-inline' blob: data: https://cdnjs.cloudflare.com https://cdn.jsdelivr.net https://esm.sh https://fonts.bunny.net https://fonts.googleapis.com https://fonts.gstatic.com https://unpkg.com; img-src blob: data: https://cdnjs.cloudflare.com https://cdn.jsdelivr.net https://esm.sh https://fonts.bunny.net https://fonts.googleapis.com https://fonts.gstatic.com https://unpkg.com; font-src blob: data: https://cdnjs.cloudflare.com https://cdn.jsdelivr.net https://esm.sh https://fonts.bunny.net https://fonts.googleapis.com https://fonts.gstatic.com https://unpkg.com; media-src blob: data: https://cdnjs.cloudflare.com https://cdn.jsdelivr.net https://esm.sh https://fonts.bunny.net https://fonts.googleapis.com https://fonts.gstatic.com https://unpkg.com; worker-src blob:; connect-src blob: data:; frame-src 'self'; object-src 'none'; base-uri 'none'; form-action 'none'">
<title>Min Koe Forslag</title>
<style>:root{color-scheme:light dark;background:light-dark(rgb(255 255 255), rgb(24 24 24))}html,body{margin:0}body{box-sizing:border-box;padding:1rem;background:inherit}iframe{display:block;width:100%;height:calc(100vh - 2rem);margin:0 auto;border:0}</style>
</head>
<body>
<iframe id="codex-visualization" sandbox="allow-scripts" referrerpolicy="no-referrer" title="Min Koe Forslag" data-srcdoc="&lt;!doctype html&gt;
&lt;html lang=&quot;en&quot; data-visualize-standalone&gt;
&lt;head&gt;
&lt;meta charset=&quot;utf-8&quot;&gt;
&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot;&gt;
&lt;meta name=&quot;referrer&quot; content=&quot;no-referrer&quot;&gt;
&lt;meta http-equiv=&quot;Content-Security-Policy&quot; content=&quot;default-src &#x27;none&#x27;; script-src &#x27;unsafe-inline&#x27; &#x27;unsafe-eval&#x27; &#x27;wasm-unsafe-eval&#x27; blob: data: https://cdnjs.cloudflare.com https://cdn.jsdelivr.net https://esm.sh https://fonts.bunny.net https://fonts.googleapis.com https://fonts.gstatic.com https://unpkg.com; style-src &#x27;unsafe-inline&#x27; blob: data: https://cdnjs.cloudflare.com https://cdn.jsdelivr.net https://esm.sh https://fonts.bunny.net https://fonts.googleapis.com https://fonts.gstatic.com https://unpkg.com; img-src blob: data: https://cdnjs.cloudflare.com https://cdn.jsdelivr.net https://esm.sh https://fonts.bunny.net https://fonts.googleapis.com https://fonts.gstatic.com https://unpkg.com; font-src blob: data: https://cdnjs.cloudflare.com https://cdn.jsdelivr.net https://esm.sh https://fonts.bunny.net https://fonts.googleapis.com https://fonts.gstatic.com https://unpkg.com; media-src blob: data: https://cdnjs.cloudflare.com https://cdn.jsdelivr.net https://esm.sh https://fonts.bunny.net https://fonts.googleapis.com https://fonts.gstatic.com https://unpkg.com; worker-src blob:; connect-src blob: data:; frame-src &#x27;none&#x27;; object-src &#x27;none&#x27;; base-uri &#x27;none&#x27;; form-action &#x27;none&#x27;&quot;&gt;
&lt;title&gt;Min Koe Forslag&lt;/title&gt;
&lt;style&gt;:root {
color-scheme: light dark;
background-color: var(
--background,
var(--color-background-primary, light-dark(rgb(255 255 255), rgb(24 24 24)))
) !important;
/* Agent-facing contract; keep in sync with SKILL.md. */
--background: var(
--color-background-primary,
light-dark(rgb(255 255 255), rgb(24 24 24))
);
--foreground: var(
--color-text-primary,
light-dark(rgb(26 28 31), rgb(255 255 255))
);
--card: color-mix(in oklab, var(--foreground) 5%, var(--background));
--card-foreground: var(
--color-text-primary,
light-dark(rgb(26 28 31), rgb(255 255 255))
);
--popover: var(
--color-background-secondary,
light-dark(rgb(255 255 255), rgb(45 45 45))
);
--popover-foreground: var(
--color-text-primary,
light-dark(rgb(26 28 31), rgb(255 255 255))
);
--primary: var(
--color-text-info,
light-dark(rgb(51 156 255), rgb(131 195 255))
);
--primary-foreground: var(
--color-text-inverse,
light-dark(rgb(255 255 255), rgb(13 13 13))
);
--secondary: var(
--color-background-secondary,
light-dark(rgb(255 255 255 / 96%), rgb(54 54 54 / 96%))
);
--secondary-foreground: var(
--color-text-primary,
light-dark(rgb(26 28 31), rgb(255 255 255))
);
--muted: color-mix(in srgb, var(--foreground) 10%, transparent);
--muted-foreground: var(
--color-text-secondary,
light-dark(rgb(26 28 31 / 49.4%), rgb(255 255 255 / 49.8%))
);
--accent: var(
--color-background-info,
light-dark(rgb(229 242 255), rgb(13 39 63))
);
--accent-foreground: var(
--color-text-info,
light-dark(rgb(51 156 255), rgb(131 195 255))
);
--destructive: var(
--color-text-warning,
light-dark(rgb(226 85 7), rgb(255 133 73))
);
--border: var(
--color-border-secondary,
light-dark(rgb(26 28 31 / 8%), rgb(255 255 255 / 8.2%))
);
--input: var(
--color-border-primary,
light-dark(
rgb(26 28 31 / 11.8%),
color-mix(in oklab, rgb(0 0 0) 10%, transparent)
)
);
--ring: var(
--color-ring-primary,
light-dark(rgb(51 156 255), rgb(131 195 255 / 76%))
);
--font-size-base: var(--font-text-md-size, 16px);
--blue: light-dark(rgb(51 156 255), rgb(51 156 255));
--orange: light-dark(rgb(226 85 7), rgb(251 106 34));
--green: light-dark(rgb(0 162 64), rgb(64 201 119));
--red: light-dark(rgb(224 46 42), rgb(255 103 100));
--purple: light-dark(rgb(146 79 247), rgb(173 123 249));
--yellow: light-dark(rgb(255 195 0), rgb(255 210 64));
--viz-series-1: var(--primary);
--viz-series-2: light-dark(rgb(243 136 59), rgb(245 154 86));
--viz-series-3: light-dark(rgb(93 201 119), rgb(116 213 139));
--viz-series-4: light-dark(rgb(235 119 177), rgb(240 143 192));
--viz-series-5: light-dark(rgb(155 121 236), rgb(170 145 239));
--viz-series-6: light-dark(rgb(58 185 177), rgb(90 203 194));
/* Internal implementation variables; not part of the agent contract. */
--font-sans:
-apple-system-body, ui-sans-serif, -apple-system, system-ui, &quot;Segoe UI&quot;,
Helvetica, &quot;Apple Color Emoji&quot;, Arial, sans-serif, &quot;Segoe UI Emoji&quot;,
&quot;Segoe UI Symbol&quot;;
--font-mono:
ui-monospace, SFMono-Regular, &quot;SF Mono&quot;, Menlo, Consolas, &quot;Liberation Mono&quot;,
monospace;
--font-size-normal: max(11px, var(--font-size-base));
--font-size-tooltip: calc(var(--font-size-base) - 1px);
--font-size-small: max(11px, calc(var(--font-size-base) - 2px));
--font-size-h1: calc(var(--font-size-normal) * 1.7142857143);
--font-size-h2: calc(var(--font-size-normal) * 1.4285714286);
--font-size-h3: calc(var(--font-size-normal) * 1.2857142857);
--font-weight-normal: 400;
--font-weight-medium: 500;
--icon-size: 16px;
--line-height-normal: calc(var(--font-size-normal) * 1.5);
--line-height-tooltip: calc(var(--font-size-tooltip) * 1.4285714286);
--line-height-small: calc(var(--font-size-small) + 4px);
--corner-shape: round;
--radius: var(--border-radius-lg, 8px);
--radius-sm: calc(var(--radius) * 0.5);
--radius-md: calc(var(--radius) * 0.75);
--radius-lg: var(--radius);
--radius-2xl: calc(var(--radius) * 2);
--radius-full: 9999px;
--shadow-sm: 0 1px 2px -1px rgb(0 0 0 / 8%);
--checkmark-image: url(&quot;data:image/svg+xml,%3Csvg xmlns=&#x27;http://www.w3.org/2000/svg&#x27; viewBox=&#x27;0 0 17 17&#x27;%3E%3Cpath d=&#x27;M12.8961 3.64101C13.1297 3.41418 13.4984 3.37523 13.7779 3.56581C14.0571 3.75635 14.1554 4.11331 14.0299 4.41347L13.9615 4.53847L7.71151 13.7045C7.59411 13.8767 7.4063 13.9877 7.19881 14.0072C6.99136 14.0267 6.78564 13.9533 6.63826 13.806L2.88826 10.056L2.79842 9.9457C2.6192 9.67407 2.64927 9.30496 2.88826 9.06581C3.12738 8.82669 3.49647 8.79676 3.76815 8.97597L3.8785 9.06581L7.03084 12.2182L12.8053 3.74941L12.8961 3.64101Z&#x27;/%3E%3C/svg%3E&quot;);
/* Legacy aliases; not part of the current agent contract. */
--viz-bg: transparent;
--viz-panel: var(--card);
--viz-border: var(--border);
--viz-text: var(--foreground);
--viz-muted: var(--muted-foreground);
--viz-accent: var(--primary);
--viz-accent-text: var(--primary-foreground);
--viz-accent-bg: var(--accent);
--viz-font-size: var(--font-size-base);
--viz-warning: var(--destructive);
}
:root[data-theme=&quot;light&quot;] {
color-scheme: light;
}
:root[data-theme=&quot;dark&quot;] {
color-scheme: dark;
}
* {
box-sizing: border-box;
}
html &gt; body {
/* Preserve MCP aliases for older inline renderers without creating :root cycles. */
--color-background-primary: var(
--background,
light-dark(rgb(255 255 255), rgb(24 24 24))
);
--color-text-primary: var(
--foreground,
light-dark(rgb(26 28 31), rgb(255 255 255))
);
--color-border-secondary: var(
--border,
light-dark(rgb(26 28 31 / 8%), rgb(255 255 255 / 8.2%))
);
margin: 0;
padding: var(--visualize-paint-gutter, 0px);
color: var(--foreground);
background: transparent !important;
font-family: var(--font-sans) !important;
-webkit-font-smoothing: antialiased;
font-size: var(--font-size-base);
line-height: calc(var(--font-size-base) * 1.625);
font-weight: var(--font-weight-normal);
}
.cursor-interaction {
cursor: var(--cursor-interaction, pointer);
}
a {
color: color-mix(in srgb, var(--viz-accent) 80%, var(--viz-text) 20%);
cursor: var(--cursor-interaction, pointer);
text-decoration: none;
text-underline-offset: 2px;
font-weight: inherit;
}
a:is(:hover, :focus-visible) {
text-decoration-line: underline;
text-decoration-style: dashed;
text-decoration-thickness: 0.5px;
}
h1,
h2,
h3,
h4,
h5,
h6,
p {
margin-block: 0;
}
p,
h1,
h2,
h3,
h4,
h5,
h6,
li,
.table {
font-family: var(--font-sans);
}
h1 {
font-size: calc(var(--font-size-base) * 1.5);
line-height: 1.3333333333;
}
h2 {
font-size: calc(var(--font-size-base) * 1.25);
line-height: 1.4;
}
h3 {
font-size: calc(var(--font-size-base) * 1.125);
line-height: 1.5555555556;
}
h4 {
font-size: var(--font-size-base);
line-height: 1.5;
}
h5,
h6 {
font-size: var(--font-size-base);
line-height: calc(var(--font-size-base) * 1.625);
}
h1,
h2,
h3,
h4,
h5,
h6,
b,
strong,
th {
font-weight: 600;
}
code:not(pre code) {
display: inline;
padding: 1px 6px;
border-radius: var(--radius-sm);
corner-shape: var(--corner-shape);
background: var(--muted);
font-family: var(--font-mono);
font-size: 0.92em;
overflow-wrap: anywhere;
-webkit-box-decoration-break: clone;
box-decoration-break: clone;
word-break: break-word;
}
pre,
pre code {
font-family: var(--font-mono);
font-size: 12px;
line-height: 20px;
}
.table-responsive {
width: 100%;
overflow-x: auto;
scrollbar-width: thin;
}
.table {
width: 100%;
color: var(--foreground);
font: inherit;
text-align: start;
border-collapse: separate;
border-spacing: 0;
font-size: calc(var(--font-size-base) * 0.875);
line-height: calc(var(--font-size-base) * 1.625);
font-family: var(--font-sans);
}
.table :is(th, td) {
padding-block: 10px;
padding-inline: 0 24px;
overflow-wrap: anywhere;
border-bottom: 1px solid var(--border);
text-align: start;
vertical-align: top;
border-bottom-color: color-mix(in srgb, var(--foreground) 5%, transparent);
}
.table :is(th, td):not(:last-child) {
padding-inline-end: calc(var(--font-size-base) * 1.5);
}
.table-responsive &gt; .table :is(th, td) {
overflow-wrap: break-word;
}
.table :is(th, td):last-child {
padding-inline-end: 0;
}
.table :is(caption, thead th) {
font-weight: 600;
}
.table thead :is(th, td) {
border-bottom-color: var(--border-strong, var(--input));
padding-block: calc(var(--font-size-base) / 2);
line-height: var(--font-size-base);
}
.table tbody tr:last-child :is(th, td) {
border-bottom: 0;
}
.table tbody :is(th, td) {
padding-block: calc(var(--font-size-base) * 0.625);
}
.table.table-sm :is(th, td) {
padding-block: 8px;
}
.table.table-sm :is(th, td):not(:last-child) {
padding-inline-end: 16px;
}
.table :is(.text-end, [align=&quot;right&quot;]) {
text-align: end;
font-variant-numeric: tabular-nums;
}
.table :is(.text-center, [align=&quot;center&quot;]) {
text-align: center;
}
.table .text-nowrap {
white-space: nowrap;
}
#widget {
display: flex;
flex-direction: column;
gap: 12px;
width: 100%;
padding: 0;
background: transparent !important;
}
.card {
min-width: 0;
padding: 12px;
overflow: hidden;
overflow-wrap: break-word;
border-radius: var(--radius-2xl);
corner-shape: var(--corner-shape);
color: var(--card-foreground);
background: var(--card);
}
.widget {
min-width: 0;
overflow: hidden;
border: 0;
border-radius: var(--radius-lg);
corner-shape: var(--corner-shape);
box-shadow: 0 0 0 0.5px var(--border-strong, var(--border));
color: var(--foreground);
background: var(--background);
}
#widget &gt; :not(.card, .widget, .progress) {
width: 100% !important;
max-width: none !important;
margin: 0 !important;
padding: 0 !important;
border: 0 !important;
border-radius: 0 !important;
background: transparent !important;
box-shadow: none !important;
}
.tooltip {
position: fixed;
z-index: 50;
top: 0;
left: 0;
width: max-content;
max-width: min(
20rem,
var(--tooltip-available-width, calc(100vw - 10px)),
calc(100vw - 10px)
);
max-height: min(
var(--tooltip-available-height, calc(100vh - 10px)),
calc(100vh - 10px)
);
border: 1px solid var(--border);
corner-shape: var(--corner-shape);
overflow-wrap: break-word;
white-space: normal;
pointer-events: none;
user-select: none;
font-family: var(--font-sans);
padding: 5px 12px;
border-color: color-mix(
in oklab,
light-dark(var(--primary-foreground), var(--foreground)) 5%,
transparent
);
border-radius: var(--radius-2xl);
color: light-dark(var(--primary-foreground), var(--foreground));
background: light-dark(var(--foreground), var(--popover));
box-shadow: 0 8px 18px rgb(15 23 42 / 20%);
letter-spacing: -0.15px;
font-size: calc(var(--font-size-base) * 14 / 16);
font-size: round(nearest, calc(var(--font-size-base) * 14 / 16), 1px);
font-weight: 600;
line-height: 18px;
text-align: center;
}
.viz-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(max(180px, 24%), 1fr));
gap: 10px;
}
.viz-stat {
display: flex;
flex-direction: column;
gap: 2px;
}
.viz-stat-value {
font-size: var(--font-size-h2);
font-weight: var(--font-weight-medium);
line-height: 1.25;
}
.viz-row {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 10px;
}
hr {
width: 100%;
height: 1px;
margin-block: 6px;
border: 0;
background: var(--border);
}
.nav {
display: flex;
min-width: 0;
align-items: center;
gap: 4px;
}
.progress {
display: flex;
height: 8px;
margin-block: calc((var(--line-height-normal, 1.5em) - 8px) / 2);
overflow: hidden;
border: 0;
border-radius: var(--radius-full);
background: var(--muted);
}
.progress-bar:only-child {
border-radius: var(--radius-full);
}
.progress-bar {
height: 100%;
flex-shrink: 0;
border: 0;
background: var(--viz-series-1);
}
.viz-badge {
display: inline-flex;
align-items: center;
padding: 4px 8px;
border-radius: var(--radius-sm);
color: color-mix(in srgb, var(--foreground) 40%, var(--muted-foreground));
background: color-mix(in srgb, var(--foreground) 5%, transparent);
font-family: var(--font-sans);
font-size: calc(var(--font-size-base) * 14 / 16);
font-size: round(nearest, calc(var(--font-size-base) * 14 / 16), 1px);
font-weight: var(--font-weight-normal);
line-height: 1;
}
small,
.text-small {
font-size: var(--font-size-small);
line-height: var(--line-height-small);
}
.text-muted {
color: var(--muted-foreground);
}
.tabular-nums {
font-variant-numeric: tabular-nums;
}
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
.viz-controls {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
}
.viz-controls &gt; .form-label {
display: grid;
min-width: min(100%, 260px);
flex: 1 1 280px;
grid-template-columns: minmax(0, 1fr) auto;
align-items: baseline;
gap: 2px 12px;
margin-bottom: 0;
}
.viz-controls &gt; .form-label &gt; :is(.form-control, .form-range, .form-select) {
grid-column: 1 / -1;
}
.btn,
.nav-link,
.form-check-input,
.form-control,
.form-range,
.form-select {
font: inherit;
}
/* Match inline alignment without overriding a containing flex or grid layout. */
.btn,
.nav-link,
.form-control,
.form-select,
.form-range,
.form-check {
vertical-align: middle;
}
.btn,
.nav-link {
appearance: button;
display: inline-flex;
inline-size: fit-content;
max-inline-size: 100%;
align-items: center;
justify-content: center;
gap: 4px;
margin: 0;
padding: 0 8px;
-webkit-app-region: no-drag;
border: 1px solid var(--input);
corner-shape: var(--corner-shape);
cursor: var(--cursor-interaction, pointer);
text-align: center;
text-decoration: none;
white-space: nowrap;
user-select: none;
-webkit-font-smoothing: inherit;
font-family: var(--font-sans);
height: fit-content;
min-height: calc(12px + 2 * var(--control-spacing, 12px));
padding-inline: var(--control-spacing, 12px);
border-color: transparent;
border-radius: var(--radius-full);
color: var(--foreground);
background: color-mix(in srgb, var(--foreground) 5%, transparent);
font-size: calc(var(--font-size-base) * 14 / 16);
font-size: round(nearest, calc(var(--font-size-base) * 14 / 16), 1px);
font-weight: var(--font-weight-normal);
line-height: 18px;
}
.btn {
border-color: var(--border);
color: var(--secondary-foreground);
background: var(--secondary);
}
.btn:is(.btn-block, .viz-tile) {
inline-size: 100%;
}
.btn.viz-tile {
min-width: 0;
overflow-wrap: anywhere;
white-space: normal;
}
a.btn {
cursor: var(--cursor-interaction, pointer);
text-decoration: none;
}
.btn:not(:disabled):hover {
background: light-dark(
color-mix(in srgb, var(--foreground) 5.5%, transparent),
color-mix(in srgb, var(--foreground) 8%, transparent)
);
}
.nav-link:not(:disabled):not([aria-disabled=&quot;true&quot;]):hover {
background: color-mix(in srgb, var(--foreground) 10%, transparent);
}
.btn-primary {
border-color: var(--border);
color: var(--primary-foreground);
background: var(--foreground);
background-clip: border-box;
}
.btn-primary .text-muted {
color: color-mix(in srgb, var(--primary-foreground) 50%, transparent);
}
.btn-primary:not(:disabled):hover {
background: color-mix(in srgb, var(--foreground) 80%, transparent);
background-clip: border-box;
}
.btn-ghost {
border-color: transparent;
color: var(--muted-foreground);
background: transparent;
}
.btn-ghost:not(:disabled):hover {
color: var(--muted-foreground);
background: light-dark(
color-mix(in srgb, var(--foreground) 5.5%, transparent),
color-mix(in srgb, var(--foreground) 8%, transparent)
);
}
.nav-pills .nav-link {
min-width: 0;
border-color: transparent;
color: var(--muted-foreground);
background: transparent;
}
.nav-justified .nav-link {
flex: 1 1 0;
overflow-wrap: anywhere;
white-space: normal;
}
.nav-pills .nav-link.active {
color: var(--foreground);
background: var(--muted);
}
/* Shared preview chrome; product styles stay inside each variant. */
.viz-dotted-background {
background-image: radial-gradient(
circle,
color-mix(in srgb, var(--foreground) 14%, transparent) 0.8px,
transparent 0.8px
);
background-position: center;
background-size: 16px 16px;
}
.viz-carousel {
position: relative;
min-width: 0;
}
.viz-carousel:has(&gt; .viz-carousel-controls) {
min-height: calc(60px + var(--viz-carousel-picker-height, 0px));
padding-bottom: 60px;
}
.viz-carousel &gt; [data-variant][hidden] {
display: none !important;
}
.viz-carousel &gt; [data-variant] {
isolation: isolate;
}
.viz-carousel-controls {
--viz-carousel-hover: light-dark(
color-mix(in srgb, var(--foreground) 5.4%, transparent),
color-mix(in srgb, var(--foreground) 7.8%, transparent)
);
position: absolute;
z-index: 2;
bottom: 8px;
left: 50%;
display: flex;
width: max-content;
/* Leave room for the host&#x27;s design-controls launcher at the trailing edge. */
max-width: calc(100% - 96px);
align-items: center;
gap: 0;
padding: 0;
border: 1px solid var(--border);
border-radius: 16px;
color: var(--foreground);
background: var(--background);
font-family: var(--font-sans);
/* Match the settings dropdown chrome independently of the content font size. */
font-size: 13px;
font-weight: var(--font-weight-normal);
line-height: 18px;
transform: translateX(-50%);
user-select: none;
}
/* Keep authored mockup button styles out of the shared carousel chrome. */
.viz-carousel-controls button,
.viz-carousel-picker &gt; summary {
display: flex !important;
height: auto !important;
min-height: 30px;
align-items: center !important;
justify-content: center !important;
gap: 4px;
padding: 0 6px !important;
-webkit-app-region: no-drag;
border: 0 !important;
border-radius: 14px !important;
color: inherit !important;
background: transparent !important;
font: inherit !important;
-webkit-tap-highlight-color: transparent;
}
.viz-carousel-controls &gt; button,
.viz-carousel-picker &gt; summary {
position: relative;
isolation: isolate;
}
.viz-carousel-controls &gt; button {
--icon-size: 16px;
width: 30px !important;
padding: 0 !important;
}
.viz-carousel-picker &gt; summary {
padding-inline: 12px !important;
}
.viz-carousel-controls .viz-carousel-caret {
width: 14px;
height: 14px;
flex: none;
margin: 0;
color: inherit;
transform: rotate(180deg);
}
.viz-carousel-controls &gt; button &gt; svg,
.viz-carousel-controls .viz-carousel-caret,
.viz-carousel-count {
opacity: 0.5;
}
.viz-carousel-controls &gt; button::before,
.viz-carousel-picker &gt; summary::before {
position: absolute;
z-index: -1;
inset: 3px;
border-radius: 12px;
content: &quot;&quot;;
pointer-events: none;
}
.viz-carousel-controls &gt; button:hover::before,
.viz-carousel-picker &gt; summary:hover::before {
background: var(--viz-carousel-hover);
}
.viz-carousel-controls &gt; button,
.viz-carousel-controls [data-lucide],
.viz-carousel-controls .lucide {
flex-shrink: 0;
}
.viz-carousel-picker {
min-width: 0;
}
.viz-carousel-picker &gt; summary {
min-width: 0;
list-style: none;
}
.viz-carousel-picker &gt; summary::-webkit-details-marker {
display: none;
}
.viz-carousel-count {
flex-shrink: 0;
white-space: nowrap;
}
.viz-carousel-name {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.viz-carousel-options {
position: absolute;
right: 0;
bottom: calc(100% + 4px);
left: 0;
display: flex;
max-height: 240px;
flex-direction: column;
gap: 2px;
overflow: auto;
padding: 4px;
border: 1px solid var(--border);
border-radius: var(--radius-lg);
background: var(--background);
box-shadow: 0 4px 12px light-dark(rgb(0 0 0 / 6%), rgb(0 0 0 / 25%));
}
.viz-carousel-options &gt; button {
border-radius: max(0px, calc(var(--radius-lg) - 5px)) !important;
width: 100% !important;
min-height: 28px;
justify-content: flex-start !important;
padding: 4px 8px !important;
font-size: var(--font-size-small) !important;
text-align: start;
white-space: normal;
overflow-wrap: anywhere;
}
.viz-carousel-options &gt; [aria-current=&quot;true&quot;],
.viz-carousel-options &gt; button:hover {
background: var(--viz-carousel-hover) !important;
}
.btn:disabled,
.nav-link:disabled,
.nav-link[aria-disabled=&quot;true&quot;] {
cursor: not-allowed;
opacity: 0.4;
}
[data-lucide] {
stroke-width: 1.6;
}
.form-label {
display: block;
margin-bottom: 6px;
color: var(--foreground);
}
.form-control {
display: block;
width: 100%;
padding: 0 8px;
outline: none;
border: 1px solid var(--input);
corner-shape: var(--corner-shape);
color: var(--foreground);
font-family: var(--font-sans);
height: calc(12px + 2 * var(--control-spacing, 12px));
min-height: calc(12px + 2 * var(--control-spacing, 12px));
padding-inline: calc(4px + var(--control-spacing, 12px) / 2);
border-color: var(--input);
border-radius: var(--radius-md);
background: color-mix(in srgb, var(--popover) 96%, transparent);
font-size: calc(var(--font-size-base) * 14 / 16);
font-size: round(nearest, calc(var(--font-size-base) * 14 / 16), 1px);
font-weight: var(--font-weight-normal);
line-height: 1.4285714286;
}
.form-control:is(
[type=&quot;file&quot;],
[type=&quot;date&quot;],
[type=&quot;datetime-local&quot;],
[type=&quot;month&quot;],
[type=&quot;week&quot;],
[type=&quot;color&quot;]
) {
height: calc(12px + 2 * var(--control-spacing, 12px));
min-height: calc(12px + 2 * var(--control-spacing, 12px));
line-height: 18px;
}
.form-control:is(
[type=&quot;date&quot;],
[type=&quot;datetime-local&quot;],
[type=&quot;month&quot;],
[type=&quot;week&quot;]
) {
padding-inline: var(--control-spacing, 12px);
}
.form-control::placeholder {
color: var(--muted-foreground);
}
.form-control[type=&quot;file&quot;] {
overflow: hidden;
cursor: var(--cursor-interaction, pointer);
padding: 0;
}
.form-control[type=&quot;file&quot;]::file-selector-button {
padding: 0 8px;
border: 0;
border-right: 1px solid var(--input);
color: var(--secondary-foreground);
background: var(--secondary);
cursor: inherit;
font: inherit;
min-height: calc(10px + 2 * var(--control-spacing, 12px));
padding-inline: var(--control-spacing, 12px);
margin-inline-end: var(--control-spacing, 12px);
}
.form-control[type=&quot;file&quot;]:not(:disabled):hover::file-selector-button {
background: color-mix(in srgb, var(--foreground) 6%, var(--secondary));
}
.form-control-color[type=&quot;color&quot;] {
width: 40px;
height: calc(12px + 2 * var(--control-spacing, 12px));
padding: 3px;
cursor: var(--cursor-interaction, pointer);
}
.form-control-color[type=&quot;color&quot;]::-webkit-color-swatch-wrapper {
padding: 0;
}
.form-control-color[type=&quot;color&quot;]::-webkit-color-swatch {
border: 0;
border-radius: calc(var(--radius-lg) - 4px);
corner-shape: var(--corner-shape);
}
textarea.form-control {
height: auto;
resize: vertical;
min-height: 96px;
padding: 8px 10px;
}
.form-control:focus-visible {
border-color: var(--ring);
box-shadow: none;
}
.form-control:disabled {
color: var(--muted-foreground);
background: color-mix(in srgb, var(--foreground) 5%, transparent);
opacity: 1;
}
.form-control[aria-invalid=&quot;true&quot;] {
border-color: var(--red);
box-shadow: 0 0 0 2px color-mix(in srgb, var(--red) 20%, transparent);
}
.form-control:disabled,
.form-select:disabled {
cursor: not-allowed;
}
.form-select {
appearance: none;
display: block;
width: 100%;
height: fit-content;
min-height: calc(12px + 2 * var(--control-spacing, 12px));
margin: 0;
/* The secondary icon role is 14px at compact density and 16px at web density. */
--icon-size: calc(10px + var(--control-spacing, 12px) / 2);
/* Settings dropdowns retain a 12px inset and a 4px text-to-chevron gap. */
padding: 0 calc(16px + var(--icon-size)) 0 12px;
outline: none;
border: 1px solid var(--border);
border-radius: var(--radius-full);
corner-shape: var(--corner-shape);
color: var(--foreground);
background-color: var(--secondary);
background-image:
linear-gradient(
45deg,
transparent calc(50% - 0.6px),
var(--muted-foreground) calc(50% - 0.6px) calc(50% + 0.6px),
transparent calc(50% + 0.6px)
),
linear-gradient(
135deg,
transparent calc(50% - 0.6px),
var(--muted-foreground) calc(50% - 0.6px) calc(50% + 0.6px),
transparent calc(50% + 0.6px)
);
background-position:
calc(100% - 12px - var(--icon-size) / 2) 50%,
calc(100% - 12px - var(--icon-size) * 0.18) 50%;
background-repeat: no-repeat;
background-size: calc(var(--icon-size) * 0.32) calc(var(--icon-size) * 0.32);
cursor: var(--cursor-interaction, default);
font-size: calc(var(--font-size-base) * 14 / 16);
font-size: round(nearest, calc(var(--font-size-base) * 14 / 16), 1px);
font-weight: var(--font-weight-normal);
line-height: 18px;
}
.form-select option {
color: var(--popover-foreground);
background-color: var(--popover);
}
.form-select:not(:disabled):hover {
background-color: light-dark(
color-mix(in srgb, var(--foreground) 5.5%, transparent),
color-mix(in srgb, var(--foreground) 8%, transparent)
);
}
.form-select:focus-visible {
box-shadow: 0 0 0 2px var(--ring);
}
.form-select:disabled {
opacity: 0.4;
}
.form-check {
display: flex;
min-height: calc(12px + 2 * var(--control-spacing, 12px));
align-items: center;
gap: 6px;
}
.form-check-input {
appearance: none;
width: 14px;
height: 14px;
flex: 0 0 auto;
margin: 0;
border: 1px solid var(--input);
color: var(--primary-foreground);
background-color: transparent;
cursor: var(--cursor-interaction, default);
transition:
background-color 150ms,
border-color 150ms,
box-shadow 150ms;
}
.form-check:not(.form-switch) .form-check-input[type=&quot;checkbox&quot;] {
corner-shape: round;
box-shadow: var(--shadow-sm);
position: relative;
width: calc(10px + var(--control-spacing, 12px) / 2);
height: calc(10px + var(--control-spacing, 12px) / 2);
border-color: var(--border-strong, var(--input));
border-radius: calc(var(--radius-sm) / 2);
background-color: transparent;
color: var(--foreground);
}
.form-check:not(.form-switch)
.form-check-input:not(:disabled):not(:checked):hover {
background-color: var(--card);
}
.form-check:not(.form-switch)
.form-check-input[type=&quot;checkbox&quot;]:is(:checked, :indeterminate) {
border-color: var(--border-strong, var(--input));
background-color: color-mix(in srgb, var(--popover) 96%, transparent);
}
.form-check:not(.form-switch)
.form-check-input[type=&quot;checkbox&quot;]:checked::before {
display: block;
width: 100%;
height: 100%;
background: var(--primary-foreground);
content: &quot;&quot;;
mask: var(--checkmark-image) center / 12px 12px no-repeat;
background-color: var(--foreground);
}
.form-check:not(.form-switch)
.form-check-input[type=&quot;checkbox&quot;]:indeterminate::before {
position: absolute;
inset: 0;
display: block;
width: 65%;
height: 6.65%;
margin: auto;
border-radius: var(--radius-full);
background: var(--foreground);
mask: none;
content: &quot;&quot;;
}
.form-check-input[type=&quot;radio&quot;] {
border-radius: var(--radius-full);
width: 16px;
height: 16px;
border: 0;
box-shadow: inset 0 0 0 1px var(--input);
}
.form-check-input[type=&quot;radio&quot;]:checked {
border: 0;
background: radial-gradient(
circle,
var(--primary-foreground) 0 3px,
var(--foreground) 3px
);
box-shadow: none;
}
.form-check:not(.form-switch) .form-check-input:focus-visible {
outline: 2px solid var(--ring);
outline-offset: 2px;
}
.form-check:not(.form-switch) .form-check-input[type=&quot;checkbox&quot;]:focus-visible {
border-color: var(--ring);
outline: none;
box-shadow: 0 0 0 2px var(--ring);
}
.form-check:not(.form-switch) .form-check-input:disabled {
cursor: not-allowed;
pointer-events: none;
}
.form-check:not(.form-switch) .form-check-input:disabled + .form-check-label {
cursor: not-allowed;
}
.form-check:not(.form-switch) .form-check-input[type=&quot;radio&quot;]:disabled,
.form-check:not(.form-switch)
.form-check-input[type=&quot;radio&quot;]:disabled
+ .form-check-label {
opacity: 0.5;
}
.form-switch .form-check-input:disabled,
.form-switch .form-check-input:disabled + .form-check-label {
cursor: not-allowed;
opacity: 0.6;
}
.form-check-label {
color: var(--foreground);
cursor: var(--cursor-interaction, default);
}
.form-switch .form-check-input {
position: relative;
width: 32px;
height: 20px;
border: 0;
border-radius: var(--radius-full);
background: var(--muted);
box-shadow: none;
transition: background-color 200ms cubic-bezier(0, 0, 0.2, 1);
}
.form-switch .form-check-input::before {
position: absolute;
top: 50%;
left: 0;
width: 16px;
height: 16px;
box-sizing: border-box;
border: 1px solid light-dark(var(--primary-foreground), var(--foreground));
border-radius: var(--radius-full);
box-shadow: var(--shadow-sm);
content: &quot;&quot;;
transform: translate(2px, -50%);
transition: transform 200ms cubic-bezier(0, 0, 0.2, 1);
border-color: #fff;
background: #fff;
}
.form-switch .form-check-input:checked {
background: var(--blue);
}
.form-switch .form-check-input:checked::before {
transform: translate(14px, -50%);
}
.form-switch .form-check-input:focus-visible {
box-shadow: 0 0 0 2px var(--ring);
outline: none;
}
.form-range {
appearance: none;
display: block;
width: 100%;
flex: 1;
margin: 0;
padding: 0;
border: 0;
accent-color: var(--primary);
height: calc(12px + 2 * var(--control-spacing, 12px));
min-height: calc(12px + 2 * var(--control-spacing, 12px));
outline: none;
border-radius: var(--radius-full);
color: var(--foreground);
background: transparent;
}
.form-range::-webkit-slider-runnable-track {
background: var(--form-range-track, var(--border));
height: 2px;
border-radius: var(--radius-full);
}
.form-range::-webkit-slider-thumb {
appearance: none;
width: 20px;
height: 20px;
border: 1px solid var(--border);
border-radius: var(--radius-full);
margin-top: -9px;
border-color: transparent;
background: currentColor;
box-shadow: var(--shadow-sm);
}
/* Outline the native track and thumb, preserving the larger input hit area. */
.form-range:focus-visible::-webkit-slider-runnable-track {
outline: 2px auto Highlight;
outline-color: -webkit-focus-ring-color;
}
.form-range::-moz-range-track {
background: var(--form-range-track, var(--border));
height: 2px;
border-radius: var(--radius-full);
}
.form-range::-moz-range-thumb {
width: 20px;
height: 20px;
border: 1px solid var(--border);
border-radius: var(--radius-full);
border-color: transparent;
background: currentColor;
box-shadow: var(--shadow-sm);
}
.form-range:focus-visible::-moz-range-thumb {
outline: 2px solid Highlight;
outline-offset: 2px;
}
.form-range:disabled {
opacity: 1;
cursor: default;
}
.btn:not(.btn-primary, .viz-tile):is(
[aria-pressed=&quot;true&quot;],
[aria-selected=&quot;true&quot;],
.is-selected
) {
border-color: transparent;
color: var(--primary-foreground);
background: var(--blue);
}
.btn.viz-tile:is([aria-pressed=&quot;true&quot;], [aria-selected=&quot;true&quot;], .is-selected) {
border-color: var(--primary);
box-shadow: inset 0 0 0 1px var(--primary);
}
.btn:focus-visible,
.nav-link:focus-visible {
outline-offset: 2px;
outline: none;
box-shadow: 0 0 0 2px var(--ring);
}
.viz-carousel-controls :is(button, summary):focus-visible {
outline: 2px solid var(--ring) !important;
outline-offset: 2px !important;
}
/* Web recipes are the default, including on renderers without inline style queries. */
@supports (border-radius: if(style(--corner-shape: round): 1px; else: 2px)) {
/* Match the host&#x27;s proportional text-sm scale at every text preference. */
.tooltip,
.viz-badge,
.btn,
.nav-link,
.form-control,
.form-select {
font-size: if(
style(--corner-shape: round): round(
nearest,
calc(var(--font-size-base) * 14 / 16),
1px
);
else: round(nearest, calc(var(--font-size-base) * 13 / 14), 1px)
);
}
.btn,
.nav-link,
.form-select {
border-radius: if(
style(--corner-shape: round): var(--radius-full); else: var(--radius-lg)
);
}
.form-check:not(.form-switch) .form-check-input[type=&quot;checkbox&quot;] {
border-radius: if(
style(--corner-shape: round): calc(var(--radius-sm) / 2);
else: calc(var(--radius-lg) * 0.4)
);
}
.tooltip {
font-weight: if(
style(--corner-shape: round): 600; else: var(--font-weight-normal)
);
}
}
@media (pointer: coarse) {
.viz-carousel:has(&gt; .viz-carousel-controls) {
min-height: calc(76px + var(--viz-carousel-picker-height, 0px));
padding-bottom: 76px;
}
.btn,
.nav-link,
.viz-carousel-controls button,
.viz-carousel-picker &gt; summary,
.form-control,
.form-select {
min-width: 44px;
min-height: 44px;
}
/* Keep small-screen WebKit from zooming when a text field receives focus. */
.form-control,
.form-select {
font-size: max(16px, calc(var(--font-size-base) * 14 / 16));
font-size: max(
16px,
round(nearest, calc(var(--font-size-base) * 14 / 16), 1px)
);
}
.form-control:is(
[type=&quot;file&quot;],
[type=&quot;date&quot;],
[type=&quot;datetime-local&quot;],
[type=&quot;month&quot;],
[type=&quot;week&quot;],
[type=&quot;time&quot;],
[type=&quot;color&quot;]
) {
height: 44px;
min-height: 44px;
font-size: max(16px, calc(var(--font-size-base) * 14 / 16));
font-size: max(
16px,
round(nearest, calc(var(--font-size-base) * 14 / 16), 1px)
);
}
.form-control[type=&quot;file&quot;]::file-selector-button {
min-width: 44px;
min-height: 44px;
}
.form-control-color[type=&quot;color&quot;] {
width: 44px;
height: 44px;
}
.form-control:is(
[type=&quot;date&quot;],
[type=&quot;datetime-local&quot;],
[type=&quot;month&quot;],
[type=&quot;time&quot;],
[type=&quot;week&quot;]
) {
min-width: 0;
max-width: 100%;
padding-block: 8px;
}
.form-check {
min-height: 44px;
padding-inline: 15px;
}
.form-check-input {
position: relative;
}
.form-check-input::after {
position: absolute;
top: 50%;
left: 50%;
width: 44px;
height: 44px;
content: &quot;&quot;;
transform: translate(-50%, -50%);
}
.form-check-label {
display: inline-flex;
min-width: 44px;
min-height: 44px;
align-items: center;
}
.form-range {
height: 44px;
touch-action: pan-y;
}
}
svg {
display: block;
max-width: 100%;
height: auto;
}
#widget &gt; svg {
width: 100%;
}
/* Model-authored chart rules can otherwise stretch icons after these styles load. */
svg.lucide {
display: block;
width: var(--icon-size) !important;
height: var(--icon-size) !important;
flex: none;
margin: 0 !important;
stroke-width: 1.6;
}
.text-warning,
.text-destructive {
color: var(--destructive);
}
html&gt;body{padding:0}&lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;script type=&quot;application/json&quot; id=&quot;codex-visualization-widget-state&quot;&gt;__CODEX_VISUALIZATION_WIDGET_STATE__&lt;/script&gt;
&lt;script&gt;(() =&gt; {
const root = document.documentElement;
const mediaQuery = globalThis.matchMedia(&quot;(prefers-color-scheme: dark)&quot;);
const apply = Reflect.apply;
const isArray = Array.isArray;
const hasOwn = Object.hasOwn;
const parentWindow = globalThis.parent;
const stringify = JSON.stringify;
const parse = JSON.parse;
const encode = TextEncoder.prototype.encode.bind(new TextEncoder());
const initialStateElement = document.getElementById(
&quot;codex-visualization-widget-state&quot;,
);
const initialGlobals = parse(initialStateElement?.textContent ?? &quot;{}&quot;);
initialStateElement?.remove();
/** @type {Record&lt;string, unknown&gt; | null} */
let state = initialGlobals.widgetState ?? null;
const statePersistence = initialGlobals.statePersistence ?? &quot;none&quot;;
let nextStateRequestId = 0;
/** @type {Map&lt;number, { resolve: () =&gt; void; reject: (error: Error) =&gt; void; timeout: ReturnType&lt;typeof globalThis.setTimeout&gt;; serializedState: string }&gt;} */
const pendingStateWrites = new Map();
const persistenceError = () =&gt;
Object.assign(
new Error(&quot;The host could not persist the widget state update&quot;),
{
code: -32000,
data: { code: &quot;widget_state_persistence_failed&quot;, retryable: false },
},
);
/**
* @param {object} prototype
* @param {string} property
*/
const getPrototypeGetter = (prototype, property) =&gt; {
// oxlint-disable-next-line typescript/unbound-method -- invoked through captured Reflect.apply
const getter = Object.getOwnPropertyDescriptor(prototype, property)?.get;
if (getter == null) {
throw new Error(`Missing ${property} getter`);
}
return getter;
};
const getMessageData = getPrototypeGetter(MessageEvent.prototype, &quot;data&quot;);
const getMessagePorts = getPrototypeGetter(MessageEvent.prototype, &quot;ports&quot;);
const getMessageSource = getPrototypeGetter(MessageEvent.prototype, &quot;source&quot;);
// oxlint-disable-next-line typescript/unbound-method -- captured before the untrusted fragment runs
const addHostMessageListener = MessagePort.prototype.addEventListener;
// oxlint-disable-next-line typescript/unbound-method -- captured before the untrusted fragment runs
const postHostMessage = MessagePort.prototype.postMessage;
// oxlint-disable-next-line typescript/unbound-method -- captured before the untrusted fragment runs
const startHostPort = MessagePort.prototype.start;
// oxlint-disable-next-line typescript/unbound-method -- captured before the untrusted fragment runs
const stopMessagePropagation = Event.prototype.stopImmediatePropagation;
/** @type {MessagePort | null} */
let hostPort = null;
const postToHost = (type, payload) =&gt; {
if (hostPort != null) {
apply(postHostMessage, hostPort, [{ type, ...payload }]);
}
};
const openExternal = ({ href }) =&gt; {
if (globalThis.navigator.userActivation?.isActive === true) {
postToHost(&quot;open-external&quot;, { href });
}
};
const sendFollowUpMessage = ({ context, prompt, title }) =&gt; {
if (globalThis.navigator.userActivation?.isActive === true) {
postToHost(&quot;follow-up&quot;, { context, prompt, title });
}
return Promise.resolve();
};
/**
* @param {Record&lt;string, unknown&gt; | ((previous: Record&lt;string, unknown&gt; | null) =&gt; Record&lt;string, unknown&gt;)} next
* @returns {Promise&lt;void&gt;}
*/
const setWidgetState = async (next) =&gt; {
const nextState = typeof next === &quot;function&quot; ? next(state) : next;
if (
nextState == null ||
typeof nextState !== &quot;object&quot; ||
isArray(nextState)
) {
throw new TypeError(&quot;Widget state must be a JSON object&quot;);
}
const inputState = stringify(
nextState,
(_key, /** @type {unknown} */ value) =&gt; {
if (
typeof value === &quot;undefined&quot; ||
typeof value === &quot;function&quot; ||
typeof value === &quot;symbol&quot; ||
(typeof value === &quot;number&quot; &amp;&amp; !Number.isFinite(value))
) {
throw new TypeError(&quot;Widget state must be a JSON object&quot;);
}
return value;
},
);
const parsedState = parse(inputState);
if (
parsedState == null ||
typeof parsedState !== &quot;object&quot; ||
isArray(parsedState)
) {
throw new TypeError(&quot;Widget state must be a JSON object&quot;);
}
const storedState = {
modelContent: null,
privateContent: null,
...parsedState,
};
const serializedState = stringify(storedState);
if (encode(serializedState).length &gt; 16 * 1024) {
throw new TypeError(
&quot;Widget state must be a JSON object of at most 16 KiB&quot;,
);
}
if (
hasOwn(storedState, &quot;imageIds&quot;) &amp;&amp;
(!isArray(storedState.imageIds) ||
storedState.imageIds.some(
(/** @type {unknown} */ id) =&gt; typeof id !== &quot;string&quot;,
))
) {
throw new TypeError(&quot;Widget state imageIds must be an array of strings&quot;);
}
if (storedState.imageIds?.length &gt; 0) {
throw Object.assign(
new TypeError(&quot;Visualization state does not support image attachments&quot;),
{ code: -32602, data: { code: &quot;images_unsupported&quot; } },
);
}
state = storedState;
globalThis.openai.widgetState = state;
globalThis.dispatchEvent(
new CustomEvent(&quot;openai:set_globals&quot;, {
detail: { globals: { widgetState: state } },
}),
);
if (statePersistence === &quot;none&quot;) {
throw persistenceError();
}
const id = ++nextStateRequestId;
return new Promise((resolve, reject) =&gt; {
const timeout = globalThis.setTimeout(() =&gt; {
pendingStateWrites.delete(id);
reject(persistenceError());
}, 30_000);
pendingStateWrites.set(id, { resolve, reject, timeout, serializedState });
postToHost(&quot;widget-state-write&quot;, { id, state: serializedState });
});
};
const syncTheme = () =&gt; {
const theme = mediaQuery.matches ? &quot;dark&quot; : &quot;light&quot;;
root.dataset.theme = theme;
globalThis.openai = {
...globalThis.openai,
openExternal,
sendFollowUpMessage,
setWidgetState,
theme,
visualizationStyleVariables: {},
visualizationTheme: theme,
widgetState: state,
statePersistence,
stateModelContext: &quot;none&quot;,
};
globalThis.dispatchEvent(
new CustomEvent(&quot;openai:set_globals&quot;, {
detail: { globals: globalThis.openai },
}),
);
};
const sendHeight = () =&gt; {
postToHost(&quot;height&quot;, {
height: Math.ceil(
Math.max(
document.body.scrollHeight,
document.body.getBoundingClientRect().height,
),
),
});
};
globalThis.addEventListener(
&quot;message&quot;,
(event) =&gt; {
const data = apply(getMessageData, event, []);
const ports = apply(getMessagePorts, event, []);
const source = apply(getMessageSource, event, []);
if (
hostPort != null ||
source !== parentWindow ||
typeof data !== &quot;object&quot; ||
data?.type !== &quot;codex-visualization-initialize&quot; ||
!isArray(ports) ||
ports.length !== 1
) {
return;
}
apply(stopMessagePropagation, event, []);
const nextHostPort = ports[0];
try {
apply(addHostMessageListener, nextHostPort, [
&quot;message&quot;,
(event) =&gt; {
const data = apply(getMessageData, event, []);
if (typeof data === &quot;object&quot; &amp;&amp; data?.type === &quot;measure&quot;) {
sendHeight();
} else if (
typeof data === &quot;object&quot; &amp;&amp;
data?.type === &quot;widget-state-result&quot;
) {
/** @type {unknown} */
const id = data.id;
if (typeof id !== &quot;number&quot;) {
return;
}
const pending = pendingStateWrites.get(id);
if (pending != null) {
pendingStateWrites.delete(id);
globalThis.clearTimeout(pending.timeout);
if (data.ok === true) {
pending.resolve();
} else {
pending.reject(persistenceError());
}
}
}
},
]);
apply(startHostPort, nextHostPort, []);
} catch {
return;
}
hostPort = nextHostPort;
for (const [id, { serializedState }] of pendingStateWrites) {
postToHost(&quot;widget-state-write&quot;, { id, state: serializedState });
}
sendHeight();
},
{ capture: true },
);
document.currentScript?.remove();
mediaQuery.addEventListener(&quot;change&quot;, syncTheme);
globalThis.addEventListener(&quot;click&quot;, (event) =&gt; {
if (event.defaultPrevented) {
return;
}
let element = null;
if (event.target instanceof globalThis.Element) {
element = event.target;
} else if (event.target instanceof globalThis.Node) {
element = event.target.parentElement;
}
const link = element?.closest(&quot;a[href]&quot;);
const href = link?.getAttribute(&quot;href&quot;);
if (href == null) {
return;
}
if (href.startsWith(&quot;#&quot;)) {
let id;
try {
id = decodeURIComponent(href.slice(1));
} catch {
return;
}
const target = id.length === 0 ? root : document.getElementById(id);
if (
target == null ||
globalThis.navigator.userActivation?.isActive !== true
) {
return;
}
event.preventDefault();
postToHost(&quot;scroll-to&quot;, {
top: target.getBoundingClientRect().top + globalThis.scrollY,
});
return;
}
event.preventDefault();
openExternal({ href });
});
new ResizeObserver(sendHeight).observe(document.body);
syncTheme();
})();
&lt;/script&gt;
&lt;script&gt;// Bundled into the sandbox only when the fragment references viz-calendar.
// Register before parsing the fragment: connectedCallback builds the styled
// shadow tree synchronously, without a network request or hydration frame.
(() =&gt; {
if (customElements.get(&quot;viz-calendar&quot;)) return;
const style = `
:host{display:block;min-width:0;padding:12px;font:400 14px/1.5 Inter,system-ui,sans-serif;color:var(--foreground);container-type:inline-size}
*{box-sizing:border-box}
.layout{display:grid;grid-template-columns:88px minmax(0,1fr);gap:24px}
.date{display:flex;flex-direction:column;gap:8px;padding-top:8px;border-right:1px solid var(--border)}
.weekday,.context{color:color-mix(in srgb,var(--foreground) 65%,var(--background))}
.day{font-size:48px;line-height:1.1;letter-spacing:-.03em}
.context{font-size:12px;line-height:16px;overflow-wrap:anywhere}
.axis{--hour:max(48px,var(--minimum-hour));position:relative;height:calc(var(--hours)*var(--hour) + 16px);margin-top:8px;min-width:0}
.axis.compact{--hour:max(32px,var(--minimum-hour))}
.hour{position:absolute;top:calc(var(--at)*var(--hour));inset-inline:0;color:var(--muted-foreground)}
.hour span{display:block;width:36px;text-align:right;transform:translateY(-50%);font-variant-numeric:tabular-nums}
.hour::after{content:&quot;&quot;;position:absolute;top:0;left:48px;right:0;border-top:1px solid var(--border)}
.events{position:absolute;inset:0 0 16px 48px;margin:0;padding:0;list-style:none;overflow:hidden}
.event{position:absolute;top:calc(var(--at)*var(--hour) + 2px);height:calc(var(--duration)*var(--hour) - 4px);left:calc(var(--lane)*100%/var(--lanes) + var(--lane)*8px/var(--lanes));width:calc((100% - (var(--lanes) - 1)*8px)/var(--lanes));border-radius:8px;background:linear-gradient(110deg,color-mix(in srgb,var(--tone) 24%,var(--background)),color-mix(in srgb,var(--tone) 32%,var(--background)));overflow:hidden;container-type:size}
.event.past{background:color-mix(in srgb,var(--tone) 12%,var(--background));color:color-mix(in srgb,var(--foreground) 70%,var(--background))}
.event-body{display:flex;align-items:center;justify-content:space-between;gap:8px;width:100%;height:100%;padding:4px 12px;font:inherit;color:inherit;text-align:left}
button.event-body{appearance:none;border:0;border-radius:inherit;background:transparent}
.cursor-interaction{cursor:var(--cursor-interaction,pointer)}
button.event-body:hover{background:color-mix(in srgb,var(--foreground) 2%,transparent)}
button.event-body:focus-visible{outline:2px solid var(--ring);outline-offset:-2px}
/* Optically center Inter&#x27;s glyphs within the unchanged line boxes. */
.event-content{min-width:0;transform:translateY(-1px)}
.title{display:block;font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.detail{display:block;font-size:12px;line-height:16px;margin-top:4px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
@container(max-height:48px){.detail{display:none}.event-body{padding-block:0}}
.video{width:16px;height:16px;flex:none}
.now{position:absolute;top:calc(var(--at)*var(--hour));left:48px;right:0;border-top:2px solid var(--red);pointer-events:none}
.now::before{content:&quot;&quot;;position:absolute;left:-4px;top:-5px;width:8px;height:8px;border-radius:50%;background:var(--red)}
.now span{position:absolute;right:calc(100% + 12px);transform:translateY(-50%);color:var(--red);font:400 12px/16px Inter,system-ui,sans-serif;font-variant-numeric:tabular-nums}
.empty{position:absolute;top:24px;left:64px;color:var(--muted-foreground)}
@media(pointer:coarse){:host .axis{--hour:max(48px,var(--minimum-hour),var(--minimum-target-hour))}}
@container(max-width:520px){.layout{grid-template-columns:minmax(0,1fr)}.date{flex-direction:row;align-items:baseline;border:0;padding:0}.event-body{padding-inline:8px}}
`;
const escape = (value) =&gt;
String(value).replace(
/[&amp;&lt;&gt;&quot;&#x27;]/g,
(char) =&gt;
({
&quot;&amp;&quot;: &quot;&amp;amp;&quot;,
&quot;&lt;&quot;: &quot;&amp;lt;&quot;,
&quot;&gt;&quot;: &quot;&amp;gt;&quot;,
&#x27;&quot;&#x27;: &quot;&amp;quot;&quot;,
&quot;&#x27;&quot;: &quot;&amp;#39;&quot;,
})[char],
);
const minutes = (value) =&gt; {
if (
typeof value !== &quot;string&quot; ||
!/^(?:[01]\d|2[0-3]):[0-5]\d$|^24:00$/.test(value)
) {
throw new Error(&quot;Use HH:MM times between 00:00 and 24:00.&quot;);
}
return Number(value.slice(0, 2)) * 60 + Number(value.slice(3));
};
class Calendar extends HTMLElement {
#initialized = false;
#events;
static observedAttributes = [
&quot;date&quot;,
&quot;start&quot;,
&quot;end&quot;,
&quot;now&quot;,
&quot;events&quot;,
&quot;lang&quot;,
&quot;time-zone&quot;,
&quot;empty-label&quot;,
&quot;interactive&quot;,
];
constructor() {
super();
// Replay data assigned before the custom element was registered.
if (Object.hasOwn(this, &quot;events&quot;)) {
const events = this.events;
delete this.events;
this.events = events;
}
}
get events() {
return this.#events === undefined
? JSON.parse(this.getAttribute(&quot;events&quot;) ?? &quot;[]&quot;)
: this.#events;
}
set events(events) {
this.#events = events;
if (this.#initialized) this.render();
}
connectedCallback() {
// Keep the shared widget surface outside Shadow DOM; only layout is private.
this.classList.add(&quot;widget&quot;);
// Preserve a server-rendered declarative shadow root when upgrading it.
// https://web.dev/articles/declarative-shadow-dom
if (!this.shadowRoot) {
this.attachShadow({ mode: &quot;open&quot;, serializable: true });
this.render();
} else if (!this.#initialized &amp;&amp; this.#events !== undefined) {
this.render();
}
if (!this.#initialized) {
this.shadowRoot.addEventListener(&quot;click&quot;, (event) =&gt; {
const button = event.target.closest?.(&quot;button[data-event-index]&quot;);
if (!button) return;
const index = Number(button.dataset.eventIndex);
this.dispatchEvent(
new CustomEvent(&quot;eventselect&quot;, {
bubbles: true,
composed: true,
detail: { event: this.events[index], index },
}),
);
});
}
this.#initialized = true;
}
attributeChangedCallback(name, before, after) {
if (before !== after &amp;&amp; this.#initialized &amp;&amp; this.shadowRoot)
this.render();
}
render() {
const shadow = this.shadowRoot;
try {
const date = this.getAttribute(&quot;date&quot;) ?? &quot;&quot;;
const calendarDate = new Date(`${date}T12:00:00Z`);
if (
!/^\d{4}-\d{2}-\d{2}$/.test(date) ||
!Number.isFinite(calendarDate.getTime()) ||
calendarDate.toISOString().slice(0, 10) !== date
) {
throw new Error(&quot;Supply a valid calendar date as YYYY-MM-DD.&quot;);
}
const start = minutes(this.getAttribute(&quot;start&quot;) ?? &quot;09:00&quot;);
const end = minutes(this.getAttribute(&quot;end&quot;) ?? &quot;17:00&quot;);
if (end &lt;= start)
throw new Error(&quot;The visible end must be later than the start.&quot;);
const now = this.hasAttribute(&quot;now&quot;)
? minutes(this.getAttribute(&quot;now&quot;))
: null;
const input = this.events;
if (!Array.isArray(input) || input.length &gt; 200)
throw new Error(&quot;Supply an array of at most 200 events.&quot;);
const tones = new Set([
&quot;blue&quot;,
&quot;green&quot;,
&quot;red&quot;,
&quot;orange&quot;,
&quot;purple&quot;,
&quot;yellow&quot;,
]);
const events = input
.map((event, index) =&gt; {
if (
event == null ||
typeof event !== &quot;object&quot; ||
typeof event.title !== &quot;string&quot;
) {
throw new Error(&quot;Each event needs a title, start and end.&quot;);
}
const from = minutes(event.start),
to = minutes(event.end);
if (to &lt;= from)
throw new Error(&quot;An event must end after it starts.&quot;);
if (event.detail != null &amp;&amp; typeof event.detail !== &quot;string&quot;)
throw new Error(&quot;Event detail must be text.&quot;);
return {
...event,
index,
from,
to,
tone: tones.has(event.tone) ? event.tone : &quot;blue&quot;,
lane: 0,
lanes: 1,
};
})
.filter((event) =&gt; event.from &lt; end &amp;&amp; event.to &gt; start)
.sort((a, b) =&gt; a.from - b.from || b.to - a.to);
// Allocate interval lanes within each connected overlap group. Adjacent
// events share a lane; a chain of overlaps shares one column count.
let group = [],
laneEnds = [],
groupEnd = -1;
const finishGroup = () =&gt; {
for (const event of group) event.lanes = laneEnds.length;
};
for (const event of events) {
if (event.from &gt;= groupEnd) {
finishGroup();
group = [];
laneEnds = [];
}
let lane = laneEnds.findIndex((until) =&gt; until &lt;= event.from);
if (lane &lt; 0) lane = laneEnds.length;
event.lane = lane;
laneEnds[lane] = event.to;
group.push(event);
groupEnd = Math.max(event.to, groupEnd);
}
finishGroup();
const locale = this.lang || document.documentElement.lang || &quot;en&quot;;
const weekday = new Intl.DateTimeFormat(locale, {
weekday: &quot;short&quot;,
timeZone: &quot;UTC&quot;,
}).format(calendarDate);
const fullDate = new Intl.DateTimeFormat(locale, {
dateStyle: &quot;full&quot;,
timeZone: &quot;UTC&quot;,
}).format(calendarDate);
const timeZone = this.getAttribute(&quot;time-zone&quot;);
const ticks = [start];
for (
let tick = (Math.floor(start / 60) + 1) * 60;
tick &lt; end;
tick += 60
)
ticks.push(tick);
ticks.push(end);
const labelTime = (time) =&gt;
`${String(Math.floor(time / 60)).padStart(2, &quot;0&quot;)}:${String(time % 60).padStart(2, &quot;0&quot;)}`;
const compact = end - start &gt; 240;
// Size from full event durations; the window clips partial events
// without letting a one-minute sliver stretch the entire time axis.
const minimumHour = Math.max(
0,
...events.map((event) =&gt; (28 * 60) / (event.to - event.from)),
);
const minimumTargetHour = Math.max(
0,
...events.map((event) =&gt; (48 * 60) / (event.to - event.from)),
);
const hourMarkup = ticks
.map(
(tick) =&gt;
`&lt;div class=&quot;hour&quot; style=&quot;--at:${(tick - start) / 60}&quot;&gt;${now != null &amp;&amp; now &gt;= start &amp;&amp; now &lt;= end &amp;&amp; Math.abs(tick - now) &lt; 15 ? &quot;&quot; : `&lt;span&gt;${tick % 60 ? labelTime(tick) : tick / 60}&lt;/span&gt;`}&lt;/div&gt;`,
)
.join(&quot;&quot;);
const interactive = this.hasAttribute(&quot;interactive&quot;);
const eventMarkup = events
.map((event) =&gt; {
const label = escape(
`${event.title}, ${event.start}-${event.end}${event.detail ? `, ${event.detail}` : &quot;&quot;}`,
);
const body = `button type=&quot;button&quot;${interactive ? ` data-event-index=&quot;${event.index}&quot;` : &quot;&quot;} aria-label=&quot;${label}&quot; data-tooltip=&quot;${label}&quot;`;
return `&lt;li class=&quot;event${now != null &amp;&amp; event.to &lt;= now ? &quot; past&quot; : &quot;&quot;}&quot; style=&quot;--at:${(event.from - start) / 60};--duration:${(event.to - event.from) / 60};--lane:${event.lane};--lanes:${event.lanes};--tone:var(--${event.tone})&quot;&gt;&lt;${body} class=&quot;event-body cursor-interaction&quot;&gt;&lt;span class=&quot;event-content&quot;&gt;&lt;span class=&quot;title&quot;&gt;${escape(event.title)}&lt;/span&gt;${event.detail ? `&lt;span class=&quot;detail&quot;&gt;${escape(event.detail)}&lt;/span&gt;` : &quot;&quot;}&lt;/span&gt;${event.video ? &#x27;&lt;svg class=&quot;video&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;m16 13 5.223 3.482a.5.5 0 0 0 .777-.416V7.87a.5.5 0 0 0-.752-.432L16 10.5&quot;/&gt;&lt;rect x=&quot;2&quot; y=&quot;6&quot; width=&quot;14&quot; height=&quot;12&quot; rx=&quot;2&quot;/&gt;&lt;/svg&gt;&#x27; : &quot;&quot;}&lt;/button&gt;&lt;/li&gt;`;
})
.join(&quot;&quot;);
const nowMarkup =
now != null &amp;&amp; now &gt;= start &amp;&amp; now &lt;= end
? `&lt;div class=&quot;now&quot; style=&quot;--at:${(now - start) / 60}&quot; role=&quot;img&quot; aria-label=&quot;${escape(labelTime(now))}&quot;&gt;&lt;span&gt;${labelTime(now)}&lt;/span&gt;&lt;/div&gt;`
: &quot;&quot;;
shadow.innerHTML = `&lt;style&gt;${style}&lt;/style&gt;&lt;section class=&quot;calendar&quot; aria-label=&quot;${escape(fullDate)}&quot;&gt;&lt;div class=&quot;layout&quot;&gt;&lt;div class=&quot;date&quot;&gt;&lt;span class=&quot;weekday&quot;&gt;${escape(weekday)}&lt;/span&gt;&lt;span class=&quot;day&quot;&gt;${Number(date.slice(-2))}&lt;/span&gt;${timeZone ? `&lt;span class=&quot;context&quot;&gt;${escape(timeZone)}&lt;/span&gt;` : &quot;&quot;}&lt;/div&gt;&lt;div class=&quot;axis${compact ? &quot; compact&quot; : &quot;&quot;}&quot; style=&quot;--hours:${(end - start) / 60};--minimum-hour:${minimumHour}px;--minimum-target-hour:${minimumTargetHour}px&quot;&gt;${hourMarkup}&lt;ol class=&quot;events&quot;&gt;${eventMarkup}&lt;/ol&gt;${nowMarkup}${events.length ? &quot;&quot; : `&lt;span class=&quot;empty&quot;&gt;${escape(this.getAttribute(&quot;empty-label&quot;) ?? &quot;No events&quot;)}&lt;/span&gt;`}&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;`;
} catch (error) {
// Invalid model-authored input remains visible and recoverable through
// the same attributes; never silently invent dates or drop events.
shadow.innerHTML = `&lt;style&gt;${style}&lt;/style&gt;&lt;div class=&quot;calendar&quot; role=&quot;alert&quot;&gt;${escape(error instanceof Error ? error.message : &quot;Invalid calendar data.&quot;)}&lt;/div&gt;`;
}
}
}
customElements.define(&quot;viz-calendar&quot;, Calendar);
})();
&lt;/script&gt;&lt;div id=&quot;bmc-queue-proposals&quot;&gt;
&lt;style&gt;
#bmc-queue-proposals{color-scheme:light dark;--q-bg:light-dark(#edf2f6,#14212c);--q-panel:light-dark(#fff,#1b2c3a);--q-ink:light-dark(#213b50,#e4edf4);--q-muted:light-dark(#536b7d,#b0c1ce);--q-line:light-dark(#d9e3ea,#3a4f60);--q-primary:light-dark(#225d88,#8bc6f1);--q-soft:light-dark(#e8f2fa,#263e52);--q-warn:light-dark(#9c4a17,#ffc494);--q-warn-bg:light-dark(#fff1e6,#463327);font:14px/1.45 system-ui,sans-serif;color:var(--q-ink)}
#bmc-queue-proposals *{box-sizing:border-box}
#bmc-queue-proposals .q-page{background:var(--q-bg);min-height:870px;padding-bottom:22px}
#bmc-queue-proposals .q-nav{display:flex;gap:22px;align-items:center;padding:16px 24px;background:var(--q-panel);border-bottom:1px solid var(--q-line);padding-right:105px}
#bmc-queue-proposals .q-brand{font-weight:700;letter-spacing:.5px}
#bmc-queue-proposals .q-muted{color:var(--q-muted)}
#bmc-queue-proposals .q-title{padding:25px 24px 18px;display:flex;justify-content:space-between;gap:16px;align-items:end;flex-wrap:wrap}
#bmc-queue-proposals h2{font-size:25px;letter-spacing:-.7px;line-height:1.2;margin:5px 0 7px;font-weight:650}
#bmc-queue-proposals h3{font-size:15px;margin:0 0 12px;font-weight:650}
#bmc-queue-proposals .q-main{display:grid;grid-template-columns:minmax(0,2fr) minmax(250px,1fr);gap:16px;padding:0 24px}
#bmc-queue-proposals .q-left{min-width:0}
#bmc-queue-proposals .q-surface{background:var(--q-panel);border:1px solid var(--q-line);border-radius:12px;padding:18px;margin-bottom:14px}
#bmc-queue-proposals .q-calendar{background:var(--q-panel);border:1px solid var(--q-line);border-radius:12px;overflow:hidden;align-self:start}
#bmc-queue-proposals .q-calendar-header{padding:16px 16px 0}
#bmc-queue-proposals .q-calendar-header h3{margin-bottom:3px}
#bmc-queue-proposals .q-table-wrap{overflow-x:auto}
#bmc-queue-proposals table{width:100%;border-collapse:collapse;font-size:12px;text-align:left}
#bmc-queue-proposals th{font-weight:500;color:var(--q-muted);padding:9px 7px;background:var(--q-bg);white-space:nowrap}
#bmc-queue-proposals td{padding:15px 7px;border-bottom:1px solid var(--q-line);vertical-align:top;font-variant-numeric:tabular-nums}
#bmc-queue-proposals tr:last-child td{border-bottom:0}
#bmc-queue-proposals td:first-child{min-width:155px}
#bmc-queue-proposals .q-task{padding:0;border:0;color:var(--q-ink);background:transparent;font:inherit;font-weight:650;text-align:left;text-decoration:underline;text-decoration-color:var(--q-line);text-underline-offset:4px;cursor:pointer}
#bmc-queue-proposals .q-caption{display:block;color:var(--q-muted);font-size:12px;margin-top:5px}
#bmc-queue-proposals .q-urgent{color:var(--q-warn)}
#bmc-queue-proposals .q-pill{display:inline-block;padding:3px 7px;border-radius:5px;background:var(--q-soft);color:var(--q-primary);font-size:12px}
#bmc-queue-proposals .q-summary{display:flex;gap:22px;flex-wrap:wrap;font-size:13px}
#bmc-queue-proposals .q-summary strong{display:block;font-size:20px;font-weight:650;margin-top:2px}
#bmc-queue-proposals .q-focus{border-left:4px solid var(--q-primary)}
#bmc-queue-proposals .q-eyebrow{color:var(--q-primary);font-size:12px;font-weight:650;letter-spacing:.5px;text-transform:uppercase}
#bmc-queue-proposals .q-focus h3{font-size:21px;margin:7px 0}
#bmc-queue-proposals .q-focus .q-facts{display:flex;gap:16px;flex-wrap:wrap;margin:15px 0 0;color:var(--q-muted);font-size:13px}
#bmc-queue-proposals .q-warning{padding:10px 12px;background:var(--q-warn-bg);color:var(--q-warn);border-radius:7px;font-size:13px;margin-bottom:16px}
#bmc-queue-proposals .q-group{display:flex;align-items:center;justify-content:space-between;padding:8px 0;gap:10px}
#bmc-queue-proposals .q-group h3{margin:0}
#bmc-queue-proposals .q-detail{border-top:1px solid var(--q-line);margin-top:16px;padding-top:13px;font-size:13px;min-height:65px}
#bmc-queue-proposals .q-picked td{background:var(--q-soft)}
#bmc-queue-proposals .q-bottom{display:flex;gap:22px;padding:16px 24px;margin-top:16px;border-top:1px solid var(--q-line);background:var(--q-panel);font-size:13px}
#bmc-queue-proposals .q-bottom strong{color:var(--q-primary)}
@media(max-width:720px){#bmc-queue-proposals .q-main{grid-template-columns:1fr;padding:0 12px}#bmc-queue-proposals .q-title{padding:18px 12px}#bmc-queue-proposals .q-nav{padding-left:12px;gap:12px;font-size:12px}#bmc-queue-proposals .q-nav .q-optional{display:none}#bmc-queue-proposals .q-page{min-height:1400px}#bmc-queue-proposals .q-surface{padding:12px}}
&lt;/style&gt;
&lt;div class=&quot;viz-carousel&quot; aria-label=&quot;Tre forslag til Min kø&quot; data-previous-label=&quot;Forrige forslag&quot; data-next-label=&quot;Næste forslag&quot;&gt;
&lt;section data-variant=&quot;Fokus&quot; aria-label=&quot;Fokus: én tydelig næste opgave&quot;&gt;&lt;div class=&quot;q-page&quot; data-design=&quot;focus&quot;&gt;&lt;/div&gt;&lt;/section&gt;
&lt;section data-variant=&quot;Arbejdsbord&quot; aria-label=&quot;Arbejdsbord: samlet prioriteret tabel&quot; hidden&gt;&lt;div class=&quot;q-page&quot; data-design=&quot;desk&quot;&gt;&lt;/div&gt;&lt;/section&gt;
&lt;section data-variant=&quot;Dagsplan&quot; aria-label=&quot;Dagsplan: faste aftaler og fleksible opgaver&quot; hidden&gt;&lt;div class=&quot;q-page&quot; data-design=&quot;day&quot;&gt;&lt;/div&gt;&lt;/section&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;script&gt;
(() =&gt; {
const root=document.getElementById(&#x27;bmc-queue-proposals&#x27;);
const tasks=[
{id:241,title:&#x27;Genskab adgang til økonomisystem&#x27;,customer:&#x27;Nordlys Kontor&#x27;,reason:&#x27;Deadline overskredet · ingen tid reserveret&#x27;,deadline:&#x27;I går&#x27;,start:&#x27;I går&#x27;,fixed:&#x27;—&#x27;,estimate:&#x27;45 min&#x27;,allocated:&#x27;0 min&#x27;,urgent:true},
{id:228,title:&#x27;Klargør ny medarbejders pc&#x27;,customer:&#x27;Fjord Service&#x27;,reason:&#x27;Skal være klar inden kundemøde kl. 11&#x27;,deadline:&#x27;I dag 11.00&#x27;,start:&#x27;I dag&#x27;,fixed:&#x27;09.00–10.00&#x27;,estimate:&#x27;1 time&#x27;,allocated:&#x27;1 time&#x27;},
{id:236,title:&#x27;Gennemgå netværk med kunden&#x27;,customer:&#x27;Birk Teknik&#x27;,reason:&#x27;Fast aftale kl. 11 · tidspunktet ligger fast&#x27;,deadline:&#x27;I dag&#x27;,start:&#x27;I dag&#x27;,fixed:&#x27;11.00–12.00&#x27;,estimate:&#x27;1 time&#x27;,allocated:&#x27;1 time&#x27;},
{id:245,title:&#x27;Opdatér server og kontrollér backup&#x27;,customer:&#x27;Nordlys Kontor&#x27;,reason:&#x27;Deadline i dag · mangler 30 min i planen&#x27;,deadline:&#x27;I dag 16.00&#x27;,start:&#x27;I dag&#x27;,fixed:&#x27;—&#x27;,estimate:&#x27;1½ time&#x27;,allocated:&#x27;1 time&#x27;},
{id:252,title:&#x27;Undersøg ustabil VPN-forbindelse&#x27;,customer:&#x27;Fjord Service&#x27;,reason:&#x27;Kan startes i dag · deadline i morgen&#x27;,deadline:&#x27;I morgen&#x27;,start:&#x27;I dag&#x27;,fixed:&#x27;—&#x27;,estimate:&#x27;1 time&#x27;,allocated:&#x27;0 min&#x27;},
{id:254,title:&#x27;Følg op på printerfejl&#x27;,customer:&#x27;Birk Teknik&#x27;,reason:&#x27;Kan løses i et kort ledigt tidsrum&#x27;,deadline:&#x27;Torsdag&#x27;,start:&#x27;I dag&#x27;,fixed:&#x27;—&#x27;,estimate:&#x27;30 min&#x27;,allocated:&#x27;0 min&#x27;}
];
const events=[{id:228,title:&#x27;Klargør pc&#x27;,start:&#x27;09:00&#x27;,end:&#x27;10:00&#x27;,detail:&#x27;Fjord Service · fast aftale&#x27;}, {id:236,title:&#x27;Kundemøde&#x27;,start:&#x27;11:00&#x27;,end:&#x27;12:00&#x27;,detail:&#x27;Birk Teknik · fast aftale&#x27;}, {id:245,title:&#x27;Server og backup&#x27;,start:&#x27;13:00&#x27;,end:&#x27;14:00&#x27;,detail:&#x27;Nordlys Kontor · reserveret arbejdstid&#x27;}];
function rows(list,compact=false){return list.map(t=&gt;`&lt;tr data-task=&quot;${t.id}&quot;&gt;&lt;td&gt;&lt;button type=&quot;button&quot; class=&quot;q-task&quot; data-id=&quot;${t.id}&quot;&gt;${t.title}&lt;/button&gt;&lt;span class=&quot;q-caption&quot;&gt;#${t.id} · ${t.customer}&lt;/span&gt;${!compact?`&lt;span class=&quot;q-caption ${t.urgent?&#x27;q-urgent&#x27;:&#x27;&#x27;}&quot;&gt;${t.reason}&lt;/span&gt;`:&#x27;&#x27;}&lt;/td&gt;&lt;td class=&quot;${t.urgent?&#x27;q-urgent&#x27;:&#x27;&#x27;}&quot;&gt;${t.deadline}&lt;/td&gt;&lt;td&gt;${t.start}&lt;/td&gt;&lt;td&gt;${t.fixed}&lt;/td&gt;&lt;td&gt;${t.estimate}&lt;span class=&quot;q-caption&quot;&gt;${t.allocated} planlagt&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;`).join(&#x27;&#x27;)}
function table(list,compact=false){return `&lt;div class=&quot;q-table-wrap&quot;&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Opgave&lt;/th&gt;&lt;th&gt;Deadline&lt;/th&gt;&lt;th&gt;Start&lt;/th&gt;&lt;th&gt;Fast tid&lt;/th&gt;&lt;th&gt;Estimat&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;${rows(list,compact)}&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;`}
const content={
focus:`&lt;div class=&quot;q-surface q-focus&quot;&gt;&lt;div class=&quot;q-eyebrow&quot;&gt;Tag denne først&lt;/div&gt;&lt;h3&gt;Genskab adgang til økonomisystem&lt;/h3&gt;&lt;span class=&quot;q-caption&quot;&gt;#241 · Nordlys Kontor&lt;/span&gt;&lt;p class=&quot;q-urgent&quot;&gt;Deadline var i går. Der er endnu ikke reserveret tid.&lt;/p&gt;&lt;div class=&quot;q-facts&quot;&gt;&lt;span&gt;Estimat: &lt;strong&gt;45 min&lt;/strong&gt;&lt;/span&gt;&lt;span&gt;Planlagt: &lt;strong&gt;0 min&lt;/strong&gt;&lt;/span&gt;&lt;span&gt;Næste faste aftale: &lt;strong&gt;09.00&lt;/strong&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;q-surface&quot;&gt;&lt;h3&gt;Derefter i dag&lt;/h3&gt;${table(tasks.slice(1))}&lt;div class=&quot;q-detail&quot; aria-live=&quot;polite&quot;&gt;Vælg en opgave for at se begrundelsen for prioriteringen.&lt;/div&gt;&lt;/div&gt;`,
desk:`&lt;div class=&quot;q-surface&quot;&gt;&lt;div class=&quot;q-summary&quot;&gt;&lt;span&gt;Opgaver i dag&lt;strong&gt;6&lt;/strong&gt;&lt;/span&gt;&lt;span&gt;Estimeret arbejde&lt;strong&gt;5 t 45 min&lt;/strong&gt;&lt;/span&gt;&lt;span&gt;Reserveret i kalender&lt;strong&gt;3 timer&lt;/strong&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;q-surface&quot;&gt;&lt;div class=&quot;q-group&quot;&gt;&lt;h3&gt;Dagens opgaver · anbefalet rækkefølge&lt;/h3&gt;&lt;span class=&quot;q-pill&quot;&gt;1 overskredet&lt;/span&gt;&lt;/div&gt;${table(tasks)}&lt;div class=&quot;q-detail&quot; aria-live=&quot;polite&quot;&gt;Vælg en opgave for at se begrundelsen for prioriteringen.&lt;/div&gt;&lt;/div&gt;`,
day:`&lt;div class=&quot;q-warning&quot;&gt;2 t 45 min af dagens estimerede arbejde mangler en plads i kalenderen.&lt;/div&gt;&lt;div class=&quot;q-surface&quot;&gt;&lt;div class=&quot;q-group&quot;&gt;&lt;h3&gt;Skal håndteres først&lt;/h3&gt;&lt;span class=&quot;q-muted&quot;&gt;1 opgave&lt;/span&gt;&lt;/div&gt;${table(tasks.slice(0,1),true)}&lt;div class=&quot;q-group&quot;&gt;&lt;h3&gt;Faste aftaler&lt;/h3&gt;&lt;span class=&quot;q-muted&quot;&gt;Tidspunktet ligger fast&lt;/span&gt;&lt;/div&gt;${table(tasks.slice(1,3),true)}&lt;div class=&quot;q-group&quot;&gt;&lt;h3&gt;Placér i dagens ledige tid&lt;/h3&gt;&lt;span class=&quot;q-muted&quot;&gt;Prioriteret&lt;/span&gt;&lt;/div&gt;${table(tasks.slice(3),true)}&lt;div class=&quot;q-detail&quot; aria-live=&quot;polite&quot;&gt;Vælg en opgave for at se, hvad der mangler i dagsplanen.&lt;/div&gt;&lt;/div&gt;`
};
const names={focus:&#x27;En opgave ad gangen&#x27;,desk:&#x27;Overblik over hele arbejdsdagen&#x27;,day:&#x27;Få opgaverne til at passe ind i dagen&#x27;};
root.querySelectorAll(&#x27;.q-page&#x27;).forEach(page=&gt;{
const design=page.dataset.design;
page.innerHTML=`&lt;div class=&quot;q-nav&quot;&gt;&lt;span class=&quot;q-brand&quot;&gt;BMC HUB&lt;/span&gt;&lt;span class=&quot;q-muted q-optional&quot;&gt;Sager&lt;/span&gt;&lt;strong&gt;Min kø&lt;/strong&gt;&lt;span class=&quot;q-muted q-optional&quot;&gt;Kalender&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;q-title&quot;&gt;&lt;div&gt;&lt;span class=&quot;q-eyebrow&quot;&gt;Christian · Min arbejdsdag&lt;/span&gt;&lt;h2&gt;${names[design]}&lt;/h2&gt;&lt;span class=&quot;q-muted&quot;&gt;Tirsdag 20. oktober 2026&lt;/span&gt;&lt;/div&gt;&lt;span class=&quot;q-muted&quot;&gt;Designforslag · eksempeldata&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;q-main&quot;&gt;&lt;div class=&quot;q-left&quot;&gt;${content[design]}&lt;/div&gt;&lt;aside class=&quot;q-calendar&quot;&gt;&lt;div class=&quot;q-calendar-header&quot;&gt;&lt;h3&gt;Min kalender i dag&lt;/h3&gt;&lt;span class=&quot;q-muted&quot;&gt;07.00–17.00 · 3 aftaler&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;q-calendar-host&quot;&gt;&lt;/div&gt;&lt;/aside&gt;&lt;/div&gt;&lt;div class=&quot;q-bottom&quot;&gt;&lt;span&gt;← Tilbage&lt;/span&gt;&lt;strong&gt;Min kø&lt;/strong&gt;&lt;span&gt;Kalender&lt;/span&gt;&lt;/div&gt;`;
const calendar=document.createElement(&#x27;viz-calendar&#x27;);
calendar.setAttribute(&#x27;date&#x27;,&#x27;2026-10-20&#x27;);calendar.setAttribute(&#x27;start&#x27;,&#x27;07:00&#x27;);calendar.setAttribute(&#x27;end&#x27;,&#x27;17:00&#x27;);calendar.setAttribute(&#x27;lang&#x27;,&#x27;da-DK&#x27;);calendar.setAttribute(&#x27;interactive&#x27;,&#x27;&#x27;);calendar.events=events;
page.querySelector(&#x27;.q-calendar-host&#x27;).append(calendar);
const select=id=&gt;{
const task=tasks.find(t=&gt;t.id===Number(id));if(!task)return;
page.querySelectorAll(&#x27;tr[data-task]&#x27;).forEach(row=&gt;row.classList.toggle(&#x27;q-picked&#x27;,Number(row.dataset.task)===task.id));
page.querySelector(&#x27;.q-detail&#x27;).textContent=`#${task.id} · ${task.reason}. Estimat: ${task.estimate}. Allokeret: ${task.allocated}.`;
};
page.querySelectorAll(&#x27;[data-id]&#x27;).forEach(button=&gt;button.addEventListener(&#x27;click&#x27;,()=&gt;select(button.dataset.id)));
calendar.addEventListener(&#x27;eventselect&#x27;,event=&gt;select(event.detail.event.id));
});
})();
&lt;/script&gt;
&lt;script src=&quot;https://unpkg.com/@floating-ui/core@1.7.3/dist/floating-ui.core.umd.min.js&quot;&gt;&lt;/script&gt;
&lt;script src=&quot;https://unpkg.com/@floating-ui/dom@1.7.4/dist/floating-ui.dom.umd.min.js&quot;&gt;&lt;/script&gt;
&lt;script&gt;
(() =&gt; {
if (window.FloatingUIDOM == null) {
return;
}
const { autoUpdate, computePosition, flip, offset, shift, size } =
window.FloatingUIDOM;
const tooltipPlacements = new Set([&quot;top&quot;, &quot;right&quot;, &quot;bottom&quot;, &quot;left&quot;]);
const tooltipId = `codex-visualization-tooltip-${
window.crypto?.randomUUID?.() ?? Date.now().toString(36)
}`;
const tooltipGap = 5;
const hoverDelay = 700;
const skipDelay = 300;
let activeTrigger = null;
let pendingTrigger = null;
let tooltip = null;
let openTimeout = null;
let autoUpdateCleanup = null;
let positionRequest = 0;
let skipDelayUntil = 0;
let lastPointerWasTouch = false;
let pinnedByTouch = false;
const getTrigger = (event) =&gt; {
for (const target of event.composedPath()) {
if (target?.nodeType === Node.ELEMENT_NODE) {
const trigger = target.closest(&quot;[data-tooltip]&quot;);
if (trigger) return trigger;
}
}
return null;
};
const containsTarget = (trigger, target) =&gt;
target?.nodeType != null &amp;&amp; trigger.contains(target);
const getContent = (trigger) =&gt;
trigger.getAttribute(&quot;data-tooltip&quot;)?.trim() ?? &quot;&quot;;
const getPlacement = (trigger) =&gt; {
const placement = trigger.getAttribute(&quot;data-tooltip-placement&quot;);
return tooltipPlacements.has(placement) ? placement : &quot;top&quot;;
};
const getTooltip = () =&gt; {
if (tooltip == null) {
tooltip = document.createElement(&quot;div&quot;);
tooltip.id = tooltipId;
tooltip.className = &quot;tooltip&quot;;
tooltip.setAttribute(&quot;role&quot;, &quot;tooltip&quot;);
}
return tooltip;
};
const setDescribedBy = (trigger, described) =&gt; {
const ids = new Set(
(trigger.getAttribute(&quot;aria-describedby&quot;) ?? &quot;&quot;)
.split(/\s+/)
.filter(Boolean),
);
if (described) {
ids.add(tooltipId);
} else {
ids.delete(tooltipId);
}
if (ids.size === 0) {
trigger.removeAttribute(&quot;aria-describedby&quot;);
} else {
trigger.setAttribute(&quot;aria-describedby&quot;, [...ids].join(&quot; &quot;));
}
};
const clearOpenTimeout = () =&gt; {
if (openTimeout != null) {
window.clearTimeout(openTimeout);
openTimeout = null;
}
pendingTrigger = null;
};
const closeTooltip = (activateSkipDelay = true) =&gt; {
clearOpenTimeout();
pinnedByTouch = false;
positionRequest += 1;
autoUpdateCleanup?.();
autoUpdateCleanup = null;
if (activeTrigger == null) {
return;
}
setDescribedBy(activeTrigger, false);
activeTrigger = null;
tooltip?.remove();
if (activateSkipDelay) {
skipDelayUntil = Date.now() + skipDelay;
}
};
const updatePosition = (trigger, floating) =&gt; {
const request = ++positionRequest;
void computePosition(trigger, floating, {
middleware: [
offset(tooltipGap),
flip({ padding: tooltipGap }),
shift({ padding: tooltipGap }),
size({
padding: tooltipGap,
apply({ availableHeight, availableWidth, elements }) {
elements.floating.style.setProperty(
&quot;--tooltip-available-width&quot;,
`${Math.max(0, availableWidth)}px`,
);
elements.floating.style.setProperty(
&quot;--tooltip-available-height&quot;,
`${Math.max(0, availableHeight)}px`,
);
},
}),
],
placement: getPlacement(trigger),
strategy: &quot;fixed&quot;,
})
.then(({ x, y }) =&gt; {
if (activeTrigger !== trigger || request !== positionRequest) {
return;
}
const scale = window.devicePixelRatio || 1;
floating.style.transform = `translate(${Math.round(x * scale) / scale}px, ${Math.round(y * scale) / scale}px)`;
floating.style.visibility = &quot;visible&quot;;
})
.catch(() =&gt; {
if (activeTrigger === trigger &amp;&amp; request === positionRequest) {
closeTooltip(false);
}
});
};
const openTooltip = (trigger) =&gt; {
const content = getContent(trigger);
if (!trigger.isConnected || content.length === 0) {
return;
}
clearOpenTimeout();
if (activeTrigger === trigger) {
return;
}
closeTooltip();
const floating = getTooltip();
floating.textContent = content;
floating.style.visibility = &quot;hidden&quot;;
floating.style.transform = &quot;translate(0, 0)&quot;;
document.body.appendChild(floating);
activeTrigger = trigger;
setDescribedBy(trigger, true);
autoUpdateCleanup = autoUpdate(trigger, floating, () =&gt; {
updatePosition(trigger, floating);
});
};
const requestOpen = (trigger, immediate = false) =&gt; {
if (
getContent(trigger).length === 0 ||
activeTrigger === trigger ||
pendingTrigger === trigger
) {
return;
}
clearOpenTimeout();
pendingTrigger = trigger;
const delay =
immediate || activeTrigger != null || Date.now() &lt; skipDelayUntil
? 0
: hoverDelay;
if (delay === 0) {
openTooltip(trigger);
return;
}
openTimeout = window.setTimeout(() =&gt; {
openTimeout = null;
pendingTrigger = null;
openTooltip(trigger);
}, delay);
};
const handlePointerOpen = (event) =&gt; {
if (event.pointerType === &quot;touch&quot;) {
return;
}
const trigger = getTrigger(event);
if (trigger == null || containsTarget(trigger, event.relatedTarget)) {
return;
}
requestOpen(trigger);
};
const handlePointer = (event) =&gt; {
lastPointerWasTouch =
event.type === &quot;pointerdown&quot; &amp;&amp; event.pointerType === &quot;touch&quot;;
};
const handleTouchClick = (event) =&gt; {
const isTouch = event.pointerType === &quot;touch&quot; || lastPointerWasTouch;
lastPointerWasTouch = false;
if (!isTouch || event.detail === 0) {
return;
}
const trigger = getTrigger(event);
if (
trigger == null ||
!trigger.isConnected ||
getContent(trigger).length === 0 ||
(pinnedByTouch &amp;&amp; activeTrigger === trigger)
) {
if (pinnedByTouch) {
closeTooltip();
}
return;
}
openTooltip(trigger);
pinnedByTouch = activeTrigger === trigger;
window.setTimeout(() =&gt; {
if (activeTrigger === trigger &amp;&amp; !trigger.isConnected) {
closeTooltip(false);
}
}, 0);
};
const handleLeave = (event) =&gt; {
if (pinnedByTouch) {
return;
}
const trigger = getTrigger(event);
if (trigger == null || containsTarget(trigger, event.relatedTarget)) {
return;
}
if (activeTrigger === trigger || pendingTrigger === trigger) {
closeTooltip();
}
};
const handleFocusIn = (event) =&gt; {
const trigger = getTrigger(event);
if (trigger?.matches(&quot;:focus-visible&quot;)) {
requestOpen(trigger, true);
}
};
const handleKeyDown = (event) =&gt; {
if (event.key === &quot;Escape&quot;) {
closeTooltip();
}
};
const handleContextMenu = (event) =&gt; {
lastPointerWasTouch = false;
const trigger = getTrigger(event);
if (activeTrigger === trigger || pendingTrigger === trigger) {
closeTooltip();
}
};
const handlers = {
pointerover: handlePointerOpen,
pointerdown: handlePointer,
pointercancel: handlePointer,
click: handleTouchClick,
pointerout: handleLeave,
focusin: handleFocusIn,
focusout: handleLeave,
keydown: handleKeyDown,
contextmenu: handleContextMenu,
};
const roots = new Map();
const handledEvents = new WeakSet();
const handleEvent = (event) =&gt; {
if (handledEvents.has(event)) return;
handledEvents.add(event);
handlers[event.type](event);
};
const discoverRoots = (node) =&gt; {
if (node.shadowRoot) listen(node.shadowRoot);
for (const element of node.querySelectorAll(&quot;*&quot;)) {
if (element.shadowRoot) listen(element.shadowRoot);
}
};
const listen = (root) =&gt; {
if (roots.has(root)) return;
for (const type of Object.keys(handlers)) root.addEventListener(type, handleEvent);
const observer = new window.MutationObserver((records) =&gt; {
for (const { addedNodes } of records) {
for (const node of addedNodes) {
if (node.nodeType === Node.ELEMENT_NODE) discoverRoots(node);
}
}
for (const [root, cleanup] of roots) {
if (root.host &amp;&amp; !root.host.isConnected) {
cleanup();
roots.delete(root);
}
}
if (activeTrigger &amp;&amp; !activeTrigger.isConnected) closeTooltip(false);
});
observer.observe(root, { childList: true, subtree: true });
roots.set(root, () =&gt; {
observer.disconnect();
for (const type of Object.keys(handlers)) root.removeEventListener(type, handleEvent);
});
discoverRoots(root);
};
const destroy = () =&gt; {
closeTooltip(false);
lastPointerWasTouch = false;
for (const cleanup of roots.values()) cleanup();
roots.clear();
tooltip = null;
};
// Events between children of one shadow root never reach the document.
listen(document);
window.addEventListener(&quot;pagehide&quot;, destroy, { once: true });
})();
&lt;/script&gt;
&lt;script id=&quot;codex-visualization-tabs&quot;&gt;
(() =&gt; {
const selector = &#x27;.nav[role=&quot;tablist&quot;] [role=&quot;tab&quot;]&#x27;;
const disabled = &#x27;:disabled,[aria-disabled=&quot;true&quot;]&#x27;;
const selectTab = (selected) =&gt; {
const panelId = selected.getAttribute(&quot;aria-controls&quot;);
for (const tab of selected.closest(&#x27;[role=&quot;tablist&quot;]&#x27;).querySelectorAll(&#x27;[role=&quot;tab&quot;]&#x27;)) {
const active = tab === selected;
tab.classList.toggle(&quot;active&quot;, active);
tab.setAttribute(&quot;aria-selected&quot;, String(active));
const id = tab.getAttribute(&quot;aria-controls&quot;);
const panel = id &amp;&amp; document.getElementById(id);
if (panel != null) {
panel.hidden = id !== panelId;
if (!panel.hidden &amp;&amp; selected.id) {
panel.setAttribute(&quot;aria-labelledby&quot;, selected.id);
}
}
}
};
const interact = (event) =&gt; {
const tab = event.target?.closest?.(selector);
if (event.defaultPrevented || tab == null || tab.matches(disabled)) {
return;
}
if (event.type === &quot;click&quot;) {
selectTab(tab);
return;
}
const tabs = Array.from(
tab.closest(&#x27;[role=&quot;tablist&quot;]&#x27;).querySelectorAll(&#x27;[role=&quot;tab&quot;]&#x27;),
).filter((candidate) =&gt; !candidate.matches(disabled));
const current = tabs.indexOf(tab);
let next;
if (event.key === &quot;ArrowRight&quot; || event.key === &quot;ArrowDown&quot;) {
next = (current + 1) % tabs.length;
} else if (event.key === &quot;ArrowLeft&quot; || event.key === &quot;ArrowUp&quot;) {
next = (current + tabs.length - 1) % tabs.length;
} else if (event.key === &quot;Home&quot;) {
next = 0;
} else if (event.key === &quot;End&quot;) {
next = tabs.length - 1;
} else {
return;
}
event.preventDefault();
tabs[next].focus();
selectTab(tabs[next]);
};
for (const list of document.querySelectorAll(&#x27;.nav[role=&quot;tablist&quot;]&#x27;)) {
const selected =
list.querySelector(&#x27;[role=&quot;tab&quot;][aria-selected=&quot;true&quot;]&#x27;) ??
list.querySelector(&#x27;[role=&quot;tab&quot;].active&#x27;) ??
list.querySelector(&#x27;[role=&quot;tab&quot;]&#x27;);
if (selected != null) {
selectTab(selected);
}
}
document.addEventListener(&quot;click&quot;, interact);
document.addEventListener(&quot;keydown&quot;, interact);
window.addEventListener(&quot;pagehide&quot;, () =&gt; {
document.removeEventListener(&quot;click&quot;, interact);
document.removeEventListener(&quot;keydown&quot;, interact);
}, { once: true });
})();
&lt;/script&gt;
&lt;script id=&quot;codex-visualization-carousel&quot;&gt;
(() =&gt; {
for (const root of document.querySelectorAll(&quot;.viz-carousel&quot;)) {
const panels = Array.from(
root.querySelectorAll(&quot;:scope &gt; [data-variant]&quot;),
);
if (panels.length === 0) {
continue;
}
root.setAttribute(&quot;role&quot;, &quot;group&quot;);
panels.forEach((panel, index) =&gt; {
panel.hidden = index !== 0;
panel.setAttribute(&quot;role&quot;, &quot;group&quot;);
if (!panel.hasAttribute(&quot;aria-label&quot;)) {
panel.setAttribute(&quot;aria-label&quot;, panel.dataset.variant);
}
});
root.dataset.activeVariant = panels[0].dataset.variant;
if (panels.length === 1) {
continue;
}
const controls = document.createElement(&quot;div&quot;);
controls.className = &quot;viz-carousel-controls&quot;;
// Portable copy of the settings dropdown&#x27;s legacy glyph; no 14px icon-package preset matches it.
// https://github.com/openai/openai/blob/master/codex/codex-apps/webview/src/icons/chevron.svg
controls.innerHTML = `
&lt;button type=&quot;button&quot; class=&quot;viz-carousel-previous cursor-interaction&quot;&gt;&lt;i data-lucide=&quot;chevron-left&quot; aria-hidden=&quot;true&quot;&gt;&lt;/i&gt;&lt;/button&gt;
&lt;details class=&quot;viz-carousel-picker&quot;&gt;
&lt;summary class=&quot;cursor-interaction&quot;&gt;
&lt;span class=&quot;viz-carousel-count tabular-nums&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;viz-carousel-name&quot;&gt;&lt;/span&gt;
&lt;svg class=&quot;viz-carousel-caret&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 20 21&quot; fill=&quot;none&quot; aria-hidden=&quot;true&quot; focusable=&quot;false&quot;&gt;
&lt;path d=&quot;M15.2793 7.71101C15.539 7.45131 15.961 7.45131 16.2207 7.71101C16.4804 7.97071 16.4804 8.39272 16.2207 8.65242L10.4707 14.4024C10.211 14.6621 9.78902 14.6621 9.52932 14.4024L3.77932 8.65242L3.69436 8.54792C3.52385 8.28979 3.55205 7.93828 3.77932 7.71101C4.00659 7.48374 4.3581 7.45554 4.61623 7.62605L4.72073 7.71101L10 12.9903L15.2793 7.71101Z&quot; fill=&quot;currentColor&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;0.6&quot; /&gt;
&lt;/svg&gt;
&lt;/summary&gt;
&lt;div class=&quot;viz-carousel-options&quot;&gt;&lt;/div&gt;
&lt;/details&gt;
&lt;button type=&quot;button&quot; class=&quot;viz-carousel-next cursor-interaction&quot;&gt;&lt;i data-lucide=&quot;chevron-right&quot; aria-hidden=&quot;true&quot;&gt;&lt;/i&gt;&lt;/button&gt;`;
const previous = controls.querySelector(&quot;.viz-carousel-previous&quot;);
const next = controls.querySelector(&quot;.viz-carousel-next&quot;);
const picker = controls.querySelector(&quot;.viz-carousel-picker&quot;);
const summary = picker.querySelector(&quot;summary&quot;);
const count = controls.querySelector(&quot;.viz-carousel-count&quot;);
const name = controls.querySelector(&quot;.viz-carousel-name&quot;);
const options = controls.querySelector(&quot;.viz-carousel-options&quot;);
const status = document.createElement(&quot;span&quot;);
status.className = &quot;sr-only&quot;;
status.setAttribute(&quot;role&quot;, &quot;status&quot;);
status.setAttribute(&quot;aria-live&quot;, &quot;polite&quot;);
status.setAttribute(&quot;aria-atomic&quot;, &quot;true&quot;);
previous.setAttribute(
&quot;aria-label&quot;,
root.dataset.previousLabel ?? &quot;Previous variant&quot;,
);
next.setAttribute(&quot;aria-label&quot;, root.dataset.nextLabel ?? &quot;Next variant&quot;);
let active = 0;
let mousePointerId = null;
let summaryPointerHandled = false;
summary.addEventListener(&quot;pointerdown&quot;, (event) =&gt; {
summaryPointerHandled =
event.pointerType === &quot;mouse&quot; &amp;&amp; event.button === 0 &amp;&amp; !event.ctrlKey;
if (!summaryPointerHandled) {
return;
}
event.preventDefault();
summary.focus({ preventScroll: true });
picker.open = !picker.open;
mousePointerId = picker.open ? event.pointerId : null;
});
summary.addEventListener(&quot;click&quot;, (event) =&gt; {
if (summaryPointerHandled &amp;&amp; event.detail &gt; 0) {
event.preventDefault();
}
});
const choices = panels.map((panel, index) =&gt; {
const choice = document.createElement(&quot;button&quot;);
choice.type = &quot;button&quot;;
choice.className = &quot;cursor-interaction&quot;;
choice.textContent = panel.dataset.variant;
choice.addEventListener(&quot;click&quot;, () =&gt; {
select(index);
summary.focus();
});
choice.addEventListener(&quot;pointerup&quot;, (event) =&gt; {
if (
picker.open &amp;&amp;
event.pointerType === &quot;mouse&quot; &amp;&amp;
event.button === 0 &amp;&amp;
event.pointerId === mousePointerId
) {
choice.click();
}
});
options.appendChild(choice);
return choice;
});
const select = (index) =&gt; {
const changed = active !== index;
active = index;
panels.forEach((panel, position) =&gt; {
panel.hidden = position !== active;
choices[position].setAttribute(
&quot;aria-current&quot;,
String(position === active),
);
});
root.dataset.activeVariant = panels[active].dataset.variant;
count.textContent = `${active + 1} / ${panels.length}`;
name.textContent = panels[active].dataset.variant;
picker.open = false;
mousePointerId = null;
if (changed) {
status.textContent = `${count.textContent}: ${name.textContent}`;
}
};
previous.addEventListener(&quot;click&quot;, () =&gt;
select((active + panels.length - 1) % panels.length),
);
next.addEventListener(&quot;click&quot;, () =&gt;
select((active + 1) % panels.length),
);
picker.addEventListener(&quot;keydown&quot;, (event) =&gt; {
if (event.key === &quot;Escape&quot; &amp;&amp; picker.open) {
event.preventDefault();
picker.open = false;
mousePointerId = null;
summary.focus();
return;
}
if (event.altKey || event.ctrlKey || event.metaKey) {
return;
}
const index = choices.indexOf(document.activeElement);
let nextIndex;
if (event.key === &quot;ArrowDown&quot;) {
nextIndex = index &lt; 0 ? active : (index + 1) % choices.length;
} else if (event.key === &quot;ArrowUp&quot;) {
nextIndex = index &lt; 0 ? active : (index + choices.length - 1) % choices.length;
} else if (picker.open &amp;&amp; event.key === &quot;Home&quot;) {
nextIndex = 0;
} else if (picker.open &amp;&amp; event.key === &quot;End&quot;) {
nextIndex = choices.length - 1;
} else {
return;
}
event.preventDefault();
picker.open = true;
choices[nextIndex].focus();
});
picker.addEventListener(&quot;focusout&quot;, (event) =&gt; {
if (!picker.contains(event.relatedTarget)) {
picker.open = false;
mousePointerId = null;
}
});
const dismiss = (event) =&gt; {
if (!summary.contains(event.target)) {
mousePointerId = null;
}
if (!picker.contains(event.target)) {
picker.open = false;
}
};
const endPointerSelection = () =&gt; {
mousePointerId = null;
};
const sizePicker = () =&gt; {
root.style.setProperty(
&quot;--viz-carousel-picker-height&quot;,
`${picker.open ? options.offsetHeight : 0}px`,
);
};
picker.addEventListener(&quot;toggle&quot;, sizePicker);
window.addEventListener(&quot;resize&quot;, sizePicker);
document.addEventListener(&quot;pointerdown&quot;, dismiss);
document.addEventListener(&quot;pointerup&quot;, endPointerSelection);
document.addEventListener(&quot;pointercancel&quot;, endPointerSelection);
window.addEventListener(&quot;blur&quot;, endPointerSelection);
window.addEventListener(
&quot;pagehide&quot;,
() =&gt; {
document.removeEventListener(&quot;pointerdown&quot;, dismiss);
document.removeEventListener(&quot;pointerup&quot;, endPointerSelection);
document.removeEventListener(&quot;pointercancel&quot;, endPointerSelection);
window.removeEventListener(&quot;blur&quot;, endPointerSelection);
window.removeEventListener(&quot;resize&quot;, sizePicker);
},
{ once: true },
);
root.append(controls, status);
select(0);
}
})();
&lt;/script&gt;
&lt;script id=&quot;codex-visualization-lucide&quot; async src=&quot;https://unpkg.com/lucide@1.17.0/dist/umd/lucide.js&quot;&gt;&lt;/script&gt;
&lt;script&gt;
(() =&gt; {
const initialize = () =&gt; {
globalThis.lucide?.createIcons({ attrs: { width: 16, height: 16 } });
};
if (globalThis.lucide != null) {
initialize();
return;
}
document
.getElementById(&quot;codex-visualization-lucide&quot;)
?.addEventListener(&quot;load&quot;, initialize, { once: true });
})();
&lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;
"></iframe>
<script>(() => {
const frame = document.getElementById("codex-visualization");
if (!(frame instanceof HTMLIFrameElement)) {
return;
}
const isShare = document.documentElement.classList.contains(
"visualization-share-root",
);
let viewportHeight = globalThis.innerHeight;
let sizeFingerprint = null;
let sizeWriteTimeout;
let lastSize = null;
let pendingSize = null;
const storageKey = `codex:visualization-widget-state-v2:${JSON.stringify([
globalThis.location.pathname,
globalThis.location.search,
])}`;
const sizeStorageKey = `${storageKey}:share-size-v1`;
const parseState = (serializedState) => {
if (
typeof serializedState !== "string" ||
new TextEncoder().encode(serializedState).length > 16 * 1024
) {
throw new TypeError(
"Widget state must be a JSON object of at most 16 KiB",
);
}
/** @type {unknown} */
const state = JSON.parse(serializedState);
if (
state == null ||
typeof state !== "object" ||
Array.isArray(state) ||
("imageIds" in state &&
(!Array.isArray(state.imageIds) || state.imageIds.length > 0))
) {
throw new TypeError("Widget state must be a JSON object");
}
const storedState = { modelContent: null, privateContent: null, ...state };
const canonicalState = JSON.stringify(
storedState,
(_key, /** @type {unknown} */ value) => {
if (typeof value === "number" && !Number.isFinite(value)) {
throw new TypeError("Widget state must contain finite numbers");
}
return value;
},
);
if (new TextEncoder().encode(canonicalState).length > 16 * 1024) {
throw new TypeError(
"Widget state must be a JSON object of at most 16 KiB",
);
}
return storedState;
};
let storage = null;
let state = null;
try {
const localStorage = globalThis.localStorage;
const probeKey = `${storageKey}:probe`;
localStorage.setItem(probeKey, "1");
localStorage.removeItem(probeKey);
storage = localStorage;
const savedState = storage.getItem(storageKey);
if (savedState != null) {
try {
state = parseState(savedState);
} catch {
// Invalid saved state does not disable future writes.
}
}
} catch {
storage = null;
}
const persistSize = () => {
globalThis.clearTimeout(sizeWriteTimeout);
if (pendingSize == null) {
return;
}
try {
storage?.setItem(sizeStorageKey, pendingSize);
} catch {
// A denied or full cache must not affect the visualization.
}
pendingSize = null;
};
if (isShare) {
globalThis.addEventListener("pagehide", persistSize);
}
const initialGlobals = {
widgetState: state,
statePersistence: storage == null ? "none" : "local",
};
const channel = new MessageChannel();
channel.port1.addEventListener("message", (event) => {
const data = event.data;
if (typeof data !== "object" || data == null) {
return;
}
if (data.type === "widget-state-write") {
if (!Number.isSafeInteger(data.id) || data.id <= 0) {
return;
}
let ok = false;
try {
if (storage != null) {
const state = parseState(data.state);
storage.setItem(storageKey, JSON.stringify(state));
ok = true;
}
} catch {
// Storage denial, quota limits, and invalid state return a failed receipt.
}
channel.port1.postMessage({
type: "widget-state-result",
id: data.id,
ok,
});
return;
}
if (data.type === "height") {
const height = data.height;
if (
typeof height !== "number" ||
!Number.isFinite(height) ||
height < 0
) {
return;
}
if (height <= 10_000) {
const width = isShare && storage != null ? frame.clientWidth : null;
frame.style.height = Math.ceil(height) + "px";
if (isShare && storage != null) {
const size = JSON.stringify({
fingerprint: sizeFingerprint,
width,
viewportHeight: globalThis.innerHeight,
dark: globalThis.matchMedia("(prefers-color-scheme: dark)").matches,
scale: globalThis.devicePixelRatio,
height: Math.ceil(height),
});
if (size !== lastSize) {
lastSize = size;
pendingSize = size;
globalThis.clearTimeout(sizeWriteTimeout);
sizeWriteTimeout = globalThis.setTimeout(persistSize, 100);
}
}
}
if (isShare) {
// Keep unusually tall content accessible without accepting an unbounded height.
if (height > 10_000 && Number.parseFloat(frame.style.height) === 0) {
frame.style.removeProperty("height");
}
frame.scrolling = height > 10_000 ? "auto" : "no";
document.documentElement.classList.add("visualization-share-ready");
}
return;
}
if (data.type === "scroll-to") {
const top = data.top;
if (
typeof top === "number" &&
Number.isFinite(top) &&
top >= 0 &&
globalThis.navigator.userActivation?.isActive === true
) {
globalThis.scrollTo({
top: Math.ceil(
globalThis.scrollY + frame.getBoundingClientRect().top + top,
),
});
}
return;
}
if (data.type === "open-external") {
const rawHref = data.href;
if (typeof rawHref !== "string") {
return;
}
let href;
try {
const url = new URL(rawHref, document.baseURI);
if (url.protocol !== "https:") {
return;
}
href = url.href;
} catch {
return;
}
if (globalThis.navigator.userActivation?.isActive === true) {
const anchor = document.createElement("a");
anchor.href = href;
anchor.rel = "noopener noreferrer";
anchor.target = "_blank";
document.body.appendChild(anchor);
anchor.click();
anchor.remove();
}
return;
}
if (data.type === "follow-up") {
const prompt = data.prompt;
if (
typeof prompt !== "string" ||
globalThis.navigator.userActivation?.isActive !== true
) {
return;
}
const title = data.title;
globalThis.prompt(typeof title === "string" ? title : prompt, prompt);
}
});
channel.port1.start();
const srcdoc = frame.dataset.srcdoc;
if (srcdoc == null) {
return;
}
globalThis.addEventListener("resize", () => {
const height = Number.parseFloat(frame.style.height);
if (!isShare && (!Number.isFinite(height) || height <= viewportHeight)) {
frame.style.removeProperty("height");
}
viewportHeight = globalThis.innerHeight;
channel.port1.postMessage({ type: "measure" });
});
frame.addEventListener(
"load",
() => {
frame.contentWindow?.postMessage(
{ type: "codex-visualization-initialize" },
"*",
[channel.port2],
);
},
{ once: true },
);
// Match the trusted bootstrap element, not the same text in an authored title.
const frameHtml = srcdoc.replace(
'<script type="application/json" id="codex-visualization-widget-state">__CODEX_VISUALIZATION_WIDGET_STATE__<\/script>',
() =>
'<script type="application/json" id="codex-visualization-widget-state">' +
JSON.stringify(initialGlobals).replaceAll("<", "\\u003c") +
"<\/script>",
);
if (isShare && storage != null) {
// Fingerprint the rendered document, including restored widget state, without
// storing its content. This is a layout cache, not a security identity.
let hash = 5381;
for (let index = 0; index < frameHtml.length; index += 1) {
hash = (hash * 33 + frameHtml.charCodeAt(index)) % 4_294_967_296;
}
sizeFingerprint = hash.toString(36);
try {
const size = JSON.parse(storage.getItem(sizeStorageKey) ?? "null");
if (
size?.fingerprint === sizeFingerprint &&
size.width === frame.clientWidth &&
size.viewportHeight === globalThis.innerHeight &&
size.dark ===
globalThis.matchMedia("(prefers-color-scheme: dark)").matches &&
size.scale === globalThis.devicePixelRatio &&
typeof size.height === "number" &&
Number.isFinite(size.height) &&
size.height >= 0 &&
size.height <= 10_000
) {
frame.style.height = size.height + "px";
}
} catch {
// Ignore missing or invalid cache entries and measure the live frame.
}
}
frame.srcdoc = frameHtml;
delete frame.dataset.srcdoc;
})();
</script>
</body>
</html>