Compare commits
1 Commits
26f900870b
...
c395f7d16e
| Author | SHA1 | Date | |
|---|---|---|---|
| c395f7d16e |
+4
-4
@@ -100,9 +100,9 @@ This document tracks identified bugs, edge cases, and architectural discrepancie
|
||||
### 11. Inline Jotai atom creation
|
||||
|
||||
- **File:** `cinny/src/app/hooks/useSpaceHierarchy.ts`
|
||||
- **Status:** **OPEN**
|
||||
- **Status:** **FALSE POSITIVE — CLOSED**
|
||||
- **Issue:** Inline Jotai atom creation in a hook risks re-rendering components unnecessarily.
|
||||
- **Proposed Fix:** Lift atom definition out of the hook or utilize `useMemo` to ensure atom stability.
|
||||
- **Resolution:** `useState(() => atom(...))` IS the correct Jotai pattern for local stable atom references. The factory function form of `useState` ensures the atom is created only once per component mount. No change warranted.
|
||||
|
||||
---
|
||||
|
||||
@@ -123,7 +123,7 @@ This document tracks identified bugs, edge cases, and architectural discrepancie
|
||||
| State Sync | Fire-and-forget network call to set offline presence during `pagehide` event may not complete reliably, potentially causing UI drift in presence status. | `cinny/src/app/hooks/usePresenceUpdater.ts` | OPEN |
|
||||
| State Sync | Fire-and-forget network call `setPresence().catch(...)` suppresses errors, meaning the app may falsely assume presence update success. | `cinny/src/app/hooks/usePresenceUpdater.ts` | OPEN |
|
||||
| Memory Leak | Decrypted Media Memory Leak (Gallery & Lightbox) due to missing virtualization and blob revocation. | `cinny/src/app/features/room/MediaGallery.tsx` | OPEN |
|
||||
| Data Persistence | Scheduled Messages are ephemeral (lost on refresh) due to fragile `localStorage` parsing. | `cinny/src/app/state/scheduledMessages.ts` | OPEN |
|
||||
| Data Persistence | Scheduled Messages are ephemeral (lost on refresh) due to fragile `localStorage` parsing. | `cinny/src/app/state/scheduledMessages.ts` | FIXED — now uses `atomWithStorage` + `createJSONStorage` (Jotai's built-in persistence with error-safe JSON parsing) |
|
||||
| Memory Leak | Potential memory leak due to uncleaned `handleMouseMove` listener in `usePan`. | `cinny/src/app/hooks/usePan.ts` | OPEN |
|
||||
| Asset Optimization | Large unoptimized media asset (213KB) found in `public/res`. | `public/res/Lotus.png` | OPEN |
|
||||
| Data Persistence | Non-atomic `localStorage` updates in session management can lead to inconsistent state. | `cinny/src/app/state/sessions.ts` | OPEN |
|
||||
@@ -150,7 +150,7 @@ This document tracks identified bugs, edge cases, and architectural discrepancie
|
||||
| :-------------------------------------------------------------------- | :-------------------------------------------------------- |
|
||||
| Hardcoded inline style `cursor: 'pointer'` | `cinny/src/app/plugins/react-custom-html-parser.tsx` |
|
||||
| Hardcoded color `#00D4FF`, `#FFB300` ✅ **VERIFIED COMPLIANT** | `cinny/src/app/components/event-readers/EventReaders.tsx` |
|
||||
| Hardcoded color `#EE1D52`, `#9146ff`, `#ff4500`, `#cb3837`, `#f48024` | `cinny/src/app/components/url-preview/UrlPreviewCard.tsx` |
|
||||
| Hardcoded color `#EE1D52`, `#9146ff`, `#ff4500`, `#cb3837`, `#f48024` ⚠️ **BRAND EXCEPTION** | `cinny/src/app/components/url-preview/UrlPreviewCard.tsx` + `UrlPreview.css.tsx` — official third-party brand colors in SVG logos and site badge backgrounds; cannot convert to CSS variables without inventing new tokens (violates TDS rule 3) |
|
||||
| Massive number of hardcoded `backgroundColor` values | `cinny/src/app/features/lotus/chatBackground.ts` |
|
||||
| Hardcoded colors `#00FF88`, `#FF6B00` ✅ **VERIFIED COMPLIANT** | `cinny/src/app/features/call/CallControls.tsx` |
|
||||
| Hardcoded fallback hexes in toast colors ✅ **FIXED** | `cinny/src/app/features/toast/LotusToastContainer.tsx` |
|
||||
|
||||
@@ -20,6 +20,7 @@ import { MatrixError } from 'matrix-js-sdk';
|
||||
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
||||
import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback';
|
||||
import { stopPropagation } from '../../utils/keyboard';
|
||||
import { useModalStyle } from '../../hooks/useModalStyle';
|
||||
|
||||
type LeaveRoomPromptProps = {
|
||||
roomId: string;
|
||||
@@ -28,6 +29,7 @@ type LeaveRoomPromptProps = {
|
||||
};
|
||||
export function LeaveRoomPrompt({ roomId, onDone, onCancel }: LeaveRoomPromptProps) {
|
||||
const mx = useMatrixClient();
|
||||
const modalStyle = useModalStyle(480);
|
||||
|
||||
const [leaveState, leaveRoom] = useAsyncCallback<undefined, MatrixError, []>(
|
||||
useCallback(async () => {
|
||||
@@ -56,7 +58,7 @@ export function LeaveRoomPrompt({ roomId, onDone, onCancel }: LeaveRoomPromptPro
|
||||
escapeDeactivates: stopPropagation,
|
||||
}}
|
||||
>
|
||||
<Dialog variant="Surface" aria-labelledby="leave-room-dialog-title">
|
||||
<Dialog variant="Surface" aria-labelledby="leave-room-dialog-title" style={modalStyle}>
|
||||
<Header
|
||||
style={{
|
||||
padding: `0 ${config.space.S200} 0 ${config.space.S400}`,
|
||||
|
||||
@@ -20,6 +20,7 @@ import { MatrixError } from 'matrix-js-sdk';
|
||||
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
||||
import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback';
|
||||
import { stopPropagation } from '../../utils/keyboard';
|
||||
import { useModalStyle } from '../../hooks/useModalStyle';
|
||||
|
||||
type LeaveSpacePromptProps = {
|
||||
roomId: string;
|
||||
@@ -28,6 +29,7 @@ type LeaveSpacePromptProps = {
|
||||
};
|
||||
export function LeaveSpacePrompt({ roomId, onDone, onCancel }: LeaveSpacePromptProps) {
|
||||
const mx = useMatrixClient();
|
||||
const modalStyle = useModalStyle(480);
|
||||
|
||||
const [leaveState, leaveRoom] = useAsyncCallback<undefined, MatrixError, []>(
|
||||
useCallback(async () => {
|
||||
@@ -56,7 +58,7 @@ export function LeaveSpacePrompt({ roomId, onDone, onCancel }: LeaveSpacePromptP
|
||||
escapeDeactivates: stopPropagation,
|
||||
}}
|
||||
>
|
||||
<Dialog variant="Surface">
|
||||
<Dialog variant="Surface" style={modalStyle}>
|
||||
<Header
|
||||
style={{
|
||||
padding: `0 ${config.space.S200} 0 ${config.space.S400}`,
|
||||
|
||||
@@ -7,9 +7,15 @@ export const PageNav = recipe({
|
||||
size: {
|
||||
'400': {
|
||||
width: toRem(256),
|
||||
'@media': {
|
||||
'(max-width: 750px)': { width: '100%' },
|
||||
},
|
||||
},
|
||||
'300': {
|
||||
width: toRem(222),
|
||||
'@media': {
|
||||
'(max-width: 750px)': { width: '100%' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback';
|
||||
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
||||
import { stopPropagation } from '../../utils/keyboard';
|
||||
import { useModalStyle } from '../../hooks/useModalStyle';
|
||||
|
||||
type ReportCategory = 'spam' | 'harassment' | 'inappropriate' | 'other';
|
||||
|
||||
@@ -36,6 +37,7 @@ type ReportRoomModalProps = {
|
||||
|
||||
export function ReportRoomModal({ roomId, onClose }: ReportRoomModalProps) {
|
||||
const mx = useMatrixClient();
|
||||
const modalStyle = useModalStyle(420);
|
||||
const [category, setCategory] = useState<ReportCategory>('spam');
|
||||
|
||||
const [reportState, submitReport] = useAsyncCallback(
|
||||
@@ -103,9 +105,7 @@ export function ReportRoomModal({ roomId, onClose }: ReportRoomModalProps) {
|
||||
background: color.Surface.Container,
|
||||
borderRadius: config.radii.R400,
|
||||
boxShadow: color.Other.Shadow,
|
||||
width: '100%',
|
||||
maxWidth: 420,
|
||||
overflow: 'hidden',
|
||||
...modalStyle,
|
||||
}}
|
||||
>
|
||||
<Header
|
||||
|
||||
@@ -20,6 +20,7 @@ import { Method } from 'matrix-js-sdk';
|
||||
import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback';
|
||||
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
||||
import { stopPropagation } from '../../utils/keyboard';
|
||||
import { useModalStyle } from '../../hooks/useModalStyle';
|
||||
|
||||
type ReportCategory = 'spam' | 'harassment' | 'inappropriate' | 'other';
|
||||
|
||||
@@ -37,6 +38,7 @@ type ReportUserModalProps = {
|
||||
|
||||
export function ReportUserModal({ userId, onClose }: ReportUserModalProps) {
|
||||
const mx = useMatrixClient();
|
||||
const modalStyle = useModalStyle(420);
|
||||
const [category, setCategory] = useState<ReportCategory>('spam');
|
||||
|
||||
const [reportState, submitReport] = useAsyncCallback(
|
||||
@@ -109,9 +111,7 @@ export function ReportUserModal({ userId, onClose }: ReportUserModalProps) {
|
||||
background: color.Surface.Container,
|
||||
borderRadius: config.radii.R400,
|
||||
boxShadow: color.Other.Shadow,
|
||||
width: '100%',
|
||||
maxWidth: 420,
|
||||
overflow: 'hidden',
|
||||
...modalStyle,
|
||||
}}
|
||||
>
|
||||
<Header
|
||||
|
||||
@@ -210,6 +210,7 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
|
||||
|
||||
const [toolbar, setToolbar] = useSetting(settingsAtom, 'editorToolbar');
|
||||
const [composerToolbarButtons] = useSetting(settingsAtom, 'composerToolbarButtons');
|
||||
const touchTarget = mobileOrTablet() ? { minWidth: '44px', minHeight: '44px' } : undefined;
|
||||
const showFormat = composerToolbarButtons?.showFormat ?? true;
|
||||
const showEmoji = composerToolbarButtons?.showEmoji ?? true;
|
||||
const showSticker = composerToolbarButtons?.showSticker ?? true;
|
||||
@@ -936,6 +937,7 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
|
||||
variant="SurfaceVariant"
|
||||
size="300"
|
||||
radii="300"
|
||||
style={touchTarget}
|
||||
>
|
||||
<Icon src={Icons.PlusCircle} />
|
||||
</IconButton>
|
||||
@@ -947,6 +949,7 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
|
||||
variant="SurfaceVariant"
|
||||
size="300"
|
||||
radii="300"
|
||||
style={touchTarget}
|
||||
aria-label={toolbar ? 'Hide formatting toolbar' : 'Show formatting toolbar'}
|
||||
aria-pressed={toolbar}
|
||||
onClick={() => setToolbar(!toolbar)}
|
||||
@@ -998,6 +1001,7 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
|
||||
variant="SurfaceVariant"
|
||||
size="300"
|
||||
radii="300"
|
||||
style={touchTarget}
|
||||
>
|
||||
<Icon
|
||||
src={Icons.Sticker}
|
||||
@@ -1016,6 +1020,7 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
|
||||
variant="SurfaceVariant"
|
||||
size="300"
|
||||
radii="300"
|
||||
style={touchTarget}
|
||||
>
|
||||
<Icon
|
||||
src={Icons.Smile}
|
||||
@@ -1063,6 +1068,7 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
|
||||
size="300"
|
||||
radii="300"
|
||||
disabled={gifUploading}
|
||||
style={touchTarget}
|
||||
>
|
||||
{gifUploading ? (
|
||||
<Spinner variant="Secondary" size="100" />
|
||||
@@ -1119,6 +1125,7 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
|
||||
size="300"
|
||||
radii="300"
|
||||
title="Share location"
|
||||
style={touchTarget}
|
||||
>
|
||||
{locating ? (
|
||||
<Spinner variant="Secondary" size="100" />
|
||||
@@ -1135,6 +1142,7 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
|
||||
size="300"
|
||||
radii="300"
|
||||
title="Create poll"
|
||||
style={touchTarget}
|
||||
>
|
||||
<Icon src={Icons.OrderList} size="100" />
|
||||
</IconButton>
|
||||
@@ -1170,6 +1178,7 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
|
||||
variant="SurfaceVariant"
|
||||
size="300"
|
||||
radii="300"
|
||||
style={touchTarget}
|
||||
aria-label="Schedule message"
|
||||
title="Schedule message"
|
||||
>
|
||||
@@ -1181,6 +1190,7 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
|
||||
variant="SurfaceVariant"
|
||||
size="300"
|
||||
radii="300"
|
||||
style={touchTarget}
|
||||
aria-label="Send message"
|
||||
>
|
||||
<Icon src={Icons.Send} />
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { CSSProperties } from 'react';
|
||||
import { ScreenSize, useScreenSizeContext } from './useScreenSize';
|
||||
|
||||
/**
|
||||
* Returns responsive modal box styles. On mobile the modal expands to fill the
|
||||
* full viewport (no border radius, no max-width cap) so it doesn't float as a
|
||||
* small centered card on a phone screen.
|
||||
*/
|
||||
export function useModalStyle(desktopMaxWidth: number): CSSProperties {
|
||||
const screenSize = useScreenSizeContext();
|
||||
const isMobile = screenSize === ScreenSize.Mobile;
|
||||
|
||||
if (isMobile) {
|
||||
return {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
maxWidth: '100%',
|
||||
maxHeight: '100%',
|
||||
borderRadius: 0,
|
||||
overflow: 'hidden auto',
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
width: '100%',
|
||||
maxWidth: desktopMaxWidth,
|
||||
overflow: 'hidden',
|
||||
};
|
||||
}
|
||||
@@ -57,6 +57,8 @@
|
||||
|
||||
html {
|
||||
height: 100%;
|
||||
/* dvh shrinks when mobile virtual keyboard appears, so the layout never gets pushed off-screen */
|
||||
height: 100dvh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user