- Restore focus to main canvas when closing the matrix modal to support keyboard navigation
- Update aria-label to 'Copied' on copy button success to support screen readers
- Conditionally restore original aria-label to prevent overwriting on error
Co-authored-by: AEmotionStudio <163354043+AEmotionStudio@users.noreply.github.com>
- Restore focus to main canvas when closing the matrix modal to support keyboard navigation
- Update aria-label to 'Copied' on copy button success to support screen readers
- Restore original aria-label after timeout
Co-authored-by: AEmotionStudio <163354043+AEmotionStudio@users.noreply.github.com>
- Add api_routes.py with POST /shader_noise_ksampler/save_params endpoint
- Update frontend to use fetch() API instead of browser download
- Register API routes with PromptServer in __init__.py
- Add CHANGELOG.md with version history
- Update README.md with navigation links and version 1.3.0
- Video comparer optimizations for memory efficiency
- Remove duplicate VideoComparer class from root (only nodes/ version used)
- Fix memory threshold check order: check 16GB before 12GB so force cleanup can trigger
- Fix metadata key calculation using proper key mapping instead of broken string replace
- Fix absolute imports in shaders/ to use relative paths
- Correct indentation in shader files
- Refactor video_comparer.py and nodes/video_comparer.py
- Add new example workflows
- Update gitignore and package.json
Remove export keyword from TITLE_GRADIENT_COLORS since it's only used
internally by createTitleGradient() and never imported elsewhere.
All 85 tests pass.
- Simplify redundant shimmer conditionals:
Both branches called calculateShimmerPosition(1.0) identically.
Now unconditionally compute shimmer, only cache lastTime conditionally.
- Use TITLE_CORNER_RADIUS constant:
Replaced local 'const cornerRadius = 8' in both golden_eyeball.ts and
gradient_title.ts with the shared TITLE_CORNER_RADIUS constant.
All 85 tests pass.
- Remove redundant Side-by-Side case in computeSize():
The case set height = Math.max(500, width) which was identical to the
default initialization, so it was effectively dead code.
- Extract resetButtonTimeout helper in copyCodeSection():
Both success and error handlers had identical timeout reset logic.
Consolidated into a single helper function, reducing ~25 lines.
All 85 tests pass.
- Restore getExtraMenuOptions context menu in advanced_comparer.ts:
- Layout Mode submenu with all 8 comparison modes
- Reset to Default Size option
- Consistent with video_comparer.ts implementation
- Create COMPARER_MODES constant to eliminate duplicate arrays:
- Used in @comparer_mode property definition
- Used in layoutWidget combo values
- Used in context menu submenu
- Share AnimationCache type in gradient_title.ts:
- Import AnimationCache from golden_eyeball.js
- Remove duplicate RenderCache interface definition
- Note: gradient_title.ts intentionally has its own drawGradientTitle
for equation-styled titles (different visual from golden eyeball)
All 85 tests pass.
- Add .js extension to local imports for browser ES module compatibility
(./golden_eyeball → ./golden_eyeball.js, ./comfy_utils → ./comfy_utils.js)
- Remove unused api import from advanced_comparer.ts and video_comparer.ts
(no longer needed since imageDataToUrl moved to comfy_utils)
- Remove unreachable dead code in gradient_title.ts
(else branch was never reached due to early return for collapsed state)
All 85 tests pass.
- Remove export from drawGoldenEyeball (internal function only)
Makes it a private implementation detail in golden_eyeball.ts
- Remove unused CACHE properties in video_comparer.ts:
- titleCanvas, titleCtx, lastWidth, lastHeight, collapsed
- Now only keeps frameCount, frameSkip, lastTime needed by AnimationCache
- Remove dead cleanup code in onRemoved
- Extract shared imageDataToUrl utility:
- Create new comfy_utils.ts module
- Update advanced_comparer.ts to import shared function
- Update video_comparer.ts to import shared function
- Eliminates duplicated URL building logic
All 85 tests pass.
- Add calculateScaledDimensions() private method to AdvancedImageComparerWidget
- Replace duplicated aspect ratio calculation in 5 draw methods:
- drawImage
- drawImageSideBySide
- drawImageStacked
- drawImageInCell
- drawImageInPair
The helper calculates scaled dimensions to fit an image within usable area
while maintaining aspect ratio, eliminating ~15 lines of repeated logic.
Addresses PR #41 review comment about code duplication.
- Remove unused 'last_y' property from AdvancedImageComparerWidget class
(property was set but never read)
- Remove unused 'declare const app' statement in gradient_title.ts
- Simplify import handling: use direct 'import { app }' instead of alias
Files modified:
- web/src/advanced_comparer.ts
- web/src/gradient_title.ts
- Remove unreachable else branch in drawGradientTitle that could never execute
after early return for collapsed state (line 301-303)
- Remove hidden clearCacheButton debug widget that served no functional purpose
(lines 768-771, also from interface)
Files modified:
- web/src/advanced_comparer.ts
- shader_noise_ksampler.py: register_shader_generator now registers to both
the legacy SHADER_GENERATORS dict and the new centralized registry,
matching the behavior in __init__.py for backward compatibility
- core/transforms.py: resize_noise_spatial now considers INPUT tensor
dimensionality when selecting interpolation mode, not just target_size.
For 5D video tensors with 2D target_size (H, W), uses frame-by-frame
bilinear interpolation via new _resize_5d_tensor_spatial helper.
Previously would incorrectly select bilinear mode for 5D tensors.
Fixes two issues where shape matching/correction handles channel dimensions
before spatial dimensions, causing errors when both differ:
- blending.py: _match_tensor_shape and _match_video_tensor_shape now handle
spatial interpolation (Step 3) before channel copying (Step 4)
- sampler.py: _correct_frame_shape and _correct_image_shape now handle
spatial interpolation before channel copying
The issue was that when creating torch.zeros_like(target) for channel
adjustment, the target has target's spatial dimensions, but the source
slice being copied has source's spatial dimensions. If both differ,
the assignment fails with incompatible shapes.
By handling spatial interpolation first, the source tensor's spatial
dimensions match the target before any channel slicing occurs.
- Fix _wrap_legacy_generator to convert ShaderParams to dict using to_dict()
for legacy function compatibility (matches fallback_wrapper behavior)
- Fix get_shader_generator to return fallback_wrapper instead of None for
unknown shader types (consistent with shader_noise_ksampler.py behavior)
Resolves:
- Duplicate get_shader_generator implementations with inconsistent behavior
- Legacy wrapper doesn't convert ShaderParams to dict
Kept dev branch versions of shader files which use centralized
imports from utility modules (utils.color_utils, utils.shape_masks,
utils.noise_utils, core.params) rather than inline definitions.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add _detect_video_channel_dim helper using the same heuristics as
blending.py and sampler.py for consistent format detection
- Add optional channel_dim parameter to match_noise_shape for explicit
format specification when known
- Auto-detect B,C,F,H,W vs B,F,C,H,W format for video tensors when
channel_dim is not provided
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Remove debug print statements that would spam logs in production when
noise shape correction occurs during normal operation.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add ShaderParams.validate() calls when creating params in sampler.py
to ensure octaves are clamped and values are properly converted
- Fix _correct_noise_shape to properly handle B,F,C,H,W video format
by transposing before trilinear interpolation and handling frame count
- Wrap legacy generator functions in get_shader_generator to accept
both 'params' (new) and 'shader_params' (legacy) keyword arguments
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Align default video tensor format between sampler.py and blending.py
to B,F,C,H,W (channel_dim=2, frame_dim=1) for consistent handling
- Remove unused imports: SUPPORTED_BLEND_MODES, SUPPORTED_TRANSFORMS,
blend_noises, apply_noise_transform
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- get_shader_generator now wraps fallback to translate params -> shader_params
- Properly handles both class-based generators (with .generate) and plain functions
- Converts ShaderParams to dict for legacy generate_noise_tensor compatibility
- register_shader_generator now registers to both legacy dict and new registry
- Removed unused inner_class_lower variable in model_compat.py
- Changed generator_args key from 'shader_params' to 'params' to match BaseNoiseGenerator signature
- Added ShaderParams import and wrap params before passing to generators
## Fixed Issues
### High Severity
- Fixed incorrect video channel detection with default frame_dim_idx=-1
- Now uses heuristics to detect video tensor format from shape
- Handles both B,C,F,H,W and B,F,C,H,W formats correctly
### Medium Severity
- Fixed type mismatch between sampler and generator interface
- Generator args now wrap dict in ShaderParams for attribute-style access
- Fixes AttributeError when generators use params.shape_type etc.
- Fixed duplicate get_shader_generator implementations
- __init__.py now returns static generate method (consistent with shader_noise_ksampler.py)
- Both versions now have same behavior
### Low Severity
- Fixed missing exception handling for non-dict JSON files
- load_shader_params now validates JSON is a dict before use
- Catches ValueError, AttributeError, TypeError in addition to existing exceptions
## Fixed Issues
### High Severity
- Fixed missing relative import dot (shader_noise_ksampler.py)
- Changed from 'shaders.registry' to '.shaders.registry'
- Added 'shader_registry' public alias to registry.py
- Fixed generator returning class instead of callable (shader_noise_ksampler.py)
- get_shader_generator now returns generator_class.generate (the static method)
- Callers can now use generator_func(**kwargs) as expected
- Fixed parameter name mismatch (core/sampler.py)
- Changed 'shader_params' to 'params' in generator_args
- Matches BaseNoiseGenerator.generate() signature
## Fixed Issues
### High Severity
- Fixed overlay blend mode formula (core/blending.py)
- Now computes full overlay result then interpolates with base
- Returns base unchanged when strength=0
- Fixed hard_light blend mode formula (core/blending.py)
- Now computes full hard light result then interpolates with base
- term2 no longer produces incorrect values at strength=0
### Medium Severity
- Fixed difference blend mode formula (core/blending.py)
- Now interpolates between base and abs(base-shader)
- No longer adds difference to base
- Fixed duplicate empty SHADER_GENERATORS registry (shader_noise_ksampler.py)
- Now imports and uses centralized shader_registry
- get_shader_generator and register_shader_generator delegate to registry
- Fixed apply_common_postprocessing ignoring shape mask (shaders/base.py)
- Now calls apply_shape_mask with coords parameter as documented
## Fixed Issues
### High Severity
- Fixed class vs instance attribute mismatch in AdvancedImageComparer (nodes/comparer.py)
- compare_images now uses type(self) to access class attributes
- Ensures cache is properly shared with clear_cache() and get_cache_info()
- Fixed bilinear interpolation on 3D tensor crash (core/blending.py)
- Frame interpolation now reshapes to 4D (B*C, 1, F, H*W) before bilinear
### Medium Severity
- Fixed linear_increase yielding zero for single stage (core/sampler.py)
- Added min_factor=0.25 and special case for single stage
- Matches behavior of linear_decrease
- Fixed inconsistent zero-stage handling (core/sampler.py)
- calculate_step_points now returns [] for num_stages=0
- Matches calculate_stage_strengths and calculate_step_ranges
### Low Severity
- Fixed unconditional warning in _log_blend_stats (core/blending.py)
- Warning now gated behind debug_level >= 1
## Fixed Issues
### High Severity
- Fixed _match_video_tensor_shape returning zeros when channels match
- Now properly handles spatial dimension mismatches
- Returns correctly interpolated data instead of discarding
- Fixed bilinear interpolation failing on 5D video tensors
- _correct_noise_shape now uses trilinear for 5D tensors
- Added _interpolate_video_framewise as fallback
### Medium Severity
- Fixed video shape fallback discarding channel adjustments
- Now uses result tensor instead of original source
- Fixed video tensor format assumption (B,F,C,H,W vs B,C,F,H,W)
- Added _detect_video_format to determine tensor layout
- Handles both formats correctly in _match_video_tensor_shape
- Fixed channel dimension check using wrong index for videos
- Now detects channel dim dynamically based on tensor format
### Low Severity
- Removed sys.path modification in shaders/base.py
- Replaced with proper relative imports (from ..utils, from ..core)
Replaced runtime bitwise logic and branching in `TemporalCoherentNoiseGenerator.simplex_noise_3d` with `torch.nn.functional.embedding` and a precomputed gradient table.
This yields a ~1.28x speedup (43ms -> 33ms) for 3D noise generation on CPU while maintaining mathematical equivalence.
- Added OverflowError handling for seed conversion to prevent crashes with infinite inputs.
- Added explicit NaN/Inf checks for float parameters before clamping, as min/max operations propagate NaN.
- Added NaN/Inf checks for seeds to prevent invalid conversions.
- Extracted color schemes into a module-level `COLOR_SCHEMES` constant.
- Implemented `_get_color_tensors` with `lru_cache` to prevent redundant tensor creation.
- Replaced manual `if/elif` interpolation logic with `_interpolate_colors` using `torch.bucketize` and cached tensors.
- Cleaned up imports to robustly handle relative importing.
- Preserved exact logic for `viridis` and procedural schemes (plasma, rainbow).
💡 What:
- Exposed the `showToast` utility from `web/shader_params_save_button.js` as `window.showComfyToast`.
- Updated `web/matrix_button.js` to use `window.showComfyToast` for the "Copy Code" feedback.
- Added `:focus-visible` styles to the "Shader Matrix" modal buttons and tabs.
🎯 Why:
- To provide consistent and delightful feedback when copying code snippets.
- To improve accessibility for keyboard users by making focus states clearly visible.
📸 Before/After:
- Before: Copy button text changed to "Copied!". No visible focus ring on dark modal buttons.
- After: "Code copied to clipboard!" toast appears. Interactive elements have a bright purple focus ring.
♿ Accessibility:
- Added `:focus-visible` outline styles using the theme's accent color.
- Validate and clamp `seed` and `base_seed` parameters to the safe 64-bit integer range [-9e18, 9e18] to prevent PyTorch `RuntimeError` crashes.
- Enable and enforce clamping for float parameters (`scale`, `warp_strength`, etc.) to reasonable bounds (+/- 1,000,000) to prevent numerical instability or Infinity/NaN propagation.
- Update `shader_params_reader.py` to implement these checks in `validate_and_sanitize_params`.
💡 What: Replaced subtle button text changes and console logs with a Toast notification system for the "Save Shader Parameters" button.
🎯 Why: Users need clear, accessible feedback when saving parameters to know if the action succeeded or failed. The previous method was easy to miss.
📸 Verification: Verified via Playwright script `verify_toast.py` with mock environment. Screenshot confirms visibility and styling.
♿ Accessibility: Included `role='alert'` and high-contrast styling for the notifications.
🔧 Fixes:
- Addressed potential zombie process in verification script.
- Added fallback cleanup for toast elements if CSS transitions are disabled.
- Fixed event listener memory leak in toast fallback.
- Standardized fallback logic in test environment.
- Fixed inconsistent memory leak fix in verification test file.
- Removed temporary verification files.