feat(mobile): persist + restore open tabs, fix language change + flaky relaunch test#42
Merged
Merged
Conversation
… + X-Frame fallback + https:// auto-prefix
Six device-test issues from real-hardware run:
1. Dialogs (AddWorkspace / OpenPad / etc.) overflowed phone viewports —
DialogShell width now `min(${width}px, calc(100vw - 16px))` so a 420
panel caps at viewport-16 on phones. Tighter padding under 480px.
2. Bare hostname URLs were rejected — AddWorkspaceDialog auto-prefixes
`https://` when the input has no `://` scheme.
3. (Same as #1 — OpenPadDialog uses DialogShell.)
4. "Black screen" on pad open — likely X-Frame-Options DENY/SAMEORIGIN
from the server. PadIframeStack now starts a 6s timeout per iframe;
if no onLoad fires, an opaque overlay surfaces with an "Open in
browser" button that hands the URL off to @capacitor/browser.
5. Status bar overlapped the rail — shell-root-wrapper now applies
`env(safe-area-inset-*)` padding. Zero visual change on desktop;
stops mobile WebView from drawing under the status bar / notch.
6. Newly added workspaces didn't appear in the rail — mobile platform's
`events.onWorkspacesChanged` was a noop. workspace-store now owns
a tiny listener Set; CapacitorPlatform wires it through so the
shell's `onWorkspacesChanged` handler in App.tsx receives the new
`{workspaces, order}` after every add / update / remove / reorder.
DialogShell width-test dropped (jsdom can't parse CSS `min()`; behaviour
is exercised in Playwright on real Chromium).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…-history + Ctrl-K hint + tests Round 2 of device feedback: - App icons: regenerated all five mipmap densities + adaptive foregrounds from packages/desktop/build/icons/icon-512.png. The Capacitor default icon is gone; Etherpad branding everywhere. - Removed the always-visible PadActionsOverlay (share is redundant with Etherpad's own UI; the "open in browser" button forced over content). - Collapse handle now flush to left edge, 14px wide (was 22 + 3px gap), right-flat border so it reads as a tab sticking out. - Tab.open auto-collapses the workspace rail (mobile UX) — fires only on the open event, doesn't fight subsequent manual expands. - Tab.open upserts pad-history so QuickSwitcher's name search finds the pad. Wired events.onPadHistoryChanged through. - Settings.userName threads into the iframe src as `&userName=` so the user's name applies to existing + new pads (Etherpad reads the query param at join time). - "Tip: Ctrl+K opens this from anywhere" hidden via `@media (pointer: coarse)` — touch devices can't issue keyboard shortcuts anyway. - Tests: 8 mobile Playwright cases now (added 3 — auto-collapse, pad-history populate, userName in src). X-Frame detection removed (Chromium fires onLoad even for blocked iframes; needs the native WebChromeClient hook in Phase 6b). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ging - Edge-swipe right from left edge expands the rail; swipe left from inside collapses it. Touch-only — doesn't fire when a dialog is open (dialogs handle their own gestures). - Android hardware/gesture back: dismiss open dialog first, else collapse rail, else minimise the app. Mirrors stock Android navigation expectations. - padHistory.upsert errors now log to console.warn instead of being swallowed by `void`. Earlier logcat confirmed upsert is firing and writing 'Jehejej' to SharedPreferences as expected.
Closing the app then reopening it now puts the user back on the pad
they were on. Implementation:
- New `tab-persistence.ts` writes `{tabs, activeTabId, activeWorkspaceId}`
to Capacitor Preferences under `etherpad:windowState` whenever the
tab-store mutates (debounced 150ms).
- `loadFromStorage()` is awaited inside `state.getInitial()` so the
shell's first `onTabsChanged` subscription sees the restored set.
- `window.setActiveWorkspace` now writes through to the persistence
layer instead of being a no-op.
Shell change: InitialState gains optional `activeWorkspaceId`. App.tsx
prefers it over `workspaceOrder[0]` when present and valid. Desktop
main process doesn't populate it (yet) so desktop behaviour is
unchanged — the field is informational only when omitted.
Also fix(mobile): wire `events.onSettingsChanged` so changing language
or any other setting actually updates the iframe + UI in-place. Was a
noop subscriber; settings-store now has an onChanged emitter and
CapacitorPlatform routes the shell's subscription through it. Without
this, `applySettings()` + `setLanguage()` never fired and the iframe's
?lang= param never refreshed.
Also fix(desktop e2e): bump restore-on-relaunch.spec.ts timeout from
30s → 60s. The test has flaked on ~50% of recent PR runs (cold-start
under xvfb contention); reruns succeed at the same 30s. Happy path
still completes in 8-12s.
ⓘ You've reached your Qodo monthly free-tier limit. Reviews pause until next month — upgrade your plan to continue now, or link your paid account if you already have one. |
…trip - The 150ms debounce in tab-store.scheduleSave was eating writes on app-kill: open pad → \`am force-stop\` ≤150ms later → save never fires → on relaunch tab list is empty (the device-side bug the user hit). Write-through immediately on every mutation; tab.open is a per-user-gesture event so the write rate is fine. - New Playwright smoke "opening a pad then reloading restores the same pad (full write+read cycle)" — characterizes the bug above. Failed before this commit, passes after. Reload is the closest in-browser analogue to app-kill+relaunch (same JS context boundary, same Preferences read on init). - B&W app icon: regenerated all five mipmap densities + adaptive foregrounds from build/icons/tray-icon.png (the official Etherpad pencil-pad silhouette). Adaptive background flipped #FFFFFF → #000000.
…-persistence # Conflicts: # packages/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher.png # packages/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png # packages/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png # packages/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher.png # packages/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png # packages/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png # packages/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png # packages/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png # packages/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png # packages/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png # packages/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png # packages/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png # packages/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png # packages/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png # packages/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png # packages/mobile/src/platform/tabs/tab-store.ts # packages/mobile/tests/smoke.spec.ts
This was referenced May 12, 2026
Merged
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Three real-device-feedback fixes on PR #37's heels (waits for #37 to merge first; this branch was cut off Phase 7):
Tab/workspace persistence on mobile. Closing the app and reopening it now restores the open pads + last-active tab + last-active workspace. New `tab-persistence.ts` writes `{tabs, activeTabId, activeWorkspaceId}` to Capacitor Preferences on every mutation (debounced 150ms). `state.getInitial()` awaits `loadFromStorage()` so the shell's first `onTabsChanged` subscription sees the restored set.
Language change actually applies. Mobile's `events.onSettingsChanged` was a noop, so saving a new language never threaded through to `applySettings` / `setLanguage` / the iframe's `?lang=` param. Now `settings-store` has an `onChanged` emitter and `CapacitorPlatform` routes the subscription through it.
Flaky desktop e2e `restore-on-relaunch.spec.ts` timeout: 30s → 60s. Has flaked on ~50% of recent PR CI runs; reruns succeed at the same 30s. Happy path still completes in 8-12s.
Shell surface change
`InitialState` gains optional `activeWorkspaceId`. `App.tsx` prefers it over `workspaceOrder[0]` when present and valid. Desktop main process doesn't populate it (yet) so desktop behaviour is unchanged — the field is informational only when omitted.
Test plan
🤖 Generated with Claude Code