clearotron 0.3.0-beta.3 → 0.3.0-beta.5

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.
@@ -10309,6 +10309,12 @@ function decodeStatus(status, body) {
10309
10309
  };
10310
10310
  return { kind: "notFound" };
10311
10311
  case 409: {
10312
+ const refused = refusalDetail(body);
10313
+ if (refused?.code.startsWith("store_")) return {
10314
+ kind: "reject",
10315
+ errors: errorsOf(body),
10316
+ detail: refused
10317
+ };
10312
10318
  const msg = errorsOf(body)[0] ?? "This action could not be completed.";
10313
10319
  return /version|conflict/i.test(msg) && !/confirmation|plan again|re-confirm/i.test(msg) ? {
10314
10320
  kind: "conflict",
@@ -10764,7 +10770,33 @@ var api = {
10764
10770
  ...asString(r["sub"]) ? { sub: asString(r["sub"]) } : {},
10765
10771
  ...asString(r["verifiedOn"]) ? { verifiedOn: asString(r["verifiedOn"]) } : {},
10766
10772
  ...asString(r["by"]) ? { by: asString(r["by"]) } : {},
10767
- steps: asArray(r["steps"]).filter((x) => typeof x === "string" && x !== ""),
10773
+ steps: asArray(r["steps"]).flatMap((x) => {
10774
+ const s = asRecord(x);
10775
+ const text = asString(s["text"]);
10776
+ if (!text) return [];
10777
+ const hint = asString(s["hint"]);
10778
+ const c = s["copy"] == null ? null : asRecord(s["copy"]);
10779
+ let copy;
10780
+ if (c) {
10781
+ const kind = asString(c["kind"]);
10782
+ if (kind === "block" && asString(c["text"])) copy = {
10783
+ kind,
10784
+ text: asString(c["text"])
10785
+ };
10786
+ else if (kind === "secret" && asString(c["label"]) && asString(c["template"]) && asString(c["slot"]) && asString(c["template"]).includes(asString(c["slot"]))) copy = {
10787
+ kind,
10788
+ label: asString(c["label"]),
10789
+ template: asString(c["template"]),
10790
+ slot: asString(c["slot"])
10791
+ };
10792
+ else return [];
10793
+ }
10794
+ return [{
10795
+ text,
10796
+ ...hint ? { hint } : {},
10797
+ ...copy ? { copy } : {}
10798
+ }];
10799
+ }),
10768
10800
  launch: (() => {
10769
10801
  const u = asString(asRecord(r["launch"])["url"]);
10770
10802
  return u && /^https:\/\//.test(u) ? { url: u } : null;
@@ -11149,6 +11181,7 @@ var PATHS = {
11149
11181
  bookmark: "M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2Z",
11150
11182
  sliders: "M4 21v-7M4 10V3M12 21v-9M12 8V3M20 21v-5M20 12V3M1 14h6M9 8h6M17 16h6",
11151
11183
  server: "M20 2H4a2 2 0 0 0-2 2v4a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2ZM20 14H4a2 2 0 0 0-2 2v4a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-4a2 2 0 0 0-2-2ZM6 6h.01M6 18h.01",
11184
+ laptop: "M20 16V7a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v9m16 0H4m16 0 1.28 2.55a1 1 0 0 1-.9 1.45H3.62a1 1 0 0 1-.9-1.45L4 16",
11152
11185
  menu: "M3 12h18M3 6h18M3 18h18",
11153
11186
  x: "M18 6 6 18M6 6l12 12",
11154
11187
  chevron: "m9 18 6-6-6-6",
@@ -11156,6 +11189,7 @@ var PATHS = {
11156
11189
  "chevron-right": "m9 18 6-6-6-6",
11157
11190
  "grip-vertical": "M9 5h.01M15 5h.01M9 12h.01M15 12h.01M9 19h.01M15 19h.01",
11158
11191
  "arrow-right": "M5 12h14M13 6l6 6-6 6",
11192
+ "arrow-left": "M19 12H5M11 18l-6-6 6-6",
11159
11193
  trash: "M3 6h18M8 6V4h8v2M19 6l-1 14H6L5 6M10 11v6M14 11v6",
11160
11194
  search: "M11 19a8 8 0 1 0 0-16 8 8 0 0 0 0 16ZM21 21l-4.3-4.3",
11161
11195
  eye: "M2 12s4-7 10-7 10 7 10 7-4 7-10 7-10-7-10-7ZM12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6Z",
@@ -11487,6 +11521,157 @@ function accessSentence(input) {
11487
11521
  return limits.length ? `${first} They ${limits.join(", and ")}.` : first;
11488
11522
  }
11489
11523
  //#endregion
11524
+ //#region src/shell/permissions.ts
11525
+ function canManage(who) {
11526
+ return who.permissions.manage;
11527
+ }
11528
+ function canRun(who) {
11529
+ return who.permissions.run;
11530
+ }
11531
+ function seesEverything(who) {
11532
+ return who.allAccounts === true;
11533
+ }
11534
+ //#endregion
11535
+ //#region src/shell/CompanyPicker.tsx
11536
+ function CompanyCard({ row, onPick }) {
11537
+ return (0, import_jsx_runtime.jsxs)("button", {
11538
+ type: "button",
11539
+ className: row.generic ? "entry-card pick-generic" : "entry-card",
11540
+ onClick: onPick,
11541
+ style: {
11542
+ flexDirection: "row",
11543
+ alignItems: "center",
11544
+ gap: 16,
11545
+ padding: "15px 17px"
11546
+ },
11547
+ children: [(0, import_jsx_runtime.jsxs)("span", {
11548
+ style: {
11549
+ minWidth: 0,
11550
+ flex: 1,
11551
+ textAlign: "left"
11552
+ },
11553
+ children: [(0, import_jsx_runtime.jsxs)("span", {
11554
+ className: "entry-title",
11555
+ style: {
11556
+ display: "block",
11557
+ marginBottom: 2
11558
+ },
11559
+ children: [row.name, row.generic ? (0, import_jsx_runtime.jsx)("span", {
11560
+ className: "pill",
11561
+ style: {
11562
+ marginLeft: 8,
11563
+ verticalAlign: "middle",
11564
+ background: "transparent",
11565
+ borderColor: "var(--accent-ink)",
11566
+ color: "var(--text-accent)"
11567
+ },
11568
+ children: "Default"
11569
+ }) : null]
11570
+ }), (0, import_jsx_runtime.jsx)("span", {
11571
+ className: "entry-body",
11572
+ style: {
11573
+ display: "block",
11574
+ overflow: "hidden",
11575
+ textOverflow: "ellipsis",
11576
+ whiteSpace: "nowrap"
11577
+ },
11578
+ children: row.line
11579
+ })]
11580
+ }), (0, import_jsx_runtime.jsx)("span", {
11581
+ "aria-hidden": "true",
11582
+ style: {
11583
+ flex: "none",
11584
+ color: "var(--text-faint)",
11585
+ display: "inline-flex"
11586
+ },
11587
+ children: (0, import_jsx_runtime.jsx)(Icon, {
11588
+ name: "arrow-right",
11589
+ size: 17
11590
+ })
11591
+ })]
11592
+ });
11593
+ }
11594
+ function CompanyPicker({ eyebrow, heading, line, keys, orgOf, organisations, companyName, factsFor, onPick, onAdd }) {
11595
+ const { headings, groups } = pickerGroups(keys, orgOf, organisations, companyName, factsFor);
11596
+ return (0, import_jsx_runtime.jsxs)("div", {
11597
+ className: "screen",
11598
+ children: [(0, import_jsx_runtime.jsx)("div", {
11599
+ className: "eyebrow",
11600
+ children: eyebrow
11601
+ }), (0, import_jsx_runtime.jsxs)("div", {
11602
+ className: "measure",
11603
+ children: [(0, import_jsx_runtime.jsxs)("div", {
11604
+ style: {
11605
+ display: "flex",
11606
+ alignItems: "flex-end",
11607
+ gap: 20,
11608
+ margin: "4px 0 18px"
11609
+ },
11610
+ children: [(0, import_jsx_runtime.jsxs)("div", {
11611
+ style: { flex: 1 },
11612
+ children: [(0, import_jsx_runtime.jsx)("h1", {
11613
+ style: {
11614
+ fontSize: 27,
11615
+ margin: "0 0 4px",
11616
+ color: "var(--text-strong)"
11617
+ },
11618
+ children: heading
11619
+ }), (0, import_jsx_runtime.jsx)("p", {
11620
+ style: {
11621
+ margin: 0,
11622
+ color: "var(--text-muted)",
11623
+ fontSize: 14.5
11624
+ },
11625
+ children: line
11626
+ })]
11627
+ }), onAdd ? (0, import_jsx_runtime.jsx)("button", {
11628
+ type: "button",
11629
+ className: "start-pill",
11630
+ onClick: onAdd,
11631
+ style: {
11632
+ flex: "none",
11633
+ borderColor: "var(--accent-ink)",
11634
+ color: "var(--text-accent)",
11635
+ fontWeight: 700
11636
+ },
11637
+ children: "+ New company"
11638
+ }) : null]
11639
+ }), groups.map((g, i) => (0, import_jsx_runtime.jsxs)("div", { children: [headings && g.org ? (0, import_jsx_runtime.jsx)("div", {
11640
+ className: "eyebrow",
11641
+ style: { margin: i === 0 ? "0 0 8px" : "16px 0 8px" },
11642
+ "data-anon": "mark",
11643
+ children: g.org.name
11644
+ }) : null, (0, import_jsx_runtime.jsx)("div", {
11645
+ style: {
11646
+ display: "flex",
11647
+ flexDirection: "column",
11648
+ gap: 10,
11649
+ marginTop: !headings && i > 0 ? 10 : 0
11650
+ },
11651
+ children: g.rows.map((r) => (0, import_jsx_runtime.jsx)(CompanyCard, {
11652
+ row: r,
11653
+ onPick: () => onPick(r.key)
11654
+ }, r.key))
11655
+ })] }, g.org?.key ?? ""))]
11656
+ })]
11657
+ });
11658
+ }
11659
+ function CompanyGate({ ctx, eyebrow, heading, line }) {
11660
+ return (0, import_jsx_runtime.jsx)(CompanyPicker, {
11661
+ eyebrow,
11662
+ heading,
11663
+ line,
11664
+ keys: ctx.ownerKeys,
11665
+ orgOf: ctx.orgOf,
11666
+ organisations: ctx.organisations,
11667
+ companyName: ctx.ownerName,
11668
+ factsFor: ctx.factsFor,
11669
+ onPick: (key) => ctx.setOwner(key),
11670
+ onAdd: canManage(ctx.me) ? () => ctx.go(NEW_COMPANY_PATH) : void 0
11671
+ });
11672
+ }
11673
+ var NEW_COMPANY_PATH = "/portal/brand/new";
11674
+ //#endregion
11490
11675
  //#region src/shell/AppShell.tsx
11491
11676
  var MOBILE = "(max-width: 899px)";
11492
11677
  function useIsMobile() {
@@ -11741,7 +11926,8 @@ function AppShell({ render }) {
11741
11926
  }), (0, import_jsx_runtime.jsx)(BrandOwnerSwitcher, {
11742
11927
  grouped,
11743
11928
  value: ownerInView,
11744
- onChange: setOwnerGuarded
11929
+ onChange: setOwnerGuarded,
11930
+ onAdd: canManage(me) ? () => go(NEW_COMPANY_PATH) : void 0
11745
11931
  })]
11746
11932
  }), (0, import_jsx_runtime.jsx)(NavList, {
11747
11933
  entries: groups.owner,
@@ -11909,14 +12095,15 @@ function AppShell({ render }) {
11909
12095
  ]
11910
12096
  });
11911
12097
  }
11912
- function BrandOwnerSwitcher({ grouped, value, onChange }) {
12098
+ var NEW_COMPANY_OPTION = ":new-company";
12099
+ function BrandOwnerSwitcher({ grouped, value, onChange, onAdd }) {
11913
12100
  const option = (r) => (0, import_jsx_runtime.jsx)("option", {
11914
12101
  value: r.key,
11915
12102
  children: switcherLabel(r)
11916
12103
  }, r.key);
11917
12104
  return (0, import_jsx_runtime.jsxs)("select", {
11918
12105
  value: value ?? "",
11919
- onChange: (e) => onChange(e.target.value || null),
12106
+ onChange: (e) => e.target.value === NEW_COMPANY_OPTION ? onAdd?.() : onChange(e.target.value || null),
11920
12107
  "aria-label": "Company",
11921
12108
  style: {
11922
12109
  width: "100%",
@@ -11929,13 +12116,21 @@ function BrandOwnerSwitcher({ grouped, value, onChange }) {
11929
12116
  fontWeight: 700,
11930
12117
  color: "var(--text-strong)"
11931
12118
  },
11932
- children: [(0, import_jsx_runtime.jsx)("option", {
11933
- value: "",
11934
- children: ALL_OWNERS
11935
- }), grouped.groups.map((g) => grouped.headings && g.org ? (0, import_jsx_runtime.jsx)("optgroup", {
11936
- label: g.org.name,
11937
- children: g.rows.map(option)
11938
- }, g.org.key) : g.rows.map(option))]
12119
+ children: [
12120
+ (0, import_jsx_runtime.jsx)("option", {
12121
+ value: "",
12122
+ children: ALL_OWNERS
12123
+ }),
12124
+ grouped.groups.map((g) => grouped.headings && g.org ? (0, import_jsx_runtime.jsx)("optgroup", {
12125
+ label: g.org.name,
12126
+ children: g.rows.map(option)
12127
+ }, g.org.key) : g.rows.map(option)),
12128
+ onAdd ? (0, import_jsx_runtime.jsx)("option", {
12129
+ value: NEW_COMPANY_OPTION,
12130
+ "data-action": "new-company",
12131
+ children: "+ New company"
12132
+ }) : null
12133
+ ]
11939
12134
  });
11940
12135
  }
11941
12136
  var initials = (email) => {
@@ -12252,17 +12447,6 @@ function moveBefore(order, id, over) {
12252
12447
  return next;
12253
12448
  }
12254
12449
  //#endregion
12255
- //#region src/shell/permissions.ts
12256
- function canManage(who) {
12257
- return who.permissions.manage;
12258
- }
12259
- function canRun(who) {
12260
- return who.permissions.run;
12261
- }
12262
- function seesEverything(who) {
12263
- return who.allAccounts === true;
12264
- }
12265
- //#endregion
12266
12450
  //#region src/screens/Home.tsx
12267
12451
  var TERMINAL$1 = new Set([
12268
12452
  "delivered",
@@ -15944,146 +16128,6 @@ function unsavedChanges({ current, empty, saved, submitted }) {
15944
16128
  return current !== saved;
15945
16129
  }
15946
16130
  //#endregion
15947
- //#region src/shell/CompanyPicker.tsx
15948
- function CompanyCard({ row, onPick }) {
15949
- return (0, import_jsx_runtime.jsxs)("button", {
15950
- type: "button",
15951
- className: row.generic ? "entry-card pick-generic" : "entry-card",
15952
- onClick: onPick,
15953
- style: {
15954
- flexDirection: "row",
15955
- alignItems: "center",
15956
- gap: 16,
15957
- padding: "15px 17px"
15958
- },
15959
- children: [(0, import_jsx_runtime.jsxs)("span", {
15960
- style: {
15961
- minWidth: 0,
15962
- flex: 1,
15963
- textAlign: "left"
15964
- },
15965
- children: [(0, import_jsx_runtime.jsxs)("span", {
15966
- className: "entry-title",
15967
- style: {
15968
- display: "block",
15969
- marginBottom: 2
15970
- },
15971
- children: [row.name, row.generic ? (0, import_jsx_runtime.jsx)("span", {
15972
- className: "pill",
15973
- style: {
15974
- marginLeft: 8,
15975
- verticalAlign: "middle",
15976
- background: "transparent",
15977
- borderColor: "var(--accent-ink)",
15978
- color: "var(--text-accent)"
15979
- },
15980
- children: "Default"
15981
- }) : null]
15982
- }), (0, import_jsx_runtime.jsx)("span", {
15983
- className: "entry-body",
15984
- style: {
15985
- display: "block",
15986
- overflow: "hidden",
15987
- textOverflow: "ellipsis",
15988
- whiteSpace: "nowrap"
15989
- },
15990
- children: row.line
15991
- })]
15992
- }), (0, import_jsx_runtime.jsx)("span", {
15993
- "aria-hidden": "true",
15994
- style: {
15995
- flex: "none",
15996
- color: "var(--text-faint)",
15997
- display: "inline-flex"
15998
- },
15999
- children: (0, import_jsx_runtime.jsx)(Icon, {
16000
- name: "arrow-right",
16001
- size: 17
16002
- })
16003
- })]
16004
- });
16005
- }
16006
- function CompanyPicker({ eyebrow, heading, line, keys, orgOf, organisations, companyName, factsFor, onPick, onAdd }) {
16007
- const { headings, groups } = pickerGroups(keys, orgOf, organisations, companyName, factsFor);
16008
- return (0, import_jsx_runtime.jsxs)("div", {
16009
- className: "screen",
16010
- children: [(0, import_jsx_runtime.jsx)("div", {
16011
- className: "eyebrow",
16012
- children: eyebrow
16013
- }), (0, import_jsx_runtime.jsxs)("div", {
16014
- className: "measure",
16015
- children: [(0, import_jsx_runtime.jsxs)("div", {
16016
- style: {
16017
- display: "flex",
16018
- alignItems: "flex-end",
16019
- gap: 20,
16020
- margin: "4px 0 18px"
16021
- },
16022
- children: [(0, import_jsx_runtime.jsxs)("div", {
16023
- style: { flex: 1 },
16024
- children: [(0, import_jsx_runtime.jsx)("h1", {
16025
- style: {
16026
- fontSize: 27,
16027
- margin: "0 0 4px",
16028
- color: "var(--text-strong)"
16029
- },
16030
- children: heading
16031
- }), (0, import_jsx_runtime.jsx)("p", {
16032
- style: {
16033
- margin: 0,
16034
- color: "var(--text-muted)",
16035
- fontSize: 14.5
16036
- },
16037
- children: line
16038
- })]
16039
- }), onAdd ? (0, import_jsx_runtime.jsx)("button", {
16040
- type: "button",
16041
- className: "start-pill",
16042
- onClick: onAdd,
16043
- style: {
16044
- flex: "none",
16045
- borderColor: "var(--accent-ink)",
16046
- color: "var(--text-accent)",
16047
- fontWeight: 700
16048
- },
16049
- children: "+ New company"
16050
- }) : null]
16051
- }), groups.map((g, i) => (0, import_jsx_runtime.jsxs)("div", { children: [headings && g.org ? (0, import_jsx_runtime.jsx)("div", {
16052
- className: "eyebrow",
16053
- style: { margin: i === 0 ? "0 0 8px" : "16px 0 8px" },
16054
- "data-anon": "mark",
16055
- children: g.org.name
16056
- }) : null, (0, import_jsx_runtime.jsx)("div", {
16057
- style: {
16058
- display: "flex",
16059
- flexDirection: "column",
16060
- gap: 10,
16061
- marginTop: !headings && i > 0 ? 10 : 0
16062
- },
16063
- children: g.rows.map((r) => (0, import_jsx_runtime.jsx)(CompanyCard, {
16064
- row: r,
16065
- onPick: () => onPick(r.key)
16066
- }, r.key))
16067
- })] }, g.org?.key ?? ""))]
16068
- })]
16069
- });
16070
- }
16071
- function CompanyGate({ ctx, eyebrow, heading, line }) {
16072
- return (0, import_jsx_runtime.jsx)(CompanyPicker, {
16073
- eyebrow,
16074
- heading,
16075
- line,
16076
- keys: ctx.ownerKeys,
16077
- orgOf: ctx.orgOf,
16078
- organisations: ctx.organisations,
16079
- companyName: ctx.ownerName,
16080
- factsFor: ctx.factsFor,
16081
- onPick: (key) => ctx.setOwner(key),
16082
- onAdd: canManage(ctx.me) ? () => ctx.go(NEW_COMPANY_PATH) : void 0
16083
- });
16084
- }
16085
- var NEW_COMPANY_PATH = "/portal/brand/new";
16086
- //#endregion
16087
16131
  //#region src/contract/companyCreated.ts
16088
16132
  var pending = null;
16089
16133
  function handOff(created) {
@@ -20031,19 +20075,19 @@ var WHAT_YOU_CAN_DO = [
20031
20075
  "Ask what-if: why a finding was rated as it was, what changes if the goods narrow"
20032
20076
  ];
20033
20077
  var COPIED_MS = 2600;
20078
+ var PLACES = ["disk", "public-http"];
20034
20079
  var WHERE = {
20035
- disk: "On this computer",
20036
- "public-http": "Somewhere else"
20037
- };
20038
- var EITHER = "either";
20039
- var placesOf = (offers) => {
20040
- const named = [...new Set(offers.map((o) => o.route ?? "public-http"))].filter((r) => r !== EITHER);
20041
- return named.length ? named : offers.length ? ["disk"] : [];
20042
- };
20043
- var servesPlace = (o, place) => {
20044
- const r = o.route ?? "public-http";
20045
- return r === place || r === EITHER;
20080
+ disk: {
20081
+ label: "Clearotron is installed on this machine (laptop/desktop)",
20082
+ icon: "laptop"
20083
+ },
20084
+ "public-http": {
20085
+ label: "Clearotron is running elsewhere (e.g. Cloud/Server)",
20086
+ icon: "server"
20087
+ }
20046
20088
  };
20089
+ var HELP_URL = "https://github.com/CordilleraSarl/clearotron/blob/main/mcp-server/CONNECT.md";
20090
+ var HELP_LINK_TEXT = "GitHub MCP Connector Documentation";
20047
20091
  async function copy(text) {
20048
20092
  try {
20049
20093
  await navigator.clipboard.writeText(text);
@@ -20053,77 +20097,121 @@ async function copy(text) {
20053
20097
  }
20054
20098
  }
20055
20099
  var mask = (key) => key.length > 3 ? `${key.slice(0, 3)}••••` : "••••";
20056
- var pasteLine = (offer) => offer.pasteAs ?? `Paste it into ${offer.name}`;
20057
- function Destination({ offer, selected, copied, onPick }) {
20058
- return (0, import_jsx_runtime.jsxs)("button", {
20059
- type: "button",
20060
- className: "ai-dest",
20061
- "data-id": offer.id,
20062
- "data-selected": selected ? "" : void 0,
20063
- "data-copied": copied ? "" : void 0,
20064
- "aria-pressed": selected,
20065
- onClick: onPick,
20066
- children: [(0, import_jsx_runtime.jsxs)("span", {
20067
- className: "ai-dest-name",
20068
- children: [offer.name, offer.sub ? (0, import_jsx_runtime.jsx)("span", {
20069
- className: "ai-dest-sub",
20070
- children: offer.sub
20071
- }) : null]
20072
- }), (0, import_jsx_runtime.jsxs)("span", {
20073
- className: "ai-dest-say",
20074
- children: [(0, import_jsx_runtime.jsx)("span", {
20075
- className: "ai-dest-idle",
20076
- children: pasteLine(offer)
20077
- }), (0, import_jsx_runtime.jsx)("span", {
20078
- className: "ai-dest-done",
20079
- children: "✓ Copied"
20080
- })]
20081
- })]
20082
- });
20083
- }
20084
- function Panel({ offer, landed }) {
20100
+ var fill = (c, value) => c.template.split(c.slot).join(value);
20101
+ function Marked({ text }) {
20102
+ const parts = text.split(/(\*\*[^*]+\*\*|`[^`]+`)/g).filter((p) => p !== "");
20103
+ return (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: parts.map((p, n) => p.length > 4 && p.startsWith("**") && p.endsWith("**") ? (0, import_jsx_runtime.jsx)("b", { children: p.slice(2, -2) }, n) : p.length > 2 && p.startsWith("`") && p.endsWith("`") ? (0, import_jsx_runtime.jsx)("code", {
20104
+ className: "ai-code",
20105
+ children: p.slice(1, -1)
20106
+ }, n) : (0, import_jsx_runtime.jsx)("span", { children: p }, n)) });
20107
+ }
20108
+ var AT_REST = {
20109
+ copiedAt: null,
20110
+ landedAt: null,
20111
+ landed: null,
20112
+ revealedAt: null,
20113
+ revealed: null
20114
+ };
20115
+ function StepsPanel({ offer, measuring = false, state = AT_REST, onBlock, onSecret }) {
20085
20116
  return (0, import_jsx_runtime.jsxs)("div", {
20086
- className: "ai-panel",
20087
- "data-for": offer.id,
20117
+ className: "steps-panel",
20118
+ "data-for": measuring ? void 0 : offer.id,
20088
20119
  children: [
20120
+ (0, import_jsx_runtime.jsx)("div", {
20121
+ className: "eyebrow",
20122
+ children: "Steps for"
20123
+ }),
20089
20124
  (0, import_jsx_runtime.jsx)("h3", {
20090
- className: "ai-panel-head",
20091
- children: pasteLine(offer)
20125
+ className: "steps-name",
20126
+ children: offer.name
20092
20127
  }),
20093
- landed ? (0, import_jsx_runtime.jsxs)("p", {
20094
- className: "ai-panel-landed",
20095
- children: ["On your clipboard now: ", (0, import_jsx_runtime.jsx)("code", {
20096
- className: "mono",
20097
- children: landed
20098
- })]
20099
- }) : null,
20100
- offer.steps.length ? (0, import_jsx_runtime.jsx)("ol", {
20101
- className: "ai-panel-steps",
20102
- children: offer.steps.map((line) => (0, import_jsx_runtime.jsx)("li", { children: line }, line))
20103
- }) : null,
20104
- offer.verifiedOn ? (0, import_jsx_runtime.jsxs)("p", {
20105
- className: "ai-panel-stamp",
20106
- children: [
20107
- "✓ Checked ",
20108
- offer.verifiedOn,
20109
- " — these are the taps this app actually has today."
20110
- ]
20111
- }) : (0, import_jsx_runtime.jsx)("p", {
20112
- className: "ai-panel-stamp ai-panel-stamp-none",
20113
- children: "These steps name no button we have not opened ourselves. Your app may word them differently."
20128
+ (0, import_jsx_runtime.jsx)("ol", {
20129
+ className: "steps",
20130
+ children: offer.steps.map((s, i) => {
20131
+ const c = s.copy;
20132
+ const flashed = !measuring && state.copiedAt === i;
20133
+ const landed = measuring && c?.kind === "secret" ? fill(c, mask("")) : state.landedAt === i ? state.landed : null;
20134
+ return (0, import_jsx_runtime.jsx)("li", { children: (0, import_jsx_runtime.jsxs)("div", { children: [
20135
+ (0, import_jsx_runtime.jsx)("div", {
20136
+ className: "step-text",
20137
+ children: (0, import_jsx_runtime.jsx)(Marked, { text: s.text })
20138
+ }),
20139
+ c?.kind === "block" ? (0, import_jsx_runtime.jsxs)("div", {
20140
+ className: "codeblock",
20141
+ children: [(0, import_jsx_runtime.jsx)("pre", { children: c.text }), (0, import_jsx_runtime.jsx)("button", {
20142
+ type: "button",
20143
+ className: `btn-ghost${flashed ? " is-copied" : ""}`,
20144
+ "data-step": i,
20145
+ tabIndex: measuring ? -1 : void 0,
20146
+ onClick: () => onBlock?.(i, c.text),
20147
+ children: flashed ? "✓ Copied" : "Copy"
20148
+ })]
20149
+ }) : null,
20150
+ c?.kind === "secret" ? (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
20151
+ (0, import_jsx_runtime.jsx)("div", {
20152
+ className: "secret-btn",
20153
+ children: (0, import_jsx_runtime.jsx)("button", {
20154
+ type: "button",
20155
+ className: `btn-primary${flashed ? " is-copied" : ""}`,
20156
+ "data-step": i,
20157
+ tabIndex: measuring ? -1 : void 0,
20158
+ onClick: () => onSecret?.(i, c),
20159
+ children: flashed ? (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [(0, import_jsx_runtime.jsx)(Icon, {
20160
+ name: "check",
20161
+ size: 15
20162
+ }), "Copied"] }) : landed ? "Copy again" : c.label
20163
+ })
20164
+ }),
20165
+ landed ? (0, import_jsx_runtime.jsxs)("div", {
20166
+ className: "landed",
20167
+ children: [(0, import_jsx_runtime.jsx)("div", {
20168
+ className: "landed-label",
20169
+ children: "On your clipboard now"
20170
+ }), landed.split("\n").map((line, n) => (0, import_jsx_runtime.jsx)("div", {
20171
+ className: "mono",
20172
+ children: line
20173
+ }, n))]
20174
+ }) : null,
20175
+ !measuring && state.revealedAt === i && state.revealed ? (0, import_jsx_runtime.jsxs)("div", {
20176
+ className: "landed",
20177
+ children: [(0, import_jsx_runtime.jsx)("p", {
20178
+ className: "ai-said",
20179
+ children: "Your browser would not let us copy it. Copy this by hand — it is yours, and it will not be shown again."
20180
+ }), (0, import_jsx_runtime.jsx)("pre", {
20181
+ className: "ai-pre",
20182
+ children: state.revealed
20183
+ })]
20184
+ }) : null
20185
+ ] }) : null,
20186
+ s.hint ? (0, import_jsx_runtime.jsx)("div", {
20187
+ className: "step-hint",
20188
+ children: (0, import_jsx_runtime.jsx)(Marked, { text: s.hint })
20189
+ }) : null
20190
+ ] }) }, i);
20191
+ })
20114
20192
  })
20115
20193
  ]
20116
20194
  });
20117
20195
  }
20196
+ function NotOnline() {
20197
+ return (0, import_jsx_runtime.jsxs)("div", {
20198
+ className: "notice ai-none",
20199
+ children: [(0, import_jsx_runtime.jsx)("strong", { children: "Not available on this installation yet" }), (0, import_jsx_runtime.jsx)("p", { children: "Your AI app reaches Clearotron over the internet, and this installation isn’t on the internet yet. Whoever installed it can put it online — it takes about a minute and needs no account." })]
20200
+ });
20201
+ }
20118
20202
  function UseYourAI({ ctx }) {
20119
20203
  const [access, setAccess] = (0, import_react.useState)(null);
20120
- const [route, setRoute] = (0, import_react.useState)(null);
20204
+ const [place, setPlace] = (0, import_react.useState)(null);
20121
20205
  const [picked, setPicked] = (0, import_react.useState)(null);
20206
+ const [copiedAt, setCopiedAt] = (0, import_react.useState)(null);
20207
+ const [landedAt, setLandedAt] = (0, import_react.useState)(null);
20122
20208
  const [landed, setLanded] = (0, import_react.useState)(null);
20123
- const [copiedId, setCopiedId] = (0, import_react.useState)(null);
20124
20209
  const [failed, setFailed] = (0, import_react.useState)(null);
20210
+ const [revealedAt, setRevealedAt] = (0, import_react.useState)(null);
20125
20211
  const [revealed, setRevealed] = (0, import_react.useState)(null);
20212
+ const [slotHeight, setSlotHeight] = (0, import_react.useState)(0);
20126
20213
  const timer = (0, import_react.useRef)(null);
20214
+ const probe = (0, import_react.useRef)(null);
20127
20215
  (0, import_react.useEffect)(() => {
20128
20216
  api.mcpAccess().then((r) => {
20129
20217
  if (r.kind === "ok") setAccess(r.value);
@@ -20133,43 +20221,66 @@ function UseYourAI({ ctx }) {
20133
20221
  if (timer.current) window.clearTimeout(timer.current);
20134
20222
  }, []);
20135
20223
  const served = (access?.offers ?? []).filter((o) => o.served);
20136
- const routes = placesOf(served);
20137
- const active = route ?? (routes.includes("disk") ? "disk" : routes[0] ?? null);
20138
- const here = served.filter((o) => active !== null && servesPlace(o, active));
20224
+ const routes = PLACES.filter((p) => served.some((o) => o.route === p));
20225
+ const asks = routes.includes("disk");
20226
+ const active = asks ? place : routes[0] ?? null;
20227
+ const here = served.filter((o) => active !== null && o.route === active);
20139
20228
  const chosen = here.find((o) => o.id === picked) ?? null;
20140
- const press = async (offer) => {
20141
- setPicked(offer.id);
20229
+ (0, import_react.useLayoutEffect)(() => {
20230
+ const el = probe.current;
20231
+ if (!el) return;
20232
+ const measure = () => setSlotHeight(Math.max(0, ...[...el.children].map((c) => c.offsetHeight)));
20233
+ measure();
20234
+ const seen = new ResizeObserver(measure);
20235
+ seen.observe(el);
20236
+ return () => seen.disconnect();
20237
+ }, [active, here.length]);
20238
+ const settle = () => {
20239
+ setCopiedAt(null);
20240
+ setLandedAt(null);
20241
+ setLanded(null);
20242
+ setRevealedAt(null);
20243
+ setRevealed(null);
20244
+ setFailed(null);
20245
+ };
20246
+ const flash = (i) => {
20247
+ setCopiedAt(i);
20248
+ if (timer.current) window.clearTimeout(timer.current);
20249
+ timer.current = window.setTimeout(() => setCopiedAt(null), COPIED_MS);
20250
+ };
20251
+ const copyBlock = async (i, text) => {
20142
20252
  setFailed(null);
20253
+ if (!await copy(text)) {
20254
+ setFailed("Your browser would not let us copy it. Select the text above and copy it by hand.");
20255
+ return;
20256
+ }
20257
+ flash(i);
20258
+ };
20259
+ const press = async (i, c) => {
20260
+ setFailed(null);
20261
+ setRevealedAt(null);
20143
20262
  setRevealed(null);
20144
- setLanded(null);
20145
- if (offer.command) {
20146
- if (!await copy(offer.command)) {
20147
- setFailed("Your browser would not let us copy it. The steps below still apply.");
20148
- return;
20149
- }
20150
- setLanded(offer.command);
20151
- } else {
20152
- const r = await api.connectKey();
20153
- if (r.kind !== "ok") {
20154
- setFailed("We could not set this up just now. Try again, or ask us.");
20155
- return;
20156
- }
20157
- if (!await copy(`${r.value.address}\n${r.value.key}`)) {
20158
- setRevealed({
20159
- address: r.value.address,
20160
- key: r.value.key
20161
- });
20162
- return;
20163
- }
20164
- setLanded(`${r.value.address} ${mask(r.value.key)}`);
20263
+ const r = await api.connectKey();
20264
+ if (r.kind !== "ok") {
20265
+ setFailed("We could not set this up just now. Try again, or ask us.");
20266
+ return;
20165
20267
  }
20166
- setCopiedId(offer.id);
20167
- if (timer.current) window.clearTimeout(timer.current);
20168
- timer.current = window.setTimeout(() => setCopiedId(null), COPIED_MS);
20268
+ if (!await copy(fill(c, r.value.key))) {
20269
+ setRevealedAt(i);
20270
+ setRevealed(fill(c, r.value.key));
20271
+ return;
20272
+ }
20273
+ setLandedAt(i);
20274
+ setLanded(fill(c, mask(r.value.key)));
20275
+ flash(i);
20169
20276
  };
20170
20277
  return (0, import_jsx_runtime.jsxs)("div", {
20171
20278
  className: "screen ai-screen",
20172
20279
  children: [
20280
+ (0, import_jsx_runtime.jsx)("div", {
20281
+ className: "eyebrow",
20282
+ children: "Use your own AI"
20283
+ }),
20173
20284
  (0, import_jsx_runtime.jsx)("h1", {
20174
20285
  className: "ai-title",
20175
20286
  children: "Use your own AI"
@@ -20179,136 +20290,156 @@ function UseYourAI({ ctx }) {
20179
20290
  children: "Run and interrogate clearances from the assistant you already use — by voice, by email, or just by asking."
20180
20291
  }),
20181
20292
  (0, import_jsx_runtime.jsxs)("section", {
20182
- className: "ai-can",
20293
+ className: "ctx-card ai-can",
20183
20294
  children: [
20184
20295
  (0, import_jsx_runtime.jsx)("h2", { children: "What you can do" }),
20185
- (0, import_jsx_runtime.jsx)("ul", { children: WHAT_YOU_CAN_DO.map((line) => (0, import_jsx_runtime.jsx)("li", { children: line }, line)) }),
20296
+ (0, import_jsx_runtime.jsx)("ul", {
20297
+ className: "ai-can-list",
20298
+ children: WHAT_YOU_CAN_DO.map((line) => (0, import_jsx_runtime.jsxs)("li", { children: [(0, import_jsx_runtime.jsx)(Icon, {
20299
+ name: "check",
20300
+ size: 15
20301
+ }), line] }, line))
20302
+ }),
20186
20303
  (0, import_jsx_runtime.jsxs)("p", {
20187
20304
  className: "ai-can-foot",
20188
20305
  children: [
20189
- "Every report also has an ",
20306
+ "Use the ",
20190
20307
  (0, import_jsx_runtime.jsx)("strong", { children: "Ask AI" }),
20191
- " button that jumps straight to that run.",
20192
- " ",
20193
- (0, import_jsx_runtime.jsx)("strong", { children: "What you set up here reaches your own clearances and nothing else." })
20308
+ " button on a report."
20194
20309
  ]
20195
20310
  })
20196
20311
  ]
20197
20312
  }),
20198
- (0, import_jsx_runtime.jsx)("h2", {
20199
- className: "ai-connect-head",
20200
- children: "Connect it"
20201
- }),
20202
- !served.length ? (0, import_jsx_runtime.jsxs)("p", {
20203
- className: "ai-none",
20204
- children: [(0, import_jsx_runtime.jsx)("strong", { children: "Not available on this installation yet." }), " Your assistants reach this service over the internet, and this installation is not on the internet. Whoever installed it can put it online — it takes about a minute and needs no account."]
20205
- }) : (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
20206
- routes.length > 1 ? (0, import_jsx_runtime.jsxs)("div", {
20207
- className: "ai-where",
20208
- children: [
20209
- (0, import_jsx_runtime.jsx)("h3", {
20210
- className: "ai-where-q",
20211
- children: "Where does your assistant run?"
20212
- }),
20213
- (0, import_jsx_runtime.jsx)("div", {
20214
- className: "ai-seg",
20215
- role: "group",
20216
- "aria-label": "Where does your assistant run?",
20217
- children: routes.map((r) => (0, import_jsx_runtime.jsx)("button", {
20218
- type: "button",
20219
- className: "ai-seg-btn",
20220
- "data-on": r === active ? "" : void 0,
20221
- "aria-pressed": r === active,
20222
- onClick: () => {
20223
- setRoute(r);
20224
- setPicked(null);
20225
- setLanded(null);
20226
- setRevealed(null);
20227
- setFailed(null);
20228
- },
20229
- children: WHERE[r] ?? WHERE["public-http"]
20230
- }, r))
20231
- }),
20232
- (0, import_jsx_runtime.jsx)("p", {
20233
- className: "ai-where-sub",
20234
- children: "Pick your assistant. We copy the one line it needs — nothing to sign up for, nothing opened up."
20235
- })
20236
- ]
20237
- }) : (0, import_jsx_runtime.jsx)("p", {
20238
- className: "ai-where-sub",
20239
- children: "Pick where you will paste it. One press copies everything your assistant needs."
20240
- }),
20241
- (0, import_jsx_runtime.jsx)("div", {
20242
- className: "ai-dests",
20243
- children: here.map((o) => (0, import_jsx_runtime.jsx)(Destination, {
20244
- offer: o,
20245
- selected: o.id === picked,
20246
- copied: o.id === copiedId,
20247
- onPick: () => void press(o)
20248
- }, o.id))
20249
- }),
20250
- (0, import_jsx_runtime.jsxs)("div", {
20251
- className: "ai-slot",
20252
- "aria-live": "polite",
20253
- children: [failed ? (0, import_jsx_runtime.jsx)("p", {
20254
- className: "ai-said",
20255
- children: failed
20256
- }) : null, revealed ? (0, import_jsx_runtime.jsxs)("div", {
20257
- className: "ai-panel",
20258
- children: [(0, import_jsx_runtime.jsxs)("p", {
20259
- className: "ai-said",
20260
- children: [
20261
- "Your browser would not let us copy it. Paste these two lines into",
20262
- " ",
20263
- chosen?.name ?? "your assistant",
20264
- " — they are yours, and they will not be shown again."
20265
- ]
20266
- }), (0, import_jsx_runtime.jsxs)("pre", {
20267
- className: "ai-pre",
20313
+ (0, import_jsx_runtime.jsxs)("section", {
20314
+ className: "ai-connect",
20315
+ children: [(0, import_jsx_runtime.jsx)("h2", {
20316
+ className: "ai-connect-head",
20317
+ children: "Connect it"
20318
+ }), !served.length ? (0, import_jsx_runtime.jsx)(NotOnline, {}) : (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [asks ? (0, import_jsx_runtime.jsx)("div", {
20319
+ className: "where-grid",
20320
+ role: "group",
20321
+ "aria-label": "Where Clearotron is running",
20322
+ children: PLACES.map((p) => (0, import_jsx_runtime.jsxs)("button", {
20323
+ type: "button",
20324
+ className: "where-card",
20325
+ "data-place": p,
20326
+ "aria-pressed": p === active,
20327
+ onClick: () => {
20328
+ if (p !== active) {
20329
+ setPlace(p);
20330
+ setPicked(null);
20331
+ settle();
20332
+ }
20333
+ },
20334
+ children: [
20335
+ (0, import_jsx_runtime.jsx)("span", {
20336
+ className: p === active ? "radio radio-on" : "radio",
20337
+ "aria-hidden": "true"
20338
+ }),
20339
+ (0, import_jsx_runtime.jsx)(Icon, {
20340
+ name: WHERE[p].icon,
20341
+ size: 19
20342
+ }),
20343
+ WHERE[p].label
20344
+ ]
20345
+ }, p))
20346
+ }) : null, active === null ? null : !here.length ? (0, import_jsx_runtime.jsx)(NotOnline, {}) : (0, import_jsx_runtime.jsxs)("div", {
20347
+ className: "ai-connect-grid",
20348
+ children: [(0, import_jsx_runtime.jsxs)("div", { children: [(0, import_jsx_runtime.jsx)("h3", {
20349
+ className: "section-title",
20350
+ children: "Select AI app"
20351
+ }), (0, import_jsx_runtime.jsx)("div", {
20352
+ className: "ai-apps",
20353
+ children: here.map((o) => (0, import_jsx_runtime.jsxs)("button", {
20354
+ type: "button",
20355
+ className: `pick-row ai-app${o.id === picked ? " pick-row-on" : ""}`,
20356
+ "data-id": o.id,
20357
+ "aria-pressed": o.id === picked,
20358
+ onClick: () => {
20359
+ if (o.id !== picked) {
20360
+ setPicked(o.id);
20361
+ settle();
20362
+ }
20363
+ },
20268
20364
  children: [
20269
- revealed.address,
20270
- "\n",
20271
- revealed.key
20365
+ (0, import_jsx_runtime.jsx)("span", {
20366
+ className: o.id === picked ? "radio radio-on" : "radio",
20367
+ "aria-hidden": "true"
20368
+ }),
20369
+ (0, import_jsx_runtime.jsxs)("span", {
20370
+ className: "ai-app-label",
20371
+ children: [(0, import_jsx_runtime.jsx)("span", {
20372
+ className: "ai-app-name",
20373
+ children: o.name
20374
+ }), o.sub ? (0, import_jsx_runtime.jsx)("span", {
20375
+ className: "ai-app-sub",
20376
+ children: o.sub
20377
+ }) : null]
20378
+ }),
20379
+ (0, import_jsx_runtime.jsx)("span", {
20380
+ className: "ai-app-go",
20381
+ children: (0, import_jsx_runtime.jsx)(Icon, {
20382
+ name: "chevron",
20383
+ size: 16
20384
+ })
20385
+ })
20272
20386
  ]
20273
- })]
20274
- }) : chosen ? (0, import_jsx_runtime.jsx)(Panel, {
20275
- offer: chosen,
20276
- landed
20277
- }) : (0, import_jsx_runtime.jsx)("p", {
20278
- className: "ai-slot-empty",
20279
- children: "Pick your assistant and we will copy what it needs, then show you exactly where it goes."
20387
+ }, o.id))
20388
+ })] }), (0, import_jsx_runtime.jsxs)("div", {
20389
+ className: "ai-slot",
20390
+ "aria-live": "polite",
20391
+ style: slotHeight ? { minHeight: slotHeight } : void 0,
20392
+ children: [
20393
+ chosen ? (0, import_jsx_runtime.jsx)(StepsPanel, {
20394
+ offer: chosen,
20395
+ state: {
20396
+ copiedAt,
20397
+ landedAt,
20398
+ landed,
20399
+ revealedAt,
20400
+ revealed
20401
+ },
20402
+ onBlock: (i, text) => void copyBlock(i, text),
20403
+ onSecret: (i, c) => void press(i, c)
20404
+ }) : (0, import_jsx_runtime.jsx)("div", {
20405
+ className: "steps-panel steps-panel-empty",
20406
+ children: (0, import_jsx_runtime.jsxs)("span", {
20407
+ className: "steps-empty",
20408
+ children: [(0, import_jsx_runtime.jsx)(Icon, {
20409
+ name: "arrow-left",
20410
+ size: 16
20411
+ }), "Select AI to see instructions"]
20412
+ })
20413
+ }),
20414
+ failed ? (0, import_jsx_runtime.jsx)("p", {
20415
+ className: "ai-said ai-failed",
20416
+ children: failed
20417
+ }) : null,
20418
+ (0, import_jsx_runtime.jsx)("div", {
20419
+ className: "ai-probe",
20420
+ ref: probe,
20421
+ "aria-hidden": "true",
20422
+ children: here.map((o) => (0, import_jsx_runtime.jsx)(StepsPanel, {
20423
+ offer: o,
20424
+ measuring: true
20425
+ }, o.id))
20426
+ })
20427
+ ]
20280
20428
  })]
20281
- })
20282
- ] }),
20283
- (0, import_jsx_runtime.jsxs)("details", {
20284
- className: "ai-help",
20285
- children: [(0, import_jsx_runtime.jsx)("summary", { children: "Not connecting, or want to set it up yourself?" }), (0, import_jsx_runtime.jsxs)("div", {
20286
- className: "ai-help-body",
20287
- children: [
20288
- (0, import_jsx_runtime.jsxs)("p", { children: [
20289
- "Press the button for your assistant and it will tell you the next thing to do — usually three taps inside that app’s own settings. If your assistant is not listed, pick",
20290
- (0, import_jsx_runtime.jsx)("strong", { children: " Another agent" }),
20291
- ": it will give you both of the things any assistant can take."
20292
- ] }),
20293
- (0, import_jsx_runtime.jsxs)("p", { children: [
20294
- "Once it is connected, you do not have to learn anything new. Ask it in your own words —",
20295
- (0, import_jsx_runtime.jsx)("em", { children: " “start a knockout for our new drinks name across the US”" }),
20296
- ", or",
20297
- (0, import_jsx_runtime.jsx)("em", { children: " “why did you rate that one high?”" }),
20298
- " — and it will do the same work you would do on these screens."
20299
- ] }),
20300
- (0, import_jsx_runtime.jsx)("p", { children: "If a press does not finish, the most common reason is that your assistant runs somewhere this service cannot be reached from. The team who set this up can tell you in a sentence." }),
20301
- (0, import_jsx_runtime.jsxs)("p", {
20302
- className: "ai-help-doc",
20303
- children: [(0, import_jsx_runtime.jsx)("a", {
20304
- href: "/portal/connect-help",
20305
- target: "_blank",
20306
- rel: "noreferrer",
20307
- children: "The full technical instructions"
20308
- }), " — written for an engineer, or for an assistant reading on your behalf."]
20309
- })
20310
- ]
20311
- })]
20429
+ })] })]
20430
+ }),
20431
+ (0, import_jsx_runtime.jsxs)("div", {
20432
+ className: "notice quiet ai-help",
20433
+ children: [
20434
+ (0, import_jsx_runtime.jsx)("strong", { children: "If it doesn’t connect" }),
20435
+ (0, import_jsx_runtime.jsx)("p", { children: "Point your AI at the full technical setup instructions to diagnose." }),
20436
+ (0, import_jsx_runtime.jsx)("p", { children: (0, import_jsx_runtime.jsxs)("a", {
20437
+ href: HELP_URL,
20438
+ target: "_blank",
20439
+ rel: "noreferrer",
20440
+ children: [HELP_LINK_TEXT, " ↗"]
20441
+ }) })
20442
+ ]
20312
20443
  })
20313
20444
  ]
20314
20445
  });