305 Commits

Author SHA1 Message Date
Sravan 49718e5d37 feat: hyperlinks, group seen-by for all, profile-pic preview, in-call invite fix + desktop update progress (0.1.6)
- #1: URLs in chat are now clickable links (open externally on desktop).
- #2: group 'Seen by X, Y +N more' now shows under the LATEST message to EVERYONE
  (server computes reads for all messages; click shows the names).
- #5: tapping the conversation-header avatar previews the DP/group photo full-size.
- #4: in-call invite lists only people you've messaged, excludes those already in the
  call/invited, and the Invite button is sticky.
- #3 (0.1.6 shell): auto-updater emits checking/available/downloading/ready/error to
  the web UI, which shows a progress banner (+ Restart button) so updates aren't silent.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 15:58:09 +05:30
Sravan 7682e17a53 fix(desktop 0.1.6): fast + persistent native notifications + screen picker
- Desktop notifications were slow (~15s), vanished in ~1s, and clicks did nothing:
  * don't block the toast on the avatar download (race a 600ms cap) → shows instantly
  * keep a strong reference to each Notification (Electron GC'd them → premature close
    + dead click)
  * call invites use timeoutType:'never' + a 45s window so they stay until clicked/ended;
    web marks call notifications persistent.
- #9: enable the OS screen/window PICKER (useSystemPicker) so users choose what to share
  (a single window avoids the whole-screen mirror); falls back to primary display.
