Common tasks and how to accomplish them in Loopi development.
Time: ~30 minutes
Complexity: Medium
-
Define the step type
- File:
src/types/steps.ts - Add interface, add to union, add to
stepTypesarray
- File:
-
Create UI component
- File:
src/components/automationBuilder/nodeDetails/stepTypes/YourStep.tsx - Use existing step as template
- File:
-
Wire up component
- File:
src/components/automationBuilder/nodeDetails/stepTypes/index.ts- add export - File:
src/components/automationBuilder/nodeDetails/StepEditor.tsx- add case
- File:
-
Implement execution
- File:
src/main/automationExecutor.ts- add case inexecuteStep()
- File:
-
Add defaults
- File:
src/hooks/useNodeActions.ts- add case ingetInitialStepData()
- File:
-
Test
- Run
pnpm start - Create automation, add your step
- Verify UI renders
- Run automation, verify execution
- Run
-
Document
- File:
docs/STEPS_REFERENCE.md- add step documentation - File:
docs/examples/- create example automation - Run:
pnpm formatbefore committing
- File:
Reference: NEW_STEP_TEMPLATE.md
Example: Extract step not storing variables correctly
-
Identify which file has the bug
- UI issue? →
src/components/.../stepTypes/ExtractStep.tsx - Execution issue? →
src/main/automationExecutor.ts(extract case) - Type issue? →
src/types/steps.ts(StepExtract interface)
- UI issue? →
-
Add logging to understand issue
// In automationExecutor.ts case "extract": { const selector = this.substituteVariables(step.selector); console.log("Extracting from:", selector); const value = await webContents.executeJavaScript(`...`); console.log("Extracted value:", value, "Type:", typeof value); if (step.storeKey) { this.variables[step.storeKey] = this.parseValue(value); console.log("Stored as:", this.variables[step.storeKey]); } break; }
-
Test in dev mode
- Run
pnpm start - Create automation with Extract step
- Open DevTools (F12) → Logs tab
- Check console output
- Run
-
Fix the issue
- Apply fix to relevant file
- Test again to verify
-
Update documentation if behavior changed
docs/STEPS_REFERENCE.md- step documentationdocs/VARIABLES.md- if variable handling changed
Example: Add optional delay field to Click step
-
Update type
// src/types/steps.ts interface StepClick extends StepBase { type: "click"; selector: string; delay?: number; // Add this }
-
Update UI component
// src/components/.../stepTypes/ClickStep.tsx <div className="space-y-2"> <Label className="text-xs">Delay (ms)</Label> <Input type="number" value={step.delay || 0} onChange={(e) => onUpdate(id, "update", { step: { ...step, delay: parseInt(e.target.value) } })} /> </div>
-
Update execution
// src/main/automationExecutor.ts case "click": { const selector = this.substituteVariables(step.selector); if (step.delay) { await this.wait(step.delay); } await webContents.executeJavaScript(`...`); break; }
-
Update documentation
docs/STEPS_REFERENCE.md- update Click step section- Add field to JSON examples
- Document the new field
Common patterns:
Error: Property 'x' does not exist on type 'StepBase'
- Cause: Accessing field not in type definition
- Fix: Update step interface in
src/types/steps.ts
Error: Type 'unknown' is not assignable to type 'string'
- Cause: Variable could be any type, but code expects string
- Fix: Add type check:
if (typeof var === "string")
Error: case 'newStep' not handled in switch
- Cause: Added new step type but didn't update switch statement
- Fix: Add case in
StepEditor.tsxorAutomationExecutor.executeStep()
Debugging:
pnpm build # Check all errors
pnpm start # Development mode with live reloadWhen to add IPC handlers:
- Need to access file system (read/write files)
- Need to access Electron APIs (dialog, session, etc.)
- Need cross-process communication between renderer and main process
Adding a new IPC handler:
-
Define the IPC handler in
src/main/ipcHandlers.ts:ipcMain.handle("channel:name", async (event, arg) => { // Handler code here return result; });
-
Expose in preload in
src/preload.ts:const electronAPI = { yourAPI: { method: () => ipcRenderer.invoke("channel:name"), }, };
-
Update types in
src/types/globals.d.ts:interface ElectronAPI { yourAPI: { method: () => Promise<ReturnType>; }; }
-
Use from React in components:
const result = await window.electronAPI.yourAPI.method();
Working with Settings:
- Stored in
~/.config/[AppName]/settings.json - Loaded/saved via
SettingsStoreservice - Auto-persisted from Settings component
- Loaded on app startup in
src/app.tsx
Example - Adding a new setting:
// 1. Update AppSettings interface
interface AppSettings {
theme: "light" | "dark" | "system";
enableNotifications: boolean;
downloadPath?: string;
newSetting?: string; // Add this
}
// 2. Add UI in Settings.tsx
<Input
value={settings.newSetting}
onChange={(e) => setSettings(prev => ({
...prev,
newSetting: e.target.value
}))}
/>
// 3. Auto-save via useEffect (already in component)
useEffect(() => {
window.electronAPI.settings.save(settings);
}, [settings]);Manual Testing Checklist:
For any change, test in this order:
-
TypeScript compilation
pnpm build # Should complete with no errors -
Application startup
pnpm start # App should launch without crashing -
Basic functionality
- Open Dashboard
- Create new automation
- Add steps of relevant types
- Edit step configurations
-
Execution
- Run automation
- Watch in preview window
- Check console logs
- Verify results
-
Code formatting
pnpm format # Fix any style issues before committing
Commit Message Format:
type: brief description
Optional longer explanation if needed.
- Detail 1
- Detail 2
Types:
feat:- New step type or featurefix:- Bug fixdocs:- Documentation changesrefactor:- Code restructuringtest:- Test additions/updateschore:- Build, dependencies, etc.
Examples:
feat: Add DatePicker step type
Allows users to interact with date picker inputs.
Supports various date formats and localization.
- Add StepDatePicker type and UI
- Implement execution in automationExecutor
- Add STEPS_REFERENCE.md documentation
fix: Correct variable substitution in API URLs
Variables with special characters now escape properly.
refactor: Simplify variable type detection
Extract parseValue logic to reduce code duplication.
docs: Update VARIABLES.md with array nesting examples
Before pushing:
# 1. Format code
pnpm format
# 2. Verify it compiles
pnpm build
# 3. Test manually
pnpm start
# 4. Check for any debug logs
git diff # Remove console.log, etc.
# 5. Write clear commit message
git commit -m "feat: Add new step type"
# 6. Push to feature branch
git push origin feature/your-featurePR Description Template:
## Description
What does this PR do?
## Type of Change
- [ ] New step type
- [ ] Bug fix
- [ ] Feature enhancement
- [ ] Documentation
- [ ] Refactoring
## Changes Made
- Change 1
- Change 2
- Change 3
## Testing
How to test this change:
1. Step 1
2. Step 2
## Documentation Updated
- [ ] STEPS_REFERENCE.md
- [ ] ARCHITECTURE.md
- [ ] VARIABLES.md
- [ ] Example JSON created
## Screenshots/Videos (if applicable)Checklist before committing:
- All code changes documented
- Step types updated in STEPS_REFERENCE.md
- New features explained in ARCHITECTURE.md
- Variable changes documented in VARIABLES.md
- Examples created if applicable
- All links verify (use relative paths:
./FILE.md) - Code formatting applied (
pnpm format)
App won't start:
# Clear cache and reinstall
rm -rf node_modules pnpm-lock.yaml
pnpm install
pnpm startTypeScript errors don't clear:
pnpm build --forceHot reload not working:
- Restart dev server:
Ctrl+Cthenpnpm start - Check file saved in editor
Variables not substituting:
- Add logging in
substituteVariables()method - Check
{{varName}}syntax is correct (no spaces allowed) - Verify variable set before use
Step not executing:
- Check case statement in
automationExecutor.ts - Add
console.log()to debug execution path - Verify selector is valid (test in browser DevTools)