- InputBar: QProgressBar with gradient (green→yellow→red) in recording overlay
- updateRecordingUI: computes dB from peakLevel, updates bar every 100ms
- showRecordOverlay: resets bar to 0 on new recording
- Waveform bars: linear→logarithmic (dB mapping -60..0dB)
- Colors: idle gray visible (130 alpha), played blue, unplayed dim blue
- Min bar height: 1→2px
- Duration: fallback to MsgVoiceDurationRole when not playing (was 0:00/0:00)
- Debug log: wfN, width, first value, dB, barH
- AudioRecorder: sliding window RMS (avg 5 frames), waveformLevels() getter
- InputBar: downsample RMS to 100 samples, encode as wf0,wf1,... in textData
- MessageList: parse 3rd pipe-separated field as waveform, store in MsgVoiceWaveformRole
- MessageDelegate: new voice layout (16px play btn + 25px waveform + 3px progress bar)
- MessageDelegate: waveform bars colored by progress (blue before, gray after)
- Progress bar integrated at bottom of waveform area as colored fill
- stopCurrentPcm(): only ma_sound_stop, no delete/uninit in runtime
- playRawPcm(): just stop old sound, create new objects (old ones leak until shutdown)
- shutdown(): cleanup accumulated ma_sound + ma_audio_buffer after engine uninit
- root cause: deleting audio objects from GUI thread while miniaudio worker reads them
- SoundManager: pauseCurrentPcm()/resumeCurrentPcm() — stop/start without destroy
- VoicePlayState: added paused/pausedPosSec fields
- editorEvent: 3-state logic — playing→pause, paused→resume, stopped→new
- Different file click: stops old with full cleanup, plays new from start
- Timer: skips paused items, full cleanup on auto-complete
- SoundManager: added stopCurrentPcm() for pause/stop of PCM playback
- messagedelegate: call stopCurrentPcm on pause, new play, and timer complete
- messagelist: skip old double-nested JSON messages (raw starts with '{')
- sound_manager.h: void* for ma_audio_buffer to avoid header dependency
- chatview: QListView::mousePressEvent(event) instead of event->accept()
(Qt needs base class press to set pressedIndex for editorEvent to fire)
- messagedelegate: GUI_DEBUG replaces qDebug (writes to log file)
- voiceplayback: DEBUG_DEBUG/DEBUG_WARN + debug_config.h include
- voicemessageencoder: same debug migration
- inputbar: GUI_INFO replaces qDebug
- messagelist: DEBUG_DEBUG/DEBUG_WARN replaces qDebug/qWarning
- db_manager: getMessagesSince() now parses m.contentType from JSON (was missing)
- messagelist: setVoiceMessageRoles uses data directly as payload, not re-parsed as JSON
(data is already the 'd' field value extracted by db_manager)
- req->data now stores 'filename|duration' instead of full nested JSON
- chat_core_submit_message builds the JSON with d='filename|duration'
- setVoiceMessageRoles splits d field by '|' to extract filename and duration
lottie_animation_from_data uses 'key' as a cache key for parsed models.
All LottieIcon instances were created with key="", causing rlottie's
model cache to return the first loaded animation for all subsequent
requests. Only visible on Windows where rlottie cache is enabled by
default.
Fix: pass unique animPath as key to each LottieIcon constructor.
Also replaced fprintf(stderr) diag with GUI_INFO logging via
AnimTimer::logRegisteredIcons() in debug_ui.h.
- Add last_msg_at column to channels table
- Update last_msg_at on every message insert (send/receive)
- Parameterize getChannels() with ChannelSortMode enum (Alphabetical/Recent)
- Add getChannelLastMessage() for preview text in channel list
- Add sorting combo to Profile settings page (stored in ui_state)
- Reload channels on message receive when sort is recent
- _get_level_hashes: use separate SQL without prefix64<=? for level 0.
UINT64_MAX cast to sqlite3_int64 becomes -1, making prefix64<=-1 always
false for positive prefixes → bitmap always 0 → sync never exchanges data.
- member_sync_init: add _rebuild_all_trees to scan all peers_* tables and
call merkle_sync_recompute_path for each member. Ensures merkle_tree_hash
has entries for members added before merkle sync existed.
- Add 2-byte dlen prefix to terminal BATCH/HASHES data so receiver knows
exact byte count — fixes rem=0 bug that truncated multi-item BATCHes.
- Fix synthetic HASHES prefix_bytes: use merkle_sync_prefix_bytes(next_lvl)
for both declaration and _prefix_write, not current level's pb_i.
- Fix initiator never getting _session_done: responder sends confirmation
HASHES(is_data=1,level=0) before _session_done so initiator's done_cb fires.
- Add level==0 guard in _member_get_items/_member_update_bucket_hash
to prevent SQL queries with bogus mask on virtual root level.
- _get_level_hashes: remove (0x1F << parent_shift) from range_end so
bitmap only includes children of the actual parent prefix (not all
parents). Add level 0 virtual root that queries all level-1 entries.
- merkle_sync_start: send HASHES at level 0 (virtual root) instead of
level 1/prefix 0. Root-level REQUEST prefix reconstruction now uses
the correct level-1 prefix from DB.
- _handle_batch: use merkle_sync_prefix_bytes(next_lvl) for synthetic
child HASHES instead of current level's pb_i (could be wrong size).
- Remove timeout/retry mechanism: delete _session_timeout_cb,
_session_start_timer, timer/retries fields. ETCP provides reliable
transport — no need for application-layer retries.
_prefix_write was writing the least significant pb bytes of the 64-bit
prefix, but the protocol expects the most significant pb bytes (bits
63..64-pb*8). For pb<=4 these ranges are disjoint → wire always got
zeros. _prefix_read also lacked the final left-shift to align the
result at bit 63.
This caused all level>=2 merkle sync requests/batches to receive
prefix=0 instead of the real prefix, making get_items/update_bucket_hash
find zero members → handle_batch len=7 with no data → sync completed
without actually exchanging member data.
После cascade_from в db_handle_send_data оповещаем ВСЕХ пиров
с synced_pos >= fix_from о том что цепочка изменилась — откатываем
их synced_pos. Это устраняет неэффективность конкурентных sync:
раньше только пир-отправитель обновлялся, остальные видели устаревшие
хеши и обнаруживали ложное расхождение на следующем цикле.