clearotron 0.3.1-beta.2 → 0.3.1-beta.4

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.
Files changed (45) hide show
  1. package/bin/grant.mjs +21 -22
  2. package/build-info.json +2 -2
  3. package/driver/CHANGELOG.md +33 -0
  4. package/driver/citation-census.json +3 -3
  5. package/driver/common-law-receipts.mjs +11 -1
  6. package/driver/connotation-search.mjs +56 -5
  7. package/driver/contract-e3-backlog.mjs +31 -31
  8. package/driver/contract-vocabulary.mjs +59 -58
  9. package/driver/engine/mcp/gather-config.mjs +1 -1
  10. package/driver/gateway.mjs +29 -2
  11. package/driver/matter-frame-record.mjs +54 -1
  12. package/driver/package.json +1 -1
  13. package/driver/portal-config-view.mjs +26 -1
  14. package/driver/portal-report.mjs +16 -2
  15. package/driver/portal-service.mjs +327 -8
  16. package/driver/publish/render.mjs +0 -45
  17. package/driver/publish/templates/report.css +1 -30
  18. package/driver/skills/knockout-assess/SKILL.md +35 -6
  19. package/driver/skills/knockout-frame/SKILL.md +2 -1
  20. package/driver/skills/prelim-search/risk-framework-triage.md +3 -3
  21. package/driver/stages.mjs +19 -5
  22. package/driver/suite-census.json +112 -16
  23. package/driver/verify-knockout.mjs +58 -0
  24. package/driver/verify.mjs +83 -7
  25. package/mcp-server/CHANGELOG.md +11 -0
  26. package/mcp-server/lib/audit.mjs +96 -3
  27. package/mcp-server/lib/http-handler.mjs +76 -3
  28. package/mcp-server/lib/runs.mjs +42 -2
  29. package/mcp-server/package.json +1 -1
  30. package/mcp-server/packs/client/CONNECT.md +10 -6
  31. package/mcp-server/server.mjs +37 -3
  32. package/package.json +1 -1
  33. package/portal-ui/dist/assets/{index-D8ITW-aD.js → index-ChIQsMYp.js} +1375 -817
  34. package/portal-ui/dist/assets/{index-D5WAoLZI.css → index-DBIs21e4.css} +37 -0
  35. package/portal-ui/dist/index.html +2 -2
  36. package/portal-ui/package.json +1 -1
  37. package/providers/oauth-mcp-bridge/CHANGELOG.md +8 -0
  38. package/providers/oauth-mcp-bridge/package.json +1 -1
  39. package/scripts/ask-ai-render-check.mjs +359 -0
  40. package/scripts/citation-line-check.mjs +53 -2
  41. package/scripts/deprecate-below.mjs +146 -0
  42. package/scripts/test-run.mjs +24 -1
  43. package/shared/connect-clients.mjs +52 -16
  44. package/shared/driver-dir.mjs +1 -1
  45. package/shared/grants-edit.mjs +99 -2
@@ -11629,6 +11629,11 @@ function decodeStatus(status, body) {
11629
11629
  errors: errorsOf(body),
11630
11630
  detail: refused
11631
11631
  };
11632
+ if (refused) return {
11633
+ kind: "gate",
11634
+ message: "That could not be done. Nothing was changed.",
11635
+ detail: refused
11636
+ };
11632
11637
  const msg = errorsOf(body)[0] ?? "This action could not be completed.";
11633
11638
  return /version|conflict/i.test(msg) && !/confirmation|plan again|re-confirm/i.test(msg) ? {
11634
11639
  kind: "conflict",
@@ -11794,6 +11799,33 @@ function decodeAccess(b) {
11794
11799
  }];
11795
11800
  });
11796
11801
  }
11802
+ var decodeSteps = (raw) => asArray(raw).flatMap((x) => {
11803
+ const s = asRecord(x);
11804
+ const text = asString(s["text"]);
11805
+ if (!text) return [];
11806
+ const hint = asString(s["hint"]);
11807
+ const c = s["copy"] == null ? null : asRecord(s["copy"]);
11808
+ let copy;
11809
+ if (c) {
11810
+ const kind = asString(c["kind"]);
11811
+ if (kind === "block" && asString(c["text"])) copy = {
11812
+ kind,
11813
+ text: asString(c["text"])
11814
+ };
11815
+ else if (kind === "secret" && asString(c["label"]) && asString(c["template"]) && asString(c["slot"]) && asString(c["template"]).includes(asString(c["slot"]))) copy = {
11816
+ kind,
11817
+ label: asString(c["label"]),
11818
+ template: asString(c["template"]),
11819
+ slot: asString(c["slot"])
11820
+ };
11821
+ else return [];
11822
+ }
11823
+ return [{
11824
+ text,
11825
+ ...hint ? { hint } : {},
11826
+ ...copy ? { copy } : {}
11827
+ }];
11828
+ });
11797
11829
  function decodePerson(r) {
11798
11830
  return {
11799
11831
  email: asString(r["email"]) ?? "",
@@ -11802,7 +11834,10 @@ function decodePerson(r) {
11802
11834
  manage: asRecord(r["permissions"])["manage"] === true
11803
11835
  },
11804
11836
  access: decodeAccess(r),
11805
- dangling: asArray(r["dangling"]).filter((s) => typeof s === "string")
11837
+ dangling: asArray(r["dangling"]).filter((s) => typeof s === "string"),
11838
+ listed: r["listed"] !== false,
11839
+ covered: r["covered"] === true,
11840
+ keys: typeof r["keys"] === "number" && r["keys"] > 0 ? r["keys"] : 0
11806
11841
  };
11807
11842
  }
