Compare commits

...
Sign in to create a new pull request.

1 commit

8 changed files with 78 additions and 10 deletions

View file

@ -38,8 +38,8 @@ versionCode = major * 1_000_000 + minor * 1_000 + patch
- **Service Worker stays active.** Critical for authenticated Matrix media (MSC3916 / Matrix spec v1.11+). DO NOT disable. `resolveServiceWorkerRequests` default `true`. - **Service Worker stays active.** Critical for authenticated Matrix media (MSC3916 / Matrix spec v1.11+). DO NOT disable. `resolveServiceWorkerRequests` default `true`.
- **Edge-to-edge.** `EdgeToEdge.enable()` in `MainActivity.java` + `windowLayoutInDisplayCutoutMode: shortEdges`. - **Edge-to-edge.** `EdgeToEdge.enable()` in `MainActivity.java` + `windowLayoutInDisplayCutoutMode: shortEdges`.
- **External links.** Opened via `@capacitor/browser` plugin — see [`src/app/utils/capacitor.ts`](../../src/app/utils/capacitor.ts). - **External links.** Opened via `@capacitor/browser` plugin — see [`src/app/utils/capacitor.ts`](../../src/app/utils/capacitor.ts).
- **Safe-area coloring.** `body` background-color is bound to the folds theme variable `var(--oq6d070)` for consistent safe-area coloring. - **Safe-area coloring.** `body` background-color reads `--vojo-safe-area-bg` (set on `:root` in [`src/app/styles/global.css.ts`](../../src/app/styles/global.css.ts), default `#0d0e11` = chat-list tone). [`Room.tsx`](../../src/app/features/room/Room.tsx) retunes the var to `#181a20` (chat-surface tone) while a chat is mounted so the status-bar / gesture-bar zones never show a seam against the active surface.
- **Safe-area insets.** Applied on `#root` (not `body`) so the theme background extends behind the system bars. - **Safe-area insets — top / left / right only on `#root`.** Bottom inset is intentionally **not** applied at `#root` so the app renders edge-to-edge under the Android gesture pill / 3-button bar / iOS home indicator (mirrors WhatsApp / Telegram). Components that anchor interactive UI at the screen bottom MUST add `padding-bottom: var(--vojo-safe-bottom)` themselves — covered: chat composer ([`RoomView.css.ts`](../../src/app/features/room/RoomView.css.ts)), PageNav inner column ([`Page.tsx`](../../src/app/components/page/Page.tsx) → catches SelfRow / WorkspaceFooter / etc.), bottom call rail ([`HorseshoeContainer.css.ts`](../../src/app/pages/HorseshoeContainer.css.ts)), AuthFooter ([`auth/styles.css.ts`](../../src/app/pages/auth/styles.css.ts)). New screens with a bottom CTA must follow this rule or the button lands behind a system 3-button nav bar.
## VSCode tasks ## VSCode tasks

View file

