clearotron 0.2.2 → 0.2.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.
Files changed (118) hide show
  1. package/.env.example +28 -0
  2. package/.nvmrc +1 -1
  3. package/INSTALL.md +13 -11
  4. package/README.md +11 -16
  5. package/THIRD-PARTY-NOTICES.md +1 -1
  6. package/bin/clearotron.mjs +22 -0
  7. package/bin/example.mjs +4 -0
  8. package/bin/onboard.mjs +195 -19
  9. package/bin/start.mjs +154 -8
  10. package/build-info.json +2 -2
  11. package/demo/full-country-search/run/_driver/framework.json +1 -1
  12. package/demo/global-preliminary-search/run/_driver/framework.json +1 -1
  13. package/demo/knockout-search/run/_driver/framework.json +1 -1
  14. package/demo/multi-country-focus-search/run/_driver/framework.json +1 -1
  15. package/docs/E2E.md +5 -5
  16. package/docs/PORTAL.md +2 -2
  17. package/docs/README.md +5 -4
  18. package/docs/SECURITY.md +17 -0
  19. package/docs/architecture/04-configuration-reference.md +2 -0
  20. package/docs/architecture/05-config-governance.md +14 -0
  21. package/docs/architecture/05-customer-profiles.md +2 -2
  22. package/docs/configuration.md +4 -3
  23. package/driver/CHANGELOG.md +56 -0
  24. package/driver/common-law-receipts.mjs +1 -1
  25. package/driver/config-staleness.mjs +1 -1
  26. package/driver/dev-portal.mjs +2 -2
  27. package/driver/driver.config.mjs +1 -1
  28. package/driver/engine/cli-version.mjs +103 -0
  29. package/driver/enqueue-schema.mjs +8 -8
  30. package/driver/feedback-store.mjs +1 -1
  31. package/driver/findings-model.mjs +52 -2
  32. package/driver/flag-snapshot.mjs +20 -0
  33. package/driver/framework.mjs +1 -1
  34. package/driver/gateway.mjs +18 -1
  35. package/driver/package.json +3 -3
  36. package/driver/party-facts.mjs +2 -2
  37. package/driver/phase0.mjs +1 -1
  38. package/driver/pipeline.mjs +5 -5
  39. package/driver/plain-register.mjs +120 -0
  40. package/driver/portal-access.mjs +1 -1
  41. package/driver/portal-config-view.mjs +40 -1
  42. package/driver/portal-families.mjs +1 -1
  43. package/driver/portal-report.mjs +1 -1
  44. package/driver/portal-service.mjs +26 -7
  45. package/driver/portal-upstream.mjs +2 -2
  46. package/driver/predelivery-lint.mjs +121 -1
  47. package/driver/profile-page.html +10 -2
  48. package/driver/profile-service.mjs +4 -4
  49. package/driver/profiles/README.md +3 -3
  50. package/driver/profiles.mjs +144 -12
  51. package/driver/publish/index.mjs +2 -2
  52. package/driver/publish/knockout.mjs +7 -1
  53. package/driver/publish/render-knockout.mjs +453 -118
  54. package/driver/publish/render.mjs +2 -2
  55. package/driver/reference-strip-signatures.mjs +20 -0
  56. package/driver/search-policy.mjs +2 -2
  57. package/driver/skills/blind-frame/SKILL.md +1 -1
  58. package/driver/skills/knockout-assess/SKILL.md +84 -0
  59. package/driver/skills/matter-frame/SKILL.md +6 -6
  60. package/driver/skills/matter-frame/watchlist-reference.md +2 -2
  61. package/driver/skills/prelim-common-law/SKILL.md +1 -1
  62. package/driver/skills/prelim-search/SKILL.md +1 -1
  63. package/driver/skills/prelim-search/report-prose.md +82 -0
  64. package/driver/skills/prelim-search/risk-framework-demo.manifest.json +1 -1
  65. package/driver/skills/prelim-search/risk-framework-triage.manifest.json +1 -1
  66. package/driver/skills/prelim-search/risk-framework.manifest.json +1 -1
  67. package/driver/skills/prelim-search/synthesis-rules.md +1 -1
  68. package/driver/skills/prelim-variants/SKILL.md +3 -3
  69. package/driver/stages.mjs +4 -4
  70. package/driver/suite-census.json +134 -38
  71. package/driver/unit-inventory.mjs +184 -27
  72. package/driver/verify.mjs +1 -1
  73. package/driver/whatif-memo-run.mjs +26 -4
  74. package/driver/whatif-memo.mjs +19 -1
  75. package/examples/README.md +2 -2
  76. package/examples/grants.example.json +6 -7
  77. package/mcp-server/CHANGELOG.md +2 -0
  78. package/mcp-server/lib/scrub.mjs +1 -1
  79. package/mcp-server/lib/whatif.mjs +2 -2
  80. package/mcp-server/package.json +1 -1
  81. package/mcp-server/packs/ops/EXAMPLES.md +2 -2
  82. package/mcp-server/server.mjs +1 -1
  83. package/package.json +28 -10
  84. package/portal-ui/dist/assets/{index-Ct3iQtZ2.css → index-1ziUJX1E.css} +43 -2
  85. package/portal-ui/dist/assets/{index-BUGgiYtF.js → index-CEYvXdqH.js} +226 -49
  86. package/portal-ui/dist/index.html +2 -2
  87. package/portal-ui/package.json +1 -1
  88. package/providers/_shared/term-shape.mjs +1 -1
  89. package/providers/oauth-mcp-bridge/CHANGELOG.md +2 -0
  90. package/providers/oauth-mcp-bridge/package.json +1 -1
  91. package/providers/uspto-local/src/index-store.js +2 -2
  92. package/scripts/citation-line-check.mjs +121 -2
  93. package/scripts/env-classify.mjs +67 -3
  94. package/scripts/live-surface-check.mjs +38 -1
  95. package/scripts/nightly-notice.mjs +0 -2
  96. package/scripts/preinstall-node-check.mjs +24 -0
  97. package/scripts/strip-tracker-citations.mjs +29 -6
  98. package/scripts/test-run.mjs +16 -0
  99. package/shared/anon-overlay.mjs +3 -3
  100. package/shared/customer-store.mjs +1 -1
  101. package/shared/invocation.mjs +28 -0
  102. package/shared/names-in-force.mjs +2 -0
  103. package/shared/node-floor.mjs +97 -0
  104. package/shared/staff-domain.mjs +154 -0
  105. package/shared/store-in-repo.mjs +23 -2
  106. package/skills/clearotron-ops/SKILL.md +1 -1
  107. package/driver/recipes/README.md +0 -49
  108. package/driver/skills/prelim-search/risk-framework-aurora.manifest.json +0 -19
  109. package/driver/skills/prelim-search/risk-framework-aurora.md +0 -77
  110. package/driver/skills/prelim-search/risk-framework-zephyr.manifest.json +0 -14
  111. package/driver/skills/prelim-search/risk-framework-zephyr.md +0 -33
  112. package/driver/skills/prelim-search/worked-examples-aurora.md +0 -10
  113. package/driver/skills/prelim-search/worked-examples-zephyr.md +0 -10
  114. package/scripts/ai-page-render-check.mjs +0 -532
  115. package/scripts/clearances-render-check.mjs +0 -480
  116. package/scripts/composer-render-check.mjs +0 -1027
  117. package/scripts/home-render-check.mjs +0 -526
  118. package/scripts/portal-lifecycle-check.mjs +0 -639
