CLAUDE.md is for how to work with the code — architecture, patterns, gotchas, testing procedures, and coding conventions. It should NOT contain roadmap items, feature wishlists, or project planning.
Roadmap and planning content belongs in:
docs/ROADMAP.md— roadmap, milestones, changelog, and open items
Review needed (future session): Audit existing CLAUDE.md sections for roadmap content that has leaked in (e.g., "Future Improvements" lists, "Testing Required (Next Session)" checklists). Move planning items to the roadmap file and keep CLAUDE.md focused on technical reference.
Use simple two-digit versions for all web tools (e.g., v1, v2, v6). No semantic versioning (1.0.0) needed.
Format in footer: Tool Name vX | YYYY-MM-DD HH:MM ET
Example: Arena Editor v2 | 2026-01-16 14:30 ET
IMPORTANT: Always include timestamp in Eastern Time (ET) to distinguish multiple updates per day. Update the timestamp whenever the page is modified.
To get current time: Run TZ='America/New_York' date "+%Y-%m-%d %H:%M ET" in Bash to get the actual current time. Never guess or make up timestamps.
All tools use a consistent dark theme:
- Background:
#0f1419 - Surface:
#1a1f26 - Border:
#2d3640 - Text:
#e6edf3 - Text dim:
#8b949e - Accent:
#00e676(green) - Hover:
#00c853 - Fonts: JetBrains Mono (headings), IBM Plex Mono (body)
When editing web tools, audit all buttons and controls in the modified code sections:
- Every interactive element needs a tooltip - buttons, tabs, inputs, sliders
- Use the
titleattribute for simple tooltips - Be descriptive but concise - explain what happens when clicked/changed
- Include keyboard shortcuts if applicable (e.g., "Save pattern (Ctrl+S)")
- Update tooltips when behavior changes - stale tooltips are worse than none
Standard tooltip patterns:
- Buttons: "Action description" (e.g., "Capture current frame to clipboard")
- Toggles: "Enable/disable feature" (e.g., "Show panel boundaries")
- Inputs: "Parameter name: valid range" (e.g., "Wavelength: 10-360 pixels")
- Tabs: "View name" (e.g., "3D arena preview")
Audit checklist when touching UI code:
- All buttons have tooltips
- All tabs have tooltips
- All sliders/inputs have tooltips with valid ranges
- Tooltips match current behavior (not outdated)
- All web tools are standalone single-page HTML files
- No build process required
- Vanilla JavaScript preferred
- Dependencies via CDN only (Three.js, etc.)
- Web outputs must match MATLAB outputs exactly
This project uses Prettier for consistent JavaScript formatting. Configuration is in .prettierrc.
Setup:
npm install # Install Prettier (first time only)Style rules:
- Single quotes (
'string') - No trailing commas
- 4-space indentation
- 100 character print width
- Semicolons required
Commands:
npm run format # Format all JS files
npm run format:check # Check formatting (for CI)
npx prettier --write path/to/file.js # Format single fileBefore committing: Run npm run format on any modified JavaScript files to ensure consistent style.
Some JavaScript modules are shared between multiple tools and must support different loading patterns:
Pat-Parser Dual Export Pattern (js/pat-parser.js):
- Icon generator loads via
<script src="proxy.php?url=https%3A%2F%2Fgithub.com%2Fjs%2Fpat-parser.js">→ requireswindow.PatParser - Pattern editor loads via
import PatParser from './js/pat-parser.js'→ requires ES6 export - Solution: Export both ways with clear comments explaining the dual export strategy
// Export for browser (global) - for <script> tags (icon generator)
if (typeof window !== 'undefined') {
window.PatParser = PatParser;
}
// ES module export - for import statements (pattern editor)
export default PatParser;Important: When modifying shared modules, test with ALL tools that depend on them.
Arena Config Helper Functions (js/arena-configs.js):
The getConfigsByGeneration() function returns objects, NOT simple strings:
// CORRECT - configs is array of { name, label, description, arena } objects
const configsByGen = getConfigsByGeneration();
for (const [gen, configs] of Object.entries(configsByGen)) {
configs.forEach(config => {
console.log(config.name); // "G6_2x10"
console.log(config.label); // "G6 (2×10) - 360°"
console.log(config.arena); // { generation, num_rows, num_cols, ... }
});
}
// WRONG - treating config objects as strings will break dropdown population
configs.forEach(name => {
option.value = name; // BUG: name is an object, not a string!
});This pattern has caused bugs multiple times. Always use config.name for the value and config.label for display text.
Web calculations are validated against MATLAB reference data:
- MATLAB generates
reference_data.json - Copy to
data/directory - GitHub Actions runs validation on push
- Tolerance: 0.0001
Standard panel generations:
- G3: 32mm width, 8x8 pixels (circle LEDs)
- G4: 40.45mm width, 16x16 pixels (circle LEDs)
- G4.1: 40mm width, 16x16 pixels (rotated rectangle LEDs)
- G6: 45.4mm width, 20x20 pixels (rotated rectangle LEDs) Note: G5 is deprecated and no longer supported.
Arena radius formula: cRadius = panelWidth / (tan(alpha/2)) / 2 where alpha = 2*PI/numPanels
- G4.1, G6 use SMD LED packages with rectangular shapes rotated 45° on the panel
- G3, G4 use circular LED visualization
- Uses Three.js r182 with OrbitControls (ES6 modules)
- Renderer requires
preserveDrawingBuffer: truefor screenshot functionality - LED meshes stored in
ledMeshes[]array for efficient animation (color-only updates) - Pattern rotation uses
state.phaseOffsetto shift pattern, not world rotation - Controls disabled during auto-rotate for performance
state = {
panelType: 'G6', // G3, G4, G4.1, G6
numCols: 12, // panels around (from 2D editor URL params)
numRows: 3, // panels vertically
activePanels: null, // array of active column indices, null = all
pattern: 'allOn', // 'allOn', 'grating', 'sine'
gratingPixels: 20, // pixels on/off (must be integer divisor)
sineWavelength: 120, // wavelength in pixels (must be integer divisor)
phaseOffset: 0 // current phase for rotation animation
}- Grating and sine wavelengths must be integer divisors of total azimuth pixels
- This ensures patterns tile seamlessly around the arena
- Use
getIntegerDivisors()to populate valid options
Format: arena_{gen}_{cols}c{rows}r_{pattern}[_stats]_{timestamp}.png
Example: arena_G6_12c3r_sine120_stats_2026-01-17T10-30-45.png
gen: Panel generation (G3, G4, G4.1, G6) - defaults to G6cols: Number of columns (panels around) - defaults to 10rows: Number of rows (panels vertically) - defaults to 3active: Comma-separated 0-based indices of active panels (omitted if all active)
When accessed from the Arena Layout Editor, all parameters are passed through. When accessed directly from the index page, defaults are used (G6, 10 columns, 3 rows, all active).
The current 3D viewer shows "average" angular resolution, which is simply total field of view divided by number of pixels. This is not accurate because each pixel subtends a different angle depending on its position.
A proper implementation should:
- Calculate per-pixel angular resolution
- Show a histogram of resolution values
- Display min, max, and median resolution
- Consider both azimuth (constant) and vertical (varies with elevation) components
The azimuth resolution is constant (360° / total_azimuth_pixels), but vertical resolution varies based on viewing angle from center - pixels near the vertical center subtend smaller angles than those at the top/bottom edges.
Status: ✅ FIXED as of 2026-02-02
The regex in inferArenaFromPath() was updated to handle partial arena names like G6_2x8of10:
const arenaPattern = /G(6|4\.1|4|3)[_-](\d+)[x×](\d+)(?:of(\d+))?/i;Status: ✅ VERIFIED WORKING as of 2026-02-03
Detailed byte-for-byte comparison with MATLAB confirmed the JavaScript implementation is correct:
cart2sphere,sphere2cart, androtateCoordinatesmatch MATLAB exactly- All motion types (rotation, expansion, translation) with rotated poles produce identical output
Bug Fix (2026-02-03): A JavaScript falsy-value bug in the UI was causing poleElevation = 0 to be silently converted to -90. The expression parseFloat(value) || -90 evaluates to -90 when value is 0 because 0 is falsy. Fixed by adding parseFloatWithDefault() helper using Number.isFinite(). Pattern Editor v0.9.7 now correctly handles Pole El = 0 and produces concentric rings matching MATLAB.
The 3D viewer supports loading .pat files directly:
Supported Formats:
- G6: 17-byte header with "G6PT" magic, 20x20 panels, GS2 (binary) and GS16 (4-bit grayscale)
- G4: 7-byte header, 16x16 panels, subpanel addressing
UI Controls:
- "Load .pat File" button opens file picker
- Pattern info displays filename, dimensions, frames, grayscale mode
- Frame slider scrubs through multi-frame patterns
- Play/Pause button with FPS dropdown (5, 10, 15, 20, 30 FPS)
- "Clear Pattern" button returns to synthetic patterns
Implementation Files:
js/pat-parser.js- Pattern file parser module (usePatParser.parsePatFile()method)- Pattern loading integrated into
arena_3d_viewer.html
Use window.testLoadPattern(url) for automated pattern validation:
// In browser console or via Claude Chrome extension
await testLoadPattern('/test_patterns/grating_G6.pat');
// Logs: Generation, dimensions, frames, grayscale mode
// Returns true if load succeededAfter loading a pattern, verify in console output:
- ✓ Generation detected correctly (G6 vs G4)
- ✓ Panel dimensions match expected (20×20 for G6, 16×16 for G4)
- ✓ Total pixels = rows × cols × panelSize²
- ✓ Frame count matches expected
- ✓ Pixel values in valid range (0-1 for GS2, 0-15 for GS16)
Patterns should display with:
- Row 0 at bottom (not flipped vertically)
- Column 0 at correct azimuth position
- Gratings aligned properly (vertical stripes appear vertical)
Use corner marker test patterns to verify:
// Bottom-left corner should be brightest
console.log('Pixel (0,0):', patternData.frames[0][0]);
console.log('Pixel (0,max):', patternData.frames[0][totalCols-1]);The Claude in Chrome extension enables automated browser testing without manual interaction.
- Start local HTTP server:
python -m http.server 8080 - Use Chrome extension tools to navigate and interact
| Tool | Purpose |
|---|---|
tabs_context_mcp |
List open tabs |
navigate |
Go to URL |
read_page |
Get page content |
javascript_tool |
Execute JS in page context |
read_console_messages |
Get console output |
computer (screenshot) |
Capture visual state |
1. Navigate to test page:
navigate to http://localhost:8080/arena_3d_viewer.html
2. Execute test JavaScript:
// Load pattern
await testLoadPattern('/test_patterns/grating_G6.pat');
// Verify playback controls
document.getElementById('frameSlider').max;
document.getElementById('playPauseButton').textContent;3. Capture and verify screenshot:
- Take screenshot with
computertool (action: screenshot) - Use Read tool to view screenshot image
- Verify UI elements render correctly
4. Check console for errors:
- Use
read_console_messagesto check for JavaScript errors - Verify pattern validation output
1. Start server: python -m http.server 8080
2. navigate → http://localhost:8080/arena_3d_viewer.html
3. javascript_tool → await testLoadPattern('/test_patterns/grating_G6.pat')
4. read_console_messages → verify no errors, check pattern info
5. computer screenshot → capture visual state
6. Read screenshot → verify pattern displays correctly
| Element | ID | Purpose |
|---|---|---|
| Pattern load button | loadPatternBtn |
Trigger file picker |
| Pattern info display | patternInfo |
Shows loaded pattern details |
| Frame slider | frameSlider |
Multi-frame navigation |
| Frame label | frameLabel |
Current frame display |
| Play/Pause button | playPauseButton |
Playback control |
| FPS dropdown | fpsSelect |
Playback speed |
| FOV slider | fovSlider |
Camera field of view |
| Clear button | clearPatternBtn |
Reset to synthetic patterns |
Browser Caching with GitHub Pages:
- GitHub Pages and browser caching can prevent updated JS files from loading
- Symptoms: "X is not defined" errors for recently added exports/functions
- Solutions:
- Hard refresh: Ctrl+Shift+R (Windows/Linux) or Cmd+Shift+R (Mac)
- Clear browser cache for localhost:8080 or GitHub Pages domain
- Add cache-busting query params:
<script src="proxy.php?url=https%3A%2F%2Fgithub.com%2Ffile.js%3Fv%3D2"> - For local testing, use
python -m http.serverwith hard refresh
- Always verify changes are visible in browser DevTools → Sources tab before debugging
CRITICAL: ES6 Module Import Failures are Catastrophic:
When an ES6 import statement fails, the entire <script type="module"> block stops executing. This is different from regular script errors - there's no partial execution.
How this breaks the Pattern Editor:
- Developer adds new import:
import { newFunction } from './module.js' - Developer adds
newFunctionto module.js exports - Local testing works (fresh files loaded)
- Push to GitHub
- GitHub Pages serves cached old module.js (without new export)
- Import fails silently → entire module stops → arena dropdown empty, no events attached
Symptoms:
- Arena dropdown is empty (most obvious)
- No JavaScript functionality works at all
- Console shows:
SyntaxError: The requested module does not provide an export named 'X'
Prevention rules:
- Never add new imports without testing on GitHub Pages after deployment
- Wait for cache to clear (or use hard refresh) before declaring success
- Test Pattern Editor loads after any change to shared modules (icon-generator.js, pat-parser.js, arena-configs.js)
- If adding new exports to shared modules, consider whether they're truly needed in the importing file
Recovery:
- Remove the failing import
- Push fix immediately
- Hard refresh on GitHub Pages to verify
CRITICAL: The 3D viewer MUST fully rebuild its geometry whenever the arena configuration changes or a new pattern is loaded. This means:
- Arena config changes (dropdown selection, pattern load auto-detect) → call
threeViewer.reinit(config, specs)to rebuild all panel/LED geometry _buildArena()must NEVER reset the camera position — only the initialinit()call sets the camera to top-down. Rebuilds preserve the user's current view.- Track which arena config the 3D viewer was last built with using
threeViewerArenaConfig. Compare on everyinit3DViewer()call and reinit if stale. - If
init()fails (scene is null), destroy the viewer and retry on next attempt — never leave a half-initialized viewer that silently ignores all controls.
These rules prevent stale geometry bugs where the 3D viewer shows an old arena after config changes.
The Pattern Editor is being developed in phases. The full migration plan is saved at:
Plan file: ~/.claude/plans/fuzzy-waddling-harbor.md
This plan covers:
- Two-pane layout (tools left, viewer right)
- Tool tabs: Generate, Frame to Pattern, Combine
- Viewer tabs: Grid/Edit, 3D, Mercator, Mollweide
- Frame clipboard for capturing and sequencing frames
- 7 implementation phases over ~6-7 weeks
Current status: Pattern Editor v0.9 complete. All major features implemented: Grid/Edit mode, 3D viewer, pattern generation, tabbed clipboard (frames/patterns), frame animation mode, sequence builder, pattern combiner. Pending: MATLAB reference validation, manual testing, polish.
The following UI improvements were made on 2026-02-02 and need testing on GitHub Pages:
-
Pattern Editor v0.9:
- GENERATE button visually larger and bolder
- Clipboard tabs switch between Frames and Patterns view
- "↓ Frame" capture button works, switches to Frames tab
- "↓ Pat" capture button works, switches to Patterns tab
- Clipboard clears when arena dropdown changes
- Clipboard clears when arena unlocked (with confirmation)
- Animate tab mode toggle works (Frame Shifting vs Frame Animation)
- Frame Animation: "Add All Clipboard Frames" populates sequence
- Frame Animation: Preview button generates pattern
- Frame Animation: Save .pat downloads file
- Image tab shows placeholder
-
Icon Generator v0.9 (was v0.8):
- No arena dropdown visible (removed)
- Loading
G6_2x10_*.patfile shows "✓ Detected: G6 (2×10)" - Loading file without arena in name shows error AND manual dropdown
- Manual arena dropdown allows selection when auto-detect fails
- "Select Folder..." button opens folder picker
- Selecting folder with .pat files loads them with arena from folder name
- Test patterns still work (use G6_2x10 default)
GitHub Issue: #6 - additional web tools for making patterns
- 3-zone layout: settings panel (280px left), editor with tab bar (flex right), filmstrip with lane view (bottom)
- Single
<script type="module">importing fromjs/arena-configs.js,js/protocol-yaml.js,js/plugin-registry.js - Data model:
experimentobject withexperiment_info,arena_info,rig_path,plugins[],experiment_structure, phases withcommands[], andconditions[]withcommands[] - Undo/redo: snapshot-based history stack (JSON.stringify/parse of
experiment, max 50 entries)
js/protocol-yaml.js— YAML parser (simpleYAMLParsewith inline comment stripping), v1/v2 generators, string helpers. Dual-export (window.ProtocolYAML + ES6 module). Used by HTML, both test files.yamlStr(str)— double-quotes strings for YAMLyamlPath(str)— single-quotes paths (no escape sequences, safe for Windows backslashes)
js/plugin-registry.js— Built-in plugin definitions (LEDControllerPlugin: 7 commands, BiasPlugin: 7 commands incl. connect), controller command definitions (6 commands), lookup functions for dropdown population. Dual-export.- Plugin config fields use
rigDefined: truefor fields already in rig YAML (ip, port) — these are NOT auto-included in exports createPluginEntry()skips fields with empty string defaults
- Plugin config fields use
Conditions and phases use command arrays as the primary data model:
condition = {
id: "string",
commands: [
{ type: "plugin", plugin_name: "camera", command_name: "getTimestamp" },
{ type: "controller", command_name: "trialParams", pattern: "...",
duration: 10, mode: 2, frame_index: 1, frame_rate: 10, gain: 0 },
{ type: "wait", duration: 3 },
{ type: "plugin", plugin_name: "backlight", command_name: "setRedLEDPower",
params: { power: 5, panel_num: 0, pattern: "1010" } },
]
}
phase = { include: true/false, commands: [ ...same format... ] }
experiment.plugins = [
{ name: "backlight", type: "class", matlab: { class: "LEDControllerPlugin" }, config: { ... } },
{ name: "camera", type: "class", matlab: { class: "BiasPlugin" }, config: { ... } }
]
experiment.rig_path = "./configs/rigs/test_rig_1.yaml"Helper functions: cmdFindTrialParams(commands), condGetDuration(cond), condGetPattern(cond), phaseGetDuration(phase).
Duration formula: max(trialParams.duration, sum_of_waits) — shows the actual wall-clock time. If waits exceed trialParams duration, the condition runs longer than the pattern display (visible to the user as a mismatch).
Shared command helpers (used by Commands tab, Table view, and phase editor):
buildAddCommandOptions()— returns HTML<option>/<optgroup>string for add-command dropdownscreateCommandFromSelectValue(value)— parses"controller:trialParams"/"wait:wait"/"plugin:backlight:setRedLEDPower"into a command objectcreatePluginCommand(pluginName, commandName)— builds plugin command with default params from registry schema
- Generates protocol v2 via
generateV2Protocol()fromjs/protocol-yaml.js rig:field replaces inlinearena_info- All string values use single quotes via
yamlPath()— pattern filenames, paths, plugin string params — prevents Windows backslash escape issues - Empty optional plugin params are omitted — e.g., an empty
patternfield is not exported (would cause MATLAB errors) - Plugin param types respected: input handlers consult
getCommandParams()schema — string-typed params likepattern: "1010"are kept as strings, not coerced to numbers plugins:section lists enabled plugins with class/config — only user-set config values are exported (empty = omit)- Conditions export full command arrays including plugin commands with params
- Phases export command arrays directly
Two tabs in the right panel, both views of the same data model:
- Commands (was "Visual" in v0.6) — Command card editor with color-coded cards (green=controller, gray=wait, blue=plugin), inline field editing, "Add Command" dropdown, up/down reorder arrows, delete buttons
- Table — Fully editable spreadsheet view with collapsible sections, inline field editing, add/move/delete commands, condition reorder/remove, Expand All/Collapse All buttons
The bottom timeline area is a unified scroll container:
- Block strip: Colored blocks (green=condition, gray=phase, dark=ITI) with drag-to-reorder conditions
- Lane view: Always-visible SVG showing controller spans (green bars), plugin events (blue dots), and wait bars (gray) across all blocks
- Fixed lane labels: Left column (70px) with lane names stays visible during scroll
- Block strip and lane SVG share the same scroll parent for perfect alignment
- Block widths use
Math.max(48, duration * pxPerSecond)— lane SVG must match this + account for 2px CSS gap between blocks - Clicking any filmstrip block switches to the Commands tab
- Snapshot stack:
undoStack[]andredoStack[]storeJSON.stringify(experiment)snapshots (max 50) saveSnapshot(): Called before every mutation. Pushes current state to undoStack, clears redoStack._restoringguard: Boolean flag set duringrestoreSnapshot()(wrapped in try/finally). Prevents focus events on re-rendered inputs from callingsaveSnapshot()and clearing the redo stack.- Selection clamping:
restoreSnapshot()clampsselection.indexto valid bounds after restoring, since the restored state may have fewer conditions. - Text inputs: Snapshot on
focus(notinput) — one undo step per field visit, not per keystroke. Browser native Ctrl+Z still works inside inputs. - Reset button: Clears conditions/phases/plugins to defaults, keeps settings (experiment_info, arena, rig_path). Clears both undo/redo stacks. Shows confirm dialog.
- Keyboard: Ctrl+Z/Cmd+Z (undo), Ctrl+Y/Cmd+Y/Ctrl+Shift+Z (redo) — only fires when focus is not in INPUT/TEXTAREA/SELECT.
When adding new mutation sites: Always call saveSnapshot() before the mutation. For new text inputs, add a focus event listener that calls saveSnapshot.
- Must use
<script type="module">to import shared modules - Mode 2 (Constant Rate):
gainfixed at 0,frame_rateeditable - Mode 4 (Closed-Loop):
frame_ratefixed at 0,gaineditable handleTrackClickcallsswitchEditorTab('commands')thenrenderEditor()— always shows Commands tab on block clickcomputeLaneData(): trialParams fires controller autonomously (doesn't advance clock), wait advances clock, plugin commands are instantaneous- Phase initialization must deep-clone:
{ include: false, commands: JSON.parse(JSON.stringify(DEFAULT_PHASE.commands)) }— shallow spread shares the commands array reference - Plugin config uses
setPluginConfig()helper that deletes empty values and removes config object when empty
experiment_designer.html— Main tool (v0.9)experiment_designer_quickstart.html— Step-by-step guide (v0.9)js/protocol-yaml.js— Shared YAML parser/generator (addedyamlPath)js/plugin-registry.js— Plugin definitions + command schemas (BiasPlugin: connect command added)tests/test-protocol-roundtrip.js— 137 CI checks (10 suites, v1+v2 + bug regression)tests/generate-roundtrip-protocol.js— YAML + manifest generator for MATLABtests/fixtures/v2_*.yaml— V2 YAML test fixtures from maDisplayToolsdocs/experiment-designer-v06-testing.md— Manual testing checklistdocs/protocol-roundtrip-testing.md— Roundtrip testing architecture- GitHub Issues: #33, #53–60 closed (tooltips, bugs, undo/redo, connect cmd, param backfill)
Automated: node tests/test-protocol-roundtrip.js — 137 checks across 10 suites (v1+v2 parse/generate roundtrips + bug #55-58 regressions). Run after any change to protocol-yaml.js or the data model.
Manual testing checklist (import a v2 YAML like full_experiment_test.yaml to populate):
Phase independence:
- Edit pretrial commands → intertrial/posttrial must NOT change
- Each phase independently editable with different commands
Plugin commands:
- Adding
setRedLEDPowershows power, panel_num, pattern fields with defaults - Adding
turnOffLEDshows NO parameter fields - Plugin params appear in exported YAML
YAML export:
- Paths use single quotes (check with Windows-style path like
C:\Users\lab\patterns) -
criticaldoes NOT appear in backlight config - Camera ip/port/frame_rate/video_format NOT in export unless user-set
- Backlight port NOT in export unless user-set
Table view:
- All fields editable inline (duration, mode, FR/gain, pattern, plugin params)
- Add Command dropdown works for conditions and phases
- Up/down arrows reorder commands
- Up/down arrows in section headers reorder conditions
- X button removes conditions (only when >1)
- Expand All / Collapse All buttons work
Filmstrip + lane view:
- Clicking a block switches to Commands tab
- Lane view labels stay fixed on left during scroll
- Lane SVG aligns with blocks above (check across zoom levels)
- White separator lines in gaps between blocks
- Zoom in/out and Fit update both blocks and lanes
- Selected block highlighted in lane view
Known issues from 2026-04-08 session:
- Last-block lane alignment: Minor drift may still be visible at certain zoom levels on the last block. Root cause was
totalWidthnot accounting for min-width clamping (Math.max(48, ...)) — fixed, but may need further tuning with complex experiments. - GitHub Pages caching: After pushing, the arena dropdown appeared empty until hard refresh (
Cmd+Shift+R). This is the standard ES6 module caching issue — always hard refresh after deploy. - CSS
content: '\u2807'escape: Unicode escapes in CSScontentproperty rendered as literal text on some browsers. Fixed by using the literal character⠇instead. - Filmstrip delete button removed: Was too easy to accidentally delete a condition when double-clicking to select. Conditions can now only be removed from the Table view or the Commands tab's "Remove" button.
- Phase shallow copy bug:
{ ...DEFAULT_PHASE }shares thecommandsarray reference between pretrial/intertrial/posttrial. Must useJSON.parse(JSON.stringify(...))for deep clone. - Visual editor first-click alert: The table view's
.btn-row-deleteclass overlapped with visual editor delete buttons, causing a "Phase 3" alert on first click. Fixed by giving visual editor buttons a separate.cmd-delete-btnclass.
Before starting work, assess whether the request is a big project or a small task:
Small tasks (no formal planning needed):
- Single file changes
- Bug fixes with clear scope
- Adding a single feature to existing code
- Documentation updates
- Running tests or validation
Big projects (use EnterPlanMode):
- Multi-file changes across different modules
- New features requiring architecture decisions
- Implementing multiple related features
- Refactoring that touches >3 files
- Work estimated to take >30 minutes of focused effort
When a big project involves multiple independent features, evaluate whether they would benefit from parallelization:
When to parallelize:
- Features don't depend on each other's output
- Each feature can be tested independently
- Different expertise areas (e.g., frontend + backend + tests)
How to coordinate parallel agents:
- Create a shared plan document outlining the work split
- Launch agents simultaneously with clear scope boundaries
- Use the TodoWrite tool to track progress across agents
- Merge results and resolve any integration issues
Example: Implementing 3 independent features
Agent 1: Implement feature A (generator module)
Agent 2: Implement feature B (viewer module)
Agent 3: Write tests for both A and B
When starting a new task or entering plan mode, consider using parallel Explore agents to efficiently understand the codebase:
- Use 1 agent when the task is isolated to known files or making a small targeted change
- Use 2-3 agents in parallel when:
- The scope is uncertain or spans multiple areas
- You need to understand existing patterns before planning
- Multiple subsystems are involved (e.g., parser + encoder + viewer)
Example parallel exploration:
Agent 1: Search for existing pattern generation implementations
Agent 2: Explore viewer integration patterns
Agent 3: Investigate testing/validation approaches
Launch all agents in a single message with multiple Task tool calls for maximum parallelism. This significantly reduces planning time for complex tasks.
When the user says "close session", enter plan mode and prepare documentation updates:
-
Summarize session work
- List files modified/created
- Describe features added, bugs fixed, or refactors completed
-
Review CLAUDE.md for updates
- New testing patterns or best practices discovered
- Browser quirks or gotchas encountered
- New utility functions that should be documented
- Any corrections to existing documentation
-
Review maDisplayTools docs/G4G6_ROADMAP.md for updates
- This is the unified roadmap for both MATLAB and web tools
- Add a one-line entry to the changelog table in
G4G6_ROADMAP.md - Append detailed session notes to
G4G6_ROADMAP_SESSIONS.md - Mark completed tasks as done
- Add any new issues discovered during the session
- Note deferred items or future improvements identified
-
Present plan for approval
- Show all proposed documentation changes
- Wait for user approval before making edits
-
After approval
- Make the documentation updates
- Optionally offer to create a git commit summarizing the session