@stackable-labs/mcp-app-extension 1.3.0 → 1.5.0

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
@@ -2801,6 +2801,324 @@ narrow widths:
2801
2801
  `;
2802
2802
  };
2803
2803
 
2804
+ // ../../sdk/extension/ai-docs/src/generators/tailwind-utilities.ts
2805
+ var SAFELIST_DIRECTIVES = [
2806
+ "m-{0,0.5,1,1.5,2,2.5,3,3.5,4,5,6,7,8,9,10,11,12,14,16}",
2807
+ "mx-{0,1,2,3,4,5,6,8,10,12,16,auto} my-{0,1,2,3,4,5,6,8,10,12,16}",
2808
+ "mt-{0,1,2,3,4,5,6,8,10,12,16} mb-{0,1,2,3,4,5,6,8,10,12,16}",
2809
+ "ml-{0,1,2,3,4,5,6,8,10,12,16,auto} mr-{0,1,2,3,4,5,6,8,10,12,16,auto}",
2810
+ "p-{0,0.5,1,1.5,2,2.5,3,3.5,4,5,6,7,8,9,10,11,12,14,16}",
2811
+ "px-{0,1,2,3,4,5,6,8,10,12,16} py-{0,1,2,3,4,5,6,8,10,12,16}",
2812
+ "pt-{0,1,2,3,4,5,6,8,10,12,16} pb-{0,1,2,3,4,5,6,8,10,12,16}",
2813
+ "pl-{0,1,2,3,4,5,6,8,10,12,16} pr-{0,1,2,3,4,5,6,8,10,12,16}",
2814
+ "gap-{0,1,2,3,4,5,6,8,10,12} gap-x-{0,1,2,3,4,5,6,8} gap-y-{0,1,2,3,4,5,6,8}",
2815
+ "text-{xs,sm,base,lg,xl,2xl}",
2816
+ "font-{light,normal,medium,semibold,bold,extrabold}",
2817
+ "uppercase lowercase capitalize normal-case",
2818
+ "tracking-{tight,normal,wide,wider,widest}",
2819
+ "leading-{none,tight,snug,normal,relaxed,loose}",
2820
+ "opacity-{0,5,10,20,25,30,40,50,60,70,75,80,90,95,100}",
2821
+ "block inline inline-block flex inline-flex grid hidden",
2822
+ "flex-{1,auto,initial,none,row,col,wrap,nowrap,row-reverse,col-reverse}",
2823
+ "justify-{start,end,center,between,around,evenly}",
2824
+ "items-{start,end,center,baseline,stretch}",
2825
+ "self-{auto,start,end,center,stretch,baseline}",
2826
+ "w-{0,full,auto,screen,1/2,1/3,2/3,1/4,3/4,1,2,3,4,5,6,8,10,12,16,20,24,32,48}",
2827
+ "h-{0,full,auto,screen,1/2,1/3,2/3,1/4,3/4,1,2,3,4,5,6,8,10,12,16,20,24,32,48}",
2828
+ "min-w-{0,full,fit,min,max} max-w-{none,xs,sm,md,lg,xl,2xl,3xl,4xl,5xl,full,fit}",
2829
+ "aspect-{auto,square,video} aspect-[9/16]",
2830
+ "rounded-{none,sm,md,lg,xl,2xl,3xl,full}",
2831
+ "border border-{0,2,4,8} border-{x,y,t,b,l,r}",
2832
+ "cursor-{pointer,default,not-allowed,wait,text,move,help}",
2833
+ "overflow-{auto,hidden,visible,scroll}",
2834
+ "overflow-x-{auto,hidden,scroll} overflow-y-{auto,hidden,scroll}",
2835
+ "transition transition-{none,all,colors,opacity,transform}",
2836
+ "duration-{75,100,150,200,300,500,700,1000} ease-{linear,in,out,in-out}",
2837
+ "shadow-{sm,md,lg,xl,2xl,none,inner}",
2838
+ "relative absolute fixed sticky static",
2839
+ "z-{0,10,20,30,40,50,auto}",
2840
+ "text-{zinc,slate,gray,neutral}-{50,100,200,300,400,500,600,700,800,900}",
2841
+ "text-{red,green,blue,amber,emerald,sky,indigo}-{500,600,700}",
2842
+ "bg-{zinc,slate,gray,neutral}-{50,100,200,300,400,500,600,700,800,900}",
2843
+ "bg-{red,green,blue,amber,emerald}-{50,100,500,600}",
2844
+ "border-{zinc,slate,gray}-{100,200,300,400} border-transparent",
2845
+ "hover:opacity-{50,75,80,90,100}",
2846
+ "hover:text-{zinc,slate,gray,neutral}-{500,600,700,800,900}",
2847
+ "hover:text-{red,green,blue,amber,emerald,sky,indigo}-{500,600,700}",
2848
+ "hover:bg-{zinc,slate,gray,neutral}-{50,100,200}",
2849
+ "hover:bg-{red,green,blue,amber,emerald}-{50,100,500,600}",
2850
+ "focus:outline-none focus:ring-2 focus:ring-offset-2",
2851
+ "disabled:opacity-50 disabled:cursor-not-allowed",
2852
+ "dark:text-{zinc,slate,gray,neutral}-{50,100,200,300,400,500,600,700,800,900}",
2853
+ "dark:text-{red,green,blue,amber,emerald,sky,indigo}-{500,600,700}",
2854
+ "dark:bg-{zinc,slate,gray,neutral}-{50,100,200,300,400,500,600,700,800,900}",
2855
+ "dark:bg-{red,green,blue,amber,emerald}-{50,100,500,600}",
2856
+ "dark:border-{zinc,slate,gray}-{600,700,800,900} dark:border-transparent",
2857
+ "dark:hover:text-{zinc,slate,gray}-{50,100,200,300,400}",
2858
+ "dark:hover:bg-{zinc,slate,gray}-{700,800,900}"
2859
+ ];
2860
+ var INLINE_DIRECTIVE_RE = /@source\s+inline\(\s*'([^']+)'\s*\)\s*;/g;
2861
+ var expand = (token) => {
2862
+ const m = token.match(/\{([^{}]+)\}/);
2863
+ if (!m) {
2864
+ return [token];
2865
+ }
2866
+ const head = token.slice(0, m.index);
2867
+ const tail = token.slice(m.index + m[0].length);
2868
+ const out = [];
2869
+ for (const opt of m[1].split(",")) {
2870
+ out.push(...expand(head + opt + tail));
2871
+ }
2872
+ return out;
2873
+ };
2874
+ var stripComments = (css) => css.replace(/\/\*[\s\S]*?\*\//g, "");
2875
+ var parseSafelist = (css) => {
2876
+ const stripped = stripComments(css);
2877
+ const classes = [];
2878
+ for (const match of stripped.matchAll(INLINE_DIRECTIVE_RE)) {
2879
+ for (const tok of match[1].split(/\s+/)) {
2880
+ if (tok) {
2881
+ classes.push(...expand(tok));
2882
+ }
2883
+ }
2884
+ }
2885
+ return classes;
2886
+ };
2887
+ var classifyFamily = (cls) => {
2888
+ if (cls.startsWith("dark:hover:text-")) return "Dark \u2014 hover text";
2889
+ if (cls.startsWith("dark:hover:bg-")) return "Dark \u2014 hover background";
2890
+ if (cls.startsWith("dark:text-")) return "Dark \u2014 text color";
2891
+ if (cls.startsWith("dark:bg-")) return "Dark \u2014 background color";
2892
+ if (cls.startsWith("dark:border-")) return "Dark \u2014 border color";
2893
+ if (cls.startsWith("hover:opacity-")) return "Hover \u2014 opacity";
2894
+ if (cls.startsWith("hover:text-")) return "Hover \u2014 text color";
2895
+ if (cls.startsWith("hover:bg-")) return "Hover \u2014 background color";
2896
+ if (cls.startsWith("focus:")) return "Focus";
2897
+ if (cls.startsWith("disabled:")) return "Disabled";
2898
+ if (/^(m|mx|my|mt|mb|ml|mr)-/.test(cls)) {
2899
+ return "Margin";
2900
+ }
2901
+ if (/^(p|px|py|pt|pb|pl|pr)-/.test(cls)) {
2902
+ return "Padding";
2903
+ }
2904
+ if (/^gap(-x|-y)?-/.test(cls)) {
2905
+ return "Gap";
2906
+ }
2907
+ if (/^(min-w|max-w|min-h|max-h)-/.test(cls)) {
2908
+ return "Sizing constraints";
2909
+ }
2910
+ if (/^(w|h)-/.test(cls)) {
2911
+ return "Width / Height";
2912
+ }
2913
+ if (/^text-(xs|sm|base|lg|xl|2xl)$/.test(cls)) {
2914
+ return "Text size";
2915
+ }
2916
+ if (/^text-([a-z]+)-(\d+)$/.test(cls)) {
2917
+ return "Text color";
2918
+ }
2919
+ if (/^font-/.test(cls)) {
2920
+ return "Font weight";
2921
+ }
2922
+ if (["uppercase", "lowercase", "capitalize", "normal-case"].includes(cls)) {
2923
+ return "Text transform";
2924
+ }
2925
+ if (/^tracking-/.test(cls)) {
2926
+ return "Letter spacing (tracking)";
2927
+ }
2928
+ if (/^leading-/.test(cls)) {
2929
+ return "Line height (leading)";
2930
+ }
2931
+ if (/^bg-([a-z]+)-(\d+)$/.test(cls)) {
2932
+ return "Background color";
2933
+ }
2934
+ if (/^border-([a-z]+)-(\d+)$/.test(cls) || cls === "border-transparent") {
2935
+ return "Border color";
2936
+ }
2937
+ if (/^(block|inline|inline-block|flex|inline-flex|grid|hidden)$/.test(cls)) {
2938
+ return "Display";
2939
+ }
2940
+ if (/^flex-/.test(cls)) {
2941
+ return "Flex";
2942
+ }
2943
+ if (/^justify-/.test(cls)) {
2944
+ return "Justify content";
2945
+ }
2946
+ if (/^items-/.test(cls)) {
2947
+ return "Align items";
2948
+ }
2949
+ if (/^self-/.test(cls)) {
2950
+ return "Align self";
2951
+ }
2952
+ if (/^aspect-/.test(cls)) {
2953
+ return "Aspect ratio";
2954
+ }
2955
+ if (/^opacity-/.test(cls)) {
2956
+ return "Opacity";
2957
+ }
2958
+ if (/^rounded(-|$)/.test(cls)) {
2959
+ return "Border radius";
2960
+ }
2961
+ if (/^border(-|$)/.test(cls) && !/^border-([a-z]+)-(\d+)$/.test(cls) && cls !== "border-transparent") {
2962
+ return "Border width";
2963
+ }
2964
+ if (/^cursor-/.test(cls)) {
2965
+ return "Cursor";
2966
+ }
2967
+ if (/^overflow(-|$)/.test(cls)) {
2968
+ return "Overflow";
2969
+ }
2970
+ if (/^transition(-|$)/.test(cls) || /^duration-/.test(cls) || /^ease-/.test(cls)) {
2971
+ return "Transition";
2972
+ }
2973
+ if (/^shadow(-|$)/.test(cls)) {
2974
+ return "Box shadow";
2975
+ }
2976
+ if (/^(relative|absolute|fixed|sticky|static)$/.test(cls)) {
2977
+ return "Position";
2978
+ }
2979
+ if (/^z-/.test(cls)) {
2980
+ return "Z-index";
2981
+ }
2982
+ return "Other";
2983
+ };
2984
+ var SECTIONS = [
2985
+ {
2986
+ title: "Utility families",
2987
+ families: [
2988
+ "Margin",
2989
+ "Padding",
2990
+ "Gap",
2991
+ "Width / Height",
2992
+ "Sizing constraints",
2993
+ "Aspect ratio",
2994
+ "Display",
2995
+ "Flex",
2996
+ "Justify content",
2997
+ "Align items",
2998
+ "Align self",
2999
+ "Position",
3000
+ "Z-index",
3001
+ "Overflow",
3002
+ "Text size",
3003
+ "Font weight",
3004
+ "Text transform",
3005
+ "Letter spacing (tracking)",
3006
+ "Line height (leading)",
3007
+ "Text color",
3008
+ "Background color",
3009
+ "Border color",
3010
+ "Border width",
3011
+ "Border radius",
3012
+ "Opacity",
3013
+ "Box shadow",
3014
+ "Cursor",
3015
+ "Transition",
3016
+ "Other"
3017
+ ]
3018
+ },
3019
+ {
3020
+ title: "State variants",
3021
+ families: [
3022
+ "Focus",
3023
+ "Disabled",
3024
+ "Hover \u2014 opacity",
3025
+ "Hover \u2014 text color",
3026
+ "Hover \u2014 background color"
3027
+ ]
3028
+ },
3029
+ {
3030
+ title: "Theme variants",
3031
+ families: [
3032
+ "Dark \u2014 text color",
3033
+ "Dark \u2014 background color",
3034
+ "Dark \u2014 border color",
3035
+ "Dark \u2014 hover text",
3036
+ "Dark \u2014 hover background"
3037
+ ]
3038
+ }
3039
+ ];
3040
+ var groupBySection = (classes) => {
3041
+ const buckets = /* @__PURE__ */ new Map();
3042
+ for (const cls of classes) {
3043
+ const family = classifyFamily(cls);
3044
+ const arr = buckets.get(family) ?? [];
3045
+ arr.push(cls);
3046
+ buckets.set(family, arr);
3047
+ }
3048
+ return SECTIONS.map((section) => ({
3049
+ title: section.title,
3050
+ families: section.families.filter((label) => buckets.has(label)).map((label) => ({
3051
+ label,
3052
+ classes: buckets.get(label).sort((a2, b2) => a2.localeCompare(b2, void 0, { numeric: true }))
3053
+ }))
3054
+ })).filter((s) => s.families.length > 0);
3055
+ };
3056
+ var renderFamilySection = (group) => {
3057
+ const lines = [];
3058
+ lines.push(`### ${group.label} (${group.classes.length})`);
3059
+ lines.push("");
3060
+ const formatted = group.classes.map((c) => `\`${c}\``).join(", ");
3061
+ lines.push(formatted);
3062
+ lines.push("");
3063
+ return lines.join("\n");
3064
+ };
3065
+ var generateTailwindUtilities = () => {
3066
+ const fm = frontmatter({
3067
+ root: false,
3068
+ targets: ["*"],
3069
+ description: "Tailwind utility classes pre-emitted in the embedded widget bundle that extensions can use without shipping their own CSS",
3070
+ globs: ["packages/extension/src/**/*.tsx"]
3071
+ });
3072
+ const css = SAFELIST_DIRECTIVES.map((d2) => `@source inline('${d2}');`).join("\n");
3073
+ const classes = parseSafelist(css);
3074
+ const sectionsData = groupBySection(classes);
3075
+ const familyCount = sectionsData.reduce((sum, s) => sum + s.families.length, 0);
3076
+ const sectionsMd = sectionsData.map((section) => {
3077
+ const familyBlocks = section.families.map(renderFamilySection).join("\n");
3078
+ return `## ${section.title}
3079
+
3080
+ ${familyBlocks}`;
3081
+ }).join("\n");
3082
+ return `${fm}
3083
+
3084
+ # Supported Tailwind Utilities
3085
+
3086
+ Stackable extensions render inside the embedded widget's Shadow Root via the platform and ship **zero custom CSS by design** \u2014 they rely entirely on the platforms's bundled stylesheet for their visual styling. To keep that bundle small while still covering the common needs of extension UI, the platform pre-emits a curated subset of Tailwind v4 utility classes via a safelist. The classes listed below are guaranteed to be available in any extension that uses them.
3087
+
3088
+ Total: **${classes.length} utility classes** across **${familyCount} families**.
3089
+
3090
+ ## Using utilities
3091
+
3092
+ Apply them via the \`className\` prop on \`ui.*\` components:
3093
+
3094
+ \`\`\`tsx
3095
+ <ui.Card className="mt-4 p-6">
3096
+ <ui.Stack className="gap-3">
3097
+ <ui.Text className="text-lg font-semibold uppercase tracking-wide">Title</ui.Text>
3098
+ <ui.Text className="text-zinc-600">Body copy</ui.Text>
3099
+ </ui.Stack>
3100
+ </ui.Card>
3101
+ \`\`\`
3102
+
3103
+ Always prefer:
3104
+ - Component variants (\`<ui.Button variant="primary">\`) over color overrides
3105
+ - Layout components (\`<ui.Stack>\`, \`<ui.Inline>\`) over manual flexbox class chains
3106
+ - The semantic color tokens (\`text-foreground\`, \`bg-background\`) which adapt to the host theme
3107
+
3108
+ ## Safelist limits
3109
+
3110
+ The safelist is intentionally a fixed list, **NOT the full Tailwind catalog** to provide UI consistency and reduce bundle size. Anything beyond what is listed below will be missing from the platform stylesheet at runtime \u2014 the class will appear in your DOM but the corresponding CSS rule will not exist, and the style will silently no-op. In particular:
3111
+
3112
+ - **Arbitrary values are NOT supported** in general (e.g. \`w-[123px]\`, \`bg-[#1a1a1a]\`, \`text-[15px]\`). The single exception is \`aspect-[9/16]\` (portrait video). For any other arbitrary value, use the named utility closest to your target.
3113
+ - **Off-list color shades** (e.g. \`bg-cyan-500\`, \`text-rose-500\`) are not pre-emitted. The supported color set below is curated for visual consistency with the host theme.
3114
+ - **Off-list ladder values** (e.g. \`mt-32\`, \`text-4xl\`, \`w-128\`) are not pre-emitted. The ladders below cover sizes appropriate for the embedded widget's narrow viewport.
3115
+
3116
+ If you need something that's not on this list, file an issue with your use case \u2014 the safelist is curated, not frozen.
3117
+
3118
+ ${sectionsMd}
3119
+ `;
3120
+ };
3121
+
2804
3122
  // ../../sdk/extension/ai-docs/src/generators/store-and-navigation.ts