@@ -598,7 +598,7 @@ table.data tr.row:hover td {
598
598
  /* tracker issue 277 — A SECTION HEADER, not a decorative divider.
599
599
  Brand owner is the organising principle of the whole list, and a reader had to work it out: the name
600
600
  was set in the smallest, lowest-contrast, most letterspaced type on the page, which reads as a rule
601
- BETWEEN rows rather than as "everything below this belongs to Aurora Interactive".
601
+ BETWEEN rows rather than as "everything below this belongs to Foxglade Interactive".
602
602
  Body-text size, medium weight, full-strength ink, sentence case rather than letterspaced caps — and a
603
603
  hairline rule spanning the table beneath it, so containment is visible without reading anything. */
604
604
  table.data tr.group-head td {
@@ -916,11 +916,21 @@ input.filter {
916
916
  color: var(--text-strong);
917
917
  }
918
918
 
919
+ /* Says what the row of pills below it is a row OF. Sentence case and body-weight rather than the
920
+ uppercase section treatment above: this is a caption belonging to the statement it follows, and
921
+ giving it a heading's weight would make it look like a new section — which is the misreading the
922
+ label exists to end. Sits tight to its pills and away from the statement above. */
923
+ .fw-ladder-label {
924
+ font-size: 12px;
925
+ color: var(--text-muted);
926
+ margin: 14px 0 0;
927
+ }
928
+
919
929
  .fw-ladder {
920
930
  display: flex;
921
931
  flex-wrap: wrap;
922
932
  gap: 6px;
923
- margin: 10px 0 4px;
933
+ margin: 6px 0 4px;
924
934
  }
925
935
 
926
936
  .fw-bmh {
@@ -991,6 +1001,37 @@ input.filter {
991
1001
  max-width: 420px; /* tracker issue 2086 — the note's width is the note's own, not its call site's */
992
1002
  }
993
1003
 
1004
+ /* The line beside the primary action: what happens next when it is available, and why it is not when
1005
+ it is not. Capped here rather than at the call site for the same reason the note above is — width is
1006
+ a decision this file makes. Narrower than the note because it sits IN the action row, beside a
1007
+ button, rather than replacing it. */
1008
+ .footer-hint {
1009
+ max-width: 260px;
1010
+ font-size: 12px;
1011
+ line-height: 1.4;
1012
+ color: var(--text-muted);
1013
+ }
1014
+
1015
+ /* The acknowledgement a save leaves beside the button that was pressed.
1016
+ Quiet on purpose: it is a receipt, not an alert, and it sits in the same row as the primary action
1017
+ without competing with it. No new colour — `--tone-minimal` (the palette's clear-green) and the hairline are what the rest of
1018
+ this file already uses for a settled, good state. */
1019
+ .save-done {
1020
+ display: inline-flex;
1021
+ align-items: center;
1022
+ gap: 7px;
1023
+ font-size: 12.5px;
1024
+ color: var(--text-body);
1025
+ background: var(--surface-sunken);
1026
+ border: 1px solid var(--border-hairline);
1027
+ border-radius: 7px;
1028
+ padding: 7px 11px;
1029
+ }
1030
+ .save-done svg {
1031
+ color: var(--tone-minimal);
1032
+ flex: none;
1033
+ }
1034
+
994
1035
  .fw-meta {
995
1036
  font-size: 12px;
996
1037
  color: var(--text-muted);
@@ -10415,6 +10415,7 @@ var api = {
10415
10415
  accountNames: Object.fromEntries(Object.entries(asRecord(b["accountNames"])).filter(([, v]) => typeof v === "string" && v)),
10416
10416
  concurrentRuns: asNumber(b["concurrentRuns"]),
10417
10417
  engineMode: b["engineMode"] === "demo" ? "demo" : b["engineMode"] === "engine-unproven" ? "engine-unproven" : null,
10418
+ setupRoute: b["setupRoute"] === "packaged" ? "packaged" : b["setupRoute"] === "checkout" ? "checkout" : null,
10418
10419
  brand: typeof b["brand"] === "string" ? b["brand"] : "",
10419
10420
  stopControl: (() => {
10420
10421
  const c = asRecord(asRecord(b["controls"])["stop"]);
@@ -10765,6 +10766,15 @@ var api = {
10765
10766
  name,
10766
10767
  modifiedAt
10767
10768
  } : null;
10769
+ })(),
10770
+ staffRule: (() => {
10771
+ const s = b["staffRule"];
10772
+ const name = asString(s?.["name"]);
10773
+ const where = asString(s?.["where"]);
10774
+ return name && where ? {
10775
+ name,
10776
+ where
10777
+ } : null;
10768
10778
  })()
10769
10779
  })),
10770
10780
  adminObserved: () => call("/portal/admin/observed", (b) => ({
@@ -11421,7 +11431,8 @@ function AppShell({ render }) {
11421
11431
  ownerName,
11422
11432
  ownerKeys,
11423
11433
  go,
11424
- visit
11434
+ visit,
11435
+ sidebarCollapsed: collapsed
11425
11436
  }) : (0, import_jsx_runtime.jsx)("div", {
11426
11437
  className: "screen",
11427
11438
  children: (0, import_jsx_runtime.jsxs)("div", {
@@ -14711,6 +14722,15 @@ function nameBudget(product, names) {
14711
14722
  over: names - product.maxNames
14712
14723
  };
14713
14724
  }
14725
+ function missingPieces(names, classes, goods) {
14726
+ const out = [];
14727
+ if (!names.length) {
14728
+ out.push("Add the brand name you want cleared, in Names above.");
14729
+ return out;
14730
+ }
14731
+ if (!classes.length && !goods.trim()) out.push("Say what the name is used for — pick classes, or describe the goods and services. Either one is enough.");
14732
+ return out;
14733
+ }
14714
14734
  function blockers(d, product, names = 0) {
14715
14735
  const out = [];
14716
14736
  if (!product) {
@@ -14737,6 +14757,25 @@ function blockers(d, product, names = 0) {
14737
14757
  if (!product.available) out.push(product.unavailableNote || "That search is not available just now.");
14738
14758
  return out;
14739
14759
  }
14760
+ function readiness(i) {
14761
+ const NOTHING_PICKED = "Pick one of the searches above to begin.";
14762
+ const blocking = [
14763
+ [i.nameStops.length > 0, i.nameStops[0] ?? ""],
14764
+ [i.gaps.length > 0, i.gaps[0] ?? ""],
14765
+ [i.stops.length > 0, i.stops[0] ?? ""],
14766
+ [i.budget != null, i.budget ? `This search reads ${i.budget.allowed} name${i.budget.allowed === 1 ? "" : "s"} at a time, and you have ${i.budget.allowed + i.budget.over}.` : ""],
14767
+ [i.exhausted, "No searches left on today’s allowance."],
14768
+ [!i.hasProduct, NOTHING_PICKED]
14769
+ ].filter(([when]) => when);
14770
+ if (!blocking.length) return {
14771
+ ready: true,
14772
+ blockedBy: null
14773
+ };
14774
+ return {
14775
+ ready: false,
14776
+ blockedBy: blocking.map(([, sentence]) => sentence).find((s) => s.trim().length > 0) ?? NOTHING_PICKED
14777
+ };
14778
+ }
14740
14779
  var ALL_TERRITORIES$1 = [...REGIONS, ...COUNTRIES];
14741
14780
  var isKnownTerritory = (entry) => {
14742
14781
  const e = String(entry).trim().toLowerCase();
@@ -15233,6 +15272,16 @@ function useUnsaved(dirty) {
15233
15272
  ref.current = dirty;
15234
15273
  (0, import_react.useEffect)(() => registerGuard(() => ref.current), []);
15235
15274
  }
15275
+ function unsavedChanges({ current, empty, saved, submitted }) {
15276
+ if (submitted) return false;
15277
+ if (current === empty) return false;
15278
+ return current !== saved;
15279
+ }
15280
+ //#endregion
15281
+ //#region src/shell/ownerPickerHint.ts
15282
+ function ownerPickerHint(sidebarCollapsed) {
15283
+ return sidebarCollapsed ? "Open the menu on the left to pick one." : "Pick one at the top left.";
15284
+ }
15236
15285
  //#endregion
15237
15286
  //#region src/screens/NewClearance.tsx
15238
15287
  function readProblem(r) {
@@ -15288,12 +15337,23 @@ function NewClearance({ ctx }) {
15288
15337
  const [saveName, setSaveName] = (0, import_react.useState)("");
15289
15338
  const [saveText, setSaveText] = (0, import_react.useState)("");
15290
15339
  const [saveNote, setSaveNote] = (0, import_react.useState)(null);
15340
+ const [saveDone, setSaveDone] = (0, import_react.useState)(null);
15291
15341
  const [plan, setPlan] = (0, import_react.useState)(null);
15292
15342
  const [runFailure, setRunFailure] = (0, import_react.useState)(null);
15293
15343
  const [busy, setBusy] = (0, import_react.useState)(false);
15294
15344
  const [problem, setProblem] = (0, import_react.useState)(null);
15295
15345
  const [submitted, setSubmitted] = (0, import_react.useState)(null);
15296
- useUnsaved((0, import_react.useMemo)(() => submitted == null && JSON.stringify(draft) !== JSON.stringify(EMPTY), [draft, submitted]));
15346
+ const [savedDraft, setSavedDraft] = (0, import_react.useState)(null);
15347
+ useUnsaved((0, import_react.useMemo)(() => unsavedChanges({
15348
+ current: JSON.stringify(draft),
15349
+ empty: JSON.stringify(EMPTY),
15350
+ saved: savedDraft,
15351
+ submitted: submitted != null
15352
+ }), [
15353
+ draft,
15354
+ submitted,
15355
+ savedDraft
15356
+ ]));
15297
15357
  const { result: projectRes } = useLoad(() => draft.project ? api.project(account, draft.project) : Promise.resolve({
15298
15358
  kind: "ok",
15299
15359
  value: null
@@ -15314,7 +15374,7 @@ function NewClearance({ ctx }) {
15314
15374
  const scope = editing.recipe["scope"] ?? {};
15315
15375
  const classes = Array.isArray(scope["classes"]) ? scope["classes"].filter((c) => typeof c === "number") : [];
15316
15376
  const platforms = Array.isArray(scope["platforms"]) ? scope["platforms"].filter((p) => typeof p === "string") : [];
15317
- setDraft((d) => ({
15377
+ writeDraft((d) => ({
15318
15378
  ...d,
15319
15379
  pick: editingPick,
15320
15380
  savedSearch: "",
@@ -15358,8 +15418,12 @@ function NewClearance({ ctx }) {
15358
15418
  platforms: marketplacesApply ? own.platforms.length + parseList(draft.platforms).length : 0,
15359
15419
  density: own.density
15360
15420
  };
15421
+ const writeDraft = (next) => {
15422
+ setSaveDone(null);
15423
+ setDraft(next);
15424
+ };
15361
15425
  const edit = (patch) => {
15362
- setDraft((d) => ({
15426
+ writeDraft((d) => ({
15363
15427
  ...d,
15364
15428
  ...patch
15365
15429
  }));
@@ -15382,7 +15446,7 @@ function NewClearance({ ctx }) {
15382
15446
  return;
15383
15447
  }
15384
15448
  const { read, dropped, worldwide } = resolveRead(r.value);
15385
- setDraft((d) => {
15449
+ writeDraft((d) => {
15386
15450
  const before = {
15387
15451
  draft: d.pick,
15388
15452
  names: d.names,
@@ -15443,11 +15507,11 @@ function NewClearance({ ctx }) {
15443
15507
  };
15444
15508
  case "pickAccount": return {
15445
15509
  title: "Choose a brand owner",
15446
- lines: ["Pick who this clearance is for, at the top left."]
15510
+ lines: [`Pick who this clearance is for. ${ownerPickerHint(ctx.sidebarCollapsed)}`]
15447
15511
  };
15448
15512
  case "notFound": return {
15449
15513
  title: "That is not available to you",
15450
- lines: ["Check the brand owner selected at the top left."]
15514
+ lines: ["Check which brand owner is selected."]
15451
15515
  };
15452
15516
  case "noAccess": return {
15453
15517
  title: "This address has no access yet",
@@ -15504,6 +15568,7 @@ function NewClearance({ ctx }) {
15504
15568
  } else setRunFailure(explain(r));
15505
15569
  };
15506
15570
  const doSave = async () => {
15571
+ setSaveDone(null);
15507
15572
  const label = saveName.trim();
15508
15573
  const slug = editingSlug ?? label.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 39);
15509
15574
  if (slug.length < 2) {
@@ -15529,6 +15594,12 @@ function NewClearance({ ctx }) {
15529
15594
  });
15530
15595
  setBusy(false);
15531
15596
  if (isOk(r)) {
15597
+ setSavedDraft(JSON.stringify({
15598
+ ...EMPTY,
15599
+ pick: draft.pick,
15600
+ classes: draft.classes,
15601
+ platforms: draft.platforms
15602
+ }));
15532
15603
  if (editingSlug) {
15533
15604
  ctx.go("/portal/brand/searches");
15534
15605
  return;
@@ -15538,6 +15609,7 @@ function NewClearance({ ctx }) {
15538
15609
  setSaveText("");
15539
15610
  const uncommitted = notCommitted(r);
15540
15611
  setSaveNote(uncommitted ? `Saved as “${label}”. ${uncommitted}` : `Saved as “${label}” — it is in your custom searches.`);
15612
+ setSaveDone(label);
15541
15613
  } else if (r.kind === "conflict") setSaveNote("Someone else changed this custom search while you were editing. Reload and re-apply.");
15542
15614
  else setSaveNote("That could not be saved. Try a different name, or check it on Custom searches.");
15543
15615
  };
@@ -15545,7 +15617,7 @@ function NewClearance({ ctx }) {
15545
15617
  go: ctx.go,
15546
15618
  onAnother: () => {
15547
15619
  setSubmitted(null);
15548
- setDraft(EMPTY);
15620
+ writeDraft(EMPTY);
15549
15621
  setEntry(null);
15550
15622
  }
15551
15623
  });
@@ -15553,12 +15625,17 @@ function NewClearance({ ctx }) {
15553
15625
  className: "screen",
15554
15626
  children: (0, import_jsx_runtime.jsxs)("div", {
15555
15627
  className: "notice",
15556
- children: [(0, import_jsx_runtime.jsx)("b", { children: "Choose a brand owner first" }), (0, import_jsx_runtime.jsx)("p", {
15628
+ children: [(0, import_jsx_runtime.jsx)("b", { children: "Choose a brand owner first" }), (0, import_jsx_runtime.jsxs)("p", {
15557
15629
  style: {
15558
15630
  margin: "6px 0 0",
15559
15631
  color: "var(--text-muted)"
15560
15632
  },
15561
- children: "A clearance is filed for one brand owner. Pick one at the top left, then start the search."
15633
+ children: [
15634
+ "A clearance is filed for one brand owner. ",
15635
+ ownerPickerHint(ctx.sidebarCollapsed),
15636
+ " ",
15637
+ "Then start the search."
15638
+ ]
15562
15639
  })]
15563
15640
  })
15564
15641
  });
@@ -15572,9 +15649,16 @@ function NewClearance({ ctx }) {
15572
15649
  const markLimit = searches.value.maxMarkName;
15573
15650
  const nameStops = overlong.map((n) => `“${n.slice(0, 30)}…” is ${n.length} characters. A mark name may be at most ${markLimit} — it becomes this run's name and part of every report link, so it cannot carry a description. Put the goods and the description in their own fields below.`);
15574
15651
  const budget = nameBudget(activeLevel, names.length);
15575
- const overBudget = budget != null;
15576
15652
  const exhausted = usage?.capped === true && usage.dailyRuns != null && usage.today >= usage.dailyRuns;
15577
- const ready = !(!names.length || !classes.length && !draft.goods.trim()) && !stops.length && !nameStops.length && !overBudget && !exhausted && activeLevel != null;
15653
+ const gaps = missingPieces(names, classes, draft.goods);
15654
+ const { ready, blockedBy } = readiness({
15655
+ gaps,
15656
+ stops,
15657
+ nameStops,
15658
+ budget,
15659
+ exhausted,
15660
+ hasProduct: activeLevel != null
15661
+ });
15578
15662
  const startedFrom = draft.savedSearch ? savedRow?.label ?? "a custom search" : activeLevel?.name ?? "no search picked yet";
15579
15663
  return (0, import_jsx_runtime.jsxs)("div", {
15580
15664
  className: "screen",
@@ -16431,6 +16515,24 @@ function NewClearance({ ctx }) {
16431
16515
  })
16432
16516
  })]
16433
16517
  }),
16518
+ gaps.length ? (0, import_jsx_runtime.jsxs)("div", {
16519
+ className: "notice",
16520
+ style: {
16521
+ borderColor: "var(--tone-medium)",
16522
+ margin: 0
16523
+ },
16524
+ children: [(0, import_jsx_runtime.jsx)("b", { children: gaps.length === 1 ? "One thing left to fill in" : "A couple of things left to fill in" }), (0, import_jsx_runtime.jsx)("ul", {
16525
+ style: {
16526
+ margin: "8px 0 0",
16527
+ paddingLeft: 18,
16528
+ color: "var(--text-muted)"
16529
+ },
16530
+ children: gaps.map((g, i) => (0, import_jsx_runtime.jsx)("li", {
16531
+ style: { marginBottom: 3 },
16532
+ children: g
16533
+ }, `g${i}`))
16534
+ })]
16535
+ }) : null,
16434
16536
  stops.length || nameStops.length ? (0, import_jsx_runtime.jsxs)("div", {
16435
16537
  className: "notice",
16436
16538
  style: {
@@ -16487,12 +16589,15 @@ function NewClearance({ ctx }) {
16487
16589
  ready,
16488
16590
  busy,
16489
16591
  demoMode: ctx.me.engineMode === "demo",
16592
+ setupRoute: ctx.me.setupRoute,
16490
16593
  saveOpen,
16491
16594
  saveName,
16492
16595
  saveText,
16493
16596
  saveNote,
16597
+ saveDone,
16494
16598
  editing: editingSlug != null,
16495
16599
  canSave: !draft.savedSearch && !stops.length,
16600
+ blockedBy,
16496
16601
  onSaveOpen: () => {
16497
16602
  setSaveOpen(true);
16498
16603
  setSaveName(activeLevel ? `${activeLevel.name}` : "Custom search");
@@ -16508,7 +16613,8 @@ function NewClearance({ ctx }) {
16508
16613
  setSaveNote(null);
16509
16614
  },
16510
16615
  onSave: doSave,
16511
- onReview: doPlan
16616
+ onReview: doPlan,
16617
+ onSeeSaved: () => ctx.go("/portal/brand/searches")
16512
16618
  }),
16513
16619
  plan ? (0, import_jsx_runtime.jsx)(ReviewDialog, {
16514
16620
  plan,
@@ -16655,7 +16761,7 @@ function NameWall({ count, allowed, first, canScreen, screenName, onScreenAll })
16655
16761
  }) : null]
16656
16762
  });
16657
16763
  }
16658
- function Footer({ startedFrom, tier, detail, units, cost, duration, runs, ready, busy, demoMode, saveOpen, saveName, saveText, saveNote, editing, canSave, onSaveOpen, onSaveName, onSaveText, onSaveCancel, onSave, onReview }) {
16764
+ function Footer({ startedFrom, tier, detail, units, cost, duration, runs, ready, busy, demoMode, setupRoute, saveOpen, saveName, saveText, saveNote, saveDone, editing, canSave, blockedBy, onSaveOpen, onSaveName, onSaveText, onSaveCancel, onSave, onReview, onSeeSaved }) {
16659
16765
  return (0, import_jsx_runtime.jsxs)("div", {
16660
16766
  className: "composer-footer",
16661
16767
  children: [
@@ -16849,6 +16955,25 @@ function Footer({ startedFrom, tier, detail, units, cost, duration, runs, ready,
16849
16955
  })
16850
16956
  ]
16851
16957
  }) : null,
16958
+ saveDone ? (0, import_jsx_runtime.jsxs)("span", {
16959
+ className: "save-done",
16960
+ role: "status",
16961
+ children: [
16962
+ (0, import_jsx_runtime.jsx)(Icon, {
16963
+ name: "check",
16964
+ size: 13
16965
+ }),
16966
+ "Saved as “",
16967
+ saveDone,
16968
+ "”",
16969
+ (0, import_jsx_runtime.jsx)("button", {
16970
+ type: "button",
16971
+ className: "link-btn",
16972
+ onClick: onSeeSaved,
16973
+ children: "See it"
16974
+ })
16975
+ ]
16976
+ }) : null,
16852
16977
  demoMode ? (0, import_jsx_runtime.jsxs)("div", {
16853
16978
  className: "footer-demo-note",
16854
16979
  role: "status",
@@ -16859,19 +16984,29 @@ function Footer({ startedFrom, tier, detail, units, cost, duration, runs, ready,
16859
16984
  children: [
16860
16985
  (0, import_jsx_runtime.jsx)("strong", { children: "No search engine is attached to this install." }),
16861
16986
  " ",
16862
- "Everything else works — the example report, its audit trail and the assistant connection are live right now. To start new searches, install a reasoning CLI and sign in, then run",
16987
+ "Everything else works — the example report, its audit trail and the assistant connection are live right now. To start new searches, install a reasoning CLI and sign in, then run the setup wizard again",
16863
16988
  " ",
16864
- (0, import_jsx_runtime.jsx)("code", { children: "npm run setup" }),
16865
- " again."
16989
+ (0, import_jsx_runtime.jsx)(SetupCommand, { route: setupRoute })
16866
16990
  ]
16867
- }) : (0, import_jsx_runtime.jsxs)("button", {
16868
- type: "button",
16869
- className: "btn-primary",
16870
- disabled: !ready || busy,
16871
- onClick: onReview,
16872
- children: [busy ? "Checking…" : "Review clearance", (0, import_jsx_runtime.jsx)(Icon, {
16873
- name: "arrow-right",
16874
- size: 14
16991
+ }) : (0, import_jsx_runtime.jsxs)("span", {
16992
+ style: {
16993
+ display: "inline-flex",
16994
+ alignItems: "center",
16995
+ gap: 10,
16996
+ flexWrap: "wrap"
16997
+ },
16998
+ children: [(0, import_jsx_runtime.jsxs)("button", {
16999
+ type: "button",
17000
+ className: "btn-primary",
17001
+ disabled: !ready || busy,
17002
+ onClick: onReview,
17003
+ children: [busy ? "Checking…" : "Start a search", (0, import_jsx_runtime.jsx)(Icon, {
17004
+ name: "arrow-right",
17005
+ size: 14
17006
+ })]
17007
+ }), (0, import_jsx_runtime.jsx)("span", {
17008
+ className: "footer-hint",
17009
+ children: blockedBy ?? "You will see the coverage and what it costs before anything runs."
16875
17010
  })]
16876
17011
  })
16877
17012
  ]
@@ -16879,6 +17014,18 @@ function Footer({ startedFrom, tier, detail, units, cost, duration, runs, ready,
16879
17014
  ]
16880
17015
  });
16881
17016
  }
17017
+ function SetupCommand({ route }) {
17018
+ if (route === "packaged") return (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [(0, import_jsx_runtime.jsx)("code", { children: "npx clearotron install" }), "."] });
17019
+ if (route === "checkout") return (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [(0, import_jsx_runtime.jsx)("code", { children: "npm run setup" }), "."] });
17020
+ return (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
17021
+ "— ",
17022
+ (0, import_jsx_runtime.jsx)("code", { children: "npx clearotron install" }),
17023
+ " if you installed the package, or ",
17024
+ (0, import_jsx_runtime.jsx)("code", { children: "npm run setup" }),
17025
+ " ",
17026
+ "from a copy of the source."
17027
+ ] });
17028
+ }
16882
17029
  function Lever({ label, hint, on, onToggle, coming }) {
16883
17030
  return (0, import_jsx_runtime.jsxs)("button", {
16884
17031
  type: "button",
@@ -17528,9 +17675,9 @@ function SavedSearches({ ctx }) {
17528
17675
  const [busy, setBusy] = (0, import_react.useState)(null);
17529
17676
  const [confirming, setConfirming] = (0, import_react.useState)(null);
17530
17677
  const [problem, setProblem] = (0, import_react.useState)(null);
17531
- if (needsOwner) return (0, import_jsx_runtime.jsx)(PickOwner, {});
17678
+ if (needsOwner) return (0, import_jsx_runtime.jsx)(PickOwner, { sidebarCollapsed: ctx.sidebarCollapsed });
17532
17679
  if (!result) return (0, import_jsx_runtime.jsx)("div", { className: "screen" });
17533
- if (result.kind === "pickAccount") return (0, import_jsx_runtime.jsx)(PickOwner, {});
17680
+ if (result.kind === "pickAccount") return (0, import_jsx_runtime.jsx)(PickOwner, { sidebarCollapsed: ctx.sidebarCollapsed });
17534
17681
  if (result.kind !== "ok") return (0, import_jsx_runtime.jsxs)("div", {
17535
17682
  className: "screen",
17536
17683
  children: [(0, import_jsx_runtime.jsx)(Heading, {}), (0, import_jsx_runtime.jsxs)("div", {
@@ -17802,17 +17949,17 @@ function Empty({ go }) {
17802
17949
  })]
17803
17950
  });
17804
17951
  }
17805
- function PickOwner() {
17952
+ function PickOwner({ sidebarCollapsed }) {
17806
17953
  return (0, import_jsx_runtime.jsx)("div", {
17807
17954
  className: "screen",
17808
17955
  children: (0, import_jsx_runtime.jsxs)("div", {
17809
17956
  className: "notice",
17810
- children: [(0, import_jsx_runtime.jsx)("b", { children: "Choose a brand owner first" }), (0, import_jsx_runtime.jsx)("p", {
17957
+ children: [(0, import_jsx_runtime.jsx)("b", { children: "Choose a brand owner first" }), (0, import_jsx_runtime.jsxs)("p", {
17811
17958
  style: {
17812
17959
  margin: "6px 0 0",
17813
17960
  color: "var(--text-muted)"
17814
17961
  },
17815
- children: "Custom searches belong to one brand owner. Pick one at the top left."
17962
+ children: ["Custom searches belong to one brand owner. ", ownerPickerHint(sidebarCollapsed)]
17816
17963
  })]
17817
17964
  })
17818
17965
  });
@@ -18490,7 +18637,7 @@ var PROFILE_FIELDS = [
18490
18637
  kind: "numbers",
18491
18638
  group: "defaults",
18492
18639
  picker: "classes",
18493
- hint: "Default Nice classes, 1-45, always changeable at search time. Commas, spaces or new lines all work."
18640
+ hint: "The numbered categories a trademark is registered in — 9 is software, 25 is clothing, 41 is training. These are the Nice classes, 1 to 45. Leave this empty and each search states its own. Commas, spaces or new lines all work."
18494
18641
  },
18495
18642
  {
18496
18643
  key: "defaultJurisdictions",
@@ -18503,7 +18650,7 @@ var PROFILE_FIELDS = [
18503
18650
  ok: isKnownTerritory,
18504
18651
  expected: "a territory from the picker below"
18505
18652
  },
18506
- hint: "One per line or comma-separated. Searched by default unless a clearance specifies otherwise."
18653
+ hint: "One per line or comma-separated — pick from the list below. A region counts as one entry: European Union covers its member states, so there is no need to add them. Anything not in the list is kept and flagged rather than refused, because a search can name a territory this list does not carry."
18507
18654
  },
18508
18655
  {
18509
18656
  key: "platforms",
@@ -18906,12 +19053,12 @@ function Profile({ ctx }) {
18906
19053
  className: "screen",
18907
19054
  children: (0, import_jsx_runtime.jsxs)("div", {
18908
19055
  className: "notice",
18909
- children: [(0, import_jsx_runtime.jsx)("b", { children: "Choose a brand owner first" }), (0, import_jsx_runtime.jsx)("p", {
19056
+ children: [(0, import_jsx_runtime.jsx)("b", { children: "Choose a brand owner first" }), (0, import_jsx_runtime.jsxs)("p", {
18910
19057
  style: {
18911
19058
  margin: "6px 0 0",
18912
19059
  color: "var(--text-muted)"
18913
19060
  },
18914
- children: "A profile belongs to one brand owner. Pick one at the top left."
19061
+ children: ["A profile belongs to one brand owner. ", ownerPickerHint(ctx.sidebarCollapsed)]
18915
19062
  })]
18916
19063
  })
18917
19064
  });
@@ -19091,11 +19238,15 @@ function Profile({ ctx }) {
19091
19238
  children: "Save"
19092
19239
  }),
19093
19240
  (0, import_jsx_runtime.jsx)("span", {
19094
- style: {
19241
+ style: dirty && !checked ? {
19242
+ fontSize: 13,
19243
+ color: "var(--tone-medium)",
19244
+ fontWeight: 600
19245
+ } : {
19095
19246
  fontSize: 12.5,
19096
19247
  color: "var(--text-muted)"
19097
19248
  },
19098
- children: !dirty ? "No changes." : checked ? "Checked — safe to save." : "Check first. A profile the engine cannot read stops this account searching."
19249
+ children: !dirty ? "No changes." : checked ? "Checked — safe to save." : "Press Check before saving. A profile the engine cannot read stops this account searching."
19099
19250
  })
19100
19251
  ]
19101
19252
  })
@@ -19194,7 +19345,10 @@ function FrameworkBlock({ readOnly, framework, staff }) {
19194
19345
  " rated under the Generic default — but its definitions are unavailable, so the bands cannot be shown here. This needs an administrator to look at it."
19195
19346
  ] }) : (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [(0, import_jsx_runtime.jsx)("b", { children: "Generic default" }), " — no custom framework is on file for this brand owner; their matters are rated under the generic framework."] })
19196
19347
  }),
19197
- bands.length ? (0, import_jsx_runtime.jsx)("div", {
19348
+ bands.length ? (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [(0, import_jsx_runtime.jsx)("div", {
19349
+ className: "fw-ladder-label",
19350
+ children: "Its ratings, strongest concern first:"
19351
+ }), (0, import_jsx_runtime.jsx)("div", {
19198
19352
  className: "fw-ladder",
19199
19353
  children: bands.map((b, i) => {
19200
19354
  const band = rec(b);
@@ -19204,7 +19358,7 @@ function FrameworkBlock({ readOnly, framework, staff }) {
19204
19358
  tone: band?.["tone"]
19205
19359
  }, i) : null;
19206
19360
  })
19207
- }) : null,
19361
+ })] }) : null,
19208
19362
  meanings.length ? (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [(0, import_jsx_runtime.jsx)("div", {
19209
19363
  className: "fw-bmh",
19210
19364
  children: "What the bands mean"
@@ -19408,7 +19562,7 @@ function explain(r) {
19408
19562
  };
19409
19563
  case "notFound": return {
19410
19564
  title: "That is not available to you",
19411
- lines: ["Check the brand owner selected at the top left."]
19565
+ lines: ["Check which brand owner is selected."]
19412
19566
  };
19413
19567
  case "noAccess": return {
19414
19568
  title: "This address has no access yet",
@@ -19455,12 +19609,12 @@ function Projects({ ctx }) {
19455
19609
  className: "screen",
19456
19610
  children: (0, import_jsx_runtime.jsxs)("div", {
19457
19611
  className: "notice",
19458
- children: [(0, import_jsx_runtime.jsx)("b", { children: "Choose a brand owner first" }), (0, import_jsx_runtime.jsx)("p", {
19612
+ children: [(0, import_jsx_runtime.jsx)("b", { children: "Choose a brand owner first" }), (0, import_jsx_runtime.jsxs)("p", {
19459
19613
  style: {
19460
19614
  margin: "6px 0 0",
19461
19615
  color: "var(--text-muted)"
19462
19616
  },
19463
- children: "Projects belong to one brand owner. Pick one at the top left."
19617
+ children: ["Projects belong to one brand owner. ", ownerPickerHint(ctx.sidebarCollapsed)]
19464
19618
  })]
19465
19619
  })
19466
19620
  });
@@ -20390,7 +20544,10 @@ function GlobalConfig({ ctx }) {
20390
20544
  }),
20391
20545
  (0, import_jsx_runtime.jsx)(Group, {
20392
20546
  title: "Engine",
20393
- children: v.engine ? (0, import_jsx_runtime.jsx)(Engine, { engine: v.engine }) : (0, import_jsx_runtime.jsx)(NotRecorded, {
20547
+ children: v.engine ? (0, import_jsx_runtime.jsx)(Engine, {
20548
+ engine: v.engine,
20549
+ programDisputed: (v.lastRun?.disagrees ?? []).some((d) => d.what === "engine program")
20550
+ }) : (0, import_jsx_runtime.jsx)(NotRecorded, {
20394
20551
  what: "which engine is running",
20395
20552
  source: v.source
20396
20553
  })
@@ -20525,12 +20682,13 @@ function Auth({ auth }) {
20525
20682
  faults: []
20526
20683
  }) : null] });
20527
20684
  }
20528
- function Engine({ engine }) {
20685
+ function Engine({ engine, programDisputed = false }) {
20529
20686
  const billed = engine.billing.apiBilled ? "API key" : "Subscription";
20530
20687
  const faults = [
20531
20688
  ...engine.known ? [] : [`This build does not ship an engine called ${engine.id}.`],
20532
20689
  ...engine.billing.missing.length ? [`Set to bill an API key, and ${engine.billing.missing.join(" and ")} is not set — a run is refused rather than billed to the subscription.`] : [],
20533
- ...engine.binaryPresent ? [] : ["The engine program cannot be found or run on this machine."]
20690
+ ...engine.binaryPresent ? [] : ["The engine program cannot be found or run on this machine."],
20691
+ ...programDisputed && engine.binaryPresent ? ["The engine program is on this machine, but the engine could not find it when it last started — so a new search will refuse. Restart the engine service, or install the CLI where the service can see it."] : []
20534
20692
  ];
20535
20693
  return (0, import_jsx_runtime.jsx)(Row$1, {
20536
20694
  ok: faults.length === 0,
@@ -20748,7 +20906,10 @@ function PeopleAccess({ ctx }) {
20748
20906
  display: "grid",
20749
20907
  gap: 6
20750
20908
  },
20751
- children: (0, import_jsx_runtime.jsx)(StaffRuleRow, { domains: v.staffDomains })
20909
+ children: (0, import_jsx_runtime.jsx)(StaffRuleRow, {
20910
+ domains: v.staffDomains,
20911
+ rule: v.staffRule
20912
+ })
20752
20913
  })] }) : null,
20753
20914
  (0, import_jsx_runtime.jsx)("div", {
20754
20915
  style: {
@@ -20838,15 +20999,15 @@ function Roles({ brand }) {
20838
20999
  ]
20839
21000
  });
20840
21001
  }
20841
- function StaffRuleRow({ domains }) {
20842
- return (0, import_jsx_runtime.jsx)("div", {
21002
+ function StaffRuleRow({ domains, rule }) {
21003
+ return (0, import_jsx_runtime.jsxs)("div", {
20843
21004
  style: {
20844
21005
  padding: "10px 13px",
20845
21006
  borderRadius: 9,
20846
21007
  border: "1px solid var(--border-hairline)",
20847
21008
  background: "var(--surface-raised)"
20848
21009
  },
20849
- children: (0, import_jsx_runtime.jsxs)("div", {
21010
+ children: [(0, import_jsx_runtime.jsxs)("div", {
20850
21011
  style: {
20851
21012
  display: "flex",
20852
21013
  alignItems: "baseline",
@@ -20871,7 +21032,23 @@ function StaffRuleRow({ domains }) {
20871
21032
  },
20872
21033
  children: "a rule, not a person"
20873
21034
  })]
20874
- })
21035
+ }), rule ? (0, import_jsx_runtime.jsxs)("p", {
21036
+ style: {
21037
+ margin: "6px 0 0",
21038
+ color: "var(--text-muted)",
21039
+ fontSize: 12.5
21040
+ },
21041
+ children: [
21042
+ "Granted by ",
21043
+ (0, import_jsx_runtime.jsx)("b", {
21044
+ className: "mono",
21045
+ children: rule.name
21046
+ }),
21047
+ ", ",
21048
+ rule.where,
21049
+ ". Change or remove that setting and restart this instance to undo the rule. Nothing outside this machine created it."
21050
+ ]
21051
+ }) : null]
20875
21052
  });
20876
21053
  }
20877
21054
  function Observed({ result }) {
@@ -49,8 +49,8 @@
49
49
  -->
50
50
  <link rel="preconnect" href="https://api.fontshare.com" crossorigin />
51
51
  <link href="https://api.fontshare.com/v2/css?f[]=satoshi@400,500,700,900&display=swap" rel="stylesheet" />
52
- <script type="module" crossorigin src="/portal/assets/index-BUGgiYtF.js"></script>
53
- <link rel="stylesheet" crossorigin href="/portal/assets/index-Ct3iQtZ2.css">
52
+ <script type="module" crossorigin src="/portal/assets/index-CEYvXdqH.js"></script>
53
+ <link rel="stylesheet" crossorigin href="/portal/assets/index-1ziUJX1E.css">
54
54
  </head>
55
55
  <body>
56
56
  <div id="root"></div>