305 Commits

Author SHA1 Message Date
Sravan 9c2ba847b0 meeting: unlock iOS media playback on Join so remote call audio isn't silent until you mute/unmute (batch143) 2026-07-20 00:08:25 +05:30
Sravan 4e78448743 mobile: revert finger-following swipe-back to the working release-based version (interactive one had an undiagnosable-blind layout offset) (batch142) 2026-07-20 00:01:33 +05:30
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
Sravan 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
Sravan 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
Sravan 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
Sravan 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
Sravan 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
Sravan 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
Sravan 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
Sravan 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
Sravan 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
Sravan 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
Sravan 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
Sravan 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
Sravan 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
Sravan 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
Sravan 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
Sravan 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
Sravan 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
Sravan 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
Sravan 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
Sravan 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
Sravan 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
Sravan 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
Sravan 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
Sravan 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
Sravan 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
Sravan 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
Sravan 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
Sravan 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
Sravan 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
Sravan 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
Sravan 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
Sravan 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
Sravan 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
Sravan 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
Sravan 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
Sravan 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
Sravan 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
Sravan 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
Sravan 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
Sravan 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
Sravan 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