- desktop 0.1.5 -> 0.1.6.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 15:43:07 +05:30
Sravan 0976b6f91a feat(dashboard): profile pic (More#3), sticky top nav (More#5), App-installs search+pagination (More#4)
- #3: profile avatar now shows the photo (avatarUrl) not just initials.
- #5: header is position:sticky so the brand + profile stay visible while scrolling.
- #4: App installs table gets a search box (user/platform/version/OS) and 10-per-page
  pagination with prev/next + count.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 14:28:12 +05:30
Sravan 3e811af3d6 fix(calls): screen shows with camera OFF (#9), screen-share button highlight, unanswered caller vs callee text
- #9 REAL cause: the tile was forced to avatar whenever the peer's CAMERA was off
  (addTile + meeting-peer-state), so a screen-share only appeared if the camera was
  also on. Now camera-off only shows the avatar when the peer is NOT sharing a screen.
- #9: screen-share button is highlighted (blue) while active, like mic/cam/record.
- Unanswered call text is viewer-relative: the caller sees 'Call not answered', the
  callee sees 'Missed call'.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 14:24:17 +05:30
Sravan 096683385a fix: chat-open scroll regression, SFU screen reliability, unanswered calls, selected-chat border
- REGRESSION (More#1): removed overflow-anchor:none (it stopped the view from staying
  at the bottom as images settle) + renderThread re-asserts scroll-to-bottom after late
  content, unless a 'New messages' divider is shown → chats open at the latest message.
- #9: dynacast off — a screen-share layer was being paused unless a camera track was
  also flowing (blank / only-with-camera / slow). Now every published track flows.
- More#7/#9: 1:1 calls track 'answered'; unanswered calls auto-end after ~40s (caller
  no longer stuck ringing) and post 'Missed call' instead of a duration; answered calls
  show duration from the answer time. meeting-ended reason 'unanswered' → 'No answer'.
- More#6: selected chat now has a thin yellow (brand) boundary.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 13:20:19 +05:30
Sravan f6962a0b4a fix: screen-share mirror (#9), sidebar tick sync (#1), desktop notification reload (#3)
- #9: sharing your whole screen no longer creates a 'hall of mirrors' — your own
  screen is NOT shown back to you (you see your camera/avatar + a 'You're sharing'
  badge); other participants still see your screen. Your own tile is never the local
  stage.
- #1: opening a DM now syncs the sidebar tick to the thread's true delivered/read
  state, so it can't show a single tick while the conversation shows double.
- #3: nativePlatform() now also detects older desktop builds that expose
  bizConnectNative but not __NATIVE__. Those were wrongly subscribing to Web Push, so
  the service worker's openWindow RELOADED the page on notification click (losing the
  header DP). Desktop now uses only the native toast → opens in-place.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 11:55:30 +05:30
Sravan 1114cd4fda fix(calls): callee tile while ringing (#7), multi-device decline no longer kills active call (#8), SFU screen-attach retry (#9)
- #7: outgoing 1:1 call shows the person you're calling (DP + name + 'Ringing…')
  instead of only your own tile; removed when they answer or the call ends.
- #8: declineDmCall now ignores the decline if that user has ALREADY accepted on
  another device (they're in the room) — declining the ringing invite on a 2nd
  device no longer tears down the active call.
- #9: sfuAttach retries (bounded) when the uid→peerId map lags the LiveKit track,
  so a shared screen isn't silently dropped due to a race.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 23:18:14 +05:30
Sravan f52d54a093 fix(chat): forward polish + scrollbar + image preview + media tabs
- #4: forward modal buttons now branded (bzc-* un-scoped from .bz-confirm); target
  rows get real checkboxes + border highlight; selected MESSAGES highlight in green
  with a filled tick (distinct from blue bubbles), checkbox on every message.
- #5: forwarded messages show an italic 'Forwarded from <origin>' label; new fwd_from
  column preserves the true origin across re-forwards.
- #6: image-preview arrows are smaller (32px) chevron icons, clear of the image
  (image max 82vw); icons.js?v=5.
- #10: image preview now opens ABOVE the media modal (z 9900) and Esc/close returns
  to the media view instead of closing everything.
- #11: Media/Links/Docs active tab underline is brand-blue, not green.
- #2: chat-list + conversation scrollbars 6px, stepper arrows hidden; pagination adds
  a cooldown + overflow-anchor:none so scrolling up no longer sticks/jumps.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 23:11:02 +05:30
Sravan 346361da8a feat(chat): dismiss a chat's notifications on other devices once seen (#13)
When you open/read a conversation, the server now also pushes a notif-clear to your
OWN other sockets. Each device tags notifications by conversation (kind:id), so on
notif-clear it closes the matching page Notification + any Service-Worker (Web Push)
notifications, and drops matching activity-center entries so the bell badge stays in
sync. (Desktop Electron native toasts are transient/auto-expire; the web+PWA surface
is covered.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 16:20:18 +05:30
Sravan ff436704ec feat(desktop 0.1.5): show version + Check for updates in Settings (#12)
- Settings now shows 'Biz Connect for Desktop · Version x.y.z' with a Check for
  updates button (desktop only, feature-detected).
- preload exposes checkForUpdates(); main.js adds the check-updates IPC (returns
  available/current/dev/error) and, when a build finishes downloading, shows a
  Restart now / Later dialog instead of only the silent on-next-launch install.
- desktop version bumped 0.1.4 -> 0.1.5.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 16:12:21 +05:30
Sravan a9b3533f7a feat(chat): forward messages with multi-select (#1)
- Message action pill gains a Forward button; tapping it enters selection mode
  (tap bubbles to multi-select, footer bar shows count + Forward/Cancel, Esc exits).
- Forward picker lists EXISTING conversations only (DMs + groups from the sidebar),
  searchable, multi-target. POST /api/messages/forward copies body+attachment into
  each target (authorized as participant/member), live-pushed like a normal send.
- /files auth now accepts ANY message carrying an attachment (allByAttachment), so
  forwarded images stay viewable for the new recipients.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 16:09:02 +05:30
Sravan 3a976d58ab feat(chat): reply-jump (#8), image prev/next nav (#3), multi-file send (#5)
- #8: reply quotes are now clickable — jump to the original message (paging older
  history in if needed), then flash it. reply DTO carries the target's timestamp.
- #3: image lightbox now has ← / → arrows + keyboard nav to flip through all images
  in the conversation.
- #5: the composer queues MULTIPLE files (file input is multiple; paste still works);
  each is shown as a removable chip and sent as its own message (first carries the
  typed text as caption), in order.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 16:02:15 +05:30
Sravan c672f7b65f fix: notification-open in-place (#2/#7/#19) + polish (#9 scrollbar, #16 call scrollbar, #17 ringback)
- #2/#7/#19: openFromNotif opened chats via a full-page reload (location.assign),
  which was slow AND dropped live state — the sidebar hadn't reloaded so the header
  showed 'Conversation' with no DP, and an incoming call's Join/invite popup was lost.
  Now it opens the chat IN-PLACE (selectChat) since the app is already running;
  full-page nav only as a fallback when the app isn't initialised.
- #9: slimmer 6px scrollbar; explicitly hide all webkit stepper-arrow variants.
- #16: 1:1/small call no longer shows a stray scrollbar — meet-grid centers tiles
  and clips overflow (call UI fits the viewport).
- #17: caller now hears a gentle ringback while waiting; stops on answer, on any
  call exit, and auto-stops after 45s so it never rings forever.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 15:54:28 +05:30
Sravan 16065315a7 fix(chat): pull-to-refresh at top loads older history, no longer resets to bottom
Pull-down-at-top called reloadThread (reload newest 500 + scroll to bottom), so
reaching the oldest message yanked the view to the newest — jarring, especially on a
single page. Now the pull gesture calls loadOlder: it pages in older history keeping
scroll position, or no-ops when there's nothing older. Never jumps to the bottom.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 15:44:32 +05:30
Sravan eb79823fd2 feat(chat): older-history pagination + full-thread server-side search
Older messages (beyond the newest 500) now load as you scroll to the top — the
thread endpoint takes a ?before=<created_at> cursor, the client prepends the older
page and preserves scroll position (renderThread keepScroll). _hasMoreOlder stops
paging when a short page returns.

Search now covers the ENTIRE thread, not just the loaded window: new
/api/messages/search (DM + group, LIKE with escaped wildcards) returns all matching
message ids; the client debounces the query, and jumping to a hit older than the
loaded window pages history back (ensureLoadedBack) until the match is in view, then
highlights + flashes it. Cap raised to 500.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 15:21:14 +05:30
Sravan 4fd323fff4 fix(chat): thread query returned OLDEST 300, hiding new messages past the cap
THE disappearing-messages root cause (found via THREAD.length=300 vs 351 on server).
messages.thread and threadByConversation did 'ORDER BY created_at ASC LIMIT 300' —
the oldest 300. Once a DM/group passed 300 messages, every newer message was silently
dropped from the fetch, so anything sent after that point 'disappeared' (persisted
server-side, never returned to the client). Now: inner 'ORDER BY created_at DESC LIMIT'
takes the NEWEST N, outer ASC presents them oldest-first. Cap raised 300 -> 500.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 15:08:38 +05:30
Sravan 507489ec55 fix(chat): stop blanking a conversation (and wiping its cache) on a failed thread fetch
THE disappearing-messages root cause. openConvo did: let msgs=[]; try{ msgs=fetch()
}catch{}; THREAD = Array.isArray(msgs)?msgs:[]. On a flaky desktop network a failed
fetch left msgs=[] (still an array) → THREAD=[] AND THREAD_CACHE.set(ckey,[]) — so
reopening a chat blanked it AND overwrote the cache, making messages vanish and stay
gone even though they persist server-side (confirmed: 982 DMs, zero dangling ids).

Now msgs stays null on a failed/non-OK fetch; we only replace THREAD/cache on a real
array response, and otherwise keep the cached render instead of blanking.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 13:47:58 +05:30
Sravan 12be747609 fix(chat): resolve merged-away user ids so DMs to merged contacts don't vanish
Root cause of 'messages to some contacts disappear (gone after reopen)': the #2
account merge deletes the merged-away user row. Any lingering reference to that old
id — a cached contact, an in-flight DM — then saved against a dead recipient / 404'd
on thread fetch, so messages silently vanished.

- New user_aliases table records old_id -> survivor on every merge (mergeInto).
- users.resolve(id) follows the redirect.
- DM send (recipient), thread fetch (with), and read now resolve() the peer id, so a
  stale id transparently routes to the surviving account.

Fixes future merges fully. Contacts merged BEFORE this (no alias recorded) may need a
one-off data check.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 13:23:49 +05:30
Sravan 1128f9811a fix: persist chat uploads on /data volume (broken images) + prevent duplicate chat sockets
- Broken images: UPLOADS_DIR/REC_DIR/TRANS_DIR were server/<dir> INSIDE the image,
  so every deploy.sh rebuild wiped uploaded files — old images 404'd ('broken
  image') though their DB rows survived. Make them env-overridable and point prod
  at /data/uploads|recordings|transcripts (persistent volume), matching DB/downloads.
  NOTE: files already lost to prior rebuilds can't be recovered; new uploads persist.
- Duplicate notifications: harden connectChatWs — close/detach any prior socket
  before opening a new one and keep a single pending reconnect timer, so a flaky
  reconnect can't leave two live sockets delivering every event/notification twice.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 13:09:24 +05:30
Sravan 7d284213d1 fix(chat): non-destructive reconnect resync — stop messages disappearing after send
batch42's resync-on-reconnect reopened the conversation via openConvo, which
replaces THREAD wholesale. On the desktop app (flaky WS), a reload racing with a
just-sent message could momentarily blank the chat ('messages disappear after
sending', seen on the Manasa chat). resync now MERGES: adds messages missed while
disconnected + refreshes read/delivered/seen/edited/deleted flags, but never
removes messages already on screen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 12:41:12 +05:30
Sravan 37c8929c5e fix(sfu): upgrade LiveKit server v1.7 -> v1.9 to match client SDK 2.20
The bundled livekit-client@2.20 uses signaling protocol 17 and the /rtc/v1 path,
which the v1.7 server didn't implement (404). The client fell back to the legacy
path, leaving the track publisher in a bad state so mic/cam publishing failed with
'InvalidAccessError: The sender was not created by this peer connection' — surfaced
to users as a misleading 'permission required' toast. v1.9 supports protocol 17.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 12:33:11 +05:30
Sravan 496eba3c17 fix(chat): batch 1 desktop fixes — resync-on-reconnect, branded confirm, esc, typing-in-panel, media errors
- #10/#18/#7: on WebSocket RE-connect, re-pull sidebar + re-open the current
  thread so the desktop/mobile apps recover missed messages/ticks without a
  refresh (the old onopen only re-sent chat-hello).
- #11: branded bzConfirm() dialog replaces window.confirm for delete (the OS/
  Electron default dialog looked off-brand on desktop).
- #4: global Esc no longer closes the conversation while an image preview is
  open — the preview closes first, the conversation on a second Esc.
- #6: live 'typing…' now also shows inside the conversation panel (animated),
  not only the header/sidebar.
- #15 (diagnostic): mic/cam failures now report the REAL cause (permission vs
  no-device vs in-use) with desktop-specific guidance, and log the raw error,
  instead of always saying 'permission required'.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 11:46:22 +05:30
Sravan 472170784b fix(livekit): single UDP media port + pin node_ip for the NAT'd server
The prod box is behind NAT (private 192.168.88.61 behind public 118.95.33.89), so
LiveKit auto-IP-detection would pick the wrong (outbound) address. Pin
rtc.node_ip=118.95.33.89 and collapse media to one UDP port (50000) + TCP 7881 to
minimize the upstream gateway port-forward the network team must add. Docs updated
with the exact forward table.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 13:50:24 +05:30
Sravan 10e393a31f chore(compose): gate livekit behind 'sfu' profile so it stays dormant until enabled
A normal deploy no longer starts the livekit container (it would crashloop with
empty keys before provisioning). Enable with 'docker compose --profile sfu up -d'.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 13:23:09 +05:30
Sravan 47b6f475e6 docs(deploy): LiveKit SFU provisioning — phase 3 (ops runbook)
Step-by-step to enable meetings SFU: generate key/secret, .env vars, DNS record,
NPM proxy host for wss signaling, VPS firewall for UDP media + TCP fallback, deploy,
verify. Includes the one-line rollback to mesh (remove the LIVEKIT_* vars).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 13:21:44 +05:30
Sravan 6118d59c5c feat(meetings): LiveKit SFU — phase 2 (client media plane, behind the flag)
Meeting media flows through LiveKit when the server reports sfu:true; otherwise the
P2P mesh is unchanged. Reuses the ENTIRE existing meeting UI + WS control plane
(join/host/mute/screen/recording/transcript) and only swaps the transport:
- vendored livekit-client 2.20 UMD (lazy-loaded; no build step).
- sfuInit() reads /api/meetings/config at boot; sfuConnect() joins the LiveKit room
  with the minted token after the WS meeting-join.
- remote tracks map back to the WS peerId via uid (LiveKit identity = app user id);
  per-peer stream prefers screen over camera, driving the existing sharing/stage UI.
- toggleMic/Cam/Screen publish via LiveKit; local tracks reflected into meetLocalStream
  so tiles, active-speaker meter, canvas recording and transcript keep working.
- meetMakePeer/peer-joined/peer-left/leaveMeeting branch on SFU.on; mesh path intact.

Needs a running LiveKit server + live test (phase 3 ops) to exercise end-to-end.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 13:20:59 +05:30
Sravan f1dbcd0f86 feat(meetings): LiveKit SFU — phase 1 (server plumbing, config-gated)
Adds the server side of scaling meetings past the ~5-peer mesh:
- config.js: LIVEKIT_URL/API_KEY/API_SECRET + LIVEKIT_ENABLED flag. All optional;
  when unset the app keeps the built-in P2P mesh (fully additive, like push).
- routes.js: GET /api/meetings/config (tells the client sfu on/off + wss url) and
  POST /api/meetings/token (mints a per-user, per-room LiveKit join token — hand-rolled
  HS256 JWT like the FCM/APNs tokens, no new dependency; secret stays server-side).
- docker-compose.yml: optional livekit service (single-node, no Redis), keys injected
  via LIVEKIT_KEYS from the same .env; media over published UDP 50000-50100 + TCP 7881,
  signaling proxied by NPM.
- livekit.yaml + .env.example documented.

Client (mesh->LiveKit media swap, behind the flag) lands in phase 2.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 13:11:37 +05:30
Sravan 30e354d58f feat(chat): per-message group read receipts (seen-by + read-by-all ticks)
Thread: every group message I send now carries a receipt tick — sent (1 grey)
-> seen-by-some (2 grey) -> seen-by-all (2 yellow) — computed from the per-message
seenBy the server already returns and kept live by onGroupRead. Tap the tick to see
exactly who ('Seen by X, Y') with an 'N of M' tooltip. Replaces the old last-message-
only 'Seen by' line with a universal, tappable per-message receipt.

Sidebar: the group row tick now reflects real read state (read/delivered/sent) via
memberReads vs member count, instead of a hardcoded 'sent' — and refreshes live when
the open group is read by all.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 10:57:08 +05:30
Sravan 3f791cb120 feat(chat): live typing indicators for DMs and groups
Ephemeral over the chat WebSocket (no DB). Composer emits chat-typing on/off
(throttled 2.5s, auto-stop after 4s idle / on send / on leaving the chat).
Server relays to the DM peer or fans out to group members (membership-checked).
Receiver shows 'typing…' in the conversation header subtitle and the sidebar
row preview (brand-blue italic), with per-sender auto-expiry so a dropped 'off'
can't stick. Group shows names ('Alice is typing…', 'N people are typing…').

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 17:17:03 +05:30
Sravan 820e7a08fc add publish-desktop.sh — one-command desktop installer publish from laptop
Mirrors redeploy.sh conventions (pinned host key, DEPLOY_PASS/deploy.secret/
prompt password resolution, plink discovery). Uploads the three electron-builder
artifacts via pscp to a temp dir, then docker cp's them into the app container's
/data/downloads (volume-path-independent), and verifies the public feed serves
the new version. Keeps older versions; only latest.yml is overwritten.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 16:04:22 +05:30
Sravan 609427747a docs(deploy): document scp-to-named-volume as the desktop release publish step
/data is a named Docker volume, not a host bind-mount, so the prior
scp root@host:/data/downloads/ path was wrong. Document the real host
path (/var/lib/docker/volumes/bizgaze_support_data/_data/downloads) plus
the docker cp alternative, and note latest.yml is the only overwritten file.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 15:43:44 +05:30
Sravan a1887064eb feat(chat/notif): single hover action-pill; sender DP in desktop+mobile notifications
- Message actions (reply/react/edit/delete) consolidated into ONE hover pill anchored to the
  bubble's top-right, overlapping it so on short messages the icons no longer float off into
  empty space and vanish before you can click.
- Desktop toast + mobile(FCM)/web-background push now show the sender's real DP:
  * renderer passes the DP URL through; desktop shell downloads it for the toast icon
    (canvas-drawing an external DP tainted it → initials). Desktop bumped to 0.1.4.
  * DM push payload carries icon=sender avatar; sw.js already uses it (web background),
    sendFcm sets notification.image (Android).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 18:01:32 +05:30
avinash ab48642cf6 Merge branch 'master' of https://code.bizgaze.com/Sravan/BizGaze_Remote 2026-07-02 17:58:23 +05:30
avinash b0473dee7d Merge branch 'master' of https://code.bizgaze.com/Sravan/BizGaze_Remote 2026-07-02 17:58:14 +05:30
avinash 5eb8f436c4 fix(redeploy): add host key verification to SSH command 2026-07-02 17:57:55 +05:30
Sravan 51e206279b feat(chat): edit message (#4)
Sender can edit their own text messages: a pencil action on the bubble loads the text into the
composer in an 'Editing' mode; saving updates the body, marks it 'edited', and pushes the change
live to the other side/tabs (chat-edited, mirroring delete). Adds messages.edited_at + editBody().

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 17:45:20 +05:30
Sravan 9bc109528e fix(brand/ux): real brand C on header, yellow read-tick, sign-in spinner, notif DP
- #5: use the ACTUAL brand C (extracted transparent white C + gold dot from the master
  app-icon) on the blue headers instead of a hand-drawn mark; login card keeps the app icon.
- #3: read-receipt double tick on my own (blue) bubble is now brand yellow (was blue-on-blue,
  invisible).
- #2: Sign-in button shows a spinner + 'Signing in…' on submit.
- #1: web notification uses the sender/group DP URL directly as the icon (drawing an external
  DP to canvas tainted it → silently fell back to initials).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 17:39:38 +05:30
Sravan 7674c456f2 fix(brand): header shows a plain white C + gold dot on the blue bar
The header is already blue, so the app-icon-in-a-white-pill looked like blue→white→blue→white
nesting. Use a transparent mark (mark-light.svg: white C + gold dot) placed directly on the blue
header (home/index/connect/dashboard), dropping the white background + padding. share.html keeps
the full app icon (it's a light card, not the blue bar).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 17:26:22 +05:30
Sravan 952242f62b fix(brand/ux): real Biz Connect logo, branded loading screen, phone login, no blank gap
- Replace the leftover 'Your Virtual CXO' logo.png with the app icon (C mark) in every
  header (home/index/connect/dashboard) + the app icon on share.html; delete logo.png.
- Loading screen now shows the orbit mark + 'Biz Connect' wordmark + 'Loading…' (was bare).
- Keep the branded splash up until the chat list finishes loading — no blank/stuck gap
  after login (previously hidden before loadSidebar()).
- Login accepts email OR phone (field was type=email, which rejected phone numbers and so
  blocked the mobile-login that triggers the account merge). Label → 'Email or phone'.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 17:16:08 +05:30
Sravan d515a562a0 fix(chat): stop echo downgrading delivered/read tick (#6); set self-chat apart (#3)
- #6: the server echoes your own sent message back over the socket; onChatMessage reset
  last_status='sent', overwriting a delivered/read that arrived first → the chat-list tick
  flipped back to single. Track last_msg_id and never downgrade the same message's tick.
- #3: pin the 'You' note-to-self chat in its own slot — tinted row + 'Note to self' tag +
  a divider separating it from the conversation list.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 16:17:30 +05:30
Sravan 515411fc83 ops: persist desktop download feed on /data volume + IT release guide
- docker-compose: DOWNLOADS_DIR=/data/downloads so uploaded installers/latest.yml
  survive image rebuilds (deploy.sh) instead of being wiped.
- DEPLOY.md: step-by-step for publishing a desktop release (build → upload the
  3 feed files → verify) so the Download button + auto-update go live.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 15:46:33 +05:30
Sravan dc1915bb43 feat: live presence, delivery-tick sync, brand rollout, call fixes, desktop 0.1.3
Live presence (fixes stale in-call/status until refresh — impossible in apps):
- server broadcasts a user's status over the chat socket on connect/disconnect, call
  join/leave, and status change (chat.js broadcastPresence; signaling + routes hooks).
- client onPresence() updates the contact dot + open-chat header live.

Chat delivery ticks (#6): chat-list row now mirrors the thread (delivered→double grey,
read→blue) via a new 'with' field on the delivered relay + onChatRead/onChatDelivered.

Call fixes: no bogus 'host handed over' when a 1:1 call ends (leaveMeeting forced);
branded call-connecting + chat-thread loaders; header subtitle tracks live call state.

Notifications: web notify + sw.js use sender/group DP + brand icon (not old wordmark);
desktop shell drops Web Push so only the single native toast fires (#5).

Brand: master icon/splash/loaders wired everywhere (PWA/favicon/apple-touch/.ico),
branded login (blue + gold CTA), branded toasts (BZToast) on all pages, Electron splash.

Desktop: dev auto-targets localhost (packaged→prod); version 0.1.3 with new multi-size
icon; dropped unused node-notifier; removed home-mockup.html.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 15:43:02 +05:30
Sravan a152005b71 feat(brand): roll out master app icon, splash & branded loaders everywhere
From brand-assets/ masters (C + orbiting-dot on brand blue):
- Regenerated PWA icons (192/512/maskable), apple-touch (180), favicons
  (32/16 + root favicon.ico) from the 1024 master; bumped manifest to v3.
- Rebuilt desktop/build/icon.ico as a proper multi-size ICO (16..256,
  PNG-compressed) to fix the tiny/blurry taskbar icon.
- Updated mobile masters (resources/icon.png 1024, splash.png/splash-dark.png
  2732) for capacitor-assets to regenerate native icons/splash.
- Wired the animated branded orbit loader into the app boot 'Loading…' screen;
  added favicon/theme-color links to index + home heads.
- logo.png (horizontal wordmark) left untouched — masters have no wordmark.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 12:41:48 +05:30
Sravan 04db586dc3 feat(auth): merge mobile+email logins into one account via BizGaze person-id (#2)
Provisioning now keys the Biz Connect account on bz.bizgazeUserId (the same
value whether the person signs in with email or mobile) instead of the typed
identifier, so both logins resolve to a single contact. Legacy rows get the
person-id stamped on next login; an existing duplicate created under the same
identifier is folded in via a transactional users.mergeInto() that reassigns
all messages/memberships/reactions/votes/favorites/ownership to the survivor.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 12:35:29 +05:30
Sravan 88e897cc9c feat(chat): modern emojis via Twemoji (#5)
Loads @twemoji/api (jsDelivr) and renders emojis as Twemoji images in messages, the emoji
picker, and reactions (twemojify at each render point). Picker inserts via data-emoji so it
survives the <img> swap. Falls back to plain Unicode if the CDN is unavailable. build batch25.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 12:23:26 +05:30
Sravan 462a167438 feat(chat): self-chat 'You' — note-to-self (#4)
Pinned 'You' chat at the top of the list (always available); messaging yourself works
(to===me), with no self push/echo notification and a 'Message yourself' header. Self is
filtered from the normal contacts and shows no status dot. build batch24.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 12:05:32 +05:30
Sravan 15350c691b feat(chat): delivery ticks on the last message in the chat list (#3)
Conversations DTO now returns last_status (sent/delivered/read for DMs; sent for groups) for
my last message. The list row shows the matching tick (single/double, blue when read) and a
red 'Draft:' indicator when there's unsent text. Live 'sent' on send/receive; upgrades to
delivered/read on refresh. e2e 119.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 11:59:53 +05:30
Sravan a33ed27ffe feat(chat): per-chat drafts + fix emoji picker closing on tab switch
- Unsent text is saved per conversation (survives switching chats and reloads) and restored
  when you reopen the chat; cleared on send. (#1)
- Emoji picker: stopPropagation on tab/grid clicks so switching category no longer closes it
  (the re-render was detaching the clicked node -> outside-click handler fired). (#6)
build batch23.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 11:55:14 +05:30
Sravan 2b27889e07 fix(desktop): use Electron's native Notification (reliable click-to-open)
node-notifier/SnoreToast click callback never fired without the crashing wait mode. Switch the
chat toast to Electron's built-in Notification: shows avatar + message and its 'click' event
reliably raises the app + opens the chat. No SnoreToast, no external tools.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 11:16:35 +05:30
Sravan f8978ff796 fix(desktop): drop crashing SnoreToast reply path — clean click-to-open toast
The bundled SnoreToast crashes (0xC0000409) handling a text reply, and that crash triggered
a second (fallback) toast. Removed the direct-SnoreToast reply path entirely; the chat toast
now reliably shows avatar + message via node-notifier and opens the chat on click. Inline
text reply needs a different toast engine (deferred).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 08:58:26 +05:30