@ -141,6 +141,11 @@ export function PageNav({
grow="Yes" grow="Yes"
direction="Column" direction="Column"
className={horseshoe ? css.PageNavInnerWebHorseshoe : undefined} className={horseshoe ? css.PageNavInnerWebHorseshoe : undefined}
// Bottom inset for native: keeps any nav-footer row (SelfRow,
// WorkspaceFooter, …) clear of the Android gesture pill / 3-button
// bar / iOS home indicator after `#root` stopped reserving the
// inset itself. `var(--vojo-safe-bottom)` resolves to 0 on web.
style={{ paddingBottom: 'var(--vojo-safe-bottom)' }}
> >
{children} {children}
</Box> </Box>
@ -268,6 +273,10 @@ function ResizablePageNav({ children }: { children: ReactNode }) {
grow="Yes" grow="Yes"
direction="Column" direction="Column"
className={horseshoe ? css.PageNavInnerWebHorseshoe : undefined} className={horseshoe ? css.PageNavInnerWebHorseshoe : undefined}
// See twin block in `PageNav` above — same native safe-area
// protection for any footer row mounted inside a resizable
// page-nav. On web `var(--vojo-safe-bottom)` is 0.
style={{ paddingBottom: 'var(--vojo-safe-bottom)' }}
> >
{children} {children}
</Box> </Box>

View file

@ -1,4 +1,4 @@
import React, { useCallback } from 'react'; import React, { useCallback, useEffect } from 'react';
import { Box, Line, toRem } from 'folds'; import { Box, Line, toRem } from 'folds';
import { useMatch, useParams } from 'react-router-dom'; import { useMatch, useParams } from 'react-router-dom';
import { isKeyHotkey } from 'is-hotkey'; import { isKeyHotkey } from 'is-hotkey';
@ -112,6 +112,28 @@ export function Room({ renderRoomView }: RoomProps) {
const profileOpen = !!useAtomValue(userRoomProfileAtom); const profileOpen = !!useAtomValue(userRoomProfileAtom);
const showProfileHorseshoe = profileOpen && !isMobile && !showThreadDrawer; const showProfileHorseshoe = profileOpen && !isMobile && !showThreadDrawer;
// Re-tune the Android edge-to-edge safe-area bg while a Room is on
// screen. The default value from `app/styles/global.css.ts` paints
// the chat-list tone (#0d0e11) into the status bar / nav bar zones —
// fine when the chat list is showing, but produces a visible seam
// once the chat itself (SurfaceVariant.Container, #181a20) is opened.
//
// Value is hardcoded for the SAME reason `global.css.ts` hardcodes
// `#0d0e11`: folds tokens (`color.SurfaceVariant.Container = var(--xxx)`)
// are scoped to the `.dark-theme` / `.lightTheme` class which ThemeManager
// applies to `document.body`, not to `:root`. Writing the var on `:root`
// means folds' `--xxx` doesn't resolve there and the whole declaration
// becomes invalid (body falls back to the `#0d0e11` literal in `index.css`).
// `#181a20` is `SurfaceVariant.Container` from the dark Dawn palette —
// kept in sync with `colors.css.ts` line 40 if the palette tone changes.
useEffect(() => {
const root = document.documentElement;
root.style.setProperty('--vojo-safe-area-bg', '#181a20');
return () => {
root.style.removeProperty('--vojo-safe-area-bg');
};
}, []);
useKeyDown( useKeyDown(
window, window,
useCallback( useCallback(

View file

@ -37,7 +37,19 @@ globalStyle(`${ChatComposer} .${Editor}`, {
// Top-left mirror (curve at y=19 from top is ~2.8px from left, text-left // Top-left mirror (curve at y=19 from top is ~2.8px from left, text-left
// at 28px → ~25px clearance) is even more generous, which fits the // at 28px → ~25px clearance) is even more generous, which fits the
// single-line composer that grows downward when wrapping. // single-line composer that grows downward when wrapping.
padding: `${toRem(6)} ${toRem(16)}`, //
// Bottom padding ALSO carries the safe-area inset (`--vojo-safe-bottom`)
// so the action row (Plus / Smiley / Send) stays clear of the Android
// gesture pill / 3-button bar / iOS home indicator now that `#root`
// stopped reserving the inset itself (see src/index.css). The card's
// bg simply grows downward to fill the safe-area zone, which is the
// edge-to-edge pattern used by every mainstream messenger. The
// ResizeObserver in `RoomView` measures the wrap's full height
// (incl. this padding) and feeds it to `RoomTimeline`, so the
// bottom scroll-padding still ends right above the visible card
// top — no extra whitespace appears between the last message and
// the card.
padding: `${toRem(6)} ${toRem(16)} calc(${toRem(6)} + var(--vojo-safe-bottom))`,
}); });
// Visual alignment goal: typed-text glyph-start and Plus-icon glyph-start // Visual alignment goal: typed-text glyph-start and Plus-icon glyph-start

View file

@ -50,13 +50,18 @@ export const appShellBottomRound = style({
// === Bottom horseshoe (call rail) === // === Bottom horseshoe (call rail) ===
// //
// Rounded *top* corners only because it sits flush against the // Rounded *top* corners only — the rail's bottom edge is the screen
// safe-area inset; the bottom is the screen edge. `position: relative` // edge. `position: relative` carries the absolute-positioned orbit
// carries the absolute-positioned orbit border. // border. Bottom padding carries the native safe-area inset so the
// call action buttons (accept / decline / hang up) stay clear of the
// Android gesture pill / 3-button bar / iOS home indicator after
// `#root` stopped reserving the inset (src/index.css). The rail's bg
// continues underneath the inset for visual continuity.
export const bottomRail = style({ export const bottomRail = style({
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
flexShrink: 0, flexShrink: 0,
paddingBottom: 'var(--vojo-safe-bottom)',
}); });
export const bottomRailActive = style({ export const bottomRailActive = style({

View file

@ -271,7 +271,10 @@ export const AuthFooter = style({
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
width: '100%', width: '100%',
padding: '20px 24px 10px', // Bottom padding folds in the native safe-area inset so the footer
// text/links stay clear of the Android gesture pill / 3-button bar /
// iOS home indicator after `#root` stopped reserving the inset.
padding: '20px 24px calc(10px + var(--vojo-safe-bottom))',
fontSize: '14px', fontSize: '14px',
color: 'rgba(232, 228, 223, 0.55)', color: 'rgba(232, 228, 223, 0.55)',
letterSpacing: '0.02em', letterSpacing: '0.02em',

View file

@ -23,5 +23,15 @@ import { globalStyle } from '@vanilla-extract/css';
globalStyle(':root', { globalStyle(':root', {
vars: { vars: {
'--vojo-safe-area-bg': '#0d0e11', '--vojo-safe-area-bg': '#0d0e11',
// Mirror of `env(safe-area-inset-bottom)` exposed as a regular CSS
// variable so consumers don't repeat the `env(..., 0px)` fallback
// boilerplate. Used as `padding-bottom` by every component that
// anchors interactive UI at the bottom of the viewport (chat
// composer, PageNav footer rows, AuthFooter, bottom call rail).
// Required since `src/index.css` no longer adds bottom safe-area
// padding to `#root` (apps render edge-to-edge under the Android
// gesture pill / iOS home indicator). On web env() resolves to 0,
// so the variable is a no-op outside native / PWA.
'--vojo-safe-bottom': 'env(safe-area-inset-bottom, 0px)',
}, },
}); });

View file

@ -60,8 +60,15 @@ body {
height: 100%; height: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) /* Top / left / right insets keep app chrome (status icons, sidebar
env(safe-area-inset-left); rail) clear of system bars and display cutouts. Bottom inset is
intentionally zero: on Android edge-to-edge the gesture bar is a
translucent overlay, not a reserved strip leaving #root padded
there reserves a tall band of body bg below the chat surface and
prevents the composer / call rail from reaching the screen edge.
Components that genuinely need bottom safe-area clearance (e.g.
SyncIndicator) read env(safe-area-inset-bottom) themselves. */
padding: env(safe-area-inset-top) env(safe-area-inset-right) 0 env(safe-area-inset-left);
} }
*, *,