Commit Graph
100 Commits
Author SHA1 Message Date
Sravan fed0e07e3e mobile: swipe-back easier completion threshold (25%) + readback telemetry to confirm conversation actually translates (batch141) 2026-07-19 23:41:13 +05:30
Sravan 9c731f087e mobile: iOS PWA keyboard — pin app to visual viewport + undo webview scroll so header stays; swipe-back telemetry (batch140) 2026-07-19 23:17:13 +05:30
Sravan 578f3a2921 mobile: interactive-widget=resizes-content so the keyboard resizes the layout instead of panning the viewport (header no longer pushed off) (batch139) 2026-07-19 23:00:43 +05:30
Sravan f178e271c8 debug: capture PWA composer-focus viewport state (scale/scroll/font) to diagnose the zoom (batch138) 2026-07-19 22:53:28 +05:30
SravanandClaude Opus 4.8 8aa2532666 mobile: interactive finger-following swipe-back to go from a conversation to the list (batch137)
Drag the open conversation rightward from the left edge and it follows the finger 1:1 with the
list parallaxing in underneath; release past ~35% (or a quick flick) completes the pop, else it
snaps back. Fixes the old stuck-pane bug: once a clear horizontal drag is detected we
preventDefault (passive:false) to CLAIM the gesture so iOS/scroll can't steal it and fire
touchcancel; touchcancel always resolves to a clean state. showWelcome(skipAnim) does the state
swap without re-animating. Works in native app, PWA and mobile browser.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 22:50:10 +05:30
Sravan 40789b06cf mobile: composer font-size 16px UNCONDITIONALLY to stop iOS/PWA focus-zoom on any viewport (batch136) 2026-07-19 18:57:45 +05:30
SravanandClaude Opus 4.8 134cb8999d ios: default in-call audio to the loudspeaker (AVAudioSession) at build time
Calls were routing to the quiet earpiece. ios-patch.sh now runs a Node helper (Node 20 is
already in the build env) that injects an AVAudioSession .playAndRecord/.voiceChat category with
.defaultToSpeaker + Bluetooth into the generated AppDelegate. Tolerant: exits 0 and no-ops if the
template differs, so it can never fail the Codemagic build. Verified locally against the Cap 7
AppDelegate template — injects correctly and is idempotent. First pass; if WebRTC re-grabs the
session mid-call on device, a follow-up plugin will re-assert .overrideOutputAudioPort(.speaker).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 16:18:52 +05:30
SravanandClaude Opus 4.8 f2b965bd3d desktop v0.1.20 + web: notify on download complete (was silent) (batch135)
Suppressing the save dialog made downloads completely silent — "nothing happened" even though
the file saved to Downloads. Now on download 'done': (1) tell the web UI → branded toast
"Saved X to your Downloads folder" when the window is focused; (2) native OS notification
(click → reveal in Explorer) when the app is minimized/in the tray, so it's never double-noticed;
(3) a failure notice. preload exposes onDownloadDone; home.html shows the toast.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 16:03:24 +05:30
SravanandClaude Opus 4.8 30eaf37773 desktop: v0.1.19 — reliable auto-update for close-to-tray users
The app closes to tray, so it kept running on the old version and only checked on a fresh launch.
Now: (1) check for updates on EVERY window show (X-to-tray then reopen, taskbar, relaunch via
second-instance) via win.on('show'), throttled 1/10min; (2) explicit autoInstallOnAppQuit=true so
a downloaded update installs on the next real quit / PC restart even if the user never clicks
"Restart now"; (3) native notification when an update finishes downloading while hidden in the
tray. The existing 6-hour background timer is unchanged and still runs regardless of window state.
Also carries the 0.1.18 downloads-to-Downloads-folder change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 15:38:01 +05:30
Sravan 3f44e2b285 desktop: v0.1.18 — downloads save to Downloads folder with no dialog 2026-07-19 11:42:38 +05:30
Sravan d098ba468e docs: revert stray whitespace in IOS_SETUP.md 2026-07-19 11:36:42 +05:30
SravanandClaude Opus 4.8 9f3e8b83c7 mobile: hide iOS keyboard accessory bar + composer-anchored attach menu (batch134)
- Accessory bar: call Keyboard.setAccessoryBarVisible({isVisible:false}) on iOS in the native
  IIFE, hiding the grey chevrons+Done strip above the keyboard. The plugin is already bundled
  in the current TestFlight build, so this takes effect on a web deploy — no rebuild.
