diff --git a/skills/CLAUDE.md b/CLAUDE.md similarity index 100% rename from skills/CLAUDE.md rename to CLAUDE.md diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..e0f1622 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,10 @@ +{ + "name": "@glitch-university/glitch-components-workspace", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@glitch-university/glitch-components-workspace" + } + } +} diff --git a/scripts/glitch-components.mjs b/scripts/glitch-components.mjs index db65bce..a2e5f84 100644 --- a/scripts/glitch-components.mjs +++ b/scripts/glitch-components.mjs @@ -27,14 +27,14 @@ function parseArgs(argv) { const options = { command, components: [], - hostDir: '', + hostDirs: [], stagingDir: path.join(cwd, '.glitch-release') } for (let index = 0; index < rest.length; index += 1) { const arg = rest[index] if (arg === '--host') { - options.hostDir = path.resolve(cwd, rest[index + 1] ?? '') + options.hostDirs.push(path.resolve(cwd, rest[index + 1] ?? '')) index += 1 continue } @@ -176,6 +176,14 @@ function ensureDirectory(directory) { fs.mkdirSync(directory, { recursive: true }) } +function getDefaultHostDirs() { + return [ + path.resolve(cwd, '..', 'gnommoweb', 'public', 'glitch'), + path.resolve(cwd, '..', 'gnommoplayer', 'public', 'glitch'), + path.resolve(cwd, '..', 'gnommoeditor', 'public', 'glitch') + ] +} + function copyDirectoryContents(sourceDir, destinationDir) { ensureDirectory(destinationDir) for (const entry of fs.readdirSync(sourceDir)) { @@ -186,7 +194,7 @@ function copyDirectoryContents(sourceDir, destinationDir) { } } -function releaseComponent(component, hostDir, stagingDir) { +function releaseComponent(component, hostDirs, stagingDir) { const distDir = path.join(component.componentDir, 'dist') if (!fs.existsSync(distDir)) { fail(`Missing dist directory for ${component.componentDirName}. Run build first.`) @@ -205,9 +213,11 @@ function releaseComponent(component, hostDir, stagingDir) { tags: ['legacy'] } - const releaseDir = path.join(hostDir, manifest.folderName) - copyDirectoryContents(distDir, releaseDir) - fs.writeFileSync(path.join(releaseDir, 'glitch.manifest.json'), `${JSON.stringify(manifest, null, 2)}\n`) + for (const hostDir of hostDirs) { + const releaseDir = path.join(hostDir, manifest.folderName) + copyDirectoryContents(distDir, releaseDir) + fs.writeFileSync(path.join(releaseDir, 'glitch.manifest.json'), `${JSON.stringify(manifest, null, 2)}\n`) + } const stagingComponentDir = path.join(stagingDir, manifest.folderName) copyDirectoryContents(distDir, stagingComponentDir) @@ -268,17 +278,17 @@ switch (options.command) { } case 'release': { - const hostDir = - options.hostDir || - path.resolve(cwd, '..', 'gnommoweb', 'src', 'components', 'glitch') + const hostDirs = options.hostDirs.length > 0 ? options.hostDirs : getDefaultHostDirs() - ensureDirectory(hostDir) + for (const hostDir of hostDirs) { + ensureDirectory(hostDir) + } ensureDirectory(options.stagingDir) for (const component of components) { console.log(`=== Releasing ${component.componentDirName} ===`) runBuild(component) - releaseComponent(component, hostDir, options.stagingDir) + releaseComponent(component, hostDirs, options.stagingDir) } break } diff --git a/setup-remotes.sh b/setup-remotes.sh index 8082730..4d1f734 100755 --- a/setup-remotes.sh +++ b/setup-remotes.sh @@ -1,12 +1,4 @@ #!/bin/bash -# Sets up git remotes for all glitch-component folders and pushes to ramanujan. -# -# Usage: -# ./setup-remotes.sh # set remotes only -# ./setup-remotes.sh --push # set remotes and push all -# -# Reads GIT_USER / GIT_PASSWORD from ../.env if present, otherwise prompts. - set -e REMOTE_HOST="ramanujan.glitch.university" @@ -14,34 +6,17 @@ COMPONENTS_DIR="$(cd "$(dirname "$0")" && pwd)" PUSH=false [[ "$1" == "--push" ]] && PUSH=true -# ── Load credentials ────────────────────────────────────────────────────────── +REMOTE_BASE="ssh://git@${REMOTE_HOST}/git/repos" -ENV_FILE="$COMPONENTS_DIR/../gnommoweb/.env.prod" -if [ -f "$ENV_FILE" ]; then - GIT_USER=$(grep -E '^GIT_USER=' "$ENV_FILE" | cut -d= -f2 | tr -d '"'"'" | head -1) - GIT_PASSWORD=$(grep -E '^GIT_PASSWORD=' "$ENV_FILE" | cut -d= -f2 | tr -d '"'"'" | head -1) -fi - -if [ -z "$GIT_USER" ] || [ -z "$GIT_PASSWORD" ]; then - read -rp "Git username: " GIT_USER - read -rsp "Git password: " GIT_PASSWORD - echo -fi - -REMOTE_BASE="https://${GIT_USER}:${GIT_PASSWORD}@${REMOTE_HOST}" - -# ── Process each component folder ───────────────────────────────────────────── for dir in "$COMPONENTS_DIR"/*/; do name=$(basename "$dir") - # Skip the skills folder and any non-component entries [[ "$name" == "skills" ]] && continue echo "── $name" cd "$dir" - # Init if not already a git repo if [ ! -d ".git" ]; then git init -q git add -A @@ -49,21 +24,27 @@ for dir in "$COMPONENTS_DIR"/*/; do echo " initialised" fi - # Add or update remote + REMOTE_URL="https://${REMOTE_HOST}/${name}.git" + if git remote get-url origin &>/dev/null; then - git remote set-url origin "${REMOTE_BASE}/${name}.git" + git remote set-url origin "$REMOTE_URL" echo " remote updated" else - git remote add origin "${REMOTE_BASE}/${name}.git" + git remote add origin "$REMOTE_URL" echo " remote added" fi - # Push if $PUSH; then + if ssh git@"$REMOTE_HOST" create-repo "${name}.git" 2>/dev/null; then + echo " remote repo ensured" + else + echo " remote repo may already exist" + fi + if git push -u origin main -q 2>/dev/null || git push -u origin master:main -q 2>/dev/null; then echo " pushed" else - echo " push failed (repo may not exist on server yet)" + echo " push failed" fi fi diff --git a/skills/component-contract.md b/skills/component-contract.md new file mode 100644 index 0000000..6e38491 --- /dev/null +++ b/skills/component-contract.md @@ -0,0 +1,92 @@ +# GlitchComponent Contract (Canonical Shape) + +Use the local `lightlane` project as the canonical source when available: + +- `../lightlane/src/types.ts` +- `../lightlane/src/index.tsx` + +This reference captures the current shape observed in `lightlane` so the skill can scaffold quickly. + +## Required Exports + +`src/index.tsx` should export: + +- `default` component +- `metadata` object (`GlitchComponentMetadata`) +- relevant types re-exported from `src/types.ts` + +## Core Types (Current Lightlane-Compatible Shape) + +```ts +export interface GlitchComponentConfig { + id: string; + name: string; + version: string; + params: Record; +} + +export interface GlitchTheme { + primary: string; + accent: string; + bg: string; + bgSecondary: string; + text: string; + textMuted: string; + border: string; +} + +export interface GlitchComponentResult { + success: boolean; + score?: number; + data?: unknown; + error?: string; +} + +export interface GlitchComponentProps { + config: GlitchComponentConfig; + onComplete: (result: GlitchComponentResult) => void; + onProgress?: (percent: number) => void; + theme?: GlitchTheme; + className?: string; +} + +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; + }; +} + +export interface GlitchComponentMetadata { + name: string; + displayName: string; + version: string; + paramSchema: ParamSchema; + defaultParams: Record; +} +``` + +## Metadata Expectations + +- `name`: kebab-case slug used by host registration +- `displayName`: human-friendly title +- `version`: semantic version string +- `paramSchema`: controls/settings schema for host and dev harness +- `defaultParams`: values that produce a working component without additional configuration + +## Behavioral Expectations + +- Call `onComplete(...)` when the experience completes or fails definitively. +- Call `onProgress(percent)` for multi-step flows when progress is meaningful. +- Use `config.params` as the single source of runtime config. +- Respect `theme` if provided, but keep fallbacks for standalone mode. + +## Validation Tip + +Before finalizing a scaffold, compare generated `src/types.ts` and `src/index.tsx` against the current `../lightlane/src/types.ts` and `../lightlane/src/index.tsx` to catch drift. diff --git a/skills/glitchcomponent.md b/skills/glitchcomponent.md new file mode 100644 index 0000000..6c3da02 --- /dev/null +++ b/skills/glitchcomponent.md @@ -0,0 +1,14 @@ +# Create GlitchComponent + +The canonical Glitch University component workflow now lives at the workspace root: + +- `/Users/jenstandstad/Projects/glitch-components/GLITCH_COMPONENT_STANDARD.md` +- `/Users/jenstandstad/Projects/glitch-components/templates/react-vite-glitch-component/` +- `/Users/jenstandstad/Projects/glitch-components/scripts/new-glitch-component.mjs` + +Use the root scaffold instead of copying this folder or reusing older component boilerplate. + +For special features suchas 3D, Sound bridge etc consider the referenes in + +/Users/jenstandstad/Projects/glitch-components/skills/references + diff --git a/skills/sound-bridge.md b/skills/sound-bridge.md new file mode 100644 index 0000000..0a43443 --- /dev/null +++ b/skills/sound-bridge.md @@ -0,0 +1,34 @@ +# Sound Bridge Notes + +Glitch University uses a host-managed sound system (Howler-based) with string sound keys, for example: + +- `playSound('ui.button_click')` +- `playSound('ui.button_hover')` + +## Recommendation + +Treat sound as an optional host integration: + +- Use a small wrapper function in the component (`safePlaySound`) that checks whether a host sound function exists. +- No-op when unavailable (standalone dev should still run). +- Keep sound identifiers as strings/constants so they match host definitions. + +## Example Pattern + +```ts +type PlaySound = (id: string) => void; + +export function safePlaySound(playSound: PlaySound | undefined, id: string) { + try { + playSound?.(id); + } catch { + // Ignore host sound errors in standalone/local mode + } +} +``` + +## Unknowns to Confirm Later + +- How the host exposes `playSound` to glitch-components (prop, context, global import, event bus) +- Which sound keys are safe/standardized across experiences +- Whether completion/reward sounds are triggered by host or component