Files
glitch_assumption_toggle/skills/glitchcomponent.md
T
2026-03-17 22:06:41 +01:00

8.8 KiB

Create GlitchComponent

Create a new GlitchComponent project that conforms to the standardized plugin architecture for Glitch University.

Arguments

  • $ARGUMENTS - Component name (e.g., "matrix-rain", "particle-system")

Instructions

When creating a new GlitchComponent:

  1. If no directory specified, create in a sibling directory: ../glitch-components/$ARGUMENTS/

  2. Create the full project structure:

    $ARGUMENTS/
    ├── src/
    │   ├── index.tsx           # Main export with metadata
    │   ├── Component.tsx       # Component implementation
    │   ├── styles.module.css   # Scoped CSS styles
    │   ├── types.ts            # TypeScript interfaces
    │   └── dev.tsx             # Leva dev harness
    ├── vite.config.ts
    ├── package.json
    ├── tsconfig.json
    └── index.html
    
  3. After creation, explain how to:

    • Run npm install && npm run dev for local development
    • Build with npm run build
    • Deploy output to host app

Architecture Principles

  1. Build as Library: Use Vite Library Mode to output a self-contained bundle
  2. CSS Isolation: Use CSS Modules to prevent style bleeding
  3. Inversion of Control: Accept props for all configurable parameters
  4. Local Dev Support: Use Leva controls during development, props in production

Required TypeScript Interfaces

// src/types.ts

export interface GlitchComponentConfig {
  id: string;
  name: string;
  version: string;
  params: Record<string, unknown>;
}

export interface GlitchComponentProps {
  config: GlitchComponentConfig;
  onComplete: (result: GlitchComponentResult) => void;
  onProgress?: (percent: number) => void;
  theme?: GlitchTheme;
  className?: string;
}

export interface GlitchComponentResult {
  success: boolean;
  score?: number;
  data?: unknown;
  error?: string;
}

export interface GlitchTheme {
  primary: string;      // #6366f1
  accent: string;       // #22d3ee
  bg: string;           // #0a0a0f
  bgSecondary: string;  // #12121a
  text: string;         // #e8e8ec
  textMuted: string;    // #9999a8
  border: string;       // #2a2a3a
}

export interface GlitchComponentMetadata {
  name: string;
  displayName: string;
  version: string;
  paramSchema: ParamSchema;
  defaultParams: Record<string, unknown>;
}

export interface ParamSchema {
  [key: string]: {
    type: 'number' | 'string' | 'boolean' | 'color' | 'select' | 'range';
    label?: string;
    description?: string;
    default: unknown;
    options?: { value: string | number; label: string }[];
    min?: number;
    max?: number;
    step?: number;
  };
}

Main Export Structure

// src/index.tsx
import Component from './Component';
import type { GlitchComponentMetadata } from './types';

export default Component;

export const metadata: GlitchComponentMetadata = {
  name: 'my-component',
  displayName: 'My Component',
  version: '1.0.0',
  paramSchema: {
    speed: { type: 'range', label: 'Speed', default: 1.0, min: 0.1, max: 5.0, step: 0.1 },
    primaryColor: { type: 'color', label: 'Color', default: '#6366f1' }
  },
  defaultParams: { speed: 1.0, primaryColor: '#6366f1' }
};

export type { GlitchComponentProps, GlitchComponentConfig, GlitchComponentResult } from './types';

Component Template

// src/Component.tsx
import { useCallback, useEffect, useState } from 'react';
import styles from './styles.module.css';
import type { GlitchComponentProps } from './types';

interface ComponentParams {
  speed: number;
  primaryColor: string;
}

export default function MyComponent({ config, onComplete, onProgress, theme, className }: GlitchComponentProps) {
  const params = config.params as ComponentParams;
  const { speed = 1.0, primaryColor } = params;

  const handleComplete = useCallback((success: boolean, score?: number) => {
    onComplete({ success, score, data: { completedAt: new Date().toISOString() } });
  }, [onComplete]);

  const cssVars = {
    '--gc-primary': primaryColor || theme?.primary || '#6366f1',
    '--gc-accent': theme?.accent || '#22d3ee',
    '--gc-bg': theme?.bg || '#0a0a0f',
    '--gc-text': theme?.text || '#e8e8ec',
  } as React.CSSProperties;

  return (
    <div className={`${styles.container} ${className || ''}`} style={cssVars}>
      <h2 className={styles.title}>My GlitchComponent</h2>
      <button className={styles.button} onClick={() => handleComplete(true, 100)}>
        Complete
      </button>
    </div>
  );
}

CSS Module Template

