@tanstack/devtools 0.6.22 → 0.6.24

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/dev.js CHANGED
@@ -30,7 +30,7 @@ var TanStackDevtoolsCore = class {
30
30
  const mountTo = el;
31
31
  const dispose = render(() => {
32
32
  const _self$ = this;
33
- this.#Component = lazy(() => import('./devtools/YRFZDV5N.js'));
33
+ this.#Component = lazy(() => import('./devtools/UUNAZSBD.js'));
34
34
  const Devtools = this.#Component;
35
35
  this.#eventBus = new ClientEventBus(this.#eventBusConfig);
36
36
  this.#eventBus.start();
@@ -1597,6 +1597,75 @@ var stylesFactory = (theme) => {
1597
1597
  text-transform: uppercase;
1598
1598
  letter-spacing: 0.05em;
1599
1599
  `,
1600
+ pluginMarketplaceFeatureBanner: css2`
1601
+ margin-top: 1rem;
1602
+ padding: 1.25rem 1.5rem;
1603
+ background: ${t(
1604
+ "linear-gradient(135deg, #3b82f6 0%, #2563eb 100%)",
1605
+ "linear-gradient(135deg, #1e3a8a 0%, #1e40af 100%)"
1606
+ )};
1607
+ border-radius: 0.75rem;
1608
+ border: 1px solid ${t(colors.blue[400], colors.blue[800])};
1609
+ box-shadow:
1610
+ 0 4px 6px -1px rgba(0, 0, 0, 0.1),
1611
+ 0 2px 4px -1px rgba(0, 0, 0, 0.06);
1612
+ `,
1613
+ pluginMarketplaceFeatureBannerContent: css2`
1614
+ display: flex;
1615
+ flex-direction: column;
1616
+ gap: 0.75rem;
1617
+ `,
1618
+ pluginMarketplaceFeatureBannerTitle: css2`
1619
+ font-size: 1.125rem;
1620
+ font-weight: 700;
1621
+ color: white;
1622
+ margin: 0;
1623
+ display: flex;
1624
+ align-items: center;
1625
+ gap: 0.5rem;
1626
+ `,
1627
+ pluginMarketplaceFeatureBannerIcon: css2`
1628
+ width: 24px;
1629
+ height: 24px;
1630
+ display: inline-flex;
1631
+ `,
1632
+ pluginMarketplaceFeatureBannerText: css2`
1633
+ font-size: 0.95rem;
1634
+ color: ${t("rgba(255, 255, 255, 0.95)", "rgba(255, 255, 255, 0.9)")};
1635
+ line-height: 1.5;
1636
+ margin: 0;
1637
+ `,
1638
+ pluginMarketplaceFeatureBannerButton: css2`
1639
+ display: inline-flex;
1640
+ align-items: center;
1641
+ gap: 0.5rem;
1642
+ padding: 0.625rem 1.25rem;
1643
+ background: white;
1644
+ color: ${colors.blue[600]};
1645
+ font-weight: 600;
1646
+ font-size: 0.95rem;
1647
+ border-radius: 0.5rem;
1648
+ border: none;
1649
+ cursor: pointer;
1650
+ transition: all 0.2s ease;
1651
+ text-decoration: none;
1652
+ align-self: flex-start;
1653
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
1654
+
1655
+ &:hover {
1656
+ background: ${t(colors.gray[50], colors.gray[100])};
1657
+ transform: translateY(-1px);
1658
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
1659
+ }
1660
+
1661
+ &:active {
1662
+ transform: translateY(0);
1663
+ }
1664
+ `,
1665
+ pluginMarketplaceFeatureBannerButtonIcon: css2`
1666
+ width: 18px;
1667
+ height: 18px;
1668
+ `,
1600
1669
  pluginMarketplaceCardDisabled: css2`
1601
1670
  opacity: 0.6;
1602
1671
  filter: grayscale(0.3);
@@ -2249,16 +2318,28 @@ var PluginCardComponent = (props) => {
2249
2318
  };
2250
2319
 
2251
2320
  // src/tabs/marketplace/plugin-section.tsx
2252
- var _tmpl$12 = ["<div", ">", "</div>"];
2253
- var _tmpl$25 = ["<div", "><div", "><div", '><div class="', '">', "</div><h3", ">", "</h3></div></div>", "</div>"];
2321
+ var _tmpl$12 = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"></path></svg>';
2322
+ var _tmpl$25 = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="4" width="20" height="16" rx="2"></rect><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"></path></svg>';
2323
+ var _tmpl$34 = ["<div", "><div", "><h4", "><span", ">", "</span>Want to be featured here?</h4><p", `>If you've built a plugin for TanStack Devtools and would like to showcase it in the featured section, we'd love to hear from you! Reach out to us to discuss partnership opportunities.</p><a href="mailto:partners+devtools@tanstack.com?subject=Featured%20Plugin%20Partnership%20Inquiry"`, "><span", ">", "</span>Contact Us</a></div></div>"];
2324
+ var _tmpl$44 = ["<div", ">", "</div>"];
2325
+ var _tmpl$53 = ["<div", "><div", "><div", '><div class="', '">', "</div><h3", ">", "</h3></div></div>", "</div>"];
2326
+ var StarIcon = () => ssr(_tmpl$12);
2327
+ var MailIcon = () => ssr(_tmpl$25);
2254
2328
  var PluginSectionComponent = (props) => {
2255
2329
  const styles = useStyles();
2256
- return ssr(_tmpl$25, ssrAttribute("class", escape(styles().pluginMarketplaceSection, true), false), ssrAttribute("class", escape(styles().pluginMarketplaceSectionHeader, true), false), ssrAttribute("class", escape(styles().pluginMarketplaceSectionHeaderLeft, true), false), `${escape(styles().pluginMarketplaceSectionChevron, true) || ""} ${props.isCollapsed() ? escape(escape(styles().pluginMarketplaceSectionChevronCollapsed, true), true) : ""}`, escape(createComponent(ChevronDownIcon, {})), ssrAttribute("class", escape(styles().pluginMarketplaceSectionTitle, true), false), escape(props.section.displayName), escape(createComponent(Show, {
2330
+ return ssr(_tmpl$53, ssrAttribute("class", escape(styles().pluginMarketplaceSection, true), false), ssrAttribute("class", escape(styles().pluginMarketplaceSectionHeader, true), false), ssrAttribute("class", escape(styles().pluginMarketplaceSectionHeaderLeft, true), false), `${escape(styles().pluginMarketplaceSectionChevron, true) || ""} ${props.isCollapsed() ? escape(escape(styles().pluginMarketplaceSectionChevronCollapsed, true), true) : ""}`, escape(createComponent(ChevronDownIcon, {})), ssrAttribute("class", escape(styles().pluginMarketplaceSectionTitle, true), false), escape(props.section.displayName), escape(createComponent(Show, {
2257
2331
  get when() {
2258
2332
  return !props.isCollapsed();
2259
2333
  },
2260
2334
  get children() {
2261
- return ssr(_tmpl$12, ssrAttribute("class", escape(styles().pluginMarketplaceGrid, true), false), escape(createComponent(For, {
2335
+ return [createComponent(Show, {
2336
+ get when() {
2337
+ return props.section.id === "featured";
2338
+ },
2339
+ get children() {
2340
+ return ssr(_tmpl$34, ssrAttribute("class", escape(styles().pluginMarketplaceFeatureBanner, true), false), ssrAttribute("class", escape(styles().pluginMarketplaceFeatureBannerContent, true), false), ssrAttribute("class", escape(styles().pluginMarketplaceFeatureBannerTitle, true), false), ssrAttribute("class", escape(styles().pluginMarketplaceFeatureBannerIcon, true), false), escape(createComponent(StarIcon, {})), ssrAttribute("class", escape(styles().pluginMarketplaceFeatureBannerText, true), false), ssrAttribute("class", escape(styles().pluginMarketplaceFeatureBannerButton, true), false), ssrAttribute("class", escape(styles().pluginMarketplaceFeatureBannerButtonIcon, true), false), escape(createComponent(MailIcon, {})));
2341
+ }
2342
+ }), ssr(_tmpl$44, ssrAttribute("class", escape(styles().pluginMarketplaceGrid, true), false), escape(createComponent(For, {
2262
2343
  get each() {
2263
2344
  return props.section.cards;
2264
2345
  },
@@ -2268,7 +2349,7 @@ var PluginSectionComponent = (props) => {
2268
2349
  return props.onCardAction;
2269
2350
  }
2270
2351
  })
2271
- })));
2352
+ })))];
2272
2353
  }
2273
2354
  })));
2274
2355
  };
@@ -2467,11 +2548,36 @@ var PLUGIN_REGISTRY = {
2467
2548
  isNew: true,
2468
2549
  // New plugin banner
2469
2550
  tags: ["TanStack"]
2470
- }
2551
+ },
2471
2552
  // ==========================================
2472
2553
  // THIRD-PARTY PLUGINS - Examples
2473
2554
  // ==========================================
2474
2555
  // External contributors can add their plugins below!
2556
+ // Dimano — Prefetch Heatmap for TanStack Router
2557
+ "@dimano/ts-devtools-plugin-prefetch-heatmap": {
2558
+ packageName: "@dimano/ts-devtools-plugin-prefetch-heatmap",
2559
+ title: "Prefetch Heatmap",
2560
+ description: "Visualize TanStack Router prefetch intent, hits, and waste with a color overlay and a live metrics panel.",
2561
+ requires: {
2562
+ packageName: "@tanstack/react-router",
2563
+ minVersion: "1.0.0"
2564
+ },
2565
+ // default export registers the plugin
2566
+ pluginImport: {
2567
+ importName: "registerPrefetchHeatmapPlugin",
2568
+ type: "function"
2569
+ },
2570
+ // helps the host match your plugin deterministically
2571
+ pluginId: "prefetch-heatmap",
2572
+ // show a nice card in the marketplace
2573
+ logoUrl: "https://raw.githubusercontent.com/dimitrianoudi/tanstack-prefetch-heatmap/main/assets/prefetch-heatmap-card.png",
2574
+ docsUrl: "https://github.com/dimitrianoudi/tanstack-prefetch-heatmap#prefetch-heatmap-devtools-plugin",
2575
+ repoUrl: "https://github.com/dimitrianoudi/tanstack-prefetch-heatmap",
2576
+ author: "Dimitris Anoudis (@dimitrianoudi)",
2577
+ framework: "react",
2578
+ isNew: true,
2579
+ tags: ["Router", "Prefetch", "Analytics", "Overlay", "TanStack"]
2580
+ }
2475
2581
  };
2476
2582
  function getAllPluginMetadata() {
2477
2583
  return Object.values(PLUGIN_REGISTRY);
@@ -2657,6 +2763,15 @@ var buildPluginCards = (pkg, currentFramework, registeredPlugins, existingCards)
2657
2763
  };
2658
2764
  var groupIntoSections = (allCards) => {
2659
2765
  const sections = [];
2766
+ const featuredCards = allCards.filter(
2767
+ (c) => c.metadata?.featured && c.actionType !== "already-installed" && c.isCurrentFramework
2768
+ // Only show featured plugins for current framework
2769
+ );
2770
+ sections.push({
2771
+ id: "featured",
2772
+ displayName: "\u2B50 Featured",
2773
+ cards: featuredCards
2774
+ });
2660
2775
  const activeCards = allCards.filter(
2661
2776
  (c) => c.actionType === "already-installed" && c.isRegistered
2662
2777
  );
@@ -2667,17 +2782,6 @@ var groupIntoSections = (allCards) => {
2667
2782
  cards: activeCards
2668
2783
  });
2669
2784
  }
2670
- const featuredCards = allCards.filter(
2671
- (c) => c.metadata?.featured && c.actionType !== "already-installed" && c.isCurrentFramework
2672
- // Only show featured plugins for current framework
2673
- );
2674
- if (featuredCards.length > 0) {
2675
- sections.push({
2676
- id: "featured",
2677
- displayName: "\u2B50 Featured",
2678
- cards: featuredCards
2679
- });
2680
- }
2681
2785
  const availableCards = allCards.filter(
2682
2786
  (c) => c.isCurrentFramework && c.actionType !== "already-installed" && !c.metadata?.featured
2683
2787
  // Not featured (already in featured section)
@@ -2922,7 +3026,7 @@ var PluginMarketplace = () => {
2922
3026
  // src/tabs/plugins-tab.tsx
2923
3027
  var _tmpl$17 = ["<div", "><div", "><div", "><div", ">", "</div><div", "><h3>Add More</h3></div></div></div>", "</div>"];
2924
3028
  var _tmpl$28 = ["<div", '><h3 id="', '"></h3></div>'];
2925
- var _tmpl$34 = ['<div id="', '"', "></div>"];
3029
+ var _tmpl$35 = ['<div id="', '"', "></div>"];
2926
3030
  var PluginsTab = () => {
2927
3031
  const {
2928
3032
  plugins,
@@ -2987,7 +3091,7 @@ var PluginsTab = () => {
2987
3091
  get each() {
2988
3092
  return activePlugins();
2989
3093
  },
2990
- children: (pluginId) => ssr(_tmpl$34, `${escape(PLUGIN_CONTAINER_ID, true)}-${escape(pluginId, true)}`, ssrAttribute("class", escape(styles().pluginsTabContent, true), false))
3094
+ children: (pluginId) => ssr(_tmpl$35, `${escape(PLUGIN_CONTAINER_ID, true)}-${escape(pluginId, true)}`, ssrAttribute("class", escape(styles().pluginsTabContent, true), false))
2991
3095
  });
2992
3096
  },
2993
3097
  get children() {
@@ -3061,9 +3165,9 @@ function useHeadChanges(onChange, opts = {}) {
3061
3165
  // src/tabs/seo-tab.tsx
3062
3166
  var _tmpl$18 = ["<div", ' style="', '"><div', ' style="', '">', " Preview</div>", "<div", ">", "</div><div", ">", "</div><div", ">", "</div></div>"];
3063
3167
  var _tmpl$29 = ["<img", ' alt="Preview"', ">"];
3064
- var _tmpl$35 = ["<div", ' style="', '">No Image</div>'];
3065
- var _tmpl$44 = ["<div", ">", "</div>"];
3066
- var _tmpl$53 = ["<div>", "", "</div>"];
3168
+ var _tmpl$36 = ["<div", ' style="', '">No Image</div>'];
3169
+ var _tmpl$45 = ["<div", ">", "</div>"];
3170
+ var _tmpl$54 = ["<div>", "", "</div>"];
3067
3171
  var _tmpl$63 = ["<div", "><strong>Missing tags for ", ":</strong><ul", ">", "</ul></div>"];
3068
3172
  var _tmpl$72 = ["<li", ">", "</li>"];
3069
3173
  var SOCIALS = [
@@ -3190,7 +3294,7 @@ var SOCIALS = [
3190
3294
  ];
3191
3295
  function SocialPreview(props) {
3192
3296
  const styles = useStyles();
3193
- return ssr(_tmpl$18, ssrAttribute("class", escape(styles().seoPreviewCard, true), false), "border-color:" + escape(props.color, true), ssrAttribute("class", escape(styles().seoPreviewHeader, true), false), "color:" + escape(props.color, true), escape(props.network), props.meta.image ? ssr(_tmpl$29, ssrAttribute("src", escape(props.meta.image, true), false), ssrAttribute("class", escape(styles().seoPreviewImage, true), false)) : ssr(_tmpl$35, ssrAttribute("class", escape(styles().seoPreviewImage, true), false), "background:#222;color:#888;display:flex;align-items:center;justify-content:center;min-height:80px;width:100%"), ssrAttribute("class", escape(styles().seoPreviewTitle, true), false), escape(props.meta.title) || "No Title", ssrAttribute("class", escape(styles().seoPreviewDesc, true), false), escape(props.meta.description) || "No Description", ssrAttribute("class", escape(styles().seoPreviewUrl, true), false), escape(props.meta.url) || escape(window.location.href));
3297
+ return ssr(_tmpl$18, ssrAttribute("class", escape(styles().seoPreviewCard, true), false), "border-color:" + escape(props.color, true), ssrAttribute("class", escape(styles().seoPreviewHeader, true), false), "color:" + escape(props.color, true), escape(props.network), props.meta.image ? ssr(_tmpl$29, ssrAttribute("src", escape(props.meta.image, true), false), ssrAttribute("class", escape(styles().seoPreviewImage, true), false)) : ssr(_tmpl$36, ssrAttribute("class", escape(styles().seoPreviewImage, true), false), "background:#222;color:#888;display:flex;align-items:center;justify-content:center;min-height:80px;width:100%"), ssrAttribute("class", escape(styles().seoPreviewTitle, true), false), escape(props.meta.title) || "No Title", ssrAttribute("class", escape(styles().seoPreviewDesc, true), false), escape(props.meta.description) || "No Description", ssrAttribute("class", escape(styles().seoPreviewUrl, true), false), escape(props.meta.url) || escape(window.location.href));
3194
3298
  }
3195
3299
  var SeoTab = () => {
3196
3300
  const [reports, setReports] = createSignal(analyzeHead());
@@ -3235,13 +3339,13 @@ var SeoTab = () => {
3235
3339
  }
3236
3340
  }), createComponent(SectionDescription, {
3237
3341
  children: "See how your current page will look when shared on popular social networks. The tool checks for essential meta tags and highlights any that are missing."
3238
- }), ssr(_tmpl$44, ssrAttribute("class", escape(styles().seoPreviewSection, true), false), escape(createComponent(For, {
3342
+ }), ssr(_tmpl$45, ssrAttribute("class", escape(styles().seoPreviewSection, true), false), escape(createComponent(For, {
3239
3343
  get each() {
3240
3344
  return reports();
3241
3345
  },
3242
3346
  children: (report, i) => {
3243
3347
  const social = SOCIALS[i()];
3244
- return ssr(_tmpl$53, escape(createComponent(SocialPreview, {
3348
+ return ssr(_tmpl$54, escape(createComponent(SocialPreview, {
3245
3349
  get meta() {
3246
3350
  return report.found;
3247
3351
  },
@@ -3286,7 +3390,7 @@ var tabs = [{
3286
3390
  // src/components/tabs.tsx
3287
3391
  var _tmpl$19 = ["<div", ">", "", "</div>"];
3288
3392
  var _tmpl$210 = ['<button type="button"', ">", "</button>"];
3289
- var _tmpl$36 = ['<div style="', '"><button type="button"', ">", '</button><button type="button"', ">", "</button></div>"];
3393
+ var _tmpl$37 = ['<div style="', '"><button type="button"', ">", '</button><button type="button"', ">", "</button></div>"];
3290
3394
  var Tabs = (props) => {
3291
3395
  const styles = useStyles();
3292
3396
  const {
@@ -3302,7 +3406,7 @@ var Tabs = (props) => {
3302
3406
  children: (tab) => ssr(_tmpl$210, ssrAttribute("class", escape(clsx3(styles().tab, {
3303
3407
  active: state().activeTab === tab.id
3304
3408
  }), true), false), escape(tab.icon()))
3305
- })), pipWindow().pipWindow !== null ? escape(null) : ssr(_tmpl$36, "margin-top:auto", ssrAttribute("class", escape(clsx3(styles().tab, "detach"), true), false), escape(createComponent(PiP, {})), ssrAttribute("class", escape(clsx3(styles().tab, "close"), true), false), escape(createComponent(X, {}))));
3409
+ })), pipWindow().pipWindow !== null ? escape(null) : ssr(_tmpl$37, "margin-top:auto", ssrAttribute("class", escape(clsx3(styles().tab, "detach"), true), false), escape(createComponent(PiP, {})), ssrAttribute("class", escape(clsx3(styles().tab, "close"), true), false), escape(createComponent(X, {}))));
3306
3410
  };
3307
3411
  var _tmpl$20 = ["<div", ">", "</div>"];
3308
3412
  var TabContent = () => {
@@ -1597,6 +1597,75 @@ var stylesFactory = (theme) => {
1597
1597
  text-transform: uppercase;
1598
1598
  letter-spacing: 0.05em;
1599
1599
  `,
1600
+ pluginMarketplaceFeatureBanner: css2`
1601
+ margin-top: 1rem;
1602
+ padding: 1.25rem 1.5rem;
1603
+ background: ${t(
1604
+ "linear-gradient(135deg, #3b82f6 0%, #2563eb 100%)",
1605
+ "linear-gradient(135deg, #1e3a8a 0%, #1e40af 100%)"
1606
+ )};
1607
+ border-radius: 0.75rem;
1608
+ border: 1px solid ${t(colors.blue[400], colors.blue[800])};
1609
+ box-shadow:
1610
+ 0 4px 6px -1px rgba(0, 0, 0, 0.1),
1611
+ 0 2px 4px -1px rgba(0, 0, 0, 0.06);
1612
+ `,
1613
+ pluginMarketplaceFeatureBannerContent: css2`
1614
+ display: flex;
1615
+ flex-direction: column;
1616
+ gap: 0.75rem;
1617
+ `,
1618
+ pluginMarketplaceFeatureBannerTitle: css2`
1619
+ font-size: 1.125rem;
1620
+ font-weight: 700;
1621
+ color: white;
1622
+ margin: 0;
1623
+ display: flex;
1624
+ align-items: center;
1625
+ gap: 0.5rem;
1626
+ `,
1627
+ pluginMarketplaceFeatureBannerIcon: css2`
1628
+ width: 24px;
1629
+ height: 24px;
1630
+ display: inline-flex;
1631
+ `,
1632
+ pluginMarketplaceFeatureBannerText: css2`
1633
+ font-size: 0.95rem;
1634
+ color: ${t("rgba(255, 255, 255, 0.95)", "rgba(255, 255, 255, 0.9)")};
1635
+ line-height: 1.5;
1636
+ margin: 0;
1637
+ `,
1638
+ pluginMarketplaceFeatureBannerButton: css2`
1639
+ display: inline-flex;
1640
+ align-items: center;
1641
+ gap: 0.5rem;
1642
+ padding: 0.625rem 1.25rem;
1643
+ background: white;
1644
+ color: ${colors.blue[600]};
1645
+ font-weight: 600;
1646
+ font-size: 0.95rem;
1647
+ border-radius: 0.5rem;
1648
+ border: none;
1649
+ cursor: pointer;
1650
+ transition: all 0.2s ease;
1651
+ text-decoration: none;
1652
+ align-self: flex-start;
1653
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
1654
+
1655
+ &:hover {
1656
+ background: ${t(colors.gray[50], colors.gray[100])};
1657
+ transform: translateY(-1px);
1658
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
1659
+ }
1660
+
1661
+ &:active {
1662
+ transform: translateY(0);
1663
+ }
1664
+ `,
1665
+ pluginMarketplaceFeatureBannerButtonIcon: css2`
1666
+ width: 18px;
1667
+ height: 18px;
1668
+ `,
1600
1669
  pluginMarketplaceCardDisabled: css2`
1601
1670
  opacity: 0.6;
1602
1671
  filter: grayscale(0.3);
@@ -2438,46 +2507,82 @@ var PluginCardComponent = (props) => {
2438
2507
  };
2439
2508
 
2440
2509
  // src/tabs/marketplace/plugin-section.tsx
2441
- var _tmpl$10 = /* @__PURE__ */ template(`<div>`);
2442
- var _tmpl$24 = /* @__PURE__ */ template(`<div><div><div><div></div><h3>`);
2510
+ var _tmpl$10 = /* @__PURE__ */ template(`<svg xmlns=http://www.w3.org/2000/svg viewBox="0 0 24 24"fill=currentColor><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z">`);
2511
+ var _tmpl$24 = /* @__PURE__ */ template(`<svg xmlns=http://www.w3.org/2000/svg viewBox="0 0 24 24"fill=none stroke=currentColor stroke-width=2 stroke-linecap=round stroke-linejoin=round><rect x=2 y=4 width=20 height=16 rx=2></rect><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7">`);
2512
+ var _tmpl$34 = /* @__PURE__ */ template(`<div><div><h4><span></span>Want to be featured here?</h4><p>If you've built a plugin for TanStack Devtools and would like to showcase it in the featured section, we'd love to hear from you! Reach out to us to discuss partnership opportunities.</p><a href="mailto:partners+devtools@tanstack.com?subject=Featured%20Plugin%20Partnership%20Inquiry"><span></span>Contact Us`);
2513
+ var _tmpl$43 = /* @__PURE__ */ template(`<div>`);
2514
+ var _tmpl$53 = /* @__PURE__ */ template(`<div><div><div><div></div><h3>`);
2515
+ var StarIcon = () => _tmpl$10();
2516
+ var MailIcon = () => _tmpl$24();
2443
2517
  var PluginSectionComponent = (props) => {
2444
2518
  const styles = useStyles();
2445
2519
  return (() => {
2446
- var _el$ = _tmpl$24(), _el$2 = _el$.firstChild, _el$3 = _el$2.firstChild, _el$4 = _el$3.firstChild, _el$5 = _el$4.nextSibling;
2447
- addEventListener(_el$2, "click", props.onToggleCollapse, true);
2448
- insert(_el$4, createComponent(ChevronDownIcon, {}));
2449
- insert(_el$5, () => props.section.displayName);
2450
- insert(_el$, createComponent(Show, {
2520
+ var _el$3 = _tmpl$53(), _el$4 = _el$3.firstChild, _el$5 = _el$4.firstChild, _el$6 = _el$5.firstChild, _el$7 = _el$6.nextSibling;
2521
+ addEventListener(_el$4, "click", props.onToggleCollapse, true);
2522
+ insert(_el$6, createComponent(ChevronDownIcon, {}));
2523
+ insert(_el$7, () => props.section.displayName);
2524
+ insert(_el$3, createComponent(Show, {
2451
2525
  get when() {
2452
2526
  return !props.isCollapsed();
2453
2527
  },
2454
2528
  get children() {
2455
- var _el$6 = _tmpl$10();
2456
- insert(_el$6, createComponent(For, {
2457
- get each() {
2458
- return props.section.cards;
2529
+ return [createComponent(Show, {
2530
+ get when() {
2531
+ return props.section.id === "featured";
2459
2532
  },
2460
- children: (card) => createComponent(PluginCardComponent, {
2461
- card,
2462
- get onAction() {
2463
- return props.onCardAction;
2464
- }
2465
- })
2466
- }));
2467
- effect(() => className(_el$6, styles().pluginMarketplaceGrid));
2468
- return _el$6;
2533
+ get children() {
2534
+ var _el$8 = _tmpl$34(), _el$9 = _el$8.firstChild, _el$0 = _el$9.firstChild, _el$1 = _el$0.firstChild, _el$10 = _el$0.nextSibling, _el$11 = _el$10.nextSibling, _el$12 = _el$11.firstChild;
2535
+ insert(_el$1, createComponent(StarIcon, {}));
2536
+ insert(_el$12, createComponent(MailIcon, {}));
2537
+ effect((_p$) => {
2538
+ var _v$ = styles().pluginMarketplaceFeatureBanner, _v$2 = styles().pluginMarketplaceFeatureBannerContent, _v$3 = styles().pluginMarketplaceFeatureBannerTitle, _v$4 = styles().pluginMarketplaceFeatureBannerIcon, _v$5 = styles().pluginMarketplaceFeatureBannerText, _v$6 = styles().pluginMarketplaceFeatureBannerButton, _v$7 = styles().pluginMarketplaceFeatureBannerButtonIcon;
2539
+ _v$ !== _p$.e && className(_el$8, _p$.e = _v$);
2540
+ _v$2 !== _p$.t && className(_el$9, _p$.t = _v$2);
2541
+ _v$3 !== _p$.a && className(_el$0, _p$.a = _v$3);
2542
+ _v$4 !== _p$.o && className(_el$1, _p$.o = _v$4);
2543
+ _v$5 !== _p$.i && className(_el$10, _p$.i = _v$5);
2544
+ _v$6 !== _p$.n && className(_el$11, _p$.n = _v$6);
2545
+ _v$7 !== _p$.s && className(_el$12, _p$.s = _v$7);
2546
+ return _p$;
2547
+ }, {
2548
+ e: void 0,
2549
+ t: void 0,
2550
+ a: void 0,
2551
+ o: void 0,
2552
+ i: void 0,
2553
+ n: void 0,
2554
+ s: void 0
2555
+ });
2556
+ return _el$8;
2557
+ }
2558
+ }), (() => {
2559
+ var _el$13 = _tmpl$43();
2560
+ insert(_el$13, createComponent(For, {
2561
+ get each() {
2562
+ return props.section.cards;
2563
+ },
2564
+ children: (card) => createComponent(PluginCardComponent, {
2565
+ card,
2566
+ get onAction() {
2567
+ return props.onCardAction;
2568
+ }
2569
+ })
2570
+ }));
2571
+ effect(() => className(_el$13, styles().pluginMarketplaceGrid));
2572
+ return _el$13;
2573
+ })()];
2469
2574
  }
2470
2575
  }), null);
2471
2576
  effect((_p$) => {
2472
- var _v$ = styles().pluginMarketplaceSection, _v$2 = styles().pluginMarketplaceSectionHeader, _v$3 = styles().pluginMarketplaceSectionHeaderLeft, _v$4 = styles().pluginMarketplaceSectionChevron, _v$5 = {
2577
+ var _v$8 = styles().pluginMarketplaceSection, _v$9 = styles().pluginMarketplaceSectionHeader, _v$0 = styles().pluginMarketplaceSectionHeaderLeft, _v$1 = styles().pluginMarketplaceSectionChevron, _v$10 = {
2473
2578
  [styles().pluginMarketplaceSectionChevronCollapsed]: props.isCollapsed()
2474
- }, _v$6 = styles().pluginMarketplaceSectionTitle;
2475
- _v$ !== _p$.e && className(_el$, _p$.e = _v$);
2476
- _v$2 !== _p$.t && className(_el$2, _p$.t = _v$2);
2477
- _v$3 !== _p$.a && className(_el$3, _p$.a = _v$3);
2478
- _v$4 !== _p$.o && className(_el$4, _p$.o = _v$4);
2479
- _p$.i = classList(_el$4, _v$5, _p$.i);
2480
- _v$6 !== _p$.n && className(_el$5, _p$.n = _v$6);
2579
+ }, _v$11 = styles().pluginMarketplaceSectionTitle;
2580
+ _v$8 !== _p$.e && className(_el$3, _p$.e = _v$8);
2581
+ _v$9 !== _p$.t && className(_el$4, _p$.t = _v$9);
2582
+ _v$0 !== _p$.a && className(_el$5, _p$.a = _v$0);
2583
+ _v$1 !== _p$.o && className(_el$6, _p$.o = _v$1);
2584
+ _p$.i = classList(_el$6, _v$10, _p$.i);
2585
+ _v$11 !== _p$.n && className(_el$7, _p$.n = _v$11);
2481
2586
  return _p$;
2482
2587
  }, {
2483
2588
  e: void 0,
@@ -2487,7 +2592,7 @@ var PluginSectionComponent = (props) => {
2487
2592
  i: void 0,
2488
2593
  n: void 0
2489
2594
  });
2490
- return _el$;
2595
+ return _el$3;
2491
2596
  })();
2492
2597
  };
2493
2598
  delegateEvents(["click"]);
@@ -2757,11 +2862,36 @@ var PLUGIN_REGISTRY = {
2757
2862
  isNew: true,
2758
2863
  // New plugin banner
2759
2864
  tags: ["TanStack"]
2760
- }
2865
+ },
2761
2866
  // ==========================================
2762
2867
  // THIRD-PARTY PLUGINS - Examples
2763
2868
  // ==========================================
2764
2869
  // External contributors can add their plugins below!
2870
+ // Dimano — Prefetch Heatmap for TanStack Router
2871
+ "@dimano/ts-devtools-plugin-prefetch-heatmap": {
2872
+ packageName: "@dimano/ts-devtools-plugin-prefetch-heatmap",
2873
+ title: "Prefetch Heatmap",
2874
+ description: "Visualize TanStack Router prefetch intent, hits, and waste with a color overlay and a live metrics panel.",
2875
+ requires: {
2876
+ packageName: "@tanstack/react-router",
2877
+ minVersion: "1.0.0"
2878
+ },
2879
+ // default export registers the plugin
2880
+ pluginImport: {
2881
+ importName: "registerPrefetchHeatmapPlugin",
2882
+ type: "function"
2883
+ },
2884
+ // helps the host match your plugin deterministically
2885
+ pluginId: "prefetch-heatmap",
2886
+ // show a nice card in the marketplace
2887
+ logoUrl: "https://raw.githubusercontent.com/dimitrianoudi/tanstack-prefetch-heatmap/main/assets/prefetch-heatmap-card.png",
2888
+ docsUrl: "https://github.com/dimitrianoudi/tanstack-prefetch-heatmap#prefetch-heatmap-devtools-plugin",
2889
+ repoUrl: "https://github.com/dimitrianoudi/tanstack-prefetch-heatmap",
2890
+ author: "Dimitris Anoudis (@dimitrianoudi)",
2891
+ framework: "react",
2892
+ isNew: true,
2893
+ tags: ["Router", "Prefetch", "Analytics", "Overlay", "TanStack"]
2894
+ }
2765
2895
  };
2766
2896
  function getAllPluginMetadata() {
2767
2897
  return Object.values(PLUGIN_REGISTRY);
@@ -2947,6 +3077,15 @@ var buildPluginCards = (pkg, currentFramework, registeredPlugins, existingCards)
2947
3077
  };
2948
3078
  var groupIntoSections = (allCards) => {
2949
3079
  const sections = [];
3080
+ const featuredCards = allCards.filter(
3081
+ (c) => c.metadata?.featured && c.actionType !== "already-installed" && c.isCurrentFramework
3082
+ // Only show featured plugins for current framework
3083
+ );
3084
+ sections.push({
3085
+ id: "featured",
3086
+ displayName: "\u2B50 Featured",
3087
+ cards: featuredCards
3088
+ });
2950
3089
  const activeCards = allCards.filter(
2951
3090
  (c) => c.actionType === "already-installed" && c.isRegistered
2952
3091
  );
@@ -2957,17 +3096,6 @@ var groupIntoSections = (allCards) => {
2957
3096
  cards: activeCards
2958
3097
  });
2959
3098
  }
2960
- const featuredCards = allCards.filter(
2961
- (c) => c.metadata?.featured && c.actionType !== "already-installed" && c.isCurrentFramework
2962
- // Only show featured plugins for current framework
2963
- );
2964
- if (featuredCards.length > 0) {
2965
- sections.push({
2966
- id: "featured",
2967
- displayName: "\u2B50 Featured",
2968
- cards: featuredCards
2969
- });
2970
- }
2971
3099
  const availableCards = allCards.filter(
2972
3100
  (c) => c.isCurrentFramework && c.actionType !== "already-installed" && !c.metadata?.featured
2973
3101
  // Not featured (already in featured section)
@@ -3234,7 +3362,7 @@ var PluginMarketplace = () => {
3234
3362
  // src/tabs/plugins-tab.tsx
3235
3363
  var _tmpl$15 = /* @__PURE__ */ template(`<div><div><div><div></div><div><h3>Add More`);
3236
3364
  var _tmpl$27 = /* @__PURE__ */ template(`<div><h3>`);
3237
- var _tmpl$34 = /* @__PURE__ */ template(`<div>`);
3365
+ var _tmpl$35 = /* @__PURE__ */ template(`<div>`);
3238
3366
  var PluginsTab = () => {
3239
3367
  const {
3240
3368
  plugins,
@@ -3331,7 +3459,7 @@ var PluginsTab = () => {
3331
3459
  return activePlugins();
3332
3460
  },
3333
3461
  children: (pluginId) => (() => {
3334
- var _el$8 = _tmpl$34();
3462
+ var _el$8 = _tmpl$35();
3335
3463
  use((el) => {
3336
3464
  setPluginRefs((prev) => {
3337
3465
  const updated = new Map(prev);
@@ -3437,9 +3565,9 @@ function useHeadChanges(onChange, opts = {}) {
3437
3565
  // src/tabs/seo-tab.tsx
3438
3566
  var _tmpl$16 = /* @__PURE__ */ template(`<div><div> Preview</div><div></div><div></div><div>`);
3439
3567
  var _tmpl$28 = /* @__PURE__ */ template(`<img alt=Preview>`);
3440
- var _tmpl$35 = /* @__PURE__ */ template(`<div>No Image`);
3441
- var _tmpl$43 = /* @__PURE__ */ template(`<div>`);
3442
- var _tmpl$53 = /* @__PURE__ */ template(`<div><strong>Missing tags for <!>:</strong><ul>`);
3568
+ var _tmpl$36 = /* @__PURE__ */ template(`<div>No Image`);
3569
+ var _tmpl$44 = /* @__PURE__ */ template(`<div>`);
3570
+ var _tmpl$54 = /* @__PURE__ */ template(`<div><strong>Missing tags for <!>:</strong><ul>`);
3443
3571
  var _tmpl$62 = /* @__PURE__ */ template(`<li>`);
3444
3572
  var SOCIALS = [
3445
3573
  {
@@ -3583,7 +3711,7 @@ function SocialPreview(props) {
3583
3711
  });
3584
3712
  return _el$7;
3585
3713
  })() : (() => {
3586
- var _el$8 = _tmpl$35();
3714
+ var _el$8 = _tmpl$36();
3587
3715
  _el$8.style.setProperty("background", "#222");
3588
3716
  _el$8.style.setProperty("color", "#888");
3589
3717
  _el$8.style.setProperty("display", "flex");
@@ -3664,7 +3792,7 @@ var SeoTab = () => {
3664
3792
  }), createComponent(SectionDescription, {
3665
3793
  children: "See how your current page will look when shared on popular social networks. The tool checks for essential meta tags and highlights any that are missing."
3666
3794
  }), (() => {
3667
- var _el$9 = _tmpl$43();
3795
+ var _el$9 = _tmpl$44();
3668
3796
  insert(_el$9, createComponent(For, {
3669
3797
  get each() {
3670
3798
  return reports();
@@ -3672,7 +3800,7 @@ var SeoTab = () => {
3672
3800
  children: (report, i) => {
3673
3801
  const social = SOCIALS[i()];
3674
3802
  return (() => {
3675
- var _el$0 = _tmpl$43();
3803
+ var _el$0 = _tmpl$44();
3676
3804
  insert(_el$0, createComponent(SocialPreview, {
3677
3805
  get meta() {
3678
3806
  return report.found;
@@ -3687,7 +3815,7 @@ var SeoTab = () => {
3687
3815
  insert(_el$0, (() => {
3688
3816
  var _c$2 = memo(() => report.missing.length > 0);
3689
3817
  return () => _c$2() ? (() => {
3690
- var _el$1 = _tmpl$53(), _el$10 = _el$1.firstChild, _el$11 = _el$10.firstChild, _el$13 = _el$11.nextSibling; _el$13.nextSibling; var _el$14 = _el$10.nextSibling;
3818
+ var _el$1 = _tmpl$54(), _el$10 = _el$1.firstChild, _el$11 = _el$10.firstChild, _el$13 = _el$11.nextSibling; _el$13.nextSibling; var _el$14 = _el$10.nextSibling;
3691
3819
  insert(_el$10, () => social?.network, _el$13);
3692
3820
  insert(_el$14, createComponent(For, {
3693
3821
  get each() {
@@ -3746,7 +3874,7 @@ var tabs = [{
3746
3874
  // src/components/tabs.tsx
3747
3875
  var _tmpl$17 = /* @__PURE__ */ template(`<div>`);
3748
3876
  var _tmpl$29 = /* @__PURE__ */ template(`<button type=button>`);
3749
- var _tmpl$36 = /* @__PURE__ */ template(`<div><button type=button></button><button type=button>`);
3877
+ var _tmpl$37 = /* @__PURE__ */ template(`<div><button type=button></button><button type=button>`);
3750
3878
  var Tabs = (props) => {
3751
3879
  const styles = useStyles();
3752
3880
  const {
@@ -3785,7 +3913,7 @@ var Tabs = (props) => {
3785
3913
  insert(_el$, (() => {
3786
3914
  var _c$ = memo(() => pipWindow().pipWindow !== null);
3787
3915
  return () => _c$() ? null : (() => {
3788
- var _el$3 = _tmpl$36(), _el$4 = _el$3.firstChild, _el$5 = _el$4.nextSibling;
3916
+ var _el$3 = _tmpl$37(), _el$4 = _el$3.firstChild, _el$5 = _el$4.nextSibling;
3789
3917
  _el$3.style.setProperty("margin-top", "auto");
3790
3918
  _el$4.$$click = handleDetachment;
3791
3919
  insert(_el$4, createComponent(PiP, {}));
package/dist/index.js CHANGED
@@ -30,7 +30,7 @@ var TanStackDevtoolsCore = class {
30
30
  const mountTo = el;
31
31
  const dispose = render(() => {
32
32
  const _self$ = this;
33
- this.#Component = lazy(() => import('./devtools/YRFZDV5N.js'));
33
+ this.#Component = lazy(() => import('./devtools/UUNAZSBD.js'));
34
34
  const Devtools = this.#Component;
35
35
  this.#eventBus = new ClientEventBus(this.#eventBusConfig);
36
36
  this.#eventBus.start();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/devtools",
3
- "version": "0.6.22",
3
+ "version": "0.6.24",
4
4
  "description": "TanStack Devtools is a set of tools for building advanced devtools for your application.",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -57,7 +57,7 @@
57
57
  "solid-js": "^1.9.9",
58
58
  "@tanstack/devtools-client": "0.0.3",
59
59
  "@tanstack/devtools-event-bus": "0.3.3",
60
- "@tanstack/devtools-ui": "0.4.3"
60
+ "@tanstack/devtools-ui": "0.4.4"
61
61
  },
62
62
  "peerDependencies": {
63
63
  "solid-js": ">=1.9.7"
@@ -1168,6 +1168,75 @@ const stylesFactory = (theme: DevtoolsStore['settings']['theme']) => {
1168
1168
  text-transform: uppercase;
1169
1169
  letter-spacing: 0.05em;
1170
1170
  `,
1171
+ pluginMarketplaceFeatureBanner: css`
1172
+ margin-top: 1rem;
1173
+ padding: 1.25rem 1.5rem;
1174
+ background: ${t(
1175
+ 'linear-gradient(135deg, #3b82f6 0%, #2563eb 100%)',
1176
+ 'linear-gradient(135deg, #1e3a8a 0%, #1e40af 100%)',
1177
+ )};
1178
+ border-radius: 0.75rem;
1179
+ border: 1px solid ${t(colors.blue[400], colors.blue[800])};
1180
+ box-shadow:
1181
+ 0 4px 6px -1px rgba(0, 0, 0, 0.1),
1182
+ 0 2px 4px -1px rgba(0, 0, 0, 0.06);
1183
+ `,
1184
+ pluginMarketplaceFeatureBannerContent: css`
1185
+ display: flex;
1186
+ flex-direction: column;
1187
+ gap: 0.75rem;
1188
+ `,
1189
+ pluginMarketplaceFeatureBannerTitle: css`
1190
+ font-size: 1.125rem;
1191
+ font-weight: 700;
1192
+ color: white;
1193
+ margin: 0;
1194
+ display: flex;
1195
+ align-items: center;
1196
+ gap: 0.5rem;
1197
+ `,
1198
+ pluginMarketplaceFeatureBannerIcon: css`
1199
+ width: 24px;
1200
+ height: 24px;
1201
+ display: inline-flex;
1202
+ `,
1203
+ pluginMarketplaceFeatureBannerText: css`
1204
+ font-size: 0.95rem;
1205
+ color: ${t('rgba(255, 255, 255, 0.95)', 'rgba(255, 255, 255, 0.9)')};
1206
+ line-height: 1.5;
1207
+ margin: 0;
1208
+ `,
1209
+ pluginMarketplaceFeatureBannerButton: css`
1210
+ display: inline-flex;
1211
+ align-items: center;
1212
+ gap: 0.5rem;
1213
+ padding: 0.625rem 1.25rem;
1214
+ background: white;
1215
+ color: ${colors.blue[600]};
1216
+ font-weight: 600;
1217
+ font-size: 0.95rem;
1218
+ border-radius: 0.5rem;
1219
+ border: none;
1220
+ cursor: pointer;
1221
+ transition: all 0.2s ease;
1222
+ text-decoration: none;
1223
+ align-self: flex-start;
1224
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
1225
+
1226
+ &:hover {
1227
+ background: ${t(colors.gray[50], colors.gray[100])};
1228
+ transform: translateY(-1px);
1229
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
1230
+ }
1231
+
1232
+ &:active {
1233
+ transform: translateY(0);
1234
+ }
1235
+ `,
1236
+ pluginMarketplaceFeatureBannerButtonIcon: css`
1237
+ width: 18px;
1238
+ height: 18px;
1239
+ `,
1171
1240
  pluginMarketplaceCardDisabled: css`
1172
1241
  opacity: 0.6;
1173
1242
  filter: grayscale(0.3);
@@ -12,6 +12,31 @@ interface PluginSectionComponentProps {
12
12
  onCardAction: (card: PluginCard) => void
13
13
  }
14
14
 
15
+ const StarIcon = () => (
16
+ <svg
17
+ xmlns="http://www.w3.org/2000/svg"
18
+ viewBox="0 0 24 24"
19
+ fill="currentColor"
20
+ >
21
+ <path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" />
22
+ </svg>
23
+ )
24
+
25
+ const MailIcon = () => (
26
+ <svg
27
+ xmlns="http://www.w3.org/2000/svg"
28
+ viewBox="0 0 24 24"
29
+ fill="none"
30
+ stroke="currentColor"
31
+ stroke-width="2"
32
+ stroke-linecap="round"
33
+ stroke-linejoin="round"
34
+ >
35
+ <rect x="2" y="4" width="20" height="16" rx="2" />
36
+ <path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7" />
37
+ </svg>
38
+ )
39
+
15
40
  export const PluginSectionComponent = (props: PluginSectionComponentProps) => {
16
41
  const styles = useStyles()
17
42
 
@@ -38,6 +63,33 @@ export const PluginSectionComponent = (props: PluginSectionComponentProps) => {
38
63
  </div>
39
64
 
40
65
  <Show when={!props.isCollapsed()}>
66
+ <Show when={props.section.id === 'featured'}>
67
+ <div class={styles().pluginMarketplaceFeatureBanner}>
68
+ <div class={styles().pluginMarketplaceFeatureBannerContent}>
69
+ <h4 class={styles().pluginMarketplaceFeatureBannerTitle}>
70
+ <span class={styles().pluginMarketplaceFeatureBannerIcon}>
71
+ <StarIcon />
72
+ </span>
73
+ Want to be featured here?
74
+ </h4>
75
+ <p class={styles().pluginMarketplaceFeatureBannerText}>
76
+ If you've built a plugin for TanStack Devtools and would like to
77
+ showcase it in the featured section, we'd love to hear from you!
78
+ Reach out to us to discuss partnership opportunities.
79
+ </p>
80
+ <a
81
+ href="mailto:partners+devtools@tanstack.com?subject=Featured%20Plugin%20Partnership%20Inquiry"
82
+ class={styles().pluginMarketplaceFeatureBannerButton}
83
+ >
84
+ <span class={styles().pluginMarketplaceFeatureBannerButtonIcon}>
85
+ <MailIcon />
86
+ </span>
87
+ Contact Us
88
+ </a>
89
+ </div>
90
+ </div>
91
+ </Show>
92
+
41
93
  <div class={styles().pluginMarketplaceGrid}>
42
94
  <For each={props.section.cards}>
43
95
  {(card) => (
@@ -223,10 +223,11 @@ describe('groupIntoSections', () => {
223
223
 
224
224
  const sections = groupIntoSections(cards)
225
225
 
226
- expect(sections).toHaveLength(1)
227
- expect(sections[0]?.id).toBe('active')
228
- expect(sections[0]?.displayName).toBe('✓ Active Plugins')
229
- expect(sections[0]?.cards).toHaveLength(1)
226
+ expect(sections).toHaveLength(2) // Featured (always present) + Active
227
+ expect(sections[0]?.id).toBe('featured')
228
+ expect(sections[1]?.id).toBe('active')
229
+ expect(sections[1]?.displayName).toBe('✓ Active Plugins')
230
+ expect(sections[1]?.cards).toHaveLength(1)
230
231
  })
231
232
 
232
233
  it('should group featured plugins', () => {
@@ -266,9 +267,11 @@ describe('groupIntoSections', () => {
266
267
 
267
268
  const sections = groupIntoSections(cards)
268
269
 
269
- expect(sections).toHaveLength(1)
270
- expect(sections[0]?.id).toBe('active')
271
- expect(sections.find((s) => s.id === 'featured')).toBeUndefined()
270
+ expect(sections).toHaveLength(2) // Featured (always present) + Active
271
+ expect(sections[0]?.id).toBe('featured')
272
+ expect(sections[1]?.id).toBe('active')
273
+ expect(sections[0]?.cards).toHaveLength(0) // Featured section empty
274
+ expect(sections[1]?.cards).toHaveLength(1) // Active has the plugin
272
275
  })
273
276
 
274
277
  it('should group available plugins', () => {
@@ -286,9 +289,10 @@ describe('groupIntoSections', () => {
286
289
 
287
290
  const sections = groupIntoSections(cards)
288
291
 
289
- expect(sections).toHaveLength(1)
290
- expect(sections[0]?.id).toBe('available')
291
- expect(sections[0]?.displayName).toBe('Available Plugins')
292
+ expect(sections).toHaveLength(2) // Featured (always present) + Available
293
+ expect(sections[0]?.id).toBe('featured')
294
+ expect(sections[1]?.id).toBe('available')
295
+ expect(sections[1]?.displayName).toBe('Available Plugins')
292
296
  })
293
297
 
294
298
  it('should not include featured plugins in available section', () => {
@@ -345,8 +349,8 @@ describe('groupIntoSections', () => {
345
349
  const sections = groupIntoSections(cards)
346
350
 
347
351
  expect(sections).toHaveLength(3)
348
- expect(sections[0]?.id).toBe('active')
349
- expect(sections[1]?.id).toBe('featured')
352
+ expect(sections[0]?.id).toBe('featured')
353
+ expect(sections[1]?.id).toBe('active')
350
354
  expect(sections[2]?.id).toBe('available')
351
355
  })
352
356
 
@@ -365,12 +369,16 @@ describe('groupIntoSections', () => {
365
369
 
366
370
  const sections = groupIntoSections(cards)
367
371
 
368
- expect(sections).toHaveLength(0)
372
+ expect(sections).toHaveLength(1) // Only featured section (always present, empty)
373
+ expect(sections[0]?.id).toBe('featured')
374
+ expect(sections[0]?.cards).toHaveLength(0)
369
375
  })
370
376
 
371
377
  it('should handle empty card array', () => {
372
378
  const sections = groupIntoSections([])
373
- expect(sections).toHaveLength(0)
379
+ expect(sections).toHaveLength(1) // Featured section always present
380
+ expect(sections[0]?.id).toBe('featured')
381
+ expect(sections[0]?.cards).toHaveLength(0)
374
382
  })
375
383
  })
376
384
 
@@ -201,6 +201,20 @@ export const groupIntoSections = (
201
201
  ): Array<PluginSection> => {
202
202
  const sections: Array<PluginSection> = []
203
203
 
204
+ // Add Featured section first - always show this section
205
+ const featuredCards = allCards.filter(
206
+ (c) =>
207
+ c.metadata?.featured &&
208
+ c.actionType !== 'already-installed' &&
209
+ c.isCurrentFramework, // Only show featured plugins for current framework
210
+ )
211
+ // Always add featured section, even if no cards to show the partner banner
212
+ sections.push({
213
+ id: 'featured',
214
+ displayName: '⭐ Featured',
215
+ cards: featuredCards,
216
+ })
217
+
204
218
  // Add Active Plugins section
205
219
  const activeCards = allCards.filter(
206
220
  (c) => c.actionType === 'already-installed' && c.isRegistered,
@@ -213,21 +227,6 @@ export const groupIntoSections = (
213
227
  })
214
228
  }
215
229
 
216
- // Add Featured section
217
- const featuredCards = allCards.filter(
218
- (c) =>
219
- c.metadata?.featured &&
220
- c.actionType !== 'already-installed' &&
221
- c.isCurrentFramework, // Only show featured plugins for current framework
222
- )
223
- if (featuredCards.length > 0) {
224
- sections.push({
225
- id: 'featured',
226
- displayName: '⭐ Featured',
227
- cards: featuredCards,
228
- })
229
- }
230
-
231
230
  // Add Available section - all plugins for current framework (TanStack + third-party)
232
231
  const availableCards = allCards.filter(
233
232
  (c) =>
@@ -212,6 +212,35 @@ const PLUGIN_REGISTRY: Record<string, PluginMetadata> = {
212
212
  // THIRD-PARTY PLUGINS - Examples
213
213
  // ==========================================
214
214
  // External contributors can add their plugins below!
215
+
216
+ // Dimano — Prefetch Heatmap for TanStack Router
217
+ '@dimano/ts-devtools-plugin-prefetch-heatmap': {
218
+ packageName: '@dimano/ts-devtools-plugin-prefetch-heatmap',
219
+ title: 'Prefetch Heatmap',
220
+ description:
221
+ 'Visualize TanStack Router prefetch intent, hits, and waste with a color overlay and a live metrics panel.',
222
+ requires: {
223
+ packageName: '@tanstack/react-router',
224
+ minVersion: '1.0.0',
225
+ },
226
+ // default export registers the plugin
227
+ pluginImport: {
228
+ importName: 'registerPrefetchHeatmapPlugin',
229
+ type: 'function',
230
+ },
231
+ // helps the host match your plugin deterministically
232
+ pluginId: 'prefetch-heatmap',
233
+ // show a nice card in the marketplace
234
+ logoUrl:
235
+ 'https://raw.githubusercontent.com/dimitrianoudi/tanstack-prefetch-heatmap/main/assets/prefetch-heatmap-card.png',
236
+ docsUrl:
237
+ 'https://github.com/dimitrianoudi/tanstack-prefetch-heatmap#prefetch-heatmap-devtools-plugin',
238
+ repoUrl: 'https://github.com/dimitrianoudi/tanstack-prefetch-heatmap',
239
+ author: 'Dimitris Anoudis (@dimitrianoudi)',
240
+ framework: 'react',
241
+ isNew: true,
242
+ tags: ['Router', 'Prefetch', 'Analytics', 'Overlay', 'TanStack'],
243
+ },
215
244
  }
216
245
 
217
246
  /**