clearotron 0.3.1-beta.2 → 0.3.1-beta.3

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.
@@ -11802,7 +11802,10 @@ function decodePerson(r) {
11802
11802
  manage: asRecord(r["permissions"])["manage"] === true
11803
11803
  },
11804
11804
  access: decodeAccess(r),
11805
- dangling: asArray(r["dangling"]).filter((s) => typeof s === "string")
11805
+ dangling: asArray(r["dangling"]).filter((s) => typeof s === "string"),
11806
+ listed: r["listed"] !== false,
11807
+ covered: r["covered"] === true,
11808
+ keys: typeof r["keys"] === "number" && r["keys"] > 0 ? r["keys"] : 0
11806
11809
  };
11807
11810
  }
11808
11811
  var api = {
@@ -12051,6 +12054,7 @@ var api = {
12051
12054
  keyUrl: asString(b["keyUrl"]),
12052
12055
  email: asString(b["email"]),
12053
12056
  enabled: b["enabled"] === true,
12057
+ aiConnected: typeof b["aiConnected"] === "boolean" ? b["aiConnected"] : null,
12054
12058
  stdio: (() => {
12055
12059
  const r = b["stdio"];
12056
12060
  if (!r || typeof r !== "object") return null;
@@ -12218,7 +12222,8 @@ var api = {
12218
12222
  } : null;
12219
12223
  })(),
12220
12224
  canAdd: b["canAdd"] === true,
12221
- localSignIn: b["localSignIn"] === true
12225
+ localSignIn: b["localSignIn"] === true,
12226
+ keysRevocable: b["keysRevocable"] === true
12222
12227
  })),