/* src/styles.module.css */
.container {
  background: var(--gc-bg, #0a0a0f);
  color: var(--gc-text, #e8e8ec);
  padding: 2rem;
  border-radius: 12px;
  box-sizing: border-box;
  font-family: system-ui, -apple-system, sans-serif;
  line-height: 1.5;
}

.container *, .container *::before, .container *::after {
  box-sizing: inherit;
}

.title {
  color: var(--gc-primary, #6366f1);
  margin: 0 0 1rem;
  font-size: 1.5rem;
  font-weight: 600;
}

.button {
  background: var(--gc-primary, #6366f1);
  color: white;
  border: none;
  padding: 0.75rem 1.5rem;
  border-radius: 8px;
  font-size: 1rem;
  cursor: pointer;
  transition: opacity 0.2s ease;
}

.button:hover { opacity: 0.9; }
.button:disabled { opacity: 0.5; cursor: not-allowed; }

Vite Configuration

// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js';
import { resolve } from 'path';

export default defineConfig(({ mode }) => ({
  plugins: [react(), cssInjectedByJsPlugin()],
  build: {
    lib: {
      entry: resolve(__dirname, 'src/index.tsx'),
      name: 'MyGlitchComponent',
      fileName: 'my-glitch-component',
      formats: ['es']
    },
    rollupOptions: {
      external: ['react', 'react-dom', 'react/jsx-runtime'],
      output: {
        globals: { react: 'React', 'react-dom': 'ReactDOM', 'react/jsx-runtime': 'jsxRuntime' },
        assetFileNames: 'assets/[name][extname]'
      }
    },
    sourcemap: true,
    minify: mode === 'production'
  },
  server: { port: 3001, open: true }
}));

Package.json

{
  "name": "my-glitch-component",
  "version": "1.0.0",
  "type": "module",
  "main": "./dist/my-glitch-component.js",
  "module": "./dist/my-glitch-component.js",
  "scripts": {
    "dev": "vite",
    "build": "tsc && vite build",
    "preview": "vite preview"
  },
  "peerDependencies": {
    "react": "^18.0.0",
    "react-dom": "^18.0.0"
  },
  "devDependencies": {
    "@types/react": "^18.2.0",
    "@types/react-dom": "^18.2.0",
    "@vitejs/plugin-react": "^4.2.0",
    "leva": "^0.9.35",
    "typescript": "^5.3.0",
    "vite": "^5.0.0",
    "vite-plugin-css-injected-by-js": "^3.3.0"
  }
}

Dev Harness with Leva

// src/dev.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import { useControls, Leva } from 'leva';
import Component from './Component';
import { metadata } from './index';

function DevHarness() {
  const levaSchema = Object.entries(metadata.paramSchema).reduce((acc, [key, schema]) => {
    if (schema.type === 'range' || schema.type === 'number') {
      acc[key] = { value: schema.default, min: schema.min, max: schema.max, step: schema.step };
    } else if (schema.type === 'select') {
      acc[key] = { value: schema.default, options: schema.options?.reduce((o, opt) => ({ ...o, [opt.label]: opt.value }), {}) };
    } else {
      acc[key] = schema.default;
    }
    return acc;
  }, {} as Record<string, unknown>);

  const params = useControls(levaSchema);

  return (
    <div style={{ minHeight: '100vh', background: '#0a0a0f', padding: '2rem' }}>
      <Leva collapsed={false} />
      <Component
        config={{ id: 'dev', name: metadata.name, version: metadata.version, params }}
        theme={{ primary: '#6366f1', accent: '#22d3ee', bg: '#0a0a0f', bgSecondary: '#12121a', text: '#e8e8ec', textMuted: '#9999a8', border: '#2a2a3a' }}
        onComplete={(r) => { console.log('Complete:', r); alert(`Done! Score: ${r.score}`); }}
      />
    </div>
  );
}

ReactDOM.createRoot(document.getElementById('root')!).render(<React.StrictMode><DevHarness /></React.StrictMode>);

Three.js / R3F Components

For 3D components, add to externals in vite.config.ts:

external: ['react', 'react-dom', 'react/jsx-runtime', 'three', '@react-three/fiber', '@react-three/drei']

Deployment

  1. nvm use 20 # to select node version 20
  2. npm run build
  3. Copy dist/*.js to host app's src/components/glitch/
  4. Add database entry:
    INSERT INTO glitch_components (name, display_name, version, file_path, param_schema, default_params)
    VALUES ('my-component', 'My Component', '1.0.0', 'my-component.js', '{"speed":{"type":"range","default":1.0}}', '{"speed":1.0}');
    
  5. Link to tech via glitch_component_id