Deduplicate by sender+content match regardless of _optimistic flag,
preventing double display when SSE event arrives before POST response.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
URLs in message content were rendered as plain text. Added linkifyContent()
that converts URLs to clickable links while preserving existing <a> tags.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
navigator.sendBeacon() with a string sends text/plain Content-Type,
causing Flask to reject with 415 Unsupported Media Type.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The clipboard.onPaste override broke text pasting in Quill 2.x
where this internal API is no longer public. Replaced with a DOM
paste event listener on quill.root that only intercepts image
pastes and lets Quill handle text paste natively.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When clicking "Skontaktuj się" on a classified ad, the conversation
now starts with a pre-filled message: "Hej, piszę w sprawie ogłoszenia
na tablicy B2B: „<tytuł>"". If conversation already exists, the
context is pre-filled in the editor for the user to send.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Changed B2B classified contact link from old messages_new to new
conversations page with ?new_to=USER_ID parameter. JS creates a
1:1 conversation via API and opens it, or opens existing one if
already present. Messages now visible in the conversations inbox.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Server stores timestamps in UTC without timezone suffix. JavaScript
new Date() treated them as local time, showing times 2h behind.
Added parseUTC() helper that appends 'Z' to naive ISO dates so the
browser correctly converts UTC → user's local timezone.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
A previous replace-all of 'currentConversation' → 'currentConversationId'
doubled the suffix on the existing 'currentConversationId' variable,
breaking all conversation state references including the group panel.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The panel was showing 0 members because it used cached conversation
details that might have been loaded before the panel was opened.
Now it always fetches fresh data from the API.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- New PATCH /api/conversations/<id>/members/<uid> endpoint for role changes
- Owner can promote members to admin and demote back to member
- Admin can add/remove members and edit group name (same as owner except role changes)
- Member list shows role labels (Właściciel/Administrator/Członek)
- Fix: state.currentConversation → state.currentConversationId (panel was empty)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When viewing a group conversation, a settings button appears in the chat
header. Clicking it opens a panel to: rename the group (owner only),
view members with roles, remove members (owner only), and add new members
via search. Uses existing API endpoints.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New button next to "Nowa wiadomość" with outline style. Modal includes
group name field, multi-member search picker, and optional first message.
Uses the existing POST /api/conversations endpoint with is_group=true.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The modal had no CSS — it was rendering as unstyled HTML. Added proper
overlay, card layout, input styling, suggestion list, and footer buttons
matching the NordaBiz design system.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The compose button was just a small pencil icon with no label. Now it shows
"Nowa wiadomość" text (hidden on mobile). Recipient search was broken because
window.__USERS__ was always empty — replaced with /api/users/search API endpoint
that queries active users by name/email with autocomplete suggestions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PWA smart banner displayed icon at 40x40 but loaded 192x192 PNG (17KB).
Now uses 48x48 WebP (1.9KB) — 89% smaller, with explicit dimensions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace external Google Fonts CSS request (780ms render-blocking) with
self-hosted woff2 files served from /static/fonts/ with 30-day cache.
- 8 woff2 files: Poppins 400/500/600/700 × latin/latin-ext (53KB total)
- Inline @font-face declarations in <style> tag (no external request)
- font-display: swap preserved (no FOIT)
- CSP updated: removed fonts.googleapis.com and fonts.gstatic.com
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Root cause of double paste: Quill's internal clipboard handler AND my DOM
handler both processed the image. Fix: override clipboard.onPaste directly
to intercept images before Quill processes them.
- uploadAndInsertImage() extracted as reusable method
- CSS: img.selected gets resize:both for native browser resize handle
- Removed complex DOM resize hack (didn't work with Quill contenteditable)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Dedup: sendContent passes tempId through queue to _doSend, which
replaces optimistic msg by matching tempId (not content). Eliminates
false negatives from HTML sanitization differences.
Profile: clicking avatar or name in chat header opens /profil/<user_id>.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Root cause: rapid Enter sends caused parallel API calls creating
real duplicates in DB. Fix: message queue processes one at a time.
- sendContent() adds to queue + shows optimistic UI instantly
- _processQueue() sends sequentially (await each before next)
- Polling skips own messages (they come via optimistic UI)
- Simple ID-only dedup in appendMessage (no complex content matching)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Root cause: when typing fast + Enter, previous send hadn't finished
clearing the editor, so new text accumulated. Fix: keydown handler
captures content snapshot and clears Quill SYNCHRONOUSLY, then sends
via sendContent(). No setTimeout, no race condition.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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>
Root cause: appendMessage() had no dedup check, so both send() and polling
could add the same message. Now appendMessage() checks if msg.id already
exists in state.messages[convId] before adding — guaranteed single display.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The actual bug: Composer.send() appends message to DOM via appendMessage(),
then 1-5s later the polling loop fetches the same message from API and
appends it again. Fix: track sent messages in state.messages[convId] so
the polling dedup check (msg.id > newestId) filters them out.
Also removed debug logging.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Added _isSending flag to Composer.send() — blocks concurrent sends
- Added Quill keyboard binding override for Enter (handler returns false)
- Added stopImmediatePropagation() to DOM keydown handler
- Added _isCreating flag to NewConversation.send()
- Flags reset in finally blocks to handle errors gracefully
Fixes: messages appearing twice when user presses Enter
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
microsoft-fluent.css is not loaded as a stylesheet — all styles are inline
in base.html. Moved the widget CSS to index.html's extra_css block where
it will actually be applied.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
On mobile screens the "Co nowego na platformie" section took up excessive
vertical space by showing full descriptions of all starred items. Now on
<768px: descriptions hidden, only 2 items shown, reduced padding.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>