feat(messages): optimistic UI — message appears instantly on Enter
Some checks are pending
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions

Instead of waiting for API response before showing the message,
the editor clears and message appears in DOM immediately. The API
call happens in the background. Real message ID replaces temp ID
in state for polling dedup.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maciej Pienczyn 2026-03-28 17:14:07 +01:00
parent 643a9cd94a
commit b2f24f02bc
2 changed files with 38 additions and 15 deletions

View File

@ -104,7 +104,7 @@ Na serwerze .249 nginx na porcie 80 przekierowuje na HTTPS. Flask/Gunicorn na po
git push origin master && git push inpi master
# 2. STAGING: Wdrożenie i test
ssh maciejpi@10.22.68.248 "cd /var/www/nordabiznes && sudo -u www-data git pull && sudo systemctl restart nordabiznes"
ssh maciejpi@10.22.68.248 "cd /var/www/nordabiznes && sudo -u www-data git pull && sudo systemctl reload nordabiznes"
# ⚠️ OBOWIĄZKOWO: Test manualny nowej funkcjonalności na staging!
# 3. PROD: Pull zmiany (DOPIERO PO WERYFIKACJI STAGING!)
@ -114,7 +114,7 @@ ssh maciejpi@10.22.68.249 "cd /var/www/nordabiznes && sudo -u www-data git pull"
ssh maciejpi@10.22.68.249 "cd /var/www/nordabiznes && /var/www/nordabiznes/venv/bin/python3 scripts/run_migration.py database/migrations/XXX_nazwa.sql"
# 5. PROD: Restart + weryfikacja
ssh maciejpi@10.22.68.249 "sudo systemctl restart nordabiznes"
ssh maciejpi@10.22.68.249 "sudo systemctl reload nordabiznes"
curl -sI https://nordabiznes.pl/health | head -3
```
@ -158,7 +158,7 @@ ssh maciejpi@10.22.68.249 "source .env && python3 skrypt.py"
### Deployment
- Przed wdrożeniem: `python -m py_compile app.py`
- SSH: `ssh maciejpi@10.22.68.249` (ZAWSZE jako maciejpi!)
- Ścieżka: `/var/www/nordabiznes` | Restart: `sudo systemctl restart nordabiznes`
- Ścieżka: `/var/www/nordabiznes` | Restart: `sudo systemctl reload nordabiznes`
- **ZAWSZE** aktualizuj `release_notes` w app.py
### Szablony Jinja2 - WAŻNE!

View File

@ -1275,28 +1275,51 @@
return;
}
var convId = state.currentConversationId;
var savedReplyTo = state.replyToMessage;
var savedFiles = state.attachedFiles.slice();
// OPTIMISTIC UI: clear editor and show message IMMEDIATELY
state.quill.setText('');
state.attachedFiles = [];
state.replyToMessage = null;
var replyPreview = document.getElementById('replyPreview');
if (replyPreview) replyPreview.style.display = 'none';
Composer.renderAttachments();
// Show optimistic message in DOM instantly
var tempId = 'temp-' + Date.now();
var optimisticMsg = {
id: tempId,
conversation_id: convId,
content: html,
sender_id: window.__CURRENT_USER__ ? window.__CURRENT_USER__.id : null,
sender: window.__CURRENT_USER__ || {},
created_at: new Date().toISOString(),
_optimistic: true
};
ChatView.appendMessage(optimisticMsg);
try {
var fd = new FormData();
if (html && text) fd.append('content', html);
if (state.replyToMessage) fd.append('reply_to_id', state.replyToMessage.id);
if (savedReplyTo) fd.append('reply_to_id', savedReplyTo.id);
state.attachedFiles.forEach(function (file) {
savedFiles.forEach(function (file) {
fd.append('files', file);
});
var result = await api('/api/conversations/' + convId + '/messages', 'POST', fd);
// Clear editor
state.quill.setText('');
state.attachedFiles = [];
state.replyToMessage = null;
var replyPreview = document.getElementById('replyPreview');
if (replyPreview) replyPreview.style.display = 'none';
Composer.renderAttachments();
// Append the sent message (dedup check inside appendMessage)
ChatView.appendMessage(result);
// Replace temp ID with real ID in state so polling dedup works
var msgs = state.messages[convId];
if (msgs) {
for (var i = msgs.length - 1; i >= 0; i--) {
if (msgs[i].id === tempId) {
msgs[i] = result;
break;
}
}
}
// Update conversation in list
ConversationList.updateConversation(convId, {