v0.8.4 full refresh

This commit is contained in:
2026-03-13 10:02:04 -04:00
parent 33b5f2ee4d
commit a02facff1a
9 changed files with 73 additions and 24 deletions

View File

@@ -7,7 +7,7 @@ TZ=UTC
# Copy this file to .env and customize
# Image version to run (set by build.sh, or use 'latest')
JAMA_VERSION=0.8.2
JAMA_VERSION=0.8.4
# Default admin credentials (used on FIRST RUN only)
ADMIN_NAME=Admin User

View File

@@ -1,6 +1,6 @@
{
"name": "jama-backend",
"version": "0.8.2",
"version": "0.8.4",
"description": "TeamChat backend server",
"main": "src/index.js",
"scripts": {

View File

@@ -13,7 +13,7 @@
# ─────────────────────────────────────────────────────────────
set -euo pipefail
VERSION="${1:-0.8.2}"
VERSION="${1:-0.8.4}"
ACTION="${2:-}"
REGISTRY="${REGISTRY:-}"
IMAGE_NAME="jama"

View File

@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/png" href="/icons/jama.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover, interactive-widget=resizes-content" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover, interactive-widget=resizes-content" />
<meta name="theme-color" content="#1a73e8" />
<meta name="description" content="jama - just another messaging app" />
<link rel="manifest" href="/manifest.json" />

View File

@@ -1,6 +1,6 @@
{
"name": "jama-frontend",
"version": "0.8.2",
"version": "0.8.4",
"private": true,
"scripts": {
"dev": "vite",

View File

@@ -78,7 +78,7 @@
.own .message-body { align-items: flex-end; }
.msg-name {
font-size: 12px;
font-size: calc(0.75rem * var(--font-scale));
font-weight: 600;
color: var(--text-secondary);
margin-bottom: 3px;
@@ -98,8 +98,8 @@
.reply-bar { width: 3px; background: var(--primary); border-radius: 2px; flex-shrink: 0; }
.reply-name { font-size: 11px; font-weight: 600; color: var(--primary); }
.reply-text { font-size: 12px; color: var(--text-secondary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 220px; }
.reply-name { font-size: calc(0.6875rem * var(--font-scale)); font-weight: 600; color: var(--primary); }
.reply-text { font-size: calc(0.75rem * var(--font-scale)); color: var(--text-secondary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 220px; }
/* Bubble row */
.msg-bubble-wrap {
@@ -204,10 +204,10 @@
border: 1px dashed var(--border);
}
.deleted-text { font-size: 13px; color: var(--text-tertiary); font-style: italic; }
.deleted-text { font-size: calc(0.8125rem * var(--font-scale)); color: var(--text-tertiary); font-style: italic; }
.msg-text {
font-size: 14px;
font-size: calc(0.875rem * var(--font-scale));
line-height: 1.5;
white-space: pre-wrap;
}
@@ -236,7 +236,7 @@
}
.msg-time {
font-size: 11px;
font-size: calc(0.6875rem * var(--font-scale));
color: var(--text-tertiary);
white-space: nowrap;
flex-shrink: 0;
@@ -260,11 +260,11 @@
border-radius: 12px;
background: var(--surface);
border: 1px solid var(--border);
font-size: 14px;
font-size: calc(0.875rem * var(--font-scale));
cursor: pointer;
transition: var(--transition);
}
.reaction-count { font-size: 12px; color: var(--text-secondary); }
.reaction-count { font-size: calc(0.75rem * var(--font-scale)); color: var(--text-secondary); }
.reaction-btn.active { background: var(--primary-light); border-color: var(--primary); }
.reaction-btn.active .reaction-count { color: var(--primary); }
.reaction-btn:hover { background: var(--primary-light); }

View File

@@ -151,7 +151,7 @@
padding: 10px 14px;
border: 1px solid var(--border);
border-radius: 20px;
font-size: 14px;
font-size: calc(0.875rem * var(--font-scale));
line-height: 1.4;
font-family: var(--font);
color: var(--text-primary);

View File

@@ -26,6 +26,7 @@
--shadow-lg: 0 4px 20px rgba(0,0,0,0.18);
--transition: 150ms cubic-bezier(0.4, 0, 0.2, 1);
--font: 'Google Sans', 'Roboto', sans-serif;
--font-scale: 1;
}
html, body, #root { height: 100%; min-width: 320px; font-family: var(--font); color: var(--text-primary); background: var(--background); }
@@ -33,6 +34,7 @@ html, body, #root { height: 100%; min-width: 320px; font-family: var(--font); co
/* Disable pull-to-refresh in PWA standalone mode — prevents viewport shift bug */
html {
overscroll-behavior-y: none;
font-size: 100%; /* inherits system font size — allows Android accessibility font scaling */
}
button { font-family: var(--font); cursor: pointer; border: none; background: none; }

View File

@@ -14,25 +14,72 @@ if ('serviceWorker' in navigator) {
// Disable pull-to-refresh in PWA standalone mode to prevent viewport shift bug.
// The CSS overscroll-behavior-y:none handles most browsers; this catches the rest.
// ─── Touch gesture handler ───────────────────────────────────────────────────
// Handles two behaviours in one unified listener set to avoid conflicts:
//
// 1. PINCH → font scale only (not viewport zoom).
// viewport has user-scalable=no so the browser never zooms the layout.
// We intercept the pinch and adjust --font-scale on <html> instead,
// which scales only text (rem-based font sizes). Persisted to localStorage.
// On first launch, html { font-size: 100% } inherits the Android system
// font size as the 1rem baseline automatically.
//
// 2. PULL-TO-REFRESH → blocked in PWA standalone mode only.
(function () {
const LS_KEY = 'jama_font_scale';
const MIN_SCALE = 0.8;
const MAX_SCALE = 2.0;
const isStandalone = window.matchMedia('(display-mode: standalone)').matches
|| window.navigator.standalone === true;
if (!isStandalone) return;
let startY = 0;
document.addEventListener('touchstart', e => {
startY = e.touches[0].clientY;
// Restore saved font scale on launch
const saved = parseFloat(localStorage.getItem(LS_KEY));
let currentScale = (saved >= MIN_SCALE && saved <= MAX_SCALE) ? saved : 1.0;
document.documentElement.style.setProperty('--font-scale', currentScale);
let pinchStartDist = null;
let pinchStartScale = currentScale;
let singleStartY = 0;
function getTouchDist(e) {
const dx = e.touches[0].clientX - e.touches[1].clientX;
const dy = e.touches[0].clientY - e.touches[1].clientY;
return Math.sqrt(dx * dx + dy * dy);
}
document.addEventListener('touchstart', function (e) {
if (e.touches.length === 2) {
pinchStartDist = getTouchDist(e);
pinchStartScale = currentScale;
} else if (e.touches.length === 1) {
singleStartY = e.touches[0].clientY;
}
}, { passive: true });
document.addEventListener('touchmove', e => {
// Only block downward pull at the very top of the document
const dy = e.touches[0].clientY - startY;
if (dy > 0 && (document.documentElement.scrollTop === 0 || document.body.scrollTop === 0)) {
document.addEventListener('touchmove', function (e) {
if (e.touches.length === 2 && pinchStartDist !== null) {
// Two-finger pinch: scale fonts, not viewport
e.preventDefault();
const ratio = getTouchDist(e) / pinchStartDist;
const newScale = Math.min(MAX_SCALE, Math.max(MIN_SCALE, pinchStartScale * ratio));
currentScale = Math.round(newScale * 100) / 100;
document.documentElement.style.setProperty('--font-scale', currentScale);
} else if (e.touches.length === 1 && isStandalone) {
// Single finger: block pull-to-refresh at top of page
const dy = e.touches[0].clientY - singleStartY;
if (dy > 0 && document.documentElement.scrollTop === 0 && document.body.scrollTop === 0) {
e.preventDefault();
}
}
}, { passive: false });
document.addEventListener('touchend', function (e) {
if (e.touches.length < 2 && pinchStartDist !== null) {
pinchStartDist = null;
localStorage.setItem(LS_KEY, currentScale);
}
}, { passive: true });
})();
// Clear badge count when user focuses the app