11808
11843
  var api = {
@@ -12051,6 +12086,7 @@ var api = {
12051
12086
  keyUrl: asString(b["keyUrl"]),
12052
12087
  email: asString(b["email"]),
12053
12088
  enabled: b["enabled"] === true,
12089
+ aiConnected: typeof b["aiConnected"] === "boolean" ? b["aiConnected"] : null,
12054
12090
  stdio: (() => {
12055
12091
  const r = b["stdio"];
12056
12092
  if (!r || typeof r !== "object") return null;
@@ -12085,33 +12121,12 @@ var api = {
12085
12121
  ...asString(r["sub"]) ? { sub: asString(r["sub"]) } : {},
12086
12122
  ...asString(r["verifiedOn"]) ? { verifiedOn: asString(r["verifiedOn"]) } : {},
12087
12123
  ...asString(r["by"]) ? { by: asString(r["by"]) } : {},
12088
- steps: asArray(r["steps"]).flatMap((x) => {
12089
- const s = asRecord(x);
12090
- const text = asString(s["text"]);
12091
- if (!text) return [];
12092
- const hint = asString(s["hint"]);
12093
- const c = s["copy"] == null ? null : asRecord(s["copy"]);
12094
- let copy;
12095
- if (c) {
12096
- const kind = asString(c["kind"]);
12097
- if (kind === "block" && asString(c["text"])) copy = {
12098
- kind,
12099
- text: asString(c["text"])
12100
- };
12101
- else if (kind === "secret" && asString(c["label"]) && asString(c["template"]) && asString(c["slot"]) && asString(c["template"]).includes(asString(c["slot"]))) copy = {
12102
- kind,
12103
- label: asString(c["label"]),
12104
- template: asString(c["template"]),
12105
- slot: asString(c["slot"])
12106
- };
12107
- else return [];
12108
- }
12109
- return [{
12110
- text,
12111
- ...hint ? { hint } : {},
12112
- ...copy ? { copy } : {}
12113
- }];
12114
- }),
12124
+ steps: decodeSteps(r["steps"]),
12125
+ ...r["door"] === "sign-in" || r["door"] === "key" ? { door: r["door"] } : Object.hasOwn(r, "door") ? { door: null } : {},
12126
+ ...!(r["door"] === "sign-in" || r["door"] === "key") && Object.hasOwn(r, "altSteps") ? (() => {
12127
+ const a = decodeSteps(r["altSteps"]);
12128
+ return a.length ? { altSteps: a } : {};
12129
+ })() : {},
12115
12130
  launch: (() => {
12116
12131
  const u = asString(asRecord(r["launch"])["url"]);
12117
12132
  return u && /^https:\/\//.test(u) ? { url: u } : null;
@@ -12218,7 +12233,8 @@ var api = {
12218
12233
  } : null;
12219
12234
  })(),
12220
12235
  canAdd: b["canAdd"] === true,
12221
- localSignIn: b["localSignIn"] === true
12236
+ localSignIn: b["localSignIn"] === true,
12237
+ keysRevocable: b["keysRevocable"] === true
12222
12238
  })),
12223
12239
  addPerson: (body) => call("/portal/admin/people", (b) => ({
12224
12240
  person: decodePerson(asRecord(b["person"])),
@@ -12227,6 +12243,32 @@ var api = {
12227
12243
  method: "POST",
12228
12244
  body: JSON.stringify(body)
12229
12245
  }),
12246
+ changePerson: (body) => call("/portal/admin/people/change", (b) => ({
12247
+ person: decodePerson(asRecord(b["person"])),
12248
+ switchesApplied: b["switchesApplied"] === true,
12249
+ added: typeof b["added"] === "number" ? b["added"] : 0,
12250
+ removed: typeof b["removed"] === "number" ? b["removed"] : 0
12251
+ }), {
12252
+ method: "POST",
12253
+ body: JSON.stringify(body)
12254
+ }),
12255
+ removePerson: (body) => call("/portal/admin/people/remove", (b) => {
12256
+ const k = asRecord(b["keys"]);
12257
+ return {
12258
+ removed: b["removed"] === "install" ? "install" : "organisations",
12259
+ organisations: asStrings(b["organisations"]),
12260
+ keys: {
12261
+ checked: k["checked"] === true,
12262
+ revoked: typeof k["revoked"] === "number" ? k["revoked"] : 0,
12263
+ lateArm: k["lateArm"] === true,
12264
+ failed: k["failed"] === true,
12265
+ note: asString(k["note"])
12266
+ }
12267
+ };
12268
+ }, {
12269
+ method: "POST",
12270
+ body: JSON.stringify(body)
12271
+ }),
12230
12272
  adminObserved: () => call("/portal/admin/observed", (b) => ({
12231
12273
  available: b["available"] === true,
12232
12274
  truncated: b["truncated"] === true,
@@ -12302,6 +12344,15 @@ var NAV = [
12302
12344
  hidden: true,
12303
12345
  scope: "account"
12304
12346
  },
12347
+ {
12348
+ id: "people.modify",
12349
+ label: "Modify access",
12350
+ path: "/portal/people/modify",
12351
+ icon: "users",
12352
+ needs: "manage",
12353
+ hidden: true,
12354
+ scope: "account"
12355
+ },
12305
12356
  {
12306
12357
  id: "new",
12307
12358
  label: "New clearance",
@@ -12454,6 +12505,7 @@ function decode(segment) {
12454
12505
  }
12455
12506
  var PEOPLE = NAV.find((e) => e.id === "people");
12456
12507
  var ADD_PERSON = NAV.find((e) => e.id === "people.add");
12508
+ var MODIFY_PERSON = NAV.find((e) => e.id === "people.modify");
12457
12509
  //#endregion
12458
12510
  //#region ../node_modules/react/cjs/react-jsx-runtime.production.js
12459
12511
  var require_react_jsx_runtime_production = __commonJSMin(((exports) => {
@@ -12848,6 +12900,26 @@ function seesEverything(who) {
12848
12900
  return who.allAccounts === true;
12849
12901
  }
12850
12902
  //#endregion
12903
+ //#region src/components/PageHeader.tsx
12904
+ function PageHeader({ title, lede, actions }) {
12905
+ return (0, import_jsx_runtime.jsxs)("header", {
12906
+ className: "page-header",
12907
+ children: [(0, import_jsx_runtime.jsxs)("div", {
12908
+ className: "page-header-text",
12909
+ children: [(0, import_jsx_runtime.jsx)("h1", {
12910
+ className: "page-title",
12911
+ children: title
12912
+ }), lede ? (0, import_jsx_runtime.jsx)("p", {
12913
+ className: "page-lede",
12914
+ children: lede
12915
+ }) : null]
12916
+ }), actions ? (0, import_jsx_runtime.jsx)("div", {
12917
+ className: "page-header-actions",
12918
+ children: actions
12919
+ }) : null]
12920
+ });
12921
+ }
12922
+ //#endregion
12851
12923
  //#region src/shell/CompanyPicker.tsx
12852
12924
  function CompanyCard({ row, onPick }) {
12853
12925
  return (0, import_jsx_runtime.jsxs)("button", {
@@ -12907,51 +12979,26 @@ function CompanyCard({ row, onPick }) {
12907
12979
  })]
12908
12980
  });
12909
12981
  }
12910
- function CompanyPicker({ eyebrow, heading, line, keys, orgOf, organisations, companyName, factsFor, onPick, onAdd }) {
12982
+ function CompanyPicker({ heading, line, keys, orgOf, organisations, companyName, factsFor, onPick, onAdd }) {
12911
12983
  const { headings, groups } = pickerGroups(keys, orgOf, organisations, companyName, factsFor);
12912
- return (0, import_jsx_runtime.jsxs)("div", {
12984
+ return (0, import_jsx_runtime.jsx)("div", {
12913
12985
  className: "screen",
12914
- children: [(0, import_jsx_runtime.jsx)("div", {
12915
- className: "eyebrow",
12916
- children: eyebrow
12917
- }), (0, import_jsx_runtime.jsxs)("div", {
12986
+ children: (0, import_jsx_runtime.jsxs)("div", {
12918
12987
  className: "measure",
12919
- children: [(0, import_jsx_runtime.jsxs)("div", {
12920
- style: {
12921
- display: "flex",
12922
- alignItems: "flex-end",
12923
- gap: 20,
12924
- margin: "4px 0 18px"
12925
- },
12926
- children: [(0, import_jsx_runtime.jsxs)("div", {
12927
- style: { flex: 1 },
12928
- children: [(0, import_jsx_runtime.jsx)("h1", {
12929
- style: {
12930
- fontSize: 27,
12931
- margin: "0 0 4px",
12932
- color: "var(--text-strong)"
12933
- },
12934
- children: heading
12935
- }), (0, import_jsx_runtime.jsx)("p", {
12936
- style: {
12937
- margin: 0,
12938
- color: "var(--text-muted)",
12939
- fontSize: 14.5
12940
- },
12941
- children: line
12942
- })]
12943
- }), onAdd ? (0, import_jsx_runtime.jsx)("button", {
12988
+ children: [(0, import_jsx_runtime.jsx)(PageHeader, {
12989
+ title: heading,
12990
+ lede: line,
12991
+ actions: onAdd ? (0, import_jsx_runtime.jsx)("button", {
12944
12992
  type: "button",
12945
12993
  className: "start-pill",
12946
12994
  onClick: onAdd,
12947
12995
  style: {
12948
- flex: "none",
12949
12996
  borderColor: "var(--accent-ink)",
12950
12997
  color: "var(--text-accent)",
12951
12998
  fontWeight: 700
12952
12999
  },
12953
13000
  children: "+ New company"
12954
- }) : null]
13001
+ }) : null
12955
13002
  }), groups.map((g, i) => (0, import_jsx_runtime.jsxs)("div", { children: [headings && g.org ? (0, import_jsx_runtime.jsx)("div", {
12956
13003
  className: "eyebrow",
12957
13004
  style: { margin: i === 0 ? "0 0 8px" : "16px 0 8px" },
@@ -12969,12 +13016,11 @@ function CompanyPicker({ eyebrow, heading, line, keys, orgOf, organisations, com
12969
13016
  onPick: () => onPick(r.key)
12970
13017
  }, r.key))
12971
13018
  })] }, g.org?.key ?? ""))]
12972
- })]
13019
+ })
12973
13020
  });
12974
13021
  }
12975
- function CompanyGate({ ctx, eyebrow, heading, line }) {
13022
+ function CompanyGate({ ctx, heading, line }) {
12976
13023
  return (0, import_jsx_runtime.jsx)(CompanyPicker, {
12977
- eyebrow,
12978
13024
  heading,
12979
13025
  line,
12980
13026
  keys: ctx.ownerKeys,
@@ -13679,11 +13725,34 @@ var RANK = {
13679
13725
  paused: 2,
13680
13726
  queued: 3
13681
13727
  };
13728
+ var STOPPED = new Set(["failed", "cancelled"]);
13729
+ var whenOf = (r) => {
13730
+ for (const raw of [r.issuedAt, r.date]) {
13731
+ if (!raw) continue;
13732
+ const t = new Date(raw).getTime();
13733
+ if (!Number.isNaN(t)) return t;
13734
+ }
13735
+ return null;
13736
+ };
13737
+ var withinWindow = (r, now, days) => {
13738
+ const at = whenOf(r);
13739
+ return at === null || now - at <= days * 24 * 60 * 60 * 1e3;
13740
+ };
13741
+ var failuresNewestFirst = (a, b) => {
13742
+ const at = whenOf(a), bt = whenOf(b);
13743
+ if (at === null && bt === null) return 0;
13744
+ if (at === null) return -1;
13745
+ if (bt === null) return 1;
13746
+ return newestFirst(a, b);
13747
+ };
13682
13748
  function inFlight(runs) {
13683
- return runs.filter((r) => r.state !== "delivered" && !r.acked).slice().sort((a, b) => (RANK[a.state] ?? 9) - (RANK[b.state] ?? 9));
13749
+ return runs.filter((r) => r.state !== "delivered" && !STOPPED.has(r.state)).slice().sort((a, b) => (RANK[a.state] ?? 9) - (RANK[b.state] ?? 9));
13750
+ }
13751
+ function recentFailures(runs, { now = Date.now(), days = 7 } = {}) {
13752
+ return runs.filter((r) => STOPPED.has(r.state) && !r.acked && withinWindow(r, now, days)).slice().sort(failuresNewestFirst);
13684
13753
  }
13685
- function acknowledged(runs) {
13686
- return runs.filter((r) => r.state !== "delivered" && r.acked).slice().sort((a, b) => (RANK[a.state] ?? 9) - (RANK[b.state] ?? 9));
13754
+ function acknowledged(runs, { now = Date.now(), days = 7 } = {}) {
13755
+ return runs.filter((r) => STOPPED.has(r.state) && r.acked === true && withinWindow(r, now, days)).slice().sort(failuresNewestFirst);
13687
13756
  }
13688
13757
  function readStamps(rows) {
13689
13758
  const identity = (r) => [
@@ -13842,11 +13911,14 @@ function Home({ ctx }) {
13842
13911
  const allRuns = result?.kind === "ok" ? result.value : [];
13843
13912
  const runs = (0, import_react.useMemo)(() => runsFor(allRuns, ctx.owner), [allRuns, ctx.owner]);
13844
13913
  const rows = (0, import_react.useMemo)(() => inFlight(runs), [runs]);
13914
+ const stopped = (0, import_react.useMemo)(() => recentFailures(runs), [runs]);
13915
+ const [showStopped, setShowStopped] = (0, import_react.useState)(false);
13845
13916
  const acked = (0, import_react.useMemo)(() => acknowledged(runs), [runs]);
13846
13917
  const [showAcked, setShowAcked] = (0, import_react.useState)(false);
13847
13918
  const cards = (0, import_react.useMemo)(() => active(rows), [rows]);
13848
13919
  const queue = (0, import_react.useMemo)(() => waiting(rows), [rows]);
13849
13920
  const stamps = (0, import_react.useMemo)(() => readStamps(rows), [rows]);
13921
+ const stoppedStamps = (0, import_react.useMemo)(() => readStamps(stopped), [stopped]);
13850
13922
  const ackedStamps = (0, import_react.useMemo)(() => readStamps(acked), [acked]);
13851
13923
  const done = (0, import_react.useMemo)(() => recentlyFinished(runs, void 0, 3), [runs]);
13852
13924
  const recent = (0, import_react.useMemo)(() => done.map(lastFinishedOf).filter((r) => r !== null), [done]);
@@ -13859,23 +13931,18 @@ function Home({ ctx }) {
13859
13931
  return (0, import_jsx_runtime.jsxs)("div", {
13860
13932
  className: "screen home2",
13861
13933
  children: [
13862
- (0, import_jsx_runtime.jsx)("div", {
13863
- className: "eyebrow",
13864
- children: "Home"
13865
- }),
13866
- (0, import_jsx_runtime.jsx)("h1", {
13867
- style: {
13868
- fontSize: 27,
13869
- margin: "4px 0 14px",
13870
- color: "var(--text-strong)"
13871
- },
13872
- children: "Now"
13934
+ (0, import_jsx_runtime.jsx)(PageHeader, {
13935
+ title: "Home",
13936
+ actions: (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [(0, import_jsx_runtime.jsxs)("button", {
13937
+ type: "button",
13938
+ className: "btn-ghost home2-all",
13939
+ onClick: () => ctx.go("/portal/clearances"),
13940
+ children: [(0, import_jsx_runtime.jsx)(Icon, { name: "layers" }), (0, import_jsx_runtime.jsx)("span", { children: "All Clearances" })]
13941
+ }), canRun(ctx.me) ? (0, import_jsx_runtime.jsx)(NewClearanceButton, { onNew: () => ctx.go("/portal/new") }) : null] })
13873
13942
  }),
13874
13943
  (0, import_jsx_runtime.jsx)(InFlightBand, {
13875
13944
  count: cards.length + queue.length,
13876
- note: cards.length + queue.length === 0 ? `Nothing running right now.${canRun(ctx.me) ? " Start one with New clearance." : ""}` : slotNote(null, ctx.me.concurrentRuns),
13877
- onNew: canRun(ctx.me) ? () => ctx.go("/portal/new") : null,
13878
- onAll: () => ctx.go("/portal/clearances")
13945
+ note: cards.length + queue.length === 0 ? `Nothing running right now.${canRun(ctx.me) ? " Start one with New clearance." : ""}` : slotNote(null, ctx.me.concurrentRuns)
13879
13946
  }),
13880
13947
  (0, import_jsx_runtime.jsx)("div", {
13881
13948
  style: { margin: "0 0 18px" },
@@ -13903,6 +13970,28 @@ function Home({ ctx }) {
13903
13970
  onChanged: reload,
13904
13971
  stamps
13905
13972
  }) : null,
13973
+ stopped.length ? (0, import_jsx_runtime.jsxs)("div", {
13974
+ className: "home2-stopped",
13975
+ children: [(0, import_jsx_runtime.jsxs)("button", {
13976
+ type: "button",
13977
+ className: "home2-stopped-toggle",
13978
+ "aria-expanded": showStopped,
13979
+ onClick: () => setShowStopped((v) => !v),
13980
+ children: [
13981
+ stopped.length,
13982
+ " stopped recently",
13983
+ showStopped ? "" : " — show"
13984
+ ]
13985
+ }), showStopped ? (0, import_jsx_runtime.jsx)("div", {
13986
+ className: "home2-cards",
13987
+ children: stopped.map((r) => (0, import_jsx_runtime.jsx)(Card, {
13988
+ run: r,
13989
+ ctx,
13990
+ onChanged: reload,
13991
+ stamp: stoppedStamps.get(r.runId) ?? null
13992
+ }, r.runId))
13993
+ }) : null]
13994
+ }) : null,
13906
13995
  acked.length ? (0, import_jsx_runtime.jsxs)("div", {
13907
13996
  className: "home2-acked",
13908
13997
  children: [(0, import_jsx_runtime.jsxs)("button", {
@@ -13942,7 +14031,7 @@ function Home({ ctx }) {
13942
14031
  total: finishedCount,
13943
14032
  ctx
13944
14033
  }) : null,
13945
- answer === "ok" && !cards.length && !queue.length && !recent.length ? (0, import_jsx_runtime.jsx)(FirstRun$1, { onNew: canRun(ctx.me) ? () => ctx.go("/portal/new") : null }) : null,
14034
+ answer === "ok" && !cards.length && !queue.length && !recent.length && !stopped.length && !acked.length ? (0, import_jsx_runtime.jsx)(FirstRun$1, { onNew: canRun(ctx.me) ? () => ctx.go("/portal/new") : null }) : null,
13946
14035
  usageRes?.kind === "ok" ? (0, import_jsx_runtime.jsx)("p", {
13947
14036
  className: "home2-limits mono",
13948
14037
  children: limitLine(usageRes.value.today, usageRes.value.dailyRuns)
@@ -13950,7 +14039,15 @@ function Home({ ctx }) {
13950
14039
  ]
13951
14040
  });
13952
14041
  }
13953
- function InFlightBand({ count, note, onNew, onAll }) {
14042
+ function NewClearanceButton({ onNew }) {
14043
+ return (0, import_jsx_runtime.jsxs)("button", {
14044
+ type: "button",
14045
+ className: "home2-new",
14046
+ onClick: onNew,
14047
+ children: [(0, import_jsx_runtime.jsx)(Icon, { name: "plus-circle" }), "New clearance"]
14048
+ });
14049
+ }
14050
+ function InFlightBand({ count, note }) {
13954
14051
  return (0, import_jsx_runtime.jsxs)("div", {
13955
14052
  className: "home2-band",
13956
14053
  children: [
@@ -13966,20 +14063,7 @@ function InFlightBand({ count, note, onNew, onAll }) {
13966
14063
  (0, import_jsx_runtime.jsx)("span", {
13967
14064
  className: "home2-band-note",
13968
14065
  children: note
13969
- }),
13970
- (0, import_jsx_runtime.jsxs)("button", {
13971
- type: "button",
13972
- className: "btn-ghost home2-all",
13973
- onClick: onAll,
13974
- style: { marginRight: 8 },
13975
- children: [(0, import_jsx_runtime.jsx)(Icon, { name: "layers" }), (0, import_jsx_runtime.jsx)("span", { children: "All Clearances" })]
13976
- }),
13977
- onNew ? (0, import_jsx_runtime.jsxs)("button", {
13978
- type: "button",
13979
- className: "home2-new",
13980
- onClick: onNew,
13981
- children: [(0, import_jsx_runtime.jsx)(Icon, { name: "plus-circle" }), "New clearance"]
13982
- }) : null
14066
+ })
13983
14067
  ]
13984
14068
  });
13985
14069
  }
@@ -14774,24 +14858,12 @@ function Clearances({ ctx }) {
14774
14858
  return (0, import_jsx_runtime.jsxs)("div", {
14775
14859
  className: "screen",
14776
14860
  children: [
14777
- (0, import_jsx_runtime.jsx)("div", {
14778
- className: "eyebrow",
14779
- children: "All Clearances"
14780
- }),
14781
- (0, import_jsx_runtime.jsx)("h1", {
14782
- style: {
14783
- fontSize: 27,
14784
- margin: "4px 0 6px",
14785
- color: "var(--text-strong)"
14786
- },
14787
- children: ownerFilter ? ctx.ownerName(ownerFilter) : "All Clearances"
14788
- }),
14789
- (0, import_jsx_runtime.jsx)("p", {
14790
- style: {
14791
- margin: 0,
14792
- color: "var(--text-muted)"
14793
- },
14794
- children: (0, import_jsx_runtime.jsx)(AllowanceLine, { account })
14861
+ (0, import_jsx_runtime.jsx)(PageHeader, {
14862
+ title: "All Clearances",
14863
+ lede: (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [ownerFilter ? (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [(0, import_jsx_runtime.jsx)("span", {
14864
+ "data-anon": "mark",
14865
+ children: ctx.ownerName(ownerFilter)
14866
+ }), " · "] }) : null, (0, import_jsx_runtime.jsx)(AllowanceLine, { account })] })
14795
14867
  }),
14796
14868
  (0, import_jsx_runtime.jsx)("div", {
14797
14869
  className: "controls",
@@ -15784,16 +15856,48 @@ function frameCommand(command, value) {
15784
15856
  }
15785
15857
  //#endregion
15786
15858
  //#region src/contract/askAi.ts
15787
- function askAiPrompt(runId, mark) {
15788
- const name = String(mark ?? "this mark").trim() || "this mark";
15789
- return runId ? `Brief me on trademark clearance run ${runId}.` : `Brief me on the ${name} trademark clearance.`;
15790
- }
15791
- function askAiOffer(runId, mark, access) {
15859
+ var MONTHS = [
15860
+ "",
15861
+ "January",
15862
+ "February",
15863
+ "March",
15864
+ "April",
15865
+ "May",
15866
+ "June",
15867
+ "July",
15868
+ "August",
15869
+ "September",
15870
+ "October",
15871
+ "November",
15872
+ "December"
15873
+ ];
15874
+ function readableDate(iso) {
15875
+ const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(String(iso ?? "").trim());
15876
+ if (!m) return null;
15877
+ const month = MONTHS[Number(m[2])];
15878
+ const day = Number(m[3]);
15879
+ if (!month || !(day >= 1 && day <= 31)) return null;
15880
+ return `${day} ${month}`;
15881
+ }
15882
+ function askAiPrompt(mark, date, kind = "clearance") {
15883
+ const name = String(mark ?? "").trim() || "this mark";
15884
+ const noun = kind === "knockout-batch" ? "knockout search" : "clearance";
15885
+ const when = readableDate(date);
15886
+ return when ? `Brief me on the ${name} ${noun} from ${when}.` : `Brief me on the ${name} ${noun}.`;
15887
+ }
15888
+ var ASSISTANTS = [{
15889
+ label: "Ask Claude",
15890
+ href: (q) => `https://claude.ai/new?q=${encodeURIComponent(q)}`
15891
+ }, {
15892
+ label: "Ask ChatGPT",
15893
+ href: (q) => `https://chatgpt.com/?q=${encodeURIComponent(q)}`
15894
+ }];
15895
+ var AI_SETUP_PATH = "/portal/ai";
15896
+ function askAiOffer(run, access) {
15792
15897
  return {
15793
- question: askAiPrompt(runId, mark),
15794
- address: access?.enabled ? access.url : null,
15795
- instructionsPath: "/portal/ai",
15796
- stdio: access?.stdio ?? null
15898
+ drawn: !!access && (!!(access.enabled && access.url) || !!access.stdio),
15899
+ question: askAiPrompt(run.markName, run.date, run.kind),
15900
+ connected: access?.aiConnected === true
15797
15901
  };
15798
15902
  }
15799
15903
  //#endregion
@@ -15851,12 +15955,12 @@ function useReportFrame() {
15851
15955
  clearFailed: () => setFailed(null)
15852
15956
  };
15853
15957
  }
15854
- function AskAiMenu({ runId, mark, ctx }) {
15958
+ function AskAiMenu({ run, ctx }) {
15855
15959
  const [open, setOpen] = (0, import_react.useState)(false);
15856
- const [copied, setCopied] = (0, import_react.useState)(null);
15960
+ const [askAnyway, setAskAnyway] = (0, import_react.useState)(false);
15857
15961
  const box = (0, import_react.useRef)(null);
15858
15962
  const { result } = useLoad(() => api.mcpAccess(), []);
15859
- const offer = askAiOffer(runId, mark, result?.kind === "ok" ? result.value : null);
15963
+ const offer = askAiOffer(run, result?.kind === "ok" ? result.value : null);
15860
15964
  (0, import_react.useEffect)(() => {
15861
15965
  if (!open) return;
15862
15966
  function onDown(e) {
@@ -15872,115 +15976,151 @@ function AskAiMenu({ runId, mark, ctx }) {
15872
15976
  document.removeEventListener("keydown", onKey);
15873
15977
  };
15874
15978
  }, [open]);
15875
- const copy = (what, value) => {
15876
- (async () => {
15877
- try {
15878
- await navigator.clipboard?.writeText(value);
15879
- setCopied(what);
15880
- } catch {
15881
- setCopied(`${what}-failed`);
15882
- }
15883
- })();
15979
+ if (!offer.drawn) return null;
15980
+ const showMenu = offer.connected || askAnyway;
15981
+ const ask = (href) => {
15982
+ setOpen(false);
15983
+ window.open(href, "_blank", "noopener,noreferrer");
15884
15984
  };
15885
- const line = (label, value, key) => (0, import_jsx_runtime.jsxs)("div", {
15886
- style: { padding: "7px 10px" },
15887
- children: [(0, import_jsx_runtime.jsx)("div", {
15888
- style: {
15889
- fontSize: 11,
15890
- color: "var(--text-faint)",
15891
- marginBottom: 3
15892
- },
15893
- children: label
15894
- }), (0, import_jsx_runtime.jsxs)("div", {
15895
- style: {
15896
- display: "flex",
15897
- alignItems: "center",
15898
- gap: 8
15899
- },
15900
- children: [(0, import_jsx_runtime.jsx)("span", {
15901
- className: "mono",
15985
+ return (0, import_jsx_runtime.jsxs)("div", {
15986
+ ref: box,
15987
+ style: { position: "relative" },
15988
+ "data-ask-ai": showMenu ? "menu" : "connect",
15989
+ children: [
15990
+ (0, import_jsx_runtime.jsxs)("button", {
15991
+ type: "button",
15992
+ className: "nav-item",
15993
+ "aria-haspopup": showMenu ? "menu" : "dialog",
15994
+ "aria-expanded": open,
15902
15995
  style: {
15903
- fontSize: 12,
15904
- color: "var(--text-strong)",
15905
- wordBreak: "break-all",
15906
- flex: 1
15996
+ width: "auto",
15997
+ margin: 0,
15998
+ padding: "6px 11px",
15999
+ border: "1px solid var(--border-hairline)"
15907
16000
  },
15908
- children: value
15909
- }), (0, import_jsx_runtime.jsx)("button", {
15910
- type: "button",
15911
- className: "link-btn",
16001
+ onClick: () => setOpen((v) => !v),
16002
+ children: [(0, import_jsx_runtime.jsx)(Icon, {
16003
+ name: "sparkles",
16004
+ size: 14
16005
+ }), (0, import_jsx_runtime.jsx)("span", { children: "Ask AI" })]
16006
+ }),
16007
+ open && showMenu ? (0, import_jsx_runtime.jsxs)("div", {
16008
+ className: "float",
16009
+ role: "menu",
15912
16010
  style: {
15913
- fontSize: 12,
15914
- flex: "none"
16011
+ position: "absolute",
16012
+ right: 0,
16013
+ top: 38,
16014
+ width: 236,
16015
+ padding: 6,
16016
+ zIndex: 50
15915
16017
  },
15916
- onClick: () => copy(key, value),
15917
- children: copied === key ? "Copied" : copied === `${key}-failed` ? "Copy failed" : "Copy"
15918
- })]
15919
- })]
15920
- });
15921
- return (0, import_jsx_runtime.jsxs)("div", {
15922
- ref: box,
15923
- style: { position: "relative" },
15924
- children: [(0, import_jsx_runtime.jsxs)("button", {
15925
- type: "button",
15926
- className: "nav-item",
15927
- "aria-haspopup": "menu",
15928
- "aria-expanded": open,
15929
- style: {
15930
- width: "auto",
15931
- margin: 0,
15932
- padding: "6px 11px",
15933
- border: "1px solid var(--border-hairline)"
15934
- },
15935
- onClick: () => setOpen((v) => !v),
15936
- children: [(0, import_jsx_runtime.jsx)(Icon, {
15937
- name: "sparkles",
15938
- size: 14
15939
- }), (0, import_jsx_runtime.jsx)("span", { children: "Ask AI" })]
15940
- }), open ? (0, import_jsx_runtime.jsxs)("div", {
15941
- className: "float",
15942
- role: "menu",
15943
- style: {
15944
- position: "absolute",
15945
- right: 0,
15946
- top: 38,
15947
- width: 320,
15948
- padding: 6,
15949
- zIndex: 50
15950
- },
15951
- children: [
15952
- line("Say this to your assistant", offer.question, "question"),
15953
- offer.address ? line("Connector address", offer.address, "address") : (0, import_jsx_runtime.jsxs)("p", {
15954
- style: {
15955
- margin: 0,
15956
- padding: "7px 10px",
15957
- fontSize: 12,
15958
- color: "var(--text-muted)",
15959
- lineHeight: 1.5
15960
- },
15961
- children: [offer.stdio ? "This deployment has no published address, but your assistant can connect to it directly. " : "No connector is set up on this deployment yet, so there is no address to paste. ", "The question above is what to ask once it is connected."]
15962
- }),
15963
- (0, import_jsx_runtime.jsx)("div", { style: {
15964
- height: 1,
15965
- background: "var(--border-hairline)",
15966
- margin: "5px 8px"
15967
- } }),
15968
- (0, import_jsx_runtime.jsx)("button", {
15969
- type: "button",
15970
- role: "menuitem",
15971
- className: "nav-item",
15972
- style: {
15973
- fontSize: 13,
15974
- padding: "7px 10px"
15975
- },
15976
- onClick: () => {
15977
- setOpen(false);
15978
- ctx.go(offer.instructionsPath);
15979
- },
15980
- children: "How to connect your assistant"
15981
- })
15982
- ]
15983
- }) : null]
16018
+ children: [
16019
+ ASSISTANTS.map((a) => (0, import_jsx_runtime.jsxs)("button", {
16020
+ type: "button",
16021
+ role: "menuitem",
16022
+ className: "nav-item",
16023
+ style: {
16024
+ fontSize: 13,
16025
+ padding: "7px 10px",
16026
+ margin: 0,
16027
+ justifyContent: "space-between"
16028
+ },
16029
+ onClick: () => ask(a.href(offer.question)),
16030
+ children: [(0, import_jsx_runtime.jsx)("span", { children: a.label }), (0, import_jsx_runtime.jsx)(Icon, {
16031
+ name: "arrow-right",
16032
+ size: 14
16033
+ })]
16034
+ }, a.label)),
16035
+ (0, import_jsx_runtime.jsx)("div", { style: {
16036
+ height: 1,
16037
+ background: "var(--border-hairline)",
16038
+ margin: "5px 8px"
16039
+ } }),
16040
+ (0, import_jsx_runtime.jsx)("button", {
16041
+ type: "button",
16042
+ role: "menuitem",
16043
+ className: "nav-item",
16044
+ style: {
16045
+ fontSize: 13,
16046
+ padding: "7px 10px",
16047
+ margin: 0,
16048
+ justifyContent: "space-between",
16049
+ color: "var(--text-muted)"
16050
+ },
16051
+ onClick: () => {
16052
+ setOpen(false);
16053
+ ctx.go(AI_SETUP_PATH);
16054
+ },
16055
+ children: (0, import_jsx_runtime.jsx)("span", { children: "Connect another AI" })
16056
+ })
16057
+ ]
16058
+ }) : null,
16059
+ open && !showMenu ? (0, import_jsx_runtime.jsxs)("div", {
16060
+ className: "float",
16061
+ role: "dialog",
16062
+ "aria-label": "Connect your AI first",
16063
+ style: {
16064
+ position: "absolute",
16065
+ right: 0,
16066
+ top: 38,
16067
+ width: 276,
16068
+ padding: 12,
16069
+ zIndex: 50,
16070
+ display: "grid",
16071
+ gap: 9
16072
+ },
16073
+ children: [
16074
+ (0, import_jsx_runtime.jsx)("div", {
16075
+ style: {
16076
+ fontSize: 13.5,
16077
+ fontWeight: 700,
16078
+ color: "var(--text-strong)"
16079
+ },
16080
+ children: "Connect your AI first"
16081
+ }),
16082
+ (0, import_jsx_runtime.jsx)("p", {
16083
+ style: {
16084
+ margin: 0,
16085
+ fontSize: 12.5,
16086
+ color: "var(--text-muted)",
16087
+ lineHeight: 1.5
16088
+ },
16089
+ children: "Connect Claude or ChatGPT once. After that, this button opens it with a question about this report typed in."
16090
+ }),
16091
+ (0, import_jsx_runtime.jsxs)("button", {
16092
+ type: "button",
16093
+ className: "btn-primary",
16094
+ style: {
16095
+ fontSize: 13,
16096
+ padding: "6px 11px",
16097
+ justifySelf: "start",
16098
+ display: "inline-flex",
16099
+ alignItems: "center",
16100
+ gap: 6
16101
+ },
16102
+ onClick: () => {
16103
+ setOpen(false);
16104
+ ctx.go(AI_SETUP_PATH);
16105
+ },
16106
+ children: ["Set it up ", (0, import_jsx_runtime.jsx)(Icon, {
16107
+ name: "arrow-right",
16108
+ size: 14
16109
+ })]
16110
+ }),
16111
+ (0, import_jsx_runtime.jsx)("button", {
16112
+ type: "button",
16113
+ className: "link-btn",
16114
+ style: {
16115
+ fontSize: 12.5,
16116
+ justifySelf: "start"
16117
+ },
16118
+ onClick: () => setAskAnyway(true),
16119
+ children: "Already connected? Ask anyway"
16120
+ })
16121
+ ]
16122
+ }) : null
16123
+ ]
15984
16124
  });
15985
16125
  }
15986
16126
  function ExportMenu({ send, runId, offered }) {
@@ -16258,8 +16398,7 @@ function Result({ ctx, runId, markSlug }) {
16258
16398
  }),
16259
16399
  (0, import_jsx_runtime.jsx)("span", { style: { flex: 1 } }),
16260
16400
  (0, import_jsx_runtime.jsx)(AskAiMenu, {
16261
- runId: run.runId,
16262
- mark: run.markName,
16401
+ run,
16263
16402
  ctx
16264
16403
  }),
16265
16404
  doc ? (0, import_jsx_runtime.jsx)(ExportMenu, {
@@ -17874,7 +18013,6 @@ function NewClearance({ ctx }) {
17874
18013
  });
17875
18014
  if (needsOwner || searches?.kind === "pickAccount") return (0, import_jsx_runtime.jsx)(CompanyGate, {
17876
18015
  ctx,
17877
- eyebrow: "New clearance",
17878
18016
  heading: "New clearance",
17879
18017
  line: "Pick a company to run this clearance on."
17880
18018
  });
@@ -17902,19 +18040,8 @@ function NewClearance({ ctx }) {
17902
18040
  return (0, import_jsx_runtime.jsxs)("div", {
17903
18041
  className: "screen",
17904
18042
  children: [
17905
- (0, import_jsx_runtime.jsx)("div", {
17906
- className: "eyebrow",
17907
- children: editingSlug ? "Custom search" : "New clearance"
17908
- }),
17909
18043
  (0, import_jsx_runtime.jsx)(CreatedStrip, {}),
17910
- (0, import_jsx_runtime.jsx)("h1", {
17911
- style: {
17912
- fontSize: 27,
17913
- margin: "4px 0 14px",
17914
- color: "var(--text-strong)"
17915
- },
17916
- children: editingSlug ? "Edit a custom search" : "New clearance"
17917
- }),
18044
+ (0, import_jsx_runtime.jsx)(PageHeader, { title: editingSlug ? "Edit a custom search" : "New clearance" }),
17918
18045
  editingSlug ? editingRes && editingRes.kind !== "ok" ? (0, import_jsx_runtime.jsxs)("div", {
17919
18046
  className: "notice",
17920
18047
  style: { borderColor: "var(--tone-high)" },
@@ -18565,7 +18692,7 @@ function NewClearance({ ctx }) {
18565
18692
  nativeControl === "toggle" ? (0, import_jsx_runtime.jsx)("div", {
18566
18693
  className: "sunken-block",
18567
18694
  style: { marginTop: 12 },
18568
- children: (0, import_jsx_runtime.jsx)(Lever$1, {
18695
+ children: (0, import_jsx_runtime.jsx)(Lever$2, {
18569
18696
  label: "Native-language investigation",
18570
18697
  hint: "Native marketplaces and native registers in the language of the countries you named. A clearance already searches the mark transliterated into the scripts its territories register marks in — this is the deeper read.",
18571
18698
  on: draft.pick.nativeLanguage,
@@ -19277,7 +19404,7 @@ function SetupCommand({ route }) {
19277
19404
  "from a copy of the source."
19278
19405
  ] });
19279
19406
  }
19280
- function Lever$1({ label, hint, on, onToggle, coming }) {
19407
+ function Lever$2({ label, hint, on, onToggle, coming }) {
19281
19408
  return (0, import_jsx_runtime.jsxs)("button", {
19282
19409
  type: "button",
19283
19410
  onClick: coming ? void 0 : onToggle,
@@ -19834,51 +19961,37 @@ function OptionsUnavailable({ kind, onRetry }) {
19834
19961
  function Submitted({ go, onAnother }) {
19835
19962
  return (0, import_jsx_runtime.jsxs)("div", {
19836
19963
  className: "screen",
19837
- children: [
19838
- (0, import_jsx_runtime.jsx)("div", {
19839
- className: "eyebrow",
19840
- children: "Queued"
19841
- }),
19842
- (0, import_jsx_runtime.jsx)("h1", {
19843
- style: {
19844
- fontSize: 27,
19845
- margin: "4px 0 14px",
19846
- color: "var(--text-strong)"
19847
- },
19848
- children: "Clearance started"
19849
- }),
19850
- (0, import_jsx_runtime.jsxs)("div", {
19851
- className: "notice prose",
19852
- children: [
19853
- (0, import_jsx_runtime.jsx)("b", { children: "It is in the queue" }),
19854
- (0, import_jsx_runtime.jsx)("p", {
19855
- style: {
19856
- margin: "6px 0 0",
19857
- color: "var(--text-muted)"
19858
- },
19859
- children: "It will appear in Clearances, and the entry there tracks it the whole way — including if it stops early."
19860
- }),
19861
- (0, import_jsx_runtime.jsxs)("div", {
19862
- style: {
19863
- marginTop: 14,
19864
- display: "flex",
19865
- gap: 10
19866
- },
19867
- children: [(0, import_jsx_runtime.jsx)("button", {
19868
- type: "button",
19869
- className: "btn-primary",
19870
- onClick: () => go("/portal/clearances"),
19871
- children: "View in Clearances"
19872
- }), (0, import_jsx_runtime.jsx)("button", {
19873
- type: "button",
19874
- className: "btn-ghost",
19875
- onClick: onAnother,
19876
- children: "Start another"
19877
- })]
19878
- })
19879
- ]
19880
- })
19881
- ]
19964
+ children: [(0, import_jsx_runtime.jsx)(PageHeader, { title: "Clearance started" }), (0, import_jsx_runtime.jsxs)("div", {
19965
+ className: "notice prose",
19966
+ children: [
19967
+ (0, import_jsx_runtime.jsx)("b", { children: "It is in the queue" }),
19968
+ (0, import_jsx_runtime.jsx)("p", {
19969
+ style: {
19970
+ margin: "6px 0 0",
19971
+ color: "var(--text-muted)"
19972
+ },
19973
+ children: "It will appear in Clearances, and the entry there tracks it the whole way — including if it stops early."
19974
+ }),
19975
+ (0, import_jsx_runtime.jsxs)("div", {
19976
+ style: {
19977
+ marginTop: 14,
19978
+ display: "flex",
19979
+ gap: 10
19980
+ },
19981
+ children: [(0, import_jsx_runtime.jsx)("button", {
19982
+ type: "button",
19983
+ className: "btn-primary",
19984
+ onClick: () => go("/portal/clearances"),
19985
+ children: "View in Clearances"
19986
+ }), (0, import_jsx_runtime.jsx)("button", {
19987
+ type: "button",
19988
+ className: "btn-ghost",
19989
+ onClick: onAnother,
19990
+ children: "Start another"
19991
+ })]
19992
+ })
19993
+ ]
19994
+ })]
19882
19995
  });
19883
19996
  }
19884
19997
  function CreatedStrip() {
@@ -20515,29 +20628,14 @@ function NewCompany({ ctx }) {
20515
20628
  const rejected = result && !isOk(result) && result.kind === "reject" ? result.errors : null;
20516
20629
  const takenBy = result && !isOk(result) && result.kind === "reject" && result.detail?.code === "key_exists" ? result.detail.key : null;
20517
20630
  const otherFailure = result && !isOk(result) && result.kind !== "reject" ? result : null;
20518
- return (0, import_jsx_runtime.jsxs)("div", {
20631
+ return (0, import_jsx_runtime.jsx)("div", {
20519
20632
  className: "screen",
20520
- children: [(0, import_jsx_runtime.jsx)("div", {
20521
- className: "eyebrow",
20522
- children: "Company"
20523
- }), (0, import_jsx_runtime.jsxs)("div", {
20633
+ children: (0, import_jsx_runtime.jsxs)("div", {
20524
20634
  className: "measure",
20525
20635
  children: [
20526
- (0, import_jsx_runtime.jsx)("h1", {
20527
- style: {
20528
- fontSize: 27,
20529
- margin: "0 0 4px",
20530
- color: "var(--text-strong)"
20531
- },
20532
- children: "New company"
20533
- }),
20534
- (0, import_jsx_runtime.jsx)("p", {
20535
- style: {
20536
- margin: "0 0 18px",
20537
- color: "var(--text-muted)",
20538
- fontSize: 14.5
20539
- },
20540
- children: "The business whose names you are checking. Only the name is needed; everything else has a default you can change later."
20636
+ (0, import_jsx_runtime.jsx)(PageHeader, {
20637
+ title: "New company",
20638
+ lede: "The business whose names you are checking. Only the name is needed; everything else has a default you can change later."
20541
20639
  }),
20542
20640
  orgsHeld.length > 1 ? (0, import_jsx_runtime.jsxs)("div", {
20543
20641
  style: { marginBottom: 8 },
@@ -20684,7 +20782,7 @@ function NewCompany({ ctx }) {
20684
20782
  }) : null]
20685
20783
  })
20686
20784
  ]
20687
- })]
20785
+ })
20688
20786
  });
20689
20787
  }
20690
20788
  function KeyLine({ value, onChange }) {
@@ -21140,23 +21238,12 @@ function Empty({ onNew }) {
21140
21238
  function PickCompany({ ctx }) {
21141
21239
  return (0, import_jsx_runtime.jsx)(CompanyGate, {
21142
21240
  ctx,
21143
- eyebrow: "Company",
21144
21241
  heading: "Custom searches",
21145
21242
  line: "Pick a company to see its custom searches."
21146
21243
  });
21147
21244
  }
21148
21245
  function Heading() {
21149
- return (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [(0, import_jsx_runtime.jsx)("div", {
21150
- className: "eyebrow",
21151
- children: "Custom searches"
21152
- }), (0, import_jsx_runtime.jsx)("h1", {
21153
- style: {
21154
- fontSize: 27,
21155
- margin: "4px 0 6px",
21156
- color: "var(--text-strong)"
21157
- },
21158
- children: "Custom searches"
21159
- })] });
21246
+ return (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: (0, import_jsx_runtime.jsx)(PageHeader, { title: "Custom searches" }) });
21160
21247
  }
21161
21248
  //#endregion
21162
21249
  //#region src/screens/Preferences.tsx
@@ -21172,222 +21259,203 @@ function Preferences({ ctx }) {
21172
21259
  };
21173
21260
  return (0, import_jsx_runtime.jsxs)("div", {
21174
21261
  className: "screen",
21175
- children: [
21176
- (0, import_jsx_runtime.jsx)("div", {
21177
- className: "eyebrow",
21178
- children: "Settings"
21179
- }),
21180
- (0, import_jsx_runtime.jsx)("h1", {
21181
- style: {
21182
- fontSize: 27,
21183
- margin: "4px 0 6px",
21184
- color: "var(--text-strong)"
21185
- },
21186
- children: "Your preferences"
21187
- }),
21188
- (0, import_jsx_runtime.jsx)("p", {
21189
- className: "prose",
21190
- style: {
21191
- margin: 0,
21192
- color: "var(--text-muted)"
21193
- },
21194
- children: "Who you are signed in as, and how the portal looks on this computer."
21195
- }),
21196
- (0, import_jsx_runtime.jsxs)("div", {
21197
- className: "measure",
21198
- style: { "--screen-measure": "720px" },
21199
- children: [
21200
- (0, import_jsx_runtime.jsxs)(Section, {
21201
- title: "Your sign-in",
21202
- hint: `Held by ${operatorName(ctx.me.brand)}. Nothing here can be changed from this page.`,
21203
- children: [
21204
- (0, import_jsx_runtime.jsxs)("dl", {
21205
- style: {
21206
- margin: 0,
21207
- display: "grid",
21208
- gridTemplateColumns: "auto 1fr",
21209
- gap: "8px 18px",
21210
- fontSize: 14
21211
- },
21212
- children: [
21213
- (0, import_jsx_runtime.jsx)("dt", {
21214
- style: { color: "var(--text-muted)" },
21215
- children: "Address"
21216
- }),
21217
- (0, import_jsx_runtime.jsx)("dd", {
21218
- style: {
21219
- margin: 0,
21220
- color: "var(--text-strong)",
21221
- wordBreak: "break-all"
21222
- },
21262
+ children: [(0, import_jsx_runtime.jsx)(PageHeader, {
21263
+ title: "Your preferences",
21264
+ lede: "Who you are signed in as, and how the portal looks on this computer."
21265
+ }), (0, import_jsx_runtime.jsxs)("div", {
21266
+ className: "measure",
21267
+ style: { "--screen-measure": "720px" },
21268
+ children: [
21269
+ (0, import_jsx_runtime.jsxs)(Section, {
21270
+ title: "Your sign-in",
21271
+ hint: `Held by ${operatorName(ctx.me.brand)}. Nothing here can be changed from this page.`,
21272
+ children: [
21273
+ (0, import_jsx_runtime.jsxs)("dl", {
21274
+ style: {
21275
+ margin: 0,
21276
+ display: "grid",
21277
+ gridTemplateColumns: "auto 1fr",
21278
+ gap: "8px 18px",
21279
+ fontSize: 14
21280
+ },
21281
+ children: [
21282
+ (0, import_jsx_runtime.jsx)("dt", {
21283
+ style: { color: "var(--text-muted)" },
21284
+ children: "Address"
21285
+ }),
21286
+ (0, import_jsx_runtime.jsx)("dd", {
21287
+ style: {
21288
+ margin: 0,
21289
+ color: "var(--text-strong)",
21290
+ wordBreak: "break-all"
21291
+ },
21292
+ "data-anon": "mark",
21293
+ children: ctx.me.email || "—"
21294
+ }),
21295
+ (0, import_jsx_runtime.jsx)("dt", {
21296
+ style: { color: "var(--text-muted)" },
21297
+ children: "Permissions"
21298
+ }),
21299
+ (0, import_jsx_runtime.jsx)("dd", {
21300
+ style: {
21301
+ margin: 0,
21302
+ color: "var(--text-strong)"
21303
+ },
21304
+ children: permissionsPhrase(ctx.me.permissions)
21305
+ }),
21306
+ (0, import_jsx_runtime.jsx)("dt", {
21307
+ style: { color: "var(--text-muted)" },
21308
+ children: "Access to"
21309
+ }),
21310
+ (0, import_jsx_runtime.jsx)("dd", {
21311
+ style: {
21312
+ margin: 0,
21313
+ color: "var(--text-strong)"
21314
+ },
21315
+ children: ctx.me.access.length ? (0, import_jsx_runtime.jsx)("span", {
21223
21316
  "data-anon": "mark",
21224
- children: ctx.me.email || "—"
21225
- }),
21226
- (0, import_jsx_runtime.jsx)("dt", {
21227
- style: { color: "var(--text-muted)" },
21228
- children: "Permissions"
21229
- }),
21230
- (0, import_jsx_runtime.jsx)("dd", {
21231
- style: {
21232
- margin: 0,
21233
- color: "var(--text-strong)"
21234
- },
21235
- children: permissionsPhrase(ctx.me.permissions)
21236
- }),
21237
- (0, import_jsx_runtime.jsx)("dt", {
21317
+ children: accessChips(ctx.me.access).map((c) => c.label).join(", ")
21318
+ }) : (0, import_jsx_runtime.jsx)("span", {
21238
21319
  style: { color: "var(--text-muted)" },
21239
- children: "Access to"
21240
- }),
21241
- (0, import_jsx_runtime.jsx)("dd", {
21242
- style: {
21243
- margin: 0,
21244
- color: "var(--text-strong)"
21245
- },
21246
- children: ctx.me.access.length ? (0, import_jsx_runtime.jsx)("span", {
21247
- "data-anon": "mark",
21248
- children: accessChips(ctx.me.access).map((c) => c.label).join(", ")
21249
- }) : (0, import_jsx_runtime.jsx)("span", {
21250
- style: { color: "var(--text-muted)" },
21251
- children: "None recorded against this address."
21252
- })
21320
+ children: "None recorded against this address."
21253
21321
  })
21254
- ]
21255
- }),
21256
- (0, import_jsx_runtime.jsxs)("p", {
21257
- style: {
21258
- margin: "14px 0 0",
21259
- fontSize: 13,
21260
- color: "var(--text-muted)"
21261
- },
21262
- children: [
21263
- "To change the address, the role or the companies on it, ask ",
21264
- operatorName(ctx.me.brand),
21265
- " — enrolment is done for you, not from this page."
21266
- ]
21267
- }),
21268
- (0, import_jsx_runtime.jsx)("a", {
21269
- className: "pill",
21270
- href: "/portal/sign-out",
21271
- style: {
21272
- display: "inline-block",
21273
- marginTop: 12,
21274
- textDecoration: "none"
21275
- },
21276
- children: "Log out"
21277
- })
21278
- ]
21279
- }),
21280
- (0, import_jsx_runtime.jsxs)(Section, {
21281
- title: "Appearance",
21282
- hint: "Applies straight away, and is remembered in this browser.",
21283
- children: [(0, import_jsx_runtime.jsxs)("div", {
21284
- style: {
21285
- display: "flex",
21286
- gap: 10,
21287
- flexWrap: "wrap"
21288
- },
21289
- children: [(0, import_jsx_runtime.jsx)(ThemeOption, {
21290
- label: "Light",
21291
- value: "light",
21292
- current: theme,
21293
- onPick: applyTheme
21294
- }), (0, import_jsx_runtime.jsx)(ThemeOption, {
21295
- label: "Dark",
21296
- value: "dark",
21297
- current: theme,
21298
- onPick: applyTheme
21299
- })]
21300
- }), (0, import_jsx_runtime.jsx)("p", {
21322
+ })
21323
+ ]
21324
+ }),
21325
+ (0, import_jsx_runtime.jsxs)("p", {
21301
21326
  style: {
21302
- margin: "12px 0 0",
21327
+ margin: "14px 0 0",
21303
21328
  fontSize: 13,
21304
21329
  color: "var(--text-muted)"
21305
21330
  },
21306
- children: "The same choice is on the top bar, under the circle icon — this page and that button set one and the same setting."
21331
+ children: [
21332
+ "To change the address, the role or the companies on it, ask ",
21333
+ operatorName(ctx.me.brand),
21334
+ " — enrolment is done for you, not from this page."
21335
+ ]
21336
+ }),
21337
+ (0, import_jsx_runtime.jsx)("a", {
21338
+ className: "pill",
21339
+ href: "/portal/sign-out",
21340
+ style: {
21341
+ display: "inline-block",
21342
+ marginTop: 12,
21343
+ textDecoration: "none"
21344
+ },
21345
+ children: "Log out"
21346
+ })
21347
+ ]
21348
+ }),
21349
+ (0, import_jsx_runtime.jsxs)(Section, {
21350
+ title: "Appearance",
21351
+ hint: "Applies straight away, and is remembered in this browser.",
21352
+ children: [(0, import_jsx_runtime.jsxs)("div", {
21353
+ style: {
21354
+ display: "flex",
21355
+ gap: 10,
21356
+ flexWrap: "wrap"
21357
+ },
21358
+ children: [(0, import_jsx_runtime.jsx)(ThemeOption, {
21359
+ label: "Light",
21360
+ value: "light",
21361
+ current: theme,
21362
+ onPick: applyTheme
21363
+ }), (0, import_jsx_runtime.jsx)(ThemeOption, {
21364
+ label: "Dark",
21365
+ value: "dark",
21366
+ current: theme,
21367
+ onPick: applyTheme
21307
21368
  })]
21308
- }),
21309
- (0, import_jsx_runtime.jsxs)(Section, {
21310
- title: "Blurring names while you share a screen",
21311
- hint: "There is one control for this, and it is on the top bar.",
21312
- children: [
21313
- (0, import_jsx_runtime.jsxs)("div", {
21369
+ }), (0, import_jsx_runtime.jsx)("p", {
21370
+ style: {
21371
+ margin: "12px 0 0",
21372
+ fontSize: 13,
21373
+ color: "var(--text-muted)"
21374
+ },
21375
+ children: "The same choice is on the top bar, under the circle icon — this page and that button set one and the same setting."
21376
+ })]
21377
+ }),
21378
+ (0, import_jsx_runtime.jsxs)(Section, {
21379
+ title: "Blurring names while you share a screen",
21380
+ hint: "There is one control for this, and it is on the top bar.",
21381
+ children: [
21382
+ (0, import_jsx_runtime.jsxs)("div", {
21383
+ style: {
21384
+ display: "flex",
21385
+ alignItems: "center",
21386
+ gap: 12
21387
+ },
21388
+ children: [(0, import_jsx_runtime.jsxs)("span", {
21389
+ className: "pill",
21314
21390
  style: {
21315
- display: "flex",
21391
+ display: "inline-flex",
21316
21392
  alignItems: "center",
21317
- gap: 12
21318
- },
21319
- children: [(0, import_jsx_runtime.jsxs)("span", {
21320
- className: "pill",
21321
- style: {
21322
- display: "inline-flex",
21323
- alignItems: "center",
21324
- gap: 7,
21325
- padding: "4px 11px"
21326
- },
21327
- children: [(0, import_jsx_runtime.jsx)(Icon, {
21328
- name: "eye",
21329
- size: 15
21330
- }), "Top bar"]
21331
- }), (0, import_jsx_runtime.jsx)("span", {
21332
- style: {
21333
- fontSize: 13.5,
21334
- color: "var(--text-muted)"
21335
- },
21336
- children: "The eye button, to the left of your initials."
21337
- })]
21338
- }),
21339
- (0, import_jsx_runtime.jsx)("p", {
21340
- style: {
21341
- margin: "12px 0 0",
21342
- color: "var(--text-muted)",
21343
- fontSize: 13.5
21393
+ gap: 7,
21394
+ padding: "4px 11px"
21344
21395
  },
21345
- children: "Pressing it blurs every brand name, mark and company the portal puts on screen — in the lists, on this page, in the heading above a report, and the report itself — so you can put the portal on a call or a projector without showing whose names are in clearance. Nothing is hidden from you: the text is still there and still selectable by the page, it is only blurred on screen. Press the button again to bring the names back."
21346
- }),
21347
- (0, import_jsx_runtime.jsxs)("div", {
21348
- className: "notice",
21396
+ children: [(0, import_jsx_runtime.jsx)(Icon, {
21397
+ name: "eye",
21398
+ size: 15
21399
+ }), "Top bar"]
21400
+ }), (0, import_jsx_runtime.jsx)("span", {
21349
21401
  style: {
21350
- marginTop: 14,
21351
- borderLeftColor: "var(--tone-medium)"
21402
+ fontSize: 13.5,
21403
+ color: "var(--text-muted)"
21352
21404
  },
21353
- children: [(0, import_jsx_runtime.jsx)("b", {
21354
- style: { color: "var(--text-strong)" },
21355
- children: "An open report blurs completely"
21356
- }), (0, import_jsx_runtime.jsx)("p", {
21357
- style: {
21358
- margin: "6px 0 0",
21359
- color: "var(--text-muted)"
21360
- },
21361
- children: "A report is a separate document held inside the page, and it can only be covered whole. With a report open, the button blurs all of it rather than just the names in it. That is deliberate — it is the safer way round for a screen you are sharing."
21362
- })]
21363
- }),
21364
- (0, import_jsx_runtime.jsx)("p", {
21405
+ children: "The eye button, to the left of your initials."
21406
+ })]
21407
+ }),
21408
+ (0, import_jsx_runtime.jsx)("p", {
21409
+ style: {
21410
+ margin: "12px 0 0",
21411
+ color: "var(--text-muted)",
21412
+ fontSize: 13.5
21413
+ },
21414
+ children: "Pressing it blurs every brand name, mark and company the portal puts on screen — in the lists, on this page, in the heading above a report, and the report itself — so you can put the portal on a call or a projector without showing whose names are in clearance. Nothing is hidden from you: the text is still there and still selectable by the page, it is only blurred on screen. Press the button again to bring the names back."
21415
+ }),
21416
+ (0, import_jsx_runtime.jsxs)("div", {
21417
+ className: "notice",
21418
+ style: {
21419
+ marginTop: 14,
21420
+ borderLeftColor: "var(--tone-medium)"
21421
+ },
21422
+ children: [(0, import_jsx_runtime.jsx)("b", {
21423
+ style: { color: "var(--text-strong)" },
21424
+ children: "An open report blurs completely"
21425
+ }), (0, import_jsx_runtime.jsx)("p", {
21365
21426
  style: {
21366
- margin: "10px 0 0",
21367
- color: "var(--text-muted)",
21368
- fontSize: 13.5
21427
+ margin: "6px 0 0",
21428
+ color: "var(--text-muted)"
21369
21429
  },
21370
- children: "It starts switched off every time you open the portal, so reloading the page brings the names back whether you meant it to or not. Turn it on again before you share."
21371
- })
21372
- ]
21373
- }),
21374
- (0, import_jsx_runtime.jsxs)("div", {
21375
- className: "notice quiet",
21376
- style: { marginTop: 26 },
21377
- children: [(0, import_jsx_runtime.jsx)("b", {
21378
- style: { color: "var(--text-strong)" },
21379
- children: "These two settings stay on this computer"
21380
- }), (0, import_jsx_runtime.jsxs)("p", {
21430
+ children: "A report is a separate document held inside the page, and it can only be covered whole. With a report open, the button blurs all of it rather than just the names in it. That is deliberate — it is the safer way round for a screen you are sharing."
21431
+ })]
21432
+ }),
21433
+ (0, import_jsx_runtime.jsx)("p", {
21381
21434
  style: {
21382
- margin: "6px 0 0",
21383
- color: "var(--text-muted)"
21435
+ margin: "10px 0 0",
21436
+ color: "var(--text-muted)",
21437
+ fontSize: 13.5
21384
21438
  },
21385
- children: [operatorName(ctx.me.brand, { lead: true }), " does not store them against your account. The appearance choice is kept by this browser, so it holds for this computer and this browser only — sign in somewhere else, or use a different browser here, and you will get the light theme again until you set it. The screen-share blur is not kept at all: it lasts until you reload or close the page."]
21386
- })]
21387
- })
21388
- ]
21389
- })
21390
- ]
21439
+ children: "It starts switched off every time you open the portal, so reloading the page brings the names back whether you meant it to or not. Turn it on again before you share."
21440
+ })
21441
+ ]
21442
+ }),
21443
+ (0, import_jsx_runtime.jsxs)("div", {
21444
+ className: "notice quiet",
21445
+ style: { marginTop: 26 },
21446
+ children: [(0, import_jsx_runtime.jsx)("b", {
21447
+ style: { color: "var(--text-strong)" },
21448
+ children: "These two settings stay on this computer"
21449
+ }), (0, import_jsx_runtime.jsxs)("p", {
21450
+ style: {
21451
+ margin: "6px 0 0",
21452
+ color: "var(--text-muted)"
21453
+ },
21454
+ children: [operatorName(ctx.me.brand, { lead: true }), " does not store them against your account. The appearance choice is kept by this browser, so it holds for this computer and this browser only — sign in somewhere else, or use a different browser here, and you will get the light theme again until you set it. The screen-share blur is not kept at all: it lasts until you reload or close the page."]
21455
+ })]
21456
+ })
21457
+ ]
21458
+ })]
21391
21459
  });
21392
21460
  }
21393
21461
  function ThemeOption({ label, value, current, onPick }) {
@@ -21512,74 +21580,103 @@ function StepsPanel({ offer, measuring = false, state = AT_REST, onBlock, onSecr
21512
21580
  className: "steps-name",
21513
21581
  children: offer.name
21514
21582
  }),
21515
- (0, import_jsx_runtime.jsx)("ol", {
21516
- className: "steps",
21517
- children: offer.steps.map((s, i) => {
21518
- const c = s.copy;
21519
- const flashed = !measuring && state.copiedAt === i;
21520
- const landed = measuring && c?.kind === "secret" ? fill(c, mask("")) : state.landedAt === i ? state.landed : null;
21521
- return (0, import_jsx_runtime.jsx)("li", { children: (0, import_jsx_runtime.jsxs)("div", { children: [
21522
- (0, import_jsx_runtime.jsx)("div", {
21523
- className: "step-text",
21524
- children: (0, import_jsx_runtime.jsx)(Marked, { text: s.text })
21525
- }),
21526
- c?.kind === "block" ? (0, import_jsx_runtime.jsxs)("div", {
21527
- className: "codeblock",
21528
- children: [(0, import_jsx_runtime.jsx)("pre", { children: c.text }), (0, import_jsx_runtime.jsx)("button", {
21529
- type: "button",
21530
- className: `btn-ghost${flashed ? " is-copied" : ""}`,
21531
- "data-step": i,
21532
- tabIndex: measuring ? -1 : void 0,
21533
- onClick: () => onBlock?.(i, c.text),
21534
- children: flashed ? "✓ Copied" : "Copy"
21535
- })]
21536
- }) : null,
21537
- c?.kind === "secret" ? (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
21538
- (0, import_jsx_runtime.jsx)("div", {
21539
- className: "secret-btn",
21540
- children: (0, import_jsx_runtime.jsx)("button", {
21541
- type: "button",
21542
- className: `btn-primary${flashed ? " is-copied" : ""}`,
21543
- "data-step": i,
21544
- tabIndex: measuring ? -1 : void 0,
21545
- onClick: () => onSecret?.(i, c),
21546
- children: flashed ? (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [(0, import_jsx_runtime.jsx)(Icon, {
21547
- name: "check",
21548
- size: 15
21549
- }), "Copied"] }) : landed ? "Copy again" : c.label
21550
- })
21551
- }),
21552
- landed ? (0, import_jsx_runtime.jsxs)("div", {
21553
- className: "landed",
21554
- children: [(0, import_jsx_runtime.jsx)("div", {
21555
- className: "landed-label",
21556
- children: "On your clipboard now"
21557
- }), landed.split("\n").map((line, n) => (0, import_jsx_runtime.jsx)("div", {
21558
- className: "mono",
21559
- children: line
21560
- }, n))]
21561
- }) : null,
21562
- !measuring && state.revealedAt === i && state.revealed ? (0, import_jsx_runtime.jsxs)("div", {
21563
- className: "landed",
21564
- children: [(0, import_jsx_runtime.jsx)("p", {
21565
- className: "ai-said",
21566
- children: "Your browser would not let us copy it. Copy this by hand — it is yours, and it will not be shown again."
21567
- }), (0, import_jsx_runtime.jsx)("pre", {
21568
- className: "ai-pre",
21569
- children: state.revealed
21570
- })]
21571
- }) : null
21572
- ] }) : null,
21573
- s.hint ? (0, import_jsx_runtime.jsx)("div", {
21574
- className: "step-hint",
21575
- children: (0, import_jsx_runtime.jsx)(Marked, { text: s.hint })
21576
- }) : null
21577
- ] }) }, i);
21578
- })
21579
- })
21583
+ offer.door === null && offer.altSteps?.length ? (0, import_jsx_runtime.jsx)("p", {
21584
+ className: "steps-unknown",
21585
+ children: "We could not check how this Clearotron lets an assistant in, so both ways are below. Try the first — your assistant will tell you if it needs the other."
21586
+ }) : null,
21587
+ (0, import_jsx_runtime.jsx)(StepList, {
21588
+ steps: offer.steps,
21589
+ offset: 0,
21590
+ measuring,
21591
+ state,
21592
+ onBlock,
21593
+ onSecret
21594
+ }),
21595
+ offer.door === null && offer.altSteps?.length ? (0, import_jsx_runtime.jsxs)("div", {
21596
+ className: "steps-alt",
21597
+ children: [(0, import_jsx_runtime.jsx)("h4", {
21598
+ className: "steps-alt-head",
21599
+ children: "If your assistant asks to be let in another way"
21600
+ }), (0, import_jsx_runtime.jsx)(StepList, {
21601
+ steps: offer.altSteps,
21602
+ offset: offer.steps.length,
21603
+ measuring,
21604
+ state,
21605
+ onBlock,
21606
+ onSecret
21607
+ })]
21608
+ }) : null
21580
21609
  ]
21581
21610
  });
21582
21611
  }
21612
+ function StepList({ steps, offset, measuring = false, state = AT_REST, onBlock, onSecret }) {
21613
+ return (0, import_jsx_runtime.jsx)("ol", {
21614
+ className: "steps",
21615
+ children: steps.map((s, n) => {
21616
+ const i = n + offset;
21617
+ const c = s.copy;
21618
+ const flashed = !measuring && state.copiedAt === i;
21619
+ const landed = measuring && c?.kind === "secret" ? fill(c, mask("")) : state.landedAt === i ? state.landed : null;
21620
+ return (0, import_jsx_runtime.jsx)("li", { children: (0, import_jsx_runtime.jsxs)("div", { children: [
21621
+ (0, import_jsx_runtime.jsx)("div", {
21622
+ className: "step-text",
21623
+ children: (0, import_jsx_runtime.jsx)(Marked, { text: s.text })
21624
+ }),
21625
+ c?.kind === "block" ? (0, import_jsx_runtime.jsxs)("div", {
21626
+ className: "codeblock",
21627
+ children: [(0, import_jsx_runtime.jsx)("pre", { children: c.text }), (0, import_jsx_runtime.jsx)("button", {
21628
+ type: "button",
21629
+ className: `btn-ghost${flashed ? " is-copied" : ""}`,
21630
+ "data-step": i,
21631
+ tabIndex: measuring ? -1 : void 0,
21632
+ onClick: () => onBlock?.(i, c.text),
21633
+ children: flashed ? "✓ Copied" : "Copy"
21634
+ })]
21635
+ }) : null,
21636
+ c?.kind === "secret" ? (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
21637
+ (0, import_jsx_runtime.jsx)("div", {
21638
+ className: "secret-btn",
21639
+ children: (0, import_jsx_runtime.jsx)("button", {
21640
+ type: "button",
21641
+ className: `btn-primary${flashed ? " is-copied" : ""}`,
21642
+ "data-step": i,
21643
+ tabIndex: measuring ? -1 : void 0,
21644
+ onClick: () => onSecret?.(i, c),
21645
+ children: flashed ? (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [(0, import_jsx_runtime.jsx)(Icon, {
21646
+ name: "check",
21647
+ size: 15
21648
+ }), "Copied"] }) : landed ? "Copy again" : c.label
21649
+ })
21650
+ }),
21651
+ landed ? (0, import_jsx_runtime.jsxs)("div", {
21652
+ className: "landed",
21653
+ children: [(0, import_jsx_runtime.jsx)("div", {
21654
+ className: "landed-label",
21655
+ children: "On your clipboard now"
21656
+ }), landed.split("\n").map((line, n) => (0, import_jsx_runtime.jsx)("div", {
21657
+ className: "mono",
21658
+ children: line
21659
+ }, n))]
21660
+ }) : null,
21661
+ !measuring && state.revealedAt === i && state.revealed ? (0, import_jsx_runtime.jsxs)("div", {
21662
+ className: "landed",
21663
+ children: [(0, import_jsx_runtime.jsx)("p", {
21664
+ className: "ai-said",
21665
+ children: "Your browser would not let us copy it. Copy this by hand — it is yours, and it will not be shown again."
21666
+ }), (0, import_jsx_runtime.jsx)("pre", {
21667
+ className: "ai-pre",
21668
+ children: state.revealed
21669
+ })]
21670
+ }) : null
21671
+ ] }) : null,
21672
+ s.hint ? (0, import_jsx_runtime.jsx)("div", {
21673
+ className: "step-hint",
21674
+ children: (0, import_jsx_runtime.jsx)(Marked, { text: s.hint })
21675
+ }) : null
21676
+ ] }) }, i);
21677
+ })
21678
+ });
21679
+ }
21583
21680
  function NotOnline() {
21584
21681
  return (0, import_jsx_runtime.jsxs)("div", {
21585
21682
  className: "notice ai-none",
@@ -21664,17 +21761,9 @@ function UseYourAI({ ctx }) {
21664
21761
  return (0, import_jsx_runtime.jsxs)("div", {
21665
21762
  className: "screen ai-screen",
21666
21763
  children: [
21667
- (0, import_jsx_runtime.jsx)("div", {
21668
- className: "eyebrow",
21669
- children: "Use your own AI"
21670
- }),
21671
- (0, import_jsx_runtime.jsx)("h1", {
21672
- className: "ai-title",
21673
- children: "Use your own AI"
21674
- }),
21675
- (0, import_jsx_runtime.jsx)("p", {
21676
- className: "ai-lead",
21677
- children: "Run and interrogate clearances from the assistant you already use — by voice, by email, or just by asking."
21764
+ (0, import_jsx_runtime.jsx)(PageHeader, {
21765
+ title: "Use your own AI",
21766
+ lede: "Run and interrogate clearances from the assistant you already use — by voice, by email, or just by asking."
21678
21767
  }),
21679
21768
  (0, import_jsx_runtime.jsxs)("section", {
21680
21769
  className: "ctx-card ai-can",
@@ -21920,7 +22009,6 @@ function Profile({ ctx }) {
21920
22009
  useUnsaved(dirty);
21921
22010
  if (needsOwner || result?.kind === "pickAccount") return (0, import_jsx_runtime.jsx)(CompanyGate, {
21922
22011
  ctx,
21923
- eyebrow: "Company",
21924
22012
  heading: "Profile",
21925
22013
  line: "Pick a company to see its profile."
21926
22014
  });
@@ -22472,7 +22560,6 @@ function Projects({ ctx }) {
22472
22560
  const projects = result?.kind === "ok" ? result.value : [];
22473
22561
  if (needsOwner || result?.kind === "pickAccount") return (0, import_jsx_runtime.jsx)(CompanyGate, {
22474
22562
  ctx,
22475
- eyebrow: "Company",
22476
22563
  heading: "Projects",
22477
22564
  line: "Pick a company to see its projects."
22478
22565
  });
@@ -23685,54 +23772,29 @@ function PeopleAccess({ ctx }) {
23685
23772
  const v = result.value;
23686
23773
  const broken = v.people.filter((p) => p.dangling.length > 0);
23687
23774
  const where = ctx.organisations.length === 1 ? `at ${ctx.organisations[0]?.name ?? ""}` : "here";
23688
- return (0, import_jsx_runtime.jsxs)("div", {
23775
+ return (0, import_jsx_runtime.jsx)("div", {
23689
23776
  className: "screen",
23690
- children: [(0, import_jsx_runtime.jsx)("div", {
23691
- className: "eyebrow",
23692
- children: "People"
23693
- }), (0, import_jsx_runtime.jsxs)("div", {
23777
+ children: (0, import_jsx_runtime.jsxs)("div", {
23694
23778
  className: "measure",
23695
23779
  style: { "--screen-measure": "900px" },
23696
23780
  children: [
23697
- (0, import_jsx_runtime.jsxs)("div", {
23698
- style: {
23699
- display: "flex",
23700
- alignItems: "flex-end",
23701
- gap: 20,
23702
- margin: "4px 0 16px"
23703
- },
23704
- children: [(0, import_jsx_runtime.jsxs)("div", {
23705
- style: { flex: 1 },
23706
- children: [(0, import_jsx_runtime.jsx)("h1", {
23707
- style: {
23708
- fontSize: 27,
23709
- margin: "0 0 4px",
23710
- color: "var(--text-strong)"
23711
- },
23712
- children: "People"
23713
- }), (0, import_jsx_runtime.jsxs)("p", {
23714
- style: {
23715
- margin: 0,
23716
- color: "var(--text-muted)",
23717
- fontSize: 14.5
23718
- },
23719
- children: [
23720
- "Who can use Clearotron ",
23721
- (0, import_jsx_runtime.jsx)("span", {
23722
- "data-anon": "mark",
23723
- children: where
23724
- }),
23725
- ", what they can do, and which companies they can see."
23726
- ]
23727
- })]
23728
- }), (0, import_jsx_runtime.jsx)("button", {
23781
+ (0, import_jsx_runtime.jsx)(PageHeader, {
23782
+ title: "People",
23783
+ lede: (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
23784
+ "Who can use Clearotron ",
23785
+ (0, import_jsx_runtime.jsx)("span", {
23786
+ "data-anon": "mark",
23787
+ children: where
23788
+ }),
23789
+ ", what they can do, and which companies they can see."
23790
+ ] }),
23791
+ actions: (0, import_jsx_runtime.jsx)("button", {
23729
23792
  type: "button",
23730
23793
  className: "btn-primary",
23731
- style: { flex: "none" },
23732
23794
  disabled: !v.canAdd,
23733
23795
  onClick: () => ctx.go(ADD_PERSON.path),
23734
23796
  children: "+ Add a person"
23735
- })]
23797
+ })
23736
23798
  }),
23737
23799
  v.localSignIn ? (0, import_jsx_runtime.jsx)("div", {
23738
23800
  className: "notice quiet",
@@ -23824,12 +23886,18 @@ function PeopleAccess({ ctx }) {
23824
23886
  children: [(0, import_jsx_runtime.jsx)("thead", { children: (0, import_jsx_runtime.jsxs)("tr", { children: [
23825
23887
  (0, import_jsx_runtime.jsx)("th", { children: "Person" }),
23826
23888
  (0, import_jsx_runtime.jsx)("th", { children: "Permissions" }),
23827
- (0, import_jsx_runtime.jsx)("th", { children: "Access to" })
23889
+ (0, import_jsx_runtime.jsx)("th", { children: "Access to" }),
23890
+ (0, import_jsx_runtime.jsx)("th", { "aria-label": "Change or remove" })
23828
23891
  ] }) }), (0, import_jsx_runtime.jsx)("tbody", { children: v.people.length === 0 ? (0, import_jsx_runtime.jsx)("tr", { children: (0, import_jsx_runtime.jsx)("td", {
23829
- colSpan: 3,
23892
+ colSpan: 4,
23830
23893
  style: { color: "var(--text-muted)" },
23831
23894
  children: "Nobody has been given access yet."
23832
- }) }) : v.people.map((p) => (0, import_jsx_runtime.jsx)(Row, { person: p }, p.email)) })]
23895
+ }) }) : v.people.map((p) => (0, import_jsx_runtime.jsx)(Row, {
23896
+ person: p,
23897
+ you: p.email.toLowerCase() === ctx.me.email.toLowerCase(),
23898
+ canModify: !v.localSignIn,
23899
+ onModify: () => ctx.go(`${MODIFY_PERSON.path}?email=${encodeURIComponent(p.email)}`)
23900
+ }, p.email)) })]
23833
23901
  })
23834
23902
  }),
23835
23903
  v.grantsFile ? (0, import_jsx_runtime.jsxs)("p", {
@@ -23862,7 +23930,7 @@ function PeopleAccess({ ctx }) {
23862
23930
  }) : null,
23863
23931
  (0, import_jsx_runtime.jsx)(Observed, { result: observed })
23864
23932
  ]
23865
- })]
23933
+ })
23866
23934
  });
23867
23935
  }
23868
23936
  function Observed({ result }) {
@@ -23958,11 +24026,11 @@ function Observed({ result }) {
23958
24026
  ]
23959
24027
  });
23960
24028
  }
23961
- function Row({ person }) {
24029
+ function Row({ person, you, canModify, onModify }) {
23962
24030
  const chips = accessChips(person.access);
23963
24031
  const viewOnly = !person.permissions.run && !person.permissions.manage;
23964
24032
  return (0, import_jsx_runtime.jsxs)("tr", { children: [
23965
- (0, import_jsx_runtime.jsx)("td", { children: (0, import_jsx_runtime.jsx)("span", {
24033
+ (0, import_jsx_runtime.jsxs)("td", { children: [(0, import_jsx_runtime.jsx)("span", {
23966
24034
  style: {
23967
24035
  fontWeight: 700,
23968
24036
  color: "var(--text-strong)",
@@ -23970,7 +24038,15 @@ function Row({ person }) {
23970
24038
  },
23971
24039
  "data-anon": "mark",
23972
24040
  children: person.email
23973
- }) }),
24041
+ }), you ? (0, import_jsx_runtime.jsx)("span", {
24042
+ className: "pill",
24043
+ style: {
24044
+ fontSize: 10.5,
24045
+ padding: "1px 7px",
24046
+ marginLeft: 7
24047
+ },
24048
+ children: "You"
24049
+ }) : null] }),
23974
24050
  (0, import_jsx_runtime.jsx)("td", {
23975
24051
  style: { color: viewOnly ? "var(--text-muted)" : "var(--text-strong)" },
23976
24052
  children: person.listed === false ? (0, import_jsx_runtime.jsx)("span", {
@@ -23992,7 +24068,24 @@ function Row({ person }) {
23992
24068
  }) : (0, import_jsx_runtime.jsx)("span", {
23993
24069
  style: { color: "var(--text-muted)" },
23994
24070
  children: "Nothing yet — signed in, and given no access."
23995
- }) })
24071
+ }) }),
24072
+ (0, import_jsx_runtime.jsx)("td", {
24073
+ style: {
24074
+ textAlign: "right",
24075
+ whiteSpace: "nowrap"
24076
+ },
24077
+ children: you ? null : (0, import_jsx_runtime.jsx)("button", {
24078
+ type: "button",
24079
+ className: "pill",
24080
+ disabled: !canModify,
24081
+ style: {
24082
+ cursor: canModify ? "pointer" : "default",
24083
+ fontSize: 12
24084
+ },
24085
+ onClick: onModify,
24086
+ children: "Modify"
24087
+ })
24088
+ })
23996
24089
  ] });
23997
24090
  }
23998
24091
  //#endregion
@@ -24020,22 +24113,12 @@ function GiveAccess({ ctx }) {
24020
24113
  (0, import_react.useEffect)(() => {
24021
24114
  if (saved && !kept) ctx.go(PEOPLE.path);
24022
24115
  }, [saved, kept]);
24023
- if (view && isOk(view) && !view.value.canAdd) return (0, import_jsx_runtime.jsxs)("div", {
24116
+ if (view && isOk(view) && !view.value.canAdd) return (0, import_jsx_runtime.jsx)("div", {
24024
24117
  className: "screen",
24025
- children: [(0, import_jsx_runtime.jsx)("div", {
24026
- className: "eyebrow",
24027
- children: "People"
24028
- }), (0, import_jsx_runtime.jsxs)("div", {
24118
+ children: (0, import_jsx_runtime.jsxs)("div", {
24029
24119
  className: "measure",
24030
24120
  style: { "--screen-measure": "640px" },
24031
- children: [(0, import_jsx_runtime.jsx)("h1", {
24032
- style: {
24033
- fontSize: 27,
24034
- margin: "4px 0 12px",
24035
- color: "var(--text-strong)"
24036
- },
24037
- children: "Give someone access to Clearotron"
24038
- }), (0, import_jsx_runtime.jsx)("div", {
24121
+ children: [(0, import_jsx_runtime.jsx)(PageHeader, { title: "Give someone access to Clearotron" }), (0, import_jsx_runtime.jsx)("div", {
24039
24122
  className: "notice quiet",
24040
24123
  children: (0, import_jsx_runtime.jsxs)("p", {
24041
24124
  style: {
@@ -24059,7 +24142,7 @@ function GiveAccess({ ctx }) {
24059
24142
  ]
24060
24143
  })
24061
24144
  })]
24062
- })]
24145
+ })
24063
24146
  });
24064
24147
  const holdsEverything = ctx.me.access.some((a) => a.kind === "everything");
24065
24148
  const wholeOrgs = new Set(ctx.me.genericOrgs);
@@ -24110,31 +24193,16 @@ function GiveAccess({ ctx }) {
24110
24193
  setBusy(false);
24111
24194
  }
24112
24195
  };
24113
- const failure = result && !isOk(result) ? result.kind === "conflict" ? "This Clearotron now signs in one person, so nobody can be added. Nothing was saved." : result.kind === "notFound" ? "You can only give access to what you have access to yourself. Nothing was saved." : result.kind === "reject" ? result.errors.join(" ") : saveFailureText(result, "That could not be saved. Nothing was changed.") : null;
24114
- return (0, import_jsx_runtime.jsxs)("div", {
24196
+ const failure = result && !isOk(result) ? result.kind === "gate" && result.detail?.code === "local_sign_in" ? "This Clearotron now signs in one person, so nobody can be added. Nothing was saved." : result.kind === "conflict" ? "This Clearotron now signs in one person, so nobody can be added. Nothing was saved." : result.kind === "notFound" ? "You can only give access to what you have access to yourself. Nothing was saved." : result.kind === "reject" ? result.errors.join(" ") : saveFailureText(result, "That could not be saved. Nothing was changed.") : null;
24197
+ return (0, import_jsx_runtime.jsx)("div", {
24115
24198
  className: "screen",
24116
- children: [(0, import_jsx_runtime.jsx)("div", {
24117
- className: "eyebrow",
24118
- children: "People"
24119
- }), (0, import_jsx_runtime.jsxs)("div", {
24199
+ children: (0, import_jsx_runtime.jsxs)("div", {
24120
24200
  className: "measure",
24121
24201
  style: { "--screen-measure": "640px" },
24122
24202
  children: [
24123
- (0, import_jsx_runtime.jsx)("h1", {
24124
- style: {
24125
- fontSize: 27,
24126
- margin: "4px 0 4px",
24127
- color: "var(--text-strong)"
24128
- },
24129
- children: "Give someone access to Clearotron"
24130
- }),
24131
- (0, import_jsx_runtime.jsx)("p", {
24132
- style: {
24133
- margin: "0 0 22px",
24134
- color: "var(--text-muted)",
24135
- fontSize: 14.5
24136
- },
24137
- children: "Enter their email, choose what they can see and do, then save. They sign in the same way you do."
24203
+ (0, import_jsx_runtime.jsx)(PageHeader, {
24204
+ title: "Give someone access to Clearotron",
24205
+ lede: "Enter their email, choose what they can see and do, then save. They sign in the same way you do."
24138
24206
  }),
24139
24207
  (0, import_jsx_runtime.jsx)("label", {
24140
24208
  className: "field-label",
@@ -24163,7 +24231,7 @@ function GiveAccess({ ctx }) {
24163
24231
  gap: 8,
24164
24232
  marginBottom: 6
24165
24233
  },
24166
- children: [(0, import_jsx_runtime.jsx)(Lever, {
24234
+ children: [(0, import_jsx_runtime.jsx)(Lever$1, {
24167
24235
  on: permissions.run,
24168
24236
  title: "Run clearances",
24169
24237
  line: "Start and stop clearances on the companies they can see.",
@@ -24172,7 +24240,7 @@ function GiveAccess({ ctx }) {
24172
24240
  ...p,
24173
24241
  run: !p.run
24174
24242
  }))
24175
- }), (0, import_jsx_runtime.jsx)(Lever, {
24243
+ }), (0, import_jsx_runtime.jsx)(Lever$1, {
24176
24244
  on: permissions.manage,
24177
24245
  title: "Manage",
24178
24246
  line: "Add companies, add people and change settings for the companies they can see.",
@@ -24202,7 +24270,7 @@ function GiveAccess({ ctx }) {
24202
24270
  gap: 6,
24203
24271
  marginBottom: 8
24204
24272
  },
24205
- children: [holdsEverything ? (0, import_jsx_runtime.jsx)(AttachRow, {
24273
+ children: [holdsEverything ? (0, import_jsx_runtime.jsx)(AttachRow$1, {
24206
24274
  on: chosen.everything,
24207
24275
  name: "Everything on this Clearotron",
24208
24276
  line: "all organisations and companies, including ones added later",
@@ -24213,7 +24281,7 @@ function GiveAccess({ ctx }) {
24213
24281
  }))
24214
24282
  }) : null, tree.map((t) => (0, import_jsx_runtime.jsxs)("div", {
24215
24283
  style: { display: "contents" },
24216
- children: [t.whole ? (0, import_jsx_runtime.jsx)(AttachRow, {
24284
+ children: [t.whole ? (0, import_jsx_runtime.jsx)(AttachRow$1, {
24217
24285
  on: chosen.everything || chosen.orgs.has(t.org.key),
24218
24286
  implied: chosen.everything,
24219
24287
  name: t.org.name,
@@ -24223,7 +24291,7 @@ function GiveAccess({ ctx }) {
24223
24291
  ...c,
24224
24292
  orgs: toggle(c.orgs, t.org.key)
24225
24293
  }))
24226
- }) : null, t.companies.map((k) => (0, import_jsx_runtime.jsx)(AttachRow, {
24294
+ }) : null, t.companies.map((k) => (0, import_jsx_runtime.jsx)(AttachRow$1, {
24227
24295
  sub: t.whole,
24228
24296
  on: covered(k),
24229
24297
  implied: chosen.everything || chosen.orgs.has(t.org.key),
@@ -24319,10 +24387,10 @@ function GiveAccess({ ctx }) {
24319
24387
  })] })
24320
24388
  })
24321
24389
  ]
24322
- })]
24390
+ })
24323
24391
  });
24324
24392
  }
24325
- function Lever({ on, title, line, disabled, onToggle }) {
24393
+ function Lever$1({ on, title, line, disabled, onToggle }) {
24326
24394
  return (0, import_jsx_runtime.jsxs)("button", {
24327
24395
  type: "button",
24328
24396
  className: on ? "lever lever-on" : "lever",
@@ -24349,7 +24417,7 @@ function Lever({ on, title, line, disabled, onToggle }) {
24349
24417
  })]
24350
24418
  });
24351
24419
  }
24352
- function AttachRow({ on, implied = false, sub = false, name, line, disabled, onToggle }) {
24420
+ function AttachRow$1({ on, implied = false, sub = false, name, line, disabled, onToggle }) {
24353
24421
  return (0, import_jsx_runtime.jsxs)("button", {
24354
24422
  type: "button",
24355
24423
  className: `attach-row${on ? " on" : ""}${sub ? " sub" : ""}`,
@@ -24374,101 +24442,604 @@ function AttachRow({ on, implied = false, sub = false, name, line, disabled, onT
24374
24442
  });
24375
24443
  }
24376
24444
  //#endregion
24377
- //#region src/screens/About.tsx
24378
- var LICENCE_URL = "https://www.gnu.org/licenses/agpl-3.0.html";
24379
- function About() {
24380
- const [info, setInfo] = (0, import_react.useState)(null);
24381
- const [failed, setFailed] = (0, import_react.useState)(false);
24382
- (0, import_react.useEffect)(() => {
24383
- let live = true;
24384
- api.about().then((r) => {
24385
- if (!live) return;
24386
- if (isOk(r)) setInfo(r.value);
24387
- else setFailed(true);
24388
- });
24389
- return () => {
24390
- live = false;
24391
- };
24392
- }, []);
24393
- if (failed) return (0, import_jsx_runtime.jsxs)("section", {
24394
- className: "screen",
24395
- children: [
24396
- (0, import_jsx_runtime.jsx)("h1", { children: "About" }),
24397
- (0, import_jsx_runtime.jsx)("p", { children: "This deployment could not report which build it is running. That is a fault, not a configuration choice — the source offer below is incomplete without it." }),
24398
- (0, import_jsx_runtime.jsxs)("p", { children: ["Source: ", (0, import_jsx_runtime.jsx)("a", {
24399
- href: "https://github.com/CordilleraSarl/Clearotron",
24400
- children: "github.com/CordilleraSarl/Clearotron"
24401
- })] })
24402
- ]
24403
- });
24404
- if (!info) return (0, import_jsx_runtime.jsxs)("section", {
24445
+ //#region src/screens/ModifyAccess.tsx
24446
+ var emailInQuery = () => (new URLSearchParams(window.location.search).get("email") ?? "").trim().toLowerCase();
24447
+ var heldBy = (person) => ({
24448
+ orgs: new Set(person.access.filter((a) => a.kind === "organisation").map((a) => a.key)),
24449
+ companies: new Set(person.access.filter((a) => a.kind === "company").map((a) => a.key))
24450
+ });
24451
+ var sameSet = (a, b) => a.size === b.size && [...a].every((k) => b.has(k));
24452
+ function ModifyAccess({ ctx }) {
24453
+ const email = emailInQuery();
24454
+ const { result: view } = useLoad(() => api.adminAccess(), []);
24455
+ if (!view) return (0, import_jsx_runtime.jsx)("div", { className: "screen" });
24456
+ if (!isOk(view)) return (0, import_jsx_runtime.jsx)("div", {
24405
24457
  className: "screen",
24406
- children: [(0, import_jsx_runtime.jsx)("h1", { children: "About" }), (0, import_jsx_runtime.jsx)("p", { children: "Loading…" })]
24458
+ children: (0, import_jsx_runtime.jsx)("div", {
24459
+ className: "empty",
24460
+ children: (0, import_jsx_runtime.jsx)("p", { children: "This page is not available." })
24461
+ })
24407
24462
  });
24408
- const shortSha = info.commit ? info.commit.slice(0, 12) : null;
24409
- return (0, import_jsx_runtime.jsxs)("section", {
24463
+ const person = view.value.people.find((p) => p.email.toLowerCase() === email) ?? null;
24464
+ if (!person) return (0, import_jsx_runtime.jsx)("div", {
24410
24465
  className: "screen",
24411
- children: [
24412
- (0, import_jsx_runtime.jsxs)("h1", { children: ["About ", info.name] }),
24413
- (0, import_jsx_runtime.jsxs)("dl", { children: [
24414
- (0, import_jsx_runtime.jsx)("dt", { children: "Product" }),
24415
- (0, import_jsx_runtime.jsxs)("dd", { children: [info.name, info.version ? ` ${info.version}` : ""] }),
24416
- (0, import_jsx_runtime.jsx)("dt", { children: "Build" }),
24417
- (0, import_jsx_runtime.jsx)("dd", { children: shortSha ? (0, import_jsx_runtime.jsx)("code", {
24418
- title: info.commit ?? void 0,
24419
- children: shortSha
24420
- }) : (0, import_jsx_runtime.jsx)("span", { children: "not reported by this deployment" }) }),
24421
- (0, import_jsx_runtime.jsx)("dt", { children: "Source" }),
24422
- (0, import_jsx_runtime.jsxs)("dd", { children: [(0, import_jsx_runtime.jsx)("a", {
24423
- href: info.sourceUrl,
24424
- rel: "noreferrer",
24425
- children: info.sourceUrl
24426
- }), !info.commit && (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [" ", (0, import_jsx_runtime.jsx)("strong", { children: "This links to the repository, not to the exact build you are using — this deployment could not report its commit." })] })] }),
24427
- (0, import_jsx_runtime.jsx)("dt", { children: "Licence" }),
24428
- (0, import_jsx_runtime.jsxs)("dd", { children: [
24429
- info.license ?? "not reported",
24430
- " — ",
24431
- (0, import_jsx_runtime.jsx)("a", {
24432
- href: LICENCE_URL,
24433
- rel: "noreferrer",
24434
- children: "full text"
24466
+ children: (0, import_jsx_runtime.jsxs)("div", {
24467
+ className: "measure",
24468
+ style: { "--screen-measure": "640px" },
24469
+ children: [
24470
+ (0, import_jsx_runtime.jsx)(PageHeader, { title: "Modify access" }),
24471
+ (0, import_jsx_runtime.jsx)("div", {
24472
+ className: "notice quiet",
24473
+ children: (0, import_jsx_runtime.jsx)("p", {
24474
+ style: {
24475
+ margin: 0,
24476
+ fontSize: 13.5,
24477
+ color: "var(--text-muted)"
24478
+ },
24479
+ children: "Nobody with that address has access you can see. They may have been removed, or their access may be somewhere you cannot reach."
24435
24480
  })
24436
- ] }),
24437
- (0, import_jsx_runtime.jsx)("dt", { children: "Copyright" }),
24438
- (0, import_jsx_runtime.jsx)("dd", { children: info.copyright })
24439
- ] }),
24440
- (0, import_jsx_runtime.jsxs)("p", { children: [
24441
- (0, import_jsx_runtime.jsx)("a", {
24442
- href: `${info.sourceRepo}/blob/main/LICENSE`,
24443
- rel: "noreferrer",
24444
- children: "Licence"
24445
- }),
24446
- " · ",
24447
- (0, import_jsx_runtime.jsx)("a", {
24448
- href: `${info.sourceRepo}/blob/main/NOTICES.md`,
24449
- rel: "noreferrer",
24450
- children: "Notices"
24451
- }),
24452
- " · ",
24453
- (0, import_jsx_runtime.jsx)("a", {
24454
- href: `${info.sourceRepo}/blob/main/TRADEMARKS.md`,
24455
- rel: "noreferrer",
24456
- children: "Trademarks policy"
24457
- }),
24458
- " · ",
24459
- (0, import_jsx_runtime.jsx)("a", {
24460
- href: `${info.sourceRepo}/blob/main/CONTRIBUTING.md`,
24461
- rel: "noreferrer",
24462
- children: "Contributing"
24463
24481
  }),
24464
- " · ",
24465
- (0, import_jsx_runtime.jsx)("a", {
24466
- href: `${info.sourceRepo}/blob/main/SECURITY.md`,
24467
- rel: "noreferrer",
24468
- children: "Security"
24469
- }),
24470
- " · ",
24471
- (0, import_jsx_runtime.jsx)("a", {
24482
+ (0, import_jsx_runtime.jsx)("div", {
24483
+ style: { marginTop: 16 },
24484
+ children: (0, import_jsx_runtime.jsx)("button", {
24485
+ type: "button",
24486
+ className: "btn-ghost",
24487
+ onClick: () => ctx.go(PEOPLE.path),
24488
+ children: "Back to People"
24489
+ })
24490
+ })
24491
+ ]
24492
+ })
24493
+ });
24494
+ return (0, import_jsx_runtime.jsx)(Form, {
24495
+ ctx,
24496
+ person,
24497
+ view: view.value
24498
+ }, person.email);
24499
+ }
24500
+ function Form({ ctx, person, view }) {
24501
+ const readOnlyPeople = view.localSignIn;
24502
+ const held = heldBy(person);
24503
+ const [permissions, setPermissions] = (0, import_react.useState)(person.permissions);
24504
+ const [chosen, setChosen] = (0, import_react.useState)(held);
24505
+ const [busy, setBusy] = (0, import_react.useState)(false);
24506
+ const [confirming, setConfirming] = (0, import_react.useState)(false);
24507
+ const [saved, setSaved] = (0, import_react.useState)(null);
24508
+ const [gone, setGone] = (0, import_react.useState)(null);
24509
+ const whole = person.covered === true;
24510
+ const holdsEverything = person.access.some((a) => a.kind === "everything");
24511
+ const orgName = (key) => ctx.organisations.find((o) => o.key === key)?.name ?? key;
24512
+ const wholeOrgs = new Set(ctx.me.genericOrgs);
24513
+ const companiesIn = (org) => ctx.ownerKeys.filter((k) => !isGenericKey(k) && ctx.orgOf(k) === org);
24514
+ const tree = ctx.organisations.map((o) => ({
24515
+ org: o,
24516
+ whole: wholeOrgs.has(o.key),
24517
+ companies: companiesIn(o.key)
24518
+ })).filter((t) => t.whole || t.companies.length > 0);
24519
+ const toggle = (set, key) => {
24520
+ const next = new Set(set);
24521
+ if (next.has(key)) next.delete(key);
24522
+ else next.add(key);
24523
+ return next;
24524
+ };
24525
+ const covered = (company) => chosen.companies.has(company) || chosen.orgs.has(ctx.orgOf(company) ?? "");
24526
+ const soloCompanies = [...chosen.companies].filter((k) => !chosen.orgs.has(ctx.orgOf(k) ?? ""));
24527
+ const dirty = !sameSet(chosen.orgs, held.orgs) || !sameSet(chosen.companies, held.companies) || permissions.run !== person.permissions.run || permissions.manage !== person.permissions.manage;
24528
+ useUnsaved(dirty && saved === null && gone === null);
24529
+ const access = [...[...chosen.orgs].map((key) => ({
24530
+ kind: "organisation",
24531
+ key
24532
+ })), ...soloCompanies.map((key) => ({
24533
+ kind: "company",
24534
+ key
24535
+ }))];
24536
+ const leftWithNothing = access.length === 0;
24537
+ const losing = [...[...held.orgs].filter((k) => !chosen.orgs.has(k)).map(orgName), ...[...held.companies].filter((k) => !covered(k)).map((k) => ctx.ownerName(k))];
24538
+ const sentence = accessSentence({
24539
+ who: person.email,
24540
+ permissions,
24541
+ everything: holdsEverything,
24542
+ organisations: [...chosen.orgs].map(orgName),
24543
+ companies: soloCompanies.map((k) => ({
24544
+ name: ctx.ownerName(k),
24545
+ organisation: orgName(ctx.orgOf(k) ?? "")
24546
+ }))
24547
+ });
24548
+ const save = async () => {
24549
+ setBusy(true);
24550
+ try {
24551
+ setSaved(await api.changePerson({
24552
+ email: person.email,
24553
+ permissions,
24554
+ access
24555
+ }));
24556
+ } finally {
24557
+ setBusy(false);
24558
+ }
24559
+ };
24560
+ const remove = async () => {
24561
+ setBusy(true);
24562
+ try {
24563
+ const r = await api.removePerson({ email: person.email });
24564
+ setGone(r);
24565
+ if (isOk(r)) ctx.go(PEOPLE.path);
24566
+ } finally {
24567
+ setBusy(false);
24568
+ }
24569
+ };
24570
+ const failure = (r) => r === null || isOk(r) ? null : r.kind === "gate" && r.detail?.code === "local_sign_in" ? "This Clearotron now signs in one person, so nobody else can be changed. Nothing was saved." : r.kind === "conflict" ? "This Clearotron now signs in one person, so nobody else can be changed. Nothing was saved." : r.kind === "notFound" ? "That is not access you can change. Nothing was saved." : r.kind === "reject" ? r.errors.join(" ") : saveFailureText(r, "That could not be saved. Nothing was changed.");
24571
+ const problem = failure(saved) ?? failure(gone);
24572
+ const loses = person.access.length ? person.access.map((a) => a.kind === "everything" ? "everything on this Clearotron" : a.name ?? a.key) : [];
24573
+ const holdsKey = (person.keys ?? 0) > 0;
24574
+ const keyStaysLive = holdsKey && view.keysRevocable !== true;
24575
+ const domain = person.email.startsWith("*@") ? person.email.slice(2) : null;
24576
+ return (0, import_jsx_runtime.jsx)("div", {
24577
+ className: "screen",
24578
+ children: (0, import_jsx_runtime.jsxs)("div", {
24579
+ className: "measure",
24580
+ style: { "--screen-measure": "640px" },
24581
+ children: [
24582
+ (0, import_jsx_runtime.jsx)(PageHeader, {
24583
+ title: "Modify access",
24584
+ lede: (0, import_jsx_runtime.jsx)("b", {
24585
+ style: {
24586
+ color: "var(--text-strong)",
24587
+ wordBreak: "break-all"
24588
+ },
24589
+ "data-anon": "mark",
24590
+ children: person.email
24591
+ })
24592
+ }),
24593
+ (0, import_jsx_runtime.jsx)("div", {
24594
+ className: "field-label",
24595
+ children: "Permissions"
24596
+ }),
24597
+ (0, import_jsx_runtime.jsxs)("div", {
24598
+ style: {
24599
+ display: "flex",
24600
+ flexDirection: "column",
24601
+ gap: 8,
24602
+ marginBottom: 6
24603
+ },
24604
+ children: [(0, import_jsx_runtime.jsx)(Lever, {
24605
+ on: permissions.run,
24606
+ title: "Run clearances",
24607
+ line: "Start and stop clearances on the companies they can see.",
24608
+ disabled: !whole || busy,
24609
+ onToggle: () => setPermissions((p) => ({
24610
+ ...p,
24611
+ run: !p.run
24612
+ }))
24613
+ }), (0, import_jsx_runtime.jsx)(Lever, {
24614
+ on: permissions.manage,
24615
+ title: "Manage",
24616
+ line: "Add companies, add people and change settings for the companies they can see.",
24617
+ disabled: !whole || busy,
24618
+ onToggle: () => setPermissions((p) => ({
24619
+ ...p,
24620
+ manage: !p.manage
24621
+ }))
24622
+ })]
24623
+ }),
24624
+ (0, import_jsx_runtime.jsx)("p", {
24625
+ style: {
24626
+ margin: "0 0 22px",
24627
+ fontSize: 12.5,
24628
+ color: "var(--text-faint)"
24629
+ },
24630
+ children: whole ? "Everyone can view reports for the companies they can see. Leave both off for a view-only person." : "They have access you can't see, so only someone who can see all of it can change their permissions."
24631
+ }),
24632
+ (0, import_jsx_runtime.jsx)("div", {
24633
+ className: "field-label",
24634
+ children: "Access to"
24635
+ }),
24636
+ (0, import_jsx_runtime.jsxs)("div", {
24637
+ style: {
24638
+ display: "flex",
24639
+ flexDirection: "column",
24640
+ gap: 6,
24641
+ marginBottom: 8
24642
+ },
24643
+ children: [holdsEverything ? (0, import_jsx_runtime.jsx)(AttachRow, {
24644
+ on: true,
24645
+ name: "Everything on this Clearotron",
24646
+ line: "all organisations and companies, including ones added later",
24647
+ disabled: true,
24648
+ onToggle: () => {}
24649
+ }) : null, tree.map((t) => (0, import_jsx_runtime.jsxs)("div", {
24650
+ style: { display: "contents" },
24651
+ children: [t.whole ? (0, import_jsx_runtime.jsx)(AttachRow, {
24652
+ on: chosen.orgs.has(t.org.key),
24653
+ name: t.org.name,
24654
+ line: "every company in it, including ones added later",
24655
+ disabled: busy,
24656
+ onToggle: () => setChosen((c) => ({
24657
+ ...c,
24658
+ orgs: toggle(c.orgs, t.org.key)
24659
+ }))
24660
+ }) : null, t.companies.map((k) => (0, import_jsx_runtime.jsx)(AttachRow, {
24661
+ sub: t.whole,
24662
+ on: covered(k),
24663
+ implied: chosen.orgs.has(t.org.key),
24664
+ name: ctx.ownerName(k),
24665
+ line: t.whole ? "company" : `company in ${t.org.name}`,
24666
+ disabled: busy,
24667
+ onToggle: () => setChosen((c) => ({
24668
+ ...c,
24669
+ companies: toggle(c.companies, k)
24670
+ }))
24671
+ }, k))]
24672
+ }, t.org.key))]
24673
+ }),
24674
+ (0, import_jsx_runtime.jsxs)("p", {
24675
+ style: {
24676
+ margin: "0 0 22px",
24677
+ fontSize: 12.5,
24678
+ color: "var(--text-faint)"
24679
+ },
24680
+ children: ["Untick a row to take that access away.", whole ? "" : " You are seeing the part of their access you can reach; the rest is not shown and is not changed."]
24681
+ }),
24682
+ (0, import_jsx_runtime.jsx)("div", {
24683
+ className: "notice quiet",
24684
+ style: { marginBottom: 18 },
24685
+ children: (0, import_jsx_runtime.jsxs)("p", {
24686
+ style: {
24687
+ margin: 0,
24688
+ fontSize: 13.5,
24689
+ color: "var(--text-muted)"
24690
+ },
24691
+ "data-anon": "mark",
24692
+ children: [sentence, losing.length ? (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [" ", (0, import_jsx_runtime.jsxs)("b", {
24693
+ style: { color: "var(--text-strong)" },
24694
+ children: [
24695
+ "They lose ",
24696
+ losing.join(", "),
24697
+ "."
24698
+ ]
24699
+ })] }) : null]
24700
+ })
24701
+ }),
24702
+ problem ? (0, import_jsx_runtime.jsx)("div", {
24703
+ className: "notice",
24704
+ style: {
24705
+ borderColor: "var(--tone-high)",
24706
+ marginBottom: 14
24707
+ },
24708
+ children: (0, import_jsx_runtime.jsx)("p", {
24709
+ style: {
24710
+ margin: 0,
24711
+ fontSize: 13.5
24712
+ },
24713
+ children: problem
24714
+ })
24715
+ }) : null,
24716
+ saved && isOk(saved) ? (0, import_jsx_runtime.jsx)("div", {
24717
+ className: "notice",
24718
+ style: {
24719
+ borderColor: "var(--tone-medium)",
24720
+ marginBottom: 14
24721
+ },
24722
+ children: (0, import_jsx_runtime.jsxs)("p", {
24723
+ style: {
24724
+ margin: 0,
24725
+ fontSize: 13.5
24726
+ },
24727
+ children: ["Saved. ", saved.value.switchesApplied ? permissionsPhrase(permissions) + "." : (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
24728
+ "Their permissions stay as they were: ",
24729
+ permissionsPhrase(person.permissions),
24730
+ ". They have access you cannot see, and permissions apply everywhere a person has access."
24731
+ ] })]
24732
+ })
24733
+ }) : null,
24734
+ readOnlyPeople ? (0, import_jsx_runtime.jsx)("div", {
24735
+ className: "notice quiet",
24736
+ style: { marginBottom: 14 },
24737
+ children: (0, import_jsx_runtime.jsxs)("p", {
24738
+ style: {
24739
+ margin: 0,
24740
+ fontSize: 13.5,
24741
+ color: "var(--text-muted)"
24742
+ },
24743
+ children: [(0, import_jsx_runtime.jsx)("b", {
24744
+ style: { color: "var(--text-strong)" },
24745
+ children: "This Clearotron signs in one person: you."
24746
+ }), " Nobody else can be changed or removed here. To hold more than one person, put it behind a login system such as your company single sign-on."]
24747
+ })
24748
+ }) : null,
24749
+ (0, import_jsx_runtime.jsxs)("div", {
24750
+ style: {
24751
+ display: "flex",
24752
+ gap: 10,
24753
+ alignItems: "center"
24754
+ },
24755
+ children: [
24756
+ (0, import_jsx_runtime.jsx)("button", {
24757
+ type: "button",
24758
+ className: "btn-primary",
24759
+ disabled: !dirty || busy || leftWithNothing || readOnlyPeople,
24760
+ onClick: () => void save(),
24761
+ children: busy && !confirming ? "Saving…" : "Save changes"
24762
+ }),
24763
+ (0, import_jsx_runtime.jsx)("button", {
24764
+ type: "button",
24765
+ className: "btn-ghost",
24766
+ onClick: () => ctx.go(PEOPLE.path),
24767
+ children: "Cancel"
24768
+ }),
24769
+ leftWithNothing ? (0, import_jsx_runtime.jsx)("span", {
24770
+ style: {
24771
+ fontSize: 12.5,
24772
+ color: "var(--text-faint)"
24773
+ },
24774
+ children: "Nothing is ticked. Leave them at least one, or remove them below."
24775
+ }) : null
24776
+ ]
24777
+ }),
24778
+ (0, import_jsx_runtime.jsx)("div", {
24779
+ style: {
24780
+ marginTop: 34,
24781
+ paddingTop: 18,
24782
+ borderTop: "1px solid var(--border-hairline)",
24783
+ display: "flex",
24784
+ flexDirection: "column",
24785
+ gap: 10,
24786
+ alignItems: "flex-start"
24787
+ },
24788
+ children: confirming ? (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [(0, import_jsx_runtime.jsx)("div", {
24789
+ className: "notice",
24790
+ style: {
24791
+ borderColor: "var(--tone-high)",
24792
+ margin: 0,
24793
+ width: "100%"
24794
+ },
24795
+ children: (0, import_jsx_runtime.jsxs)("p", {
24796
+ style: {
24797
+ margin: 0,
24798
+ fontSize: 13.5
24799
+ },
24800
+ children: [
24801
+ (0, import_jsx_runtime.jsx)("b", {
24802
+ "data-anon": "mark",
24803
+ children: person.email
24804
+ }),
24805
+ " ",
24806
+ whole ? (0, import_jsx_runtime.jsxs)("b", { children: [
24807
+ "loses access to ",
24808
+ loses.length ? loses.join(" and ") : "this Clearotron",
24809
+ " straight away,"
24810
+ ] }) : (0, import_jsx_runtime.jsxs)("b", { children: [
24811
+ "loses ",
24812
+ loses.join(" and "),
24813
+ " straight away,"
24814
+ ] }),
24815
+ " ",
24816
+ keyStaysLive ? (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
24817
+ "here. Their AI was issued a key this Clearotron cannot withdraw — it was started without a revocation list — so that key keeps working until it expires.",
24818
+ " ",
24819
+ (0, import_jsx_runtime.jsx)("b", {
24820
+ className: "mono",
24821
+ children: "clearotron doctor"
24822
+ }),
24823
+ " reports it.",
24824
+ " "
24825
+ ] }) : (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: ["here and through their AI.", " "] }),
24826
+ holdsKey && !keyStaysLive ? (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
24827
+ "Their AI’s key is withdrawn at the same time;",
24828
+ " ",
24829
+ (0, import_jsx_runtime.jsx)("b", {
24830
+ className: "mono",
24831
+ children: "clearotron doctor"
24832
+ }),
24833
+ " says whether the connector has picked it up.",
24834
+ " "
24835
+ ] }) : null,
24836
+ whole ? "To stop them reaching your sign-in page, take them off your login system too." : "They keep the access you cannot see.",
24837
+ domain ? (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [" ", (0, import_jsx_runtime.jsxs)("b", { children: [
24838
+ "This takes access away from everyone with an address at ",
24839
+ (0, import_jsx_runtime.jsx)("span", {
24840
+ "data-anon": "mark",
24841
+ children: domain
24842
+ }),
24843
+ "."
24844
+ ] })] }) : null
24845
+ ]
24846
+ })
24847
+ }), (0, import_jsx_runtime.jsxs)("div", {
24848
+ style: {
24849
+ display: "flex",
24850
+ gap: 8
24851
+ },
24852
+ children: [(0, import_jsx_runtime.jsx)("button", {
24853
+ type: "button",
24854
+ className: "pill",
24855
+ style: {
24856
+ cursor: "pointer",
24857
+ fontSize: 12,
24858
+ borderColor: "var(--tone-high)"
24859
+ },
24860
+ disabled: busy,
24861
+ onClick: () => void remove(),
24862
+ children: busy ? "Working…" : "Confirm — remove"
24863
+ }), (0, import_jsx_runtime.jsx)("button", {
24864
+ type: "button",
24865
+ className: "pill",
24866
+ style: {
24867
+ cursor: "pointer",
24868
+ fontSize: 12
24869
+ },
24870
+ onClick: () => setConfirming(false),
24871
+ children: "Cancel"
24872
+ })]
24873
+ })] }) : (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [(0, import_jsx_runtime.jsx)("button", {
24874
+ type: "button",
24875
+ className: "pill",
24876
+ style: {
24877
+ cursor: readOnlyPeople ? "default" : "pointer",
24878
+ fontSize: 12
24879
+ },
24880
+ disabled: busy || readOnlyPeople,
24881
+ onClick: () => setConfirming(true),
24882
+ children: whole ? domain ? `Remove everyone at ${domain}` : "Remove from Clearotron" : `Remove from ${[...held.orgs].map(orgName).join(", ") || "here"}`
24883
+ }), whole ? null : (0, import_jsx_runtime.jsx)("p", {
24884
+ style: {
24885
+ margin: 0,
24886
+ fontSize: 12.5,
24887
+ color: "var(--text-faint)"
24888
+ },
24889
+ children: "They keep the access you cannot see."
24890
+ })] })
24891
+ })
24892
+ ]
24893
+ })
24894
+ });
24895
+ }
24896
+ function Lever({ on, title, line, disabled, onToggle }) {
24897
+ return (0, import_jsx_runtime.jsxs)("button", {
24898
+ type: "button",
24899
+ className: on ? "lever lever-on" : "lever",
24900
+ "aria-pressed": on,
24901
+ disabled,
24902
+ onClick: onToggle,
24903
+ children: [(0, import_jsx_runtime.jsx)("span", { className: on ? "lever-box lever-box-on" : "lever-box" }), (0, import_jsx_runtime.jsxs)("span", {
24904
+ style: { textAlign: "left" },
24905
+ children: [(0, import_jsx_runtime.jsx)("span", {
24906
+ style: {
24907
+ display: "block",
24908
+ fontWeight: 600,
24909
+ color: "var(--text-strong)"
24910
+ },
24911
+ children: title
24912
+ }), (0, import_jsx_runtime.jsx)("span", {
24913
+ style: {
24914
+ display: "block",
24915
+ fontSize: 12.5,
24916
+ color: "var(--text-muted)"
24917
+ },
24918
+ children: line
24919
+ })]
24920
+ })]
24921
+ });
24922
+ }
24923
+ function AttachRow({ on, implied = false, sub = false, name, line, disabled, onToggle }) {
24924
+ return (0, import_jsx_runtime.jsxs)("button", {
24925
+ type: "button",
24926
+ className: `attach-row${on ? " on" : ""}${sub ? " sub" : ""}`,
24927
+ "aria-pressed": on,
24928
+ disabled: disabled || implied,
24929
+ onClick: onToggle,
24930
+ children: [
24931
+ (0, import_jsx_runtime.jsx)("span", { className: on ? "lever-box lever-box-on" : "lever-box" }),
24932
+ (0, import_jsx_runtime.jsx)("span", {
24933
+ style: {
24934
+ color: "var(--text-strong)",
24935
+ fontWeight: sub ? 400 : 600
24936
+ },
24937
+ "data-anon": "mark",
24938
+ children: name
24939
+ }),
24940
+ (0, import_jsx_runtime.jsx)("span", {
24941
+ className: "sub-t",
24942
+ children: line
24943
+ })
24944
+ ]
24945
+ });
24946
+ }
24947
+ //#endregion
24948
+ //#region src/screens/About.tsx
24949
+ var LICENCE_URL = "https://www.gnu.org/licenses/agpl-3.0.html";
24950
+ function About() {
24951
+ const [info, setInfo] = (0, import_react.useState)(null);
24952
+ const [failed, setFailed] = (0, import_react.useState)(false);
24953
+ (0, import_react.useEffect)(() => {
24954
+ let live = true;
24955
+ api.about().then((r) => {
24956
+ if (!live) return;
24957
+ if (isOk(r)) setInfo(r.value);
24958
+ else setFailed(true);
24959
+ });
24960
+ return () => {
24961
+ live = false;
24962
+ };
24963
+ }, []);
24964
+ if (failed) return (0, import_jsx_runtime.jsxs)("section", {
24965
+ className: "screen",
24966
+ children: [
24967
+ (0, import_jsx_runtime.jsx)(PageHeader, { title: "About" }),
24968
+ (0, import_jsx_runtime.jsx)("p", { children: "This deployment could not report which build it is running. That is a fault, not a configuration choice — the source offer below is incomplete without it." }),
24969
+ (0, import_jsx_runtime.jsxs)("p", { children: ["Source: ", (0, import_jsx_runtime.jsx)("a", {
24970
+ href: "https://github.com/CordilleraSarl/Clearotron",
24971
+ children: "github.com/CordilleraSarl/Clearotron"
24972
+ })] })
24973
+ ]
24974
+ });
24975
+ if (!info) return (0, import_jsx_runtime.jsxs)("section", {
24976
+ className: "screen",
24977
+ children: [(0, import_jsx_runtime.jsx)(PageHeader, { title: "About" }), (0, import_jsx_runtime.jsx)("p", { children: "Loading…" })]
24978
+ });
24979
+ const shortSha = info.commit ? info.commit.slice(0, 12) : null;
24980
+ return (0, import_jsx_runtime.jsxs)("section", {
24981
+ className: "screen",
24982
+ children: [
24983
+ (0, import_jsx_runtime.jsx)(PageHeader, { title: `About ${info.name}` }),
24984
+ (0, import_jsx_runtime.jsxs)("dl", { children: [
24985
+ (0, import_jsx_runtime.jsx)("dt", { children: "Product" }),
24986
+ (0, import_jsx_runtime.jsxs)("dd", { children: [info.name, info.version ? ` ${info.version}` : ""] }),
24987
+ (0, import_jsx_runtime.jsx)("dt", { children: "Build" }),
24988
+ (0, import_jsx_runtime.jsx)("dd", { children: shortSha ? (0, import_jsx_runtime.jsx)("code", {
24989
+ title: info.commit ?? void 0,
24990
+ children: shortSha
24991
+ }) : (0, import_jsx_runtime.jsx)("span", { children: "not reported by this deployment" }) }),
24992
+ (0, import_jsx_runtime.jsx)("dt", { children: "Source" }),
24993
+ (0, import_jsx_runtime.jsxs)("dd", { children: [(0, import_jsx_runtime.jsx)("a", {
24994
+ href: info.sourceUrl,
24995
+ rel: "noreferrer",
24996
+ children: info.sourceUrl
24997
+ }), !info.commit && (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [" ", (0, import_jsx_runtime.jsx)("strong", { children: "This links to the repository, not to the exact build you are using — this deployment could not report its commit." })] })] }),
24998
+ (0, import_jsx_runtime.jsx)("dt", { children: "Licence" }),
24999
+ (0, import_jsx_runtime.jsxs)("dd", { children: [
25000
+ info.license ?? "not reported",
25001
+ " — ",
25002
+ (0, import_jsx_runtime.jsx)("a", {
25003
+ href: LICENCE_URL,
25004
+ rel: "noreferrer",
25005
+ children: "full text"
25006
+ })
25007
+ ] }),
25008
+ (0, import_jsx_runtime.jsx)("dt", { children: "Copyright" }),
25009
+ (0, import_jsx_runtime.jsx)("dd", { children: info.copyright })
25010
+ ] }),
25011
+ (0, import_jsx_runtime.jsxs)("p", { children: [
25012
+ (0, import_jsx_runtime.jsx)("a", {
25013
+ href: `${info.sourceRepo}/blob/main/LICENSE`,
25014
+ rel: "noreferrer",
25015
+ children: "Licence"
25016
+ }),
25017
+ " · ",
25018
+ (0, import_jsx_runtime.jsx)("a", {
25019
+ href: `${info.sourceRepo}/blob/main/NOTICES.md`,
25020
+ rel: "noreferrer",
25021
+ children: "Notices"
25022
+ }),
25023
+ " · ",
25024
+ (0, import_jsx_runtime.jsx)("a", {
25025
+ href: `${info.sourceRepo}/blob/main/TRADEMARKS.md`,
25026
+ rel: "noreferrer",
25027
+ children: "Trademarks policy"
25028
+ }),
25029
+ " · ",
25030
+ (0, import_jsx_runtime.jsx)("a", {
25031
+ href: `${info.sourceRepo}/blob/main/CONTRIBUTING.md`,
25032
+ rel: "noreferrer",
25033
+ children: "Contributing"
25034
+ }),
25035
+ " · ",
25036
+ (0, import_jsx_runtime.jsx)("a", {
25037
+ href: `${info.sourceRepo}/blob/main/SECURITY.md`,
25038
+ rel: "noreferrer",
25039
+ children: "Security"
25040
+ }),
25041
+ " · ",
25042
+ (0, import_jsx_runtime.jsx)("a", {
24472
25043
  href: `${info.sourceRepo}/blob/main/CODE_OF_CONDUCT.md`,
24473
25044
  rel: "noreferrer",
24474
25045
  children: "Code of conduct"
@@ -24506,41 +25077,27 @@ function About() {
24506
25077
  function NotYet({ title, phase, what }) {
24507
25078
  return (0, import_jsx_runtime.jsxs)("div", {
24508
25079
  className: "screen",
24509
- children: [
24510
- (0, import_jsx_runtime.jsx)("div", {
24511
- className: "eyebrow",
24512
- children: title
24513
- }),
24514
- (0, import_jsx_runtime.jsx)("h1", {
25080
+ children: [(0, import_jsx_runtime.jsx)(PageHeader, { title }), (0, import_jsx_runtime.jsxs)("div", {
25081
+ className: "notice quiet",
25082
+ children: [(0, import_jsx_runtime.jsx)("p", {
24515
25083
  style: {
24516
- fontSize: 25,
24517
- margin: "4px 0 10px",
24518
- color: "var(--text-strong)"
25084
+ margin: 0,
25085
+ color: "var(--text-muted)"
24519
25086
  },
24520
- children: title
24521
- }),
24522
- (0, import_jsx_runtime.jsxs)("div", {
24523
- className: "notice quiet",
24524
- children: [(0, import_jsx_runtime.jsx)("p", {
24525
- style: {
24526
- margin: 0,
24527
- color: "var(--text-muted)"
24528
- },
24529
- children: what
24530
- }), (0, import_jsx_runtime.jsxs)("p", {
24531
- style: {
24532
- margin: "8px 0 0",
24533
- fontSize: 13,
24534
- color: "var(--text-faint)"
24535
- },
24536
- children: [
24537
- "Being built — ",
24538
- phase,
24539
- "."
24540
- ]
24541
- })]
24542
- })
24543
- ]
25087
+ children: what
25088
+ }), (0, import_jsx_runtime.jsxs)("p", {
25089
+ style: {
25090
+ margin: "8px 0 0",
25091
+ fontSize: 13,
25092
+ color: "var(--text-faint)"
25093
+ },
25094
+ children: [
25095
+ "Being built — ",
25096
+ phase,
25097
+ "."
25098
+ ]
25099
+ })]
25100
+ })]
24544
25101
  });
24545
25102
  }
24546
25103
  var ownerKey = (ctx) => `${ctx.owner ?? "*"}#${ctx.visit}`;
@@ -24570,6 +25127,7 @@ function screen(id, ctx) {
24570
25127
  case "about": return (0, import_jsx_runtime.jsx)(About, {});
24571
25128
  case "people": return (0, import_jsx_runtime.jsx)(PeopleAccess, { ctx });
24572
25129
  case "people.add": return (0, import_jsx_runtime.jsx)(GiveAccess, { ctx });
25130
+ case "people.modify": return (0, import_jsx_runtime.jsx)(ModifyAccess, { ctx });
24573
25131
  case "admin":
24574
25132
  case "admin.config": return (0, import_jsx_runtime.jsx)(GlobalConfig, { ctx });
24575
25133
  default: return (0, import_jsx_runtime.jsx)(NotYet, {