@crvouga/mockingbird-service-plane 1.4.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -533,10 +533,10 @@ var OpenAPIReferenceError = class extends Error {
533
533
  };
534
534
  var unescapePointer = (segment) => segment.replace(/~1/g, "/").replace(/~0/g, "~");
535
535
  var isReference = (value) => typeof value === "object" && value !== null && typeof value.$ref === "string";
536
- var resolveRef = (document2, ref) => {
536
+ var resolveRef = (document3, ref) => {
537
537
  if (!ref.startsWith("#/"))
538
538
  throw new OpenAPIReferenceError(ref);
539
- let cursor = document2;
539
+ let cursor = document3;
540
540
  for (const raw of ref.slice(2).split("/")) {
541
541
  const segment = unescapePointer(raw);
542
542
  if (typeof cursor !== "object" || cursor === null || !(segment in cursor)) {
@@ -548,14 +548,14 @@ var resolveRef = (document2, ref) => {
548
548
  throw new OpenAPIReferenceError(ref);
549
549
  return cursor;
550
550
  };
551
- var deref = (document2, value) => {
551
+ var deref = (document3, value) => {
552
552
  let current = value;
553
553
  const seen = /* @__PURE__ */ new Set();
554
554
  while (isReference(current)) {
555
555
  if (seen.has(current.$ref))
556
556
  throw new OpenAPIReferenceError(`${current.$ref} (cycle)`);
557
557
  seen.add(current.$ref);
558
- current = resolveRef(document2, current.$ref);
558
+ current = resolveRef(document3, current.$ref);
559
559
  }
560
560
  return current;
561
561
  };
@@ -573,36 +573,36 @@ var HTTP_METHODS = [
573
573
  ];
574
574
 
575
575
  // ../../openapi/core/dist/document.js
576
- var mergeParameters = (document2, item, own) => {
576
+ var mergeParameters = (document3, item, own) => {
577
577
  const merged = /* @__PURE__ */ new Map();
578
578
  for (const raw of item.parameters ?? []) {
579
- const parameter = deref(document2, raw);
579
+ const parameter = deref(document3, raw);
580
580
  merged.set(`${parameter.in}:${parameter.name}`, parameter);
581
581
  }
582
582
  for (const raw of own ?? []) {
583
- const parameter = deref(document2, raw);
583
+ const parameter = deref(document3, raw);
584
584
  merged.set(`${parameter.in}:${parameter.name}`, parameter);
585
585
  }
586
586
  return [...merged.values()];
587
587
  };
588
- var listOperations = (document2) => {
588
+ var listOperations = (document3) => {
589
589
  const operations = [];
590
- for (const [path, item] of Object.entries(document2.paths)) {
590
+ for (const [path, item] of Object.entries(document3.paths)) {
591
591
  for (const method of HTTP_METHODS) {
592
592
  const operation = item[method];
593
593
  if (operation?.operationId === void 0)
594
594
  continue;
595
595
  const responses = {};
596
596
  for (const [status, response] of Object.entries(operation.responses)) {
597
- responses[status] = deref(document2, response);
597
+ responses[status] = deref(document3, response);
598
598
  }
599
599
  operations.push({
600
600
  operationId: operation.operationId,
601
601
  method,
602
602
  path,
603
603
  operation,
604
- parameters: mergeParameters(document2, item, operation.parameters),
605
- requestBody: operation.requestBody === void 0 ? void 0 : deref(document2, operation.requestBody),
604
+ parameters: mergeParameters(document3, item, operation.parameters),
605
+ requestBody: operation.requestBody === void 0 ? void 0 : deref(document3, operation.requestBody),
606
606
  responses
607
607
  });
608
608
  }
@@ -611,7 +611,7 @@ var listOperations = (document2) => {
611
611
  };
612
612
 
613
613
  // ../../openapi/core/dist/schema.js
614
- var resolveSchema = (document2, schema) => {
614
+ var resolveSchema = (document3, schema) => {
615
615
  let current = schema;
616
616
  const seen = /* @__PURE__ */ new Set();
617
617
  while (typeof current.$ref === "string") {
@@ -620,7 +620,7 @@ var resolveSchema = (document2, schema) => {
620
620
  break;
621
621
  seen.add(ref);
622
622
  const { $ref: _ignored, ...siblings } = current;
623
- const target = resolveRef(document2, ref);
623
+ const target = resolveRef(document3, ref);
624
624
  current = { ...target, ...siblings };
625
625
  }
626
626
  if (current.nullable === true) {
@@ -691,9 +691,9 @@ var FORMAT_PATTERNS = {
691
691
  ipv4: /^(25[0-5]|2[0-4]\d|1?\d?\d)(\.(25[0-5]|2[0-4]\d|1?\d?\d)){3}$/
692
692
  };
693
693
  var graphemeLength = (value) => [...value].length;
694
- var validateValue = (document2, schema, value, path = []) => {
694
+ var validateValue = (document3, schema, value, path = []) => {
695
695
  const errors = [];
696
- const s = resolveSchema(document2, schema);
696
+ const s = resolveSchema(document3, schema);
697
697
  const fail3 = (message) => errors.push({ path, message });
698
698
  const actual = jsonTypeOf(value);
699
699
  if (actual === "undefined") {
@@ -755,7 +755,7 @@ var validateValue = (document2, schema, value, path = []) => {
755
755
  value.forEach((item, i) => {
756
756
  const itemSchema = s.prefixItems?.[i] ?? s.items;
757
757
  if (itemSchema)
758
- errors.push(...validateValue(document2, itemSchema, item, [...path, i]));
758
+ errors.push(...validateValue(document3, itemSchema, item, [...path, i]));
759
759
  });
760
760
  }
761
761
  if (actual === "object") {
@@ -771,16 +771,16 @@ var validateValue = (document2, schema, value, path = []) => {
771
771
  for (const key of keys) {
772
772
  const property = s.properties?.[key];
773
773
  if (property) {
774
- errors.push(...validateValue(document2, property, record[key], [...path, key]));
774
+ errors.push(...validateValue(document3, property, record[key], [...path, key]));
775
775
  continue;
776
776
  }
777
777
  if (s.additionalProperties === false)
778
778
  fail3(`unexpected property ${key}`);
779
779
  else if (typeof s.additionalProperties === "object") {
780
- errors.push(...validateValue(document2, s.additionalProperties, record[key], [...path, key]));
780
+ errors.push(...validateValue(document3, s.additionalProperties, record[key], [...path, key]));
781
781
  }
782
782
  if (s.propertyNames) {
783
- const nameErrors = validateValue(document2, s.propertyNames, key, [...path, key]);
783
+ const nameErrors = validateValue(document3, s.propertyNames, key, [...path, key]);
784
784
  if (nameErrors.length > 0)
785
785
  fail3(`property name ${key} is invalid: ${nameErrors[0]?.message}`);
786
786
  }
@@ -788,15 +788,15 @@ var validateValue = (document2, schema, value, path = []) => {
788
788
  }
789
789
  if (s.allOf)
790
790
  for (const branch of s.allOf)
791
- errors.push(...validateValue(document2, branch, value, path));
792
- if (s.anyOf && !s.anyOf.some((branch) => validateValue(document2, branch, value).length === 0))
791
+ errors.push(...validateValue(document3, branch, value, path));
792
+ if (s.anyOf && !s.anyOf.some((branch) => validateValue(document3, branch, value).length === 0))
793
793
  fail3("matches no anyOf branch");
794
794
  if (s.oneOf) {
795
- const matches2 = s.oneOf.filter((branch) => validateValue(document2, branch, value).length === 0).length;
795
+ const matches2 = s.oneOf.filter((branch) => validateValue(document3, branch, value).length === 0).length;
796
796
  if (matches2 !== 1)
797
797
  fail3(`matches ${matches2} oneOf branches, expected exactly 1`);
798
798
  }
799
- if (s.not && validateValue(document2, s.not, value).length === 0)
799
+ if (s.not && validateValue(document3, s.not, value).length === 0)
800
800
  fail3("matches forbidden `not` schema");
801
801
  return errors;
802
802
  };
@@ -1370,6 +1370,1232 @@ var clearNamespace = (sqlite, namespace) => {
1370
1370
  });
1371
1371
  };
1372
1372
 
1373
+ // ../../ui/dist/reset.js
1374
+ var CSS_RESET = `*,
1375
+ *::before,
1376
+ *::after {
1377
+ box-sizing: border-box;
1378
+ margin: 0;
1379
+ padding: 0;
1380
+ }
1381
+
1382
+ * {
1383
+ scrollbar-width: thin;
1384
+ scrollbar-color: color-mix(in srgb, CanvasText 35%, Canvas) Canvas;
1385
+ }
1386
+
1387
+ *::-webkit-scrollbar {
1388
+ width: 12px;
1389
+ height: 12px;
1390
+ }
1391
+
1392
+ *::-webkit-scrollbar-track,
1393
+ *::-webkit-scrollbar-corner {
1394
+ background: Canvas;
1395
+ }
1396
+
1397
+ *::-webkit-scrollbar-thumb {
1398
+ background: color-mix(in srgb, CanvasText 35%, Canvas);
1399
+ border: 3px solid Canvas;
1400
+ border-radius: 999px;
1401
+ }
1402
+
1403
+ html {
1404
+ hanging-punctuation: first allow-end last;
1405
+ -webkit-text-size-adjust: none;
1406
+ text-size-adjust: none;
1407
+ tab-size: 4;
1408
+ font-family:
1409
+ ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI Variable", "Segoe UI",
1410
+ "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
1411
+ font-size: 16px;
1412
+ line-height: 1.5;
1413
+ -webkit-font-smoothing: antialiased;
1414
+ -moz-osx-font-smoothing: grayscale;
1415
+ text-rendering: optimizeLegibility;
1416
+ -webkit-tap-highlight-color: transparent;
1417
+ scroll-behavior: smooth;
1418
+ background: Canvas;
1419
+ color: CanvasText;
1420
+ }
1421
+
1422
+ body {
1423
+ min-height: 100svh;
1424
+ line-height: 1.5;
1425
+ background: Canvas;
1426
+ color: CanvasText;
1427
+ }
1428
+
1429
+ h1 {
1430
+ font-size: 2em;
1431
+ }
1432
+
1433
+ h2 {
1434
+ font-size: 1.5em;
1435
+ }
1436
+
1437
+ h3 {
1438
+ font-size: 1.17em;
1439
+ }
1440
+
1441
+ h4 {
1442
+ font-size: 1em;
1443
+ }
1444
+
1445
+ h5 {
1446
+ font-size: 0.83em;
1447
+ }
1448
+
1449
+ h6 {
1450
+ font-size: 0.67em;
1451
+ }
1452
+
1453
+ h1,
1454
+ h2,
1455
+ h3,
1456
+ h4,
1457
+ h5,
1458
+ h6 {
1459
+ font-weight: 700;
1460
+ line-height: 1.2;
1461
+ text-wrap: balance;
1462
+ overflow-wrap: break-word;
1463
+ }
1464
+
1465
+ p,
1466
+ li,
1467
+ figcaption,
1468
+ dd,
1469
+ dt,
1470
+ blockquote {
1471
+ overflow-wrap: break-word;
1472
+ }
1473
+
1474
+ p,
1475
+ li,
1476
+ figcaption,
1477
+ blockquote {
1478
+ text-wrap: pretty;
1479
+ }
1480
+
1481
+ ul,
1482
+ ol,
1483
+ menu {
1484
+ list-style: none;
1485
+ }
1486
+
1487
+ a {
1488
+ color: inherit;
1489
+ text-decoration: underline;
1490
+ text-decoration-thickness: 1px;
1491
+ text-underline-offset: 0.15em;
1492
+ text-decoration-skip-ink: auto;
1493
+ }
1494
+
1495
+ b,
1496
+ strong {
1497
+ font-weight: 700;
1498
+ }
1499
+
1500
+ small {
1501
+ font-size: 0.875em;
1502
+ }
1503
+
1504
+ code,
1505
+ kbd,
1506
+ samp,
1507
+ pre {
1508
+ font-family: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
1509
+ font-size: 1em;
1510
+ }
1511
+
1512
+ sub,
1513
+ sup {
1514
+ font-size: 75%;
1515
+ line-height: 0;
1516
+ position: relative;
1517
+ vertical-align: baseline;
1518
+ }
1519
+
1520
+ sub {
1521
+ bottom: -0.25em;
1522
+ }
1523
+
1524
+ sup {
1525
+ top: -0.5em;
1526
+ }
1527
+
1528
+ abbr[title] {
1529
+ text-decoration: underline dotted;
1530
+ text-underline-offset: 0.15em;
1531
+ }
1532
+
1533
+ mark {
1534
+ background: Mark;
1535
+ color: MarkText;
1536
+ }
1537
+
1538
+ hr {
1539
+ height: 0;
1540
+ border: 0;
1541
+ border-top: 1px solid color-mix(in srgb, CanvasText 20%, transparent);
1542
+ color: inherit;
1543
+ }
1544
+
1545
+ img,
1546
+ picture,
1547
+ video,
1548
+ canvas {
1549
+ display: block;
1550
+ max-width: 100%;
1551
+ }
1552
+
1553
+ img,
1554
+ video {
1555
+ height: auto;
1556
+ }
1557
+
1558
+ svg {
1559
+ overflow: hidden;
1560
+ }
1561
+
1562
+ iframe {
1563
+ border: 0;
1564
+ max-width: 100%;
1565
+ }
1566
+
1567
+ table {
1568
+ border-collapse: collapse;
1569
+ border-spacing: 0;
1570
+ }
1571
+
1572
+ fieldset {
1573
+ border: 0;
1574
+ min-inline-size: 0;
1575
+ }
1576
+
1577
+ legend {
1578
+ padding: 0;
1579
+ }
1580
+
1581
+ button,
1582
+ input,
1583
+ select,
1584
+ textarea,
1585
+ optgroup {
1586
+ font: inherit;
1587
+ letter-spacing: inherit;
1588
+ color: inherit;
1589
+ margin: 0;
1590
+ vertical-align: middle;
1591
+ }
1592
+
1593
+ button,
1594
+ input[type="button"],
1595
+ input[type="submit"],
1596
+ input[type="reset"] {
1597
+ appearance: none;
1598
+ background: none;
1599
+ border: 0;
1600
+ padding: 0;
1601
+ cursor: pointer;
1602
+ text-align: center;
1603
+ }
1604
+
1605
+ input,
1606
+ textarea,
1607
+ select {
1608
+ background-color: Canvas;
1609
+ border: 1px solid color-mix(in srgb, CanvasText 28%, transparent);
1610
+ border-radius: 0;
1611
+ padding: 0.5em 0.65em;
1612
+ }
1613
+
1614
+ input[type="checkbox"],
1615
+ input[type="radio"],
1616
+ input[type="range"],
1617
+ input[type="file"],
1618
+ input[type="color"],
1619
+ input[type="button"],
1620
+ input[type="submit"],
1621
+ input[type="reset"],
1622
+ input[type="image"],
1623
+ input[type="hidden"] {
1624
+ background: none;
1625
+ border: 0;
1626
+ padding: 0;
1627
+ border-radius: 0;
1628
+ }
1629
+
1630
+ input[type="checkbox"],
1631
+ input[type="radio"] {
1632
+ accent-color: CanvasText;
1633
+ }
1634
+
1635
+ input[type="search"] {
1636
+ appearance: none;
1637
+ background-color: Canvas;
1638
+ border: 1px solid color-mix(in srgb, CanvasText 28%, transparent);
1639
+ }
1640
+
1641
+ input[type="search"]::-webkit-search-decoration,
1642
+ input[type="search"]::-webkit-search-cancel-button,
1643
+ input[type="search"]::-webkit-search-results-button,
1644
+ input[type="search"]::-webkit-search-results-decoration {
1645
+ -webkit-appearance: none;
1646
+ display: none;
1647
+ }
1648
+
1649
+ input[type="number"] {
1650
+ appearance: textfield;
1651
+ background-color: Canvas;
1652
+ border: 1px solid color-mix(in srgb, CanvasText 28%, transparent);
1653
+ }
1654
+
1655
+ input[type="number"]::-webkit-inner-spin-button,
1656
+ input[type="number"]::-webkit-outer-spin-button {
1657
+ -webkit-appearance: none;
1658
+ margin: 0;
1659
+ }
1660
+
1661
+ textarea {
1662
+ resize: vertical;
1663
+ }
1664
+
1665
+ ::placeholder {
1666
+ opacity: 1;
1667
+ color: color-mix(in srgb, CanvasText 45%, transparent);
1668
+ }
1669
+
1670
+ :focus-visible {
1671
+ outline: 2px solid CanvasText;
1672
+ outline-offset: 2px;
1673
+ }
1674
+
1675
+ :focus:not(:focus-visible) {
1676
+ outline: none;
1677
+ }
1678
+
1679
+ :disabled,
1680
+ [disabled] {
1681
+ cursor: default;
1682
+ opacity: 0.6;
1683
+ }
1684
+
1685
+ label,
1686
+ summary,
1687
+ select,
1688
+ [role="button"] {
1689
+ cursor: pointer;
1690
+ }
1691
+
1692
+ summary {
1693
+ display: list-item;
1694
+ }
1695
+
1696
+ details {
1697
+ display: block;
1698
+ }
1699
+
1700
+ dialog {
1701
+ margin: auto;
1702
+ padding: 1rem;
1703
+ border: 1px solid color-mix(in srgb, CanvasText 20%, transparent);
1704
+ background: Canvas;
1705
+ color: CanvasText;
1706
+ max-width: calc(100vw - 2rem);
1707
+ max-height: calc(100svh - 2rem);
1708
+ }
1709
+
1710
+ dialog:not([open]) {
1711
+ display: none;
1712
+ }
1713
+
1714
+ ::backdrop {
1715
+ background: color-mix(in srgb, CanvasText 40%, transparent);
1716
+ }
1717
+
1718
+ [hidden] {
1719
+ display: none !important;
1720
+ }
1721
+
1722
+ ::selection {
1723
+ background: Highlight;
1724
+ color: HighlightText;
1725
+ }
1726
+
1727
+ @media (prefers-reduced-motion: reduce) {
1728
+ *,
1729
+ *::before,
1730
+ *::after {
1731
+ animation-duration: 0.01ms !important;
1732
+ animation-iteration-count: 1 !important;
1733
+ transition-duration: 0.01ms !important;
1734
+ scroll-behavior: auto !important;
1735
+ }
1736
+ }
1737
+
1738
+ @media (pointer: coarse) {
1739
+ input,
1740
+ select,
1741
+ textarea {
1742
+ font-size: max(16px, 1em);
1743
+ }
1744
+ }`;
1745
+
1746
+ // ../core/dist/admin-client.js
1747
+ function isRecord2(value) {
1748
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1749
+ }
1750
+ function mountAdminBrand(doc, host, service, catalog) {
1751
+ const clip = (value, max) => {
1752
+ if (typeof value !== "string")
1753
+ return null;
1754
+ const trimmed = value.trim();
1755
+ if (trimmed === "")
1756
+ return null;
1757
+ return trimmed.length > max ? trimmed.slice(0, max) : trimmed;
1758
+ };
1759
+ const httpUrl = (value) => {
1760
+ if (typeof value !== "string" || value.length > 2e3)
1761
+ return null;
1762
+ try {
1763
+ const url = new URL(value);
1764
+ if (url.protocol !== "https:" && url.protocol !== "http:")
1765
+ return null;
1766
+ return url.href;
1767
+ } catch {
1768
+ return null;
1769
+ }
1770
+ };
1771
+ const hexColor = (value) => {
1772
+ if (typeof value !== "string")
1773
+ return null;
1774
+ const raw = value.trim();
1775
+ return /^#[0-9a-fA-F]{3,8}$/.test(raw) ? raw : null;
1776
+ };
1777
+ const hostname = (value) => {
1778
+ try {
1779
+ return new URL(value).hostname.replace(/^www\./, "");
1780
+ } catch {
1781
+ return value;
1782
+ }
1783
+ };
1784
+ host.replaceChildren();
1785
+ host.hidden = true;
1786
+ if (!isRecord2(catalog) || !Object.hasOwn(catalog, service))
1787
+ return;
1788
+ const row = catalog[service];
1789
+ if (!isRecord2(row))
1790
+ return;
1791
+ const vendor = clip(row.vendor, 80);
1792
+ const website = httpUrl(row.website);
1793
+ const guide = httpUrl(row.guide);
1794
+ const docs = httpUrl(row.docs);
1795
+ const logo = httpUrl(row.logo);
1796
+ const color = hexColor(row.color);
1797
+ const description = clip(row.description, 280);
1798
+ if (vendor === null || website === null && guide === null && docs === null)
1799
+ return;
1800
+ const add = (node) => {
1801
+ host.append(node);
1802
+ return node;
1803
+ };
1804
+ if (logo !== null) {
1805
+ const img = doc.createElement("img");
1806
+ img.alt = "";
1807
+ img.src = logo;
1808
+ img.width = 16;
1809
+ img.height = 16;
1810
+ img.decoding = "async";
1811
+ img.referrerPolicy = "no-referrer";
1812
+ if (color !== null)
1813
+ img.style.borderColor = color;
1814
+ add(img);
1815
+ }
1816
+ const name = doc.createElement("span");
1817
+ name.className = "vendor-name";
1818
+ name.textContent = vendor;
1819
+ if (description !== null)
1820
+ name.title = description;
1821
+ add(name);
1822
+ const sep = () => {
1823
+ const node = doc.createElement("span");
1824
+ node.className = "vendor-sep";
1825
+ node.setAttribute("aria-hidden", "true");
1826
+ node.textContent = "\xB7";
1827
+ return node;
1828
+ };
1829
+ const link = (href, label, title) => {
1830
+ const node = doc.createElement("a");
1831
+ node.className = "vendor-link";
1832
+ node.href = href;
1833
+ node.target = "_blank";
1834
+ node.rel = "noopener noreferrer";
1835
+ node.referrerPolicy = "no-referrer";
1836
+ node.textContent = label;
1837
+ node.title = title;
1838
+ return node;
1839
+ };
1840
+ if (website !== null) {
1841
+ add(sep());
1842
+ add(link(website, hostname(website), `${vendor} website`));
1843
+ }
1844
+ if (guide !== null) {
1845
+ add(sep());
1846
+ add(link(guide, "Docs", `Docs for ${vendor}`));
1847
+ }
1848
+ if (docs !== null) {
1849
+ add(sep());
1850
+ add(link(docs, "API", `${vendor} API reference`));
1851
+ }
1852
+ host.removeAttribute("hidden");
1853
+ host.hidden = false;
1854
+ }
1855
+ function startAdminBrand(env) {
1856
+ const requested = env.params.get("brands");
1857
+ let brandsUrl = env.brandsUrl;
1858
+ if (requested) {
1859
+ try {
1860
+ const url = new URL(requested, env.location.href);
1861
+ const local = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1" || url.hostname === "[::1]" || url.origin === env.location.origin;
1862
+ if ((url.protocol === "https:" || url.protocol === "http:") && local)
1863
+ brandsUrl = url.href;
1864
+ } catch {
1865
+ brandsUrl = env.brandsUrl;
1866
+ }
1867
+ }
1868
+ const vendorHost = env.$("vendor");
1869
+ const brandRoot = env.document.querySelector("[data-mockingbird-admin]");
1870
+ const brandService = brandRoot?.getAttribute("data-service") || "";
1871
+ if (vendorHost === null)
1872
+ return;
1873
+ env.fetch(brandsUrl, {
1874
+ headers: { accept: "application/json" },
1875
+ signal: AbortSignal.timeout(5e3)
1876
+ }).then(async (response) => {
1877
+ if (!response.ok)
1878
+ return null;
1879
+ const catalog = await response.json();
1880
+ return catalog;
1881
+ }).then((catalog) => {
1882
+ mountAdminBrand(env.document, vendorHost, brandService, catalog);
1883
+ }).catch(() => {
1884
+ });
1885
+ }
1886
+ function faultPresetList(value) {
1887
+ if (!isRecord2(value))
1888
+ throw new Error("Fault presets response was not an object");
1889
+ if (!Object.hasOwn(value, "presets") || value.presets === void 0 || value.presets === null) {
1890
+ return { presets: [] };
1891
+ }
1892
+ if (!Array.isArray(value.presets))
1893
+ throw new Error("Fault presets response was not a list");
1894
+ return {
1895
+ presets: value.presets.flatMap((item) => {
1896
+ if (!isRecord2(item) || typeof item.name !== "string")
1897
+ return [];
1898
+ return [{ name: item.name }];
1899
+ })
1900
+ };
1901
+ }
1902
+ function bootAdmin(config) {
1903
+ const fieldKinds = ["string", "number", "boolean", "null", "object", "array", "unknown"];
1904
+ const sources = ["declared", "inferred", "mixed"];
1905
+ const isFieldKind = (value) => typeof value === "string" && fieldKinds.some((kind) => kind === value);
1906
+ const isSource = (value) => typeof value === "string" && sources.some((source) => source === value);
1907
+ const readJson2 = (text) => {
1908
+ const value = JSON.parse(text);
1909
+ return value;
1910
+ };
1911
+ const esc = (value) => String(value ?? "").replace(/[&<>"']/g, (char) => {
1912
+ switch (char) {
1913
+ case "&":
1914
+ return "&amp;";
1915
+ case "<":
1916
+ return "&lt;";
1917
+ case ">":
1918
+ return "&gt;";
1919
+ case '"':
1920
+ return "&quot;";
1921
+ default:
1922
+ return "&#39;";
1923
+ }
1924
+ });
1925
+ const el = (id, kind) => {
1926
+ const node = document.getElementById(id);
1927
+ if (!(node instanceof kind))
1928
+ throw new Error(`admin shell is missing #${id}`);
1929
+ return node;
1930
+ };
1931
+ const errorMessage = (data, fallback) => {
1932
+ if (!isRecord2(data))
1933
+ return fallback;
1934
+ const error = data.error;
1935
+ if (!isRecord2(error) || typeof error.message !== "string" || error.message === "") {
1936
+ return fallback;
1937
+ }
1938
+ return error.message;
1939
+ };
1940
+ const requireList = (value, label) => {
1941
+ if (value === void 0 || value === null)
1942
+ return [];
1943
+ if (!Array.isArray(value))
1944
+ throw new Error(`${label} was not a list`);
1945
+ return value;
1946
+ };
1947
+ const state = {
1948
+ namespace: "default",
1949
+ key: sessionStorage.getItem("mockingbird-admin-key") || "",
1950
+ view: "overview",
1951
+ collection: "",
1952
+ shape: null,
1953
+ route: null,
1954
+ editing: null
1955
+ };
1956
+ const standard = new Set(config.standardRoutes);
1957
+ const banner = el("banner", HTMLElement);
1958
+ const keyInput = el("admin-key", HTMLInputElement);
1959
+ keyInput.value = state.key;
1960
+ const params = new URLSearchParams(location.search);
1961
+ const queryKey = params.get("key");
1962
+ if (queryKey) {
1963
+ state.key = queryKey;
1964
+ keyInput.value = state.key;
1965
+ sessionStorage.setItem("mockingbird-admin-key", state.key);
1966
+ params.delete("key");
1967
+ const next = params.toString();
1968
+ history.replaceState(null, "", location.pathname + (next ? `?${next}` : ""));
1969
+ }
1970
+ const queryNamespace = params.get("namespace");
1971
+ if (queryNamespace)
1972
+ state.namespace = queryNamespace;
1973
+ const showError = (error) => {
1974
+ banner.textContent = error instanceof Error ? error.message : String(error);
1975
+ banner.classList.add("show");
1976
+ };
1977
+ const clearError = () => {
1978
+ banner.classList.remove("show");
1979
+ };
1980
+ const api = async (method, path, body) => {
1981
+ const headers = { accept: "application/json" };
1982
+ if (state.key !== "")
1983
+ headers[config.adminKeyHeader] = state.key;
1984
+ if (state.namespace !== "")
1985
+ headers["x-mockingbird-namespace"] = state.namespace;
1986
+ if (body !== void 0)
1987
+ headers["content-type"] = "application/json";
1988
+ const init = { method, headers };
1989
+ if (body !== void 0)
1990
+ init.body = JSON.stringify(body);
1991
+ const response = await fetch(config.adminPrefix + path, init);
1992
+ const text = await response.text();
1993
+ let data = null;
1994
+ if (text !== "") {
1995
+ try {
1996
+ data = readJson2(text);
1997
+ } catch {
1998
+ data = { raw: text };
1999
+ }
2000
+ }
2001
+ if (response.status === 401) {
2002
+ keyInput.focus();
2003
+ throw new Error(errorMessage(data, "Admin key required"));
2004
+ }
2005
+ if (!response.ok) {
2006
+ throw new Error(errorMessage(data, `${response.status} ${method} ${path}`));
2007
+ }
2008
+ return data;
2009
+ };
2010
+ const originGet = async (path) => {
2011
+ const response = await fetch(path, { headers: { accept: "application/json" } });
2012
+ if (!response.ok)
2013
+ throw new Error(`${response.status} ${path}`);
2014
+ const value = await response.json();
2015
+ return value;
2016
+ };
2017
+ const panelApi = {
2018
+ get: (path) => api("GET", path),
2019
+ send: (method, path, body) => api(method, path, body),
2020
+ namespace: () => state.namespace
2021
+ };
2022
+ const isPanelScript = (value) => typeof value === "function";
2023
+ const setTheme = (theme) => {
2024
+ if (theme)
2025
+ document.documentElement.dataset.theme = theme;
2026
+ else
2027
+ delete document.documentElement.dataset.theme;
2028
+ };
2029
+ const storedTheme = localStorage.getItem("mockingbird-admin-theme");
2030
+ if (storedTheme)
2031
+ setTheme(storedTheme);
2032
+ el("theme", HTMLButtonElement).addEventListener("click", () => {
2033
+ const current = document.documentElement.dataset.theme;
2034
+ const dark = current ? current === "dark" : matchMedia("(prefers-color-scheme: dark)").matches;
2035
+ const next = dark ? "light" : "dark";
2036
+ localStorage.setItem("mockingbird-admin-theme", next);
2037
+ setTheme(next);
2038
+ });
2039
+ const show = (view) => {
2040
+ state.view = view;
2041
+ for (const button of document.querySelectorAll("#nav button")) {
2042
+ if (!(button instanceof HTMLButtonElement))
2043
+ continue;
2044
+ button.setAttribute("aria-current", button.dataset.view === view ? "true" : "false");
2045
+ }
2046
+ for (const section of document.querySelectorAll("main .view")) {
2047
+ if (!(section instanceof HTMLElement))
2048
+ continue;
2049
+ section.hidden = section.id !== `view-${view}`;
2050
+ }
2051
+ };
2052
+ el("nav", HTMLElement).addEventListener("click", (event) => {
2053
+ const target = event.target;
2054
+ if (!(target instanceof Element))
2055
+ return;
2056
+ const button = target.closest("button[data-view]");
2057
+ if (!(button instanceof HTMLButtonElement))
2058
+ return;
2059
+ const view = button.dataset.view;
2060
+ if (view === void 0)
2061
+ return;
2062
+ show(view);
2063
+ refresh().catch(showError);
2064
+ });
2065
+ const cards = (items) => `<div class="grid">${items.map((item) => `<article class="card"><div class="k">${item.k}</div><div class="v">${item.v}</div></article>`).join("")}</div>`;
2066
+ const table = (columns, rows) => {
2067
+ if (rows.length === 0)
2068
+ return '<p class="empty">Nothing here yet.</p>';
2069
+ const head = `<tr>${columns.map((column) => `<th>${column.label}</th>`).join("")}</tr>`;
2070
+ const body = rows.map((row) => {
2071
+ const cells = columns.map((column) => `<td data-label="${column.label}">${column.cell(row)}</td>`).join("");
2072
+ const attrs = "attrs" in row && typeof row.attrs === "string" ? row.attrs : "";
2073
+ return `<tr${attrs}>${cells}</tr>`;
2074
+ }).join("");
2075
+ return `<table><thead>${head}</thead><tbody>${body}</tbody></table>`;
2076
+ };
2077
+ const preview = (value) => {
2078
+ const text = JSON.stringify(value) ?? "null";
2079
+ return esc(text.length > 180 ? `${text.slice(0, 180)}\u2026` : text);
2080
+ };
2081
+ const when = (epoch) => {
2082
+ const date = new Date(epoch);
2083
+ return Number.isNaN(date.getTime()) ? String(epoch) : date.toISOString();
2084
+ };
2085
+ const clockCard = (epoch) => {
2086
+ const date = new Date(epoch);
2087
+ if (Number.isNaN(date.getTime()))
2088
+ return esc(String(epoch));
2089
+ const iso = date.toISOString();
2090
+ return `${esc(iso.slice(0, 10))}<span class="clock-time">${esc(iso.slice(11, 19))} UTC</span>`;
2091
+ };
2092
+ const decodeClock = (value) => {
2093
+ if (!isRecord2(value) || typeof value.now !== "number" || typeof value.offsetMs !== "number" || typeof value.frozen !== "boolean") {
2094
+ throw new Error("Clock response was not a clock");
2095
+ }
2096
+ return { now: value.now, offsetMs: value.offsetMs, frozen: value.frozen };
2097
+ };
2098
+ const decodeField = (value) => {
2099
+ if (!isRecord2(value) || typeof value.name !== "string" || !isFieldKind(value.kind)) {
2100
+ return void 0;
2101
+ }
2102
+ const field = {
2103
+ name: value.name,
2104
+ kind: value.kind,
2105
+ optional: value.optional === true
2106
+ };
2107
+ if (typeof value.description === "string")
2108
+ field.description = value.description;
2109
+ if (Array.isArray(value.fields)) {
2110
+ field.fields = value.fields.flatMap((item) => {
2111
+ const child = decodeField(item);
2112
+ return child === void 0 ? [] : [child];
2113
+ });
2114
+ }
2115
+ return field;
2116
+ };
2117
+ const decodeCollection = (value) => {
2118
+ if (!isRecord2(value) || typeof value.name !== "string")
2119
+ return void 0;
2120
+ const fields = Array.isArray(value.fields) ? value.fields.flatMap((item) => {
2121
+ const field = decodeField(item);
2122
+ return field === void 0 ? [] : [field];
2123
+ }) : [];
2124
+ const collection = {
2125
+ name: value.name,
2126
+ label: typeof value.label === "string" ? value.label : value.name,
2127
+ count: typeof value.count === "number" ? value.count : 0,
2128
+ source: isSource(value.source) ? value.source : "inferred",
2129
+ fields
2130
+ };
2131
+ if (typeof value.description === "string")
2132
+ collection.description = value.description;
2133
+ return collection;
2134
+ };
2135
+ const decodeState = (value) => {
2136
+ if (!isRecord2(value) || !Array.isArray(value.collections)) {
2137
+ throw new Error("State response has no collection list");
2138
+ }
2139
+ return {
2140
+ namespace: typeof value.namespace === "string" ? value.namespace : state.namespace,
2141
+ storageNamespace: typeof value.storageNamespace === "string" ? value.storageNamespace : "",
2142
+ collections: value.collections.flatMap((item) => {
2143
+ const collection = decodeCollection(item);
2144
+ return collection === void 0 ? [] : [collection];
2145
+ })
2146
+ };
2147
+ };
2148
+ const decodeRecords = (value) => requireList(isRecord2(value) ? value.records : void 0, "Records").flatMap((item) => {
2149
+ if (!isRecord2(item) || typeof item.id !== "string")
2150
+ return [];
2151
+ return [
2152
+ {
2153
+ id: item.id,
2154
+ seq: typeof item.seq === "number" ? item.seq : 0,
2155
+ value: item.value,
2156
+ attrs: ` class="clickable" data-id="${esc(item.id)}"`
2157
+ }
2158
+ ];
2159
+ });
2160
+ const textOf = (value) => typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? String(value) : "";
2161
+ const decodeJournal = (value) => requireList(isRecord2(value) ? value.requests : void 0, "Journal").flatMap((item) => {
2162
+ if (!isRecord2(item))
2163
+ return [];
2164
+ return [
2165
+ {
2166
+ at: textOf(item.at),
2167
+ method: textOf(item.method),
2168
+ path: textOf(item.path),
2169
+ status: textOf(item.status),
2170
+ durationMs: textOf(item.durationMs),
2171
+ operationId: textOf(item.operationId)
2172
+ }
2173
+ ];
2174
+ });
2175
+ const decodeFaults = (value) => requireList(isRecord2(value) ? value.faults : void 0, "Faults").flatMap((item) => {
2176
+ if (!isRecord2(item))
2177
+ return [];
2178
+ return [
2179
+ {
2180
+ id: textOf(item.id),
2181
+ method: typeof item.method === "string" ? item.method : void 0,
2182
+ pathPrefix: typeof item.pathPrefix === "string" ? item.pathPrefix : void 0,
2183
+ operationId: typeof item.operationId === "string" ? item.operationId : void 0,
2184
+ status: typeof item.status === "number" ? item.status : void 0,
2185
+ effect: typeof item.effect === "string" ? item.effect : void 0,
2186
+ drop: typeof item.drop === "boolean" ? item.drop : void 0,
2187
+ latencyMs: typeof item.latencyMs === "number" ? item.latencyMs : void 0
2188
+ }
2189
+ ];
2190
+ });
2191
+ const loadNamespaces = async () => {
2192
+ const data = await api("GET", "/namespaces");
2193
+ const select = el("namespace", HTMLSelectElement);
2194
+ const listed = requireList(isRecord2(data) ? data.namespaces : void 0, "Namespaces");
2195
+ const fallback = isRecord2(data) && typeof data.default === "string" && data.default !== "" ? data.default : "default";
2196
+ const names = (listed.length > 0 ? listed.map((name) => String(name)) : [fallback]).slice();
2197
+ if (!names.includes(state.namespace))
2198
+ names.unshift(state.namespace);
2199
+ select.innerHTML = names.map((name) => `<option value="${esc(name)}">${esc(name)}</option>`).join("");
2200
+ select.value = state.namespace;
2201
+ };
2202
+ const renderOverview = async () => {
2203
+ const [health, shape, clock, journal] = await Promise.all([
2204
+ originGet("/health"),
2205
+ api("GET", "/state"),
2206
+ api("GET", "/clock"),
2207
+ api("GET", "/requests?limit=8")
2208
+ ]);
2209
+ if (!isRecord2(health))
2210
+ throw new Error("Health response was not an object");
2211
+ const decoded = decodeState(shape);
2212
+ state.shape = decoded;
2213
+ const time = decodeClock(clock);
2214
+ el("overview-cards", HTMLElement).innerHTML = cards([
2215
+ {
2216
+ k: "Service",
2217
+ v: esc(typeof health.service === "string" && health.service !== "" ? health.service : config.service)
2218
+ },
2219
+ {
2220
+ k: "Status",
2221
+ v: esc(typeof health.status === "string" && health.status !== "" ? health.status : "ok")
2222
+ },
2223
+ { k: "Namespace", v: esc(state.namespace) },
2224
+ { k: "Collections", v: String(decoded.collections.length) },
2225
+ {
2226
+ k: "Records",
2227
+ v: String(decoded.collections.reduce((sum, item) => sum + item.count, 0))
2228
+ },
2229
+ { k: "Clock", v: clockCard(time.now) }
2230
+ ]);
2231
+ el("overview-requests", HTMLElement).innerHTML = table([
2232
+ { label: "Method", cell: (row) => esc(row.method) },
2233
+ { label: "Path", cell: (row) => esc(row.path) },
2234
+ {
2235
+ label: "Status",
2236
+ cell: (row) => `<span class="status-${row.status.charAt(0)}">${esc(row.status)}</span>`
2237
+ },
2238
+ { label: "Operation", cell: (row) => esc(row.operationId) }
2239
+ ], decodeJournal(journal));
2240
+ };
2241
+ const schemaChips = (collection) => {
2242
+ if (collection.fields.length === 0) {
2243
+ return '<p class="empty">No fields yet. Add a record or declare the collection on the service.</p>';
2244
+ }
2245
+ return `<div class="chips">${collection.fields.map((field) => `<span class="chip">${esc(field.name)} <i>${esc(field.kind)}${field.optional ? "?" : ""}</i></span>`).join("")}</div>`;
2246
+ };
2247
+ const renderState = async () => {
2248
+ const shape = decodeState(await api("GET", "/state"));
2249
+ state.shape = shape;
2250
+ const host = el("collections", HTMLElement);
2251
+ if (shape.collections.length === 0) {
2252
+ host.innerHTML = '<p class="empty">This namespace has no collections yet.</p>';
2253
+ el("schema", HTMLElement).innerHTML = "";
2254
+ el("records", HTMLElement).innerHTML = "";
2255
+ return;
2256
+ }
2257
+ if (!shape.collections.some((item) => item.name === state.collection)) {
2258
+ const first = shape.collections[0];
2259
+ if (first === void 0)
2260
+ return;
2261
+ state.collection = first.name;
2262
+ }
2263
+ host.innerHTML = shape.collections.map((item) => `<button type="button" class="collection" data-collection="${esc(item.name)}" aria-current="${item.name === state.collection ? "true" : "false"}"><strong>${esc(item.label)}</strong><small>${esc(item.count)} \xB7 ${esc(item.source)}</small></button>`).join("");
2264
+ const selected = shape.collections.find((item) => item.name === state.collection);
2265
+ if (selected === void 0)
2266
+ return;
2267
+ const description = selected.description ? `<p class="lede">${esc(selected.description)}</p>` : "";
2268
+ el("schema", HTMLElement).innerHTML = `<h3>${esc(selected.label)}</h3>${description}${schemaChips(selected)}`;
2269
+ const page = await api("GET", `/state/${encodeURIComponent(state.collection)}?limit=50`);
2270
+ el("records", HTMLElement).innerHTML = table([
2271
+ { label: "Id", cell: (row) => `<code>${esc(row.id)}</code>` },
2272
+ { label: "Seq", cell: (row) => esc(row.seq) },
2273
+ { label: "Value", cell: (row) => preview(row.value) }
2274
+ ], decodeRecords(page));
2275
+ };
2276
+ const renderClock = async () => {
2277
+ const clock = decodeClock(await api("GET", "/clock"));
2278
+ el("clock-readout", HTMLElement).innerHTML = `<div class="k">Now</div><div class="v">${esc(when(clock.now))}</div><p>${esc(clock.now)} ms \xB7 offset ${esc(clock.offsetMs)} ms ${clock.frozen ? "<span class='badge warn'>frozen</span>" : "<span class='badge ok'>live</span>"}</p>`;
2279
+ const freeze = el("freeze", HTMLButtonElement);
2280
+ freeze.textContent = clock.frozen ? "Unfreeze" : "Freeze";
2281
+ freeze.dataset.frozen = clock.frozen ? "1" : "0";
2282
+ };
2283
+ const renderFaults = async () => {
2284
+ const [faults, presets] = await Promise.all([
2285
+ api("GET", "/faults"),
2286
+ api("GET", "/faults/presets").catch(() => ({ presets: [] }))
2287
+ ]);
2288
+ const select = el("fault-preset", HTMLSelectElement);
2289
+ const current = select.value;
2290
+ const list = faultPresetList(presets).presets;
2291
+ select.innerHTML = `<option value="">Custom rule</option>${list.map((preset) => `<option value="${esc(preset.name)}">${esc(preset.name)}</option>`).join("")}`;
2292
+ select.value = current;
2293
+ el("fault-list", HTMLElement).innerHTML = table([
2294
+ { label: "Id", cell: (row) => esc(row.id) },
2295
+ {
2296
+ label: "Match",
2297
+ cell: (row) => esc([row.method, row.pathPrefix, row.operationId].filter((part) => Boolean(part)).join(" ") || "all")
2298
+ },
2299
+ {
2300
+ label: "Effect",
2301
+ cell: (row) => esc(row.status ?? row.effect ?? (row.drop ? "drop" : row.latencyMs || ""))
2302
+ }
2303
+ ], decodeFaults(faults));
2304
+ };
2305
+ const renderJournal = async () => {
2306
+ const journal = await api("GET", "/requests?limit=100");
2307
+ el("journal", HTMLElement).innerHTML = table([
2308
+ { label: "When", cell: (row) => esc(row.at) },
2309
+ { label: "Method", cell: (row) => esc(row.method) },
2310
+ { label: "Path", cell: (row) => esc(row.path) },
2311
+ { label: "Status", cell: (row) => esc(row.status) },
2312
+ { label: "ms", cell: (row) => esc(row.durationMs) }
2313
+ ], decodeJournal(journal));
2314
+ };
2315
+ const renderRoutes = async () => {
2316
+ const data = await api("GET", "/");
2317
+ if (!isRecord2(data))
2318
+ throw new Error("Route list was not an object");
2319
+ const routes = requireList(data.routes, "Routes").map((route) => String(route));
2320
+ el("route-list", HTMLElement).innerHTML = routes.map((route) => {
2321
+ const extra = standard.has(route) ? "" : " <span class='badge'>extension</span>";
2322
+ const parts = route.split(" ");
2323
+ const method = parts[0] ?? "";
2324
+ const path = parts.slice(1).join(" ");
2325
+ return `<div class="route"><span class="method">${esc(method)}</span><span>${esc(path)}${extra}</span><button class="btn" type="button" data-route="${esc(route)}">Use</button></div>`;
2326
+ }).join("");
2327
+ };
2328
+ const renderers = {
2329
+ overview: renderOverview,
2330
+ state: renderState,
2331
+ clock: renderClock,
2332
+ faults: renderFaults,
2333
+ journal: renderJournal,
2334
+ routes: renderRoutes
2335
+ };
2336
+ async function refresh() {
2337
+ clearError();
2338
+ const render = renderers[state.view];
2339
+ if (render)
2340
+ await render();
2341
+ }
2342
+ el("namespace", HTMLSelectElement).addEventListener("change", () => {
2343
+ state.namespace = el("namespace", HTMLSelectElement).value;
2344
+ refresh().catch(showError);
2345
+ });
2346
+ keyInput.addEventListener("change", () => {
2347
+ state.key = keyInput.value.trim();
2348
+ sessionStorage.setItem("mockingbird-admin-key", state.key);
2349
+ refresh().catch(showError);
2350
+ });
2351
+ el("refresh", HTMLButtonElement).addEventListener("click", () => {
2352
+ refresh().catch(showError);
2353
+ });
2354
+ el("collections", HTMLElement).addEventListener("click", (event) => {
2355
+ const target = event.target;
2356
+ if (!(target instanceof Element))
2357
+ return;
2358
+ const button = target.closest("[data-collection]");
2359
+ if (!(button instanceof HTMLElement))
2360
+ return;
2361
+ const collection = button.dataset.collection;
2362
+ if (collection === void 0)
2363
+ return;
2364
+ state.collection = collection;
2365
+ renderState().catch(showError);
2366
+ });
2367
+ el("records", HTMLElement).addEventListener("click", (event) => {
2368
+ const target = event.target;
2369
+ if (!(target instanceof Element))
2370
+ return;
2371
+ const row = target.closest("[data-id]");
2372
+ if (!(row instanceof HTMLElement))
2373
+ return;
2374
+ const id = row.dataset.id;
2375
+ if (id === void 0)
2376
+ return;
2377
+ openEditor(id).catch(showError);
2378
+ });
2379
+ const editor = el("editor", HTMLDialogElement);
2380
+ const editorId = el("editor-id", HTMLInputElement);
2381
+ const editorValue = el("editor-value", HTMLTextAreaElement);
2382
+ const editorDelete = el("editor-delete", HTMLButtonElement);
2383
+ async function openEditor(id) {
2384
+ state.editing = id;
2385
+ el("editor-title", HTMLElement).textContent = id ? "Edit record" : "New record";
2386
+ editorId.value = id ?? "";
2387
+ editorId.readOnly = id !== null;
2388
+ editorDelete.hidden = id === null;
2389
+ if (id) {
2390
+ const record = await api("GET", `/state/${encodeURIComponent(state.collection)}/${encodeURIComponent(id)}`);
2391
+ if (!isRecord2(record))
2392
+ throw new Error("Record response was not an object");
2393
+ editorValue.value = JSON.stringify(record.value, null, 2) ?? "";
2394
+ } else {
2395
+ editorValue.value = "{\n \n}";
2396
+ }
2397
+ editor.showModal();
2398
+ }
2399
+ el("new-record", HTMLButtonElement).addEventListener("click", () => {
2400
+ openEditor(null).catch(showError);
2401
+ });
2402
+ el("editor-form", HTMLFormElement).addEventListener("submit", (event) => {
2403
+ const submitter = event.submitter;
2404
+ if (!(submitter instanceof HTMLButtonElement) || submitter.value !== "save")
2405
+ return;
2406
+ event.preventDefault();
2407
+ let value;
2408
+ try {
2409
+ value = readJson2(editorValue.value);
2410
+ } catch {
2411
+ showError(new Error("Value is not JSON"));
2412
+ return;
2413
+ }
2414
+ const id = editorId.value.trim();
2415
+ const path = `/state/${encodeURIComponent(state.collection)}`;
2416
+ const request = state.editing ? api("PUT", `${path}/${encodeURIComponent(id)}`, { value }) : api("POST", path, { id: id || void 0, value });
2417
+ request.then(() => {
2418
+ editor.close();
2419
+ return renderState();
2420
+ }).catch(showError);
2421
+ });
2422
+ editorDelete.addEventListener("click", () => {
2423
+ const id = state.editing;
2424
+ if (!id)
2425
+ return;
2426
+ api("DELETE", `/state/${encodeURIComponent(state.collection)}/${encodeURIComponent(id)}`).then(() => {
2427
+ editor.close();
2428
+ return renderState();
2429
+ }).catch(showError);
2430
+ });
2431
+ for (const button of document.querySelectorAll("[data-advance]")) {
2432
+ if (!(button instanceof HTMLButtonElement))
2433
+ continue;
2434
+ button.addEventListener("click", () => {
2435
+ const raw = button.dataset.advance;
2436
+ if (raw === void 0)
2437
+ return;
2438
+ api("POST", "/clock", { advance: Number(raw) }).then(() => renderClock()).catch(showError);
2439
+ });
2440
+ }
2441
+ el("freeze", HTMLButtonElement).addEventListener("click", () => {
2442
+ const frozen = el("freeze", HTMLButtonElement).dataset.frozen === "1";
2443
+ api("POST", "/clock", { freeze: !frozen }).then(() => renderClock()).catch(showError);
2444
+ });
2445
+ el("clock-reset", HTMLButtonElement).addEventListener("click", () => {
2446
+ api("POST", "/clock", { reset: true }).then(() => renderClock()).catch(showError);
2447
+ });
2448
+ el("clock-set", HTMLFormElement).addEventListener("submit", (event) => {
2449
+ event.preventDefault();
2450
+ const raw = el("clock-instant", HTMLInputElement).value.trim();
2451
+ const set = /^-?\d+$/.test(raw) ? Number(raw) : raw;
2452
+ api("POST", "/clock", { set }).then(() => renderClock()).catch(showError);
2453
+ });
2454
+ el("fault-form", HTMLFormElement).addEventListener("submit", (event) => {
2455
+ event.preventDefault();
2456
+ const preset = el("fault-preset", HTMLSelectElement).value;
2457
+ if (preset !== "") {
2458
+ api("POST", "/faults", { preset }).then(() => renderFaults()).catch(showError);
2459
+ return;
2460
+ }
2461
+ const status = Number(el("fault-status", HTMLInputElement).value);
2462
+ if (!Number.isFinite(status)) {
2463
+ showError(new Error("Status must be a number, or choose a preset"));
2464
+ return;
2465
+ }
2466
+ const path = el("fault-path", HTMLInputElement).value;
2467
+ const method = el("fault-method", HTMLInputElement).value;
2468
+ api("POST", "/faults", {
2469
+ status,
2470
+ ...path !== "" ? { pathPrefix: path } : {},
2471
+ ...method !== "" ? { method } : {}
2472
+ }).then(() => renderFaults()).catch(showError);
2473
+ });
2474
+ el("fault-clear", HTMLButtonElement).addEventListener("click", () => {
2475
+ api("DELETE", "/faults").then(() => renderFaults()).catch(showError);
2476
+ });
2477
+ el("journal-clear", HTMLButtonElement).addEventListener("click", () => {
2478
+ api("DELETE", "/requests").then(() => renderJournal()).catch(showError);
2479
+ });
2480
+ el("route-list", HTMLElement).addEventListener("click", (event) => {
2481
+ const target = event.target;
2482
+ if (!(target instanceof Element))
2483
+ return;
2484
+ const button = target.closest("[data-route]");
2485
+ if (!(button instanceof HTMLElement))
2486
+ return;
2487
+ const route = button.dataset.route;
2488
+ if (route === void 0)
2489
+ return;
2490
+ state.route = route;
2491
+ el("route-title", HTMLElement).textContent = route;
2492
+ const method = route.split(" ")[0] ?? "";
2493
+ el("route-body", HTMLTextAreaElement).disabled = method === "GET" || method === "DELETE";
2494
+ });
2495
+ el("route-form", HTMLFormElement).addEventListener("submit", (event) => {
2496
+ event.preventDefault();
2497
+ const send = async () => {
2498
+ if (state.route === null)
2499
+ throw new Error("Choose a route first");
2500
+ const parts = state.route.split(" ");
2501
+ const method = parts[0];
2502
+ const path = parts.slice(1).join(" ");
2503
+ if (method === void 0)
2504
+ throw new Error("Choose a route first");
2505
+ const concrete = path.replace(/:([A-Za-z0-9_]+)/g, (_match, key) => prompt(`Value for ${key}`) || "");
2506
+ let body;
2507
+ const routeBody = el("route-body", HTMLTextAreaElement);
2508
+ if (method !== "GET" && method !== "DELETE" && routeBody.value.trim() !== "") {
2509
+ try {
2510
+ body = readJson2(routeBody.value);
2511
+ } catch {
2512
+ throw new Error("Body is not JSON");
2513
+ }
2514
+ }
2515
+ const data = await api(method, concrete, body);
2516
+ el("route-result", HTMLElement).textContent = JSON.stringify(data, null, 2);
2517
+ };
2518
+ send().catch(showError);
2519
+ });
2520
+ const decodePanel = (value) => {
2521
+ if (!isRecord2(value) || typeof value.id !== "string" || typeof value.title !== "string") {
2522
+ return void 0;
2523
+ }
2524
+ const panel = {
2525
+ id: value.id,
2526
+ title: value.title,
2527
+ description: typeof value.description === "string" ? value.description : "",
2528
+ html: typeof value.html === "string" ? value.html : ""
2529
+ };
2530
+ if (typeof value.script === "string")
2531
+ panel.script = value.script;
2532
+ return panel;
2533
+ };
2534
+ const mountPanels = (panels) => {
2535
+ const nav = el("nav", HTMLElement);
2536
+ const views = el("views", HTMLElement);
2537
+ for (const panel of panels) {
2538
+ const button = document.createElement("button");
2539
+ button.type = "button";
2540
+ button.dataset.view = panel.id;
2541
+ button.textContent = panel.title;
2542
+ nav.append(button);
2543
+ const section = document.createElement("section");
2544
+ section.className = "view";
2545
+ section.id = `view-${panel.id}`;
2546
+ section.hidden = true;
2547
+ const title = document.createElement("h2");
2548
+ title.textContent = panel.title;
2549
+ const lede = document.createElement("p");
2550
+ lede.className = "lede";
2551
+ lede.textContent = panel.description;
2552
+ const body = document.createElement("div");
2553
+ body.className = "panel-body";
2554
+ body.id = `panel-${panel.id}`;
2555
+ body.innerHTML = panel.html;
2556
+ section.append(title, lede, body);
2557
+ views.append(section);
2558
+ if (panel.script === void 0)
2559
+ continue;
2560
+ try {
2561
+ const compiled = new Function("root", "api", panel.script);
2562
+ if (!isPanelScript(compiled))
2563
+ throw new Error("panel script did not compile");
2564
+ compiled(body, panelApi);
2565
+ } catch (error) {
2566
+ const note = document.createElement("p");
2567
+ note.className = "banner show";
2568
+ note.textContent = error instanceof Error ? error.message : String(error);
2569
+ body.append(note);
2570
+ }
2571
+ }
2572
+ };
2573
+ startAdminBrand({
2574
+ document,
2575
+ $: (id) => document.getElementById(id),
2576
+ params,
2577
+ location: { href: location.href, origin: location.origin },
2578
+ fetch,
2579
+ brandsUrl: config.brandsUrl
2580
+ });
2581
+ loadNamespaces().then(() => api("GET", "/ui/manifest")).then((manifest) => {
2582
+ const panels = requireList(isRecord2(manifest) ? manifest.panels : void 0, "Panels").flatMap((panel) => {
2583
+ const decoded = decodePanel(panel);
2584
+ return decoded === void 0 ? [] : [decoded];
2585
+ });
2586
+ mountPanels(panels);
2587
+ return refresh();
2588
+ }).catch(showError);
2589
+ }
2590
+ var clientPrelude = [isRecord2, mountAdminBrand, startAdminBrand, faultPresetList, bootAdmin].map((fn) => fn.toString()).join("\n");
2591
+ var adminClientSource = (config) => {
2592
+ const payload = JSON.stringify(config).replace(/</g, "\\u003c");
2593
+ return `<script>
2594
+ ${clientPrelude}
2595
+ bootAdmin(${payload});
2596
+ </script>`;
2597
+ };
2598
+
1373
2599
  // ../core/dist/surface.js
1374
2600
  var defineMock = (runtime) => runtime;
1375
2601
  var STANDARD_ADMIN_ROUTES = [
@@ -1438,94 +2664,106 @@ var escapeHtml = (value) => value.replace(/[&<>"']/g, (char) => {
1438
2664
  return "&#39;";
1439
2665
  }
1440
2666
  });
2667
+ var ADMIN_BRANDS_URL = "https://mockingbird.chrisvouga.dev/brands.json";
1441
2668
  var renderAdminDocument = (service) => {
1442
2669
  const name = escapeHtml(service);
1443
- const standard = JSON.stringify(STANDARD_ADMIN_ROUTES);
2670
+ const script = adminClientSource({
2671
+ standardRoutes: STANDARD_ADMIN_ROUTES,
2672
+ adminPrefix: ADMIN_PREFIX,
2673
+ adminKeyHeader: ADMIN_KEY_HEADER,
2674
+ brandsUrl: ADMIN_BRANDS_URL,
2675
+ service
2676
+ });
1444
2677
  return `<!DOCTYPE html>
1445
2678
  <html lang="en">
1446
2679
  <head>
1447
2680
  <meta charset="utf-8">
1448
2681
  <meta name="viewport" content="width=device-width, initial-scale=1">
1449
2682
  <meta name="color-scheme" content="light dark">
1450
- <link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Crect width='16' height='16' rx='3' fill='%233d4f3a'/%3E%3C/svg%3E">
2683
+ <link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Crect width='16' height='16' rx='2' fill='%2327272a'/%3E%3C/svg%3E">
1451
2684
  <title>${name} admin</title>
1452
2685
  <style>
2686
+ ${CSS_RESET}
1453
2687
  :root {
1454
2688
  color-scheme: light;
1455
- --bg: #f6f5f2;
2689
+ --bg: #fafafa;
1456
2690
  --bg-elev: #ffffff;
1457
- --bg-muted: #eeece6;
1458
- --bg-inset: #f3f1eb;
1459
- --line: #e2dfd6;
1460
- --line-strong: #d0ccc1;
1461
- --ink: #1c1915;
1462
- --muted: #5e584f;
1463
- --faint: #8a8378;
1464
- --accent: #3d4f3a;
1465
- --accent-ink: #f7f6f2;
1466
- --accent-soft: #e5ece2;
1467
- --ok: #1f7a45;
1468
- --ok-soft: #e5f4eb;
1469
- --warn: #9a6700;
1470
- --warn-soft: #fbf3df;
1471
- --err: #a33b32;
1472
- --err-soft: #fdecea;
1473
- --shadow: 0 1px 2px rgb(28 25 21 / 0.05), 0 12px 32px rgb(28 25 21 / 0.06);
1474
- --sans: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
2691
+ --bg-muted: #f4f4f5;
2692
+ --bg-inset: #f4f4f5;
2693
+ --line: #dedee3;
2694
+ --line-strong: #c4c4cc;
2695
+ --ink: #18181b;
2696
+ --muted: #62626b;
2697
+ --faint: #71717a;
2698
+ --accent: #27272a;
2699
+ --accent-ink: #ffffff;
2700
+ --accent-soft: #f4f4f5;
2701
+ --ok: #166534;
2702
+ --ok-soft: #f0fdf4;
2703
+ --warn: #a16207;
2704
+ --warn-soft: #fefce8;
2705
+ --err: #a82d32;
2706
+ --err-soft: #fef2f2;
2707
+ --shadow: none;
2708
+ --sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
1475
2709
  --mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
1476
- --radius: 14px;
1477
- --header: 60px;
1478
- --nav: 220px;
2710
+ --radius: 10px;
2711
+ --header: 56px;
2712
+ --nav: 200px;
1479
2713
  }
1480
2714
  :root[data-theme="dark"] {
1481
2715
  color-scheme: dark;
1482
- --bg: #141311;
1483
- --bg-elev: #1e1c19;
1484
- --bg-muted: #26231f;
1485
- --bg-inset: #181715;
1486
- --line: #34302b;
1487
- --line-strong: #4a453d;
1488
- --ink: #f4f1ea;
1489
- --muted: #c4bdb1;
1490
- --faint: #8f877c;
1491
- --accent: #c5d5b8;
1492
- --accent-ink: #1a2118;
1493
- --accent-soft: #2a3328;
1494
- --ok: #8fd6a8;
1495
- --ok-soft: #1a2e22;
1496
- --warn: #f0cc78;
1497
- --warn-soft: #332911;
1498
- --err: #f0a8a2;
1499
- --err-soft: #3a201e;
1500
- --shadow: 0 1px 2px rgb(0 0 0 / 0.3), 0 16px 40px rgb(0 0 0 / 0.28);
2716
+ --bg: #111113;
2717
+ --bg-elev: #19191c;
2718
+ --bg-muted: #242428;
2719
+ --bg-inset: #111113;
2720
+ --line: #36363c;
2721
+ --line-strong: #52525b;
2722
+ --ink: #f4f4f5;
2723
+ --muted: #a9a9b2;
2724
+ --faint: #71717a;
2725
+ --accent: #e4e4e7;
2726
+ --accent-ink: #18181b;
2727
+ --accent-soft: #242428;
2728
+ --ok: #86efac;
2729
+ --ok-soft: #14241b;
2730
+ --warn: #fde68a;
2731
+ --warn-soft: #2a2410;
2732
+ --err: #ffaaaa;
2733
+ --err-soft: #2c1516;
2734
+ --shadow: none;
1501
2735
  }
1502
2736
  @media (prefers-color-scheme: dark) {
1503
2737
  :root:not([data-theme="light"]) {
1504
2738
  color-scheme: dark;
1505
- --bg: #141311;
1506
- --bg-elev: #1e1c19;
1507
- --bg-muted: #26231f;
1508
- --bg-inset: #181715;
1509
- --line: #34302b;
1510
- --line-strong: #4a453d;
1511
- --ink: #f4f1ea;
1512
- --muted: #c4bdb1;
1513
- --faint: #8f877c;
1514
- --accent: #c5d5b8;
1515
- --accent-ink: #1a2118;
1516
- --accent-soft: #2a3328;
1517
- --ok: #8fd6a8;
1518
- --ok-soft: #1a2e22;
1519
- --warn: #f0cc78;
1520
- --warn-soft: #332911;
1521
- --err: #f0a8a2;
1522
- --err-soft: #3a201e;
1523
- --shadow: 0 1px 2px rgb(0 0 0 / 0.3), 0 16px 40px rgb(0 0 0 / 0.28);
2739
+ --bg: #111113;
2740
+ --bg-elev: #19191c;
2741
+ --bg-muted: #242428;
2742
+ --bg-inset: #111113;
2743
+ --line: #36363c;
2744
+ --line-strong: #52525b;
2745
+ --ink: #f4f4f5;
2746
+ --muted: #a9a9b2;
2747
+ --faint: #71717a;
2748
+ --accent: #e4e4e7;
2749
+ --accent-ink: #18181b;
2750
+ --accent-soft: #242428;
2751
+ --ok: #86efac;
2752
+ --ok-soft: #14241b;
2753
+ --warn: #fde68a;
2754
+ --warn-soft: #2a2410;
2755
+ --err: #ffaaaa;
2756
+ --err-soft: #2c1516;
2757
+ --shadow: none;
1524
2758
  }
1525
2759
  }
1526
- * { box-sizing: border-box; }
1527
- html, body { margin: 0; height: 100%; }
2760
+ /* Fill the viewport, then grow with the content so the background covers the scroll. */
2761
+ html { height: 100%; }
1528
2762
  body {
2763
+ margin: 0;
2764
+ min-height: 100%;
2765
+ display: flex;
2766
+ flex-direction: column;
1529
2767
  font-family: var(--sans);
1530
2768
  background: var(--bg);
1531
2769
  color: var(--ink);
@@ -1534,24 +2772,37 @@ var renderAdminDocument = (service) => {
1534
2772
  padding-right: env(safe-area-inset-right);
1535
2773
  -webkit-text-size-adjust: 100%;
1536
2774
  }
1537
- button, input, select, textarea { font: inherit; color: inherit; }
1538
- button { cursor: pointer; }
1539
2775
  :focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
1540
- @media (prefers-reduced-motion: reduce) {
1541
- * { scroll-behavior: auto !important; transition: none !important; }
1542
- }
1543
- .app { min-height: 100%; display: flex; flex-direction: column; }
2776
+ .app { flex: 1 0 auto; display: flex; flex-direction: column; min-width: 0; width: 100%; }
1544
2777
  header.top {
1545
2778
  position: sticky; top: 0; z-index: 5;
1546
2779
  display: flex; align-items: center; gap: 12px;
1547
2780
  min-height: var(--header); padding: 10px 16px;
1548
- background: color-mix(in srgb, var(--bg-elev) 92%, transparent);
2781
+ background: var(--bg-elev);
1549
2782
  border-bottom: 1px solid var(--line);
1550
- backdrop-filter: blur(10px);
1551
- }
1552
- .brand { display: flex; flex-direction: column; min-width: 0; margin-right: auto; }
1553
- .brand strong { font-size: 15px; letter-spacing: -0.01em; }
1554
- .brand span { color: var(--faint); font-size: 12px; }
2783
+ min-width: 0; max-width: 100%;
2784
+ }
2785
+ .brand {
2786
+ display: flex; flex-flow: row wrap; align-items: center; gap: 14px;
2787
+ min-width: 0; max-width: 100%; margin-right: auto;
2788
+ }
2789
+ .brand-id { display: flex; flex-direction: column; min-width: 0; flex: none; }
2790
+ .brand-id strong { font-size: 15px; letter-spacing: -0.01em; }
2791
+ .brand-id span { color: var(--faint); font-size: 12px; }
2792
+ .vendor {
2793
+ display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
2794
+ min-width: 0; max-width: 100%; font-size: 12px; color: var(--muted);
2795
+ }
2796
+ .vendor[hidden] { display: none; }
2797
+ /* Logos are drawn for a light tile, same as the docs site, so the plate stays white. */
2798
+ .vendor img {
2799
+ width: 16px; height: 16px; padding: 2px; box-sizing: content-box;
2800
+ border-radius: 4px; border: 1px solid var(--line); background: #fff; object-fit: contain;
2801
+ }
2802
+ .vendor-name { color: var(--ink); font-weight: 600; }
2803
+ .vendor-sep { color: var(--faint); }
2804
+ .vendor-link { color: inherit; text-decoration: none; }
2805
+ .vendor-link:hover { color: var(--ink); text-decoration: underline; }
1555
2806
  .controls { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
1556
2807
  label.field { display: flex; flex-direction: column; gap: 2px; font-size: 11px; color: var(--faint); }
1557
2808
  label.field input, label.field select, .controls input[type="password"] {
@@ -1569,7 +2820,7 @@ var renderAdminDocument = (service) => {
1569
2820
  record or the full nav row would widen the page past the viewport. */
1570
2821
  .layout {
1571
2822
  display: grid; grid-template-columns: var(--nav) minmax(0, 1fr);
1572
- flex: 1; min-height: 0; min-width: 0;
2823
+ flex: 1 0 auto; min-width: 0;
1573
2824
  }
1574
2825
  nav.side {
1575
2826
  border-right: 1px solid var(--line); padding: 12px;
@@ -1623,8 +2874,8 @@ var renderAdminDocument = (service) => {
1623
2874
  .banner.show { display: block; }
1624
2875
  .empty { color: var(--muted); padding: 24px 8px; }
1625
2876
  dialog {
1626
- border: 1px solid var(--line); border-radius: 16px; padding: 0; background: var(--bg-elev);
1627
- color: var(--ink); width: min(640px, calc(100vw - 24px)); box-shadow: var(--shadow);
2877
+ border: 1px solid var(--line); border-radius: var(--radius); padding: 0; background: var(--bg-elev);
2878
+ color: var(--ink); width: min(640px, calc(100vw - 24px)); box-shadow: none;
1628
2879
  }
1629
2880
  dialog::backdrop { background: rgb(20 19 17 / 0.45); }
1630
2881
  dialog form, .dialog-body { padding: 16px; display: flex; flex-direction: column; gap: 10px; }
@@ -1651,7 +2902,7 @@ var renderAdminDocument = (service) => {
1651
2902
  padding-top: max(10px, env(safe-area-inset-top));
1652
2903
  }
1653
2904
  input, select, textarea { font-size: 16px; }
1654
- .controls { width: 100%; }
2905
+ .brand, .vendor, .controls { width: 100%; }
1655
2906
  label.field, label.field select, label.field input { flex: 1; min-width: 0; }
1656
2907
  .layout { grid-template-columns: minmax(0, 1fr); }
1657
2908
  nav.side {
@@ -1682,8 +2933,11 @@ var renderAdminDocument = (service) => {
1682
2933
  <div class="app" data-service="${name}" data-mockingbird-admin>
1683
2934
  <header class="top">
1684
2935
  <div class="brand">
1685
- <strong>${name}</strong>
1686
- <span>Mockingbird admin</span>
2936
+ <div class="brand-id">
2937
+ <strong>${name}</strong>
2938
+ <span>Admin</span>
2939
+ </div>
2940
+ <div class="vendor" id="vendor" hidden></div>
1687
2941
  </div>
1688
2942
  <div class="controls">
1689
2943
  <label class="field">Namespace
@@ -1813,460 +3067,7 @@ var renderAdminDocument = (service) => {
1813
3067
  </div>
1814
3068
  </form>
1815
3069
  </dialog>
1816
- <script>
1817
- (() => {
1818
- const STANDARD = new Set(${standard})
1819
- const state = {
1820
- namespace: "default",
1821
- key: sessionStorage.getItem("mockingbird-admin-key") || "",
1822
- view: "overview",
1823
- collection: "",
1824
- shape: null,
1825
- route: null,
1826
- editing: null
1827
- }
1828
- const $ = (id) => document.getElementById(id)
1829
- const banner = $("banner")
1830
- const keyInput = $("admin-key")
1831
- keyInput.value = state.key
1832
- const params = new URLSearchParams(location.search)
1833
- if (params.get("key")) {
1834
- state.key = params.get("key")
1835
- keyInput.value = state.key
1836
- sessionStorage.setItem("mockingbird-admin-key", state.key)
1837
- params.delete("key")
1838
- const next = params.toString()
1839
- history.replaceState(null, "", location.pathname + (next ? "?" + next : ""))
1840
- }
1841
- if (params.get("namespace")) state.namespace = params.get("namespace")
1842
-
1843
- const showError = (error) => {
1844
- banner.textContent = error instanceof Error ? error.message : String(error)
1845
- banner.classList.add("show")
1846
- }
1847
- const clearError = () => banner.classList.remove("show")
1848
-
1849
- async function originGet(path) {
1850
- const response = await fetch(path, { headers: { accept: "application/json" } })
1851
- if (!response.ok) throw new Error(response.status + " " + path)
1852
- return response.json()
1853
- }
1854
- async function api(method, path, body) {
1855
- const headers = { accept: "application/json" }
1856
- if (state.key) headers["${ADMIN_KEY_HEADER}"] = state.key
1857
- if (state.namespace) headers["x-mockingbird-namespace"] = state.namespace
1858
- if (body !== undefined) headers["content-type"] = "application/json"
1859
- const response = await fetch("${ADMIN_PREFIX}" + path, {
1860
- method,
1861
- headers,
1862
- body: body === undefined ? undefined : JSON.stringify(body)
1863
- })
1864
- const text = await response.text()
1865
- let data = null
1866
- if (text) {
1867
- try { data = JSON.parse(text) } catch { data = { raw: text } }
1868
- }
1869
- if (response.status === 401) {
1870
- keyInput.focus()
1871
- throw new Error((data && data.error && data.error.message) || "Admin key required")
1872
- }
1873
- if (!response.ok) {
1874
- throw new Error((data && data.error && data.error.message) || (response.status + " " + method + " " + path))
1875
- }
1876
- return data
1877
- }
1878
- const panelApi = {
1879
- get: (path) => api("GET", path),
1880
- send: (method, path, body) => api(method, path, body),
1881
- namespace: () => state.namespace
1882
- }
1883
-
1884
- function setTheme(theme) {
1885
- if (theme) document.documentElement.dataset.theme = theme
1886
- else delete document.documentElement.dataset.theme
1887
- }
1888
- const storedTheme = localStorage.getItem("mockingbird-admin-theme")
1889
- if (storedTheme) setTheme(storedTheme)
1890
- $("theme").addEventListener("click", () => {
1891
- const current = document.documentElement.dataset.theme
1892
- const dark = current ? current === "dark" : matchMedia("(prefers-color-scheme: dark)").matches
1893
- const next = dark ? "light" : "dark"
1894
- localStorage.setItem("mockingbird-admin-theme", next)
1895
- setTheme(next)
1896
- })
1897
-
1898
- function show(view) {
1899
- state.view = view
1900
- for (const button of document.querySelectorAll("#nav button")) {
1901
- button.setAttribute("aria-current", button.dataset.view === view ? "true" : "false")
1902
- }
1903
- for (const section of document.querySelectorAll("main .view")) {
1904
- section.hidden = section.id !== "view-" + view
1905
- }
1906
- }
1907
- document.getElementById("nav").addEventListener("click", (event) => {
1908
- const button = event.target.closest("button[data-view]")
1909
- if (!button) return
1910
- show(button.dataset.view)
1911
- refresh().catch(showError)
1912
- })
1913
-
1914
- function cards(items) {
1915
- return '<div class="grid">' + items.map((item) =>
1916
- '<article class="card"><div class="k">' + item.k + '</div><div class="v">' + item.v + '</div></article>'
1917
- ).join("") + "</div>"
1918
- }
1919
- function table(columns, rows) {
1920
- if (rows.length === 0) return '<p class="empty">Nothing here yet.</p>'
1921
- const head = "<tr>" + columns.map((column) => "<th>" + column.label + "</th>").join("") + "</tr>"
1922
- const body = rows.map((row) => "<tr" + (row.attrs || "") + ">" + columns.map((column) =>
1923
- '<td data-label="' + column.label + '">' + column.cell(row) + "</td>"
1924
- ).join("") + "</tr>").join("")
1925
- return "<table><thead>" + head + "</thead><tbody>" + body + "</tbody></table>"
1926
- }
1927
- const esc = (value) => String(value ?? "").replace(/[&<>"']/g, (char) => ({
1928
- "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;"
1929
- }[char]))
1930
- const preview = (value) => {
1931
- const text = JSON.stringify(value)
1932
- return esc(text.length > 180 ? text.slice(0, 180) + "\u2026" : text)
1933
- }
1934
- const when = (epoch) => {
1935
- const date = new Date(epoch)
1936
- return Number.isNaN(date.getTime()) ? String(epoch) : date.toISOString()
1937
- }
1938
- const clockCard = (epoch) => {
1939
- const date = new Date(epoch)
1940
- if (Number.isNaN(date.getTime())) return esc(String(epoch))
1941
- const iso = date.toISOString()
1942
- return esc(iso.slice(0, 10)) + '<span class="clock-time">' + esc(iso.slice(11, 19)) + " UTC</span>"
1943
- }
1944
-
1945
- async function loadNamespaces() {
1946
- const data = await api("GET", "/namespaces")
1947
- const select = $("namespace")
1948
- const names = data.namespaces && data.namespaces.length ? data.namespaces : [data.default || "default"]
1949
- if (!names.includes(state.namespace)) names.unshift(state.namespace)
1950
- select.innerHTML = names.map((name) =>
1951
- '<option value="' + esc(name) + '">' + esc(name) + "</option>"
1952
- ).join("")
1953
- select.value = state.namespace
1954
- }
1955
-
1956
- async function renderOverview() {
1957
- const [health, shape, clock, journal] = await Promise.all([
1958
- originGet("/health"),
1959
- api("GET", "/state"),
1960
- api("GET", "/clock"),
1961
- api("GET", "/requests?limit=8")
1962
- ])
1963
- state.shape = shape
1964
- $("overview-cards").innerHTML = cards([
1965
- { k: "Service", v: esc(health.service || "${name}") },
1966
- { k: "Status", v: esc(health.status || "ok") },
1967
- { k: "Namespace", v: esc(state.namespace) },
1968
- { k: "Collections", v: String(shape.collections.length) },
1969
- { k: "Records", v: String(shape.collections.reduce((sum, item) => sum + item.count, 0)) },
1970
- { k: "Clock", v: clockCard(clock.now) }
1971
- ])
1972
- const requests = (journal && journal.requests) || []
1973
- $("overview-requests").innerHTML = table(
1974
- [
1975
- { label: "Method", cell: (row) => esc(row.method) },
1976
- { label: "Path", cell: (row) => esc(row.path) },
1977
- { label: "Status", cell: (row) => '<span class="status-' + String(row.status).charAt(0) + '">' + esc(row.status) + "</span>" },
1978
- { label: "Operation", cell: (row) => esc(row.operationId || "") }
1979
- ],
1980
- requests
1981
- )
1982
- }
1983
-
1984
- function schemaChips(collection) {
1985
- if (!collection || collection.fields.length === 0) {
1986
- return '<p class="empty">No fields yet. Add a record or declare the collection on the service.</p>'
1987
- }
1988
- return '<div class="chips">' + collection.fields.map((field) =>
1989
- '<span class="chip">' + esc(field.name) + ' <i>' + esc(field.kind) + (field.optional ? "?" : "") + "</i></span>"
1990
- ).join("") + "</div>"
1991
- }
1992
-
1993
- async function renderState() {
1994
- const shape = await api("GET", "/state")
1995
- state.shape = shape
1996
- const host = $("collections")
1997
- if (shape.collections.length === 0) {
1998
- host.innerHTML = '<p class="empty">This namespace has no collections yet.</p>'
1999
- $("schema").innerHTML = ""
2000
- $("records").innerHTML = ""
2001
- return
2002
- }
2003
- if (!shape.collections.some((item) => item.name === state.collection)) {
2004
- state.collection = shape.collections[0].name
2005
- }
2006
- host.innerHTML = shape.collections.map((item) =>
2007
- '<button type="button" class="collection" data-collection="' + esc(item.name) + '" aria-current="' +
2008
- (item.name === state.collection ? "true" : "false") + '"><strong>' + esc(item.label) +
2009
- '</strong><small>' + esc(item.count) + " \xB7 " + esc(item.source) + "</small></button>"
2010
- ).join("")
2011
- const selected = shape.collections.find((item) => item.name === state.collection)
2012
- $("schema").innerHTML = "<h3>" + esc(selected.label) + "</h3>" +
2013
- (selected.description ? "<p class=" + '"lede">' + esc(selected.description) + "</p>" : "") +
2014
- schemaChips(selected)
2015
- const page = await api("GET", "/state/" + encodeURIComponent(state.collection) + "?limit=50")
2016
- const rows = page.records.map((record) => ({
2017
- ...record,
2018
- attrs: ' class="clickable" data-id="' + esc(record.id) + '"'
2019
- }))
2020
- $("records").innerHTML = table(
2021
- [
2022
- { label: "Id", cell: (row) => "<code>" + esc(row.id) + "</code>" },
2023
- { label: "Seq", cell: (row) => esc(row.seq) },
2024
- { label: "Value", cell: (row) => preview(row.value) }
2025
- ],
2026
- rows
2027
- )
2028
- }
2029
-
2030
- async function renderClock() {
2031
- const clock = await api("GET", "/clock")
2032
- $("clock-readout").innerHTML = "<div class='k'>Now</div><div class='v'>" + esc(when(clock.now)) +
2033
- "</div><p>" + esc(clock.now) + " ms \xB7 offset " + esc(clock.offsetMs) + " ms " +
2034
- (clock.frozen ? "<span class='badge warn'>frozen</span>" : "<span class='badge ok'>live</span>") + "</p>"
2035
- $("freeze").textContent = clock.frozen ? "Unfreeze" : "Freeze"
2036
- $("freeze").dataset.frozen = clock.frozen ? "1" : "0"
2037
- }
2038
-
2039
- async function renderFaults() {
2040
- const [faults, presets] = await Promise.all([
2041
- api("GET", "/faults"),
2042
- api("GET", "/faults/presets").catch(() => ({ presets: [] }))
2043
- ])
2044
- const select = $("fault-preset")
2045
- const current = select.value
2046
- const list = presets.presets || []
2047
- select.innerHTML = '<option value="">Custom rule</option>' + list.map((preset) =>
2048
- '<option value="' + esc(preset.name) + '">' + esc(preset.name) + "</option>"
2049
- ).join("")
2050
- select.value = current
2051
- const rules = faults.faults || []
2052
- $("fault-list").innerHTML = table(
2053
- [
2054
- { label: "Id", cell: (row) => esc(row.id) },
2055
- { label: "Match", cell: (row) => esc([row.method, row.pathPrefix, row.operationId].filter(Boolean).join(" ") || "all") },
2056
- { label: "Effect", cell: (row) => esc(row.status ?? row.effect ?? (row.drop ? "drop" : row.latencyMs || "")) }
2057
- ],
2058
- rules
2059
- )
2060
- }
2061
-
2062
- async function renderJournal() {
2063
- const journal = await api("GET", "/requests?limit=100")
2064
- $("journal").innerHTML = table(
2065
- [
2066
- { label: "When", cell: (row) => esc(row.at || "") },
2067
- { label: "Method", cell: (row) => esc(row.method) },
2068
- { label: "Path", cell: (row) => esc(row.path) },
2069
- { label: "Status", cell: (row) => esc(row.status) },
2070
- { label: "ms", cell: (row) => esc(row.durationMs) }
2071
- ],
2072
- journal.requests || []
2073
- )
2074
- }
2075
-
2076
- async function renderRoutes() {
2077
- const data = await api("GET", "/")
2078
- const routes = data.routes || []
2079
- $("route-list").innerHTML = routes.map((route) => {
2080
- const extra = STANDARD.has(route) ? "" : " <span class='badge'>extension</span>"
2081
- return '<div class="route"><span class="method">' + esc(route.split(" ")[0]) +
2082
- '</span><span>' + esc(route.split(" ").slice(1).join(" ")) + extra +
2083
- '</span><button class="btn" type="button" data-route="' + esc(route) + '">Use</button></div>'
2084
- }).join("")
2085
- }
2086
-
2087
- const renderers = {
2088
- overview: renderOverview,
2089
- state: renderState,
2090
- clock: renderClock,
2091
- faults: renderFaults,
2092
- journal: renderJournal,
2093
- routes: renderRoutes
2094
- }
2095
-
2096
- async function refresh() {
2097
- clearError()
2098
- if (state.view in renderers) await renderers[state.view]()
2099
- }
2100
-
2101
- $("namespace").addEventListener("change", () => {
2102
- state.namespace = $("namespace").value
2103
- refresh().catch(showError)
2104
- })
2105
- keyInput.addEventListener("change", () => {
2106
- state.key = keyInput.value.trim()
2107
- sessionStorage.setItem("mockingbird-admin-key", state.key)
2108
- refresh().catch(showError)
2109
- })
2110
- $("refresh").addEventListener("click", () => refresh().catch(showError))
2111
-
2112
- $("collections").addEventListener("click", (event) => {
2113
- const button = event.target.closest("[data-collection]")
2114
- if (!button) return
2115
- state.collection = button.dataset.collection
2116
- renderState().catch(showError)
2117
- })
2118
- $("records").addEventListener("click", (event) => {
2119
- const row = event.target.closest("[data-id]")
2120
- if (!row) return
2121
- openEditor(row.dataset.id).catch(showError)
2122
- })
2123
-
2124
- const editor = $("editor")
2125
- async function openEditor(id) {
2126
- state.editing = id || null
2127
- $("editor-title").textContent = id ? "Edit record" : "New record"
2128
- $("editor-id").value = id || ""
2129
- $("editor-id").readOnly = Boolean(id)
2130
- $("editor-delete").hidden = !id
2131
- if (id) {
2132
- const record = await api("GET", "/state/" + encodeURIComponent(state.collection) + "/" + encodeURIComponent(id))
2133
- $("editor-value").value = JSON.stringify(record.value, null, 2)
2134
- } else {
2135
- $("editor-value").value = "{\\n \\n}"
2136
- }
2137
- editor.showModal()
2138
- }
2139
- $("new-record").addEventListener("click", () => openEditor(null).catch(showError))
2140
- $("editor-form").addEventListener("submit", (event) => {
2141
- const submitter = event.submitter
2142
- if (!submitter || submitter.value !== "save") return
2143
- event.preventDefault()
2144
- let value
2145
- try { value = JSON.parse($("editor-value").value) }
2146
- catch { showError(new Error("Value is not JSON")); return }
2147
- const id = $("editor-id").value.trim()
2148
- const path = "/state/" + encodeURIComponent(state.collection)
2149
- const request = state.editing
2150
- ? api("PUT", path + "/" + encodeURIComponent(id), { value })
2151
- : api("POST", path, { id: id || undefined, value })
2152
- request.then(() => { editor.close(); return renderState() }).catch(showError)
2153
- })
2154
- $("editor-delete").addEventListener("click", () => {
2155
- const id = state.editing
2156
- if (!id) return
2157
- api("DELETE", "/state/" + encodeURIComponent(state.collection) + "/" + encodeURIComponent(id))
2158
- .then(() => { editor.close(); return renderState() })
2159
- .catch(showError)
2160
- })
2161
-
2162
- document.querySelectorAll("[data-advance]").forEach((button) => {
2163
- button.addEventListener("click", () => {
2164
- api("POST", "/clock", { advance: Number(button.dataset.advance) }).then(renderClock).catch(showError)
2165
- })
2166
- })
2167
- $("freeze").addEventListener("click", () => {
2168
- const frozen = $("freeze").dataset.frozen === "1"
2169
- api("POST", "/clock", { freeze: !frozen }).then(renderClock).catch(showError)
2170
- })
2171
- $("clock-reset").addEventListener("click", () => {
2172
- api("POST", "/clock", { reset: true }).then(renderClock).catch(showError)
2173
- })
2174
- $("clock-set").addEventListener("submit", (event) => {
2175
- event.preventDefault()
2176
- const raw = $("clock-instant").value.trim()
2177
- const set = /^-?\\d+$/.test(raw) ? Number(raw) : raw
2178
- api("POST", "/clock", { set }).then(renderClock).catch(showError)
2179
- })
2180
-
2181
- $("fault-form").addEventListener("submit", (event) => {
2182
- event.preventDefault()
2183
- const preset = $("fault-preset").value
2184
- const body = preset
2185
- ? { preset }
2186
- : {
2187
- status: Number($("fault-status").value),
2188
- ...($("fault-path").value ? { pathPrefix: $("fault-path").value } : {}),
2189
- ...($("fault-method").value ? { method: $("fault-method").value } : {})
2190
- }
2191
- if (!preset && !Number.isFinite(body.status)) {
2192
- showError(new Error("Status must be a number, or choose a preset"))
2193
- return
2194
- }
2195
- api("POST", "/faults", body).then(renderFaults).catch(showError)
2196
- })
2197
- $("fault-clear").addEventListener("click", () => api("DELETE", "/faults").then(renderFaults).catch(showError))
2198
- $("journal-clear").addEventListener("click", () => api("DELETE", "/requests").then(renderJournal).catch(showError))
2199
-
2200
- $("route-list").addEventListener("click", (event) => {
2201
- const button = event.target.closest("[data-route]")
2202
- if (!button) return
2203
- state.route = button.dataset.route
2204
- $("route-title").textContent = state.route
2205
- const method = state.route.split(" ")[0]
2206
- $("route-body").disabled = method === "GET" || method === "DELETE"
2207
- })
2208
- $("route-form").addEventListener("submit", async (event) => {
2209
- event.preventDefault()
2210
- if (!state.route) return showError(new Error("Choose a route first"))
2211
- const [method, path] = [state.route.split(" ")[0], state.route.split(" ").slice(1).join(" ")]
2212
- const concrete = path.replace(/:([A-Za-z0-9_]+)/g, (_, key) => prompt("Value for " + key) || "")
2213
- let body
2214
- if (method !== "GET" && method !== "DELETE" && $("route-body").value.trim()) {
2215
- try { body = JSON.parse($("route-body").value) }
2216
- catch { return showError(new Error("Body is not JSON")) }
2217
- }
2218
- try {
2219
- const data = await api(method, concrete, body)
2220
- $("route-result").textContent = JSON.stringify(data, null, 2)
2221
- } catch (error) {
2222
- showError(error)
2223
- }
2224
- })
2225
-
2226
- function mountPanels(panels) {
2227
- const nav = $("nav")
2228
- const views = $("views")
2229
- for (const panel of panels) {
2230
- const button = document.createElement("button")
2231
- button.type = "button"
2232
- button.dataset.view = panel.id
2233
- button.textContent = panel.title
2234
- nav.append(button)
2235
- const section = document.createElement("section")
2236
- section.className = "view"
2237
- section.id = "view-" + panel.id
2238
- section.hidden = true
2239
- const title = document.createElement("h2")
2240
- title.textContent = panel.title
2241
- const lede = document.createElement("p")
2242
- lede.className = "lede"
2243
- lede.textContent = panel.description || ""
2244
- const body = document.createElement("div")
2245
- body.className = "panel-body"
2246
- body.id = "panel-" + panel.id
2247
- body.innerHTML = panel.html || ""
2248
- section.append(title, lede, body)
2249
- views.append(section)
2250
- if (panel.script) {
2251
- try {
2252
- const run = new Function("root", "api", panel.script)
2253
- run(body, panelApi)
2254
- } catch (error) {
2255
- const note = document.createElement("p")
2256
- note.className = "banner show"
2257
- note.textContent = error instanceof Error ? error.message : String(error)
2258
- body.append(note)
2259
- }
2260
- }
2261
- }
2262
- }
2263
-
2264
- loadNamespaces()
2265
- .then(() => api("GET", "/ui/manifest"))
2266
- .then((manifest) => { mountPanels((manifest && manifest.panels) || []); return refresh() })
2267
- .catch(showError)
2268
- })()
2269
- </script>
3070
+ ${script}
2270
3071
  </body>
2271
3072
  </html>`;
2272
3073
  };
@@ -2283,10 +3084,10 @@ var json2 = (body) => new Response(JSON.stringify(body), {
2283
3084
  });
2284
3085
  var adminUiRoutes = (service, ui) => {
2285
3086
  const shell = () => renderAdminDocument(service);
2286
- const document2 = () => ui?.render ? ui.render({ service, defaultHtml: shell }) : shell();
3087
+ const document3 = () => ui?.render ? ui.render({ service, defaultHtml: shell }) : shell();
2287
3088
  return {
2288
- "GET /ui": () => htmlResponse(document2()),
2289
- "GET /ui/": () => htmlResponse(document2()),
3089
+ "GET /ui": () => htmlResponse(document3()),
3090
+ "GET /ui/": () => htmlResponse(document3()),
2290
3091
  "GET /ui/manifest": () => json2({
2291
3092
  service,
2292
3093
  panels: (ui?.panels ?? []).map((panel) => ({
@@ -2312,11 +3113,11 @@ var EXTENSION_KEYS = {
2312
3113
  };
2313
3114
 
2314
3115
  // ../../openapi/metadata/dist/read.js
2315
- var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3116
+ var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2316
3117
  var extensionOf = (holder, key) => holder[key];
2317
3118
  var operationMetadata = (operation) => {
2318
3119
  const raw = extensionOf(operation, EXTENSION_KEYS.operation);
2319
- const ext = isRecord2(raw) ? raw : {};
3120
+ const ext = isRecord3(raw) ? raw : {};
2320
3121
  const supported = ext.supported ?? true;
2321
3122
  const parity = ext.parity ?? {};
2322
3123
  return {
@@ -2426,9 +3227,9 @@ ${problems.map((p) => ` - ${p}`).join("\n")}`);
2426
3227
  this.name = "OperationRegistryError";
2427
3228
  }
2428
3229
  };
2429
- var verifyOperations = (document2, handlers) => {
3230
+ var verifyOperations = (document3, handlers) => {
2430
3231
  const problems = [];
2431
- const operations = listOperations(document2);
3232
+ const operations = listOperations(document3);
2432
3233
  const seen = /* @__PURE__ */ new Set();
2433
3234
  for (const operation of operations) {
2434
3235
  if (seen.has(operation.operationId))
@@ -2544,7 +3345,7 @@ var SAMPLE = 25;
2544
3345
  var WALK_LIMIT = 1e4;
2545
3346
  var SKIP_CTORS = /* @__PURE__ */ new Set(["Hono", "HonoRequest", "Router"]);
2546
3347
  var fail2 = (status, message) => ({ ok: false, status, message });
2547
- var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3348
+ var isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2548
3349
  var kindOf = (value) => {
2549
3350
  if (value === null)
2550
3351
  return "null";
@@ -2643,7 +3444,7 @@ var absorb = (accum, value) => {
2643
3444
  const kind = kindOf(value);
2644
3445
  accum.kinds.add(kind);
2645
3446
  accum.present++;
2646
- if (!isRecord3(value))
3447
+ if (!isRecord4(value))
2647
3448
  return;
2648
3449
  for (const [key, child] of Object.entries(value)) {
2649
3450
  const nested = accum.nested.get(key) ?? blankAccum(key);
@@ -2668,7 +3469,7 @@ var inferFields = (records) => {
2668
3469
  if (records.length === 0)
2669
3470
  return [];
2670
3471
  const sample = records.slice(0, SAMPLE);
2671
- const objects = sample.filter((record) => isRecord3(record.value));
3472
+ const objects = sample.filter((record) => isRecord4(record.value));
2672
3473
  if (objects.length === 0) {
2673
3474
  const kinds = new Set(sample.map((record) => kindOf(record.value)));
2674
3475
  const list = [...kinds];
@@ -2855,7 +3656,7 @@ var patchStateRecord = (scope, name, id, value) => {
2855
3656
  const parsed = jsonValue(value);
2856
3657
  if (!parsed.ok)
2857
3658
  return parsed;
2858
- const next = isRecord3(current.value.value) && isRecord3(parsed.value) ? { ...current.value.value, ...parsed.value } : parsed.value;
3659
+ const next = isRecord4(current.value.value) && isRecord4(parsed.value) ? { ...current.value.value, ...parsed.value } : parsed.value;
2859
3660
  return replaceStateRecord(scope, name, id, next);
2860
3661
  };
2861
3662
  var deleteStateRecord = (scope, name, id) => {
@@ -2923,7 +3724,7 @@ var stateAdminRoutes = (routes) => {
2923
3724
  const scope = open(namespace);
2924
3725
  if (!scope.ok)
2925
3726
  return fromResult(scope, 201);
2926
- if (!isRecord3(body) || !("value" in body)) {
3727
+ if (!isRecord4(body) || !("value" in body)) {
2927
3728
  return adminError2(400, 'expected {"id?": "<id>", "value": <json>}');
2928
3729
  }
2929
3730
  const id = body.id;
@@ -2941,7 +3742,7 @@ var stateAdminRoutes = (routes) => {
2941
3742
  const scope = open(namespace);
2942
3743
  if (!scope.ok)
2943
3744
  return fromResult(scope, 200);
2944
- if (!isRecord3(body) || !("value" in body))
3745
+ if (!isRecord4(body) || !("value" in body))
2945
3746
  return adminError2(400, 'expected {"value": <json>}');
2946
3747
  const replaced = replaceStateRecord(scope.value, params.collection ?? "", params.id ?? "", body.value);
2947
3748
  if (replaced.ok)
@@ -2952,7 +3753,7 @@ var stateAdminRoutes = (routes) => {
2952
3753
  const scope = open(namespace);
2953
3754
  if (!scope.ok)
2954
3755
  return fromResult(scope, 200);
2955
- if (!isRecord3(body) || !("value" in body))
3756
+ if (!isRecord4(body) || !("value" in body))
2956
3757
  return adminError2(400, 'expected {"value": <json>}');
2957
3758
  const patched = patchStateRecord(scope.value, params.collection ?? "", params.id ?? "", body.value);
2958
3759
  if (patched.ok)
@@ -2972,14 +3773,14 @@ var stateAdminRoutes = (routes) => {
2972
3773
  };
2973
3774
 
2974
3775
  // ../core/dist/version.js
2975
- var PACKAGE_VERSION = true ? "1.4.0" : UNRELEASED_VERSION;
3776
+ var PACKAGE_VERSION = true ? "1.5.0" : UNRELEASED_VERSION;
2976
3777
 
2977
3778
  // ../core/dist/webhooks.js
2978
3779
  var json4 = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
2979
3780
  var adminError3 = (status, message) => json4(status, { error: { type: "mockingbird_admin", message } });
2980
- var isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3781
+ var isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2981
3782
  var parseEndpoint = (value) => {
2982
- if (!isRecord4(value) || typeof value.url !== "string")
3783
+ if (!isRecord5(value) || typeof value.url !== "string")
2983
3784
  return "each endpoint needs a url";
2984
3785
  try {
2985
3786
  new URL(value.url);
@@ -2996,12 +3797,12 @@ var parseEndpoint = (value) => {
2996
3797
  const events = value.events ?? value.enabledEvents;
2997
3798
  if (Array.isArray(events))
2998
3799
  endpoint.events = events.map(String);
2999
- if (isRecord4(value.tags)) {
3800
+ if (isRecord5(value.tags)) {
3000
3801
  endpoint.tags = Object.fromEntries(Object.entries(value.tags).map(([k, v]) => [k, String(v)]));
3001
3802
  }
3002
3803
  if (typeof value.account === "string")
3003
3804
  endpoint.tags = { ...endpoint.tags, account: value.account };
3004
- if (isRecord4(value.headers)) {
3805
+ if (isRecord5(value.headers)) {
3005
3806
  endpoint.headers = Object.fromEntries(Object.entries(value.headers).map(([k, v]) => [k, String(v)]));
3006
3807
  }
3007
3808
  return endpoint;
@@ -3028,7 +3829,7 @@ var webhookAdminRoutes = (hub) => ({
3028
3829
  return json4(200, { status: "ok" });
3029
3830
  },
3030
3831
  "POST /webhooks/faults": ({ body, namespace }) => {
3031
- if (!isRecord4(body) || !["duplicate", "reorder", "drop"].includes(String(body.mode))) {
3832
+ if (!isRecord5(body) || !["duplicate", "reorder", "drop"].includes(String(body.mode))) {
3032
3833
  return adminError3(400, "mode must be duplicate, reorder or drop");
3033
3834
  }
3034
3835
  const fault = { mode: body.mode };
@@ -3044,7 +3845,7 @@ var webhookAdminRoutes = (hub) => ({
3044
3845
  }))
3045
3846
  }),
3046
3847
  "PUT /webhook-endpoints": ({ body, namespace }) => {
3047
- const list = Array.isArray(body) ? body : isRecord4(body) ? body.endpoints : void 0;
3848
+ const list = Array.isArray(body) ? body : isRecord5(body) ? body.endpoints : void 0;
3048
3849
  if (!Array.isArray(list))
3049
3850
  return adminError3(400, "expected [{url, secret?, events?}]");
3050
3851
  const parsed = [];
@@ -3114,8 +3915,8 @@ var DroppedConnectionError = class extends TypeError {
3114
3915
  this.name = "TypeError";
3115
3916
  }
3116
3917
  };
3117
- var operationMatcher = (document2) => {
3118
- const matchers = listOperations(document2).map((operation) => ({
3918
+ var operationMatcher = (document3) => {
3919
+ const matchers = listOperations(document3).map((operation) => ({
3119
3920
  operationId: operation.operationId,
3120
3921
  method: operation.method.toUpperCase(),
3121
3922
  pattern: new RegExp(`^${operation.path.split("/").map((segment) => segment.startsWith("{") ? "[^/]+" : segment.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("/")}/?$`),
@@ -3210,10 +4011,10 @@ var createRuntime = (options) => {
3210
4011
  const ensureBranch = (namespace, branch2, at) => {
3211
4012
  if (!BRANCH_PATTERN2.test(branch2))
3212
4013
  throw new RangeError(`branch must match ${BRANCH_PATTERN2}`);
3213
- const history = timeline(namespace);
4014
+ const history2 = timeline(namespace);
3214
4015
  if (branch2 === "main") {
3215
4016
  if (at !== void 0) {
3216
- const point = history.checkout("main", at);
4017
+ const point = history2.checkout("main", at);
3217
4018
  restoreNamespace(sqlite, storageNamespace(namespace), point.value.snapshot);
3218
4019
  captured.set(namespace, point.value.snapshot);
3219
4020
  rng.setState(point.value.rngState);
@@ -3226,10 +4027,10 @@ var createRuntime = (options) => {
3226
4027
  return namespace;
3227
4028
  }
3228
4029
  const storage = physicalBranch(namespace, branch2);
3229
- if (!history.hasBranch(branch2)) {
4030
+ if (!history2.hasBranch(branch2)) {
3230
4031
  if (at === void 0)
3231
- history.commit(capture(namespace));
3232
- const point = history.fork(branch2, at === void 0 ? {} : { from: at });
4032
+ history2.commit(capture(namespace));
4033
+ const point = history2.fork(branch2, at === void 0 ? {} : { from: at });
3233
4034
  const branchRng = createRng(options.seed ?? 0);
3234
4035
  if (point)
3235
4036
  branchRng.setState(point.value.rngState);
@@ -3238,8 +4039,8 @@ var createRuntime = (options) => {
3238
4039
  restoreNamespace(sqlite, storageNamespace(storage), point.value.snapshot);
3239
4040
  if (point)
3240
4041
  captured.set(storage, point.value.snapshot);
3241
- } else if (at !== void 0 && history.head(branch2)?.id !== at) {
3242
- const point = history.checkout(branch2, at);
4042
+ } else if (at !== void 0 && history2.head(branch2)?.id !== at) {
4043
+ const point = history2.checkout(branch2, at);
3243
4044
  if (!instances.has(storage)) {
3244
4045
  const branchRng = createRng(options.seed ?? 0);
3245
4046
  branchRng.setState(point.value.rngState);
@@ -3250,7 +4051,7 @@ var createRuntime = (options) => {
3250
4051
  captured.set(storage, point.value.snapshot);
3251
4052
  } else {
3252
4053
  if (!instances.has(storage)) {
3253
- const point = history.head(branch2);
4054
+ const point = history2.head(branch2);
3254
4055
  const branchRng = createRng(options.seed ?? 0);
3255
4056
  if (point)
3256
4057
  branchRng.setState(point.value.rngState);
@@ -3274,8 +4075,8 @@ var createRuntime = (options) => {
3274
4075
  const checkout = (checkpointId, checkoutOptions = {}) => {
3275
4076
  const namespace = checkoutOptions.namespace ?? DEFAULT_NAMESPACE;
3276
4077
  const branchName = checkoutOptions.branch ?? "main";
3277
- const history = timeline(namespace);
3278
- const point = history.checkout(branchName, checkpointId);
4078
+ const history2 = timeline(namespace);
4079
+ const point = history2.checkout(branchName, checkpointId);
3279
4080
  const storage = ensureBranch(namespace, branchName);
3280
4081
  restoreNamespace(sqlite, storageNamespace(storage), point.value.snapshot);
3281
4082
  captured.set(storage, point.value.snapshot);
@@ -3325,9 +4126,9 @@ var createRuntime = (options) => {
3325
4126
  instance(name);
3326
4127
  restoreNamespace(sqlite, storageNamespace(name), from);
3327
4128
  captured.set(name, from);
3328
- const history = timelines.get(name);
3329
- if (history)
3330
- history.commit(capture(name), { branch: "main" });
4129
+ const history2 = timelines.get(name);
4130
+ if (history2)
4131
+ history2.commit(capture(name), { branch: "main" });
3331
4132
  else
3332
4133
  timeline(name);
3333
4134
  };
@@ -3556,10 +4357,10 @@ var createRuntime = (options) => {
3556
4357
  },
3557
4358
  release: (name, checkpointId) => timeline(name).release(checkpointId),
3558
4359
  inspect: (name) => {
3559
- const history = timeline(name);
4360
+ const history2 = timeline(name);
3560
4361
  return {
3561
- branches: history.branches(),
3562
- checkpoints: history.checkpoints().map(({ id, branch: branchName, parent, at }) => ({
4362
+ branches: history2.branches(),
4363
+ checkpoints: history2.checkpoints().map(({ id, branch: branchName, parent, at }) => ({
3563
4364
  id,
3564
4365
  branch: branchName,
3565
4366
  parent,
@@ -3574,11 +4375,11 @@ var createRuntime = (options) => {
3574
4375
  } : {},
3575
4376
  routes: {
3576
4377
  ...credentialRoutes(credentials),
3577
- ...options.presets ? presetRoutes(options.presets, runtime) : {},
3578
4378
  ...options.webhooks ? webhookAdminRoutes(options.webhooks) : {},
3579
4379
  ...options.admin?.(runtime) ?? {},
3580
- // Standard state and UI routes win a colliding key. A service adds its own keys
3581
- // beside these; it does not replace `/state` or `/ui`.
4380
+ // Shell routes win a colliding key. Fault presets are one of them: the admin page
4381
+ // maps `presets`, and a service route must not replace that list with a record.
4382
+ ...options.presets ? presetRoutes(options.presets, runtime) : {},
3582
4383
  ...stateAdminRoutes({
3583
4384
  open: stateScope,
3584
4385
  afterWrite: (namespace) => {
@@ -3653,9 +4454,12 @@ var credentialRoutes = (registry) => ({
3653
4454
  }
3654
4455
  });
3655
4456
  var presetRoutes = (presets, runtime) => ({
3656
- "GET /faults/presets": () => adminJson(200, {
3657
- presets: Object.entries(presets).map(([name, preset]) => ({ name, ...preset }))
3658
- }),
4457
+ "GET /faults/presets": () => {
4458
+ const body = {
4459
+ presets: Object.entries(presets).map(([name, preset]) => ({ name, ...preset }))
4460
+ };
4461
+ return adminJson(200, body);
4462
+ },
3659
4463
  "POST /faults/presets/:name": ({ params, body, namespace }) => {
3660
4464
  const name = params.name;
3661
4465
  if (!presets[name])
@@ -3666,7 +4470,7 @@ var presetRoutes = (presets, runtime) => ({
3666
4470
  });
3667
4471
 
3668
4472
  // src/generated/openapi.ts
3669
- var document = JSON.parse(`{"openapi":"3.1.0","info":{"title":"Plane REST API (Mockingbird subset)","description":"Stateful mock subset of Plane's public REST API v1 for one project: work items (cursor-paginated list, get, create, patch), comments, links, states and labels. Hand-authored from Plane's API reference and the consumer's zod schemas (plane-response.ts).\\n","version":"1","x-mockingbird-upstream":{"note":"Shapes from https://developers.plane.so/api-reference; consumer: apps/backend/src/modules/bug-reports/adapters/native-fetch-plane.adapter.ts, plane-http-client.ts, plane-response.ts."}},"servers":[{"url":"https://api.plane.so"}],"security":[{"apiKey":[]}],"paths":{"/api/v1/workspaces/{slug}/projects/{project_id}/work-items/":{"get":{"operationId":"ListWorkItems","description":"Cursor-paginated work items (\`cursor=<per_page>:<page>:<is_prev>\`).","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"A page","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkItemPage"}}}},"400":{"description":"Invalid cursor","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/Throttled"}},"parameters":[{"$ref":"#/components/parameters/PerPage"},{"$ref":"#/components/parameters/Cursor"},{"$ref":"#/components/parameters/OrderBy"}]},"post":{"operationId":"CreateWorkItem","description":"Create a work item.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkItem"}}}},"400":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/Throttled"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWorkItemBody"}}}}},"parameters":[{"$ref":"#/components/parameters/Slug"},{"$ref":"#/components/parameters/ProjectId"}]},"/api/v1/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/":{"parameters":[{"$ref":"#/components/parameters/Slug"},{"$ref":"#/components/parameters/ProjectId"},{"$ref":"#/components/parameters/WorkItemId"}],"get":{"operationId":"GetWorkItem","description":"One work item.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"The work item","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkItem"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/Throttled"}}},"patch":{"operationId":"UpdateWorkItem","description":"Partial update (the consumer sends \`state\` or \`labels\`).","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkItem"}}}},"400":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/Throttled"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateWorkItemBody"}}}}}},"/api/v1/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/comments/":{"parameters":[{"$ref":"#/components/parameters/Slug"},{"$ref":"#/components/parameters/ProjectId"},{"$ref":"#/components/parameters/WorkItemId"}],"get":{"operationId":"ListComments","description":"Comments on a work item.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"A page","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommentPage"}}}},"400":{"description":"Invalid cursor","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/Throttled"}},"parameters":[{"$ref":"#/components/parameters/PerPage"},{"$ref":"#/components/parameters/Cursor"}]},"post":{"operationId":"CreateComment","description":"Add a comment.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Comment"}}}},"400":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/Throttled"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCommentBody"}}}}}},"/api/v1/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/links/":{"parameters":[{"$ref":"#/components/parameters/Slug"},{"$ref":"#/components/parameters/ProjectId"},{"$ref":"#/components/parameters/WorkItemId"}],"get":{"operationId":"ListLinks","description":"Links on a work item.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"A page","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LinkPage"}}}},"400":{"description":"Invalid cursor","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/Throttled"}},"parameters":[{"$ref":"#/components/parameters/PerPage"},{"$ref":"#/components/parameters/Cursor"}]},"post":{"operationId":"CreateLink","description":"Attach a link (the same URL twice is a 400).","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Link"}}}},"400":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"The URL is already linked (carries the existing link id)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"429":{"$ref":"#/components/responses/Throttled"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateLinkBody"}}}}}},"/api/v1/workspaces/{slug}/projects/{project_id}/states/":{"get":{"operationId":"ListStates","description":"The project's workflow states.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"A page","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StatePage"}}}},"400":{"description":"Invalid cursor","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/Throttled"}},"parameters":[{"$ref":"#/components/parameters/PerPage"},{"$ref":"#/components/parameters/Cursor"}]},"parameters":[{"$ref":"#/components/parameters/Slug"},{"$ref":"#/components/parameters/ProjectId"}]},"/api/v1/workspaces/{slug}/projects/{project_id}/labels/":{"get":{"operationId":"ListLabels","description":"The project's labels.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"A page","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LabelPage"}}}},"400":{"description":"Invalid cursor","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/Throttled"}},"parameters":[{"$ref":"#/components/parameters/PerPage"},{"$ref":"#/components/parameters/Cursor"}]},"post":{"operationId":"CreateLabel","description":"Create a label (a duplicate name is a 409 carrying the existing id).","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Label"}}}},"400":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"Duplicate name","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"429":{"$ref":"#/components/responses/Throttled"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateLabelBody"}}}}},"parameters":[{"$ref":"#/components/parameters/Slug"},{"$ref":"#/components/parameters/ProjectId"}]}},"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"X-API-Key"}},"parameters":{"Slug":{"name":"slug","in":"path","required":true,"schema":{"type":"string","enum":["acme"]}},"ProjectId":{"name":"project_id","in":"path","required":true,"schema":{"type":"string","enum":["33333333-3333-4333-8333-333333333333"]}},"WorkItemId":{"name":"work_item_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","x-mockingbird-resource-ref":{"type":"work_item","missing":"00000000-0000-4000-8000-000000000000"}}},"PerPage":{"name":"per_page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100}},"Cursor":{"name":"cursor","in":"query","required":false,"schema":{"type":"string","enum":["2:0:0","2:1:0","2:2:0","100:0:0"]}},"OrderBy":{"name":"order_by","in":"query","required":false,"schema":{"type":"string","enum":["-created_at","created_at","sequence_id","-sequence_id","name","-name"]}}},"responses":{"Unauthorized":{"description":"Missing or invalid X-API-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"NotFound":{"description":"Unknown workspace, project or item","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"Throttled":{"description":"Rate limited (60 requests per minute per key)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}},"schemas":{"WorkItem":{"type":"object","required":["id","sequence_id","name","description_html","state","labels","priority","created_at","updated_at","project","workspace"],"properties":{"id":{"type":"string","format":"uuid","x-mockingbird-resource":{"type":"work_item","identity":true},"x-mockingbird-volatile":{"kind":"id"}},"sequence_id":{"type":"integer"},"name":{"type":"string"},"description_html":{"type":"string"},"description_stripped":{"type":["string","null"]},"priority":{"type":"string","enum":["urgent","high","medium","low","none"]},"state":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"labels":{"type":"array","items":{"type":"string","x-mockingbird-volatile":{"kind":"id"}}},"assignees":{"type":"array","items":{"type":"string"}},"parent":{"type":["string","null"]},"start_date":{"type":["string","null"]},"target_date":{"type":["string","null"]},"completed_at":{"type":["string","null"],"x-mockingbird-volatile":{"kind":"timestamp"}},"archived_at":{"type":["string","null"]},"is_draft":{"type":"boolean"},"sort_order":{"type":"number"},"project":{"type":"string"},"workspace":{"type":"string"},"created_by":{"type":["string","null"]},"updated_by":{"type":["string","null"]},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"updated_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"State":{"type":"object","required":["id","name","group","color","sequence","default"],"properties":{"id":{"type":"string","format":"uuid","x-mockingbird-resource":{"type":"state","identity":true},"x-mockingbird-volatile":{"kind":"id"}},"name":{"type":"string"},"group":{"type":"string","enum":["backlog","unstarted","started","completed","cancelled","triage"]},"color":{"type":"string"},"sequence":{"type":"number"},"default":{"type":"boolean"},"description":{"type":"string"},"project":{"type":"string"},"workspace":{"type":"string"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"updated_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"Label":{"type":"object","required":["id","name","color"],"properties":{"id":{"type":"string","format":"uuid","x-mockingbird-resource":{"type":"label","identity":true},"x-mockingbird-volatile":{"kind":"id"}},"name":{"type":"string"},"color":{"type":"string"},"description":{"type":"string"},"parent":{"type":["string","null"]},"sort_order":{"type":"number"},"project":{"type":"string"},"workspace":{"type":"string"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"updated_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"Comment":{"type":"object","required":["id","comment_html","issue","created_at"],"properties":{"id":{"type":"string","format":"uuid","x-mockingbird-volatile":{"kind":"id"}},"comment_html":{"type":"string"},"comment_stripped":{"type":"string"},"access":{"type":"string"},"issue":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"actor":{"type":["string","null"]},"project":{"type":"string"},"workspace":{"type":"string"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"updated_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"Link":{"type":"object","required":["id","url","title","issue","created_at"],"properties":{"id":{"type":"string","format":"uuid","x-mockingbird-volatile":{"kind":"id"}},"url":{"type":"string"},"title":{"type":["string","null"]},"metadata":{"type":"object"},"issue":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"project":{"type":"string"},"workspace":{"type":"string"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"updated_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"WorkItemPage":{"type":"object","required":["results","next_cursor","prev_cursor","next_page_results","prev_page_results","count","total_count","total_pages","total_results"],"properties":{"grouped_by":{"type":["string","null"]},"sub_grouped_by":{"type":["string","null"]},"total_count":{"type":"integer"},"next_cursor":{"type":"string"},"prev_cursor":{"type":"string"},"next_page_results":{"type":"boolean"},"prev_page_results":{"type":"boolean"},"count":{"type":"integer"},"total_pages":{"type":"integer"},"total_results":{"type":"integer"},"extra_stats":{"type":["object","null"]},"results":{"type":"array","items":{"$ref":"#/components/schemas/WorkItem"}}}},"StatePage":{"type":"object","required":["results","next_cursor","prev_cursor","next_page_results","prev_page_results","count","total_count","total_pages","total_results"],"properties":{"grouped_by":{"type":["string","null"]},"sub_grouped_by":{"type":["string","null"]},"total_count":{"type":"integer"},"next_cursor":{"type":"string"},"prev_cursor":{"type":"string"},"next_page_results":{"type":"boolean"},"prev_page_results":{"type":"boolean"},"count":{"type":"integer"},"total_pages":{"type":"integer"},"total_results":{"type":"integer"},"extra_stats":{"type":["object","null"]},"results":{"type":"array","items":{"$ref":"#/components/schemas/State"}}}},"LabelPage":{"type":"object","required":["results","next_cursor","prev_cursor","next_page_results","prev_page_results","count","total_count","total_pages","total_results"],"properties":{"grouped_by":{"type":["string","null"]},"sub_grouped_by":{"type":["string","null"]},"total_count":{"type":"integer"},"next_cursor":{"type":"string"},"prev_cursor":{"type":"string"},"next_page_results":{"type":"boolean"},"prev_page_results":{"type":"boolean"},"count":{"type":"integer"},"total_pages":{"type":"integer"},"total_results":{"type":"integer"},"extra_stats":{"type":["object","null"]},"results":{"type":"array","items":{"$ref":"#/components/schemas/Label"}}}},"CommentPage":{"type":"object","required":["results","next_cursor","prev_cursor","next_page_results","prev_page_results","count","total_count","total_pages","total_results"],"properties":{"grouped_by":{"type":["string","null"]},"sub_grouped_by":{"type":["string","null"]},"total_count":{"type":"integer"},"next_cursor":{"type":"string"},"prev_cursor":{"type":"string"},"next_page_results":{"type":"boolean"},"prev_page_results":{"type":"boolean"},"count":{"type":"integer"},"total_pages":{"type":"integer"},"total_results":{"type":"integer"},"extra_stats":{"type":["object","null"]},"results":{"type":"array","items":{"$ref":"#/components/schemas/Comment"}}}},"LinkPage":{"type":"object","required":["results","next_cursor","prev_cursor","next_page_results","prev_page_results","count","total_count","total_pages","total_results"],"properties":{"grouped_by":{"type":["string","null"]},"sub_grouped_by":{"type":["string","null"]},"total_count":{"type":"integer"},"next_cursor":{"type":"string"},"prev_cursor":{"type":"string"},"next_page_results":{"type":"boolean"},"prev_page_results":{"type":"boolean"},"count":{"type":"integer"},"total_pages":{"type":"integer"},"total_results":{"type":"integer"},"extra_stats":{"type":["object","null"]},"results":{"type":"array","items":{"$ref":"#/components/schemas/Link"}}}},"CreateWorkItemBody":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":255},"description_html":{"type":"string","maxLength":2000},"state":{"type":"string","format":"uuid","x-mockingbird-resource-ref":{"type":"state","missing":"00000000-0000-4000-8000-000000000000"}},"labels":{"type":"array","maxItems":3,"items":{"type":"string","format":"uuid","x-mockingbird-resource-ref":{"type":"label","missing":"00000000-0000-4000-8000-000000000000"}}},"priority":{"type":"string","enum":["urgent","high","medium","low","none"]}}},"UpdateWorkItemBody":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":255},"description_html":{"type":"string","maxLength":2000},"state":{"type":"string","format":"uuid","x-mockingbird-resource-ref":{"type":"state","missing":"00000000-0000-4000-8000-000000000000"}},"labels":{"type":"array","maxItems":3,"items":{"type":"string","format":"uuid","x-mockingbird-resource-ref":{"type":"label","missing":"00000000-0000-4000-8000-000000000000"}}},"priority":{"type":"string","enum":["urgent","high","medium","low","none"]}}},"CreateCommentBody":{"type":"object","required":["comment_html"],"properties":{"comment_html":{"type":"string","minLength":1,"maxLength":2000},"access":{"type":"string","enum":["INTERNAL","EXTERNAL"]}}},"CreateLinkBody":{"type":"object","required":["url"],"properties":{"url":{"type":"string","format":"uri","pattern":"^https://","maxLength":200},"title":{"type":"string","minLength":1,"maxLength":255}}},"CreateLabelBody":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":255},"color":{"type":"string","pattern":"^#[0-9a-fA-F]{6}$"},"description":{"type":"string","maxLength":255}}},"ErrorBody":{"type":"object","properties":{"error":{"type":"string"},"detail":{"type":"string"},"id":{"type":"string"}}},"ValidationError":{"type":"object","additionalProperties":{"type":"array","items":{"type":"string"}}}}}}`);
4473
+ var document2 = JSON.parse(`{"openapi":"3.1.0","info":{"title":"Plane REST API (Mockingbird subset)","description":"Stateful mock subset of Plane's public REST API v1 for one project: work items (cursor-paginated list, get, create, patch), comments, links, states and labels. Hand-authored from Plane's API reference and the consumer's zod schemas (plane-response.ts).\\n","version":"1","x-mockingbird-upstream":{"note":"Shapes from https://developers.plane.so/api-reference; consumer: apps/backend/src/modules/bug-reports/adapters/native-fetch-plane.adapter.ts, plane-http-client.ts, plane-response.ts."}},"servers":[{"url":"https://api.plane.so"}],"security":[{"apiKey":[]}],"paths":{"/api/v1/workspaces/{slug}/projects/{project_id}/work-items/":{"get":{"operationId":"ListWorkItems","description":"Cursor-paginated work items (\`cursor=<per_page>:<page>:<is_prev>\`).","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"A page","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkItemPage"}}}},"400":{"description":"Invalid cursor","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/Throttled"}},"parameters":[{"$ref":"#/components/parameters/PerPage"},{"$ref":"#/components/parameters/Cursor"},{"$ref":"#/components/parameters/OrderBy"}]},"post":{"operationId":"CreateWorkItem","description":"Create a work item.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkItem"}}}},"400":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/Throttled"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWorkItemBody"}}}}},"parameters":[{"$ref":"#/components/parameters/Slug"},{"$ref":"#/components/parameters/ProjectId"}]},"/api/v1/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/":{"parameters":[{"$ref":"#/components/parameters/Slug"},{"$ref":"#/components/parameters/ProjectId"},{"$ref":"#/components/parameters/WorkItemId"}],"get":{"operationId":"GetWorkItem","description":"One work item.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"The work item","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkItem"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/Throttled"}}},"patch":{"operationId":"UpdateWorkItem","description":"Partial update (the consumer sends \`state\` or \`labels\`).","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkItem"}}}},"400":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/Throttled"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateWorkItemBody"}}}}}},"/api/v1/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/comments/":{"parameters":[{"$ref":"#/components/parameters/Slug"},{"$ref":"#/components/parameters/ProjectId"},{"$ref":"#/components/parameters/WorkItemId"}],"get":{"operationId":"ListComments","description":"Comments on a work item.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"A page","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommentPage"}}}},"400":{"description":"Invalid cursor","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/Throttled"}},"parameters":[{"$ref":"#/components/parameters/PerPage"},{"$ref":"#/components/parameters/Cursor"}]},"post":{"operationId":"CreateComment","description":"Add a comment.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Comment"}}}},"400":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/Throttled"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCommentBody"}}}}}},"/api/v1/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/links/":{"parameters":[{"$ref":"#/components/parameters/Slug"},{"$ref":"#/components/parameters/ProjectId"},{"$ref":"#/components/parameters/WorkItemId"}],"get":{"operationId":"ListLinks","description":"Links on a work item.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"A page","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LinkPage"}}}},"400":{"description":"Invalid cursor","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/Throttled"}},"parameters":[{"$ref":"#/components/parameters/PerPage"},{"$ref":"#/components/parameters/Cursor"}]},"post":{"operationId":"CreateLink","description":"Attach a link (the same URL twice is a 400).","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Link"}}}},"400":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"The URL is already linked (carries the existing link id)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"429":{"$ref":"#/components/responses/Throttled"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateLinkBody"}}}}}},"/api/v1/workspaces/{slug}/projects/{project_id}/states/":{"get":{"operationId":"ListStates","description":"The project's workflow states.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"A page","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StatePage"}}}},"400":{"description":"Invalid cursor","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/Throttled"}},"parameters":[{"$ref":"#/components/parameters/PerPage"},{"$ref":"#/components/parameters/Cursor"}]},"parameters":[{"$ref":"#/components/parameters/Slug"},{"$ref":"#/components/parameters/ProjectId"}]},"/api/v1/workspaces/{slug}/projects/{project_id}/labels/":{"get":{"operationId":"ListLabels","description":"The project's labels.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"A page","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LabelPage"}}}},"400":{"description":"Invalid cursor","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/Throttled"}},"parameters":[{"$ref":"#/components/parameters/PerPage"},{"$ref":"#/components/parameters/Cursor"}]},"post":{"operationId":"CreateLabel","description":"Create a label (a duplicate name is a 409 carrying the existing id).","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Label"}}}},"400":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"Duplicate name","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"429":{"$ref":"#/components/responses/Throttled"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateLabelBody"}}}}},"parameters":[{"$ref":"#/components/parameters/Slug"},{"$ref":"#/components/parameters/ProjectId"}]}},"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"X-API-Key"}},"parameters":{"Slug":{"name":"slug","in":"path","required":true,"schema":{"type":"string","enum":["acme"]}},"ProjectId":{"name":"project_id","in":"path","required":true,"schema":{"type":"string","enum":["33333333-3333-4333-8333-333333333333"]}},"WorkItemId":{"name":"work_item_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","x-mockingbird-resource-ref":{"type":"work_item","missing":"00000000-0000-4000-8000-000000000000"}}},"PerPage":{"name":"per_page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100}},"Cursor":{"name":"cursor","in":"query","required":false,"schema":{"type":"string","enum":["2:0:0","2:1:0","2:2:0","100:0:0"]}},"OrderBy":{"name":"order_by","in":"query","required":false,"schema":{"type":"string","enum":["-created_at","created_at","sequence_id","-sequence_id","name","-name"]}}},"responses":{"Unauthorized":{"description":"Missing or invalid X-API-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"NotFound":{"description":"Unknown workspace, project or item","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"Throttled":{"description":"Rate limited (60 requests per minute per key)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}},"schemas":{"WorkItem":{"type":"object","required":["id","sequence_id","name","description_html","state","labels","priority","created_at","updated_at","project","workspace"],"properties":{"id":{"type":"string","format":"uuid","x-mockingbird-resource":{"type":"work_item","identity":true},"x-mockingbird-volatile":{"kind":"id"}},"sequence_id":{"type":"integer"},"name":{"type":"string"},"description_html":{"type":"string"},"description_stripped":{"type":["string","null"]},"priority":{"type":"string","enum":["urgent","high","medium","low","none"]},"state":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"labels":{"type":"array","items":{"type":"string","x-mockingbird-volatile":{"kind":"id"}}},"assignees":{"type":"array","items":{"type":"string"}},"parent":{"type":["string","null"]},"start_date":{"type":["string","null"]},"target_date":{"type":["string","null"]},"completed_at":{"type":["string","null"],"x-mockingbird-volatile":{"kind":"timestamp"}},"archived_at":{"type":["string","null"]},"is_draft":{"type":"boolean"},"sort_order":{"type":"number"},"project":{"type":"string"},"workspace":{"type":"string"},"created_by":{"type":["string","null"]},"updated_by":{"type":["string","null"]},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"updated_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"State":{"type":"object","required":["id","name","group","color","sequence","default"],"properties":{"id":{"type":"string","format":"uuid","x-mockingbird-resource":{"type":"state","identity":true},"x-mockingbird-volatile":{"kind":"id"}},"name":{"type":"string"},"group":{"type":"string","enum":["backlog","unstarted","started","completed","cancelled","triage"]},"color":{"type":"string"},"sequence":{"type":"number"},"default":{"type":"boolean"},"description":{"type":"string"},"project":{"type":"string"},"workspace":{"type":"string"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"updated_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"Label":{"type":"object","required":["id","name","color"],"properties":{"id":{"type":"string","format":"uuid","x-mockingbird-resource":{"type":"label","identity":true},"x-mockingbird-volatile":{"kind":"id"}},"name":{"type":"string"},"color":{"type":"string"},"description":{"type":"string"},"parent":{"type":["string","null"]},"sort_order":{"type":"number"},"project":{"type":"string"},"workspace":{"type":"string"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"updated_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"Comment":{"type":"object","required":["id","comment_html","issue","created_at"],"properties":{"id":{"type":"string","format":"uuid","x-mockingbird-volatile":{"kind":"id"}},"comment_html":{"type":"string"},"comment_stripped":{"type":"string"},"access":{"type":"string"},"issue":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"actor":{"type":["string","null"]},"project":{"type":"string"},"workspace":{"type":"string"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"updated_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"Link":{"type":"object","required":["id","url","title","issue","created_at"],"properties":{"id":{"type":"string","format":"uuid","x-mockingbird-volatile":{"kind":"id"}},"url":{"type":"string"},"title":{"type":["string","null"]},"metadata":{"type":"object"},"issue":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"project":{"type":"string"},"workspace":{"type":"string"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"updated_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"WorkItemPage":{"type":"object","required":["results","next_cursor","prev_cursor","next_page_results","prev_page_results","count","total_count","total_pages","total_results"],"properties":{"grouped_by":{"type":["string","null"]},"sub_grouped_by":{"type":["string","null"]},"total_count":{"type":"integer"},"next_cursor":{"type":"string"},"prev_cursor":{"type":"string"},"next_page_results":{"type":"boolean"},"prev_page_results":{"type":"boolean"},"count":{"type":"integer"},"total_pages":{"type":"integer"},"total_results":{"type":"integer"},"extra_stats":{"type":["object","null"]},"results":{"type":"array","items":{"$ref":"#/components/schemas/WorkItem"}}}},"StatePage":{"type":"object","required":["results","next_cursor","prev_cursor","next_page_results","prev_page_results","count","total_count","total_pages","total_results"],"properties":{"grouped_by":{"type":["string","null"]},"sub_grouped_by":{"type":["string","null"]},"total_count":{"type":"integer"},"next_cursor":{"type":"string"},"prev_cursor":{"type":"string"},"next_page_results":{"type":"boolean"},"prev_page_results":{"type":"boolean"},"count":{"type":"integer"},"total_pages":{"type":"integer"},"total_results":{"type":"integer"},"extra_stats":{"type":["object","null"]},"results":{"type":"array","items":{"$ref":"#/components/schemas/State"}}}},"LabelPage":{"type":"object","required":["results","next_cursor","prev_cursor","next_page_results","prev_page_results","count","total_count","total_pages","total_results"],"properties":{"grouped_by":{"type":["string","null"]},"sub_grouped_by":{"type":["string","null"]},"total_count":{"type":"integer"},"next_cursor":{"type":"string"},"prev_cursor":{"type":"string"},"next_page_results":{"type":"boolean"},"prev_page_results":{"type":"boolean"},"count":{"type":"integer"},"total_pages":{"type":"integer"},"total_results":{"type":"integer"},"extra_stats":{"type":["object","null"]},"results":{"type":"array","items":{"$ref":"#/components/schemas/Label"}}}},"CommentPage":{"type":"object","required":["results","next_cursor","prev_cursor","next_page_results","prev_page_results","count","total_count","total_pages","total_results"],"properties":{"grouped_by":{"type":["string","null"]},"sub_grouped_by":{"type":["string","null"]},"total_count":{"type":"integer"},"next_cursor":{"type":"string"},"prev_cursor":{"type":"string"},"next_page_results":{"type":"boolean"},"prev_page_results":{"type":"boolean"},"count":{"type":"integer"},"total_pages":{"type":"integer"},"total_results":{"type":"integer"},"extra_stats":{"type":["object","null"]},"results":{"type":"array","items":{"$ref":"#/components/schemas/Comment"}}}},"LinkPage":{"type":"object","required":["results","next_cursor","prev_cursor","next_page_results","prev_page_results","count","total_count","total_pages","total_results"],"properties":{"grouped_by":{"type":["string","null"]},"sub_grouped_by":{"type":["string","null"]},"total_count":{"type":"integer"},"next_cursor":{"type":"string"},"prev_cursor":{"type":"string"},"next_page_results":{"type":"boolean"},"prev_page_results":{"type":"boolean"},"count":{"type":"integer"},"total_pages":{"type":"integer"},"total_results":{"type":"integer"},"extra_stats":{"type":["object","null"]},"results":{"type":"array","items":{"$ref":"#/components/schemas/Link"}}}},"CreateWorkItemBody":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":255},"description_html":{"type":"string","maxLength":2000},"state":{"type":"string","format":"uuid","x-mockingbird-resource-ref":{"type":"state","missing":"00000000-0000-4000-8000-000000000000"}},"labels":{"type":"array","maxItems":3,"items":{"type":"string","format":"uuid","x-mockingbird-resource-ref":{"type":"label","missing":"00000000-0000-4000-8000-000000000000"}}},"priority":{"type":"string","enum":["urgent","high","medium","low","none"]}}},"UpdateWorkItemBody":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":255},"description_html":{"type":"string","maxLength":2000},"state":{"type":"string","format":"uuid","x-mockingbird-resource-ref":{"type":"state","missing":"00000000-0000-4000-8000-000000000000"}},"labels":{"type":"array","maxItems":3,"items":{"type":"string","format":"uuid","x-mockingbird-resource-ref":{"type":"label","missing":"00000000-0000-4000-8000-000000000000"}}},"priority":{"type":"string","enum":["urgent","high","medium","low","none"]}}},"CreateCommentBody":{"type":"object","required":["comment_html"],"properties":{"comment_html":{"type":"string","minLength":1,"maxLength":2000},"access":{"type":"string","enum":["INTERNAL","EXTERNAL"]}}},"CreateLinkBody":{"type":"object","required":["url"],"properties":{"url":{"type":"string","format":"uri","pattern":"^https://","maxLength":200},"title":{"type":"string","minLength":1,"maxLength":255}}},"CreateLabelBody":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":255},"color":{"type":"string","pattern":"^#[0-9a-fA-F]{6}$"},"description":{"type":"string","maxLength":255}}},"ErrorBody":{"type":"object","properties":{"error":{"type":"string"},"detail":{"type":"string"},"id":{"type":"string"}}},"ValidationError":{"type":"object","additionalProperties":{"type":"array","items":{"type":"string"}}}}}}`);
3670
4474
  var operationIds = ["ListWorkItems", "CreateWorkItem", "GetWorkItem", "UpdateWorkItem", "ListComments", "CreateComment", "ListLinks", "CreateLink", "ListStates", "ListLabels", "CreateLabel"];
3671
4475
  var supportedOperationIds = ["ListWorkItems", "CreateWorkItem", "GetWorkItem", "UpdateWorkItem", "ListComments", "CreateComment", "ListLinks", "CreateLink", "ListStates", "ListLabels", "CreateLabel"];
3672
4476
 
@@ -3862,18 +4666,18 @@ form.addEventListener("submit", (event) => {
3862
4666
  `.trim();
3863
4667
  var json5 = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
3864
4668
  var adminError4 = (status, message) => json5(status, { error: { type: "mockingbird_admin", message } });
3865
- var isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4669
+ var isRecord6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3866
4670
  var adminRoutes = (runtime) => ({
3867
4671
  "GET /work-items": ({ namespace }) => json5(200, { work_items: runtime.instance(namespace).workItems() }),
3868
4672
  "POST /work-items/:id/state": ({ params, body, namespace }) => {
3869
- if (!isRecord5(body) || typeof body.state !== "string") {
4673
+ if (!isRecord6(body) || typeof body.state !== "string") {
3870
4674
  return adminError4(400, 'expected {"state": "<state id or name, e.g. Done>"}');
3871
4675
  }
3872
4676
  const item = runtime.instance(namespace).moveToState(params.id, body.state);
3873
4677
  return item ? json5(200, item) : adminError4(404, `no work item ${params.id} or state ${body.state}`);
3874
4678
  },
3875
4679
  "POST /projects": ({ body, namespace }) => {
3876
- if (!isRecord5(body) || typeof body.workspace !== "string" || typeof body.project !== "string") {
4680
+ if (!isRecord6(body) || typeof body.workspace !== "string" || typeof body.project !== "string") {
3877
4681
  return adminError4(400, 'expected {"workspace": "<slug>", "project": "<uuid>"}');
3878
4682
  }
3879
4683
  const api = runtime.instance(namespace);
@@ -3882,7 +4686,7 @@ var adminRoutes = (runtime) => ({
3882
4686
  },
3883
4687
  "GET /settings": ({ namespace }) => json5(200, runtime.instance(namespace).state.current()),
3884
4688
  "PUT /settings": ({ body, namespace }) => {
3885
- if (!isRecord5(body)) return adminError4(400, "expected a JSON object");
4689
+ if (!isRecord6(body)) return adminError4(400, "expected a JSON object");
3886
4690
  const patch = {};
3887
4691
  if (body.apiKeys !== void 0) {
3888
4692
  if (!Array.isArray(body.apiKeys)) return adminError4(400, "apiKeys: string[]");
@@ -3903,7 +4707,7 @@ var adminRoutes = (runtime) => ({
3903
4707
  });
3904
4708
  var createRuntime2 = (options = {}) => createRuntime({
3905
4709
  name: PLANE_NAMESPACE,
3906
- document,
4710
+ document: document2,
3907
4711
  ...options.sqlite ? { sqlite: options.sqlite } : {},
3908
4712
  ...options.clock ? { clock: options.clock } : {},
3909
4713
  ...options.seed !== void 0 ? { seed: options.seed } : {},
@@ -3994,7 +4798,7 @@ var PlaneAPI = class {
3994
4798
  CreateLabel: (context) => this.createLabel(context)
3995
4799
  });
3996
4800
  this.service = createService({
3997
- document,
4801
+ document: document2,
3998
4802
  handlers,
3999
4803
  sqlite,
4000
4804
  namespace,
@@ -4328,7 +5132,7 @@ var PlaneAPI = class {
4328
5132
  };
4329
5133
 
4330
5134
  export {
4331
- document,
5135
+ document2 as document,
4332
5136
  operationIds,
4333
5137
  supportedOperationIds,
4334
5138
  DEFAULT_STATES,
@@ -4339,4 +5143,4 @@ export {
4339
5143
  apiKeyCredential,
4340
5144
  PlaneAPI
4341
5145
  };
4342
- //# sourceMappingURL=chunk-CCLSLHDJ.js.map
5146
+ //# sourceMappingURL=chunk-DDWGUZQ5.js.map