TL;DR TL;DR Paradigm: Causal Graph Programming (CGP) — you wire functions, not components; the framework auto-detects what each function needs and “snaps” it into a single causal graph (UI ⇄ logic ⇄ effects ⇄ style ⇄ backend). Three engines: ScrollMesh → component/templating via context auto-wiring (unlimited functions, zero manual wiring). ScrollScript → universal signal store (client + server) with actions, watchers, derived signals, time travel. ScrollWeave → logic-reactive styling (state/logic drives CSS & animations at runtime). Why now: less boilerplate, fewer classes/hooks/providers, more causality visibility. Showcase: real-time chat app in < 500 lines (HTML + JS + a tiny server). Use cases: dashboards, real-time apps, design systems that react to logic, compact full-stack prototypes. One-liner: ScrollForge – Causal Graph Programming: unify state, logic, style, and backend into one reactive graph. Paradigm: Causal Graph Programming (CGP) — you wire functions, not components; the framework auto-detects what each function needs and “snaps” it into a single causal graph (UI ⇄ logic ⇄ effects ⇄ style ⇄ backend). Three engines: ScrollMesh → component/templating via context auto-wiring (unlimited functions, zero manual wiring). ScrollScript → universal signal store (client + server) with actions, watchers, derived signals, time travel. ScrollWeave → logic-reactive styling (state/logic drives CSS & animations at runtime). Why now: less boilerplate, fewer classes/hooks/providers, more causality visibility. Showcase: real-time chat app in < 500 lines (HTML + JS + a tiny server). Use cases: dashboards, real-time apps, design systems that react to logic, compact full-stack prototypes. One-liner: ScrollForge – Causal Graph Programming: unify state, logic, style, and backend into one reactive graph. What is “Causal Graph Programming”? What is “Causal Graph Programming”? The short version: Instead of pushing data through props and bouncing events back through callbacks (typical UI frameworks), CGP lets you register as many functions as you want. Each function declares its intent implicitly by its signature (parameters), and the engine auto-provides matching contexts: ({ ...stateProps }) => ui → UI renderer (gets state) (events, state) => { ... } → event logic (state, weave) => { ... } → styling/animation driven by state (state, effects) => { ... } → reactive effects () => ({ ... }) → initial state provider (…and several more contexts, all optional.) ({ ...stateProps }) => ui → UI renderer (gets state) (events, state) => { ... } → event logic (state, weave) => { ... } → styling/animation driven by state (state, effects) => { ... } → reactive effects () => ({ ... }) → initial state provider (…and several more contexts, all optional.) (…and several more contexts, all optional.) Order doesn’t matter. Wiring doesn’t exist. The framework assembles a causal graph out of your functions and keeps it live. Why this is different? Why this is different? No props drilling, no provider pyramids, no manual event buses. UI, logic, effects, and styles coordinate through shared, reactive signals (ScrollScript) and auto-wired contexts (ScrollMesh). Style is not static: ScrollWeave treats CSS as a live system, not a file. No props drilling, no provider pyramids, no manual event buses. UI, logic, effects, and styles coordinate through shared, reactive signals (ScrollScript) and auto-wired contexts (ScrollMesh). Style is not static: ScrollWeave treats CSS as a live system, not a file. The three engines (in one project) 1) ScrollMesh — recursive component assembly (auto-wiring): ScrollMesh Write components by passing functions. The engine reads signatures and provides what you need. import { HTMLScrollMesh } from 'scrollforge/dist/mesh-full.browser.js'; const Counter = HTMLScrollMesh( // UI (gets state via destructuring) ({ count }) => `<button class="btn">Count: ${count}</button>`, // Logic (gets events + state) (events, state) => { events.on('click', '.btn', () => state.count++); }, // Initial state () => ({ count: 0 }) ); Counter.mount('#app'); import { HTMLScrollMesh } from 'scrollforge/dist/mesh-full.browser.js'; const Counter = HTMLScrollMesh( // UI (gets state via destructuring) ({ count }) => `<button class="btn">Count: ${count}</button>`, // Logic (gets events + state) (events, state) => { events.on('click', '.btn', () => state.count++); }, // Initial state () => ({ count: 0 }) ); Counter.mount('#app'); Enter fullscreen mode Exit fullscreen mode 2) ScrollScript — universal data flow (signals, actions, derived): ScrollScript Client and server share the same API. Signals update; watchers react; derived signals memoize computed values. // Create global signals app.Script.signal('messages', []); app.Script.signal('username', ''); app.Script.watch('messages', (msgs) => console.log('Count:', msgs.length)); // Create global signals app.Script.signal('messages', []); app.Script.signal('username', ''); app.Script.watch('messages', (msgs) => console.log('Count:', msgs.length)); Enter fullscreen mode Exit fullscreen mode 3)** ScrollWeave **— logic-reactive styling Let state and logic shape style at runtime. (state, weave) => { weave.when('.status', state.online, { background: 'rgba(76, 175, 80, .2)' }, { background: 'rgba(244, 67, 54, .2)' } ); // Micro-interaction weave.spring('.btn', { transform: 'scale(1.0)' }, { stiffness: 200, damping: 20 }); }; Let state and logic shape style at runtime. (state, weave) => { weave.when('.status', state.online, { background: 'rgba(76, 175, 80, .2)' }, { background: 'rgba(244, 67, 54, .2)' } ); // Micro-interaction weave.spring('.btn', { transform: 'scale(1.0)' }, { stiffness: 200, damping: 20 }); }; Enter fullscreen mode Exit fullscreen mode **The <500-line demo: real-time chat Using this paradigm, we made a fully working chatapp in under 500 lines of code (present in the github repo at the end). ScrollMesh Context Auto-Wiring - Deep Dive ScrollMesh Context Auto-Wiring - Deep Dive The Revolutionary Breakthrough The Revolutionary Breakthrough ScrollMesh Context is the most powerful feature in ScrollForge. It allows you to pass UNLIMITED functions that automatically detect what they need and connect themselves. How It Works How It Works import { HTMLScrollMesh } from 'scrollforge/mesh'; const component = HTMLScrollMesh( function1, function2, function3, // ... add as many as you want! ); import { HTMLScrollMesh } from 'scrollforge/mesh'; const component = HTMLScrollMesh( function1, function2, function3, // ... add as many as you want! ); Enter fullscreen mode Exit fullscreen mode The framework: The framework: Reads each function's signature (parameters) Detects what contexts each function needs Automatically provides those contexts Wires everything together NO manual configuration required! ✨ Reads each function's signature (parameters) Detects what contexts each function needs Automatically provides those contexts Wires everything together NO manual configuration required! ✨ The 8 Available Contexts: The 8 Available Contexts: Every function can request any of these contexts by adding them as parameters: 1. state - Reactive State ProxyGet it by: Adding state as parameterWhat you can do: 1. state - Reactive State Proxy (state) => { // READ const count = state.count; const name = state.user.name; // WRITE (triggers re-render!) state.count++; state.user.name = 'Jane'; // Deep updates work state.user.profile.settings.theme = 'dark'; // Arrays state.items.push(newItem); state.items = [...state.items, newItem]; } (state) => { // READ const count = state.count; const name = state.user.name; // WRITE (triggers re-render!) state.count++; state.user.name = 'Jane'; // Deep updates work state.user.profile.settings.theme = 'dark'; // Arrays state.items.push(newItem); state.items = [...state.items, newItem]; } Enter fullscreen mode Exit fullscreen mode 2. events - Event SystemGet it by: Adding events as parameterWhat you can do: 2. events - Event System (events, state) => { // Listen to DOM events events.on('click', '.button', (e) => { state.count++; }); events.on('input', '.search', (e) => { state.query = e.target.value; }); // Custom events events.emit('customEvent', { data: 'value' }); events.on('customEvent', (data) => { console.log('Event:', data); }); // Remove listener events.off('click', '.button', handler); } (events, state) => { // Listen to DOM events events.on('click', '.button', (e) => { state.count++; }); events.on('input', '.search', (e) => { state.query = e.target.value; }); // Custom events events.emit('customEvent', { data: 'value' }); events.on('customEvent', (data) => { console.log('Event:', data); }); // Remove listener events.off('click', '.button', handler); } Enter fullscreen mode Exit fullscreen mode 3. effects - Side EffectsGet it by: Adding effects as parameterWhat you can do: 3. effects - Side Effects (state, effects) => { // Watch state changes effects.when('count', (count) => { console.log('Count changed:', count); document.title = `Count: ${count}`; }); // Watch with old value effects.when('status', (newStatus, oldStatus) => { console.log(`${oldStatus} → ${newStatus}`); }); // Run once on mount effects.once('mounted', () => { console.log('Component mounted!'); }); // Async effects effects.when('userId', async (userId) => { const user = await fetchUser(userId); state.user = user; }); } (state, effects) => { // Watch state changes effects.when('count', (count) => { console.log('Count changed:', count); document.title = `Count: ${count}`; }); // Watch with old value effects.when('status', (newStatus, oldStatus) => { console.log(`${oldStatus} → ${newStatus}`); }); // Run once on mount effects.once('mounted', () => { console.log('Component mounted!'); }); // Async effects effects.when('userId', async (userId) => { const user = await fetchUser(userId); state.user = user; }); } Enter fullscreen mode Exit fullscreen mode **4. weave - Styling (ScrollWeave)**Get it by: Adding weave as parameterWhat you can do: (state, weave) => { // Apply styles weave.apply('.element', { background: 'blue', padding: '20px' }); // Conditional weave.when('.button', state.isActive, { background: 'green' }, { background: 'gray' } ); // Animations weave.fadeIn('.modal', 300); weave.spring('.card', { transform: 'scale(1)' }); } (state, weave) => { // Apply styles weave.apply('.element', { background: 'blue', padding: '20px' }); // Conditional weave.when('.button', state.isActive, { background: 'green' }, { background: 'gray' } ); // Animations weave.fadeIn('.modal', 300); weave.spring('.card', { transform: 'scale(1)' }); } Enter fullscreen mode Exit fullscreen mode 5. api - API CallsGet it by: Adding api as parameterWhat you can do: 5. api - API Calls async (state, api) => { // Fetch when signal changes api.when('userId', async (userId) => { const response = await api.fetch(`/api/users/${userId}`); const user = await response.json(); state.user = user; }); // Manual fetch const response = await api.fetch('/api/data'); const data = await response.json(); state.data = data; } async (state, api) => { // Fetch when signal changes api.when('userId', async (userId) => { const response = await api.fetch(`/api/users/${userId}`); const user = await response.json(); state.user = user; }); // Manual fetch const response = await api.fetch('/api/data'); const data = await response.json(); state.data = data; } Enter fullscreen mode Exit fullscreen mode 6. storage - PersistenceGet it by: Adding storage as parameterWhat you can do: 6. storage - Persistence (state, storage) => { // Save storage.persist('settings', state.settings); // Load (async) const saved = await storage.load('settings'); if (saved) state.settings = saved; // Remove storage.remove('settings'); } (state, storage) => { // Save storage.persist('settings', state.settings); // Load (async) const saved = await storage.load('settings'); if (saved) state.settings = saved; // Remove storage.remove('settings'); } Enter fullscreen mode Exit fullscreen mode WARNING: storage.load() is async - don't use in state function for initial load! WARNING: storage.load() is async - don't use in state function for initial load! () => ({ todos: JSON.parse(localStorage.getItem('todos') || '[]') // Sync! }), (state, effects) => { effects.when('todos', (todos) => { localStorage.setItem('todos', JSON.stringify(todos)); // Save }); } () => ({ todos: JSON.parse(localStorage.getItem('todos') || '[]') // Sync! }), (state, effects) => { effects.when('todos', (todos) => { localStorage.setItem('todos', JSON.stringify(todos)); // Save }); } Enter fullscreen mode Exit fullscreen mode 7. validate - ValidationGet it by: Adding validate as parameterWhat you can do: 7. validate - Validation (validate) => { validate.rule('email', (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value), 'Invalid email format' ); validate.rule('age', (value) => value >= 18, 'Must be 18 or older' ); } (validate) => { validate.rule('email', (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value), 'Invalid email format' ); validate.rule('age', (value) => value >= 18, 'Must be 18 or older' ); } Enter fullscreen mode Exit fullscreen mode 8. analytics - Analytics TrackingGet it by: Adding analytics as parameterWhat you can do: 8. analytics - Analytics Tracking (state, analytics) => { analytics.track('buttonClicked', () => state.clickCount); analytics.track('pageView', () => ({ page: state.currentPage, user: state.username })); } (state, analytics) => { analytics.track('buttonClicked', () => state.clickCount); analytics.track('pageView', () => ({ page: state.currentPage, user: state.username })); } Enter fullscreen mode Exit fullscreen mode Auto-Detection Rules Auto-Detection Rules The framework detects function type by its signature: **Signature Detected As Gets** ({ count }) => ... UI Function State (destructured) (state) => ... Logic/Effect State proxy (events) => ... Logic Events (events, state) => ... Logic Events + State (state, weave) => ... Styling State + Weave (state, effects) => ... Effects State + Effects (state, api) => ... API State + API () => ({ ... }) State Provider Nothing (returns state) (state, events, weave, effects, api, storage, validate, analytics) => ... All Contexts All 8! **Signature Detected As Gets** ({ count }) => ... UI Function State (destructured) (state) => ... Logic/Effect State proxy (events) => ... Logic Events (events, state) => ... Logic Events + State (state, weave) => ... Styling State + Weave (state, effects) => ... Effects State + Effects (state, api) => ... API State + API () => ({ ... }) State Provider Nothing (returns state) (state, events, weave, effects, api, storage, validate, analytics) => ... All Contexts All 8! Enter fullscreen mode Exit fullscreen mode State Function Special Rules State Function Special Rules Must have ZERO parameters and return object: // CORRECT () => ({ count: 0, user: { name: 'John' } }) // WRONG - has parameters (someParam) => ({ count: 0 }) // WRONG - doesn't return object () => { const count = 0; // Missing return! } // CORRECT () => ({ count: 0, user: { name: 'John' } }) // WRONG - has parameters (someParam) => ({ count: 0 }) // WRONG - doesn't return object () => { const count = 0; // Missing return! } Enter fullscreen mode Exit fullscreen mode Can include special properties: Can include special properties: () => ({ // Regular state count: 0, email: '', // Computed properties (auto-update!) computed: { doubleCount: (state) => state.count * 2 }, // Selectors (memoized) selectors: { evenCount: (state) => state.count % 2 === 0 }, // Middleware (intercept changes) middleware: { count: (oldValue, newValue) => { return newValue < 0 ? 0 : newValue; // Prevent negative } }, // Validation (runtime checks) validate: { email: (value) => /^[^\s@]+@[^\s@]+/.test(value) || 'Invalid email' }, // Options immutable: true, // Freeze state debug: { logChanges: true, breakOnChange: ['count'] } }) () => ({ // Regular state count: 0, email: '', // Computed properties (auto-update!) computed: { doubleCount: (state) => state.count * 2 }, // Selectors (memoized) selectors: { evenCount: (state) => state.count % 2 === 0 }, // Middleware (intercept changes) middleware: { count: (oldValue, newValue) => { return newValue < 0 ? 0 : newValue; // Prevent negative } }, // Validation (runtime checks) validate: { email: (value) => /^[^\s@]+@[^\s@]+/.test(value) || 'Invalid email' }, // Options immutable: true, // Freeze state debug: { logChanges: true, breakOnChange: ['count'] } }) Enter fullscreen mode Exit fullscreen mode HTMLScrollMesh - Quick Reference HTMLScrollMesh - Quick Reference HTMLScrollMesh = ScrollMesh Context + HTML template strings Basic Pattern: Basic Pattern: import { HTMLScrollMesh } from 'scrollforge/mesh'; const App = HTMLScrollMesh( // UI - Write HTML directly ({ count }) => `<button>${count}</button>`, // Events (events, state) => { events.on('click', 'button', () => state.count++); }, // State () => ({ count: 0 }) ); App.mount('#app'); import { HTMLScrollMesh } from 'scrollforge/mesh'; const App = HTMLScrollMesh( // UI - Write HTML directly ({ count }) => `<button>${count}</button>`, // Events (events, state) => { events.on('click', 'button', () => state.count++); }, // State () => ({ count: 0 }) ); App.mount('#app'); Enter fullscreen mode Exit fullscreen mode All 8 Contexts Work Identically All 8 Contexts Work Identically HTMLScrollMesh has the SAME context auto-wiring as ScrollMesh: (events, state) → Events + State (state, weave) → State + ScrollWeave styling (state, effects) → State + Side effects (state, api) → State + API calls (storage) → Storage context (validate) → Validation (analytics) → Analytics () => ({ ... }) → State provider (zero params!) Same rules. Same auto-detection. Just HTML instead of JS objects. (events, state) → Events + State (state, weave) → State + ScrollWeave styling (state, effects) → State + Side effects (state, api) → State + API calls (storage) → Storage context (validate) → Validation (analytics) → Analytics () => ({ ... }) → State provider (zero params!) Same rules. Same auto-detection. Just HTML instead of JS objects. HTML Features HTML Features ({ items, isLoggedIn, user }) => ` <!-- Conditionals --> ${isLoggedIn ? `<p>Hello ${user.name}</p>` : `<p>Login</p>`} <!-- Loops --> <ul> ${items.map(i => `<li>${i.name}</li>`).join('')} </ul> <!-- Expressions --> <p>Total: $${(price * quantity).toFixed(2)}</p> ` ({ items, isLoggedIn, user }) => ` <!-- Conditionals --> ${isLoggedIn ? `<p>Hello ${user.name}</p>` : `<p>Login</p>`} <!-- Loops --> <ul> ${items.map(i => `<li>${i.name}</li>`).join('')} </ul> <!-- Expressions --> <p>Total: $${(price * quantity).toFixed(2)}</p> ` Enter fullscreen mode Exit fullscreen mode Key Difference from ScrollMesh Context: Key Difference from ScrollMesh Context: 1. ScrollMesh HTMLScrollMesh 2. { tag: 'div', content: 'Hi' } <div>Hi</div> 3. JS Objects HTML Strings 1. ScrollMesh HTMLScrollMesh 2. { tag: 'div', content: 'Hi' } <div>Hi</div> 3. JS Objects HTML Strings Enter fullscreen mode Exit fullscreen mode ** Using ScrollWeave with HTMLScrollMesh** The Pattern: The Pattern: HTMLScrollMesh( // UI function ({ count }) => `<button class="my-btn">${count}</button>`, // Weave function - gets (state, weave) automatically! (state, weave) => { // Apply reactive styles based on state weave.when('.my-btn', state.count > 10, { background: 'green', fontSize: '2rem' }, // If count > 10 { background: 'blue', fontSize: '1rem' } // Else ); }, // Other functions... (events, state) => { events.on('click', '.my-btn', () => state.count++); }, () => ({ count: 0 }) ); HTMLScrollMesh( // UI function ({ count }) => `<button class="my-btn">${count}</button>`, // Weave function - gets (state, weave) automatically! (state, weave) => { // Apply reactive styles based on state weave.when('.my-btn', state.count > 10, { background: 'green', fontSize: '2rem' }, // If count > 10 { background: 'blue', fontSize: '1rem' } // Else ); }, // Other functions... (events, state) => { events.on('click', '.my-btn', () => state.count++); }, () => ({ count: 0 }) ); Enter fullscreen mode Exit fullscreen mode The framework automatically: The framework automatically: Detects (state, weave) signature Provides state proxy + ScrollWeave instance Styles update when state changes Zero manual wiring! ✨ Detects (state, weave) signature Provides state proxy + ScrollWeave instance Styles update when state changes Zero manual wiring! ✨ How It Works How It Works HTMLScrollMesh( // Function with (state, weave) parameters (state, weave) => { // Framework provides: // - state: reactive component state // - weave: ScrollWeave instance (app.Weave) // Use state to drive styles weave.apply('.element', { color: state.isActive ? 'green' : 'gray', fontSize: state.count > 5 ? '2rem' : '1rem' }); } ); // Framework auto-detects parameter names! HTMLScrollMesh( // Function with (state, weave) parameters (state, weave) => { // Framework provides: // - state: reactive component state // - weave: ScrollWeave instance (app.Weave) // Use state to drive styles weave.apply('.element', { color: state.isActive ? 'green' : 'gray', fontSize: state.count > 5 ? '2rem' : '1rem' }); } ); // Framework auto-detects parameter names! Enter fullscreen mode Exit fullscreen mode Complete Example Complete Example const Counter = HTMLScrollMesh( // UI ({ count, isHigh }) => ` <div class="counter"> <h1 class="display">${count}</h1> <button class="increment">+</button> <button class="decrement">-</button> ${isHigh ? `<p class="warning">⚠️ High count!</p>` : ''} </div> `, // Weave - Reactive styling! (state, weave) => { // Style changes based on state weave.when('.display', state.count > 10, { color: 'green', fontSize: '4rem', fontWeight: 'bold' }, { color: 'blue', fontSize: '2rem', fontWeight: 'normal' } ); // Button styling weave.when('.increment', state.count >= 20, { background: '#ccc', cursor: 'not-allowed' }, { background: '#4CAF50', cursor: 'pointer' } ); // Animate warning if (state.isHigh) { weave.spring('.warning', { opacity: 1, transform: 'scale(1)' }); } }, // Events (events, state) => { events.on('click', '.increment', () => { if (state.count < 20) state.count++; }); events.on('click', '.decrement', () => { if (state.count > 0) state.count--; }); }, // State () => ({ count: 0, computed: { isHigh: (state) => state.count > 15 } }) ); Counter.mount('#app'); const Counter = HTMLScrollMesh( // UI ({ count, isHigh }) => ` <div class="counter"> <h1 class="display">${count}</h1> <button class="increment">+</button> <button class="decrement">-</button> ${isHigh ? `<p class="warning">⚠️ High count!</p>` : ''} </div> `, // Weave - Reactive styling! (state, weave) => { // Style changes based on state weave.when('.display', state.count > 10, { color: 'green', fontSize: '4rem', fontWeight: 'bold' }, { color: 'blue', fontSize: '2rem', fontWeight: 'normal' } ); // Button styling weave.when('.increment', state.count >= 20, { background: '#ccc', cursor: 'not-allowed' }, { background: '#4CAF50', cursor: 'pointer' } ); // Animate warning if (state.isHigh) { weave.spring('.warning', { opacity: 1, transform: 'scale(1)' }); } }, // Events (events, state) => { events.on('click', '.increment', () => { if (state.count < 20) state.count++; }); events.on('click', '.decrement', () => { if (state.count > 0) state.count--; }); }, // State () => ({ count: 0, computed: { isHigh: (state) => state.count > 15 } }) ); Counter.mount('#app'); Enter fullscreen mode Exit fullscreen mode State changes → Weave updates styles → UI reflects changes! ✨ State changes → Weave updates styles → UI reflects changes! ✨ Key Points Key Points Get weave context: Add weave as parameter after state Signature: (state, weave) => { ... } Framework provides: Your app's app.Weave instance automatically Use state: Access component state to drive styles Reactive: Styles update automatically when state changes Get weave context: Add weave as parameter after state Signature: (state, weave) => { ... } Framework provides: Your app's app.Weave instance automatically Use state: Access component state to drive styles Reactive: Styles update automatically when state changes That's it! Just add weave parameter and you get reactive styling! Links: Links: https://www.npmjs.com/package/scrollforge www.infernusreal.com -> Portfolio website https://www.npmjs.com/package/scrollforge https://www.npmjs.com/package/scrollforge www.infernusreal.com -> Portfolio website www.infernusreal.com Thank you <3, also although I have tested all the features and examples I have shown and even used it to make many small samples, if you find any problems with it, kindly contact me through the number given in the portfolio website! I am only 16 so hopefully I am not embarrassing myself here, I also just entered Nasa space apps challenge 2025 this year, you can find the link to that page here: https://www.spaceappschallenge.org/2025/find-a-team/perseverance5/ https://www.spaceappschallenge.org/2025/find-a-team/perseverance5/