| name | add-inject |
| description | Create a game UI injection that modifies existing game elements with proper cleanup |
| aliases | ["new-inject","create-inject","add-qol"] |
Add Inject Skill
Usage
/add-inject <featureName>
Note: This creates a game UI injection (modifies existing game UI). For Gemini HUD UI, use /add-component or /add-section.
Step 1: Ask Questions
1. Injection Purpose
What game UI element does this modify?
Brief description:
2. Target Elements
Which game UI elements are targeted?
Examples:
- Inventory panel
- Shop interface
- Player stats display
- Game toolbar
- Chat window
3. Modification Type
What modifications are made?
[ ] Add new elements (buttons, indicators, overlays)
[ ] Modify existing elements (styles, text, attributes)
[ ] Add event listeners (click handlers, observers)
[ ] Inject data displays (stats, timers, counters)
4. Game Data
Does it need game data?
A) Static data → MGData
B) Real-time state → Globals
C) Both
D) None
5. Sprites
Does it display game sprites? → MGSprite.toCanvas()
6. Persistence
Does it need persistent state?
A) Yes - User preferences saved
B) No - Stateless
Step 2: Create Structure
src/ui/inject/qol/<featureName>/
├── index.ts # Public API (init, destroy, isEnabled)
├── inject.ts # DOM injection logic
├── styles.css.ts # Scoped styles (optional)
└── state.ts # Persistent state (optional)
Step 3: File Templates
index.ts
import { injectElements, removeElements } from './inject';
let injected = false;
const cleanups: (() => void)[] = [];
export const <FeatureName>Inject = {
init(): void {
if (injected) return;
injected = true;
const cleanup = injectElements();
if (cleanup) cleanups.push(cleanup);
console.log('[<FeatureName>Inject] Initialized');
},
destroy(): void {
if (!injected) return;
cleanups.forEach(fn => fn());
cleanups.length = 0;
removeElements();
injected = false;
console.log('[<FeatureName>Inject] Destroyed');
},
isEnabled(): boolean {
return injected;
},
};
inject.ts
import { <featureName>Css } from './styles.css';
const CONTAINER_SELECTOR = '.game-ui-container';
const INJECT_CLASS = 'gemini-qol-<featureName>';
export function injectElements(): (() => void) | null {
const container = document.querySelector(CONTAINER_SELECTOR);
if (!container) {
console.warn('[<FeatureName>Inject] Container not found');
return null;
}
const cleanups: (() => void)[] = [];
const style = document.createElement('style');
style.textContent = <featureName>Css;
style.setAttribute('data-gemini', '<featureName>');
document.head.appendChild(style);
cleanups.push(() => style.remove());
const wrapper = document.createElement('div');
wrapper.className = INJECT_CLASS;
container.appendChild(wrapper);
cleanups.push(() => wrapper.remove());
const handleClick = () => { };
wrapper.addEventListener('click', handleClick);
cleanups.push(() => wrapper.removeEventListener('click', handleClick));
return () => {
cleanups.forEach(fn => fn());
};
}
export function removeElements(): void {
document.querySelectorAll(`.${INJECT_CLASS}`).forEach(el => el.remove());
document.querySelectorAll('style[data-gemini="<featureName>"]').forEach(el => el.remove());
}
styles.css.ts
export const <featureName>Css = `
.gemini-qol-<featureName> {
/* Use CSS variables when possible */
position: absolute;
z-index: 100;
}
.gemini-qol-<featureName>__button {
background: var(--color-primary, #4a9eff);
color: var(--color-text, #fff);
border: none;
border-radius: 4px;
padding: 8px 12px;
cursor: pointer;
min-height: 44px; /* Touch-friendly */
}
.gemini-qol-<featureName>__button:hover {
opacity: 0.9;
}
`;
state.ts (if persistent)
import { storageGet, storageSet } from '../../../../utils/storage';
const STORAGE_KEY = 'inject:<featureName>:config';
export interface <FeatureName>Config {
enabled: boolean;
}
const DEFAULT_CONFIG: <FeatureName>Config = {
enabled: true,
};
export function loadConfig(): <FeatureName>Config {
return storageGet(STORAGE_KEY, DEFAULT_CONFIG);
}
export function saveConfig(config: <FeatureName>Config): void {
storageSet(STORAGE_KEY, config);
}
Step 4: Register (if standalone)
If this injection is standalone (not part of a feature):
Export → src/ui/inject/qol/index.ts
export { <FeatureName>Inject } from './<featureName>';
Initialize in bootstrap or feature
import { <FeatureName>Inject } from '../../ui/inject/qol/<featureName>';
<FeatureName>Inject.init();
<FeatureName>Inject.destroy();
Step 5: Validate
Structure
Lifecycle
Styling
Game Data (if applicable)
Boundaries
Integration with Features
If the injection is part of a feature:
import { <FeatureName>Inject } from '../../ui/inject/qol/<featureName>';
function init(): void {
<FeatureName>Inject.init();
}
function destroy(): void {
<FeatureName>Inject.destroy();
}
References
- Rules:
.claude/rules/ui/ui.inject.md
- Existing injections:
src/ui/inject/qol/*/
- Storage:
src/utils/storage.ts