Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@orkait-ai/hyperstack",
"version": "1.0.3",
"version": "1.0.5",
"description": "Disciplined MCP server + skill system. 11 plugins, 79 tools, 21 skills with adversarial enforcement. Designer/DESIGN.md pipeline, shadcn/ui, React Flow, Motion, Lenis, React 19, Echo, Go, Rust, design tokens, UI/UX.",
"bin": {
"hyperstack": "bin/hyperstack.mjs"
Expand Down
6 changes: 4 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const server = new McpServer({
version: "1.0.0",
});

loadPlugins(server, [
export const allPlugins = [
reactflowPlugin,
motionPlugin,
lenisPlugin,
Expand All @@ -32,7 +32,9 @@ loadPlugins(server, [
uiUxPlugin,
designerPlugin,
shadcnPlugin,
]);
];

loadPlugins(server, allPlugins);

async function main() {
const transport = new StdioServerTransport();
Expand Down
5 changes: 5 additions & 0 deletions src/internal/context-compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,11 @@ function extractSimpleBullets(section: string): string[] {
.map((line) => line);
}

export function generateHyperstackBootstrap(source: string): string {
const { content } = compileUsingHyperstackBootstrap(source);
return content;
}

export function compileUsingHyperstackBootstrap(source: string): { content: string; stats: BootstrapCompilationStats } {
const body = stripFrontmatter(source);
const criticalBlock = extractTaggedBlock(body, "EXTREMELY-IMPORTANT");
Expand Down
82 changes: 44 additions & 38 deletions src/plugins/lenis/tools/generate-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,29 @@ export function register(server: McpServer): void {
),
},
async ({ description }) => {
const desc = description.toLowerCase();
const { code, notes } = generateSetupCode(description);

const isNextJs = desc.includes("next") || desc.includes("app router") || desc.includes("app dir");
const isGSAP = desc.includes("gsap") || desc.includes("scroll trigger") || desc.includes("scrolltrigger");
const isFramer = desc.includes("framer") || desc.includes("motion");
const isHorizontal = desc.includes("horizontal");
const isAccessibility = desc.includes("a11y") || desc.includes("accessibility") || desc.includes("reduced motion");
const isContainer = desc.includes("container") || desc.includes("panel") || desc.includes("section");
const text = `# Lenis Setup: ${description}\n\n\`\`\`tsx\n${code}\n\`\`\`\n\n## Notes\n\n${notes}`;
return { content: [{ type: "text", text }] };
},
);
}

export function generateSetupCode(description: string): { code: string; notes: string } {
const desc = description.toLowerCase();

let code = "";
let notes = "";
const isNextJs = desc.includes("next") || desc.includes("app router") || desc.includes("app dir");
const isGSAP = desc.includes("gsap") || desc.includes("scroll trigger") || desc.includes("scrolltrigger");
const isFramer = desc.includes("framer") || desc.includes("motion");
const isHorizontal = desc.includes("horizontal");
const isAccessibility = desc.includes("a11y") || desc.includes("accessibility") || desc.includes("reduced motion");
const isContainer = desc.includes("container") || desc.includes("panel") || desc.includes("section");

if (isGSAP && isNextJs) {
code = `// components/lenis-gsap-provider.tsx
let code = "";
let notes = "";

if (isGSAP && isNextJs) {
code = `// components/lenis-gsap-provider.tsx
"use client";

import { useEffect } from "react";
Expand Down Expand Up @@ -72,9 +81,9 @@ export default function RootLayout({ children }: { children: React.ReactNode })
</html>
);
}`;
notes = "- autoRaf: false is critical - prevents Lenis and GSAP from each running their own RAF loop\n- gsap.ticker.lagSmoothing(0) prevents stutter after tab switches\n- lenis.raf() expects ms - multiply GSAP seconds by 1000";
} else if (isGSAP) {
code = `// lenis-gsap-setup.tsx
notes = "- autoRaf: false is critical - prevents Lenis and GSAP from each running their own RAF loop\n- gsap.ticker.lagSmoothing(0) prevents stutter after tab switches\n- lenis.raf() expects ms - multiply GSAP seconds by 1000";
} else if (isGSAP) {
code = `// lenis-gsap-setup.tsx
"use client";

import { useEffect } from "react";
Expand Down Expand Up @@ -105,9 +114,9 @@ export function App() {
</ReactLenis>
);
}`;
notes = "- Set autoRaf: false to prevent double-frame rendering with GSAP\n- Multiply GSAP ticker time (seconds) by 1000 for lenis.raf()";
} else if (isFramer) {
code = `// components/lenis-framer-provider.tsx
notes = "- Set autoRaf: false to prevent double-frame rendering with GSAP\n- Multiply GSAP ticker time (seconds) by 1000 for lenis.raf()";
} else if (isFramer) {
code = `// components/lenis-framer-provider.tsx
"use client";

import { useEffect } from "react";
Expand Down Expand Up @@ -147,9 +156,9 @@ export default function RootLayout({ children }: { children: React.ReactNode })
</html>
);
}` : ""}`;
notes = "- Import frame from 'motion' (not 'framer-motion') - this is the v11+ low-level scheduler\n- frame.update(fn, true) loops on every frame\n- autoRaf: false prevents duplicate ticking";
} else if (isHorizontal) {
code = `// components/horizontal-scroll.tsx
notes = "- Import frame from 'motion' (not 'framer-motion') - this is the v11+ low-level scheduler\n- frame.update(fn, true) loops on every frame\n- autoRaf: false prevents duplicate ticking";
} else if (isHorizontal) {
code = `// components/horizontal-scroll.tsx
"use client";

import { ReactLenis } from "lenis/react";
Expand All @@ -171,9 +180,9 @@ export function HorizontalScroll({ children }: { children: React.ReactNode }) {
</ReactLenis>
);
}`;
notes = "- orientation: 'horizontal' tells Lenis which axis to scroll\n- gestureOrientation: 'both' captures both wheel and touch gestures\n- The inner div should use flex-nowrap to allow horizontal overflow";
} else if (isAccessibility) {
code = `// components/smooth-scroll-provider.tsx
notes = "- orientation: 'horizontal' tells Lenis which axis to scroll\n- gestureOrientation: 'both' captures both wheel and touch gestures\n- The inner div should use flex-nowrap to allow horizontal overflow";
} else if (isAccessibility) {
code = `// components/smooth-scroll-provider.tsx
"use client";

import { useEffect, useState } from "react";
Expand Down Expand Up @@ -219,9 +228,9 @@ export default function RootLayout({ children }: { children: React.ReactNode })
</html>
);
}` : ""}`;
notes = "- Skipping ReactLenis falls back to native scroll - no extra setup needed\n- WCAG 2.1 SC 2.3.3 (AAA): smooth scrolling must respect prefers-reduced-motion";
} else if (isNextJs) {
code = `// components/smooth-scroll-provider.tsx
notes = "- Skipping ReactLenis falls back to native scroll - no extra setup needed\n- WCAG 2.1 SC 2.3.3 (AAA): smooth scrolling must respect prefers-reduced-motion";
} else if (isNextJs) {
code = `// components/smooth-scroll-provider.tsx
"use client";

import { ReactLenis } from "lenis/react";
Expand Down Expand Up @@ -249,9 +258,9 @@ export default function RootLayout({ children }: { children: React.ReactNode })
</html>
);
}`;
notes = "- Keep app/layout.tsx as a Server Component - extract 'use client' into SmoothScrollProvider\n- This preserves RSC boundaries and server rendering for child pages\n- The CSS import in the client component is required";
} else if (isContainer) {
code = `// components/scroll-panel.tsx
notes = "- Keep app/layout.tsx as a Server Component - extract 'use client' into SmoothScrollProvider\n- This preserves RSC boundaries and server rendering for child pages\n- The CSS import in the client component is required";
} else if (isContainer) {
code = `// components/scroll-panel.tsx
"use client";

import { useRef } from "react";
Expand All @@ -267,9 +276,9 @@ export function ScrollPanel({ children }: { children: React.ReactNode }) {
</ReactLenis>
);
}`;
notes = "- ReactLenis without root creates a scoped container scroll\n- The wrapper needs a fixed height for the scroll to work\n- Use wrapper/content refs via LenisOptions for full control";
} else {
code = `// App.tsx - basic React SPA setup
notes = "- ReactLenis without root creates a scoped container scroll\n- The wrapper needs a fixed height for the scroll to work\n- Use wrapper/content refs via LenisOptions for full control";
} else {
code = `// App.tsx - basic React SPA setup
import { ReactLenis } from "lenis/react";
import "lenis/dist/lenis.css";

Expand All @@ -282,11 +291,8 @@ export function App() {
</ReactLenis>
);
}`;
notes = "- root={true} attaches Lenis to the window scroll\n- lerp: 0.1 is the recommended starting value - tune between 0.05 (smoother) and 0.15 (snappier)\n- The CSS import is required - it sets up the scroll container styles";
}
notes = "- root={true} attaches Lenis to the window scroll\n- lerp: 0.1 is the recommended starting value - tune between 0.05 (smoother) and 0.15 (snappier)\n- The CSS import is required - it sets up the scroll container styles";
}

const text = `# Lenis Setup: ${description}\n\n\`\`\`tsx\n${code}\n\`\`\`\n\n## Notes\n\n${notes}`;
return { content: [{ type: "text", text }] };
},
);
return { code, notes };
}
Loading
Loading