@trops/dash-core 0.1.492 → 0.1.494

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.
package/dist/index.esm.js CHANGED
@@ -48715,6 +48715,223 @@ var AiAssistantSection = function AiAssistantSection() {
48715
48715
  });
48716
48716
  };
48717
48717
 
48718
+ var GrantManuallyModal = function GrantManuallyModal(_ref) {
48719
+ var isOpen = _ref.isOpen,
48720
+ setIsOpen = _ref.setIsOpen,
48721
+ widgetId = _ref.widgetId,
48722
+ _ref$knownServerNames = _ref.knownServerNames,
48723
+ knownServerNames = _ref$knownServerNames === void 0 ? [] : _ref$knownServerNames,
48724
+ onGranted = _ref.onGranted;
48725
+ var _useState = useState(""),
48726
+ _useState2 = _slicedToArray(_useState, 2),
48727
+ serverName = _useState2[0],
48728
+ setServerName = _useState2[1];
48729
+ var _useState3 = useState(""),
48730
+ _useState4 = _slicedToArray(_useState3, 2),
48731
+ toolsText = _useState4[0],
48732
+ setToolsText = _useState4[1];
48733
+ var _useState5 = useState(""),
48734
+ _useState6 = _slicedToArray(_useState5, 2),
48735
+ readPathsText = _useState6[0],
48736
+ setReadPathsText = _useState6[1];
48737
+ var _useState7 = useState(""),
48738
+ _useState8 = _slicedToArray(_useState7, 2),
48739
+ writePathsText = _useState8[0],
48740
+ setWritePathsText = _useState8[1];
48741
+ var _useState9 = useState(null),
48742
+ _useState0 = _slicedToArray(_useState9, 2),
48743
+ error = _useState0[0],
48744
+ setError = _useState0[1];
48745
+ var _useState1 = useState(false),
48746
+ _useState10 = _slicedToArray(_useState1, 2),
48747
+ isSubmitting = _useState10[0],
48748
+ setIsSubmitting = _useState10[1];
48749
+ useEffect(function () {
48750
+ if (isOpen) {
48751
+ setServerName("");
48752
+ setToolsText("");
48753
+ setReadPathsText("");
48754
+ setWritePathsText("");
48755
+ setError(null);
48756
+ setIsSubmitting(false);
48757
+ }
48758
+ }, [isOpen]);
48759
+ var splitLines = function splitLines(s) {
48760
+ return s.split(/\r?\n/).map(function (l) {
48761
+ return l.trim();
48762
+ }).filter(Boolean);
48763
+ };
48764
+ var splitCsv = function splitCsv(s) {
48765
+ return s.split(",").map(function (t) {
48766
+ return t.trim();
48767
+ }).filter(Boolean);
48768
+ };
48769
+ var handleGrant = /*#__PURE__*/function () {
48770
+ var _ref2 = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee() {
48771
+ var tools, perms, _window$mainApi, _window$mainApi$setGr, ok, _t;
48772
+ return _regeneratorRuntime.wrap(function (_context) {
48773
+ while (1) switch (_context.prev = _context.next) {
48774
+ case 0:
48775
+ setError(null);
48776
+ if (serverName.trim()) {
48777
+ _context.next = 1;
48778
+ break;
48779
+ }
48780
+ setError("Server name is required.");
48781
+ return _context.abrupt("return");
48782
+ case 1:
48783
+ tools = splitCsv(toolsText);
48784
+ if (!(tools.length === 0)) {
48785
+ _context.next = 2;
48786
+ break;
48787
+ }
48788
+ setError("At least one tool name is required.");
48789
+ return _context.abrupt("return");
48790
+ case 2:
48791
+ perms = {
48792
+ grantOrigin: "manual",
48793
+ servers: _defineProperty({}, serverName.trim(), {
48794
+ tools: tools,
48795
+ readPaths: splitLines(readPathsText),
48796
+ writePaths: splitLines(writePathsText)
48797
+ })
48798
+ };
48799
+ setIsSubmitting(true);
48800
+ _context.prev = 3;
48801
+ _context.next = 4;
48802
+ return typeof window !== "undefined" ? (_window$mainApi = window.mainApi) === null || _window$mainApi === void 0 || (_window$mainApi = _window$mainApi.widgetMcp) === null || _window$mainApi === void 0 || (_window$mainApi$setGr = _window$mainApi.setGrant) === null || _window$mainApi$setGr === void 0 ? void 0 : _window$mainApi$setGr.call(_window$mainApi, widgetId, perms) : null;
48803
+ case 4:
48804
+ ok = _context.sent;
48805
+ if (!(ok === false)) {
48806
+ _context.next = 5;
48807
+ break;
48808
+ }
48809
+ setError("Could not save grant.");
48810
+ setIsSubmitting(false);
48811
+ return _context.abrupt("return");
48812
+ case 5:
48813
+ if (typeof onGranted === "function") onGranted();
48814
+ setIsOpen(false);
48815
+ _context.next = 7;
48816
+ break;
48817
+ case 6:
48818
+ _context.prev = 6;
48819
+ _t = _context["catch"](3);
48820
+ setError((_t === null || _t === void 0 ? void 0 : _t.message) || String(_t));
48821
+ setIsSubmitting(false);
48822
+ case 7:
48823
+ case "end":
48824
+ return _context.stop();
48825
+ }
48826
+ }, _callee, null, [[3, 6]]);
48827
+ }));
48828
+ return function handleGrant() {
48829
+ return _ref2.apply(this, arguments);
48830
+ };
48831
+ }();
48832
+ if (!widgetId) return null;
48833
+ return /*#__PURE__*/jsx(Modal, {
48834
+ isOpen: isOpen,
48835
+ setIsOpen: setIsOpen,
48836
+ children: /*#__PURE__*/jsxs("div", {
48837
+ className: "flex flex-col w-full max-w-xl ring-2 ring-amber-500",
48838
+ children: [/*#__PURE__*/jsxs("div", {
48839
+ className: "px-5 py-4 border-b border-gray-700",
48840
+ children: [/*#__PURE__*/jsxs("div", {
48841
+ className: "text-base font-semibold",
48842
+ children: ["Grant manually: ", widgetId]
48843
+ }), /*#__PURE__*/jsx("div", {
48844
+ className: "text-xs opacity-60 mt-1",
48845
+ children: "This widget did not declare its MCP needs and the install-time scanner found nothing. You are granting access based on your own judgment \u2014 be conservative. Revoke any time."
48846
+ })]
48847
+ }), /*#__PURE__*/jsxs("div", {
48848
+ className: "flex flex-col gap-4 px-5 py-4 max-h-96 overflow-y-auto",
48849
+ children: [/*#__PURE__*/jsxs("div", {
48850
+ className: "flex flex-col gap-1",
48851
+ children: [/*#__PURE__*/jsx("label", {
48852
+ className: "text-xs uppercase tracking-wider opacity-60",
48853
+ children: "Server name"
48854
+ }), /*#__PURE__*/jsx("input", {
48855
+ type: "text",
48856
+ list: "known-mcp-servers",
48857
+ value: serverName,
48858
+ onChange: function onChange(e) {
48859
+ return setServerName(e.target.value);
48860
+ },
48861
+ placeholder: "e.g. filesystem, github, slack",
48862
+ className: "text-xs px-2 py-1.5 rounded bg-gray-800 border border-gray-700"
48863
+ }), /*#__PURE__*/jsx("datalist", {
48864
+ id: "known-mcp-servers",
48865
+ children: knownServerNames.map(function (n) {
48866
+ return /*#__PURE__*/jsx("option", {
48867
+ value: n
48868
+ }, n);
48869
+ })
48870
+ })]
48871
+ }), /*#__PURE__*/jsxs("div", {
48872
+ className: "flex flex-col gap-1",
48873
+ children: [/*#__PURE__*/jsx("label", {
48874
+ className: "text-xs uppercase tracking-wider opacity-60",
48875
+ children: "Tools (comma-separated)"
48876
+ }), /*#__PURE__*/jsx("input", {
48877
+ type: "text",
48878
+ value: toolsText,
48879
+ onChange: function onChange(e) {
48880
+ return setToolsText(e.target.value);
48881
+ },
48882
+ placeholder: "e.g. read_file, list_directory",
48883
+ className: "text-xs px-2 py-1.5 rounded bg-gray-800 border border-gray-700 font-mono"
48884
+ })]
48885
+ }), /*#__PURE__*/jsxs("div", {
48886
+ className: "flex flex-col gap-1",
48887
+ children: [/*#__PURE__*/jsx("label", {
48888
+ className: "text-xs uppercase tracking-wider opacity-60",
48889
+ children: "Read paths (one per line, optional)"
48890
+ }), /*#__PURE__*/jsx("textarea", {
48891
+ value: readPathsText,
48892
+ onChange: function onChange(e) {
48893
+ return setReadPathsText(e.target.value);
48894
+ },
48895
+ placeholder: "/Users/jane/Documents\n/tmp/notes",
48896
+ rows: 3,
48897
+ className: "text-xs px-2 py-1.5 rounded bg-gray-800 border border-gray-700 font-mono"
48898
+ })]
48899
+ }), /*#__PURE__*/jsxs("div", {
48900
+ className: "flex flex-col gap-1",
48901
+ children: [/*#__PURE__*/jsx("label", {
48902
+ className: "text-xs uppercase tracking-wider opacity-60",
48903
+ children: "Write paths (one per line, optional)"
48904
+ }), /*#__PURE__*/jsx("textarea", {
48905
+ value: writePathsText,
48906
+ onChange: function onChange(e) {
48907
+ return setWritePathsText(e.target.value);
48908
+ },
48909
+ placeholder: "/tmp/output",
48910
+ rows: 3,
48911
+ className: "text-xs px-2 py-1.5 rounded bg-gray-800 border border-gray-700 font-mono"
48912
+ })]
48913
+ }), error && /*#__PURE__*/jsx("div", {
48914
+ className: "text-xs text-red-400 bg-red-900 bg-opacity-20 border border-red-700 rounded px-3 py-2",
48915
+ children: error
48916
+ })]
48917
+ }), /*#__PURE__*/jsxs("div", {
48918
+ className: "flex justify-end gap-2 px-5 py-3 border-t border-gray-700",
48919
+ children: [/*#__PURE__*/jsx(Button, {
48920
+ title: "Cancel",
48921
+ onClick: function onClick() {
48922
+ return setIsOpen(false);
48923
+ },
48924
+ disabled: isSubmitting
48925
+ }), /*#__PURE__*/jsx(Button, {
48926
+ title: "Grant",
48927
+ onClick: handleGrant,
48928
+ disabled: isSubmitting
48929
+ })]
48930
+ })]
48931
+ })
48932
+ });
48933
+ };
48934
+
48718
48935
  var PrivacySecuritySection = function PrivacySecuritySection() {
48719
48936
  var _useState = useState([]),
48720
48937
  _useState2 = _slicedToArray(_useState, 2),
@@ -48728,6 +48945,14 @@ var PrivacySecuritySection = function PrivacySecuritySection() {
48728
48945
  _useState6 = _slicedToArray(_useState5, 2),
48729
48946
  error = _useState6[0],
48730
48947
  setError = _useState6[1];
48948
+ var _useState7 = useState(null),
48949
+ _useState8 = _slicedToArray(_useState7, 2),
48950
+ manualGrantWidgetId = _useState8[0],
48951
+ setManualGrantWidgetId = _useState8[1];
48952
+ var _useState9 = useState([]),
48953
+ _useState0 = _slicedToArray(_useState9, 2),
48954
+ knownServerNames = _useState0[0],
48955
+ setKnownServerNames = _useState0[1];
48731
48956
  var reload = useCallback(/*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee() {
48732
48957
  var _api$widgetMcp, api, result, _t;
48733
48958
  return _regeneratorRuntime.wrap(function (_context) {
@@ -48768,42 +48993,55 @@ var PrivacySecuritySection = function PrivacySecuritySection() {
48768
48993
  useEffect(function () {
48769
48994
  reload();
48770
48995
  }, [reload]);
48771
- var revokeWidget = /*#__PURE__*/function () {
48772
- var _ref2 = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee2(widgetId) {
48773
- var _window$mainApi, _window$mainApi$revok, _t2;
48996
+
48997
+ // Pull catalog server names once, used as a datalist hint in the
48998
+ // manual-grant modal. Best-effort — if the API isn't there, the
48999
+ // datalist is just empty.
49000
+ useEffect(function () {
49001
+ var cancelled = false;
49002
+ _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee2() {
49003
+ var _api$mcp, _api$mcp$getCatalog, api, result, servers;
48774
49004
  return _regeneratorRuntime.wrap(function (_context2) {
48775
49005
  while (1) switch (_context2.prev = _context2.next) {
48776
49006
  case 0:
48777
49007
  _context2.prev = 0;
49008
+ api = typeof window !== "undefined" ? window.mainApi : null;
48778
49009
  _context2.next = 1;
48779
- return (_window$mainApi = window.mainApi) === null || _window$mainApi === void 0 || (_window$mainApi = _window$mainApi.widgetMcp) === null || _window$mainApi === void 0 || (_window$mainApi$revok = _window$mainApi.revoke) === null || _window$mainApi$revok === void 0 ? void 0 : _window$mainApi$revok.call(_window$mainApi, widgetId);
49010
+ return api === null || api === void 0 || (_api$mcp = api.mcp) === null || _api$mcp === void 0 || (_api$mcp$getCatalog = _api$mcp.getCatalog) === null || _api$mcp$getCatalog === void 0 ? void 0 : _api$mcp$getCatalog.call(_api$mcp);
48780
49011
  case 1:
48781
- reload();
49012
+ result = _context2.sent;
49013
+ servers = (result === null || result === void 0 ? void 0 : result.catalog) || [];
49014
+ if (!cancelled && Array.isArray(servers)) {
49015
+ setKnownServerNames(servers.map(function (s) {
49016
+ return s === null || s === void 0 ? void 0 : s.name;
49017
+ }).filter(function (n) {
49018
+ return typeof n === "string";
49019
+ }));
49020
+ }
48782
49021
  _context2.next = 3;
48783
49022
  break;
48784
49023
  case 2:
48785
49024
  _context2.prev = 2;
48786
- _t2 = _context2["catch"](0);
48787
- setError((_t2 === null || _t2 === void 0 ? void 0 : _t2.message) || String(_t2));
49025
+ _context2["catch"](0);
48788
49026
  case 3:
48789
49027
  case "end":
48790
49028
  return _context2.stop();
48791
49029
  }
48792
49030
  }, _callee2, null, [[0, 2]]);
48793
- }));
48794
- return function revokeWidget(_x) {
48795
- return _ref2.apply(this, arguments);
49031
+ }))();
49032
+ return function () {
49033
+ cancelled = true;
48796
49034
  };
48797
- }();
48798
- var revokeServer = /*#__PURE__*/function () {
48799
- var _ref3 = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee3(widgetId, serverName) {
48800
- var _window$mainApi2, _window$mainApi2$revo, _t3;
49035
+ }, []);
49036
+ var revokeWidget = /*#__PURE__*/function () {
49037
+ var _ref3 = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee3(widgetId) {
49038
+ var _window$mainApi, _window$mainApi$revok, _t3;
48801
49039
  return _regeneratorRuntime.wrap(function (_context3) {
48802
49040
  while (1) switch (_context3.prev = _context3.next) {
48803
49041
  case 0:
48804
49042
  _context3.prev = 0;
48805
49043
  _context3.next = 1;
48806
- return (_window$mainApi2 = window.mainApi) === null || _window$mainApi2 === void 0 || (_window$mainApi2 = _window$mainApi2.widgetMcp) === null || _window$mainApi2 === void 0 || (_window$mainApi2$revo = _window$mainApi2.revokeServer) === null || _window$mainApi2$revo === void 0 ? void 0 : _window$mainApi2$revo.call(_window$mainApi2, widgetId, serverName);
49044
+ return (_window$mainApi = window.mainApi) === null || _window$mainApi === void 0 || (_window$mainApi = _window$mainApi.widgetMcp) === null || _window$mainApi === void 0 || (_window$mainApi$revok = _window$mainApi.revoke) === null || _window$mainApi$revok === void 0 ? void 0 : _window$mainApi$revok.call(_window$mainApi, widgetId);
48807
49045
  case 1:
48808
49046
  reload();
48809
49047
  _context3.next = 3;
@@ -48818,10 +49056,37 @@ var PrivacySecuritySection = function PrivacySecuritySection() {
48818
49056
  }
48819
49057
  }, _callee3, null, [[0, 2]]);
48820
49058
  }));
48821
- return function revokeServer(_x2, _x3) {
49059
+ return function revokeWidget(_x) {
48822
49060
  return _ref3.apply(this, arguments);
48823
49061
  };
48824
49062
  }();
49063
+ var revokeServer = /*#__PURE__*/function () {
49064
+ var _ref4 = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee4(widgetId, serverName) {
49065
+ var _window$mainApi2, _window$mainApi2$revo, _t4;
49066
+ return _regeneratorRuntime.wrap(function (_context4) {
49067
+ while (1) switch (_context4.prev = _context4.next) {
49068
+ case 0:
49069
+ _context4.prev = 0;
49070
+ _context4.next = 1;
49071
+ return (_window$mainApi2 = window.mainApi) === null || _window$mainApi2 === void 0 || (_window$mainApi2 = _window$mainApi2.widgetMcp) === null || _window$mainApi2 === void 0 || (_window$mainApi2$revo = _window$mainApi2.revokeServer) === null || _window$mainApi2$revo === void 0 ? void 0 : _window$mainApi2$revo.call(_window$mainApi2, widgetId, serverName);
49072
+ case 1:
49073
+ reload();
49074
+ _context4.next = 3;
49075
+ break;
49076
+ case 2:
49077
+ _context4.prev = 2;
49078
+ _t4 = _context4["catch"](0);
49079
+ setError((_t4 === null || _t4 === void 0 ? void 0 : _t4.message) || String(_t4));
49080
+ case 3:
49081
+ case "end":
49082
+ return _context4.stop();
49083
+ }
49084
+ }, _callee4, null, [[0, 2]]);
49085
+ }));
49086
+ return function revokeServer(_x2, _x3) {
49087
+ return _ref4.apply(this, arguments);
49088
+ };
49089
+ }();
48825
49090
  if (loading) {
48826
49091
  return /*#__PURE__*/jsx("div", {
48827
49092
  className: "flex flex-col p-6",
@@ -48840,55 +49105,90 @@ var PrivacySecuritySection = function PrivacySecuritySection() {
48840
49105
  padding: false
48841
49106
  }), /*#__PURE__*/jsx("span", {
48842
49107
  className: "text-xs opacity-60",
48843
- children: "Tools and paths each widget is allowed to call via MCP. Granted paths are visible to other widgets in the same dashboard that use the same MCP server. Revoke any time."
49108
+ children: "Granting access here is a trust signal about the widget \u2014 not a per-dashboard switch."
48844
49109
  })]
48845
- }), error && /*#__PURE__*/jsx("div", {
49110
+ }), /*#__PURE__*/jsx(HowThisWorksPanel, {}), error && /*#__PURE__*/jsx("div", {
48846
49111
  className: "text-xs text-red-400 bg-red-900 bg-opacity-20 border border-red-700 rounded p-3",
48847
49112
  children: error
48848
49113
  }), rows.length === 0 && /*#__PURE__*/jsx("div", {
48849
49114
  className: "text-sm opacity-60",
48850
- children: "No widgets have requested MCP permissions yet."
48851
- }), rows.map(function (_ref4) {
48852
- var widgetId = _ref4.widgetId,
48853
- declared = _ref4.declared,
48854
- granted = _ref4.granted;
49115
+ children: "No widgets installed."
49116
+ }), rows.map(function (_ref5) {
49117
+ var widgetId = _ref5.widgetId,
49118
+ declared = _ref5.declared,
49119
+ granted = _ref5.granted,
49120
+ hasManifest = _ref5.hasManifest,
49121
+ grantOrigin = _ref5.grantOrigin;
48855
49122
  return /*#__PURE__*/jsx(WidgetGrantRow, {
48856
49123
  widgetId: widgetId,
48857
49124
  declared: declared,
48858
49125
  granted: granted,
49126
+ hasManifest: hasManifest,
49127
+ grantOrigin: grantOrigin,
48859
49128
  onRevokeWidget: function onRevokeWidget() {
48860
49129
  return revokeWidget(widgetId);
48861
49130
  },
48862
49131
  onRevokeServer: function onRevokeServer(serverName) {
48863
49132
  return revokeServer(widgetId, serverName);
49133
+ },
49134
+ onGrantManually: function onGrantManually() {
49135
+ return setManualGrantWidgetId(widgetId);
48864
49136
  }
48865
49137
  }, widgetId);
49138
+ }), /*#__PURE__*/jsx(GrantManuallyModal, {
49139
+ isOpen: !!manualGrantWidgetId,
49140
+ setIsOpen: function setIsOpen(open) {
49141
+ if (!open) setManualGrantWidgetId(null);
49142
+ },
49143
+ widgetId: manualGrantWidgetId,
49144
+ knownServerNames: knownServerNames,
49145
+ onGranted: function onGranted() {
49146
+ setManualGrantWidgetId(null);
49147
+ reload();
49148
+ }
48866
49149
  })]
