@trops/dash-core 0.1.493 → 0.1.495

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
@@ -49105,9 +49105,9 @@ var PrivacySecuritySection = function PrivacySecuritySection() {
49105
49105
  padding: false
49106
49106
  }), /*#__PURE__*/jsx("span", {
49107
49107
  className: "text-xs opacity-60",
49108
- 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."
49109
49109
  })]
49110
- }), error && /*#__PURE__*/jsx("div", {
49110
+ }), /*#__PURE__*/jsx(HowThisWorksPanel, {}), error && /*#__PURE__*/jsx("div", {
49111
49111
  className: "text-xs text-red-400 bg-red-900 bg-opacity-20 border border-red-700 rounded p-3",
49112
49112
  children: error
49113
49113
  }), rows.length === 0 && /*#__PURE__*/jsx("div", {
@@ -49192,6 +49192,7 @@ var WidgetGrantRow = function WidgetGrantRow(_ref6) {
49192
49192
  }), allServerNames.map(function (serverName) {
49193
49193
  var decl = declaredServers[serverName] || {};
49194
49194
  var grant = grantedServers[serverName];
49195
+ var allStale = isServerEntirelyStale(decl, grant);
49195
49196
  return /*#__PURE__*/jsxs("div", {
49196
49197
  className: "flex flex-col space-y-2 border-t border-gray-800 pt-2",
49197
49198
  children: [/*#__PURE__*/jsxs("div", {
@@ -49208,6 +49209,9 @@ var WidgetGrantRow = function WidgetGrantRow(_ref6) {
49208
49209
  return onRevokeServer(serverName);
49209
49210
  }
49210
49211
  })]
49212
+ }), allStale && /*#__PURE__*/jsx("div", {
49213
+ className: "text-xs text-amber-400 bg-amber-900 bg-opacity-20 border border-amber-700 rounded px-2 py-1.5",
49214
+ children: "All grants on this server are no longer in the manifest \u2014 the widget likely no longer uses this server. Consider revoking."
49211
49215
  }), /*#__PURE__*/jsx(PermsList, {
49212
49216
  label: "Tools",
49213
49217
  declaredItems: decl.tools || [],
@@ -49241,17 +49245,44 @@ var PermsList = function PermsList(_ref7) {
49241
49245
  }), all.map(function (item) {
49242
49246
  var isGranted = grantedSet.has(item);
49243
49247
  var isDeclared = declaredSet.has(item);
49248
+ var isStale = isGranted && !isDeclared;
49244
49249
  return /*#__PURE__*/jsxs("span", {
49245
- className: "text-xs font-mono break-all ".concat(isGranted ? "opacity-100" : isDeclared ? "opacity-50 line-through" : "opacity-100 text-amber-400"),
49246
- children: [item, !isDeclared && isGranted && /*#__PURE__*/jsx("span", {
49247
- className: "ml-2 opacity-60",
49248
- children: "(no longer declared)"
49250
+ className: "text-xs font-mono break-all ".concat(isStale ? "text-amber-400" : isGranted ? "opacity-100" : "opacity-50 line-through"),
49251
+ children: [item, isStale && /*#__PURE__*/jsx("span", {
49252
+ className: "ml-2 not-italic font-sans normal-case tracking-normal text-amber-400",
49253
+ children: "(stale \u2014 widget no longer requests this; consider revoking)"
49249
49254
  })]
49250
49255
  }, item);
49251
49256
  })]
49252
49257
  });
49253
49258
  };
49254
49259
 
49260
+ /**
49261
+ * True when the granted entry has at least one item AND every granted
49262
+ * item is missing from the current declared block (i.e. all of this
49263
+ * server's grants are unused by the current manifest). Used to surface
49264
+ * a "this whole server's grant looks unused" suggestion at the row level.
49265
+ */
49266
+ function isServerEntirelyStale(decl, grant) {
49267
+ if (!grant) return false;
49268
+ var declTools = new Set(decl.tools || []);
49269
+ var declRead = new Set(decl.readPaths || []);
49270
+ var declWrite = new Set(decl.writePaths || []);
49271
+ var grantedTools = grant.tools || [];
49272
+ var grantedRead = grant.readPaths || [];
49273
+ var grantedWrite = grant.writePaths || [];
49274
+ var total = grantedTools.length + grantedRead.length + grantedWrite.length;
49275
+ if (total === 0) return false;
49276
+ var stale = grantedTools.every(function (t) {
49277
+ return !declTools.has(t);
49278
+ }) && grantedRead.every(function (p) {
49279
+ return !declRead.has(p);
49280
+ }) && grantedWrite.every(function (p) {
49281
+ return !declWrite.has(p);
49282
+ });
49283
+ return stale;
49284
+ }
49285
+
49255
49286
  /**
49256
49287
  * Renders a small badge showing how the user got to this grant. Helps
49257
49288
  * the user audit grants that were approved against a scanner guess
@@ -49282,6 +49313,248 @@ var GrantOriginBadge = function GrantOriginBadge(_ref8) {
49282
49313
  });
49283
49314
  };
49284
49315
 
49316
+ // Mock fixtures for the "Example rows" section. These use the same
49317
+ // WidgetGrantRow component the real rows use, so the preview always
49318
+ // reflects the real rendering. Click handlers are no-ops — the panel is
49319
+ // for visualization only.
49320
+ var EXAMPLE_FIXTURES = [{
49321
+ caption: "Declared by the developer and granted by the user.",
49322
+ widgetId: "@example/notes-summarizer",
49323
+ hasManifest: true,
49324
+ grantOrigin: "declared",
49325
+ declared: {
49326
+ servers: {
49327
+ filesystem: {
49328
+ tools: ["read_file", "list_directory"],
49329
+ readPaths: ["~/Documents/notes"],
49330
+ writePaths: []
49331
+ }
49332
+ }
49333
+ },
49334
+ granted: {
49335
+ grantOrigin: "declared",
49336
+ servers: {
49337
+ filesystem: {
49338
+ tools: ["read_file", "list_directory"],
49339
+ readPaths: ["~/Documents/notes"],
49340
+ writePaths: []
49341
+ }
49342
+ }
49343
+ }
49344
+ }, {
49345
+ caption: "Declared by the developer — the user hasn't decided yet.",
49346
+ widgetId: "@example/code-search",
49347
+ hasManifest: true,
49348
+ grantOrigin: null,
49349
+ declared: {
49350
+ servers: {
49351
+ github: {
49352
+ tools: ["search_repositories", "get_file_contents"]
49353
+ }
49354
+ }
49355
+ },
49356
+ granted: null
49357
+ }, {
49358
+ caption: "Detected by the install-time scanner and granted.",
49359
+ widgetId: "@example/file-helper",
49360
+ hasManifest: false,
49361
+ grantOrigin: "discovered",
49362
+ declared: null,
49363
+ granted: {
49364
+ grantOrigin: "discovered",
49365
+ servers: {
49366
+ filesystem: {
49367
+ tools: ["read_file"],
49368
+ readPaths: [],
49369
+ writePaths: []
49370
+ }
49371
+ }
49372
+ }
49373
+ }, {
49374
+ caption: "Granted manually because the widget had no manifest.",
49375
+ widgetId: "@example/legacy-widget",
49376
+ hasManifest: false,
49377
+ grantOrigin: "manual",
49378
+ declared: null,
49379
+ granted: {
49380
+ grantOrigin: "manual",
49381
+ servers: {
49382
+ filesystem: {
49383
+ tools: ["read_file", "write_file"],
49384
+ readPaths: ["~/Downloads"],
49385
+ writePaths: ["/tmp/widget-output"]
49386
+ }
49387
+ }
49388
+ }
49389
+ }, {
49390
+ caption: "Stale grant — the widget upgraded and dropped readPaths from its manifest, but the user's grant is still present.",
49391
+ widgetId: "@example/upgraded-widget",
49392
+ hasManifest: true,
49393
+ grantOrigin: "declared",
49394
+ declared: {
49395
+ // Manifest now declares only the tool, no paths.
49396
+ servers: {
49397
+ filesystem: {
49398
+ tools: ["read_file"],
49399
+ readPaths: [],
49400
+ writePaths: []
49401
+ }
49402
+ }
49403
+ },
49404
+ granted: {
49405
+ grantOrigin: "declared",
49406
+ servers: {
49407
+ filesystem: {
49408
+ tools: ["read_file"],
49409
+ readPaths: ["~/Documents/old-notes"],
49410
+ writePaths: []
49411
+ }
49412
+ }
49413
+ }
49414
+ }];
49415
+ var noop = function noop() {};
49416
+
49417
+ /**
49418
+ * Collapsible explainer that documents how grants flow per-widget vs
49419
+ * per-dashboard, with a concrete example table and rendered preview rows
49420
+ * for each grant state. Default-collapsed so users who don't care never
49421
+ * see it.
49422
+ */
49423
+ var HowThisWorksPanel = function HowThisWorksPanel() {
49424
+ var _useState1 = useState(false),
49425
+ _useState10 = _slicedToArray(_useState1, 2),
49426
+ open = _useState10[0],
49427
+ setOpen = _useState10[1];
49428
+ return /*#__PURE__*/jsxs("div", {
49429
+ className: "border border-gray-700 rounded",
49430
+ children: [/*#__PURE__*/jsxs("button", {
49431
+ type: "button",
49432
+ onClick: function onClick() {
49433
+ return setOpen(function (v) {
49434
+ return !v;
49435
+ });
49436
+ },
49437
+ className: "w-full flex flex-row items-center justify-between px-3 py-2 text-left text-sm hover:bg-gray-800",
49438
+ children: [/*#__PURE__*/jsx("span", {
49439
+ children: "How widget MCP permissions work"
49440
+ }), /*#__PURE__*/jsx(FontAwesomeIcon, {
49441
+ icon: open ? "chevron-up" : "chevron-down",
49442
+ className: "h-3 w-3 opacity-60"
49443
+ })]
49444
+ }), open && /*#__PURE__*/jsxs("div", {
49445
+ className: "flex flex-col space-y-4 px-3 py-3 border-t border-gray-800 text-xs leading-relaxed",
49446
+ children: [/*#__PURE__*/jsxs("div", {
49447
+ className: "space-y-2",
49448
+ children: [/*#__PURE__*/jsxs("p", {
49449
+ children: [/*#__PURE__*/jsx("span", {
49450
+ className: "font-semibold",
49451
+ children: "The grant is about the widget, not the dashboard."
49452
+ }), " ", "When you grant ", /*#__PURE__*/jsx("code", {
49453
+ children: "@trops/notes-summarizer"
49454
+ }), " access to", " ", /*#__PURE__*/jsx("code", {
49455
+ children: "~/Documents"
49456
+ }), ", you're saying \"I trust this widget with this path, anywhere.\" Grants live one-per-widget, regardless of how many dashboards use it."]
49457
+ }), /*#__PURE__*/jsxs("p", {
49458
+ children: [/*#__PURE__*/jsx("span", {
49459
+ className: "font-semibold",
49460
+ children: "Each dashboard automatically scopes its servers."
49461
+ }), " ", "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."]
49462
+ }), /*#__PURE__*/jsxs("p", {
49463
+ children: [/*#__PURE__*/jsx("span", {
49464
+ className: "font-semibold",
49465
+ children: "What this doesn't do."
49466
+ }), " ", "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."]
49467
+ })]
49468
+ }), /*#__PURE__*/jsxs("div", {
49469
+ className: "space-y-2",
49470
+ children: [/*#__PURE__*/jsxs("div", {
49471
+ className: "font-semibold",
49472
+ children: ["Example: widget A granted ", /*#__PURE__*/jsx("code", {
49473
+ children: "/Documents"
49474
+ }), ", widget B granted ", /*#__PURE__*/jsx("code", {
49475
+ children: "/Code"
49476
+ })]
49477
+ }), /*#__PURE__*/jsxs("table", {
49478
+ className: "w-full text-xs border border-gray-800",
49479
+ children: [/*#__PURE__*/jsx("thead", {
49480
+ children: /*#__PURE__*/jsxs("tr", {
49481
+ className: "bg-gray-900",
49482
+ children: [/*#__PURE__*/jsx("th", {
49483
+ className: "text-left px-2 py-1 border-b border-gray-800",
49484
+ children: "Scenario"
49485
+ }), /*#__PURE__*/jsx("th", {
49486
+ className: "text-left px-2 py-1 border-b border-gray-800",
49487
+ children: "Dashboard 1 sees"
49488
+ }), /*#__PURE__*/jsx("th", {
49489
+ className: "text-left px-2 py-1 border-b border-gray-800",
49490
+ children: "Dashboard 2 sees"
49491
+ })]
49492
+ })
49493
+ }), /*#__PURE__*/jsxs("tbody", {
49494
+ children: [/*#__PURE__*/jsxs("tr", {
49495
+ children: [/*#__PURE__*/jsx("td", {
49496
+ className: "px-2 py-1 border-b border-gray-800",
49497
+ children: "A on Dash 1, B on Dash 2"
49498
+ }), /*#__PURE__*/jsx("td", {
49499
+ className: "px-2 py-1 border-b border-gray-800 font-mono",
49500
+ children: "/Documents"
49501
+ }), /*#__PURE__*/jsx("td", {
49502
+ className: "px-2 py-1 border-b border-gray-800 font-mono",
49503
+ children: "/Code"
49504
+ })]
49505
+ }), /*#__PURE__*/jsxs("tr", {
49506
+ children: [/*#__PURE__*/jsx("td", {
49507
+ className: "px-2 py-1 border-b border-gray-800",
49508
+ children: "A on both, B on Dash 2"
49509
+ }), /*#__PURE__*/jsx("td", {
49510
+ className: "px-2 py-1 border-b border-gray-800 font-mono",
49511
+ children: "/Documents"
49512
+ }), /*#__PURE__*/jsx("td", {
49513
+ className: "px-2 py-1 border-b border-gray-800 font-mono",
49514
+ children: "/Documents, /Code"
49515
+ })]
49516
+ }), /*#__PURE__*/jsxs("tr", {
49517
+ children: [/*#__PURE__*/jsx("td", {
49518
+ className: "px-2 py-1",
49519
+ children: "A + B both on Dash 1"
49520
+ }), /*#__PURE__*/jsx("td", {
49521
+ className: "px-2 py-1 font-mono",
49522
+ children: "/Documents, /Code"
49523
+ }), /*#__PURE__*/jsx("td", {
49524
+ className: "px-2 py-1 opacity-60",
49525
+ children: "(no server)"
49526
+ })]
49527
+ })]
49528
+ })]
49529
+ })]
49530
+ }), /*#__PURE__*/jsxs("div", {
49531
+ className: "space-y-3",
49532
+ children: [/*#__PURE__*/jsx("div", {
49533
+ className: "font-semibold",
49534
+ children: "What each row state looks like"
49535
+ }), EXAMPLE_FIXTURES.map(function (f) {
49536
+ return /*#__PURE__*/jsxs("div", {
49537
+ className: "space-y-1",
49538
+ children: [/*#__PURE__*/jsx("div", {
49539
+ className: "italic opacity-60",
49540
+ children: f.caption
49541
+ }), /*#__PURE__*/jsx(WidgetGrantRow, {
49542
+ widgetId: f.widgetId,
49543
+ declared: f.declared,
49544
+ granted: f.granted,
49545
+ hasManifest: f.hasManifest,
49546
+ grantOrigin: f.grantOrigin,
49547
+ onRevokeWidget: noop,
49548
+ onRevokeServer: noop,
49549
+ onGrantManually: noop
49550
+ })]
49551
+ }, f.widgetId);
49552
+ })]
49553
+ })]
49554
+ })]
49555
+ });
49556
+ };
49557
+
49285
49558
  var SECTIONS = [{
49286
49559
  key: "general",
49287
49560
  label: "General",