- Attach: tapping the paperclip now opens a composer-anchored Photos/Camera/Document menu
  (like the emoji/mention popups) instead of firing the generic mid-screen OS chooser as the
  first thing. Each option opens a type-scoped picker (image/*, capture). Web fix, all clients.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 11:35:59 +05:30
SravanandClaude Opus 4.8 a7f954ae44 mobile: Enter=newline on mobile, hide preview zoom buttons; desktop: downloads to Downloads folder (batch133)
- Mobile: the Return key now inserts a newline instead of sending (send via the button, like
  every native chat app). Desktop keeps Enter=send / Shift+Enter=newline.
- Image preview: hide the on-screen +/- zoom buttons on mobile (pinch-to-zoom covers it).
- Desktop (Electron): a will-download handler saves straight to the OS Downloads folder with no
  "where to save?" dialog, de-duping the name if it exists. NOTE: desktop code only — NOT
  published to the update feed (needs an explicit desktop rebuild/publish).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 10:38:36 +05:30
SravanandClaude Opus 4.8 c32584df54 mobile: fix zoom + notch on the non-home pages (dashboard/index/connect/share/host)
The mobile viewport fixes only ever went into home.html. The other standalone pages still had
the old viewport, so in the native app they auto-zoom on input focus and (dashboard/host) run
under the notch/Dynamic Island. Bring every page's viewport to match home.html
(maximum-scale=1, user-scalable=no, viewport-fit=cover) and add safe-area top padding to the
dashboard header and the host body/indicator so nothing sits under the island now that the
viewport is cover. index/connect/share already pad for safe-area; they only needed maximum-scale.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 08:39:50 +05:30
SravanandClaude Opus 4.8 3a7a026533 mobile PWA: fix keyboard zoom (16px inputs) + composer lift via VisualViewport (batch132)
iOS home-screen PWAs IGNORE viewport maximum-scale (Apple disabled it for accessibility), so
the maximum-scale=1 that stops auto-zoom in the Capacitor WebView does nothing in the PWA — it
still zooms on input focus. Real cross-platform fix: make every focusable text field 16px on
mobile (the composer was .92rem). autoGrow's empty-guard keeps it one line.

Also the PWA has no Capacitor Keyboard plugin, so the composer never lifted above the keyboard
("not the same keyboard"). Added a VisualViewport-based lift for non-native clients (browser +
PWA); native still uses the plugin. >100px threshold ignores the Safari toolbar.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 08:31:32 +05:30
SravanandClaude Opus 4.8 150a074578 mobile: stop the open-pin schedule the instant the user scrolls (batch131)
Bug: for ~3.4s after opening a chat, the "keep newest in view as images load" pin schedule
would yank you back to the bottom if you tried to scroll up for older history — so scrolling
up snapped back to latest, and only worked once the schedule expired (~5s). Fix: the user's
first scroll gesture now sets _openScrolled, which cancels the pending pin timers and short-
circuits _pinNewest (so the late image-load pins stop too). loadOlder/anchor then works
immediately, no yank.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 08:21:34 +05:30
SravanandClaude Opus 4.8 2a60c01268 mobile: apply network content DURING the fade, not at landing (batch130)
Device log showed the slide motion itself is now clean 60fps (frames 0-15 all ~16ms in every
sample); the only remaining spike was at frame ~16 — AFTER the slide lands — from applying the
network thread result + pin at that instant (the "adjusts after it lands" feel). Since the open
is a compositor opacity/transform fade, main-thread work during it doesn't stall the animation
and is masked by the low opacity. So apply the reconcile/render + pins immediately when the
fetch returns (mid-fade) instead of deferring to slide-end. By the time the pane is fully opaque
the content is already settled. Removed the now-obsolete appendBubble mid-slide deferral.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 07:32:21 +05:30
SravanandClaude Opus 4.8 9bb74cffd2 mobile: real slide-in + fade on open, list column untouched = zero reflow (batch129)
The reflow that caused the end-of-slide hitch was never the content slide itself — it was
SHOWING the list column during the slide and hiding it (display:flex->none) at the end. So
keep .chatcol display:none the whole time (untouched) and animate ONLY .content: it pushes in
from translateX(12%)->0 with an opacity fade, on its already-promoted GPU layer. Real slide
motion, and nothing to reflow when it ends. (This also explains the batch126 breakage: that
made .chatcol position:absolute, disturbing the touch/layout target — not touched here.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 06:57:48 +05:30
SravanandClaude Opus 4.8 9c703d6e64 mobile: chat open = instant + quick opacity fade (no slide) (batch128)
User chose the fade over chasing the WebView slide-teardown reflow. Open now: the cache-
rendered conversation is placed instantly and fades opacity 0->1 over 140ms. No transform,
no off-screen pane, no list-column display toggle -> nothing to reflow at the end, so the
~40ms end-of-slide settle cannot occur. Same afterOpenSlide queue flushes network reconcile/
pins/appends at fade-end. Desktop unaffected (fade is mobile-only via __freshOpen).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 06:51:38 +05:30
SravanandClaude Opus 4.8 9eb4a99679 mobile: revert chatcol absolute-behind — it broke chat opening (batch127)
The list-column position:absolute change stopped conversations from opening. Revert to the
known-good display:none. Keep the other batch124-125 fixes (content promoted, reconcile,
reopen-at-latest, download interceptor). Slide back to the batch125 behaviour (opens fine,
minor end hitch) while I find a safe way to remove the reflow.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 06:35:45 +05:30
SravanandClaude Opus 4.8 82d0154313 mobile: kill the real slide hitch — stop reflowing the list column at slide-end (batch126)
Device log proved the residual 30-78ms end-of-slide hitch is NOT the content-layer demote
(content stays promoted) and NOT the re-render (reconcile, longtasks:[]). It's the chat-list
column flipping display:flex->none at transitionend, which reflows that whole subtree. Keep
the list RENDERED behind the conversation (position:absolute; z-index:0; covered by the opaque
content pane) instead of toggling display. Teardown now only clears transforms = no reflow.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 01:04:40 +05:30
SravanandClaude Opus 4.8 ea04556596 mobile: definitive slide fix (zero teardown) + real native download + reconcile (batch125)
Slide: the residual ~40ms end-of-slide hitch (down from 128ms in b124) was the layer
DEMOTE + the deferred full renderThread at transitionend. Two fixes:
- Keep .content promoted for the WHOLE time a chat is open (will-change lives on
  body.chat-open, not on the animating class). The open transition now only changes the
  transform VALUE 100%->0; when it ends there is nothing to tear down -> no demote raster.
- Slide-end no longer rebuilds all 40 nodes: reconcileOpen() appends only the 0-2 genuinely
  new tail messages (full renderThread only if the page structure diverged).

Download: routing to Safari failed auth (no login cookie). Real fix: fetch the file in the
WebView (cookie present) and hand the bytes to the OS save/share sheet via Filesystem+Share
(added to mobile deps; ships next TestFlight build). Until then, images fall back to the iOS
long-press "Save to Photos" instead of breaking the app.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 00:48:34 +05:30
SravanandClaude Opus 4.8 91b3e5c522 mobile: kill slide end-hitch (compositor teardown), fix reopen-at-latest + native download hang (batch124)
Slide: the 40-128ms hitch was a compositor RE-RASTER at transitionend, not JS
(longtasks:[], renderMs 2-6ms in the device log). Removed contain:layout paint /
isolation:isolate from the .content slide layer and the filter:brightness parallax
on .chatcol (both force a full re-raster on teardown); defer the layer demotion two
frames past transitionend so it lands off the motion path. Also replaced the fragile
length+lastId slide-end skip (it almost never held once a live message grew the cache,
so the full innerHTML rebuild kept running at slide-end) with an exact per-message diff.

Reopen-at-latest: loadOlder wrote the GROWN thread (100+ msgs) into THREAD_CACHE, so
re-opening re-rendered all of it and stranded you mid-history as images shifted the
(pre-load) bottom past the 1200px pin guard. Open now renders the latest PAGE only;
added _forcePinOpen to glue to the newest through late image loads until the user scrolls.

Download hang: <a download> navigated the whole WKWebView away to the raw file (no back,
app frozen). Native-only capture-phase interceptor opens downloads in the system browser.
Lightbox close/download buttons: solid dark chips so they're visible over bright images.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 00:31:57 +05:30
SravanandClaude Opus 4.8 433703cfeb perf(mobile): skip redundant end-of-slide re-render (unchanged content) + load-older before rubber-band
Device log: slide is 60fps EXCEPT one ~60-130ms hitch at the END = the deferred renderThread rebuilding
the (identical) DOM at slide end. Skip it when the network result matches the cache render (signature).
Load-older: fired at scrollTop<120 = during the rubber-band over-scroll past the top (beforeScrollTop
was -289), so the prepend+anchor jerked momentum. Trigger earlier (<700, skip negative scrollTop) so it
loads while still scrolling and never interrupts the bounce.
Build marker -> 2026-07-19-batch123.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 00:10:27 +05:30
SravanandClaude Opus 4.8 a4600f6b5f fix(mobile): close the debug <script> block (batch122 left it open, breaking the keyboard block)
The perf-diagnostics script tag was never closed, so it merged with the following native/keyboard
<script> and threw 'Unexpected token <', disabling the keyboard/safe-area init. Add the missing </script>.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 00:02:48 +05:30
SravanandClaude Opus 4.8 d02d8976d3 diag(mobile): instrument slide frame-timing + render cost + load-older scroll to find the real jank
Re-add /api/dbg + a frame-gap/long-task probe over the chat-open slide, the innerHTML render time, and
before/after scroll for load-older — to measure on-device where the jank actually is (main-thread block
vs compositor) instead of guessing. Temporary; removed once fixed.
Build marker -> 2026-07-19-batch122.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 00:00:28 +05:30
SravanandClaude Opus 4.8 529bc26cb5 perf(mobile): compositor 'render-then-arm' chat-open slide + robust load-older anchor; remove diagnostics
Slide (workflow-verified): the jump was the main thread building the message DOM + scroll writes DURING
the 300ms animation. Now: render off-screen first, then arm a transform-only slide on a promoted GPU
layer (translate3d/contain:layout paint) with all innerHTML/scrollTop work deferred to transitionend
(afterOpenSlide queue). armOpenSlide runs after the sync render; appendBubble defers live writes mid-slide.
Load-older jump: anchor on the specific oldest-loaded message element (not scrollHeight math, which broke
when prepended images loaded and shifted content) and re-anchor as those images load.
Removed all /api/dbg diagnostics (client reporter + server route + spike/diag probes).
Build marker -> 2026-07-18-batch121.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 23:49:38 +05:30
SravanandClaude Opus 4.8 f3c3e08be4 perf(mobile): paginate threads to 40 (was 500) — kills the heavy-DOM jank at the source
User's insight: the jank/jumps come from rendering ALL ~500 messages on open. Load only the latest 40
(server + client PAGE), and the existing loadOlder() pages in older history on scroll-up with a scroll
anchor (no jump). Whole-thread SEARCH is a separate endpoint, unaffected. Also #1: keyboard-show only
pins to newest when already near the bottom, so replying to an OLD message no longer yanks to latest.
Build marker -> 2026-07-18-batch120.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 23:31:05 +05:30
SravanandClaude Opus 4.8 2bd8b3c8c1 fix(mobile): image pager direction + end-arrows; reset --kb on composer blur
- Pager: force the staged (opposite-edge) frame to PAINT (void offsetWidth) before animating in, so the
  incoming image enters from the correct side (was appearing from the exit side). Hide prev-arrow at the
  first image and next-arrow at the last.
- #1: keyboard closed after reply-cancel but the --kb lift stayed (empty gap). Add keyboardDidHide reset +
  a composer-blur fallback that drops --kb when focus leaves the composer.
Build marker -> 2026-07-18-batch119.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 23:11:05 +05:30
SravanandClaude Opus 4.8 da3d44cbb7 fix(mobile): clamp reply-quote (balloon), clip swipe-reply overflow, animated image pager
- Reply quote: was showing the FULL quoted body untruncated -> DIV.quote rendered 1571px, ballooning the
  bubble + leaving the chat not-at-bottom. Clamp to 2 lines (-webkit-line-clamp) + collapse newlines.
- Swipe-to-reply: translateX(+72px) on a right-edge bubble pushed past the viewport -> a horizontal scroll
  bar. .convo-msgs overflow-x:clip stops it.
- Image pager (#8): replace instant src swap with a real slide — current image slides out, next slides in
  from the opposite side; clamp at first/last (no wrap -> no blank slide past the end).
Build marker -> 2026-07-18-batch118.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 22:32:10 +05:30
SravanandClaude Opus 4.8 1f5b4bd42c fix(mobile): stop bubble/composer balloon + transition pop (shared root cause, workflow-verified)
Parallel root-cause analysis: during body.chat-opening the pane is momentarily ~0 width; word-break:
break-word / overflow-wrap:anywhere let bubbles collapse to 1-GLYPH min-content, so pre-wrap text wraps
into hundreds of lines (5684px 'balloon'); same inflates the empty/draft textarea (autoGrow->140=4 lines);
and translateX(100%) of a ~0-width pane = no travel = the 'jump'. The tall box hid in anonymous line
boxes (renderMsgBody emits bare text nodes), which is why the probe only saw the 84px timestamp span.
Fixes (all @media max-width:760px / chat-opening scoped, desktop untouched):
 1. .bubble: word-break:normal + overflow-wrap:break-word (min-content = longest word, never collapses)
 2. mobile bubble group: overflow-wrap:anywhere -> break-word
 3. .msg-link: word-break:break-word -> overflow-wrap:break-word (URL-only msgs can't reopen the hole)
 4. body.chat-opening .content: width:100vw -> definite width, no collapse frame, real slide travel
Build marker -> 2026-07-18-batch117.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 18:53:38 +05:30
SravanandClaude Opus 4.8 898d389dfe diag(mobile): probe composer measurements + transition animation + tall-bubble content
tallImg was no-img, so the balloon is a TEXT bubble stretching (even in a 17-msg chat). Add: composer/
textarea height+scrollHeight+rows+value+fontSize+lineHeight (why ~4 lines), whether chat-opening class +
.content animationName are applied (why transition jumps), and the tall bubble's text + tallest child.
Build marker -> 2026-07-18-batch116.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 17:59:58 +05:30
SravanandClaude Opus 4.8 7f7ff8ba1f fix(mobile): cap in-bubble image height (kills the stretch balloon) + sharper spike diagnostic
Spike log showed bubbles ballooning to 3576-5684px (an image loading at natural ~2778px height before
constraint). att-img is capped 240 but some image path wasn't — added .convo-msgs img/.bubble img
max-height:340px as a blanket cap so no message can stretch the thread. Enhanced spike report to name
the tall bubble's img (natural size, rendered height, computed max-height) to confirm.
Build marker -> 2026-07-18-batch115.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 17:34:31 +05:30
SravanandClaude Opus 4.8 676612a12b fix(mobile): disable zoom via viewport meta (not font-size) + reveal msgs during slide
- Revert font-size:16px !important (it made the composer ~4 lines tall). Disable iOS zoom with
  viewport maximum-scale=1,user-scalable=no instead -> composer keeps its size, AND double-tap-to-zoom
  (in-chat search arrows #3) is gone. touch-action:manipulation on controls as belt-and-suspenders.
  Custom lightbox pinch still works (it's transform-based, not browser zoom).
- Transition: show the newest message immediately (was hidden until images loaded -> empty pane slid in
  then popped = the 'jumping' feel). Pinning still prevents dance.
Build marker -> 2026-07-18-batch114.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 17:27:54 +05:30
SravanandClaude Opus 4.8 78ea36f7d0 polish(mobile): native-style parallax chat-open transition; diagnose scrollH balloon
- Transition: open now slides the whole .content pane in over the list with a subtle parallax + iOS
  easing (was an instant/panel-only slide). Close/back already parallax.
- scrollH blip: added a spike watcher that reports the tallest element (class + img src tail) when the
  thread's per-message height balloons, so we can see what stretches then settles.
Build marker -> 2026-07-18-batch113.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 17:13:39 +05:30
SravanandClaude Opus 4.8 2cadf90bc8 fix(mobile): lift composer by Capacitor Keyboard plugin height (resize:none, vv doesn't reflect kbd)
Device log (post-zoom-fix): keyboard open leaves clientHeight AND visualViewport at 926 -> vv gives 0 ->
composer covered. On resize:none the plugin's keyboardHeight is the only signal; now that zoom is gone
it's clean, so --kb = keyboardHeight lifts the composer flush, and keyboardWillShow at animation start +
the CSS bottom-transition makes it slide up WITH the keyboard (smooth). Config pinned to resize:none.
Build marker -> 2026-07-18-batch112.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 16:46:00 +05:30
SravanandClaude Opus 4.8 9f7561bf8b diag+fix(mobile): capture keyboard-open composer position; replay open slide transition
- Debug now reports curKb/compBottom/kbTop/coveredBy on keyboard events so I can see WHY the composer is
  covered (lift coming out 0) from the server log.
- Transition: force the bzConvoIn slide to replay on every chat open (CSS animation only auto-plays on a
  class change, so chat->chat looked instant). Restart via reflow + inline animation.
Build marker -> 2026-07-18-batch111.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 16:23:22 +05:30
SravanandClaude Opus 4.8 bf8df593a1 fix(mobile): force input font-size 16px with !important (was overridden by .composer-row .92rem)
Device log confirmed zoom persisted (vvW:394,vvTop:74) because .composer-row textarea{font-size:.92rem}
out-specified the generic rule, keeping the composer <16px -> iOS still zoomed on focus. !important
forces 16px on all fields so the zoom never fires. (#7 latest + budge already verified fixed.)
Build marker -> 2026-07-18-batch110.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 15:58:41 +05:30
SravanandClaude Opus 4.8 0e38bd9229 fix(mobile): stop iOS input auto-zoom (font-size 16px) = real keyboard fix; robust pin-to-newest
Device log revealed the truth:
- Keyboard: focusing the <16px composer made iOS ZOOM the page (vvW 428->394, vvTop->74), scrambling
  every viewport/keyboard calc. Set all form fields to 16px on mobile -> no zoom -> keyboard math holds.
- Latest messages: on a 500-msg chat atBottom was false because late images grow the thread after the
  initial scroll. Now re-pin on a schedule + on each image load while near the bottom.
- Budge already gone (device reported ovf:false).
Build marker -> 2026-07-18-batch109.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 14:17:20 +05:30
SravanandClaude Opus 4.8 f9d6472f77 chore(mobile): add /api/dbg diagnostics sink + client reporter (build, overflow, viewport, scroll)
Temporary: the app POSTs its real runtime state to the server log so device-only bugs (budge source,
keyboard viewport numbers, chat-open scroll state) can be diagnosed from docker logs instead of
screenshots. Removed once mobile issues settle.
Build marker -> 2026-07-18-batch108.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 13:18:58 +05:30
SravanandClaude Opus 4.8 720b1f0c40 fix(mobile): revert keyboard to live VisualViewport tracking (no timer), undo budge; add unit test
- Keyboard: the 60ms timer in batch106 sampled the viewport MID-animation (clientHeight/visualViewport
  temporarily inconsistent) and over-lifted -> gap. Reverted to reading clientHeight-visualViewport LIVE
  on every vv resize, which is correct and self-adapting (unit test: residual=46 flush on the device).
- Budge: reverted the file <input> back to display:none (the position:fixed;left:50% hack overflowed
  the viewport horizontally).
- Config back to resize:native to match the installed build (works, no rebuild).
- Added test/keyboard-lift.test.js (5/5 pass) verifying the lift math against real device numbers.
Build marker -> 2026-07-18-batch107.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 10:22:45 +05:30
SravanandClaude Opus 4.8 6e3cf95deb fix(mobile): mode-adaptive smooth keyboard + force-latest-scroll + bottom-anchored file input
- Keyboard: auto-detect resize mode. resize:none -> lift full keyboard height at willShow so the CSS
  transition slides the composer up WITH the keyboard (smooth, no iOS resize lag). resize:native ->
  residual only. Config set to resize:none for the smooth path (needs build); web stays correct on the
  current resize:native build meanwhile.
- #7: force scroll-to-bottom at 0/120/320/600ms on open so a chat always opens on the newest message.
- File picker: anchor the file <input> to a fixed bottom spot (was display:none -> iOS dropped the menu
  mid-screen); now it comes up as a bottom sheet even as the composer moves.
Build marker -> 2026-07-18-batch106.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 08:32:23 +05:30
SravanandClaude Opus 4.8 c775ca2740 fix(mobile): lock in device-independent keyboard (VisualViewport residual), remove debug
Debug confirmed resize:native shrinks the WebView by the keyboard height, so the composer only needs the
residual lift = clientHeight - visualViewport.height (46px on the test device — the prediction-bar sliver).
Tracked live via VisualViewport so it follows the keyboard. Zero per-device constants. Removed debug bar;
reverted Keyboard config to resize:native (which the logic relies on).
Build marker -> 2026-07-18-batch105.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 08:11:39 +05:30
SravanandClaude Opus 4.8 8867d0abfc fix(mobile): keyboard via VisualViewport (device-independent) + temp debug readout
Replace the hardcoded factor with the real overlap = clientHeight - visualViewport.height (works on any
screen). Adds a temporary on-screen debug line to confirm which signal the device reports, so the final
logic needs no per-device tuning. Debug removed once confirmed.
Build marker -> 2026-07-18-batch104.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 01:25:10 +05:30
SravanandClaude Opus 4.8 c5489d7ad6 fix(mobile): scale keyboard lift by measured 0.52 (plugin over-reports ~2x)
Measured from device screenshot: composer at ~678pt from bottom vs 351pt keyboard = 2x overshoot.
Scale the reported height by 0.52 so the composer sits flush on the keyboard. Single tunable const.
Build marker -> 2026-07-18-batch103.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 01:19:49 +05:30
SravanandClaude Opus 4.8 77a3ad455f fix(mobile): normalize keyboard height device-px -> points (was overshooting ~3x on 3x screens)
Root cause: the Keyboard plugin reports keyboardHeight in device pixels on this build; CSS needs points,
so the raw value overshot by the devicePixelRatio. Divide by DPR when the raw value exceeds the screen's
point height. Web-only.
Build marker -> 2026-07-18-batch102.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 01:13:57 +05:30
SravanandClaude Opus 4.8 233db2c140 fix(mobile): keyboard lift via VisualViewport (measured gap, exact) not plugin height
The Keyboard plugin's reported height overshot. With resize:none the WebView stays full-size, so
window.innerHeight - visualViewport.height is the EXACT keyboard overlap -> composer lands on the
keyboard. Web-only, works on the resize:none build already installed.
Build marker -> 2026-07-18-batch101.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 01:05:12 +05:30
SravanandClaude Opus 4.8 357f0168ba fix(mobile): keyboard resize:none + web-tunable JS lift (deterministic, no plugin fight)
resize:native gave contradictory results (covered vs header) because the safe-area plugin fights it.
Set resize:none so the native layer does nothing, and lift the composer purely in JS by keyboardHeight
* KB_LIFT. Once resize:none is built, the lift is web-tunable with zero further builds.
Build marker -> 2026-07-18-batch100.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 00:55:17 +05:30
SravanandClaude Opus 4.8 9bee2c635c fix(mobile): remove redundant --kb lift; keyboard uses resize:native alone (was double-lifting to header)
Isolated test showed 'too high' = resize:native already lifts the composer, so the JS --kb added a
second lift up to the header. Removed it. resize:native is the single mover now.
Build marker -> 2026-07-18-batch99.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 00:50:18 +05:30
SravanandClaude Opus 4.8 f55af18e90 fix(mobile): deterministic keyboard lift via --kb (single mover, no rebuild needed)
batch92 proved resize:native is a no-op with the safe-area plugin, and batch97 turned off the
safe-area keyboard offset, so re-adding the JS --kb lift makes it the ONLY thing moving the composer
= it sits exactly on the keyboard. Works on the current installed build (web-only).
Build marker -> 2026-07-18-batch98.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 00:45:06 +05:30
SravanandClaude Opus 4.8 cc2a76450f fix(mobile): native keyboard (remove JS hack), open at latest + no double-render dance, splash cfg
- #3 keyboard: the overshoot proved resize:native DOES lift the composer; my --kb JS was a second lift.
  Removed the JS hack entirely; keyboard is now purely @capacitor/keyboard resize:native. Config: turn
  OFF safe-area offsetForKeyboardInsetBug so only one mechanism moves the view (needs the build).
- #7: gate the open-time hide/reveal with _convoRevealed so the cache->network double render doesn't
  flash/dance; on mobile open at the LATEST message (skip auto-scroll to first-unread).
- #6: add SplashScreen config (bg #16294F, no spinner) — asset gen already produces the logo.
Build marker -> 2026-07-18-batch97.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 00:17:58 +05:30
SravanandClaude Opus 4.8 763b2996d1 fix(mobile): revert app-breaking interactive edge-back (iOS touchcancel left UI stuck)
The finger-follow edge gesture starting at the very left edge collided with iOS's own edge-swipe,
which fires touchcancel instead of touchend, so cleanup never ran and .content stayed transformed +
chat-dragging stuck on top -> bottom nav hidden and all clicks blocked. Back to the safe release-based
swipe (still gets showWelcome's parallax slide). Keyboard --kb inset, #7 no-dance, #8 image swipe kept.
Build marker -> 2026-07-17-batch96.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 23:58:42 +05:30
SravanandClaude Opus 4.8 3393e44691 fix(mobile): JS-driven keyboard inset, interactive edge-back, finger-follow image swipe, no-dance
- #3 keyboard (was B: chat didn't move, keyboard covered composer): native resize wasn't moving our
  fixed layout, so drive it from JS — the Keyboard plugin reports its height, we expose --kb and lift the
  composer to sit exactly on the keyboard; keyboard config -> resize:none so native won't double-adjust.
- #4: the left-edge back is now INTERACTIVE — the conversation follows the finger with the list revealed
  underneath (parallax), commit past 1/3 width else spring back (like Teams). Falls back to release-based
  bzcBack for popups/search.
- #8: the image now follows the finger sideways (and down) during the swipe, not just on release.
- #7: hold the thread hidden until its images load (max 700ms) then reveal at the bottom — no dance.
Build marker -> 2026-07-17-batch95.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 23:11:54 +05:30
SravanandClaude Opus 4.8 f2e2e8b08a fix(mobile): parallax slide-back, swipe images left/right, glue thread while images load
- #4: on back, reveal the chat list UNDERNEATH with a parallax slide-in while the conversation slides
  off to the right (slide the whole .content, list forced visible beneath) — matches the Teams push/pop.
- #8: swipe left/right in the image viewer flips to the prev/next image (not just swipe-down to close).
- #7: re-pin to the bottom as each image finishes loading (when near bottom), so late images don't
  reflow/'dance' the thread.
Build marker -> 2026-07-17-batch94.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 22:07:42 +05:30
SravanandClaude Opus 4.8 e29810ea2a fix(mobile): un-clip popups, swipe-to-reply, swipe-down-close image, no msg dance, slide-back
Addresses reported iOS observations (web-side, no rebuild):
- #1: the overflow-x:hidden I added to stop sideways-scroll was clipping the profile/bell popup menus;
  now only the chat-list scroller is clipped and long content wraps instead of widening the page.
- #9: swipe a message bubble right to reply (WhatsApp/Teams style).
- #8: swipe an opened image down to dismiss the lightbox.
- #7: hide the thread for one frame on open so messages don't visibly scroll/'dance' into place.
- #4: slide the outgoing conversation off-screen on back (inline transform, reliable across WebKit).
Build marker -> 2026-07-17-batch93.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 21:34:15 +05:30
SravanandClaude Opus 4.8 13b0f910ab ci(ios): set CFBundleVersion to Codemagic BUILD_NUMBER so each upload is unique
App Store Connect rejected the upload: 'The bundle version must be higher than the
previously uploaded version: 1'. Capacitor defaults CFBundleVersion to 1, so every
build collided with the first upload. Patch it to the monotonic BUILD_NUMBER.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 20:09:51 +05:30
SravanandClaude Opus 4.8 2ce7ec72fb fix(ios): PERMANENT native safe-area + keyboard via Capacitor 7 + safe-area plugin
Device screenshots proved the WebView reports env(safe-area-inset-*)=0, so no web/CSS/JS trick can
move content off the Dynamic Island — it must be fixed natively. Upgrade the Capacitor shell to v7 and
add @capacitor-community/safe-area (populates env() with real per-device insets, edge-to-edge) and
@capacitor/keyboard (resize:native so the composer sits directly on the keyboard, like Teams).

- mobile: Capacitor 6 -> 7 for all plugins; add safe-area + keyboard; SafeArea/Keyboard config.
- codemagic: npm ci -> npm install (dependency set changed); drop stale Cap6 lockfile.
- web: remove the temporary StatusBar/34px hacks (env() now works); keep var(--sat)/var(--sab) padding.
- share/connect: back link now respects env(safe-area-inset-top/left).
- add native-style slide in/out (push/pop) animation when opening/closing a chat on mobile.
Build marker -> 2026-07-17-batch92.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 17:36:14 +05:30
SravanandClaude Opus 4.8 a609bfd2e0 fix(ios): status-bar inset via StatusBar plugin, hide nav in open chat, bulletproof h-scroll
Root cause from device screenshots: the iOS Capacitor WebView reports env(safe-area-inset-*)=0,
so CSS padding did nothing and content slid under the Dynamic Island (every page: home/share/
connect/login) and the bottom nav was clipped.
- Use the already-installed StatusBar plugin: setOverlaysWebView(false) pushes content below the
  status bar natively (no rebuild). Applied on all entry pages via a small native-chrome snippet.
- Hard-code the bottom home-indicator inset (--sab:34px on html.native-ios) since env() can't give it.
- #2 keyboard: hide the bottom tab bar inside an open conversation so the composer sits directly on
  the keyboard (was sandwiched between composer and keyboard).
- #3 h-scroll: clip every scroll surface + overflow-wrap:anywhere so long content can't widen the page.
Build marker -> 2026-07-17-batch91.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 17:19:37 +05:30
SravanandClaude Opus 4.8 95e13a707e fix(ios/mobile): safe-area insets, no keyboard auto-open, no h-scroll, swipe-back, hide desktop-download
Reported on the iOS TestFlight build:
- #1/#5 notch/home-indicator: apply top safe-area inset per surface (chat list column +
  content panels) instead of on the whole shell, so headers sit below the Dynamic Island
  and nothing is cut off top/bottom.
- #2: opening a chat no longer auto-focuses the composer (was popping the phone keyboard
  and shoving the layout up); tapping the box still focuses normally.
- #3: overflow-x:hidden + max-width:100vw guards so pages can't pan left/right.
- #4: left-edge swipe-right now triggers the existing bzcBack() (close popup/search/open
  chat) — a native-feeling back gesture with no native code.
- #6: hide the 'Download app' (Windows) button when running inside the Capacitor app.
Build marker -> 2026-07-17-batch90.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 16:44:12 +05:30
SravanandClaude Opus 4.8 2e4e0210b0 ci(ios): internal-testing-only publishing + declare encryption exemption
The IPA now builds, signs, uploads and processes on App Store Connect. Set
submit_to_testflight=false so the build stays green via internal testing; external
beta review (which needs Test Information + a demo login) is opt-in later. Also set
ITSAppUsesNonExemptEncryption=false in Info.plist so ASC stops prompting for export
compliance on every upload.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 16:02:28 +05:30
SravanandClaude Opus 4.8 1ffc69469b ci(ios): pass a fixed CERTIFICATE_PRIVATE_KEY so the distribution cert is reusable
fetch-signing-files --create alone mints a throwaway distribution certificate whose
private key dies with the build VM; the next build then finds a cert it has no key
for and fails 'Cannot save Signing Certificates without certificate private key'.
Supplying our own fixed private key (secure var in the ios_signing group) makes the
cert reproducible and reusable across builds.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 15:47:28 +05:30
SravanandClaude Opus 4.8 84315ccfb4 ci(ios): run xcode-project use-profiles after pod install, not before
The archive failed with 'App requires a provisioning profile' because use-profiles
ran inside the signing step before pod install created the workspace, so the
profile never bound to the App target. Move it to just before build-ipa, matching
Codemagic's Capacitor recipe.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 13:39:01 +05:30
SravanandClaude Opus 4.8 054b0bb70f ci(ios): remove ios_signing auto-fetch block so first build creates its own signing profile
The environment.ios_signing block makes Codemagic fetch an EXISTING profile at
build init and fails with 'No matching profiles found' on a brand-new app. The
'Set up code signing' script already creates the cert+profile via
fetch-signing-files --create, so the block was both redundant and blocking.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 13:30:11 +05:30
Sravan c18e55b5ec ci: remove hard-coded personal email from codemagic.yaml (watch builds on the dashboard) 2026-07-16 18:09:26 +05:30
Sravan 42e0122504 docs(ci): how to connect Codemagic to self-hosted Gitea over SSH (expose port + read-only deploy key) 2026-07-16 15:46:22 +05:30
Sravan 6b5ca687e4 docs(ios): track ReplayKit screen-broadcast + native audio-routing as phase-2 follow-ups
iOS WebViews don't support getDisplayMedia, so sharing the device screen into a meeting needs a
native ReplayKit Broadcast Upload Extension (+ App Groups) — viewing a shared screen works today.
Also note native audio routing (AVAudioSession) as a phase-2 plugin. Adjusted the App Review note
so we don't advertise iOS screen-sharing before that extension ships.
2026-07-16 14:47:28 +05:30
Sravan 3b09702593 docs(ios): add App ID registration step (fields + Push Notifications capability) 2026-07-16 14:34:46 +05:30
SravanandClaude Opus 4.8 bc909cb0c0 build(ios): Codemagic pipeline + runbook for App Store (no Mac needed)
The iOS app is a Capacitor shell over the live web UI — the web/server side is already fully
Capacitor-ready (nativePlatform() detects Capacitor; setupNativePush registers APNs tokens via
/api/v1/devices; the APNs sender is built into server/push.js, config-gated). So this adds only
the build/sign/upload path:

- codemagic.yaml: macOS-cloud workflow that generates the iOS project, patches Info.plist,
  generates icons/splash, signs via an App Store Connect API key (automatic signing), archives,
  and uploads to TestFlight. No Mac required.
- mobile/scripts/ios-patch.sh: adds the App-Review privacy usage strings (camera/mic/photos) +
  display name to the generated Info.plist.
- mobile/IOS_SETUP.md: click-by-click runbook — ASC app record, API key, Codemagic integration,
  first build, APNs key → server .env, and the public-submission checklist.

Bundle id com.bizgaze.connect. No secrets committed — Apple keys live in Codemagic + server .env.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 16:45:10 +05:30
SravanandClaude Opus 4.8 c8f383cbcb fix(gif): send a GIF as the active reply; show GIF (not URL) in reply bar/quote (batch89)
sendGif ignored replyTarget, so picking a GIF while replying sent a plain message instead of a
reply. It now carries replyTo and clears the reply, like sendMessage. Reply bar + quote show
'GIF' instead of the raw media URL.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 15:54:31 +05:30
SravanandClaude Opus 4.8 a172214c44 fix(gif): fixed-height grid rows so GIFs stop stacking over each other (batch88)
aspect-ratio:1 on the grid cells collapsed to ~0 height in the emoji-pop flex column, so the
images overflowed and overlapped. Use grid-auto-rows + a fixed cell height instead.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 15:46:05 +05:30
SravanandClaude Opus 4.8 02075fbd47 feat(chat): GIF picker via server-proxied GIPHY (#5) (batch87)
- Server: GET /api/gifs proxies GIPHY search/trending. The API key is read from the server
  env only (config.GIPHY_API_KEY, from the gitignored .env) and NEVER reaches the browser;
  the picker is hidden when it isn't configured.
- Emoji picker gains a GIF tab (separated from the emoji categories) with a search box + a 2-col
  grid, "Powered by GIPHY" attribution. Clicking a GIF sends it immediately.
- GIFs are HOTLINKED to GIPHY's CDN (their terms require this — no re-hosting): the message body
  is the GIF url, and a body that is a lone GIF url renders inline as the animated GIF (reusing
  the image/lightbox path). Sidebar previews + notifications show "🎞️ GIF", not the raw url.

Key is NOT in git — set as GIPHY_API_KEY in the server .env.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 15:30:44 +05:30
SravanandClaude Opus 4.8 c3dc47a94c fix(meetings): Past shows only scheduled + instant meetings; ad-hoc calls hidden w/o recording (batch86)
Revised rule per user: a call is not a meeting. Past meetings now = real SCHEDULED meetings +
INSTANT meetings (Start-a-meeting, logged in call_history as adhoc). Ad-hoc CALLS — 1:1 direct,
group calls, and a 1:1 that a 3rd person joined — are shown ONLY if they produced a
recording/transcript. Dropped the earlier ">2 participants shows it" exception.
Also stop rendering a bare "Host: —" on logged call cards.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 14:58:48 +05:30
SravanandClaude Opus 4.8 f708cc2f12 fix(meetings): hide empty 1:1 direct calls; date from/to validation + field alignment (batch85)
#3 The 136 "Direct Call" cards: calls.js writes a scheduled_meetings row for EVERY call
   ("Direct Call" / "Group call"), which is separate from the call_history table. Those rows
   came through the scheduled-meetings path and my earlier filter never touched them. Now an
   auto call-history row is shown in Past ONLY if it produced a recording/transcript OR the
   call ever held >2 people (peak from the call log); plain 1:1s with neither are dropped.
   Removed the now-redundant callRows synthesis. Recordings stay attached (rows WITH a
   recording are always kept).
#4 Date range: from ≤ to enforced by disabling out-of-range days in each picker (can't pick a
   from after to, or a to before from). Filter controls share one height/baseline so the
   calendar icon, preset dropdown and date fields align cleanly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 13:19:12 +05:30
SravanandClaude Opus 4.8 fad8a52da4 fix: last-seen backfill, report-style date filter, hover overflow (batch84)
#1 Last seen still read "Offline": last_seen is a NEW column, so every existing user was NULL
   until they happened to reconnect. Backfill it from the newest message each user sent (best
   evidence we already have), and also stamp it on message send — not just on connect/disconnect
   — so it stays fresh if a socket never closes cleanly.
#3 Past-meetings filter is a report filter now: presets (Today, Yesterday, Last 7/30 days, This
   month, Year to date, All time) plus a Custom range that uses the SAME branded calendar popup
   as the scheduler. The raw <input type=date> looked foreign and behaved differently per browser.
#4 Hover action row overflowed off-panel on SHORT received messages: it's wider than the bubble
   and was anchored to the bubble's right edge, so it ran off the left. Received bubbles now
   anchor it from the left (growing into the empty space); own messages keep the right anchor.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 01:02:59 +05:30
SravanandClaude Opus 4.8 6fa261b68f feat(meetings): call log, past pagination + date filter; last-seen exact time (batch83)
#7 The past list was hard-capped at 12 (a .slice(0,12) in the client) and there was NO record
   of how many people were ever in a finished call — so the "only calls with >2 people" rule
   was impossible to apply. Added a call_history table: signaling tracks the HIGH-WATER
   participant count per room and logs the call when the room tears down (scheduled meetings
   are skipped — they already have their own row).
   Past meetings now follow the rules asked for:
     • a plain 1:1 direct call is NOT listed — unless it produced a recording/transcript
       (those already surface as recording entries);
     • a call that ever held MORE than 2 people IS listed (e.g. a 1:1 a third person joined),
       showing its participant count and duration;
     • entries are visible only to people who were actually in the call (or the group).
   Server-side pagination (10/page) + a from/to date filter; nothing is double-listed.

#2 Last seen now shows the exact time/date, WhatsApp-style — "last seen today at 1:36 PM",
   "last seen yesterday at 10:15 AM", "last seen 14/07/2026 at 9:00 AM" — instead of "10
   minutes ago".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 00:42:53 +05:30
SravanandClaude Opus 4.8 860a7bd6cf feat(chat): emoji speed, action rework, copy, phone links, image zoom, upload %, profile card, last seen (batch82)
#6 Emoji were slow because Twemoji swapped EVERY emoji for an <img> fetched individually
   from a CDN — opening the picker fired hundreds of image requests. Now uses the OS's own
   colour emoji font: instant, zero network. twemojify() kept as a no-op.
#4 Message hover row reworked: three one-tap reactions (Like/Laugh/Surprised) + the emoji
   picker + (own) Edit. Reply / Forward / Copy / Delete moved behind a ⋮ menu. Added Copy.
   The row now sits FULLY above the bubble (was top:-14px, overlapping the first text line).
#9 Phone numbers linkify to tel: — mobile gets the OS "call this number?" prompt; desktop has
   no dialer so it offers to copy. Regex kept conservative (10–15 digits, needs +/grouping) so
   it won't grab amounts, dates or 6-digit meeting codes.
#3 Image preview zooms: wheel + pinch + double-click + ± buttons, drag to pan, keys (+/-/0),
   cursor-anchored. Arrows hide while zoomed so panning isn't hijacked.
#8 Upload progress: fetch() can't report upload progress at all, so a large file just said
   "uploading…". Switched to XHR (upload.onprogress) → real bar + %, and cancel aborts in flight.
#1 Clicking a sender in a group opens a mini profile card (photo, presence, last seen) with a
   Message button that opens the 1:1 (and a view-photo button).
#2 Last seen: new users.last_seen column, stamped on connect and when the last socket drops;
   carried on the presence broadcast, so an offline contact reads "Last seen 10 minutes ago"
   instead of a bare "Offline".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 23:46:52 +05:30
SravanandClaude Opus 4.8 667b4c69d5 perf(update-check): throttle + single-flight the build check (batch81)
The check itself is cheap (one ~35-byte response, served from a variable the server reads
once at startup), but it was hooked to BOTH `focus` and `visibilitychange` with no throttle —
and a single alt-tab back into the app fires both, so every refocus cost two redundant
requests. Now: one check per minute at most, never overlapping itself, and it stops checking
entirely once a new build is known (the retry loop takes over).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 16:14:50 +05:30
SravanandClaude Opus 4.8 c1f01e796a fix: DP missing in 1:1 for un-messaged contacts; make web updates fully silent (batch80)
DP bug — real cause found (client-side, not the DB):
loadSidebar builds a DM row for every contact you haven't messaged yet, but it copied only
{name, online} from the contact and DROPPED `avatar` (and status/email). So an un-messaged
contact always rendered initials in the 1:1, while the SAME person showed their photo in a
group (which reads /api/groups/members). Carry the whole contact through.
Kept a server-side safety net: avatarsFor() now indexes known photos under person-id, email
AND name, so a duplicate row missing a photo can match on any of them (the previous single
composite key missed twins with different emails).

Web updates are now completely silent: no banner, no toast. A web build is an implementation
detail — surfacing it makes users reason about "web build vs app version", which is exactly
the confusion to avoid. New code simply applies itself as soon as it's safe.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 16:06:28 +05:30
SravanandClaude Opus 4.8 2aeeb0a096 fix: auto-apply new web builds; DP missing in DM but shown in group (batch79)
Auto-update (no manual step, no version confusion):
- New code now applies ITSELF. The client polls /api/build and, as soon as it's SAFE,
  silently hard-reloads onto the new build. Safe = not in a call, no live screen session,
  no dialog open, nothing half-typed; if the user is busy we wait and apply the moment
  they're free. A brief "Updated to the latest version" toast confirms it.
- Removed the "web build" row from Settings: users must never have to reason about an app
  version vs a web build. The only version surfaced is the desktop app's (auto-updater).

DP bug: a contact showed their photo in a GROUP but fell back to initials in the 1:1.
Two causes, both handled:
- Duplicate rows for one person (signed in by email once and by mobile another time before
  the bizgaze_user_id merge landed) — only one row carries the DP, and the group happened to
  reference the row WITH the photo. avatarsFor() now keys rows by stable person identity
  (bizgaze person id → email → name) so a photo-less row borrows its twin's photo. Applied to
  contacts, conversations, group members and group info.
- A DM whose counterparty was merged away is now keyed by the SURVIVING account, so the row
  carries that account's name/photo/presence (and split threads collapse into one).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 15:34:35 +05:30
SravanandClaude Opus 4.8 a0017f2036 feat: detect new web builds + hard-refresh escape hatch (0.1.17/batch78)
Root cause of "the fix works on mobile but not on desktop/web": nothing was wrong with
the code — the desktop app now CLOSES TO TRAY, so it can run for weeks on the page it
loaded on day one and never re-fetch after a deploy. There was also no way to force it
off a stale page (no menu bar → no reload accelerator).

- Server: GET /api/build returns home.html's __BUILD marker.
- Client: polls it (boot, on focus/visibility, every 10 min); when the server's build
  differs from the running one, shows a branded "A new version is available — Refresh"
  banner. Settings gains an always-available "Refresh app" with the current build shown.
- hardReloadApp(): in the browser it unregisters service workers + clears CacheStorage
  then reloads cache-busted; in the shell it calls the native hard reload.
- Desktop: hard-reload IPC (clears the session HTTP cache + reloadIgnoringCache), wired to
  Ctrl+R (reload), Ctrl+Shift+R / F5 (hard reload), and a "Refresh app (get latest)" tray
  item. Previously there was literally no way to clear the cache from the app.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 13:38:55 +05:30
SravanandClaude Opus 4.8 5a138f5bc4 fix(meetings/mobile): PTR, ringing tile, responsive bar, ⋮ toggle, add-people search (batch77)
#6 Pull-to-refresh genuinely broken: the indicator lived INSIDE the scroller (so it
   scrolled away unseen) and only became visible after ~96px while the trigger fired at
   70 — and preventDefault was deferred until the pull passed 10px, by which point
   iOS/Chrome had already claimed the gesture. Indicator now mounts on the list's
   non-scrolling parent with the branded loader, the gesture is claimed immediately, and
   the trigger is reachable. Works on Android + iOS.
#4 ⋮ More now TOGGLES (it used to remove then instantly rebuild, so it never closed).
#3 "Ringing…" is centred over the tile with the avatar/name dimmed behind it.
#2 Meeting bar fits the screen on mobile: one row of controls sized to the viewport,
   room code on its own line, safe-area padding.
#5 Search box in the in-call Add-people tab.
#1 Speaker control: setSinkId (the only web API for audio output) is NOT implemented in
   Android Chrome or iOS Safari — the OS owns the route there, and iOS forces loudspeaker
   whenever a mic track is live. Rather than ship a button that silently does nothing, it
   now only renders where output switching actually works. Real speaker/earpiece/Bluetooth
   switching on phones needs the native (Capacitor) audio plugin.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 12:35:01 +05:30
SravanandClaude Opus 4.8 e8c0b1889f fix: off-state colors, mobile audio route, iOS rail/PTR, in-place join, draggable bars (0.1.16/batch76)
Regressions I introduced, now fixed:
#8d Mic/Camera OFF went DIM — my new `.meet-ic.off` rule overrode the original RED.
    Scoped the lite style to the speaker button only; mic/cam OFF are red again.
#7  Guest "left the meeting" was unstyled (dark text on blue, no card): the card CSS was
    scoped to .guest-prejoin only. Shared with .guest-left. Rejoin no longer dead-ends —
    if the room is gone we say "This meeting has ended" and hide the button.

Reported:
#8d Speaker button no longer shows on desktop (devices already live under the mic ▾).
#8c/#8e Mobile speaker button now CYCLES Speaker → Earpiece → Bluetooth (icon follows the
    route); dropped the redundant "Audio devices" entry from the ⋮ menu.
#8a iOS gap under the bottom rail: the page rubber-band-bounced, exposing background beneath
    the fixed bar. overscroll-behavior:none + fixed body pins it.
#8b Pull-to-refresh now fires on iOS too (overscroll-behavior:contain on the lists so Safari's
    rubber band stops swallowing the gesture; scrollTop>2 tolerance for momentum).
#5  A meeting link no longer opens a whole new window: same-origin urls navigate the main
    window in the shell, and a link clicked in chat joins the meeting IN PLACE.

New observations:
1. The viewer's mic now starts MUTED on a screen session (and can actually be un/muted).
2. iOS lightbox close/download buttons moved below the Dynamic Island (safe-area insets).
3. Meeting chat: recipient picker moved to the BOTTOM next to the input; a private message
   auto-targets your reply back to that person; private vs everyone are visibly different
   (brand amber vs blue/neutral) and the compose area tints in private mode.
4. Meeting bar + screen-session bars are DRAGGABLE (position remembered) so they stop
   covering the shared screen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 22:24:36 +05:30
SravanandClaude Opus 4.8 48b6a4050a fix: SFU participants, duplicate peers, RC without login, devices, mobile bar (batch75)
#4 Participants list only ever showed "you": in SFU mode meetMakePeer drew a tile but
   NEVER added to meetPeers, so the roster (and the meeting-chat "To" list) was empty.
#5 Same person as several tiles: each tab/reload/reconnect gets a new peerId, leaving
   ghost peers. One uid = one tile now (dropDupPeers keeps the newest).
#1 Remote control needed a login: /share opened top-level (the no-login "Share my
   screen") has the preload bridge RIGHT THERE, but we only postMessage'd to a parent
   frame that doesn't exist — so control never armed. Use the native bridge directly.
#2 Sharer's session bar moved to the shared Lucide icon set (was old inline SVGs).
#3/#6 Device menu: collapse Windows' "Default -"/"Communications -" triplicates into one
   clean list (single "System default" + each real device once), and actually re-apply the
   saved mic/speaker to the room on connect so a picked headset/Bluetooth is used.
#7 Guest "left the meeting" screen rebranded to match the new pre-join.
#9 Tapping a member in group info opens the private conversation with them.
#8a iOS bottom rail: border-box + height incl. the home-indicator inset, px env() fallbacks
   (unitless 0 breaks calc() in Safari), promoted layer so it stops drifting.
#8c/#8e Mobile meeting bar: Mic / Camera / Speaker / End only; screen, record, transcript,
   chat, participants move behind a ⋮ More menu. No mic device dropdown on mobile.
#8d Speaker button reflects the real route: speaker-on / speaker-off (dimmed) / bluetooth
   when a headset is in use; re-detected on devicechange.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 15:39:05 +05:30
SravanandClaude Opus 4.8 94b8fac32f fix: guest pre-join, post-admit lobby, audio device menu, RC keyboard (0.1.15/batch74)
1. Guest pre-join redesigned: brand backdrop, live camera preview, mic/cam toggles
   applied on entry, initials avatar, name required, "host may admit you" note.
2. Post-admit bug: the guest stayed on "Waiting for the host…" forever — the lobby
   screen had replaced the call UI and meeting-joined never re-rendered it. Now it
   rebuilds the call on admission (_inLobby).
3. Audio devices: dropped the standalone headphones button. The MIC now has a ▾ caret
   opening one Teams-style menu with Speaker + Microphone sections (radio-selected);
   speaker uses setSinkId/LiveKit switchActiveDevice, mic switches the live input.
   Mobile gets a speakerphone toggle that prefers a connected BT/headset when off.
4. Remote-control keyboard:
   - Injector now maps the PHYSICAL key (KeyboardEvent.code) instead of the character,
     so Shift+1 types "!" etc. Character mapping was why typing "performed differently".
   - Keys reach the sharer ONLY while control is ENGAGED (window focused AND you clicked
     their screen). Minimised/unfocused/chat typing stays local. Esc or clicking away
     releases; modifiers are released on disengage so nothing sticks.
   - Explicit control icons: viewer gets a Control ON/OFF button (green when engaged) +
     an on-screen hint; the SHARER gets a control icon beside mic/chat to allow/stop
     access at a glance, synced with the consent dialog and banner.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 13:18:01 +05:30
SravanandClaude Opus 4.8 82fa4a24d3 feat(meetings): in-meeting chat — Everyone or direct-to-participant, brand-styled (batch73)
Adds an ephemeral chat inside meetings (relayed over the meeting signaling socket,
not persisted). A chat button in the meeting bar (with an unread badge) opens a
brand-styled panel: pick "Everyone" or a specific participant (private), send with
Enter/Send. Direct messages are marked private on both sides. Works for guests too.
Chat state resets when you leave the call.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 15:08:24 +05:30
SravanandClaude Opus 4.8 d0f9355553 feat: close-to-tray, meeting lobby, speaker select, link expiry, mobile RC touch (0.1.14/batch72)
General #1/#2 (closed-app notifications): the desktop app now CLOSES TO TRAY instead
of quitting, keeping its chat WebSocket alive so calls/messages still notify. Tray
icon + menu (Open / Quit), single-instance lock, first-close hint.

Guest #4 (lobby/admit): meetings can require the host to admit guests joining by link.
Setting on the schedule form ("Guests must be admitted by the host", default on) +
ad-hoc default. Guests wait on a "waiting to be let in" screen; the host gets an
Admit/Deny prompt; auto-cleanup on leave. Logged-in members always join directly.

Guest #5 (speaker): headphones/speaker output picker in the meeting (setSinkId),
remembered and applied to every tile.

Guest #3 (link expiry): guest link/token dies ~2h after a scheduled meeting's end
(HTTP 410) with a clear message; live-room links expire when the room empties.

RC #4 (mobile): touch→mouse mapping so a phone/tablet viewer can control (tap=click,
drag=move). Uses the same letterbox-correct coordinate mapping.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 14:44:58 +05:30
SravanandClaude Opus 4.8 bd488b3286 fix(rc+guest): coordinate/keyboard/UI for remote control; guest media + panel fixes (batch71)
Remote control (connect.html viewer):
- Coordinate offset fixed: map clicks to the actual video CONTENT rect (object-fit
  letterbox-aware), not the element rect — cursor now lands where you click.
- Keyboard: capture at document level while a session is live (the <video> lost
  focus on bar clicks → keys did nothing). Skips the chat input.
- Control bar moved to bottom-right with tiny modern icons; video fills the viewport.
- Smoother cursor (mousemove ~60/s).

Guest meetings:
- Guest mic inaudible + guest invisible in the participant list + outsider screen
  share not showing (#1/#8/#11): root cause was the SFU media→tile map keyed on
  identity==uid, but guests had a random LiveKit identity and a null signaling uid.
  Guests now carry ONE stable id across signaling (meeting-join guestId) and the
  LiveKit token identity, so their media attaches and they appear to everyone.
- Guests can't add participants (#9) and don't see the transcript button (#12).
- Search box in the schedule participant list (#10).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 14:28:37 +05:30
SravanandClaude Opus 4.8 e562099344 feat(remote-control): viewer can control a desktop sharer's screen, with consent (0.1.13/batch70)
Fixes the core "viewer can't control the sharer even on desktop" gap. Three root
causes addressed:
- share.html DISCARDED every input-channel message (onmessage=()=>{}). It now
  parses the viewer's mouse/keyboard events and forwards them to the desktop shell.
- The main desktop app had NO OS injector (it lived only in the separate agent).
  Ported the nut-js injector (agent/input/inject.js) into desktop/input, wired an
  inject IPC + injectInput bridge, HARD-gated behind a consent flag (rcArmed).
- /share runs in an iframe (no direct bridge access) → it postMessages input to
  the top frame (home.html), which relays to the native bridge.

Consent + safety: the sharer sees an Allow/Deny prompt the first time the agent
interacts; while active a persistent "your screen is being controlled — Stop"
banner; instant revoke; auto-release on session end/teardown. Browser sharers stay
view-only (no OS injection possible). nut-js is an optionalDependency (N-API, ABI-
stable across Electron) — degrades to no-op if the native module is unavailable.
Windows-first; maps to the primary display.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 16:30:39 +05:30
SravanandClaude Opus 4.8 7bc40d8397 feat(meetings): email invites + add external participants by email + share link (batch69)
#4: scheduled meetings can now invite people who aren't on Connect.
- SMTP config (config.js, env-gated: SMTP_HOST/PORT/USER/PASS/FROM/SECURE,
  PUBLIC_BASE_URL) + a small nodemailer wrapper (mailer.js) with a branded
  meeting-invite template carrying the guest join link. No-op until SMTP is set.
- /api/meetings/schedule + /update accept participantEmails; external emails are
  persisted (scheduled_meetings.guest_emails migration) and emailed the guest
  link (plus any invited Connect users with an email on file). Fire-and-forget —
  a mail outage never fails scheduling.
- Meetings list DTO returns `link`; schedule form gains an "Invite by email"
  chip input; each scheduled-meeting card gets a "Copy link" action.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 16:02:45 +05:30
SravanandClaude Opus 4.8 466c0d5d70 feat(meetings): guest join by link — external people can join without a Connect account (batch68)
- Server: POST /api/meetings/guest-token issues an unauthenticated LiveKit token
  for a room that is currently live or a valid scheduled meeting (throwaway guest
  identity). Mesh signaling already tolerates anonymous peers.
- Client: opening /home?meet=CODE while signed out runs a lightweight GUEST mode —
  name prompt, then straight into the meeting with the full meeting client and the
  chat/sidebar chrome hidden. Signed-in users who open the link auto-join.
- sfuConnect uses the guest token for guests. Guests get a friendly "left the
  meeting / rejoin" screen (no chat to fall back to).
- Meeting "Add people" panel gains a "Copy invite link" (copyMeetingLink) that
  copies the guest link — foundation for the emailed invites in #4.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 15:44:28 +05:30
SravanandClaude Opus 4.8 9b86def921 feat(search): only surface Connect users; clear "not on Connect"/"not found" (batch67)
Contact search no longer lists BizGaze directory people who aren't on Connect as
addable rows (they can't be messaged). It shows only people actually on Connect;
if a search matches nobody on Connect it shows either "Not on Connect" (they exist
on BizGaze but haven't signed in) or "Contact not found".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 15:36:18 +05:30
SravanandClaude Opus 4.8 aa4bb2902d fix: group toast icon, left-click spell suggestions, scroll no-yank (0.1.12/batch66)
1. Group message toast had no icon: the generated group icon is a data: URL, but
   the 0.1.11 handler only used ALREADY-cached icons and skipped it. data: URLs
   are synchronous — use them immediately, so every group toast shows an icon.
2. Spell suggestions on a LEFT click: clicking a red-squiggled word in the message
   box now pops the native suggestions menu (renderer asks the shell to synthesize
   a right-click at that point → real dictionary suggestions). No right-click.
3. Scroll felt "stuck" because an incoming message yanked you to the bottom even
   when you'd scrolled up to read history (worse now that bg messages arrive live).
   appendBubble now keeps your position unless you were at the bottom / it's your
   own message, and surfaces the "jump to latest" control instead.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 14:57:02 +05:30
SravanandClaude Opus 4.8 1dfb6b16cb fix: unstick scrolling, group toast icon, seen separator, autocorrect (batch65)
1. Scroll felt "stuck": the seen-by card had TWO nested scroll containers
   (.modal overflow-y:auto + inner .seen-scroll) fighting each other — card is
   now overflow:hidden so only the inner list scrolls. Global scrollbar widened
   6→11px, dropped the inset border, added a min thumb length so it's easy to
   grab and drags smoothly everywhere.
2. Group message toast now carries a generated group icon (initials on a brand
   disc) when the group has no photo — was iconless.
3. Seen / Not-seen split by a visual divider in the message-info popup.
4. Auto-correct: curated common-typo dictionary applied as you type (on a word
   boundary) — corrections happen automatically, no right-click. Web/desktop/mobile.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 13:56:18 +05:30
SravanandClaude Opus 4.8 adafc8c960 fix: seen-by layout, group-name notif, teams-style badge, excel paste, spellcheck (0.1.11/batch64)
1. Message-info popup: ONE scroll region for the whole list with sticky
   Seen / Not-seen headers (was a separate scrollbar per section), realigned.
2. Group message notifications now title = GROUP name, body = "Sender: text".
3. Unread taskbar badge: solid red rounded-square (Teams-style) + white outline
   instead of the gradient disc.
4. Pasting Excel/Sheets cells no longer uploads a screenshot — when the clipboard
   carries real text, the text is pasted; image-only clipboards still upload.
5. Spell check in the message box (red squiggles) with a right-click menu of
   corrections + add-to-dictionary; spellcheck enabled on the textarea.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 13:25:37 +05:30
SravanandClaude Opus 4.8 70a776fa6c fix: real-time bg messages, join clears invite, richer seen-by, nicer badge (0.1.10/batch63)
1. Notifications/messages lagged when the desktop window was backgrounded —
   Electron throttles hidden renderers by default, stalling the chat WS's
   onmessage + timers. Set backgroundThrottling:false → real-time in the tray.
   (Transport is WebSocket; no Firebase needed.)
2. Joining a call via the header Join button left the Join/Decline invite popup
   on screen — enterMeeting now dismisses the invite (and stops the ring).
3. Seen-by is now "Message info": real contact DPs, a "Not seen yet" list for
   groups, and dark lettering (white-on-pastel initials were invisible).
4. App-icon unread badge: red→pink gradient pill with a white ring + shadow,
   drawn at 2x, instead of a flat red disc.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 12:51:14 +05:30
SravanandClaude Opus 4.8 62a5f750af fix(desktop): notification click no longer reloads to a dead page (0.1.9/batch62)
Root cause of the persistent "notification opens a dead page / no Join" bug:
openFromNotif gated the in-place open on `window.ME`, but ME is declared with
`let` — which never creates a global property — so window.ME was ALWAYS
undefined and every click hit the full-page-reload fallback. Use bare `ME`.

Also:
- Notifications fire instantly: never block on the DP download. Use the photo
  only if already cached; warm the cache in the background + pre-warm all
  contact DPs on chat load (precache-avatars IPC). Removes the ~2.5s lag.
- Wire call Join/Decline handlers BEFORE firing the OS notification so Join is
  live the instant the invite popup appears (was dead until the toast settled).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 18:00:56 +05:30
SravanandClaude Opus 4.8 6ed0fa0ea0 fix(desktop): custom screen picker, single restart prompt, reliable call Join (0.1.8/batch61)
- Custom branded screen/window picker (picker.html) — useSystemPicker was
  silently no-op on Win11 and auto-shared the primary display with no choice.
- Use checkForUpdates (not ...AndNotify): drops electron-updater's own native
  "Update ready" toast that duplicated our in-app banner (two restart prompts).
- Call notification DP: wait up to 2.5s for the caller photo on persistent
  (call) toasts instead of 600ms so the DP actually shows.
- Re-surface Join/Decline invite when opening a chat with an active incoming
  call, so a call-notification click always lands on a joinable call.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 17:38:50 +05:30
SravanandClaude Opus 4.8 4e2ccb5d60 fix: seen-by popup (#2), info-view DP preview (#5), notification DP cache + no Close btn, branded update flow (0.1.7)
- #2: 'Seen by' opens an on-screen popup listing readers (was a flash toast).
- #5: the DM contact-info view now shows the DP; clicking it previews full-size.
- Desktop notifications: cache DPs per-sender (fast AND with photo after the first);
  removed timeoutType:'never' which added an unwanted 'Close' button.
- Update flow: dropped the unbranded native restart dialog — the branded web banner
  handles Restart. Banner text clearer ('Downloading update…'), plus an update
  indicator that cascades profile 'i' badge → Settings → version line, with the
  Settings button becoming 'Restart now' when the update is downloaded. desktop 0.1.7.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 16:56:39 +05:30
SravanandClaude Opus 4.8 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
SravanandClaude Opus 4.8 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