48867
49150
  });
48868
49151
  };
48869
- var WidgetGrantRow = function WidgetGrantRow(_ref5) {
48870
- var widgetId = _ref5.widgetId,
48871
- declared = _ref5.declared,
48872
- granted = _ref5.granted,
48873
- onRevokeWidget = _ref5.onRevokeWidget,
48874
- onRevokeServer = _ref5.onRevokeServer;
49152
+ var WidgetGrantRow = function WidgetGrantRow(_ref6) {
49153
+ var widgetId = _ref6.widgetId,
49154
+ declared = _ref6.declared,
49155
+ granted = _ref6.granted,
49156
+ hasManifest = _ref6.hasManifest,
49157
+ grantOrigin = _ref6.grantOrigin,
49158
+ onRevokeWidget = _ref6.onRevokeWidget,
49159
+ onRevokeServer = _ref6.onRevokeServer,
49160
+ onGrantManually = _ref6.onGrantManually;
48875
49161
  var declaredServers = declared && declared.servers || {};
48876
49162
  var grantedServers = granted && granted.servers || {};
48877
49163
  var allServerNames = Array.from(new Set([].concat(_toConsumableArray(Object.keys(declaredServers)), _toConsumableArray(Object.keys(grantedServers)))));
48878
49164
  return /*#__PURE__*/jsxs("div", {
48879
49165
  className: "flex flex-col space-y-3 border border-gray-700 rounded p-3",
48880
49166
  children: [/*#__PURE__*/jsxs("div", {
48881
- className: "flex flex-row items-center justify-between",
48882
- children: [/*#__PURE__*/jsx("span", {
48883
- className: "text-sm font-mono break-all",
48884
- children: widgetId
48885
- }), Object.keys(grantedServers).length > 0 && /*#__PURE__*/jsx(Button, {
48886
- title: "Revoke all",
48887
- onClick: onRevokeWidget
49167
+ className: "flex flex-row items-center justify-between gap-2",
49168
+ children: [/*#__PURE__*/jsxs("div", {
49169
+ className: "flex flex-row items-center gap-2 min-w-0",
49170
+ children: [/*#__PURE__*/jsx("span", {
49171
+ className: "text-sm font-mono break-all",
49172
+ children: widgetId
49173
+ }), grantOrigin && /*#__PURE__*/jsx(GrantOriginBadge, {
49174
+ origin: grantOrigin
49175
+ }), !hasManifest && !granted && /*#__PURE__*/jsx("span", {
49176
+ className: "text-xs uppercase tracking-wider text-amber-400",
49177
+ children: "no manifest"
49178
+ })]
49179
+ }), /*#__PURE__*/jsxs("div", {
49180
+ className: "flex flex-row gap-2",
49181
+ children: [!hasManifest && !granted && /*#__PURE__*/jsx(Button, {
49182
+ title: "Grant manually",
49183
+ onClick: onGrantManually
49184
+ }), Object.keys(grantedServers).length > 0 && /*#__PURE__*/jsx(Button, {
49185
+ title: "Revoke all",
49186
+ onClick: onRevokeWidget
49187
+ })]
48888
49188
  })]
48889
49189
  }), !declared && !granted && /*#__PURE__*/jsx("span", {
48890
49190
  className: "text-xs opacity-50",
48891
- children: "(no manifest, no grant \u2014 should not happen)"
49191
+ children: "This widget did not declare MCP permissions and the install-time scanner found nothing. Use Grant manually if you trust it."
48892
49192
  }), allServerNames.map(function (serverName) {
48893
49193
  var decl = declaredServers[serverName] || {};
48894
49194
  var grant = grantedServers[serverName];
@@ -48925,10 +49225,10 @@ var WidgetGrantRow = function WidgetGrantRow(_ref5) {
48925
49225
  })]
48926
49226
  });
48927
49227
  };
48928
- var PermsList = function PermsList(_ref6) {
48929
- var label = _ref6.label,
48930
- declaredItems = _ref6.declaredItems,
48931
- grantedItems = _ref6.grantedItems;
49228
+ var PermsList = function PermsList(_ref7) {
49229
+ var label = _ref7.label,
49230
+ declaredItems = _ref7.declaredItems,
49231
+ grantedItems = _ref7.grantedItems;
48932
49232
  if (declaredItems.length === 0 && grantedItems.length === 0) return null;
48933
49233
  var grantedSet = new Set(grantedItems);
48934
49234
  var declaredSet = new Set(declaredItems);
@@ -48952,6 +49252,253 @@ var PermsList = function PermsList(_ref6) {
48952
49252
  });
48953
49253
  };
48954
49254
 
49255
+ /**
49256
+ * Renders a small badge showing how the user got to this grant. Helps
49257
+ * the user audit grants that were approved against a scanner guess
49258
+ * rather than the developer's explicit declaration.
49259
+ */
49260
+ var GrantOriginBadge = function GrantOriginBadge(_ref8) {
49261
+ var origin = _ref8.origin;
49262
+ var styles = {
49263
+ declared: {
49264
+ label: "declared",
49265
+ color: "text-green-400"
49266
+ },
49267
+ discovered: {
49268
+ label: "discovered",
49269
+ color: "text-amber-400"
49270
+ },
49271
+ manual: {
49272
+ label: "manual",
49273
+ color: "text-blue-400"
49274
+ }
49275
+ };
49276
+ var style = styles[origin];
49277
+ if (!style) return null;
49278
+ return /*#__PURE__*/jsx("span", {
49279
+ className: "text-xs uppercase tracking-wider ".concat(style.color),
49280
+ title: "Origin: ".concat(origin),
49281
+ children: style.label
49282
+ });
49283
+ };
49284
+
49285
+ // Mock fixtures for the "Example rows" section. These use the same
49286
+ // WidgetGrantRow component the real rows use, so the preview always
49287
+ // reflects the real rendering. Click handlers are no-ops — the panel is
49288
+ // for visualization only.
49289
+ var EXAMPLE_FIXTURES = [{
49290
+ caption: "Declared by the developer and granted by the user.",
49291
+ widgetId: "@example/notes-summarizer",
49292
+ hasManifest: true,
49293
+ grantOrigin: "declared",
49294
+ declared: {
49295
+ servers: {
49296
+ filesystem: {
49297
+ tools: ["read_file", "list_directory"],
49298
+ readPaths: ["~/Documents/notes"],
49299
+ writePaths: []
49300
+ }
49301
+ }
49302
+ },
49303
+ granted: {
49304
+ grantOrigin: "declared",
49305
+ servers: {
49306
+ filesystem: {
49307
+ tools: ["read_file", "list_directory"],
49308
+ readPaths: ["~/Documents/notes"],
49309
+ writePaths: []
49310
+ }
49311
+ }
49312
+ }
49313
+ }, {
49314
+ caption: "Declared by the developer — the user hasn't decided yet.",
49315
+ widgetId: "@example/code-search",
49316
+ hasManifest: true,
49317
+ grantOrigin: null,
49318
+ declared: {
49319
+ servers: {
49320
+ github: {
49321
+ tools: ["search_repositories", "get_file_contents"]
49322
+ }
49323
+ }
49324
+ },
49325
+ granted: null
49326
+ }, {
49327
+ caption: "Detected by the install-time scanner and granted.",
49328
+ widgetId: "@example/file-helper",
49329
+ hasManifest: false,
49330
+ grantOrigin: "discovered",
49331
+ declared: null,
49332
+ granted: {
49333
+ grantOrigin: "discovered",
49334
+ servers: {
49335
+ filesystem: {
49336
+ tools: ["read_file"],
49337
+ readPaths: [],
49338
+ writePaths: []
49339
+ }
49340
+ }
49341
+ }
49342
+ }, {
49343
+ caption: "Granted manually because the widget had no manifest.",
49344
+ widgetId: "@example/legacy-widget",
49345
+ hasManifest: false,
49346
+ grantOrigin: "manual",
49347
+ declared: null,
49348
+ granted: {
49349
+ grantOrigin: "manual",
49350
+ servers: {
49351
+ filesystem: {
49352
+ tools: ["read_file", "write_file"],
49353
+ readPaths: ["~/Downloads"],
49354
+ writePaths: ["/tmp/widget-output"]
49355
+ }
49356
+ }
49357
+ }
49358
+ }];
49359
+ var noop = function noop() {};
49360
+
49361
+ /**
49362
+ * Collapsible explainer that documents how grants flow per-widget vs
49363
+ * per-dashboard, with a concrete example table and rendered preview rows
49364
+ * for each grant state. Default-collapsed so users who don't care never
49365
+ * see it.
49366
+ */
49367
+ var HowThisWorksPanel = function HowThisWorksPanel() {
49368
+ var _useState1 = useState(false),
49369
+ _useState10 = _slicedToArray(_useState1, 2),
49370
+ open = _useState10[0],
49371
+ setOpen = _useState10[1];
49372
+ return /*#__PURE__*/jsxs("div", {
49373
+ className: "border border-gray-700 rounded",
49374
+ children: [/*#__PURE__*/jsxs("button", {
49375
+ type: "button",
49376
+ onClick: function onClick() {
49377
+ return setOpen(function (v) {
49378
+ return !v;
49379
+ });
49380
+ },
49381
+ className: "w-full flex flex-row items-center justify-between px-3 py-2 text-left text-sm hover:bg-gray-800",
49382
+ children: [/*#__PURE__*/jsx("span", {
49383
+ children: "How widget MCP permissions work"
49384
+ }), /*#__PURE__*/jsx(FontAwesomeIcon, {
49385
+ icon: open ? "chevron-up" : "chevron-down",
49386
+ className: "h-3 w-3 opacity-60"
49387
+ })]
49388
+ }), open && /*#__PURE__*/jsxs("div", {
49389
+ className: "flex flex-col space-y-4 px-3 py-3 border-t border-gray-800 text-xs leading-relaxed",
49390
+ children: [/*#__PURE__*/jsxs("div", {
49391
+ className: "space-y-2",
49392
+ children: [/*#__PURE__*/jsxs("p", {
49393
+ children: [/*#__PURE__*/jsx("span", {
49394
+ className: "font-semibold",
49395
+ children: "The grant is about the widget, not the dashboard."
49396
+ }), " ", "When you grant ", /*#__PURE__*/jsx("code", {
49397
+ children: "@trops/notes-summarizer"
49398
+ }), " access to", " ", /*#__PURE__*/jsx("code", {
49399
+ children: "~/Documents"
49400
+ }), ", you're saying \"I trust this widget with this path, anywhere.\" Grants live one-per-widget, regardless of how many dashboards use it."]
49401
+ }), /*#__PURE__*/jsxs("p", {
49402
+ children: [/*#__PURE__*/jsx("span", {
49403
+ className: "font-semibold",
49404
+ children: "Each dashboard automatically scopes its servers."
49405
+ }), " ", "When you open a dashboard, Dash spawns a separate MCP server process per dashboard. That server is configured with only the paths granted to widgets actually on that dashboard \u2014 nothing else. Two dashboards using the same widget share the same grant; two dashboards using different widgets get different effective scopes."]
49406
+ }), /*#__PURE__*/jsxs("p", {
49407
+ children: [/*#__PURE__*/jsx("span", {
49408
+ className: "font-semibold",
49409
+ children: "What this doesn't do."
49410
+ }), " ", "There's no way today to say \"this widget can use filesystem on Dashboard 1 but not Dashboard 2.\" Grants are per-widget; per-(widget, dashboard) granularity would need a bigger UX rework. If you don't want a widget to access a path on a particular dashboard, the workaround is to remove it from that dashboard or revoke the grant entirely."]
49411
+ })]
49412
+ }), /*#__PURE__*/jsxs("div", {
49413
+ className: "space-y-2",
49414
+ children: [/*#__PURE__*/jsxs("div", {
49415
+ className: "font-semibold",
49416
+ children: ["Example: widget A granted ", /*#__PURE__*/jsx("code", {
49417
+ children: "/Documents"
49418
+ }), ", widget B granted ", /*#__PURE__*/jsx("code", {
49419
+ children: "/Code"
49420
+ })]
49421
+ }), /*#__PURE__*/jsxs("table", {
49422
+ className: "w-full text-xs border border-gray-800",
49423
+ children: [/*#__PURE__*/jsx("thead", {
49424
+ children: /*#__PURE__*/jsxs("tr", {
49425
+ className: "bg-gray-900",
49426
+ children: [/*#__PURE__*/jsx("th", {
49427
+ className: "text-left px-2 py-1 border-b border-gray-800",
49428
+ children: "Scenario"
49429
+ }), /*#__PURE__*/jsx("th", {
49430
+ className: "text-left px-2 py-1 border-b border-gray-800",
49431
+ children: "Dashboard 1 sees"
49432
+ }), /*#__PURE__*/jsx("th", {
49433
+ className: "text-left px-2 py-1 border-b border-gray-800",
49434
+ children: "Dashboard 2 sees"
49435
+ })]
49436
+ })
49437
+ }), /*#__PURE__*/jsxs("tbody", {
49438
+ children: [/*#__PURE__*/jsxs("tr", {
49439
+ children: [/*#__PURE__*/jsx("td", {
49440
+ className: "px-2 py-1 border-b border-gray-800",
49441
+ children: "A on Dash 1, B on Dash 2"
49442
+ }), /*#__PURE__*/jsx("td", {
49443
+ className: "px-2 py-1 border-b border-gray-800 font-mono",
49444
+ children: "/Documents"
49445
+ }), /*#__PURE__*/jsx("td", {
49446
+ className: "px-2 py-1 border-b border-gray-800 font-mono",
49447
+ children: "/Code"
49448
+ })]
49449
+ }), /*#__PURE__*/jsxs("tr", {
49450
+ children: [/*#__PURE__*/jsx("td", {
49451
+ className: "px-2 py-1 border-b border-gray-800",
49452
+ children: "A on both, B on Dash 2"
49453
+ }), /*#__PURE__*/jsx("td", {
49454
+ className: "px-2 py-1 border-b border-gray-800 font-mono",
49455
+ children: "/Documents"
49456
+ }), /*#__PURE__*/jsx("td", {
49457
+ className: "px-2 py-1 border-b border-gray-800 font-mono",
49458
+ children: "/Documents, /Code"
49459
+ })]
49460
+ }), /*#__PURE__*/jsxs("tr", {
49461
+ children: [/*#__PURE__*/jsx("td", {
49462
+ className: "px-2 py-1",
49463
+ children: "A + B both on Dash 1"
49464
+ }), /*#__PURE__*/jsx("td", {
49465
+ className: "px-2 py-1 font-mono",
49466
+ children: "/Documents, /Code"
49467
+ }), /*#__PURE__*/jsx("td", {
49468
+ className: "px-2 py-1 opacity-60",
49469
+ children: "(no server)"
49470
+ })]
49471
+ })]
49472
+ })]
49473
+ })]
49474
+ }), /*#__PURE__*/jsxs("div", {
49475
+ className: "space-y-3",
49476
+ children: [/*#__PURE__*/jsx("div", {
49477
+ className: "font-semibold",
49478
+ children: "What each row state looks like"
49479
+ }), EXAMPLE_FIXTURES.map(function (f) {
49480
+ return /*#__PURE__*/jsxs("div", {
49481
+ className: "space-y-1",
49482
+ children: [/*#__PURE__*/jsx("div", {
49483
+ className: "italic opacity-60",
49484
+ children: f.caption
49485
+ }), /*#__PURE__*/jsx(WidgetGrantRow, {
49486
+ widgetId: f.widgetId,
49487
+ declared: f.declared,
49488
+ granted: f.granted,
49489
+ hasManifest: f.hasManifest,
49490
+ grantOrigin: f.grantOrigin,
49491
+ onRevokeWidget: noop,
49492
+ onRevokeServer: noop,
49493
+ onGrantManually: noop
49494
+ })]
49495
+ }, f.widgetId);
49496
+ })]
49497
+ })]
49498
+ })]
49499
+ });
49500
+ };
49501
+
48955
49502
  var SECTIONS = [{
48956
49503
  key: "general",
48957
49504
  label: "General",