Commit Graph

806 Commits

Author SHA1 Message Date
Alex P dc0ccf9af5 Fix HIGH priority issues in audio system
Addressed 5 HIGH priority issues identified in code review:

HIGH #12: safe_alsa_open validation (audio.c:314-327)
- Added validation for snd_pcm_nonblock() return value
- Properly close handle and return error if blocking mode fails
- Prevents silent failures when switching to blocking mode

HIGH #13: WebRTC write failure handling (relay.go:74-147)
- Track consecutive WebRTC write failures in OutputRelay
- Reconnect source after 50 consecutive failures
- Throttle warning logs (first failure + every 10th)
- Prevents silent audio degradation from persistent write errors

HIGH #14: Opus packet size validation (audio.c:1024-1027)
- Moved validation before mutex acquisition
- Reduces unnecessary lock contention for invalid packets
- Validates opus_buf, opus_size bounds before hot path

HIGH #15: FEC recovery validation (audio.c:1050-1071)
- Added detailed logging for FEC usage
- Log warnings when decode fails and FEC is attempted
- Log info/error messages for FEC success/failure
- Log warning when FEC returns 0 frames (silence)
- Improves debuggability of packet loss scenarios

Comment accuracy fixes (audio.c:63-67, 149-150, 164-165)
- Clarified RFC 7587 comment: RTP clock rate vs codec sample rate
- Explained why sr/fs parameters are ignored
- Added context about SpeexDSP resampling

Channel map validation (audio.c:572-579)
- Added validation for unexpected channel counts
- Check for SND_CHMAP_UNKNOWN positions
- Prevents crashes from malformed channel map data
2025-11-24 20:30:57 +02:00
Alex P 0f8b368427 Fix critical error handling and race conditions in audio system
Address 5 critical issues found in comprehensive code review:

1. Opus Encoder Configuration Failures (CRITICAL)
   - Split encoder settings into critical vs non-critical
   - Critical settings (bitrate, VBR, FEC) now fail initialization on error
   - Non-critical settings (complexity, DTX) log warnings but continue
   - Prevents silent audio quality degradation from misconfigured encoder

2. V4L2 Sample Rate Detection Error Reporting (CRITICAL)
   - Add specific error messages for different failure modes
   - Distinguish permission errors, device not found, and no signal
   - Validate detected sample rates are in reasonable range (8-192kHz)
   - Improves debuggability when HDMI audio detection fails

3. Mutex Handling in ALSA Error Recovery (CRITICAL)
   - Refactor handle_alsa_error() to NEVER unlock mutex internally
   - Caller now always responsible for unlocking after checking return
   - Eliminates complex mutex ownership semantics that caused deadlocks
   - Consistent lock/unlock patterns prevent double-unlock bugs

4. Async Audio Start Error Propagation (CRITICAL)
   - Make SetAudioOutputEnabled/SetAudioInputEnabled synchronous
   - Add 5-second timeout for audio initialization
   - Return errors to caller instead of only logging
   - Revert state on failure to maintain consistency
   - Users now get immediate feedback if audio fails to start

5. CgoSource Race Condition (CRITICAL)
   - Hold c.mu mutex during C function calls in ReadMessage/WriteMessage
   - Prevents use-after-free when Disconnect() called concurrently
   - Lock order (c.mu -> capture_mutex) is consistent, no deadlock risk
   - Fixes potential crash from accessing freed ALSA/codec resources

These changes eliminate silent failures, improve error visibility, and
prevent race conditions that could cause crashes or audio degradation.
2025-11-24 20:23:14 +02:00
Alex P 81ff87fb66 Address code review feedback and optimize stereo channel swapping
Simplify channel swap detection and improve performance based on
IDisposable's review comments:

- Pass bool pointer directly instead of encoding in bit flag
- Remove redundant channel count check (already verified earlier)
- Use ARM NEON SIMD for channel swapping (4x faster)
- Process 4 frames (8 samples) per iteration with vrev32q_s16

These changes improve code clarity and boost channel swap performance
by ~4x using vectorized operations.
2025-11-24 20:05:54 +02:00
Alex P db2dc88250 Fix HDMI audio sample rate detection for non-48kHz sources
Query TC358743 HDMI receiver for detected audio sample rate before
initializing ALSA capture device. This fixes distortion issues when
HDMI sources send 44.1kHz audio (e.g., Armbian SBC) instead of 48kHz.

