Imported from clansdown/LlmImageCreator (
AGENTS.md). Install upstream withnpx skills add clansdown/LlmImageCreator. Copyright stays with the author.
AGENTS.md - Guidelines for Agentic Coding in LlmImageCreator
This file provides instructions for AI coding agents (e.g., opencode) working on the LlmImageCreator repository. It includes code style guidelines, conventions, and requirements for maintaining consistency. The project is a stand-alone HTML/JavaScript application for generating images interactively using OpenRouter.
CRITICAL: The existing codebase (agent.js, sw.js) is NON-COMPLIANT with JSDoc requirements. Agents must write NEW code that strictly follows these guidelines.
1. General Principles
- Readability First: Code should be self-explanatory. Use descriptive names and structures.
- Modularity: Keep functions small (<100 lines). One responsibility per function.
- Security: Never expose secrets. Validate user inputs.
- Performance: Optimize for client-side (e.g., avoid large loops; use efficient data structures like Maps/Sets).
- Browser Compatibility: Support modern browsers (Chrome, Firefox, Safari). Avoid polyfills unless necessary.
- Code Functionality: Agents must write fully functional, production-ready code unless the user explicitly requests stubs, placeholders, or incomplete implementations. Avoid TODO comments or non-working code segments—ensure all logic is complete and runnable.
2. Testing
- Agents DO NOT run tests: User performs all manual testing
./run_test_webserver.sh: User starts local server at http://localhost:8001/- No automated tests - manual browser testing only
3. JSDoc Requirements - MANDATORY
Every function and array/object declaration MUST have proper JSDoc documentation.
Functions - REQUIRED Tags
@param- All parameters with types@returns- Return type (even for void functions)@throws- Any errors thrown
// GOOD
/**
* Fetches all available models from OpenRouter
* @param {string} apiKey - OpenRouter API key
* @returns {Promise<Array<Object>>} Array of model objects
* @throws {Error} If API request fails
*/
function fetchModels(apiKey) { ... }
// BAD - NO JSDoc
function handleApiKeyEntry() { ... }
Arrays/Objects - REQUIRED @type
- Every inline array
[]or object{}needs@typeJSDoc - Exception: Variables assigned from well-documented function returns
// GOOD
/** @type {Array<{role: string, content: string}>} */
const conversationHistory = [];
// GOOD - from documented function
const slots = generateTimeSlots(); // No @type needed
// BAD - NO @type
let selectedModel = null;
let conversationHistory = [];
@typedef for Complex Types
/**
* @typedef {Object} ImageConfig
* @property {string} imageSize - Image size (1K, 2K, 4K)
* @property {string} aspectRatio - Aspect ratio (1:1, 16:9, etc)
*/
4. File Structure & Dependencies
Module Loading (in index.html)
ES Modules loaded via <script type="module" src="main.js"></script>
main.js imports agent.js which then imports all other modules
File Purposes
index.html: Main HTML with Bootstrap 5, inline styles, script loadingmain.js: Application entry point, imports agent.js and calls init()state.js: Centralized state object (selectedModel, currentConversation, conversationHistory, isGenerating, deferredPrompt)openrouter.js: OpenRouter API calls (fetchModels, fetchBalance, generateImage)prompt.js: System prompt constantsstorage.js: OPFS storage (preferences, conversations, images)ui.js: DOM manipulation and UI updatesutil.js: Utility functions (generateRandomSeed, generateConversationTitle, getApiKey)agent.js: Main orchestration and event handlingsw.js: Service Worker for offline support (non-module)
Module Dependencies
- Bootstrap 5 via CDN (CSS in
<head>, JS before</body>) - All JS files use ES6 modules with
import/export - State managed in state.js, imported by ui.js and agent.js
Import Chain
main.js → agent.js → (prompt.js, openrouter.js, storage.js, state.js, ui.js, util.js)
5. Code Formatting
- Indentation: 4 spaces
- Line Length: <140 characters
- Semicolons: Always use at end of statements
- Braces: Always use for blocks (
if (cond) { ... }) - Spacing: One space around operators, after commas, no trailing spaces
- Blank Lines: One between functions, two between major sections
- Quotes: Single quotes for strings, double for HTML attributes
- Declarations: Use
letorconstfor all variable declarations MANDATORY
6. Naming Conventions
- Variables/Functions: camelCase (e.g.,
parseCsvToObjects) - Constants: UPPER_SNAKE_CASE (e.g.,
OPENROUTER_BASE_URL) - HTML IDs: kebab-case (e.g.,
api-key-input) - Descriptive Names: Avoid abbreviations
- ❌
p,a,tsStart - ✅
player,appointment,timeSlotStart
- ❌
7. Function Guidelines
- Max 100 lines per function (excluding comments)
- Single Responsibility: One function does one thing
- Parameters: Limit to <5; use objects for many params
- Early Returns: Use for clarity and reduced nesting
- Arrow Functions: Use for short callbacks only
// BAD - 100+ lines doing multiple things
function submitAddPlayer() { /* ... */ }
// GOOD - split into smaller functions
function handleApiKeyEntry() { ... }
function handleGenerate() { ... }
8. Variables & Data Structures
- Scope: Minimize globals; use objects to group related data
- Arrays: Use literals
[], prefer Maps for key-value - Objects: Use literals
{} - Strings: Template literals for interpolation
`${var}`
9. Error Handling
- Validation: Check inputs early (
if (!apiKey) return;) - Throws: For critical errors; ALWAYS document with
@throws - User Feedback: Display errors via
displayError()in UI - Logging: Use
console.logfor debug only
10. Null Safety Patterns
Use modern JavaScript operators for clean null/undefined handling instead of explicit checks.
Optional Chaining (?.)
When to use: Access properties or methods of objects that may be null/undefined
Pattern: Use ?. instead of explicit if checks or ternaries
// GOOD - Optional chaining
const resolution = entry.response.imageResolutions?.[index] ?? "1K";
const apiKey = STATE.currentConversation?.apiKey;
// BAD - Verbose checks
const resolution = entry.response.imageResolutions ? entry.response.imageResolutions[index] : "1K";
const apiKey = STATE.currentConversation && STATE.currentConversation.apiKey;
Nullish Coalescing (??)
When to use: Provide fallback value only when left side is null or undefined
Pattern: ?? is safer than || (which also triggers on false, 0, "")
// GOOD - Nullish coalescing (only catches null/undefined)
const resolution = entry.response.imageResolutions?.[index] ?? "1K";
// AVOID - Logical OR (catches falsy values like 0, false, "")
const resolution = entry.response.imageResolutions?.[index] || "1K";
Initialization with ??=
When to use: Initialize arrays/objects only if they don't exist
Pattern: ??= is shorthand for x = x ?? default
// GOOD - Conditionally initialize
entry.response.imageResolutions ??= [];
entry.response.imageResolutions.push(resolution);
Key Rule
Prefer ?. and ?? over explicit null checks: These operators provide the same safety with cleaner, more readable code.
11. HTML/CSS & Bootstrap
- Framework: Bootstrap 5 via CDN
- Custom CSS: Inline
<style>in index.html for layout-specific needs - Modals: Bootstrap modals with
data-bs-dismiss - Accessibility: Add
aria-labelfor inputs/buttons without labels
Bootstrap JavaScript Usage - RESTRICTED
CRITICAL: Bootstrap JavaScript components should ONLY be used for modal dialogs. All other UI interactions should be implemented directly with captured references and native DOM APIs or helper functions.
PERMITTED use cases:
- Modal dialogs (showing/hiding modals with
bootstrap.Modal) - Modal event handling (
hidden.bs.modal, etc.) - Tooltip initialization for accessibility
FORBIDDEN use cases:
- Collapse components (use class toggling with captured references)
- Dropdowns (use click handlers with captured references)
- Toggles (use class toggling with captured references)
- Any other Bootstrap JavaScript components
Pattern for non-modal interactions:
// GOOD - Use captured references with native DOM
function setupCollapse(reference) {
const button = reference.querySelector("button[data-action='toggle']");
const collapse = reference.querySelector(".collapse");
button.addEventListener("click", async function() {
collapse.classList.toggle("show");
// logic here
});
}
// BAD - Using Bootstrap JavaScript
const collapseInstance = new bootstrap.Collapse(collapseElement);
Helper Function Pattern: For repeated UI interaction patterns, create helper functions instead of using Bootstrap JavaScript:
// GOOD - Helper for toggle interactions
function toggleElementVisibility(element: HTMLElement, attributeName: string = "show"): boolean {
const isExpanded = element.classList.toggle(attributeName);
return isExpanded;
}
DOM Insertions - MANDATORY
All DOM insertions MUST use HTML templates with cloneNode(true) and follow this exact pattern:
<!-- conversation-item-template: Template for displaying a single conversation in the sidebar -->
<template id="conversation-item-template">
<div class="conversation-item">
[content goes here]
</div>
</template>
// GOOD - Correct template usage pattern
function addConversationItem(conversation) {
const template = document.getElementById('conversation-item-template');
const clone = template.content.cloneNode(true);
const container = clone.firstElementChild; // Capture BEFORE adding to DOM
container.textContent = conversation.title;
document.getElementById('conversation-list').appendChild(clone);
// Now 'container' is a reference to the element now in the DOM
container.addEventListener('click', () => handleConversationClick(conversation.id));
}
// BAD - Incorrect patterns
function addConversationItem(conversation) {
const div = document.createElement('div'); // No template
div.textContent = conversation.title;
document.getElementById('conversation-list').appendChild(div);
}
Template Requirements
- Every template must have a comment describing what the template is for (placed on the line above the
<template>tag) - Templates must have a single interior div - the template should contain exactly one root element (the
<div>inside<template>) - Capture reference before DOM insertion - Always capture
clone.firstElementChildBEFORE callingappendChild,insertBefore, or any method that adds the clone to the document - Use the captured reference for all subsequent DOM manipulations (adding event listeners, setting content, etc.)
- querySelector usage in template context -
querySelectorshould ONLY be used immediately after cloning a template to obtain references to elements within it. All event handlers and subsequent DOM manipulations MUST use the captured references obtained during template cloning. DO NOT usequerySelectorinside handlers or in any context after the template has been appended to the DOM.
12. Security & Best Practices
- Input Sanitization: Trim/validate all user inputs
- XSS Prevention: Use
textContentnotinnerHTMLwith user data - No Secrets: Never hardcode API keys; use user input or storage
- PWA: Service Worker handles offline caching
13. Version Control
- Agents must NEVER commit, push, or perform git operations
- Only make code changes; user handles all version control
Code Review Checklist
Before submitting any code changes, verify:
- Every function has JSDoc with @param, @returns
- Every function that throws has @throws
- All variables use let or const (no var declarations)
- Every inline array
[]has @type comment - Every inline object
{}has @type comment - No single-letter variable names (except trivial loop counters)
- No abbreviations in names
- Functions under 100 lines
- All HTML IDs use kebab-case
- All DOM insertions use HTML templates with cloneNode(true)
- All templates have descriptive comments
- All templates have a single interior div
- References captured via clone.firstElementChild before DOM insertion
- querySelector only used during template cloning, not in handlers
By following these guidelines, agents maintain a clean, maintainable, and well-documented codebase.
14. TypeScript Usage
Strict Type Checking
All TypeScript code uses strict: true mode. This includes:
noImplicitAny- No implicit any typesstrictNullChecks- Strict null checkingstrictFunctionTypes- Strict function type checking- Always strict mode enabled
The any Type
Strongly discouraged. Only use any when absolutely necessary, and in those cases provide thorough documentation about what's contained.
WHEN to use any:
-
Legacy library types - When a library has poor or missing type definitions and you cannot create proper types
// Example: third-party library with no types const result: any = legacyLibrary.getData(); // Returns complex structure we don't know // Must document: result contains { data, metadata, timestamp } -
Bootstrap/Third-party runtime types - When CDN-loaded types are imperfect but we need to interoperate
// Bootstrap type not perfect - need any for dynamic instance properties const tooltipInstance: any = new bootstrap.Tooltip(element); // Must document: tooltipInstance has { show(), hide(), dispose() } methods -
Temporary migration placeholder - Only during active migration from JS to TS, not in final code
// TEMPORARY DURING MIGRATION - needs proper type after migration complete const externalData: unknown = getExternalData(); // Must document: externalData expected structure
Use unknown instead of any where possible for better type safety.
WHEN NOT to use any:
- When you can define a proper type/interface/create type
- When TypeScript can infer the type
- When you can refine it to a more specific type
- As a shortcut to avoid defining proper types
ALTERNATIVES to any:
- Use
unknownwhen the type is unknown but will be checked - Define proper interfaces/types
- Use union types for multiple possibilities
- Use generics for type parameterization
Documentation Requirements for any:
If you must use any, you MUST add a comment immediately following explaining:
const complexData: any = getComplexData();
// COMPLEX DATA STRUCTURE: Contains { field1: string, field2: number[], field3: { nested: object } }
// Reason: External API returns variable structure not properly typed yet
Without this documentation, use of any will be rejected in code review.