Commit Graph

210 Commits

Author SHA1 Message Date
Sravan 46b5ae16a4 speaker: force-add audio-route plugin dist stubs (global dist/ ignore would drop them from a fresh clone) 2026-07-20 17:07:01 +05:30
Sravan c6f236ecf9 speaker: make AudioRoute a real Capacitor plugin PACKAGE so it registers (device log: has:0)
Device telemetry proved the app-embedded AudioRoute class never registered (not in
Capacitor.Plugins) — appending a CAPBridgedPlugin to AppDelegate.swift gets stripped/undiscovered
in release builds. The plugins that DO register (Share, Camera, Filesystem) are all npm packages
wired by cap sync. So AudioRoute is now a local plugin package (mobile/plugins/audio-route,
file: dep in mobile/package.json) with a podspec + CAPBridgedPlugin Swift — cap sync adds its pod
and Capacitor registers it like the others. load() sets the launch speaker default; setSpeaker({on})
overrides the output port. inject-audio.js no longer injects the plugin class (would duplicate);
it keeps only the AppDelegate launch default as a fallback.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 17:06:16 +05:30
Sravan a574816eaa debug: instrument bzApplyRoute to see if native AudioRoute plugin registered + setSpeaker result (batch146) 2026-07-20 16:43:39 +05:30
Sravan 5960efb612 uploads: support up to 1 GB attachments, streamed to disk (was 25 MB, buffered in memory) (batch145)
- Server streams the upload body straight to /data/uploads (a .part temp file, atomic rename on
  success), backpressure-aware, so a 1 GB file never buffers in RAM. MAX_UPLOAD_MB env (default
  1024 = 1 GB) controls the cap; error message reflects it.
- Client size guard raised 25 MB -> 1 GB.
- docker-compose documents MAX_UPLOAD_MB and the required Nginx Proxy Manager client_max_body_size.
NOTE: the actual bottleneck for the user's 9.7 MB reject is almost certainly NPM's client_max_body_size
(nginx default 1 MB) — that must be raised in the NPM admin; the app change alone can't lift it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 16:06:19 +05:30
Sravan 90d49d29d0 speaker: native AudioRoute plugin for real earpiece<->speaker toggle on iOS (batch144)
- ios-patch.sh now injects an AudioRoutePlugin (CAPBridgedPlugin, Capacitor 7 auto-registers it)
  into AppDelegate.swift with setSpeaker({on}) -> AVAudioSession.overrideOutputAudioPort. Tolerant/
  build-safe: if it doesn't register, the web call just no-ops (can't crash or fail the build).
- web: nativeAudioRoute()/bzApplyRoute() drive the plugin; toggleSpeakerphone + the on-join/on-tap
  unlock now actually switch the route on iOS (setSinkId can't). canRouteAudio() shows the toggle
  when the native plugin is present. Dormant until the next Codemagic build ships the plugin.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 00:31:25 +05:30
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