Previously, the code always requested 48kHz from ALSA, but in I2S slave
mode, the RV1106 I2S controller receives whatever clock rate the TC358743
master provides. This caused a sample rate mismatch where ALSA thought
it was 48kHz but hardware was actually running at 44.1kHz, resulting in
incorrect SpeexDSP resampling and audio distortion.

Changes:
- Add V4L2 ioctl to query TC358743's audio_sampling_rate control
- Use detected rate when configuring ALSA (falls back to 48kHz if unavailable)
- SpeexDSP resampler now gets correct input rate (44.1k, 48k, etc.)
- Supports all HDMI audio sample rates: 32k, 44.1k, 48k, 88.2k, 96k, etc.
2025-11-24 19:25:27 +02:00
Alex P 818a2ca050 Update DefaultEDID to force 60Hz video output
Replace EDID with version that only advertises 60Hz timing modes
(1920x1080@60Hz and 1280x720@60Hz), removing the 1920x1080@50Hz mode
that was causing HDMI sources to prefer 50fps over 60fps output.
2025-11-24 18:06:15 +02:00
Alex P d4bd9dbc33 Improve audio capture reliability and remove soft-clipping
Refactor audio processing to enhance stability and code clarity:
- Remove soft-clipping from audio capture pipeline
- Fix hardware frame size calculation for variable sample rates
- Add comprehensive error codes for audio initialization failures
- Clear stop flags after cleanup to prevent initialization deadlocks
- Improve mutex handling during device initialization
- Simplify constant validation and remove redundant comments
- Add DevPod setup instructions for Apple Silicon users
- Enforce Go cache clearing in dev_deploy.sh for CGO reliability

These changes improve audio capture stability when switching between
HDMI and USB audio sources, and fix race conditions during device
initialization and teardown.
2025-11-24 14:58:37 +02:00
Alex P 5ed70083ec Add NULL checks before snd_pcm_close() calls
Defensive programming to prevent undefined behavior when closing ALSA
PCM handles. While the previous commit disabled assertions with -DNDEBUG,
adding explicit NULL checks ensures graceful handling even if handles are
unexpectedly NULL.

All error paths that call snd_pcm_close() now verify the handle is non-NULL
before closing, preventing potential crashes in edge cases.
2025-11-21 22:42:44 +02:00
Alex P fb6dbe53eb Disable assertions in ALSA static library build
Production builds should not include debug assertions. ALSA's assert()
calls cause aborts when internal invariants are violated, even for
recoverable error conditions.

The crash occurred when snd_pcm_close() was called with a NULL pointer,
triggering assertion failure at pcm.c:779 instead of graceful error
handling.

