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