From e653d4bba34690fa8c28ffe1478696cdbdd46bcc Mon Sep 17 00:00:00 2001 From: arthurcai Date: Sun, 15 Mar 2026 00:43:33 +0800 Subject: [PATCH 1/4] fix: address PR #164 Copilot review issues - Initialize MediaQueryList variables as undefined to prevent potential errors if onUnmounted runs before onMounted - Compute initial isMobile/isSmall/isLandscape values synchronously in setup() to avoid layout flash on first render - Extract media query strings to module-level constants --- frontend/src/composables/useIsMobile.ts | 38 ++++++++++++++----------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/frontend/src/composables/useIsMobile.ts b/frontend/src/composables/useIsMobile.ts index 61dfedfa..ac33a559 100644 --- a/frontend/src/composables/useIsMobile.ts +++ b/frontend/src/composables/useIsMobile.ts @@ -1,28 +1,34 @@ import { ref, onMounted, onUnmounted } from "vue"; +const MQ_MOBILE = "(max-width: 768px)"; +const MQ_SMALL = "(max-width: 480px)"; +const MQ_LANDSCAPE = "(max-height: 500px) and (orientation: landscape)"; + export function useIsMobile() { - const isMobile = ref(false); - const isSmall = ref(false); - const isLandscape = ref(false); + const isMobile = ref( + typeof window !== "undefined" && window.matchMedia(MQ_MOBILE).matches, + ); + const isSmall = ref( + typeof window !== "undefined" && window.matchMedia(MQ_SMALL).matches, + ); + const isLandscape = ref( + typeof window !== "undefined" && window.matchMedia(MQ_LANDSCAPE).matches, + ); function update() { - isMobile.value = window.matchMedia("(max-width: 768px)").matches; - isSmall.value = window.matchMedia("(max-width: 480px)").matches; - isLandscape.value = window.matchMedia( - "(max-height: 500px) and (orientation: landscape)", - ).matches; + isMobile.value = window.matchMedia(MQ_MOBILE).matches; + isSmall.value = window.matchMedia(MQ_SMALL).matches; + isLandscape.value = window.matchMedia(MQ_LANDSCAPE).matches; } - let mql768: MediaQueryList; - let mql480: MediaQueryList; - let mqlLandscape: MediaQueryList; + let mql768: MediaQueryList | undefined; + let mql480: MediaQueryList | undefined; + let mqlLandscape: MediaQueryList | undefined; onMounted(() => { - mql768 = window.matchMedia("(max-width: 768px)"); - mql480 = window.matchMedia("(max-width: 480px)"); - mqlLandscape = window.matchMedia( - "(max-height: 500px) and (orientation: landscape)", - ); + mql768 = window.matchMedia(MQ_MOBILE); + mql480 = window.matchMedia(MQ_SMALL); + mqlLandscape = window.matchMedia(MQ_LANDSCAPE); update(); mql768.addEventListener("change", update); mql480.addEventListener("change", update); From a1aba977bef4517584e489c889c032119d4f000a Mon Sep 17 00:00:00 2001 From: arthurcai Date: Sun, 15 Mar 2026 00:51:09 +0800 Subject: [PATCH 2/4] fix: replace COPY . . with explicit file copies in Dockerfiles Avoid copying the entire build context to prevent accidentally including sensitive files (.env, credentials, keys). Copy only the specific source directories and config files needed for each build stage. Resolves SonarCloud security hotspot docker:S6497. --- backend/Dockerfile | 4 +++- frontend/Dockerfile | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/backend/Dockerfile b/backend/Dockerfile index 4f2adb12..4c82586e 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -2,7 +2,9 @@ FROM golang:1.23-alpine AS builder WORKDIR /app COPY go.mod go.sum ./ RUN go mod download -COPY . . +COPY cmd/ cmd/ +COPY internal/ internal/ +COPY pkg/ pkg/ RUN CGO_ENABLED=0 go build -o /server cmd/server/main.go FROM alpine:3.20 diff --git a/frontend/Dockerfile b/frontend/Dockerfile index f7312109..4a77882f 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -2,7 +2,9 @@ FROM node:24-alpine AS builder WORKDIR /app COPY package.json package-lock.json ./ RUN npm ci --ignore-scripts -COPY . . +COPY index.html tsconfig.json tsconfig.node.json vite.config.ts env.d.ts eslint.config.js ./ +COPY src/ src/ +COPY public/ public/ ARG VITE_API_BASE_URL= ENV VITE_API_BASE_URL=$VITE_API_BASE_URL RUN npm run build From 69c3af6e22bcd8fdfb1faedbf6e55f424d6d8bce Mon Sep 17 00:00:00 2001 From: arthurcai Date: Sun, 15 Mar 2026 01:02:06 +0800 Subject: [PATCH 3/4] fix: address PR #165 Copilot review issues - Update backend Dockerfile from golang:1.23 to golang:1.25 to match go.mod requirement (go 1.25.0) - Reuse stored MediaQueryList objects in update() instead of creating new ones via window.matchMedia on every change event --- backend/Dockerfile | 2 +- frontend/src/composables/useIsMobile.ts | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/backend/Dockerfile b/backend/Dockerfile index 4c82586e..760a9281 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.23-alpine AS builder +FROM golang:1.25-alpine AS builder WORKDIR /app COPY go.mod go.sum ./ RUN go mod download diff --git a/frontend/src/composables/useIsMobile.ts b/frontend/src/composables/useIsMobile.ts index ac33a559..df14a089 100644 --- a/frontend/src/composables/useIsMobile.ts +++ b/frontend/src/composables/useIsMobile.ts @@ -16,9 +16,11 @@ export function useIsMobile() { ); function update() { - isMobile.value = window.matchMedia(MQ_MOBILE).matches; - isSmall.value = window.matchMedia(MQ_SMALL).matches; - isLandscape.value = window.matchMedia(MQ_LANDSCAPE).matches; + if (mql768 && mql480 && mqlLandscape) { + isMobile.value = mql768.matches; + isSmall.value = mql480.matches; + isLandscape.value = mqlLandscape.matches; + } } let mql768: MediaQueryList | undefined; From c4f48b2289015a17d116397ea838404ce86625e3 Mon Sep 17 00:00:00 2001 From: arthurcai Date: Sun, 15 Mar 2026 01:11:30 +0800 Subject: [PATCH 4/4] docs: update documents --- CHANGELOG.md | 22 +++++++++++++++++++++- README.md | 3 ++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c83c2158..0aca797a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,10 +5,20 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] +## [1.2.0] - 2026-03-15 ### Added +#### Mobile Responsive & PWA + +- **Mobile-first responsive design**: Full mobile adaptation for the beach scene, overlay panels, and all feature pages +- **Responsive sprite positioning**: `SpriteData` interface extended with `mobile` and `landscape` override fields; `spriteStyle()` and `labelStyle()` functions select the correct tier (landscape > mobile > desktop) +- **Landscape orientation support**: Separate sprite positioning config for landscape mode via `useIsMobile` composable returning `isMobile`, `isSmall`, and `isLandscape` reactive refs +- **Portrait scroll range**: Increased horizontal parallax scroll distance on portrait mobile (`maxOffset` multiplier 1.5x vs 1.2x desktop) +- **PWA service worker**: Service worker registration for offline caching, gated behind `import.meta.env.PROD` to prevent stale-cache issues during development +- **Touch gesture support**: `touch-action: pan-x` on `.scene` element enables horizontal swipe navigation on mobile +- **Dynamic viewport units**: `dvh`/`vh` fallback pattern across all mobile and landscape media queries for correct viewport height on mobile browsers with dynamic toolbars + #### Navigation & Scene Interaction - **Collapsible NavBar**: Glassmorphism floating navigation menu in the top-right corner; collapsed by default, expands on click with frosted glass styling and monochrome outline icons @@ -26,8 +36,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- **dvh/vh fallback order**: Corrected dynamic viewport height fallback across `mobile.css`, `OverlayPanel.vue`, and `RoleSelectPanel.vue` — `vh` first (fallback), `dvh` second (override) +- **touch-action scope**: Moved `touch-action: pan-x` from `body` to `.scene` only, so overlay panel content can scroll vertically +- **Event listener leaks**: Extracted anonymous `resize` and `orientationchange` handlers to named functions with proper cleanup in `onUnmounted` (`useParallax.ts`) +- **useIsMobile layout flash**: Compute initial `isMobile`/`isSmall`/`isLandscape` values synchronously in `setup()` to prevent false → true flicker on first render +- **useIsMobile MediaQueryList reuse**: `update()` reads `.matches` from stored MQL objects instead of recreating them on each call - **Go static analysis**: Replaced `fmt.Errorf` with `errors.New` for constant error strings across `photo.go`, `task.go`, and `whisper.go` (12 occurrences, SA1006) +### Security + +- **Dockerfile hardening**: Replaced `COPY . .` with explicit file/directory copies in both `frontend/Dockerfile` and `backend/Dockerfile` to prevent leaking secrets, `.env` files, or unintended files into container images +- **Go version alignment**: Updated `backend/Dockerfile` from `golang:1.23-alpine` to `golang:1.25-alpine` to match `go.mod` requirement + ### Performance - **Gesture control optimization**: Reduced GPU contention on Mac by switching to MediaPipe lite model, lowering camera resolution to 320×240, throttling inference to ~10fps with manual rAF loop, and adding GSAP camera follow throttle with `overwrite: true` diff --git a/README.md b/README.md index a3273abe..ebe4360d 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ AI-powered wellness platform combining emotional companionship, community suppor | **Photo Gallery** | Photo wall with AI-generated images, lifecycle management, and drag/zoom UI | | **Whisper** | Audio-to-text conversation using speech recognition | | **Tasks** | Goal-setting and task-tracking system with partner support | +| **Mobile & PWA** | Responsive mobile layout with portrait/landscape sprite configs, touch gestures, dynamic viewport units, and offline-capable service worker | | **Admin Panel** | Embedded single-page admin at `/admin` — dashboard, user CRUD, config management | ## Quick Start @@ -55,7 +56,7 @@ MomShell/ ├── frontend/ # Vue 3 (Vite + TypeScript + Pinia) │ └── src/ │ ├── components/ # Overlay panels + beach scene + React 3D shell -│ ├── composables/# Animation, parallax, waves, music +│ ├── composables/# Animation, parallax, waves, music, mobile detection │ ├── lib/api/ # API client modules (chat, community, echo, photo, etc.) │ ├── stores/ # Pinia stores (auth, UI) │ ├── types/ # TypeScript type definitions