From e03ff497641f872b5ce27ee0f5ea1d2bb8ca405b Mon Sep 17 00:00:00 2001 From: Julien Calixte Date: Fri, 24 Apr 2026 23:42:22 +0200 Subject: [PATCH] fix(mobile): restore overflow-y and unstick readme on vertical scroll MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Restore explicit overflow-y:auto on #main-app for mobile (removed in 63f5d64) — implicit coercion from overflow-x:auto is not reliable in all Safari/WebKit versions. - Override position:sticky on .readme to position:relative on mobile. The desktop sticky (left:0) is correct for horizontal scroll, but on mobile vertical scroll it pinned the 100dvh-tall readme across the entire viewport, hiding all stacked notes behind it. --- ...026-04-20-react-native-migration-design.md | 43 +++++++++++-------- src/App.vue | 6 +++ src/components/FluxNote.vue | 10 +---- src/components/FontChange.vue | 18 ++------ src/components/HomeButton.vue | 5 ++- src/hooks/useComputeBacklinks.hook.ts | 11 +++-- src/modules/repo/store/userRepo.store.ts | 18 ++++++-- src/shims-vue.d.ts | 4 +- src/theme.config.ts | 4 +- src/views/RepoList.vue | 10 ++++- 10 files changed, 72 insertions(+), 57 deletions(-) diff --git a/docs/superpowers/specs/2026-04-20-react-native-migration-design.md b/docs/superpowers/specs/2026-04-20-react-native-migration-design.md index a9be6b5..6e1bb71 100644 --- a/docs/superpowers/specs/2026-04-20-react-native-migration-design.md +++ b/docs/superpowers/specs/2026-04-20-react-native-migration-design.md @@ -9,21 +9,21 @@ Migrate Remanso from a Vue 3 web app to a fully native iOS + Android app built w ## Tech Stack -| Concern | Current (Vue) | React Native | -|---|---|---| -| Framework | Vue 3 + Vite | Expo SDK (managed workflow) | -| Routing | Vue Router | Expo Router (file-system routing over React Navigation v7) | -| State | Pinia | Zustand | -| Server state | TanStack Vue Query | TanStack Query (same library) | -| Styling | DaisyUI + Tailwind CSS | NativeWind v4 | -| Local DB | PouchDB (IndexedDB) | Expo SQLite | -| Simple KV store | localStorage | MMKV | -| Auth | OAuth redirects | expo-auth-session | -| GitHub API | @octokit/rest | @octokit/rest (unchanged) | -| i18n | vue-i18n | react-i18next | -| Date utils | date-fns | date-fns (unchanged) | -| Markdown content | markdown-it (DOM) | react-native-webview | -| Fonts | CSS custom properties | expo-font | +| Concern | Current (Vue) | React Native | +| ---------------- | ---------------------- | ---------------------------------------------------------- | +| Framework | Vue 3 + Vite | Expo SDK (managed workflow) | +| Routing | Vue Router | Expo Router (file-system routing over React Navigation v7) | +| State | Pinia | Zustand | +| Server state | TanStack Vue Query | TanStack Query (same library) | +| Styling | DaisyUI + Tailwind CSS | NativeWind v4 | +| Local DB | PouchDB (IndexedDB) | Expo SQLite | +| Simple KV store | localStorage | MMKV | +| Auth | OAuth redirects | expo-auth-session | +| GitHub API | @octokit/rest | @octokit/rest (unchanged) | +| i18n | vue-i18n | react-i18next | +| Date utils | date-fns | date-fns (unchanged) | +| Markdown content | markdown-it (DOM) | react-native-webview | +| Fonts | CSS custom properties | expo-font | ## Navigation Structure @@ -137,14 +137,18 @@ Zustand replaces Pinia. The store shape is identical to `userRepo.store.ts`: ```ts const useRepoStore = create((set, get) => ({ - user: '', - repo: '', + user: "", + repo: "", files: [], userSettings: null, needToLogin: false, setRepo: (user, repo) => set({ user, repo }), - loadFiles: async () => { /* Octokit call */ }, - loadSettings: async () => { /* MMKV read */ }, + loadFiles: async () => { + /* Octokit call */ + }, + loadSettings: async () => { + /* MMKV read */ + } })) ``` @@ -165,6 +169,7 @@ The markdown-it pipeline (KaTeX, Mermaid, shiki, tabler icons, html5-media, GitH `NoteWebView` is a native UIView/View wrapper around a WebView engine. It is a React Native component — not a web app. The surrounding app (navigation chrome, tab bar, headers, settings, auth screens) is 100% native. Only the note content pane renders HTML. This is the standard pattern for rich content in React Native (used by GitHub Mobile, Linear, and others). The WebView communicates back to the native layer via `postMessage` for: + - Internal note link taps (trigger React Navigation push) - External URL taps (open in system browser) - Backlink detection events diff --git a/src/App.vue b/src/App.vue index 2b9e7bc..9d7363a 100644 --- a/src/App.vue +++ b/src/App.vue @@ -25,6 +25,12 @@ const { isATProtoReady } = useATProtoLogin() overflow-x: auto; } +@media screen and (max-width: 768px) { + #main-app { + overflow-y: auto; + } +} + ::view-transition-old(root), ::view-transition-new(root) { animation-duration: 0.25s; diff --git a/src/components/FluxNote.vue b/src/components/FluxNote.vue index 388e783..2ddf7bc 100644 --- a/src/components/FluxNote.vue +++ b/src/components/FluxNote.vue @@ -1,12 +1,5 @@ diff --git a/src/hooks/useComputeBacklinks.hook.ts b/src/hooks/useComputeBacklinks.hook.ts index 32fbb13..fc83e02 100644 --- a/src/hooks/useComputeBacklinks.hook.ts +++ b/src/hooks/useComputeBacklinks.hook.ts @@ -16,7 +16,9 @@ const isMarkdown = (filename?: string) => filename?.endsWith(".md") ?? false const yieldToMain = () => "scheduler" in globalThis - ? (globalThis as unknown as { scheduler: { yield: () => Promise } }).scheduler.yield() + ? ( + globalThis as unknown as { scheduler: { yield: () => Promise } } + ).scheduler.yield() : new Promise((r) => setTimeout(r, 0)) export const useComputeBacklinks = () => { @@ -43,9 +45,10 @@ export const useComputeBacklinks = () => { } const fileBacklinkId = generateId(DataType.BacklinkNote, file.sha) - const fileBacklink = await data.get( - fileBacklinkId - ) + const fileBacklink = await data.get< + DataType.BacklinkNote, + BacklinkNote + >(fileBacklinkId) if (fileBacklink) { continue } diff --git a/src/modules/repo/store/userRepo.store.ts b/src/modules/repo/store/userRepo.store.ts index d355c81..6a82149 100644 --- a/src/modules/repo/store/userRepo.store.ts +++ b/src/modules/repo/store/userRepo.store.ts @@ -37,11 +37,20 @@ export const useUserRepoStore = defineStore("USER_REPO_STATE", { _persistFonts() { if (!this.userSettings) return try { - const { chosenTitleFont, chosenBodyFont, chosenFontSize, chosenFontFamily } = - this.userSettings + const { + chosenTitleFont, + chosenBodyFont, + chosenFontSize, + chosenFontFamily + } = this.userSettings localStorage.setItem( `remanso:fonts:${this.user}:${this.repo}`, - JSON.stringify({ chosenTitleFont, chosenBodyFont, chosenFontSize, chosenFontFamily }) + JSON.stringify({ + chosenTitleFont, + chosenBodyFont, + chosenFontSize, + chosenFontFamily + }) ) } catch { // ignore @@ -61,7 +70,8 @@ export const useUserRepoStore = defineStore("USER_REPO_STATE", { } if (Object.keys(lsFonts).length) { - if (!this.userSettings) this.userSettings = { $type: DataType.UserSettings } + if (!this.userSettings) + this.userSettings = { $type: DataType.UserSettings } Object.assign(this.userSettings, lsFonts) } diff --git a/src/shims-vue.d.ts b/src/shims-vue.d.ts index e410c28..0799a0d 100644 --- a/src/shims-vue.d.ts +++ b/src/shims-vue.d.ts @@ -1,5 +1,5 @@ -declare module '*.vue' { - import type { DefineComponent } from 'vue' +declare module "*.vue" { + import type { DefineComponent } from "vue" const component: DefineComponent<{}, {}, any> export default component } diff --git a/src/theme.config.ts b/src/theme.config.ts index 35b296a..1363cd5 100644 --- a/src/theme.config.ts +++ b/src/theme.config.ts @@ -2,6 +2,6 @@ // Update these values to change the light and dark themes export const themeConfig = { - light: 'emerald', - dark: 'forest' + light: "emerald", + dark: "forest" } diff --git a/src/views/RepoList.vue b/src/views/RepoList.vue index d9ccf03..3cc4a6a 100644 --- a/src/views/RepoList.vue +++ b/src/views/RepoList.vue @@ -8,8 +8,14 @@ import { useRepoList } from "@/modules/repo/hooks/useRepoList.hook" const { username } = useGitHubLogin() const { isReady } = useRepos() -const { favoriteRepos, otherRepos, favoriteCheckboxes, toggleCheckbox, canLoadMore, loadMore } = - useRepoList() +const { + favoriteRepos, + otherRepos, + favoriteCheckboxes, + toggleCheckbox, + canLoadMore, + loadMore +} = useRepoList()