Stack trace:
  pcm.c:779: snd_pcm_close: Assertion `pcm' failed
  SIGABRT in jetkvm_audio_capture_init()

Adding -DNDEBUG disables all assert() calls in ALSA, Opus, and SpeexDSP
libraries for production robustness.
2025-11-21 22:40:30 +02:00
Alex P 60fcd91377 Update default EDID to 1920x1080@60Hz
Previous EDID was configured for 29.95Hz (30 FPS). Updated to standard
1920x1080@60Hz using CEA timing (148.5 MHz pixel clock, 2200x1125 total)
for smoother video capture.
2025-11-21 21:20:29 +02:00
Alex P 2ef6cb2d4d Improve soft-clipper with validation, error handling, and overflow protection
- Add input validation: NULL checks, bounds checking (max 7680 samples)
- Change return type to int for error propagation
- Use saturating NEON arithmetic (vqaddq_s16, vqsubq_s16) to prevent overflow
- Fix type consistency: use int16_t instead of short throughout
- Update documentation: precise threshold (0.9375 or 15/16), describe 4:1 compression
- Remove redundant clamping operations (mathematically proven unnecessary)
- Add stdbool.h include for bool type support
- Handle soft-clip errors at call site to prevent encoding corrupted audio
2025-11-21 21:17:40 +02:00
Alex P 9e6ffb34e3 Add ARM NEON soft-clipper to prevent clipping on sharp transients
Implements SIMD-optimized soft-clipping before Opus encoding to prevent
digital clipping distortion on sharp transient attacks (e.g., plastic cup
impacts, percussive sounds). Uses smooth saturation curve starting at
±30720 (~94% of max amplitude) to preserve audio quality while eliminating
crackles and pops.

Processes 8 samples per iteration using ARM NEON intrinsics for optimal
performance on the ARM Cortex-A7 platform.
2025-11-21 20:23:10 +02:00
Alex P 698978253b Remove redundant ALSA plugins (plug, rate) from build
We use direct hw: device access with SpeexDSP for resampling, so the
ALSA plugin layer (plug) and rate conversion plugin are not needed.
This reduces library size while maintaining all required functionality.
2025-11-21 20:02:53 +02:00
Alex P 3692cdae83 Fix reversed stereo channels by querying ALSA channel map
Query the ALSA channel map (snd_pcm_get_chmap) to detect hardware that
reports non-standard channel ordering (R,L instead of L,R). When detected,
swap channels after capture to ensure correct left/right positioning.

This properly handles hardware quirks (like TC358743 HDMI audio) without
hardcoding device names, making the solution portable and correct.
2025-11-21 19:28:30 +02:00
Alex P 72966389d9 Update default EDID to JetKVM 1920x1080@60Hz with audio support
Changes default EDID to JetKVM branded display configuration:
- Display name: JetKVM
- Full HD resolution (1920x1080@60Hz)
- Digital RGB 8-bit color support
- CEA-861 extension with PCM audio capability
- Broader compatibility with source devices

This EDID declares audio support which may improve HDMI audio detection
on certain source hardware.
2025-11-21 17:35:03 +02:00
Alex P ac568c7bbf Fix HDMI hotplug crash by releasing mutex during blocking ALSA I/O
When HDMI is unplugged during active audio capture, the blocking
snd_pcm_readi() call was holding the mutex, preventing clean shutdown.
This caused snd_pcm_drop() to race with the blocking read, leading to
undefined behavior and crashes.

Solution mirrors PiKVM's approach:
- Release mutex before snd_pcm_readi()/snd_pcm_writei()
- Reacquire mutex after I/O completes
- Verify handle and stop flag before proceeding

This allows snd_pcm_drop() to immediately abort pending I/O when the
device is closed, ensuring clean shutdown during HDMI hotplug events.
2025-11-21 16:50:35 +02:00
Alex P 9d86b02e66 Integrate libspeexdsp for high-quality audio resampling
Replace ALSA plugin layer resampling with libspeexdsp for improved audio
quality and reliability. This implementation uses direct hardware access
(hw:) instead of ALSA plugins (plughw:) and handles sample rate conversion
with SpeexDSP's high-quality sinc-based resampler.

Key changes:
- Add libspeexdsp 1.2.1 with ARM NEON optimizations to build dependencies
- Switch from plughw: to hw: device access for lower latency
- Implement conditional resampling (only when hardware rate ≠ 48kHz)
- Use SPEEX_RESAMPLER_QUALITY_DESKTOP for high-quality interpolation
- Add automatic audio dependency building in dev_deploy.sh

Quality improvements:
- Fix race condition in resampler cleanup with mutex protection
- Fix memory leak on resampler re-initialization
- Add buffer overflow validation (3840 frame limit for 192kHz)
- Improve error logging for resampling, encoding, and ALSA configuration
- Simplify code structure while maintaining all functionality

Technical details:
- Hardware negotiates actual sample rate (e.g., HDMI may vary)
- SpeexDSP converts hardware rate → 48kHz for Opus encoding
- USB Audio Gadget hardcoded to 48kHz (no resampling overhead)
- Static buffer allocation for zero allocation in hot path
- WebRTC requires 48kHz RTP clock rate per RFC 7587
2025-11-21 16:29:02 +02:00
Alex P 0be9dbcc6c Enable ALSA rate resampling for configurable audio sample rates
Changes the audio subsystem from hw: (direct hardware access) to plughw:
(plugin layer with rate conversion) to enable configurable sample rates.

Changes:
- Update ALSA build to include plug,rate,linear,copy plugins
- Change device names from hw: to plughw: in C and Go code
- Remove 48kHz hardcoding for HDMI audio output
- Keep USB at 48kHz since hardware is fixed at that rate
- Update all comments to reflect plughw usage

Technical details:
- hw: devices bypass all ALSA plugins and require exact hardware rate match
- plughw: devices enable the ALSA plugin layer for automatic rate conversion
- Hardware still receives at native rate (48kHz), resampling happens in userspace
- HDMI can now use 8k/12k/16k/24k/48kHz, USB remains at 48kHz
- NEON-optimized resampling provides good performance on Cortex-A7

Requires rebuilding ALSA library with updated plugin configuration.
2025-11-21 02:22:22 +02:00
Alex P 2040db6094 Fix USB Audio Gadget sample rate constraints
USB Audio Gadget (hw:1,0) hardware only supports 48kHz for both capture
and playback due to configfs p_srate/c_srate being hardcoded. This commit
ensures both audio paths respect this hardware limitation:

- Output path: Force 48kHz when using hw:1,0, allow configurable rates for HDMI
- Input path: Always use 48kHz regardless of UI configuration
- Calculate frame size dynamically based on actual sample rate used

Also removes redundant comments that don't add debugging or maintainability value.
2025-11-21 01:56:56 +02:00
Alex P 57baa14ee6 Fix frame size calculation for configurable sample rates
Calculate frame size dynamically based on sample rate (20ms frames):
- 48kHz: 960 samples
- 24kHz: 480 samples
- 16kHz: 320 samples
- 12kHz: 240 samples
- 8kHz: 160 samples

Previously hardcoded to 960, causing decoder init failures at non-48kHz rates
2025-11-21 01:44:03 +02:00
Alex P 1dfb4ab77f Make audio sample rate user-configurable
- Add sample rate dropdown in UI with Opus-supported rates (8k/12k/16k/24k/48kHz)
- Add sampleRate parameter to setAudioConfig RPC handler
- Validate sample rate is one of the 5 Opus-compatible values
- Configuration takes effect on next audio restart (Apply button)
2025-11-21 01:38:00 +02:00
Alex P 584b9fe3bf Fix comment inaccuracies and restore lint targets
- Clarify sample rate is configurable (8k/12k/16k/24k/48k), not fixed at 48kHz
- Expand mutex comment to include full lifecycle protection scope
- Document that ALSA playback init fails immediately with no fallback
- Add async behavior documentation to audio enable/restart functions
- Restore build_audio_deps target lost during merge
- Restore lint-fix, lint-go, lint-ui Makefile targets
- Fix variable alignment per linter
2025-11-21 01:31:33 +02:00
Alex P 4001ef651f Fix silent failures and improve documentation
- Remove silent fallback to ALSA 'default' device on playback init failure
- Return error from SetAudioOutputSource for invalid source values
- Fix misleading comment about mutex scope in C audio code
- Clarify inputSourceMutex purpose for WebRTC packet serialization
2025-11-21 00:57:31 +02:00
Alex P 5f7c90649a Simplify audio configuration and error handling
- Replace helper function in getAudioConfig with explicit validation
- Consolidate audio default application in LoadConfig
- Streamline relay retry logic with inline conditions
- Extract closeFile and openHidFile helpers in USB gadget
- Simplify setPendingInputTrack pointer handling
- Improve error handling clarity in startAudio and updateUsbRelatedConfig
- Clean up processInputPacket mutex usage
2025-11-21 00:54:32 +02:00
Alex P 3ed663b4d1 Enable ALSA software resampling for hw: devices
Use snd_pcm_hw_params_set_rate_resample(1) to enable ALSA's rate plugin,
which provides software resampling even with hw: device interface.

This fixes audio distortion when HDMI sources output non-48kHz rates
(e.g., 44.1kHz from SBCs). ALSA now automatically resamples any input
rate to the configured 48kHz that Opus expects.

The rate plugin is available because ALSA is compiled with
--with-pcm-plugins=rate in install_audio_deps.sh
2025-11-21 00:32:10 +02:00
Alex P 3cd5bdd16c Make RestartAudioOutput async to prevent RPC hanging
Consistent with other audio toggle functions, restart now happens
asynchronously to avoid blocking the RPC caller.
2025-11-21 00:15:38 +02:00
Alex P edd06e2346 Fix UI hanging when toggling audio output enable/disable
Make audio start asynchronous to prevent blocking the RPC response.
Previously, enabling audio would block until ALSA initialization completed,
which can take 30-60 seconds for HDMI audio due to TC358743 hardware.

This also fixes the -1 decode errors that occurred when packets arrived
during the synchronous restart window.

Matches the existing async pattern used in SetAudioOutputSource().
2025-11-21 00:10:10 +02:00
Alex P a6cbf20f66 Fix audio capture to force Opus-compatible sample rates
ALSA now forces the configured sample rate (default 48kHz) instead of
auto-detecting the source rate. This prevents Opus encoder initialization
failures when HDMI sources output 44.1kHz audio, which Opus doesn't support.

Changes:
- Use snd_pcm_hw_params_set_rate() to force exact rate (48kHz by default)
- ALSA performs software resampling if hardware rate differs
- Update valid rates to Opus-compatible only (8k, 12k, 16k, 24k, 48k)
- Remove auto-adaptation logic that caused Opus failures with 44.1kHz

This ensures audio capture works reliably with any HDMI source rate.
2025-11-21 00:02:29 +02:00
Alex P fba4eabf3b Merge branch 'dev' into feat/audio-support
Integrated latest dev branch changes including:
- Native process refactoring with gRPC architecture
- OTA update system refactor with new component-based updates
- Updated build system and dependencies
- UI improvements and bug fixes

Post-merge fixes applied:
- Remove duplicate OTA RPC function declarations (now in ota.go)
- Fix GetDefaultEDID reference to use native.DefaultEDID constant
- Fix IsUpdatePending to use otaState.IsUpdatePending() method
- Add missing OTA RPC handler registrations for new update system

All audio functionality from feat/audio-support preserved.
All dev branch functionality preserved.
2025-11-20 23:47:44 +02:00
Siyuan 9c4a9e144f chore: bump version to 0.5.0-dev 2025-11-20 17:38:04 +00:00
Adam Shiervani 3652c4ceea
fix: enhance reboot state management with health check and redirect options (#994) 2025-11-20 18:25:21 +01:00
Adam Shiervani ba8a169ef2
refactor: remove unused state for log download in FailSafeModeOverlay (#993) 2025-11-20 17:48:56 +01:00
Adam Shiervani 661110cdb5
fix: update downgrade navigation parameter (#992) 2025-11-20 17:33:34 +01:00
Aveline d34d01c4b3
feat: add failsafe reason for video max restart attempts reached (#991) 2025-11-20 17:32:54 +01:00
Aveline 641b03199e
chore: make udhcpc the default DHCP client (#990) 2025-11-20 16:48:07 +01:00
Adam Shiervani 0a09d9e8bf
feat: add hostname change detection and reboot requirement in network settings (#989) 2025-11-20 16:36:02 +01:00
Adam Shiervani 0952c6abf2
chore: use en by default (#988) 2025-11-20 16:34:02 +01:00
Adam Shiervani 85eb4babdf
feat: handle grpc events (#986)
Co-authored-by: Siyuan <siyuan@buildjet.com>
2025-11-20 16:07:50 +01:00
Adam Shiervani 07935add15
refactor: remove redundant initialization of native and display components in Main function (#987) 2025-11-20 14:56:17 +01:00
Nitish Agarwal 78cef12c97
fix: mobile viewport cropping on video element (#985) 2025-11-20 13:55:24 +01:00
Alex P fe4fb33561 Fix misleading comment and incorrect Go terminology
- config.go: Clarify that package-level defaults are for efficiency, not temporary
- jsonrpc.go: Correct "thread" to "goroutine" (Go uses goroutines, not threads)

After thorough review of all reported issues:
- processInputPacket early nil check is correct double-checked locking (not a race)
- Async audio source switching is intentional design for 30-60s HDMI init time
- TypeScript JSON.parse is safe (backend controls data, React catches errors)

Only actual terminology issues needed fixing.
2025-11-19 17:28:31 +02:00
Alex P 41604626cf Remove inaccurate and redundant comments
Fix comment inaccuracies:
- NEON: Change from "required" to "optimizations" (scalar fallback exists)
- capture_channels: Remove "always stereo" claim (configurable via API)

Remove redundant comments that duplicate code:
- "Apply boolean flags directly" (line duplicates what code does)
- Mutex comments in setAudioTrack (visible from code structure)

Also remove accidentally committed review artifacts (PR_REVIEW.md, jetkvm_default_edid.bin).
2025-11-19 17:19:46 +02:00
Alex P 3897a61729 Fix critical comment inaccuracies in audio code
- Fix validateAndApply comment to clarify it returns values, doesn't apply them
- Correct capture_channels comment about hardware capabilities
- Fix opus_packet_loss_perc default value from 0 to 20 (matches backend default)
- Fix handle_alsa_error return value documentation (return 0 also unlocks mutex)
2025-11-19 17:19:46 +02:00
Alex P 1d570a8cbf Fix critical audio race conditions and improve reliability
- Replace volatile with C11 atomics for proper ARM memory barriers
- Fix race condition in audio source swapping (swap to nil before cleanup)
- Prevent double-close of ALSA handles via atomic ownership claim
- Add exponential backoff with 10-retry circuit breaker to prevent infinite loops
- Improve error propagation to report dual failures
- Add defensive null checks for concurrent access safety
- Simplify UI error handling with helper functions
- Fix TypeScript compilation error in packet loss dropdown
2025-11-19 17:19:46 +02:00
Aveline 3fcd5e7def
feat: move native to a separate process, again (#964) 2025-11-19 16:02:37 +01:00
Aveline 752fb55799
refactor: OTA (#912)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Adam Shiervani <adam.shiervani@gmail.com>
2025-11-19 15:20:59 +01:00
Alex P 8d69780061 Update audio source switch notification to indicate 30-60s delay
The HDMI audio device can take 30-60 seconds to initialize due to
TC358743 hardware characteristics. Updated success notification in
all languages to inform users that audio will start shortly.
2025-11-19 14:23:26 +02:00
Alex P 7fc90b86a8 Make audio source switching instant with async initialization
When switching audio output source between HDMI and USB, the HDMI
audio device (hw:0,0) can take 18-31 seconds to initialize due to
hardware characteristics of the TC358743 chip. This caused the UI
to freeze during source changes.

Changes:
- Move startAudio() to background goroutine in SetAudioOutputSource
- Move SaveConfig() to background goroutine to avoid blocking on disk I/O
- Return immediately after updating in-memory config
- Audio will initialize in background while UI remains responsive

The in-memory config is updated synchronously so subsequent calls
see the new source immediately. Both async operations are protected
by their respective mutexes (audioMutex, configLock).
2025-11-19 14:12:12 +02:00
Alex P ee23e3bf22 Refactor audio subsystem for improved maintainability
Changes:
- Consolidate duplicate stop logic into helper functions
- Fix RPC getAudioConfig to return actual runtime values instead of
  inconsistent defaults (bitrate was returning 128 vs actual 192)
- Improve setAudioTrack mutex handling to eliminate nested locking
- Simplify ALSA error retry logic by reorganizing conditional branches
- Split CGO Connect() into separate input/output methods for clarity
- Use map lookup for sample rate validation instead of long if-chain
- Add inline comments documenting validation steps

All changes preserve existing functionality while reducing code
duplication and improving readability. Tested with both HDMI and
USB audio sources.
2025-11-19 13:42:51 +02:00
Alex P 0dbf2dfda9 Update default EDID for improved compatibility
Changes:
- Switch manufacturer ID from DEL to LNX for better open-source alignment
- Add dual audio sample rate support (44.1kHz + 48kHz) to eliminate
  resampling quality loss on MacBooks and other devices
- Declare 640×480p60 in established timings and CEA video block (VIC-1)
- Use 1920×1200p60 as secondary timing to meet validator requirements
- Fix white point coordinates to D65 standard (0.313, 0.329)

This EDID now passes edidtool.com validation and provides universal
compatibility across macOS, Linux, and Windows systems.
2025-11-19 12:31:48 +02:00
Alex P 0168fcbdbd Make config.EdidString the single source of truth for EDID
- Set DefaultEDID in config defaults instead of empty string
- Pass config EDID to Native.Start() to fix initialization race condition
- Update DefaultEDID to MacBook-compatible value (2ch, 48kHz, 16/20/24-bit)
- Add getDefaultEDID RPC endpoint for UI to fetch backend constant
- Update UI to dynamically fetch default EDID instead of hardcoding
- Remove all EDID fallback logic now that config always has a value
- Simplify rpcGetEDID to return config value directly

This ensures the configured EDID is used from startup and eliminates
sync issues between backend constant, config, and UI.
2025-11-19 11:08:32 +02:00