Skip to content
Open
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 README.md
Original file line number Diff line number Diff line change
Expand Up @@ -470,7 +470,7 @@ Optional fallback token behavior:
- Server-side effect moderation/generation endpoints treat submitted/generated code as inert text only (validated/stored/returned), and never execute that code in the server environment. This keeps server secrets (for example `process.env` values) out of reach of model-generated effects.
- Before any generated runtime code is compiled for preview/review, the client applies a safety scanner that rejects obviously dangerous primitives (`process.env`, `document.cookie`, browser storage APIs, network APIs like `fetch`/`XMLHttpRequest`/`sendBeacon`, and dynamic code loaders such as `eval`/`Function`/`import()`/`require()`).
- Generated effect payloads now also include optional parameter control metadata (`params`) and concise docs (`docs`). After approval, these generated controls are exposed in the debug panel + editor parameter pickers just like built-in effects.
- The effect generator modal now keeps the initial prompt editor focused and roomy, then reveals preview/code/param sections only after a successful generation. While generating, it shows approved community effects in a carousel with previous/next controls, and the eventual submission uses the current preview param values as defaults automatically (no separate “set defaults” step).
- The effect generator modal now keeps the initial prompt editor focused and roomy, then reveals preview/code/param sections only after a successful generation. While generating, it shows approved community effects in a carousel with previous/next controls plus live parameter controls so users can tweak those community effects while waiting, and the eventual submission uses the current preview param values as defaults automatically (no separate “set defaults” step).
- Effect generation and moderation preview now use `public/songloop.ogg` as a dedicated seamless background loop (low volume), while a synthetic preview timeline keeps generated effects animating continuously even as the audio loops.
- After approving or denying from `/effect-review.html`, the page now offers a **Next effect** button whenever additional pending effects are still in the moderation queue.
- The client also normalizes escaped code payloads (for example strings containing literal `\\n`) before compiling preview/runtime effects.
Expand Down
5 changes: 5 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@
<div id="effect-idea-busy-spinner" class="effect-idea-busy-spinner"></div>
<div id="effect-idea-busy-title" class="effect-idea-busy-title">Generating your effect…</div>
<div id="effect-idea-busy-copy" class="effect-idea-busy-copy">Enjoy approved community effects while Codex cooks your idea.</div>
<div id="effect-idea-busy-controls" class="effect-idea-busy-controls hidden">
<div class="effect-idea-busy-controls-label">Try these params while you wait</div>
<div id="effect-idea-busy-controls-grid" class="debug-grid"></div>
<div id="effect-idea-busy-controls-empty" class="debug-empty">This community effect has no tweakable params.</div>
</div>
<button id="effect-idea-retry" type="button" class="hidden">Retry generation</button>
</div>
</div>
Expand Down
90 changes: 88 additions & 2 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,9 @@ const effectIdeaBusyModal = document.querySelector<HTMLDivElement>("#effect-idea
const effectIdeaBusySpinner = document.querySelector<HTMLDivElement>("#effect-idea-busy-spinner");
const effectIdeaBusyTitle = document.querySelector<HTMLDivElement>("#effect-idea-busy-title");
const effectIdeaBusyCopy = document.querySelector<HTMLDivElement>("#effect-idea-busy-copy");
const effectIdeaBusyControls = document.querySelector<HTMLDivElement>("#effect-idea-busy-controls");
const effectIdeaBusyControlsGrid = document.querySelector<HTMLDivElement>("#effect-idea-busy-controls-grid");
const effectIdeaBusyControlsEmpty = document.querySelector<HTMLDivElement>("#effect-idea-busy-controls-empty");
const effectIdeaRetryButton = document.querySelector<HTMLButtonElement>("#effect-idea-retry");
const effectIdeaGeneratedSections = document.querySelector<HTMLDivElement>("#effect-idea-generated-sections");
const effectIdeaControls = document.querySelector<HTMLDivElement>("#effect-idea-controls");
Expand Down Expand Up @@ -222,7 +225,12 @@ let effectIdeaPreviewFrame = 0;
let effectIdeaPreviewEffect: ReturnType<typeof compileRuntimeEffect> | null = null;
let effectIdeaPreviewParams: Record<string, number | string> = {};
let effectIdeaCountdownTimer = 0;
let effectIdeaCarouselEntries: Array<{ name: string; effect: ReturnType<typeof compileRuntimeEffect>; params: Record<string, number | string> }> = [];
let effectIdeaCarouselEntries: Array<{
name: string;
effect: ReturnType<typeof compileRuntimeEffect>;
params: Record<string, number | string>;
controls: GeneratedEffectParam[];
}> = [];
let effectIdeaCarouselIndex = 0;
let effectIdeaAudioPreview = new EffectPreviewAudioController(EFFECT_PREVIEW_AUDIO_SRC);
let availableEffectNames = getEffectRegistryKeys();
Expand Down Expand Up @@ -595,11 +603,13 @@ function setEffectIdeaBusyState(mode: "hidden" | "busy" | "error", message?: str
if (mode === "busy") {
effectIdeaBusyTitle.textContent = "Generating your effect…";
effectIdeaBusyCopy.textContent = "Enjoy approved community effects while Codex cooks your idea.";
effectIdeaBusyControls?.classList.remove("hidden");
return;
}
if (mode === "error") {
effectIdeaBusyTitle.textContent = "Generation hiccup";
effectIdeaBusyCopy.textContent = message ?? "That attempt did not compile cleanly. Retry to generate a fresh variation.";
effectIdeaBusyControls?.classList.add("hidden");
}
}

Expand All @@ -626,6 +636,7 @@ function applyEffectIdeaCarouselEntry(): void {
}
effectIdeaPreviewEffect = entry.effect;
effectIdeaPreviewParams = { ...entry.params };
renderEffectIdeaBusyControls(entry.controls);
stopEffectIdeaPreview();
previewGeneratedIdea();
}
Expand All @@ -645,7 +656,8 @@ async function hydrateEffectIdeaCarousel(): Promise<void> {
return [{
name: entry.name,
effect: compileRuntimeEffect(entry.runtimeCode),
params: getGeneratedEffectDefaultParams(entry.params)
params: getGeneratedEffectDefaultParams(entry.params),
controls: entry.params ?? []
}];
} catch {
return [];
Expand All @@ -659,6 +671,79 @@ async function hydrateEffectIdeaCarousel(): Promise<void> {
effectIdeaCarouselIndex = 0;
}

function renderEffectIdeaBusyControls(params: GeneratedEffectParam[]): void {
if (!effectIdeaBusyControls || !effectIdeaBusyControlsGrid || !effectIdeaBusyControlsEmpty) {
return;
}
const hasControls = params.length > 0;
effectIdeaBusyControls.classList.toggle("hidden", !hasControls);
effectIdeaBusyControlsGrid.innerHTML = "";
effectIdeaBusyControlsEmpty.classList.toggle("hidden", hasControls);
if (!hasControls) {
return;
}

params.forEach((param) => {
const field = document.createElement("label");
field.classList.add("debug-field");
const label = document.createElement("span");
label.textContent = param.label;
field.appendChild(label);

if (param.type === "select") {
const select = document.createElement("select");
select.dataset.effectIdeaParam = param.key;
(param.options ?? []).forEach((option) => {
const optionEl = document.createElement("option");
optionEl.value = option.value;
optionEl.textContent = option.label;
select.appendChild(optionEl);
});
select.value = String(effectIdeaPreviewParams[param.key] ?? "");
select.addEventListener("change", () => {
effectIdeaPreviewParams[param.key] = select.value;
});
field.appendChild(select);
effectIdeaBusyControlsGrid.appendChild(field);
return;
}

const input = document.createElement("input");
input.dataset.effectIdeaParam = param.key;
if (param.type === "toggle") {
input.type = "checkbox";
input.checked = Number(effectIdeaPreviewParams[param.key]) !== 0;
input.addEventListener("change", () => {
effectIdeaPreviewParams[param.key] = input.checked ? 1 : 0;
});
field.appendChild(input);
effectIdeaBusyControlsGrid.appendChild(field);
return;
}

input.type = "number";
if (param.min !== undefined) {
input.min = String(param.min);
}
if (param.max !== undefined) {
input.max = String(param.max);
}
if (param.step !== undefined) {
input.step = String(param.step);
}
input.value = String(effectIdeaPreviewParams[param.key] ?? 0);
input.addEventListener("input", () => {
const numeric = Number(input.value);
if (!Number.isFinite(numeric)) {
return;
}
effectIdeaPreviewParams[param.key] = numeric;
});
field.appendChild(input);
effectIdeaBusyControlsGrid.appendChild(field);
});
}

function runEffectIdeaSuccessCountdown(onDone: () => void): void {
if (!effectIdeaCountdown) {
onDone();
Expand Down Expand Up @@ -824,6 +909,7 @@ function setEffectIdeaModalVisible(visible: boolean): void {
effectIdeaPreviewEffect = null;
effectIdeaCarouselEntries = [];
effectIdeaCarouselIndex = 0;
renderEffectIdeaBusyControls([]);
if (effectIdeaNameInput) {
effectIdeaNameInput.value = "";
}
Expand Down
24 changes: 24 additions & 0 deletions src/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -1474,6 +1474,30 @@ body,
text-align: center;
}

.effect-idea-busy-controls {
width: 100%;
display: grid;
gap: 0.35rem;
margin-top: 0.2rem;
}

.effect-idea-busy-controls-label {
color: rgba(232, 247, 255, 0.86);
font-size: 0.66rem;
letter-spacing: 0.07em;
text-transform: uppercase;
}

#effect-idea-busy-controls-grid {
grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
gap: 0.4rem;
}

#effect-idea-busy-controls-grid .debug-field {
background: rgba(4, 10, 18, 0.65);
padding: 0.3rem 0.4rem;
}

#effect-idea-name {
border: 1px solid rgba(142, 249, 255, 0.22);
background: rgba(6, 12, 20, 0.9);
Expand Down
2 changes: 2 additions & 0 deletions src/style.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,11 @@ describe("effect idea modal styling", () => {

expect(indexHtml).toContain('id="effect-idea-generated-sections" class="hidden"');
expect(indexHtml).toContain('id="effect-idea-busy-modal"');
expect(indexHtml).toContain('id="effect-idea-busy-controls-grid"');
expect(indexHtml).toContain('id="effect-idea-input" rows="8"');
expect(css).toContain("#effect-idea-input");
expect(css).toContain("min-height: 10.5rem;");
expect(css).toContain(".effect-idea-busy-controls");
});
});

Expand Down