12223
12228
  addPerson: (body) => call("/portal/admin/people", (b) => ({
12224
12229
  person: decodePerson(asRecord(b["person"])),
@@ -12227,6 +12232,32 @@ var api = {
12227
12232
  method: "POST",
12228
12233
  body: JSON.stringify(body)
12229
12234
  }),
12235
+ changePerson: (body) => call("/portal/admin/people/change", (b) => ({
12236
+ person: decodePerson(asRecord(b["person"])),
12237
+ switchesApplied: b["switchesApplied"] === true,
12238
+ added: typeof b["added"] === "number" ? b["added"] : 0,
12239
+ removed: typeof b["removed"] === "number" ? b["removed"] : 0
12240
+ }), {
12241
+ method: "POST",
12242
+ body: JSON.stringify(body)
12243
+ }),
12244
+ removePerson: (body) => call("/portal/admin/people/remove", (b) => {
12245
+ const k = asRecord(b["keys"]);
12246
+ return {
12247
+ removed: b["removed"] === "install" ? "install" : "organisations",
12248
+ organisations: asStrings(b["organisations"]),
12249
+ keys: {
12250
+ checked: k["checked"] === true,
12251
+ revoked: typeof k["revoked"] === "number" ? k["revoked"] : 0,
12252
+ lateArm: k["lateArm"] === true,
12253
+ failed: k["failed"] === true,
12254
+ note: asString(k["note"])
12255
+ }
12256
+ };
12257
+ }, {
12258
+ method: "POST",
12259
+ body: JSON.stringify(body)
12260
+ }),
12230
12261
  adminObserved: () => call("/portal/admin/observed", (b) => ({
12231
12262
  available: b["available"] === true,
12232
12263
  truncated: b["truncated"] === true,
@@ -12302,6 +12333,15 @@ var NAV = [
12302
12333
  hidden: true,
12303
12334
  scope: "account"
12304
12335
  },
12336
+ {
12337
+ id: "people.modify",
12338
+ label: "Modify access",
12339
+ path: "/portal/people/modify",
12340
+ icon: "users",
12341
+ needs: "manage",
12342
+ hidden: true,
12343
+ scope: "account"
12344
+ },
12305
12345
  {
12306
12346
  id: "new",
12307
12347
  label: "New clearance",
@@ -12454,6 +12494,7 @@ function decode(segment) {
12454
12494
  }
12455
12495
  var PEOPLE = NAV.find((e) => e.id === "people");
12456
12496
  var ADD_PERSON = NAV.find((e) => e.id === "people.add");
12497
+ var MODIFY_PERSON = NAV.find((e) => e.id === "people.modify");
12457
12498
  //#endregion
12458
12499
  //#region ../node_modules/react/cjs/react-jsx-runtime.production.js
12459
12500
  var require_react_jsx_runtime_production = __commonJSMin(((exports) => {
@@ -12848,6 +12889,26 @@ function seesEverything(who) {
12848
12889
  return who.allAccounts === true;
12849
12890
  }
12850
12891
  //#endregion
12892
+ //#region src/components/PageHeader.tsx
12893
+ function PageHeader({ title, lede, actions }) {
12894
+ return (0, import_jsx_runtime.jsxs)("header", {
12895
+ className: "page-header",
12896
+ children: [(0, import_jsx_runtime.jsxs)("div", {
12897
+ className: "page-header-text",
12898
+ children: [(0, import_jsx_runtime.jsx)("h1", {
12899
+ className: "page-title",
12900
+ children: title
12901
+ }), lede ? (0, import_jsx_runtime.jsx)("p", {
12902
+ className: "page-lede",
12903
+ children: lede
12904
+ }) : null]
12905
+ }), actions ? (0, import_jsx_runtime.jsx)("div", {
12906
+ className: "page-header-actions",
12907
+ children: actions
12908
+ }) : null]
12909
+ });
12910
+ }
12911
+ //#endregion
12851
12912
  //#region src/shell/CompanyPicker.tsx
12852
12913
  function CompanyCard({ row, onPick }) {
12853
12914
  return (0, import_jsx_runtime.jsxs)("button", {
@@ -12907,51 +12968,26 @@ function CompanyCard({ row, onPick }) {
12907
12968
  })]
12908
12969
  });
12909
12970
  }
12910
- function CompanyPicker({ eyebrow, heading, line, keys, orgOf, organisations, companyName, factsFor, onPick, onAdd }) {
12971
+ function CompanyPicker({ heading, line, keys, orgOf, organisations, companyName, factsFor, onPick, onAdd }) {
12911
12972
  const { headings, groups } = pickerGroups(keys, orgOf, organisations, companyName, factsFor);
12912
- return (0, import_jsx_runtime.jsxs)("div", {
12973
+ return (0, import_jsx_runtime.jsx)("div", {
12913
12974
  className: "screen",
12914
- children: [(0, import_jsx_runtime.jsx)("div", {
12915
- className: "eyebrow",
12916
- children: eyebrow
12917
- }), (0, import_jsx_runtime.jsxs)("div", {
12975
+ children: (0, import_jsx_runtime.jsxs)("div", {
12918
12976
  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", {
12977
+ children: [(0, import_jsx_runtime.jsx)(PageHeader, {
12978
+ title: heading,
12979
+ lede: line,
12980
+ actions: onAdd ? (0, import_jsx_runtime.jsx)("button", {
12944
12981
  type: "button",
12945
12982
  className: "start-pill",
12946
12983
  onClick: onAdd,
12947
12984
  style: {
12948
- flex: "none",
12949
12985
  borderColor: "var(--accent-ink)",
12950
12986
  color: "var(--text-accent)",
12951
12987
  fontWeight: 700
12952
12988
  },
12953
12989
  children: "+ New company"
12954
- }) : null]
12990
+ }) : null
12955
12991
  }), groups.map((g, i) => (0, import_jsx_runtime.jsxs)("div", { children: [headings && g.org ? (0, import_jsx_runtime.jsx)("div", {
12956
12992
  className: "eyebrow",
12957
12993
  style: { margin: i === 0 ? "0 0 8px" : "16px 0 8px" },
@@ -12969,12 +13005,11 @@ function CompanyPicker({ eyebrow, heading, line, keys, orgOf, organisations, com
12969
13005
  onPick: () => onPick(r.key)
12970
13006
  }, r.key))
12971
13007
  })] }, g.org?.key ?? ""))]
12972
- })]
13008
+ })
12973
13009
  });
12974
13010
  }
12975
- function CompanyGate({ ctx, eyebrow, heading, line }) {
13011
+ function CompanyGate({ ctx, heading, line }) {
12976
13012
  return (0, import_jsx_runtime.jsx)(CompanyPicker, {
12977
- eyebrow,
12978
13013
  heading,
12979
13014
  line,
12980
13015
  keys: ctx.ownerKeys,
@@ -13859,23 +13894,18 @@ function Home({ ctx }) {
13859
13894
  return (0, import_jsx_runtime.jsxs)("div", {
13860
13895
  className: "screen home2",
13861
13896
  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"
13897
+ (0, import_jsx_runtime.jsx)(PageHeader, {
13898
+ title: "Home",
13899
+ actions: (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [(0, import_jsx_runtime.jsxs)("button", {
13900
+ type: "button",
13901
+ className: "btn-ghost home2-all",
13902
+ onClick: () => ctx.go("/portal/clearances"),
13903
+ children: [(0, import_jsx_runtime.jsx)(Icon, { name: "layers" }), (0, import_jsx_runtime.jsx)("span", { children: "All Clearances" })]
13904
+ }), canRun(ctx.me) ? (0, import_jsx_runtime.jsx)(NewClearanceButton, { onNew: () => ctx.go("/portal/new") }) : null] })
13873
13905
  }),
13874
13906
  (0, import_jsx_runtime.jsx)(InFlightBand, {
13875
13907
  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")
13908
+ note: cards.length + queue.length === 0 ? `Nothing running right now.${canRun(ctx.me) ? " Start one with New clearance." : ""}` : slotNote(null, ctx.me.concurrentRuns)
13879
13909
  }),
13880
13910
  (0, import_jsx_runtime.jsx)("div", {
13881
13911
  style: { margin: "0 0 18px" },
@@ -13950,7 +13980,15 @@ function Home({ ctx }) {
13950
13980
  ]
13951
13981
  });
13952
13982
  }
13953
- function InFlightBand({ count, note, onNew, onAll }) {
13983
+ function NewClearanceButton({ onNew }) {
13984
+ return (0, import_jsx_runtime.jsxs)("button", {
13985
+ type: "button",
13986
+ className: "home2-new",
13987
+ onClick: onNew,
13988
+ children: [(0, import_jsx_runtime.jsx)(Icon, { name: "plus-circle" }), "New clearance"]
13989
+ });
13990
+ }
13991
+ function InFlightBand({ count, note }) {
13954
13992
  return (0, import_jsx_runtime.jsxs)("div", {
13955
13993
  className: "home2-band",
13956
13994
  children: [
@@ -13966,20 +14004,7 @@ function InFlightBand({ count, note, onNew, onAll }) {
13966
14004
  (0, import_jsx_runtime.jsx)("span", {
13967
14005
  className: "home2-band-note",
13968
14006
  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
14007
+ })
13983
14008
  ]
13984
14009
  });
13985
14010
  }
@@ -14774,24 +14799,12 @@ function Clearances({ ctx }) {
14774
14799
  return (0, import_jsx_runtime.jsxs)("div", {
14775
14800
  className: "screen",
14776
14801
  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 })
14802
+ (0, import_jsx_runtime.jsx)(PageHeader, {
14803
+ title: "All Clearances",
14804
+ 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", {
14805
+ "data-anon": "mark",
14806
+ children: ctx.ownerName(ownerFilter)
14807
+ }), " · "] }) : null, (0, import_jsx_runtime.jsx)(AllowanceLine, { account })] })
14795
14808
  }),
14796
14809
  (0, import_jsx_runtime.jsx)("div", {
14797
14810
  className: "controls",
@@ -15784,16 +15797,48 @@ function frameCommand(command, value) {
15784
15797
  }
15785
15798
  //#endregion
15786
15799
  //#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) {
15800
+ var MONTHS = [
15801
+ "",
15802
+ "January",
15803
+ "February",
15804
+ "March",
15805
+ "April",
15806
+ "May",
15807
+ "June",
15808
+ "July",
15809
+ "August",
15810
+ "September",
15811
+ "October",
15812
+ "November",
15813
+ "December"
15814
+ ];
15815
+ function readableDate(iso) {
15816
+ const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(String(iso ?? "").trim());
15817
+ if (!m) return null;
15818
+ const month = MONTHS[Number(m[2])];
15819
+ const day = Number(m[3]);
15820
+ if (!month || !(day >= 1 && day <= 31)) return null;
15821
+ return `${day} ${month}`;
15822
+ }
15823
+ function askAiPrompt(mark, date, kind = "clearance") {
15824
+ const name = String(mark ?? "").trim() || "this mark";
15825
+ const noun = kind === "knockout-batch" ? "knockout search" : "clearance";
15826
+ const when = readableDate(date);
15827
+ return when ? `Brief me on the ${name} ${noun} from ${when}.` : `Brief me on the ${name} ${noun}.`;
15828
+ }
15829
+ var ASSISTANTS = [{
15830
+ label: "Ask Claude",
15831
+ href: (q) => `https://claude.ai/new?q=${encodeURIComponent(q)}`
15832
+ }, {
15833
+ label: "Ask ChatGPT",
15834
+ href: (q) => `https://chatgpt.com/?q=${encodeURIComponent(q)}`
15835
+ }];
15836
+ var AI_SETUP_PATH = "/portal/ai";
15837
+ function askAiOffer(run, access) {
15792
15838
  return {
15793
- question: askAiPrompt(runId, mark),
15794
- address: access?.enabled ? access.url : null,
15795
- instructionsPath: "/portal/ai",
15796
- stdio: access?.stdio ?? null
15839
+ drawn: !!access && (!!(access.enabled && access.url) || !!access.stdio),
15840
+ question: askAiPrompt(run.markName, run.date, run.kind),
15841
+ connected: access?.aiConnected === true
15797
15842
  };
15798
15843
  }
15799
15844
  //#endregion
@@ -15851,12 +15896,12 @@ function useReportFrame() {
15851
15896
  clearFailed: () => setFailed(null)
15852
15897
  };
15853
15898
  }
15854
- function AskAiMenu({ runId, mark, ctx }) {
15899
+ function AskAiMenu({ run, ctx }) {
15855
15900
  const [open, setOpen] = (0, import_react.useState)(false);
15856
- const [copied, setCopied] = (0, import_react.useState)(null);
15901
+ const [askAnyway, setAskAnyway] = (0, import_react.useState)(false);
15857
15902
  const box = (0, import_react.useRef)(null);
15858
15903
  const { result } = useLoad(() => api.mcpAccess(), []);
15859
- const offer = askAiOffer(runId, mark, result?.kind === "ok" ? result.value : null);
15904
+ const offer = askAiOffer(run, result?.kind === "ok" ? result.value : null);
15860
15905
  (0, import_react.useEffect)(() => {
15861
15906
  if (!open) return;
15862
15907
  function onDown(e) {
@@ -15872,115 +15917,151 @@ function AskAiMenu({ runId, mark, ctx }) {
15872
15917
  document.removeEventListener("keydown", onKey);
15873
15918
  };
15874
15919
  }, [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
- })();
15920
+ if (!offer.drawn) return null;
15921
+ const showMenu = offer.connected || askAnyway;
15922
+ const ask = (href) => {
15923
+ setOpen(false);
15924
+ window.open(href, "_blank", "noopener,noreferrer");
15884
15925
  };
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",
15926
+ return (0, import_jsx_runtime.jsxs)("div", {
15927
+ ref: box,
15928
+ style: { position: "relative" },
15929
+ "data-ask-ai": showMenu ? "menu" : "connect",
15930
+ children: [
15931
+ (0, import_jsx_runtime.jsxs)("button", {
15932
+ type: "button",
15933
+ className: "nav-item",
15934
+ "aria-haspopup": showMenu ? "menu" : "dialog",
15935
+ "aria-expanded": open,
15902
15936
  style: {
15903
- fontSize: 12,
15904
- color: "var(--text-strong)",
15905
- wordBreak: "break-all",
15906
- flex: 1
15937
+ width: "auto",
15938
+ margin: 0,
15939
+ padding: "6px 11px",
15940
+ border: "1px solid var(--border-hairline)"
15907
15941
  },
15908
- children: value
15909
- }), (0, import_jsx_runtime.jsx)("button", {
15910
- type: "button",
15911
- className: "link-btn",
15942
+ onClick: () => setOpen((v) => !v),
15943
+ children: [(0, import_jsx_runtime.jsx)(Icon, {
15944
+ name: "sparkles",
15945
+ size: 14
15946
+ }), (0, import_jsx_runtime.jsx)("span", { children: "Ask AI" })]
15947
+ }),
15948
+ open && showMenu ? (0, import_jsx_runtime.jsxs)("div", {
15949
+ className: "float",
15950
+ role: "menu",
15912
15951
  style: {
15913
- fontSize: 12,
15914
- flex: "none"
15952
+ position: "absolute",
15953
+ right: 0,
15954
+ top: 38,
15955
+ width: 236,
15956
+ padding: 6,
15957
+ zIndex: 50
15915
15958
  },
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]
15959
+ children: [
15960
+ ASSISTANTS.map((a) => (0, import_jsx_runtime.jsxs)("button", {
15961
+ type: "button",
15962
+ role: "menuitem",
15963
+ className: "nav-item",
15964
+ style: {
15965
+ fontSize: 13,
15966
+ padding: "7px 10px",
15967
+ margin: 0,
15968
+ justifyContent: "space-between"
15969
+ },
15970
+ onClick: () => ask(a.href(offer.question)),
15971
+ children: [(0, import_jsx_runtime.jsx)("span", { children: a.label }), (0, import_jsx_runtime.jsx)(Icon, {
15972
+ name: "arrow-right",
15973
+ size: 14
15974
+ })]
15975
+ }, a.label)),
15976
+ (0, import_jsx_runtime.jsx)("div", { style: {
15977
+ height: 1,
15978
+ background: "var(--border-hairline)",
15979
+ margin: "5px 8px"
15980
+ } }),
15981
+ (0, import_jsx_runtime.jsx)("button", {
15982
+ type: "button",
15983
+ role: "menuitem",
15984
+ className: "nav-item",
15985
+ style: {
15986
+ fontSize: 13,
15987
+ padding: "7px 10px",
15988
+ margin: 0,
15989
+ justifyContent: "space-between",
15990
+ color: "var(--text-muted)"
15991
+ },
15992
+ onClick: () => {
15993
+ setOpen(false);
15994
+ ctx.go(AI_SETUP_PATH);
15995
+ },
15996
+ children: (0, import_jsx_runtime.jsx)("span", { children: "Connect another AI" })
15997
+ })
15998
+ ]
15999
+ }) : null,
16000
+ open && !showMenu ? (0, import_jsx_runtime.jsxs)("div", {
16001
+ className: "float",
16002
+ role: "dialog",
16003
+ "aria-label": "Connect your AI first",
16004
+ style: {
16005
+ position: "absolute",
16006
+ right: 0,
16007
+ top: 38,
16008
+ width: 276,
16009
+ padding: 12,
16010
+ zIndex: 50,
16011
+ display: "grid",
16012
+ gap: 9
16013
+ },
16014
+ children: [
16015
+ (0, import_jsx_runtime.jsx)("div", {
16016
+ style: {
16017
+ fontSize: 13.5,
16018
+ fontWeight: 700,
16019
+ color: "var(--text-strong)"
16020
+ },
16021
+ children: "Connect your AI first"
16022
+ }),
16023
+ (0, import_jsx_runtime.jsx)("p", {
16024
+ style: {
16025
+ margin: 0,
16026
+ fontSize: 12.5,
16027
+ color: "var(--text-muted)",
16028
+ lineHeight: 1.5
16029
+ },
16030
+ children: "Connect Claude or ChatGPT once. After that, this button opens it with a question about this report typed in."
16031
+ }),
16032
+ (0, import_jsx_runtime.jsxs)("button", {
16033
+ type: "button",
16034
+ className: "btn-primary",
16035
+ style: {
16036
+ fontSize: 13,
16037
+ padding: "6px 11px",
16038
+ justifySelf: "start",
16039
+ display: "inline-flex",
16040
+ alignItems: "center",
16041
+ gap: 6
16042
+ },
16043
+ onClick: () => {
16044
+ setOpen(false);
16045
+ ctx.go(AI_SETUP_PATH);
16046
+ },
16047
+ children: ["Set it up ", (0, import_jsx_runtime.jsx)(Icon, {
16048
+ name: "arrow-right",
16049
+ size: 14
16050
+ })]
16051
+ }),
16052
+ (0, import_jsx_runtime.jsx)("button", {
16053
+ type: "button",
16054
+ className: "link-btn",
16055
+ style: {
16056
+ fontSize: 12.5,
16057
+ justifySelf: "start"
16058
+ },
16059
+ onClick: () => setAskAnyway(true),
16060
+ children: "Already connected? Ask anyway"
16061
+ })
16062
+ ]
16063
+ }) : null
16064
+ ]
15984
16065
  });
15985
16066
  }
15986
16067
  function ExportMenu({ send, runId, offered }) {
@@ -16258,8 +16339,7 @@ function Result({ ctx, runId, markSlug }) {
16258
16339
  }),
16259
16340
  (0, import_jsx_runtime.jsx)("span", { style: { flex: 1 } }),
16260
16341
  (0, import_jsx_runtime.jsx)(AskAiMenu, {
16261
- runId: run.runId,
16262
- mark: run.markName,
16342
+ run,
16263
16343
  ctx
16264
16344
  }),
16265
16345
  doc ? (0, import_jsx_runtime.jsx)(ExportMenu, {
@@ -17874,7 +17954,6 @@ function NewClearance({ ctx }) {
17874
17954
  });
17875
17955
  if (needsOwner || searches?.kind === "pickAccount") return (0, import_jsx_runtime.jsx)(CompanyGate, {
17876
17956
  ctx,
17877
- eyebrow: "New clearance",
17878
17957
  heading: "New clearance",
17879
17958
  line: "Pick a company to run this clearance on."
17880
17959
  });
@@ -17902,19 +17981,8 @@ function NewClearance({ ctx }) {
17902
17981
  return (0, import_jsx_runtime.jsxs)("div", {
17903
17982
  className: "screen",
17904
17983
  children: [
17905
- (0, import_jsx_runtime.jsx)("div", {
17906
- className: "eyebrow",
17907
- children: editingSlug ? "Custom search" : "New clearance"
17908
- }),
17909
17984
  (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
- }),
17985
+ (0, import_jsx_runtime.jsx)(PageHeader, { title: editingSlug ? "Edit a custom search" : "New clearance" }),
17918
17986
  editingSlug ? editingRes && editingRes.kind !== "ok" ? (0, import_jsx_runtime.jsxs)("div", {
17919
17987
  className: "notice",
17920
17988
  style: { borderColor: "var(--tone-high)" },
@@ -18565,7 +18633,7 @@ function NewClearance({ ctx }) {
18565
18633
  nativeControl === "toggle" ? (0, import_jsx_runtime.jsx)("div", {
18566
18634
  className: "sunken-block",
18567
18635
  style: { marginTop: 12 },
18568
- children: (0, import_jsx_runtime.jsx)(Lever$1, {
18636
+ children: (0, import_jsx_runtime.jsx)(Lever$2, {
18569
18637
  label: "Native-language investigation",
18570
18638
  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
18639
  on: draft.pick.nativeLanguage,
@@ -19277,7 +19345,7 @@ function SetupCommand({ route }) {
19277
19345
  "from a copy of the source."
19278
19346
  ] });
19279
19347
  }
19280
- function Lever$1({ label, hint, on, onToggle, coming }) {
19348
+ function Lever$2({ label, hint, on, onToggle, coming }) {
19281
19349
  return (0, import_jsx_runtime.jsxs)("button", {
19282
19350
  type: "button",
19283
19351
  onClick: coming ? void 0 : onToggle,
@@ -19834,51 +19902,37 @@ function OptionsUnavailable({ kind, onRetry }) {
19834
19902
  function Submitted({ go, onAnother }) {
19835
19903
  return (0, import_jsx_runtime.jsxs)("div", {
19836
19904
  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
- ]
19905
+ children: [(0, import_jsx_runtime.jsx)(PageHeader, { title: "Clearance started" }), (0, import_jsx_runtime.jsxs)("div", {
19906
+ className: "notice prose",
19907
+ children: [
19908
+ (0, import_jsx_runtime.jsx)("b", { children: "It is in the queue" }),
19909
+ (0, import_jsx_runtime.jsx)("p", {
19910
+ style: {
19911
+ margin: "6px 0 0",
19912
+ color: "var(--text-muted)"
19913
+ },
19914
+ children: "It will appear in Clearances, and the entry there tracks it the whole way — including if it stops early."
19915
+ }),
19916
+ (0, import_jsx_runtime.jsxs)("div", {
19917
+ style: {
19918
+ marginTop: 14,
19919
+ display: "flex",
19920
+ gap: 10
19921
+ },
19922
+ children: [(0, import_jsx_runtime.jsx)("button", {
19923
+ type: "button",
19924
+ className: "btn-primary",
19925
+ onClick: () => go("/portal/clearances"),
19926
+ children: "View in Clearances"
19927
+ }), (0, import_jsx_runtime.jsx)("button", {
19928
+ type: "button",
19929
+ className: "btn-ghost",
19930
+ onClick: onAnother,
19931
+ children: "Start another"
19932
+ })]
19933
+ })
19934
+ ]
19935
+ })]
19882
19936
  });
19883
19937
  }
19884
19938
  function CreatedStrip() {
@@ -20515,29 +20569,14 @@ function NewCompany({ ctx }) {
20515
20569
  const rejected = result && !isOk(result) && result.kind === "reject" ? result.errors : null;
20516
20570
  const takenBy = result && !isOk(result) && result.kind === "reject" && result.detail?.code === "key_exists" ? result.detail.key : null;
20517
20571
  const otherFailure = result && !isOk(result) && result.kind !== "reject" ? result : null;
20518
- return (0, import_jsx_runtime.jsxs)("div", {
20572
+ return (0, import_jsx_runtime.jsx)("div", {
20519
20573
  className: "screen",
20520
- children: [(0, import_jsx_runtime.jsx)("div", {
20521
- className: "eyebrow",
20522
- children: "Company"
20523
- }), (0, import_jsx_runtime.jsxs)("div", {
20574
+ children: (0, import_jsx_runtime.jsxs)("div", {
20524
20575
  className: "measure",
20525
20576
  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."
20577
+ (0, import_jsx_runtime.jsx)(PageHeader, {
20578
+ title: "New company",
20579
+ lede: "The business whose names you are checking. Only the name is needed; everything else has a default you can change later."
20541
20580
  }),
20542
20581
  orgsHeld.length > 1 ? (0, import_jsx_runtime.jsxs)("div", {
20543
20582
  style: { marginBottom: 8 },
@@ -20684,7 +20723,7 @@ function NewCompany({ ctx }) {
20684
20723
  }) : null]
20685
20724
  })
20686
20725
  ]
20687
- })]
20726
+ })
20688
20727
  });
20689
20728
  }
20690
20729
  function KeyLine({ value, onChange }) {
@@ -21140,23 +21179,12 @@ function Empty({ onNew }) {
21140
21179
  function PickCompany({ ctx }) {
21141
21180
  return (0, import_jsx_runtime.jsx)(CompanyGate, {
21142
21181
  ctx,
21143
- eyebrow: "Company",
21144
21182
  heading: "Custom searches",
21145
21183
  line: "Pick a company to see its custom searches."
21146
21184
  });
21147
21185
  }
21148
21186
  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
- })] });
21187
+ return (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: (0, import_jsx_runtime.jsx)(PageHeader, { title: "Custom searches" }) });
21160
21188
  }
21161
21189
  //#endregion
21162
21190
  //#region src/screens/Preferences.tsx
@@ -21172,222 +21200,203 @@ function Preferences({ ctx }) {
21172
21200
  };
21173
21201
  return (0, import_jsx_runtime.jsxs)("div", {
21174
21202
  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
- },
21203
+ children: [(0, import_jsx_runtime.jsx)(PageHeader, {
21204
+ title: "Your preferences",
21205
+ lede: "Who you are signed in as, and how the portal looks on this computer."
21206
+ }), (0, import_jsx_runtime.jsxs)("div", {
21207
+ className: "measure",
21208
+ style: { "--screen-measure": "720px" },
21209
+ children: [
21210
+ (0, import_jsx_runtime.jsxs)(Section, {
21211
+ title: "Your sign-in",
21212
+ hint: `Held by ${operatorName(ctx.me.brand)}. Nothing here can be changed from this page.`,
21213
+ children: [
21214
+ (0, import_jsx_runtime.jsxs)("dl", {
21215
+ style: {
21216
+ margin: 0,
21217
+ display: "grid",
21218
+ gridTemplateColumns: "auto 1fr",
21219
+ gap: "8px 18px",
21220
+ fontSize: 14
21221
+ },
21222
+ children: [
21223
+ (0, import_jsx_runtime.jsx)("dt", {
21224
+ style: { color: "var(--text-muted)" },
21225
+ children: "Address"
21226
+ }),
21227
+ (0, import_jsx_runtime.jsx)("dd", {
21228
+ style: {
21229
+ margin: 0,
21230
+ color: "var(--text-strong)",
21231
+ wordBreak: "break-all"
21232
+ },
21233
+ "data-anon": "mark",
21234
+ children: ctx.me.email || "—"
21235
+ }),
21236
+ (0, import_jsx_runtime.jsx)("dt", {
21237
+ style: { color: "var(--text-muted)" },
21238
+ children: "Permissions"
21239
+ }),
21240
+ (0, import_jsx_runtime.jsx)("dd", {
21241
+ style: {
21242
+ margin: 0,
21243
+ color: "var(--text-strong)"
21244
+ },
21245
+ children: permissionsPhrase(ctx.me.permissions)
21246
+ }),
21247
+ (0, import_jsx_runtime.jsx)("dt", {
21248
+ style: { color: "var(--text-muted)" },
21249
+ children: "Access to"
21250
+ }),
21251
+ (0, import_jsx_runtime.jsx)("dd", {
21252
+ style: {
21253
+ margin: 0,
21254
+ color: "var(--text-strong)"
21255
+ },
21256
+ children: ctx.me.access.length ? (0, import_jsx_runtime.jsx)("span", {
21223
21257
  "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", {
21258
+ children: accessChips(ctx.me.access).map((c) => c.label).join(", ")
21259
+ }) : (0, import_jsx_runtime.jsx)("span", {
21238
21260
  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
- })
21261
+ children: "None recorded against this address."
21253
21262
  })
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", {
21263
+ })
21264
+ ]
21265
+ }),
21266
+ (0, import_jsx_runtime.jsxs)("p", {
21301
21267
  style: {
21302
- margin: "12px 0 0",
21268
+ margin: "14px 0 0",
21303
21269
  fontSize: 13,
21304
21270
  color: "var(--text-muted)"
21305
21271
  },
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."
21272
+ children: [
21273
+ "To change the address, the role or the companies on it, ask ",
21274
+ operatorName(ctx.me.brand),
21275
+ " — enrolment is done for you, not from this page."
21276
+ ]
21277
+ }),
21278
+ (0, import_jsx_runtime.jsx)("a", {
21279
+ className: "pill",
21280
+ href: "/portal/sign-out",
21281
+ style: {
21282
+ display: "inline-block",
21283
+ marginTop: 12,
21284
+ textDecoration: "none"
21285
+ },
21286
+ children: "Log out"
21287
+ })
21288
+ ]
21289
+ }),
21290
+ (0, import_jsx_runtime.jsxs)(Section, {
21291
+ title: "Appearance",
21292
+ hint: "Applies straight away, and is remembered in this browser.",
21293
+ children: [(0, import_jsx_runtime.jsxs)("div", {
21294
+ style: {
21295
+ display: "flex",
21296
+ gap: 10,
21297
+ flexWrap: "wrap"
21298
+ },
21299
+ children: [(0, import_jsx_runtime.jsx)(ThemeOption, {
21300
+ label: "Light",
21301
+ value: "light",
21302
+ current: theme,
21303
+ onPick: applyTheme
21304
+ }), (0, import_jsx_runtime.jsx)(ThemeOption, {
21305
+ label: "Dark",
21306
+ value: "dark",
21307
+ current: theme,
21308
+ onPick: applyTheme
21307
21309
  })]
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", {
21310
+ }), (0, import_jsx_runtime.jsx)("p", {
21311
+ style: {
21312
+ margin: "12px 0 0",
21313
+ fontSize: 13,
21314
+ color: "var(--text-muted)"
21315
+ },
21316
+ children: "The same choice is on the top bar, under the circle icon — this page and that button set one and the same setting."
21317
+ })]
21318
+ }),
21319
+ (0, import_jsx_runtime.jsxs)(Section, {
21320
+ title: "Blurring names while you share a screen",
21321
+ hint: "There is one control for this, and it is on the top bar.",
21322
+ children: [
21323
+ (0, import_jsx_runtime.jsxs)("div", {
21324
+ style: {
21325
+ display: "flex",
21326
+ alignItems: "center",
21327
+ gap: 12
21328
+ },
21329
+ children: [(0, import_jsx_runtime.jsxs)("span", {
21330
+ className: "pill",
21314
21331
  style: {
21315
- display: "flex",
21332
+ display: "inline-flex",
21316
21333
  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
21334
+ gap: 7,
21335
+ padding: "4px 11px"
21344
21336
  },
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",
21337
+ children: [(0, import_jsx_runtime.jsx)(Icon, {
21338
+ name: "eye",
21339
+ size: 15
21340
+ }), "Top bar"]
21341
+ }), (0, import_jsx_runtime.jsx)("span", {
21349
21342
  style: {
21350
- marginTop: 14,
21351
- borderLeftColor: "var(--tone-medium)"
21343
+ fontSize: 13.5,
21344
+ color: "var(--text-muted)"
21352
21345
  },
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", {
21346
+ children: "The eye button, to the left of your initials."
21347
+ })]
21348
+ }),
21349
+ (0, import_jsx_runtime.jsx)("p", {
21350
+ style: {
21351
+ margin: "12px 0 0",
21352
+ color: "var(--text-muted)",
21353
+ fontSize: 13.5
21354
+ },
21355
+ 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."
21356
+ }),
21357
+ (0, import_jsx_runtime.jsxs)("div", {
21358
+ className: "notice",
21359
+ style: {
21360
+ marginTop: 14,
21361
+ borderLeftColor: "var(--tone-medium)"
21362
+ },
21363
+ children: [(0, import_jsx_runtime.jsx)("b", {
21364
+ style: { color: "var(--text-strong)" },
21365
+ children: "An open report blurs completely"
21366
+ }), (0, import_jsx_runtime.jsx)("p", {
21365
21367
  style: {
21366
- margin: "10px 0 0",
21367
- color: "var(--text-muted)",
21368
- fontSize: 13.5
21368
+ margin: "6px 0 0",
21369
+ color: "var(--text-muted)"
21369
21370
  },
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", {
21371
+ 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."
21372
+ })]
21373
+ }),
21374
+ (0, import_jsx_runtime.jsx)("p", {
21381
21375
  style: {
21382
- margin: "6px 0 0",
21383
- color: "var(--text-muted)"
21376
+ margin: "10px 0 0",
21377
+ color: "var(--text-muted)",
21378
+ fontSize: 13.5
21384
21379
  },
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
- ]
21380
+ 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."
21381
+ })
21382
+ ]
21383
+ }),
21384
+ (0, import_jsx_runtime.jsxs)("div", {
21385
+ className: "notice quiet",
21386
+ style: { marginTop: 26 },
21387
+ children: [(0, import_jsx_runtime.jsx)("b", {
21388
+ style: { color: "var(--text-strong)" },
21389
+ children: "These two settings stay on this computer"
21390
+ }), (0, import_jsx_runtime.jsxs)("p", {
21391
+ style: {
21392
+ margin: "6px 0 0",
21393
+ color: "var(--text-muted)"
21394
+ },
21395
+ 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."]
21396
+ })]
21397
+ })
21398
+ ]
21399
+ })]
21391
21400
  });
21392
21401
  }
21393
21402
  function ThemeOption({ label, value, current, onPick }) {
@@ -21664,17 +21673,9 @@ function UseYourAI({ ctx }) {
21664
21673
  return (0, import_jsx_runtime.jsxs)("div", {
21665
21674
  className: "screen ai-screen",
21666
21675
  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."
21676
+ (0, import_jsx_runtime.jsx)(PageHeader, {
21677
+ title: "Use your own AI",
21678
+ lede: "Run and interrogate clearances from the assistant you already use — by voice, by email, or just by asking."
21678
21679
  }),
21679
21680
  (0, import_jsx_runtime.jsxs)("section", {
21680
21681
  className: "ctx-card ai-can",
@@ -21920,7 +21921,6 @@ function Profile({ ctx }) {
21920
21921
  useUnsaved(dirty);
21921
21922
  if (needsOwner || result?.kind === "pickAccount") return (0, import_jsx_runtime.jsx)(CompanyGate, {
21922
21923
  ctx,
21923
- eyebrow: "Company",
21924
21924
  heading: "Profile",
21925
21925
  line: "Pick a company to see its profile."
21926
21926
  });
@@ -22472,7 +22472,6 @@ function Projects({ ctx }) {
22472
22472
  const projects = result?.kind === "ok" ? result.value : [];
22473
22473
  if (needsOwner || result?.kind === "pickAccount") return (0, import_jsx_runtime.jsx)(CompanyGate, {
22474
22474
  ctx,
22475
- eyebrow: "Company",
22476
22475
  heading: "Projects",
22477
22476
  line: "Pick a company to see its projects."
22478
22477
  });
@@ -23685,54 +23684,29 @@ function PeopleAccess({ ctx }) {
23685
23684
  const v = result.value;
23686
23685
  const broken = v.people.filter((p) => p.dangling.length > 0);
23687
23686
  const where = ctx.organisations.length === 1 ? `at ${ctx.organisations[0]?.name ?? ""}` : "here";
23688
- return (0, import_jsx_runtime.jsxs)("div", {
23687
+ return (0, import_jsx_runtime.jsx)("div", {
23689
23688
  className: "screen",
23690
- children: [(0, import_jsx_runtime.jsx)("div", {
23691
- className: "eyebrow",
23692
- children: "People"
23693
- }), (0, import_jsx_runtime.jsxs)("div", {
23689
+ children: (0, import_jsx_runtime.jsxs)("div", {
23694
23690
  className: "measure",
23695
23691
  style: { "--screen-measure": "900px" },
23696
23692
  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", {
23693
+ (0, import_jsx_runtime.jsx)(PageHeader, {
23694
+ title: "People",
23695
+ lede: (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
23696
+ "Who can use Clearotron ",
23697
+ (0, import_jsx_runtime.jsx)("span", {
23698
+ "data-anon": "mark",
23699
+ children: where
23700
+ }),
23701
+ ", what they can do, and which companies they can see."
23702
+ ] }),
23703
+ actions: (0, import_jsx_runtime.jsx)("button", {
23729
23704
  type: "button",
23730
23705
  className: "btn-primary",
23731
- style: { flex: "none" },
23732
23706
  disabled: !v.canAdd,
23733
23707
  onClick: () => ctx.go(ADD_PERSON.path),
23734
23708
  children: "+ Add a person"
23735
- })]
23709
+ })
23736
23710
  }),
23737
23711
  v.localSignIn ? (0, import_jsx_runtime.jsx)("div", {
23738
23712
  className: "notice quiet",
@@ -23824,12 +23798,17 @@ function PeopleAccess({ ctx }) {
23824
23798
  children: [(0, import_jsx_runtime.jsx)("thead", { children: (0, import_jsx_runtime.jsxs)("tr", { children: [
23825
23799
  (0, import_jsx_runtime.jsx)("th", { children: "Person" }),
23826
23800
  (0, import_jsx_runtime.jsx)("th", { children: "Permissions" }),
23827
- (0, import_jsx_runtime.jsx)("th", { children: "Access to" })
23801
+ (0, import_jsx_runtime.jsx)("th", { children: "Access to" }),
23802
+ (0, import_jsx_runtime.jsx)("th", { "aria-label": "Change or remove" })
23828
23803
  ] }) }), (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,
23804
+ colSpan: 4,
23830
23805
  style: { color: "var(--text-muted)" },
23831
23806
  children: "Nobody has been given access yet."
23832
- }) }) : v.people.map((p) => (0, import_jsx_runtime.jsx)(Row, { person: p }, p.email)) })]
23807
+ }) }) : v.people.map((p) => (0, import_jsx_runtime.jsx)(Row, {
23808
+ person: p,
23809
+ you: p.email.toLowerCase() === ctx.me.email.toLowerCase(),
23810
+ onModify: () => ctx.go(`${MODIFY_PERSON.path}?email=${encodeURIComponent(p.email)}`)
23811
+ }, p.email)) })]
23833
23812
  })
23834
23813
  }),
23835
23814
  v.grantsFile ? (0, import_jsx_runtime.jsxs)("p", {
@@ -23862,7 +23841,7 @@ function PeopleAccess({ ctx }) {
23862
23841
  }) : null,
23863
23842
  (0, import_jsx_runtime.jsx)(Observed, { result: observed })
23864
23843
  ]
23865
- })]
23844
+ })
23866
23845
  });
23867
23846
  }
23868
23847
  function Observed({ result }) {
@@ -23958,11 +23937,11 @@ function Observed({ result }) {
23958
23937
  ]
23959
23938
  });
23960
23939
  }
23961
- function Row({ person }) {
23940
+ function Row({ person, you, onModify }) {
23962
23941
  const chips = accessChips(person.access);
23963
23942
  const viewOnly = !person.permissions.run && !person.permissions.manage;
23964
23943
  return (0, import_jsx_runtime.jsxs)("tr", { children: [
23965
- (0, import_jsx_runtime.jsx)("td", { children: (0, import_jsx_runtime.jsx)("span", {
23944
+ (0, import_jsx_runtime.jsxs)("td", { children: [(0, import_jsx_runtime.jsx)("span", {
23966
23945
  style: {
23967
23946
  fontWeight: 700,
23968
23947
  color: "var(--text-strong)",
@@ -23970,7 +23949,15 @@ function Row({ person }) {
23970
23949
  },
23971
23950
  "data-anon": "mark",
23972
23951
  children: person.email
23973
- }) }),
23952
+ }), you ? (0, import_jsx_runtime.jsx)("span", {
23953
+ className: "pill",
23954
+ style: {
23955
+ fontSize: 10.5,
23956
+ padding: "1px 7px",
23957
+ marginLeft: 7
23958
+ },
23959
+ children: "You"
23960
+ }) : null] }),
23974
23961
  (0, import_jsx_runtime.jsx)("td", {
23975
23962
  style: { color: viewOnly ? "var(--text-muted)" : "var(--text-strong)" },
23976
23963
  children: person.listed === false ? (0, import_jsx_runtime.jsx)("span", {
@@ -23992,7 +23979,23 @@ function Row({ person }) {
23992
23979
  }) : (0, import_jsx_runtime.jsx)("span", {
23993
23980
  style: { color: "var(--text-muted)" },
23994
23981
  children: "Nothing yet — signed in, and given no access."
23995
- }) })
23982
+ }) }),
23983
+ (0, import_jsx_runtime.jsx)("td", {
23984
+ style: {
23985
+ textAlign: "right",
23986
+ whiteSpace: "nowrap"
23987
+ },
23988
+ children: you ? null : (0, import_jsx_runtime.jsx)("button", {
23989
+ type: "button",
23990
+ className: "pill",
23991
+ style: {
23992
+ cursor: "pointer",
23993
+ fontSize: 12
23994
+ },
23995
+ onClick: onModify,
23996
+ children: "Modify"
23997
+ })
23998
+ })
23996
23999
  ] });
23997
24000
  }
23998
24001
  //#endregion
@@ -24020,22 +24023,12 @@ function GiveAccess({ ctx }) {
24020
24023
  (0, import_react.useEffect)(() => {
24021
24024
  if (saved && !kept) ctx.go(PEOPLE.path);
24022
24025
  }, [saved, kept]);
24023
- if (view && isOk(view) && !view.value.canAdd) return (0, import_jsx_runtime.jsxs)("div", {
24026
+ if (view && isOk(view) && !view.value.canAdd) return (0, import_jsx_runtime.jsx)("div", {
24024
24027
  className: "screen",
24025
- children: [(0, import_jsx_runtime.jsx)("div", {
24026
- className: "eyebrow",
24027
- children: "People"
24028
- }), (0, import_jsx_runtime.jsxs)("div", {
24028
+ children: (0, import_jsx_runtime.jsxs)("div", {
24029
24029
  className: "measure",
24030
24030
  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", {
24031
+ children: [(0, import_jsx_runtime.jsx)(PageHeader, { title: "Give someone access to Clearotron" }), (0, import_jsx_runtime.jsx)("div", {
24039
24032
  className: "notice quiet",
24040
24033
  children: (0, import_jsx_runtime.jsxs)("p", {
24041
24034
  style: {
@@ -24059,7 +24052,7 @@ function GiveAccess({ ctx }) {
24059
24052
  ]
24060
24053
  })
24061
24054
  })]
24062
- })]
24055
+ })
24063
24056
  });
24064
24057
  const holdsEverything = ctx.me.access.some((a) => a.kind === "everything");
24065
24058
  const wholeOrgs = new Set(ctx.me.genericOrgs);
@@ -24111,30 +24104,15 @@ function GiveAccess({ ctx }) {
24111
24104
  }
24112
24105
  };
24113
24106
  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", {
24107
+ return (0, import_jsx_runtime.jsx)("div", {
24115
24108
  className: "screen",
24116
- children: [(0, import_jsx_runtime.jsx)("div", {
24117
- className: "eyebrow",
24118
- children: "People"
24119
- }), (0, import_jsx_runtime.jsxs)("div", {
24109
+ children: (0, import_jsx_runtime.jsxs)("div", {
24120
24110
  className: "measure",
24121
24111
  style: { "--screen-measure": "640px" },
24122
24112
  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."
24113
+ (0, import_jsx_runtime.jsx)(PageHeader, {
24114
+ title: "Give someone access to Clearotron",
24115
+ lede: "Enter their email, choose what they can see and do, then save. They sign in the same way you do."
24138
24116
  }),
24139
24117
  (0, import_jsx_runtime.jsx)("label", {
24140
24118
  className: "field-label",
@@ -24163,7 +24141,7 @@ function GiveAccess({ ctx }) {
24163
24141
  gap: 8,
24164
24142
  marginBottom: 6
24165
24143
  },
24166
- children: [(0, import_jsx_runtime.jsx)(Lever, {
24144
+ children: [(0, import_jsx_runtime.jsx)(Lever$1, {
24167
24145
  on: permissions.run,
24168
24146
  title: "Run clearances",
24169
24147
  line: "Start and stop clearances on the companies they can see.",
@@ -24172,7 +24150,7 @@ function GiveAccess({ ctx }) {
24172
24150
  ...p,
24173
24151
  run: !p.run
24174
24152
  }))
24175
- }), (0, import_jsx_runtime.jsx)(Lever, {
24153
+ }), (0, import_jsx_runtime.jsx)(Lever$1, {
24176
24154
  on: permissions.manage,
24177
24155
  title: "Manage",
24178
24156
  line: "Add companies, add people and change settings for the companies they can see.",
@@ -24202,7 +24180,7 @@ function GiveAccess({ ctx }) {
24202
24180
  gap: 6,
24203
24181
  marginBottom: 8
24204
24182
  },
24205
- children: [holdsEverything ? (0, import_jsx_runtime.jsx)(AttachRow, {
24183
+ children: [holdsEverything ? (0, import_jsx_runtime.jsx)(AttachRow$1, {
24206
24184
  on: chosen.everything,
24207
24185
  name: "Everything on this Clearotron",
24208
24186
  line: "all organisations and companies, including ones added later",
@@ -24213,7 +24191,7 @@ function GiveAccess({ ctx }) {
24213
24191
  }))
24214
24192
  }) : null, tree.map((t) => (0, import_jsx_runtime.jsxs)("div", {
24215
24193
  style: { display: "contents" },
24216
- children: [t.whole ? (0, import_jsx_runtime.jsx)(AttachRow, {
24194
+ children: [t.whole ? (0, import_jsx_runtime.jsx)(AttachRow$1, {
24217
24195
  on: chosen.everything || chosen.orgs.has(t.org.key),
24218
24196
  implied: chosen.everything,
24219
24197
  name: t.org.name,
@@ -24223,7 +24201,7 @@ function GiveAccess({ ctx }) {
24223
24201
  ...c,
24224
24202
  orgs: toggle(c.orgs, t.org.key)
24225
24203
  }))
24226
- }) : null, t.companies.map((k) => (0, import_jsx_runtime.jsx)(AttachRow, {
24204
+ }) : null, t.companies.map((k) => (0, import_jsx_runtime.jsx)(AttachRow$1, {
24227
24205
  sub: t.whole,
24228
24206
  on: covered(k),
24229
24207
  implied: chosen.everything || chosen.orgs.has(t.org.key),
@@ -24319,9 +24297,496 @@ function GiveAccess({ ctx }) {
24319
24297
  })] })
24320
24298
  })
24321
24299
  ]
24300
+ })
24301
+ });
24302
+ }
24303
+ function Lever$1({ on, title, line, disabled, onToggle }) {
24304
+ return (0, import_jsx_runtime.jsxs)("button", {
24305
+ type: "button",
24306
+ className: on ? "lever lever-on" : "lever",
24307
+ "aria-pressed": on,
24308
+ disabled,
24309
+ onClick: onToggle,
24310
+ children: [(0, import_jsx_runtime.jsx)("span", { className: on ? "lever-box lever-box-on" : "lever-box" }), (0, import_jsx_runtime.jsxs)("span", {
24311
+ style: { textAlign: "left" },
24312
+ children: [(0, import_jsx_runtime.jsx)("span", {
24313
+ style: {
24314
+ display: "block",
24315
+ fontWeight: 600,
24316
+ color: "var(--text-strong)"
24317
+ },
24318
+ children: title
24319
+ }), (0, import_jsx_runtime.jsx)("span", {
24320
+ style: {
24321
+ display: "block",
24322
+ fontSize: 12.5,
24323
+ color: "var(--text-muted)"
24324
+ },
24325
+ children: line
24326
+ })]
24322
24327
  })]
24323
24328
  });
24324
24329
  }
24330
+ function AttachRow$1({ on, implied = false, sub = false, name, line, disabled, onToggle }) {
24331
+ return (0, import_jsx_runtime.jsxs)("button", {
24332
+ type: "button",
24333
+ className: `attach-row${on ? " on" : ""}${sub ? " sub" : ""}`,
24334
+ "aria-pressed": on,
24335
+ disabled: disabled || implied,
24336
+ onClick: onToggle,
24337
+ children: [
24338
+ (0, import_jsx_runtime.jsx)("span", { className: on ? "lever-box lever-box-on" : "lever-box" }),
24339
+ (0, import_jsx_runtime.jsx)("span", {
24340
+ style: {
24341
+ color: "var(--text-strong)",
24342
+ fontWeight: sub ? 400 : 600
24343
+ },
24344
+ "data-anon": "mark",
24345
+ children: name
24346
+ }),
24347
+ (0, import_jsx_runtime.jsx)("span", {
24348
+ className: "sub-t",
24349
+ children: line
24350
+ })
24351
+ ]
24352
+ });
24353
+ }
24354
+ //#endregion
24355
+ //#region src/screens/ModifyAccess.tsx
24356
+ var emailInQuery = () => (new URLSearchParams(window.location.search).get("email") ?? "").trim().toLowerCase();
24357
+ var heldBy = (person) => ({
24358
+ orgs: new Set(person.access.filter((a) => a.kind === "organisation").map((a) => a.key)),
24359
+ companies: new Set(person.access.filter((a) => a.kind === "company").map((a) => a.key))
24360
+ });
24361
+ var sameSet = (a, b) => a.size === b.size && [...a].every((k) => b.has(k));
24362
+ function ModifyAccess({ ctx }) {
24363
+ const email = emailInQuery();
24364
+ const { result: view } = useLoad(() => api.adminAccess(), []);
24365
+ if (!view) return (0, import_jsx_runtime.jsx)("div", { className: "screen" });
24366
+ if (!isOk(view)) return (0, import_jsx_runtime.jsx)("div", {
24367
+ className: "screen",
24368
+ children: (0, import_jsx_runtime.jsx)("div", {
24369
+ className: "empty",
24370
+ children: (0, import_jsx_runtime.jsx)("p", { children: "This page is not available." })
24371
+ })
24372
+ });
24373
+ const person = view.value.people.find((p) => p.email.toLowerCase() === email) ?? null;
24374
+ if (!person) return (0, import_jsx_runtime.jsx)("div", {
24375
+ className: "screen",
24376
+ children: (0, import_jsx_runtime.jsxs)("div", {
24377
+ className: "measure",
24378
+ style: { "--screen-measure": "640px" },
24379
+ children: [
24380
+ (0, import_jsx_runtime.jsx)(PageHeader, { title: "Modify access" }),
24381
+ (0, import_jsx_runtime.jsx)("div", {
24382
+ className: "notice quiet",
24383
+ children: (0, import_jsx_runtime.jsx)("p", {
24384
+ style: {
24385
+ margin: 0,
24386
+ fontSize: 13.5,
24387
+ color: "var(--text-muted)"
24388
+ },
24389
+ children: "Nobody with that address has access you can see. They may have been removed, or their access may be somewhere you cannot reach."
24390
+ })
24391
+ }),
24392
+ (0, import_jsx_runtime.jsx)("div", {
24393
+ style: { marginTop: 16 },
24394
+ children: (0, import_jsx_runtime.jsx)("button", {
24395
+ type: "button",
24396
+ className: "btn-ghost",
24397
+ onClick: () => ctx.go(PEOPLE.path),
24398
+ children: "Back to People"
24399
+ })
24400
+ })
24401
+ ]
24402
+ })
24403
+ });
24404
+ return (0, import_jsx_runtime.jsx)(Form, {
24405
+ ctx,
24406
+ person,
24407
+ view: view.value
24408
+ }, person.email);
24409
+ }
24410
+ function Form({ ctx, person, view }) {
24411
+ const held = heldBy(person);
24412
+ const [permissions, setPermissions] = (0, import_react.useState)(person.permissions);
24413
+ const [chosen, setChosen] = (0, import_react.useState)(held);
24414
+ const [busy, setBusy] = (0, import_react.useState)(false);
24415
+ const [confirming, setConfirming] = (0, import_react.useState)(false);
24416
+ const [saved, setSaved] = (0, import_react.useState)(null);
24417
+ const [gone, setGone] = (0, import_react.useState)(null);
24418
+ const whole = person.covered === true;
24419
+ const holdsEverything = person.access.some((a) => a.kind === "everything");
24420
+ const orgName = (key) => ctx.organisations.find((o) => o.key === key)?.name ?? key;
24421
+ const wholeOrgs = new Set(ctx.me.genericOrgs);
24422
+ const companiesIn = (org) => ctx.ownerKeys.filter((k) => !isGenericKey(k) && ctx.orgOf(k) === org);
24423
+ const tree = ctx.organisations.map((o) => ({
24424
+ org: o,
24425
+ whole: wholeOrgs.has(o.key),
24426
+ companies: companiesIn(o.key)
24427
+ })).filter((t) => t.whole || t.companies.length > 0);
24428
+ const toggle = (set, key) => {
24429
+ const next = new Set(set);
24430
+ if (next.has(key)) next.delete(key);
24431
+ else next.add(key);
24432
+ return next;
24433
+ };
24434
+ const covered = (company) => chosen.companies.has(company) || chosen.orgs.has(ctx.orgOf(company) ?? "");
24435
+ const soloCompanies = [...chosen.companies].filter((k) => !chosen.orgs.has(ctx.orgOf(k) ?? ""));
24436
+ const dirty = !sameSet(chosen.orgs, held.orgs) || !sameSet(chosen.companies, held.companies) || permissions.run !== person.permissions.run || permissions.manage !== person.permissions.manage;
24437
+ useUnsaved(dirty && saved === null && gone === null);
24438
+ const access = [...[...chosen.orgs].map((key) => ({
24439
+ kind: "organisation",
24440
+ key
24441
+ })), ...soloCompanies.map((key) => ({
24442
+ kind: "company",
24443
+ key
24444
+ }))];
24445
+ const leftWithNothing = access.length === 0;
24446
+ const losing = [...[...held.orgs].filter((k) => !chosen.orgs.has(k)).map(orgName), ...[...held.companies].filter((k) => !covered(k)).map((k) => ctx.ownerName(k))];
24447
+ const sentence = accessSentence({
24448
+ who: person.email,
24449
+ permissions,
24450
+ everything: holdsEverything,
24451
+ organisations: [...chosen.orgs].map(orgName),
24452
+ companies: soloCompanies.map((k) => ({
24453
+ name: ctx.ownerName(k),
24454
+ organisation: orgName(ctx.orgOf(k) ?? "")
24455
+ }))
24456
+ });
24457
+ const save = async () => {
24458
+ setBusy(true);
24459
+ try {
24460
+ setSaved(await api.changePerson({
24461
+ email: person.email,
24462
+ permissions,
24463
+ access
24464
+ }));
24465
+ } finally {
24466
+ setBusy(false);
24467
+ }
24468
+ };
24469
+ const remove = async () => {
24470
+ setBusy(true);
24471
+ try {
24472
+ const r = await api.removePerson({ email: person.email });
24473
+ setGone(r);
24474
+ if (isOk(r)) ctx.go(PEOPLE.path);
24475
+ } finally {
24476
+ setBusy(false);
24477
+ }
24478
+ };
24479
+ const failure = (r) => r === null || isOk(r) ? null : 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.");
24480
+ const problem = failure(saved) ?? failure(gone);
24481
+ const loses = person.access.length ? person.access.map((a) => a.kind === "everything" ? "everything on this Clearotron" : a.name ?? a.key) : [];
24482
+ const holdsKey = (person.keys ?? 0) > 0;
24483
+ const keyStaysLive = holdsKey && view.keysRevocable !== true;
24484
+ const domain = person.email.startsWith("*@") ? person.email.slice(2) : null;
24485
+ return (0, import_jsx_runtime.jsx)("div", {
24486
+ className: "screen",
24487
+ children: (0, import_jsx_runtime.jsxs)("div", {
24488
+ className: "measure",
24489
+ style: { "--screen-measure": "640px" },
24490
+ children: [
24491
+ (0, import_jsx_runtime.jsx)(PageHeader, {
24492
+ title: "Modify access",
24493
+ lede: (0, import_jsx_runtime.jsx)("b", {
24494
+ style: {
24495
+ color: "var(--text-strong)",
24496
+ wordBreak: "break-all"
24497
+ },
24498
+ "data-anon": "mark",
24499
+ children: person.email
24500
+ })
24501
+ }),
24502
+ (0, import_jsx_runtime.jsx)("div", {
24503
+ className: "field-label",
24504
+ children: "Permissions"
24505
+ }),
24506
+ (0, import_jsx_runtime.jsxs)("div", {
24507
+ style: {
24508
+ display: "flex",
24509
+ flexDirection: "column",
24510
+ gap: 8,
24511
+ marginBottom: 6
24512
+ },
24513
+ children: [(0, import_jsx_runtime.jsx)(Lever, {
24514
+ on: permissions.run,
24515
+ title: "Run clearances",
24516
+ line: "Start and stop clearances on the companies they can see.",
24517
+ disabled: !whole || busy,
24518
+ onToggle: () => setPermissions((p) => ({
24519
+ ...p,
24520
+ run: !p.run
24521
+ }))
24522
+ }), (0, import_jsx_runtime.jsx)(Lever, {
24523
+ on: permissions.manage,
24524
+ title: "Manage",
24525
+ line: "Add companies, add people and change settings for the companies they can see.",
24526
+ disabled: !whole || busy,
24527
+ onToggle: () => setPermissions((p) => ({
24528
+ ...p,
24529
+ manage: !p.manage
24530
+ }))
24531
+ })]
24532
+ }),
24533
+ (0, import_jsx_runtime.jsx)("p", {
24534
+ style: {
24535
+ margin: "0 0 22px",
24536
+ fontSize: 12.5,
24537
+ color: "var(--text-faint)"
24538
+ },
24539
+ 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."
24540
+ }),
24541
+ (0, import_jsx_runtime.jsx)("div", {
24542
+ className: "field-label",
24543
+ children: "Access to"
24544
+ }),
24545
+ (0, import_jsx_runtime.jsxs)("div", {
24546
+ style: {
24547
+ display: "flex",
24548
+ flexDirection: "column",
24549
+ gap: 6,
24550
+ marginBottom: 8
24551
+ },
24552
+ children: [holdsEverything ? (0, import_jsx_runtime.jsx)(AttachRow, {
24553
+ on: true,
24554
+ name: "Everything on this Clearotron",
24555
+ line: "all organisations and companies, including ones added later",
24556
+ disabled: true,
24557
+ onToggle: () => {}
24558
+ }) : null, tree.map((t) => (0, import_jsx_runtime.jsxs)("div", {
24559
+ style: { display: "contents" },
24560
+ children: [t.whole ? (0, import_jsx_runtime.jsx)(AttachRow, {
24561
+ on: chosen.orgs.has(t.org.key),
24562
+ name: t.org.name,
24563
+ line: "every company in it, including ones added later",
24564
+ disabled: busy,
24565
+ onToggle: () => setChosen((c) => ({
24566
+ ...c,
24567
+ orgs: toggle(c.orgs, t.org.key)
24568
+ }))
24569
+ }) : null, t.companies.map((k) => (0, import_jsx_runtime.jsx)(AttachRow, {
24570
+ sub: t.whole,
24571
+ on: covered(k),
24572
+ implied: chosen.orgs.has(t.org.key),
24573
+ name: ctx.ownerName(k),
24574
+ line: t.whole ? "company" : `company in ${t.org.name}`,
24575
+ disabled: busy,
24576
+ onToggle: () => setChosen((c) => ({
24577
+ ...c,
24578
+ companies: toggle(c.companies, k)
24579
+ }))
24580
+ }, k))]
24581
+ }, t.org.key))]
24582
+ }),
24583
+ (0, import_jsx_runtime.jsxs)("p", {
24584
+ style: {
24585
+ margin: "0 0 22px",
24586
+ fontSize: 12.5,
24587
+ color: "var(--text-faint)"
24588
+ },
24589
+ 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."]
24590
+ }),
24591
+ (0, import_jsx_runtime.jsx)("div", {
24592
+ className: "notice quiet",
24593
+ style: { marginBottom: 18 },
24594
+ children: (0, import_jsx_runtime.jsxs)("p", {
24595
+ style: {
24596
+ margin: 0,
24597
+ fontSize: 13.5,
24598
+ color: "var(--text-muted)"
24599
+ },
24600
+ "data-anon": "mark",
24601
+ children: [sentence, losing.length ? (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [" ", (0, import_jsx_runtime.jsxs)("b", {
24602
+ style: { color: "var(--text-strong)" },
24603
+ children: [
24604
+ "They lose ",
24605
+ losing.join(", "),
24606
+ "."
24607
+ ]
24608
+ })] }) : null]
24609
+ })
24610
+ }),
24611
+ problem ? (0, import_jsx_runtime.jsx)("div", {
24612
+ className: "notice",
24613
+ style: {
24614
+ borderColor: "var(--tone-high)",
24615
+ marginBottom: 14
24616
+ },
24617
+ children: (0, import_jsx_runtime.jsx)("p", {
24618
+ style: {
24619
+ margin: 0,
24620
+ fontSize: 13.5
24621
+ },
24622
+ children: problem
24623
+ })
24624
+ }) : null,
24625
+ saved && isOk(saved) ? (0, import_jsx_runtime.jsx)("div", {
24626
+ className: "notice",
24627
+ style: {
24628
+ borderColor: "var(--tone-medium)",
24629
+ marginBottom: 14
24630
+ },
24631
+ children: (0, import_jsx_runtime.jsxs)("p", {
24632
+ style: {
24633
+ margin: 0,
24634
+ fontSize: 13.5
24635
+ },
24636
+ children: ["Saved. ", saved.value.switchesApplied ? permissionsPhrase(permissions) + "." : (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
24637
+ "Their permissions stay as they were: ",
24638
+ permissionsPhrase(person.permissions),
24639
+ ". They have access you cannot see, and permissions apply everywhere a person has access."
24640
+ ] })]
24641
+ })
24642
+ }) : null,
24643
+ (0, import_jsx_runtime.jsxs)("div", {
24644
+ style: {
24645
+ display: "flex",
24646
+ gap: 10,
24647
+ alignItems: "center"
24648
+ },
24649
+ children: [
24650
+ (0, import_jsx_runtime.jsx)("button", {
24651
+ type: "button",
24652
+ className: "btn-primary",
24653
+ disabled: !dirty || busy || leftWithNothing,
24654
+ onClick: () => void save(),
24655
+ children: busy && !confirming ? "Saving…" : "Save changes"
24656
+ }),
24657
+ (0, import_jsx_runtime.jsx)("button", {
24658
+ type: "button",
24659
+ className: "btn-ghost",
24660
+ onClick: () => ctx.go(PEOPLE.path),
24661
+ children: "Cancel"
24662
+ }),
24663
+ leftWithNothing ? (0, import_jsx_runtime.jsx)("span", {
24664
+ style: {
24665
+ fontSize: 12.5,
24666
+ color: "var(--text-faint)"
24667
+ },
24668
+ children: "Nothing is ticked. Leave them at least one, or remove them below."
24669
+ }) : null
24670
+ ]
24671
+ }),
24672
+ (0, import_jsx_runtime.jsx)("div", {
24673
+ style: {
24674
+ marginTop: 34,
24675
+ paddingTop: 18,
24676
+ borderTop: "1px solid var(--border-hairline)",
24677
+ display: "flex",
24678
+ flexDirection: "column",
24679
+ gap: 10,
24680
+ alignItems: "flex-start"
24681
+ },
24682
+ children: confirming ? (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [(0, import_jsx_runtime.jsx)("div", {
24683
+ className: "notice",
24684
+ style: {
24685
+ borderColor: "var(--tone-high)",
24686
+ margin: 0,
24687
+ width: "100%"
24688
+ },
24689
+ children: (0, import_jsx_runtime.jsxs)("p", {
24690
+ style: {
24691
+ margin: 0,
24692
+ fontSize: 13.5
24693
+ },
24694
+ children: [
24695
+ (0, import_jsx_runtime.jsx)("b", {
24696
+ "data-anon": "mark",
24697
+ children: person.email
24698
+ }),
24699
+ " ",
24700
+ whole ? (0, import_jsx_runtime.jsxs)("b", { children: [
24701
+ "loses access to ",
24702
+ loses.length ? loses.join(" and ") : "this Clearotron",
24703
+ " straight away,"
24704
+ ] }) : (0, import_jsx_runtime.jsxs)("b", { children: [
24705
+ "loses ",
24706
+ loses.join(" and "),
24707
+ " straight away,"
24708
+ ] }),
24709
+ " ",
24710
+ keyStaysLive ? (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
24711
+ "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.",
24712
+ " ",
24713
+ (0, import_jsx_runtime.jsx)("b", {
24714
+ className: "mono",
24715
+ children: "clearotron doctor"
24716
+ }),
24717
+ " reports it.",
24718
+ " "
24719
+ ] }) : (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: ["here and through their AI.", " "] }),
24720
+ holdsKey && !keyStaysLive ? (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
24721
+ "Their AI’s key is withdrawn at the same time;",
24722
+ " ",
24723
+ (0, import_jsx_runtime.jsx)("b", {
24724
+ className: "mono",
24725
+ children: "clearotron doctor"
24726
+ }),
24727
+ " says whether the connector has picked it up.",
24728
+ " "
24729
+ ] }) : null,
24730
+ whole ? "To stop them reaching your sign-in page, take them off your login system too." : "They keep the access you cannot see.",
24731
+ domain ? (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [" ", (0, import_jsx_runtime.jsxs)("b", { children: [
24732
+ "This takes access away from everyone with an address at ",
24733
+ (0, import_jsx_runtime.jsx)("span", {
24734
+ "data-anon": "mark",
24735
+ children: domain
24736
+ }),
24737
+ "."
24738
+ ] })] }) : null
24739
+ ]
24740
+ })
24741
+ }), (0, import_jsx_runtime.jsxs)("div", {
24742
+ style: {
24743
+ display: "flex",
24744
+ gap: 8
24745
+ },
24746
+ children: [(0, import_jsx_runtime.jsx)("button", {
24747
+ type: "button",
24748
+ className: "pill",
24749
+ style: {
24750
+ cursor: "pointer",
24751
+ fontSize: 12,
24752
+ borderColor: "var(--tone-high)"
24753
+ },
24754
+ disabled: busy,
24755
+ onClick: () => void remove(),
24756
+ children: busy ? "Working…" : "Confirm — remove"
24757
+ }), (0, import_jsx_runtime.jsx)("button", {
24758
+ type: "button",
24759
+ className: "pill",
24760
+ style: {
24761
+ cursor: "pointer",
24762
+ fontSize: 12
24763
+ },
24764
+ onClick: () => setConfirming(false),
24765
+ children: "Cancel"
24766
+ })]
24767
+ })] }) : (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [(0, import_jsx_runtime.jsx)("button", {
24768
+ type: "button",
24769
+ className: "pill",
24770
+ style: {
24771
+ cursor: "pointer",
24772
+ fontSize: 12
24773
+ },
24774
+ disabled: busy,
24775
+ onClick: () => setConfirming(true),
24776
+ children: whole ? domain ? `Remove everyone at ${domain}` : "Remove from Clearotron" : `Remove from ${[...held.orgs].map(orgName).join(", ") || "here"}`
24777
+ }), whole ? null : (0, import_jsx_runtime.jsx)("p", {
24778
+ style: {
24779
+ margin: 0,
24780
+ fontSize: 12.5,
24781
+ color: "var(--text-faint)"
24782
+ },
24783
+ children: "They keep the access you cannot see."
24784
+ })] })
24785
+ })
24786
+ ]
24787
+ })
24788
+ });
24789
+ }
24325
24790
  function Lever({ on, title, line, disabled, onToggle }) {
24326
24791
  return (0, import_jsx_runtime.jsxs)("button", {
24327
24792
  type: "button",
@@ -24393,7 +24858,7 @@ function About() {
24393
24858
  if (failed) return (0, import_jsx_runtime.jsxs)("section", {
24394
24859
  className: "screen",
24395
24860
  children: [
24396
- (0, import_jsx_runtime.jsx)("h1", { children: "About" }),
24861
+ (0, import_jsx_runtime.jsx)(PageHeader, { title: "About" }),
24397
24862
  (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
24863
  (0, import_jsx_runtime.jsxs)("p", { children: ["Source: ", (0, import_jsx_runtime.jsx)("a", {
24399
24864
  href: "https://github.com/CordilleraSarl/Clearotron",
@@ -24403,13 +24868,13 @@ function About() {
24403
24868
  });
24404
24869
  if (!info) return (0, import_jsx_runtime.jsxs)("section", {
24405
24870
  className: "screen",
24406
- children: [(0, import_jsx_runtime.jsx)("h1", { children: "About" }), (0, import_jsx_runtime.jsx)("p", { children: "Loading…" })]
24871
+ children: [(0, import_jsx_runtime.jsx)(PageHeader, { title: "About" }), (0, import_jsx_runtime.jsx)("p", { children: "Loading…" })]
24407
24872
  });
24408
24873
  const shortSha = info.commit ? info.commit.slice(0, 12) : null;
24409
24874
  return (0, import_jsx_runtime.jsxs)("section", {
24410
24875
  className: "screen",
24411
24876
  children: [
24412
- (0, import_jsx_runtime.jsxs)("h1", { children: ["About ", info.name] }),
24877
+ (0, import_jsx_runtime.jsx)(PageHeader, { title: `About ${info.name}` }),
24413
24878
  (0, import_jsx_runtime.jsxs)("dl", { children: [
24414
24879
  (0, import_jsx_runtime.jsx)("dt", { children: "Product" }),
24415
24880
  (0, import_jsx_runtime.jsxs)("dd", { children: [info.name, info.version ? ` ${info.version}` : ""] }),
@@ -24506,41 +24971,27 @@ function About() {
24506
24971
  function NotYet({ title, phase, what }) {
24507
24972
  return (0, import_jsx_runtime.jsxs)("div", {
24508
24973
  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", {
24974
+ children: [(0, import_jsx_runtime.jsx)(PageHeader, { title }), (0, import_jsx_runtime.jsxs)("div", {
24975
+ className: "notice quiet",
24976
+ children: [(0, import_jsx_runtime.jsx)("p", {
24515
24977
  style: {
24516
- fontSize: 25,
24517
- margin: "4px 0 10px",
24518
- color: "var(--text-strong)"
24978
+ margin: 0,
24979
+ color: "var(--text-muted)"
24519
24980
  },
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
- ]
24981
+ children: what
24982
+ }), (0, import_jsx_runtime.jsxs)("p", {
24983
+ style: {
24984
+ margin: "8px 0 0",
24985
+ fontSize: 13,
24986
+ color: "var(--text-faint)"
24987
+ },
24988
+ children: [
24989
+ "Being built — ",
24990
+ phase,
24991
+ "."
24992
+ ]
24993
+ })]
24994
+ })]
24544
24995
  });
24545
24996
  }
24546
24997
  var ownerKey = (ctx) => `${ctx.owner ?? "*"}#${ctx.visit}`;
@@ -24570,6 +25021,7 @@ function screen(id, ctx) {
24570
25021
  case "about": return (0, import_jsx_runtime.jsx)(About, {});
24571
25022
  case "people": return (0, import_jsx_runtime.jsx)(PeopleAccess, { ctx });
24572
25023
  case "people.add": return (0, import_jsx_runtime.jsx)(GiveAccess, { ctx });
25024
+ case "people.modify": return (0, import_jsx_runtime.jsx)(ModifyAccess, { ctx });
24573
25025
  case "admin":
24574
25026
  case "admin.config": return (0, import_jsx_runtime.jsx)(GlobalConfig, { ctx });
24575
25027
  default: return (0, import_jsx_runtime.jsx)(NotYet, {