2805
3123
  var generateStoreAndNavigation = () => {
2806
3124
  const fm = frontmatter({
@@ -3740,6 +4058,12 @@ var SKILLS = [
3740
4058
  type: "knowledge",
3741
4059
  content: () => generateStylingAndTheming()
3742
4060
  },
4061
+ {
4062
+ id: "tailwind-utilities",
4063
+ description: "Tailwind utility classes pre-emitted in the embedded widget bundle (margin, padding, sizing, color, layout, typography, state variants). Use when picking className values for extension UI \u2014 anything outside this list will silently no-op at runtime.",
4064
+ type: "knowledge",
4065
+ content: () => generateTailwindUtilities()
4066
+ },
3743
4067
  {
3744
4068
  id: "instance-settings",
3745
4069
  description: "Instance Settings: declaring settingsSchema in your extension manifest, the install-time admin form, and reading regular values via useSettings() vs secure values via {{settings.x}} placeholders in data.fetch. Use when adding per-Instance configuration to an extension.",
@@ -3840,7 +4164,7 @@ var SKILLS = [
3840
4164
  id: "deploy",
3841
4165
  description: "Build the production bundle, host it on Netlify / Vercel / Cloudflare Pages / S3+CloudFront, and register your Bundle URL with Stackable via the admin dashboard or CLI. Use when shipping an extension after validation passes.",
3842
4166
  type: "action",
3843
- // TODO: T3 #24 — remove `scopes: ['docs']` once `cli deploy` is implemented so Studio's
4167
+ // TODO: cli deploy — remove `scopes: ['docs']` once `cli deploy` is implemented so Studio's
3844
4168
  // AI assistant in the future should be able invoke the deploy flow (not just describe it).
3845
4169
  scopes: ["docs"],
3846
4170
  content: () => generateDeployExtensionCommand()
package/dist/server.js CHANGED
@@ -2794,6 +2794,324 @@ narrow widths:
2794
2794
  `;
2795
2795
  };
2796
2796
 
2797
+ // ../../sdk/extension/ai-docs/src/generators/tailwind-utilities.ts
2798
+ var SAFELIST_DIRECTIVES = [
2799
+ "m-{0,0.5,1,1.5,2,2.5,3,3.5,4,5,6,7,8,9,10,11,12,14,16}",
2800
+ "mx-{0,1,2,3,4,5,6,8,10,12,16,auto} my-{0,1,2,3,4,5,6,8,10,12,16}",
2801
+ "mt-{0,1,2,3,4,5,6,8,10,12,16} mb-{0,1,2,3,4,5,6,8,10,12,16}",
2802
+ "ml-{0,1,2,3,4,5,6,8,10,12,16,auto} mr-{0,1,2,3,4,5,6,8,10,12,16,auto}",
2803
+ "p-{0,0.5,1,1.5,2,2.5,3,3.5,4,5,6,7,8,9,10,11,12,14,16}",
2804
+ "px-{0,1,2,3,4,5,6,8,10,12,16} py-{0,1,2,3,4,5,6,8,10,12,16}",
2805
+ "pt-{0,1,2,3,4,5,6,8,10,12,16} pb-{0,1,2,3,4,5,6,8,10,12,16}",
2806
+ "pl-{0,1,2,3,4,5,6,8,10,12,16} pr-{0,1,2,3,4,5,6,8,10,12,16}",
2807
+ "gap-{0,1,2,3,4,5,6,8,10,12} gap-x-{0,1,2,3,4,5,6,8} gap-y-{0,1,2,3,4,5,6,8}",
2808
+ "text-{xs,sm,base,lg,xl,2xl}",
2809
+ "font-{light,normal,medium,semibold,bold,extrabold}",
2810
+ "uppercase lowercase capitalize normal-case",
2811
+ "tracking-{tight,normal,wide,wider,widest}",
2812
+ "leading-{none,tight,snug,normal,relaxed,loose}",
2813
+ "opacity-{0,5,10,20,25,30,40,50,60,70,75,80,90,95,100}",
2814
+ "block inline inline-block flex inline-flex grid hidden",
2815
+ "flex-{1,auto,initial,none,row,col,wrap,nowrap,row-reverse,col-reverse}",
2816
+ "justify-{start,end,center,between,around,evenly}",
2817
+ "items-{start,end,center,baseline,stretch}",
2818
+ "self-{auto,start,end,center,stretch,baseline}",
2819
+ "w-{0,full,auto,screen,1/2,1/3,2/3,1/4,3/4,1,2,3,4,5,6,8,10,12,16,20,24,32,48}",
2820
+ "h-{0,full,auto,screen,1/2,1/3,2/3,1/4,3/4,1,2,3,4,5,6,8,10,12,16,20,24,32,48}",
2821
+ "min-w-{0,full,fit,min,max} max-w-{none,xs,sm,md,lg,xl,2xl,3xl,4xl,5xl,full,fit}",
2822
+ "aspect-{auto,square,video} aspect-[9/16]",
2823
+ "rounded-{none,sm,md,lg,xl,2xl,3xl,full}",
2824
+ "border border-{0,2,4,8} border-{x,y,t,b,l,r}",
2825
+ "cursor-{pointer,default,not-allowed,wait,text,move,help}",
2826
+ "overflow-{auto,hidden,visible,scroll}",
2827
+ "overflow-x-{auto,hidden,scroll} overflow-y-{auto,hidden,scroll}",
2828
+ "transition transition-{none,all,colors,opacity,transform}",
2829
+ "duration-{75,100,150,200,300,500,700,1000} ease-{linear,in,out,in-out}",
2830
+ "shadow-{sm,md,lg,xl,2xl,none,inner}",
2831
+ "relative absolute fixed sticky static",
2832
+ "z-{0,10,20,30,40,50,auto}",
2833
+ "text-{zinc,slate,gray,neutral}-{50,100,200,300,400,500,600,700,800,900}",
2834
+ "text-{red,green,blue,amber,emerald,sky,indigo}-{500,600,700}",
2835
+ "bg-{zinc,slate,gray,neutral}-{50,100,200,300,400,500,600,700,800,900}",
2836
+ "bg-{red,green,blue,amber,emerald}-{50,100,500,600}",
2837
+ "border-{zinc,slate,gray}-{100,200,300,400} border-transparent",
2838
+ "hover:opacity-{50,75,80,90,100}",
2839
+ "hover:text-{zinc,slate,gray,neutral}-{500,600,700,800,900}",
2840
+ "hover:text-{red,green,blue,amber,emerald,sky,indigo}-{500,600,700}",
2841
+ "hover:bg-{zinc,slate,gray,neutral}-{50,100,200}",
2842
+ "hover:bg-{red,green,blue,amber,emerald}-{50,100,500,600}",
2843
+ "focus:outline-none focus:ring-2 focus:ring-offset-2",
2844
+ "disabled:opacity-50 disabled:cursor-not-allowed",
2845
+ "dark:text-{zinc,slate,gray,neutral}-{50,100,200,300,400,500,600,700,800,900}",
2846
+ "dark:text-{red,green,blue,amber,emerald,sky,indigo}-{500,600,700}",
2847
+ "dark:bg-{zinc,slate,gray,neutral}-{50,100,200,300,400,500,600,700,800,900}",
2848
+ "dark:bg-{red,green,blue,amber,emerald}-{50,100,500,600}",
2849
+ "dark:border-{zinc,slate,gray}-{600,700,800,900} dark:border-transparent",
2850
+ "dark:hover:text-{zinc,slate,gray}-{50,100,200,300,400}",
2851
+ "dark:hover:bg-{zinc,slate,gray}-{700,800,900}"
2852
+ ];
2853
+ var INLINE_DIRECTIVE_RE = /@source\s+inline\(\s*'([^']+)'\s*\)\s*;/g;
2854
+ var expand = (token) => {
2855
+ const m = token.match(/\{([^{}]+)\}/);
2856
+ if (!m) {
2857
+ return [token];
2858
+ }
2859
+ const head = token.slice(0, m.index);
2860
+ const tail = token.slice(m.index + m[0].length);
2861
+ const out = [];
2862
+ for (const opt of m[1].split(",")) {
2863
+ out.push(...expand(head + opt + tail));
2864
+ }
2865
+ return out;
2866
+ };
2867
+ var stripComments = (css) => css.replace(/\/\*[\s\S]*?\*\//g, "");
2868
+ var parseSafelist = (css) => {
2869
+ const stripped = stripComments(css);
2870
+ const classes = [];
2871
+ for (const match of stripped.matchAll(INLINE_DIRECTIVE_RE)) {
2872
+ for (const tok of match[1].split(/\s+/)) {
2873
+ if (tok) {
2874
+ classes.push(...expand(tok));
2875
+ }
2876
+ }
2877
+ }
2878
+ return classes;
2879
+ };
2880
+ var classifyFamily = (cls) => {
2881
+ if (cls.startsWith("dark:hover:text-")) return "Dark \u2014 hover text";
2882
+ if (cls.startsWith("dark:hover:bg-")) return "Dark \u2014 hover background";
2883
+ if (cls.startsWith("dark:text-")) return "Dark \u2014 text color";
2884
+ if (cls.startsWith("dark:bg-")) return "Dark \u2014 background color";
2885
+ if (cls.startsWith("dark:border-")) return "Dark \u2014 border color";
2886
+ if (cls.startsWith("hover:opacity-")) return "Hover \u2014 opacity";
2887
+ if (cls.startsWith("hover:text-")) return "Hover \u2014 text color";
2888
+ if (cls.startsWith("hover:bg-")) return "Hover \u2014 background color";
2889
+ if (cls.startsWith("focus:")) return "Focus";
2890
+ if (cls.startsWith("disabled:")) return "Disabled";
2891
+ if (/^(m|mx|my|mt|mb|ml|mr)-/.test(cls)) {
2892
+ return "Margin";
2893
+ }
2894
+ if (/^(p|px|py|pt|pb|pl|pr)-/.test(cls)) {
2895
+ return "Padding";
2896
+ }
2897
+ if (/^gap(-x|-y)?-/.test(cls)) {
2898
+ return "Gap";
2899
+ }
2900
+ if (/^(min-w|max-w|min-h|max-h)-/.test(cls)) {
2901
+ return "Sizing constraints";
2902
+ }
2903
+ if (/^(w|h)-/.test(cls)) {
2904
+ return "Width / Height";
2905
+ }
2906
+ if (/^text-(xs|sm|base|lg|xl|2xl)$/.test(cls)) {
2907
+ return "Text size";
2908
+ }
2909
+ if (/^text-([a-z]+)-(\d+)$/.test(cls)) {
2910
+ return "Text color";
2911
+ }
2912
+ if (/^font-/.test(cls)) {
2913
+ return "Font weight";
2914
+ }
2915
+ if (["uppercase", "lowercase", "capitalize", "normal-case"].includes(cls)) {
2916
+ return "Text transform";
2917
+ }
2918
+ if (/^tracking-/.test(cls)) {
2919
+ return "Letter spacing (tracking)";
2920
+ }
2921
+ if (/^leading-/.test(cls)) {
2922
+ return "Line height (leading)";
2923
+ }
2924
+ if (/^bg-([a-z]+)-(\d+)$/.test(cls)) {
2925
+ return "Background color";
2926
+ }
2927
+ if (/^border-([a-z]+)-(\d+)$/.test(cls) || cls === "border-transparent") {
2928
+ return "Border color";
2929
+ }
2930
+ if (/^(block|inline|inline-block|flex|inline-flex|grid|hidden)$/.test(cls)) {
2931
+ return "Display";
2932
+ }
2933
+ if (/^flex-/.test(cls)) {
2934
+ return "Flex";
2935
+ }
2936
+ if (/^justify-/.test(cls)) {
2937
+ return "Justify content";
2938
+ }
2939
+ if (/^items-/.test(cls)) {
2940
+ return "Align items";
2941
+ }
2942
+ if (/^self-/.test(cls)) {
2943
+ return "Align self";
2944
+ }
2945
+ if (/^aspect-/.test(cls)) {
2946
+ return "Aspect ratio";
2947
+ }
2948
+ if (/^opacity-/.test(cls)) {
2949
+ return "Opacity";
2950
+ }
2951
+ if (/^rounded(-|$)/.test(cls)) {
2952
+ return "Border radius";
2953
+ }
2954
+ if (/^border(-|$)/.test(cls) && !/^border-([a-z]+)-(\d+)$/.test(cls) && cls !== "border-transparent") {
2955
+ return "Border width";
2956
+ }
2957
+ if (/^cursor-/.test(cls)) {
2958
+ return "Cursor";
2959
+ }
2960
+ if (/^overflow(-|$)/.test(cls)) {
2961
+ return "Overflow";
2962
+ }
2963
+ if (/^transition(-|$)/.test(cls) || /^duration-/.test(cls) || /^ease-/.test(cls)) {
2964
+ return "Transition";
2965
+ }
2966
+ if (/^shadow(-|$)/.test(cls)) {
2967
+ return "Box shadow";
2968
+ }
2969
+ if (/^(relative|absolute|fixed|sticky|static)$/.test(cls)) {
2970
+ return "Position";
2971
+ }
2972
+ if (/^z-/.test(cls)) {
2973
+ return "Z-index";
2974
+ }
2975
+ return "Other";
2976
+ };
2977
+ var SECTIONS = [
2978
+ {
2979
+ title: "Utility families",
2980
+ families: [
2981
+ "Margin",
2982
+ "Padding",
2983
+ "Gap",
2984
+ "Width / Height",
2985
+ "Sizing constraints",
2986
+ "Aspect ratio",
2987
+ "Display",
2988
+ "Flex",
2989
+ "Justify content",
2990
+ "Align items",
2991
+ "Align self",
2992
+ "Position",
2993
+ "Z-index",
2994
+ "Overflow",
2995
+ "Text size",
2996
+ "Font weight",
2997
+ "Text transform",
2998
+ "Letter spacing (tracking)",
2999
+ "Line height (leading)",
3000
+ "Text color",
3001
+ "Background color",
3002
+ "Border color",
3003
+ "Border width",
3004
+ "Border radius",
3005
+ "Opacity",
3006
+ "Box shadow",
3007
+ "Cursor",
3008
+ "Transition",
3009
+ "Other"
3010
+ ]
3011
+ },
3012
+ {
3013
+ title: "State variants",
3014
+ families: [
3015
+ "Focus",
3016
+ "Disabled",
3017
+ "Hover \u2014 opacity",
3018
+ "Hover \u2014 text color",
3019
+ "Hover \u2014 background color"
3020
+ ]
3021
+ },
3022
+ {
3023
+ title: "Theme variants",
3024
+ families: [
3025
+ "Dark \u2014 text color",
3026
+ "Dark \u2014 background color",
3027
+ "Dark \u2014 border color",
3028
+ "Dark \u2014 hover text",
3029
+ "Dark \u2014 hover background"
3030
+ ]
3031
+ }
3032
+ ];
3033
+ var groupBySection = (classes) => {
3034
+ const buckets = /* @__PURE__ */ new Map();
3035
+ for (const cls of classes) {
3036
+ const family = classifyFamily(cls);
3037
+ const arr = buckets.get(family) ?? [];
3038
+ arr.push(cls);
3039
+ buckets.set(family, arr);
3040
+ }
3041
+ return SECTIONS.map((section) => ({
3042
+ title: section.title,
3043
+ families: section.families.filter((label) => buckets.has(label)).map((label) => ({
3044
+ label,
3045
+ classes: buckets.get(label).sort((a2, b2) => a2.localeCompare(b2, void 0, { numeric: true }))
3046
+ }))
3047
+ })).filter((s) => s.families.length > 0);
3048
+ };
3049
+ var renderFamilySection = (group) => {
3050
+ const lines = [];
3051
+ lines.push(`### ${group.label} (${group.classes.length})`);
3052
+ lines.push("");
3053
+ const formatted = group.classes.map((c) => `\`${c}\``).join(", ");
3054
+ lines.push(formatted);
3055
+ lines.push("");
3056
+ return lines.join("\n");
3057
+ };
3058
+ var generateTailwindUtilities = () => {
3059
+ const fm = frontmatter({
3060
+ root: false,
3061
+ targets: ["*"],
3062
+ description: "Tailwind utility classes pre-emitted in the embedded widget bundle that extensions can use without shipping their own CSS",
3063
+ globs: ["packages/extension/src/**/*.tsx"]
3064
+ });
3065
+ const css = SAFELIST_DIRECTIVES.map((d2) => `@source inline('${d2}');`).join("\n");
3066
+ const classes = parseSafelist(css);
3067
+ const sectionsData = groupBySection(classes);
3068
+ const familyCount = sectionsData.reduce((sum, s) => sum + s.families.length, 0);
3069
+ const sectionsMd = sectionsData.map((section) => {
3070
+ const familyBlocks = section.families.map(renderFamilySection).join("\n");
3071
+ return `## ${section.title}
3072
+
3073
+ ${familyBlocks}`;
3074
+ }).join("\n");
3075
+ return `${fm}
3076
+
3077
+ # Supported Tailwind Utilities
3078
+
3079
+ Stackable extensions render inside the embedded widget's Shadow Root via the platform and ship **zero custom CSS by design** \u2014 they rely entirely on the platforms's bundled stylesheet for their visual styling. To keep that bundle small while still covering the common needs of extension UI, the platform pre-emits a curated subset of Tailwind v4 utility classes via a safelist. The classes listed below are guaranteed to be available in any extension that uses them.
3080
+
3081
+ Total: **${classes.length} utility classes** across **${familyCount} families**.
3082
+
3083
+ ## Using utilities
3084
+
3085
+ Apply them via the \`className\` prop on \`ui.*\` components:
3086
+
3087
+ \`\`\`tsx
3088
+ <ui.Card className="mt-4 p-6">
3089
+ <ui.Stack className="gap-3">
3090
+ <ui.Text className="text-lg font-semibold uppercase tracking-wide">Title</ui.Text>
3091
+ <ui.Text className="text-zinc-600">Body copy</ui.Text>
3092
+ </ui.Stack>
3093
+ </ui.Card>
3094
+ \`\`\`
3095
+
3096
+ Always prefer:
3097
+ - Component variants (\`<ui.Button variant="primary">\`) over color overrides
3098
+ - Layout components (\`<ui.Stack>\`, \`<ui.Inline>\`) over manual flexbox class chains
3099
+ - The semantic color tokens (\`text-foreground\`, \`bg-background\`) which adapt to the host theme
3100
+
3101
+ ## Safelist limits
3102
+
3103
+ The safelist is intentionally a fixed list, **NOT the full Tailwind catalog** to provide UI consistency and reduce bundle size. Anything beyond what is listed below will be missing from the platform stylesheet at runtime \u2014 the class will appear in your DOM but the corresponding CSS rule will not exist, and the style will silently no-op. In particular:
3104
+
3105
+ - **Arbitrary values are NOT supported** in general (e.g. \`w-[123px]\`, \`bg-[#1a1a1a]\`, \`text-[15px]\`). The single exception is \`aspect-[9/16]\` (portrait video). For any other arbitrary value, use the named utility closest to your target.
3106
+ - **Off-list color shades** (e.g. \`bg-cyan-500\`, \`text-rose-500\`) are not pre-emitted. The supported color set below is curated for visual consistency with the host theme.
3107
+ - **Off-list ladder values** (e.g. \`mt-32\`, \`text-4xl\`, \`w-128\`) are not pre-emitted. The ladders below cover sizes appropriate for the embedded widget's narrow viewport.
3108
+
3109
+ If you need something that's not on this list, file an issue with your use case \u2014 the safelist is curated, not frozen.
3110
+
3111
+ ${sectionsMd}
3112
+ `;
3113
+ };
3114
+
2797
3115
  // ../../sdk/extension/ai-docs/src/generators/store-and-navigation.ts
2798
3116
  var generateStoreAndNavigation = () => {
2799
3117
  const fm = frontmatter({
@@ -3733,6 +4051,12 @@ var SKILLS = [
3733
4051
  type: "knowledge",
3734
4052
  content: () => generateStylingAndTheming()
3735
4053
  },
4054
+ {
4055
+ id: "tailwind-utilities",
4056
+ description: "Tailwind utility classes pre-emitted in the embedded widget bundle (margin, padding, sizing, color, layout, typography, state variants). Use when picking className values for extension UI \u2014 anything outside this list will silently no-op at runtime.",
4057
+ type: "knowledge",
4058
+ content: () => generateTailwindUtilities()
4059
+ },
3736
4060
  {
3737
4061
  id: "instance-settings",
3738
4062
  description: "Instance Settings: declaring settingsSchema in your extension manifest, the install-time admin form, and reading regular values via useSettings() vs secure values via {{settings.x}} placeholders in data.fetch. Use when adding per-Instance configuration to an extension.",
@@ -3833,7 +4157,7 @@ var SKILLS = [
3833
4157
  id: "deploy",
3834
4158
  description: "Build the production bundle, host it on Netlify / Vercel / Cloudflare Pages / S3+CloudFront, and register your Bundle URL with Stackable via the admin dashboard or CLI. Use when shipping an extension after validation passes.",
3835
4159
  type: "action",
3836
- // TODO: T3 #24 — remove `scopes: ['docs']` once `cli deploy` is implemented so Studio's
4160
+ // TODO: cli deploy — remove `scopes: ['docs']` once `cli deploy` is implemented so Studio's
3837
4161
  // AI assistant in the future should be able invoke the deploy flow (not just describe it).
3838
4162
  scopes: ["docs"],
3839
4163
  content: () => generateDeployExtensionCommand()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stackable-labs/mcp-app-extension",
3
- "version": "1.3.0",
3
+ "version": "1.5.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "mcp-app-extension": "./dist/index.js"