From 52047662760520a918ecdc369d61251b5c36782b Mon Sep 17 00:00:00 2001 From: Jared Vititoe Date: Sun, 28 Jun 2026 21:00:56 -0400 Subject: [PATCH] docs: clean up LOTUS_BUGS.md and LOTUS_TODO.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per request, removed completed/resolved items (full history is in git) and reorganized both into actionable form. LOTUS_BUGS.md (864 -> 77 lines): dropped ~120 fixed-and-verified entries plus all false-positive / won't-fix records. Now two clear sections: "Needs Verification" (fixed in code, awaiting live test, cross-referenced to LOTUS_TESTING.md) and "Open — Actionable" (grouped by theme). LOTUS_TODO.md (771 -> 694 lines): removed completed [x] blocks (they live in LOTUS_FEATURES.md) and consolidated the done-but-untested ones into a single "Done — Awaiting Verification" index pointing at LOTUS_TESTING.md. Pending [ ] items and nested roadmaps (e.g. DeepFilterNet/FRCRN under P5-30) were preserved exactly (verified 42 -> 42); empty sections removed. Co-Authored-By: Claude Opus 4.8 --- LOTUS_BUGS.md | 897 ++++---------------------------------------------- LOTUS_TODO.md | 113 +------ 2 files changed, 73 insertions(+), 937 deletions(-) diff --git a/LOTUS_BUGS.md b/LOTUS_BUGS.md index 129870130..dab4b7f6f 100644 --- a/LOTUS_BUGS.md +++ b/LOTUS_BUGS.md @@ -1,864 +1,77 @@ -# Lotus Chat — Bug Report & Technical Audit +# Lotus Chat — Open Bugs & Technical Debt -**Date:** June 2026 +**Only OPEN and awaiting-verification items live here.** Resolved findings +(fixed-and-verified, false-positives, won't-fix) have been removed to keep this +actionable — the full history is in git. Items fixed in code but not yet +verified in a real environment are in **Needs Verification** below and have +step-by-step checks in [`LOTUS_TESTING.md`](./LOTUS_TESTING.md). -This document tracks identified bugs, edge cases, and architectural discrepancies found during the audit of the Lotus Chat codebase. Recommended fixes are provided for each item. +> Design rules for any fix here: follow the **Native-Cinny Law** and **TDS +> Design Law** in [`LOTUS_TODO.md`](./LOTUS_TODO.md). --- -## 🚩 Critical & UI Bugs +## ⚠️ Needs Verification — fixed in code, awaiting live testing -### 12. PiP Mute Icon Misidentifies Whose Mic Is Muted +Implemented and gate-green; confirm each per `LOTUS_TESTING.md`, then delete the row. -- **File:** `cinny/src/app/components/CallEmbedProvider.tsx` -- **Status:** **FIXED ⚠️ UNTESTED** — needs verification in a live call with at least one other participant who mutes/unmutes -- **Issue:** The muted-mic badge in the Picture-in-Picture window used `useRemoteAllMuted` (fires when ANY remote participant is muted) and rendered in the bottom-left corner — the conventional position for "YOUR" mic status. Users read it as their own mic being muted. -- **Root Cause:** `PipMuteOverlay` was triggering on remote-mute events while displaying in a position that implies local-user status. -- **Fix Applied:** - - **Bottom-left badge** now shows only when the LOCAL user's mic is muted (checked via `!controlState.microphone` from `useCallControlState`). Includes "You" label to make it unambiguous. Uses `color.Critical.Main`. - - **Top-right badge** (new) shows "All muted" in `color.Warning.Main` when all remote participants are muted — positioned and labeled so it's clearly about other people, not the local user. - - Both badges use `aria-label` / `title` for accessibility. +| ID | Item | File / area | Test | +| :--- | :------------------------------------------------------- | :--------------------------------------------------- | :---- | +| #1 | Camera focus during screenshare ("Focus camera" menu) | `CallControl.ts`, `MemberGlance.tsx` | A5 | +| #2 | Chat-background animation flicker (`contain:paint`) | `lotus/chatBackground.ts` | F1 | +| #3 | Avatar decorations on call tiles | `call/CallMemberCard.tsx` | A6 | +| #4 | DM/group ringtone selection + in-call banner | `CallEmbedProvider.tsx`, `ringtones.ts` | A1–A4 | +| #6 | Background vs. seasonal theme mutual exclusion | `state/settings.ts`, `General.tsx` | F2 | +| #7 | Composer toolbar touch targets (≥44px) | `room/RoomInput.tsx` | E1 | +| #8 | Room Settings horizontal overflow (mobile) | `components/page/style.css.ts` | E2 | +| #9 | Modal fullscreen on mobile (`useModalStyle`) | 22+ modal files | E3 | +| #10 | Composer not hidden by keyboard (`100dvh`) | `src/index.css` | E4 | +| #12 | PiP mute badge attribution (you vs. all-muted) | `CallEmbedProvider.tsx` | G1 | +| N96 | Call-recovery overlay single "Back" button | `call/CallView.tsx` | A7 | +| EC | EC iframe load watchdog + self-heal + recovery UI | `plugins/call/CallEmbed.ts`, `CallView.tsx` | A7 | +| Gal | MediaGallery lazy-decrypt (true virtualization deferred) | `room/MediaGallery.tsx` | H1 | +| a11y | aria-labels: edit-history / reaction / thread / reply | `message/*` (`FallbackContent`, `Reaction`, `Reply`) | I | --- -### 1. No Camera Focus During Screenshare +## 🔴 Open — Actionable -- **File:** `cinny/src/app/plugins/call/CallControl.ts`, `cinny/src/app/features/call-status/MemberGlance.tsx` -- **Status:** **FIXED ⚠️ UNTESTED** — needs verification in a live call with an active screenshare + a participant on camera -- **Issue:** Automatic screenshare spotlighting forces primary display override, preventing users from manually focusing on camera feeds. -- **Root Cause:** Before this feature there was no UI path to manually pick a camera to focus, so EC's auto-spotlight (which prioritizes an active screenshare) always won. -- **Fix Applied:** `CallControl.focusCameraParticipant(userId)` switches EC to spotlight mode and clicks that participant's `[data-testid="videoTile"]` inside the EC iframe — in Element Call, clicking a tile in spotlight **pins** it, so the user's explicit selection takes precedence over the auto-pinned screenshare. Exposed via a "Focus camera" item in the `MemberGlance` participant menu (avatar → menu). Falls back to a plain spotlight toggle if the tile isn't rendered (e.g. camera off). -- **Architectural note:** EC owns the grid/spotlight renderer inside its iframe; our control is DOM-level tile clicks. The pin persists until changed, so a one-shot focus is sufficient. A continuously-enforced "sticky" focus that re-pins on every EC spotlight change was deliberately **not** built — it would require fighting EC's internal state on each mutation and risks flicker. +### Calls / Audio -### 2. Chat Background Animation Flickering +- **N95 — AFK auto-mute keeps the hardware mic active while muted.** `useAfkAutoMute.ts` holds its own `getUserMedia` stream independent of EC's; muting in the UI doesn't stop those tracks, so the OS recording indicator stays lit. Fix: stop the `MediaStream` tracks on mute, re-request on unmute. (Repro: `LOTUS_TESTING.md` L1.) +- **N127 — ML denoise shim is never injected in `vite dev`.** The `lotusDenoise` plugin injects only on `closeBundle` (build), so ML noise suppression is silently inactive during local dev. Add a dev-mode injection (`configureServer` / `transformIndexHtml`). Dev-only impact. -- **File:** `cinny/src/app/features/lotus/chatBackground.ts` -- **Status:** **FIXED ⚠️ UNTESTED** — needs verification on a real device with an animated background active -- **Issue:** Animated background properties cause visible flickering on message text and the composer area, particularly on browsers/GPUs susceptible to repaint-induced artifacts. -- **Root Cause:** Animation triggers excessive repaints or layout recalculations on descendant elements, likely due to animating non-GPU accelerated properties on parent containers without proper rendering context isolation. -- **Fix Applied:** `getChatBg()` now injects `willChange: 'background-position'` and `contain: 'paint'` for any animated variant. This promotes the element to its own compositor layer and isolates repaints from descendants. Background-position animation is already GPU-hinted on modern browsers; `contain: paint` prevents descendant elements from being invalidated during each frame. +### Security & Privacy -### 3. Avatar Decorations in Element Call +- **N97 — Access token stored in plaintext `localStorage`** (`state/sessions.ts`), vulnerable to XSS; device ID likewise. Architectural — needs a token-protection / session-storage redesign. +- **Session writes are non-atomic and not cross-tab synced** (`state/sessions.ts`) — risks inconsistent state / races across tabs. +- **Persisted PII without encryption:** user status message + expiry (`settings/account/Profile.tsx`), unsent composer drafts (`room/RoomInput.tsx`). Leak risk on shared devices. -- **File:** `cinny/src/app/features/call/CallMemberCard.tsx` -- **Status:** **FIXED ⚠️ UNTESTED** — needs verification in a live call with a participant who has a decoration set -- **Issue:** Avatar decorations are failing to render within the call/room interface member lists. -- **Root Cause:** Member lists and the people drawer already wrapped avatars in ``, but the call participant tile (`CallMemberCard`) rendered a bare `` with no decoration wrapper — so decorations were absent specifically on call tiles. (Note: avatars rendered _inside_ the Element Call iframe are EC-rendered and out of our control; this fix covers our own participant roster / prescreen.) -- **Fix Applied:** Wrapped the call-tile avatar in `` (commit `0394fce9`), matching the member-list pattern. +### PWA / Offline / Notifications -### 4. DM and Group Message Calls +- **N105 — Service worker has no `notificationclick` handler** — notification clicks are broken when the tab is closed. Needs `showNotification()` via the SW + a `notificationclick` listener. +- **N107 — SW has no `push` handler** — Web Push delivery is entirely non-functional. Needs a `push` listener + a Matrix push-gateway integration. +- **N108 — No maskable PWA icon** — Android adaptive icons render incorrectly. Needs a maskable icon asset + `purpose: "maskable"` manifest entry. +- **No app-asset caching strategy** (`src/sw.ts`) — no offline capability. +- **`manifest: false`** in `vite.config.js` — may block correct PWA install if not handled externally. -- **File:** `cinny/src/app/components/CallEmbedProvider.tsx` -- **Status:** **FIXED ⚠️ UNTESTED** — needs live-call verification: (a) ring/preview per selected ringtone & volume; (b) the corner banner appearing (with a single ping, not a loop) when a second call arrives while already in a call. -- **Issue:** Incoming call ringtone is hardcoded, lacks volume control, and is suppressed if the user is already in an active call. -- **Root Cause:** Ringing logic is tightly coupled to `RTCNotification` events in `CallEmbedProvider.tsx`, using a hardcoded audio file path. It lacks an abstraction for sound management or user-configurable settings for ringtones/volumes. -- **Fix Applied:** - - `ringtoneVolume` setting (0–100, default 70); applied to the ring. Slider in Settings → General → Calls. - - **(a) Ringtone selection** (`4a875884`): `ringtoneId` setting (`classic | chime | soft | retro | none`). New `utils/ringtones.ts` synthesizes the three styles in-browser (WebAudio, mirroring `callSounds.ts`) — no new binary assets; `classic` keeps `call.ogg`; `none` is silent/visual-only. `startRingtone()` loops until stopped; `previewRingtone()` powers the on-select preview in Settings. Persisted id is whitelisted in `getSettings`. - - **(b) Active-call notification** (`c67aed01`): when already joined to a _different_ call, a compact, non-intrusive `IncomingCallBanner` (caller avatar + name + Answer/Reject, top-right) replaces the full-screen `IncomingCall` overlay and plays a **single soft ping** (one-shot ringtone) instead of the looping ring — so it never takes over the screen or talks over the active call. Full overlay still shows when in no call; being in the ringing room's own call still shows nothing. +### Dependencies & Build -### 5. Seasonal Themes and Chat Backgrounds Design +- **`matrix-js-sdk` pinned to a Release Candidate** (`41.6.0-rc.0`); `@atlaskit` and build tools (`vite`, `typescript`, `eslint`) on unstable/experimental pins — review for stable versions; RC SDK is a tree-shaking/bundle-size risk. +- **Build-time overhead:** `lotusDenoise` does heavy sequential `fs` work in `closeBundle`; `viteStaticCopy` config is complex with redundant renames — could be streamlined. -- **File:** `cinny/src/app/hooks/useTheme.ts`, `cinny/src/app/features/lotus/chatBackground.ts` -- **Status:** **OPEN** -- **Issue:** Basic CSS or random moving lines are insufficient for high-fidelity wallpaper/theming. They lack professional design theory, coherence, and aesthetic depth. -- **Root Cause:** Current implementation relies on basic CSS, lacks advanced design theory, and does not leverage modern, performant CSS wallpaper techniques. -- **Proposed Fix (Extreme Depth Redesign):** - - **Research-Backed Implementation:** Implement advanced design techniques (layered `oklch` gradients, `backdrop-filter` for refractive "liquid glass" effects, GPU-accelerated `transform` animations) to create living, breathing backgrounds. - - **Performance Optimization:** Ensure all animations strictly use compositor-thread properties (`transform`, `opacity`) and apply `contain: paint` / `will-change: transform` to prevent layout thrashing/flickering. - - **Design Resources (Examples/Inspiration):** - - [Uiverse.io Patterns](https://uiverse.io/patterns) - - [MagicPattern CSS Backgrounds](https://www.magicpattern.design/tools/css-backgrounds) - - [Prismic Blog: CSS Background Effects](https://prismic.io/blog/css-background-effects) - - [CSS-Pattern.com](https://css-pattern.com) (Pure CSS pattern library) - - [BGJar](https://bgjar.com) (Performance-focused generators) - - **Goal:** Treat each theme/background as a week-long development sprint to ensure professional polish, WCAG AA contrast compliance for overlaying UI, and seamless integration with the Lotus TDS. +### Code Hygiene / DevEx -### 6. Exclusive Background vs. Seasonal Choice +- **No automated test suite** (`src/`) — no unit/integration tests configured. +- **Extensive `as any` casts** across `src/` — gradual typing cleanup. +- **`types/matrix/` mirrors SDK types** instead of importing them — drift risk. +- **Hardcoded CDN URL** should move to an env var (the decoration CDN is now single-sourced in `avatarDecorations.ts`, but the literal is still in-repo). +- **`patch-folds.mjs` edits `node_modules` directly** — consider `patch-package`. +- **Infra docs:** `contrib/nginx` lacks security headers (HSTS/CSP) + uses rewrites over `try_files`; `contrib/caddy` has a placeholder path. CI/CD (`prod-deploy.yml`): sequential deploy, aggressive 1-min Netlify timeout, `package-manager-cache: false`. +- **README / CONTRIBUTING:** stale upstream bug-tracker/donations/CLA links; README↔CONTRIBUTING misalignment. +- **Architecture notes (low priority):** deep `features/` + `hooks/` nesting, many small coupled hooks, possible dead CSS/components, `SpacingVariant` / `DropTarget` recipe simplification. +- **Git workflow (forward-looking):** keep commits scoped — past monolithic "fix all bugs" commits and inconsistent prefixes hurt `git bisect`. -- **File:** `cinny/src/app/state/settings.ts` -- **Status:** **FIXED ⚠️ UNTESTED** — needs verification: (a) pick a background, confirm seasonal theme auto-clears; (b) pick a seasonal theme, confirm background auto-clears; (c) set both via old localStorage data and reload, confirm SeasonalEffect guard suppresses the overlay -- **Issue:** Concurrent application of both Chat Backgrounds and Seasonal Themes causes visual clutter and high GPU usage. -- **Root Cause:** These are currently handled as independent settings in the `settingsAtom` and applied simultaneously without mutual exclusion. -- **Fix Applied:** Mutual exclusion enforced at two layers: (1) `General.tsx` — ChatBgGrid clears seasonalThemeOverride→'off' when any non-'none' background is picked; SeasonalBgGrid clears chatBackground→'none' when any real seasonal theme is selected. (2) `SeasonalEffect.tsx` — runtime guard returns null if `chatBackground !== 'none'`, protecting against legacy persisted state. +### Big Projects -### 7. Tiny Touch Targets in Composer Toolbar - -- **File:** `cinny/src/app/features/room/RoomInput.tsx` -- **Status:** **FIXED ⚠️ UNTESTED** — needs verification on a real mobile device: open composer, confirm all toolbar buttons are tappable without mis-taps -- **Issue:** Toolbar buttons have hit areas smaller than the WCAG-recommended 44x44px for touch, hindering mobile accessibility. -- **Fix Applied:** Added `touchTarget = { minWidth: '44px', minHeight: '44px' }` computed from `mobileOrTablet()` and applied as `style={touchTarget}` to all 8 composer toolbar `IconButton` elements (attach, format, sticker, emoji, GIF, location, poll, schedule, send). - -### 8. Horizontal Overflow in Room Settings - -- **File:** `cinny/src/app/components/page/style.css.ts` -- **Status:** **FIXED ⚠️ UNTESTED** — needs verification: open Room Settings on a narrow mobile screen, confirm nav panel fills full width and no horizontal scrollbar appears -- **Issue:** Wide tables and input elements in room settings cause horizontal overflow on mobile viewports. -- **Fix Applied:** Added `@media (max-width: 750px) { width: '100%' }` to both `'400'` and `'300'` size variants of the `PageNav` vanilla-extract recipe in `style.css.ts`. - -### 9. Modal Float-Style Responsiveness - -- **File:** Multiple modal files -- **Status:** **FIXED ⚠️ UNTESTED** — needs verification by opening each modal on a real mobile device -- **Issue:** Modals appear as floating boxes on mobile, creating navigation and readability challenges. -- **Fix Applied:** Created `useModalStyle(desktopMaxWidth)` hook (`src/app/hooks/useModalStyle.ts`) that returns fullscreen styles on mobile (no border-radius, no max-width, `height: 100%`) and desktop box styles otherwise. Applied to all 22+ modal files: `LeaveRoomPrompt`, `LeaveSpacePrompt`, `ReportRoomModal`, `ReportUserModal`, `DeviceVerification`, `InviteUserPrompt`, `LogoutDialog`, `DeviceVerificationSetup`, `DeviceVerificationReset`, `JoinAddressPrompt`, `JumpToTime`, `EditHistoryModal`, `ForwardMessageDialog`, `RemindMeDialog`, `CreateRoomModal`, `CreateSpaceModal`, `ScheduleMessageModal`, `PollCreator`, `AddExistingModal`, `RoomEncryption`, `RoomUpgrade`, `Modal500`, `ReadReceiptAvatars`, `RoomTopicViewer`. -- **Note:** `UIAFlowOverlay` already fullscreen via `` — no change needed. `JoinRulesSwitcher`/`RoomNotificationSwitcher` are dropdowns, not modals. - -### 10. Composer Keyboard Obscurity - -- **File:** `src/index.css` -- **Status:** **FIXED ⚠️ UNTESTED** — needs verification on iOS Safari specifically (the worst offender); on Android Chrome `100dvh` has been standard since Chrome 108 -- **Issue:** The chat composer is often partially or fully obscured by the virtual keyboard on mobile. -- **Fix Applied:** Added `height: 100dvh` (dynamic viewport height) to `html` alongside the existing `height: 100%` fallback. `dvh` updates when the software keyboard appears, ensuring the layout shrinks correctly and the composer stays visible. - -### 11. Inline Jotai atom creation - -- **File:** `cinny/src/app/hooks/useSpaceHierarchy.ts` -- **Status:** **FALSE POSITIVE — CLOSED** -- **Issue:** Inline Jotai atom creation in a hook risks re-rendering components unnecessarily. -- **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. - ---- - -## 📦 Barrel File Audit - -| File Path | Note | Status | -| :------------------------------------------ | :------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------- | -| `cinny/src/app/plugins/call/index.ts` | Extensive `export *` usage | WON'T FIX — `export *` is idiomatic and tree-shakes correctly in Vite/Rollup; converting to explicit named exports is high-churn for ~zero benefit. | -| `cinny/src/app/plugins/text-area/index.ts` | Extensive `export *` usage | WON'T FIX — same as above (acceptable pattern). | -| `cinny/src/app/components/message/index.ts` | Extensive `export *` usage | WON'T FIX — same as above (acceptable pattern). | - ---- - -## 🔍 Technical & Performance Refinements - -| Category | Issue Description | File Path | Status | -| :---------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| 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` | FIXED (`d2946c00`) — unload path now uses `fetch({ keepalive: true })` so the request survives page teardown (`sendBeacon` was unusable here: it can't set the auth header). | -| 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` | FIXED (`d2946c00`) — errors are now surfaced via `warnPresenceFailure` (redacted logging) instead of being silently swallowed. | -| Memory Leak | Decrypted Media Memory Leak (Gallery & Lightbox) due to missing virtualization and blob revocation. | `cinny/src/app/features/room/MediaGallery.tsx` | PARTIALLY FIXED ⚠️ UNTESTED — Blob revocation was already correct; added `enabled` param to `useDecryptedMediaUrl` and `useNearViewport(300px)` to each `GalleryTile` to gate decryption until near-viewport, reducing burst on pagination. True virtualization (windowing) deferred — requires significant refactor. | -| 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` | FALSE POSITIVE — `usePan` already uses `attachedRef` to track listeners and cleans them up in an unmount `useEffect`. No change needed. | -| Asset Optimization | Large unoptimized media asset (213KB) found in `public/res`. | `public/res/Lotus.png` | FIXED — resized 1080x1080 -> 256x256 + pngquant; 213KB -> 20KB (displayed at <=70px, so no visible quality loss). | -| Data Persistence | Non-atomic `localStorage` updates in session management can lead to inconsistent state. | `cinny/src/app/state/sessions.ts` | OPEN | -| Data Persistence | Lack of cross-tab synchronization for `localStorage` updates in session management risks race conditions. | `cinny/src/app/state/sessions.ts` | OPEN | -| Network Resilience | `uploadContent` lacks retry logic, failing immediately upon network error. | `cinny/src/app/utils/matrix.ts` | FIXED (`d2946c00`) — bounded retry (`UPLOAD_MAX_RETRY_COUNT=3`) gated by `isRetryableUploadError` (transient/network/5xx/429 only, not 4xx), reusing the `rateLimitedActions` capped-exponential backoff. | -| Network Resilience | `rateLimitedActions` uses basic retry logic without exponential backoff, which may exacerbate 429 issues. | `cinny/src/app/utils/matrix.ts` | FIXED — fallback delay now uses capped exponential backoff (`min(1000 * 2^retryCount, 30_000)ms`) when server doesn't send `Retry-After`; server header still takes precedence via `getRetryAfterMs()`. | -| Matrix Event Robustness | `useMatrixEventRenderer` handles unknown events gracefully by returning `null`, which may hide potentially important unrendered data. | `cinny/src/app/hooks/useMatrixEventRenderer.ts` | FALSE POSITIVE — returning `null` for unrendered types is the intended contract. Callers opt into rendering unknowns via the `renderStateEvent` / `renderEvent` fallback params; `null` only results when the caller deliberately supplies no fallback. No change warranted. | -| Data Contract | `MatrixError` instantiation with `UploadResponse` might be brittle. | `cinny/src/app/utils/matrix.ts` | FIXED (`d2946c00`) — replaced the brittle direct construction with `matrixErrorFromUploadResponse` / `matrixErrorFromUnknown` guards that validate shape before building a `MatrixError`. | -| Type Safety | `addRoomIdToMDirect` uses `as any` cast for `AccountDataEvent.Direct`, bypassing type contract validation. | `cinny/src/app/utils/matrix.ts` | FIXED (`d2946c00`) — `addRoomIdToMDirect` / `removeRoomIdFromMDirect` now use `EventType.Direct` + a typed `MDirectContent`, dropping the `as any` cast. | -| Robustness | `rateLimitedActions` relies on `MatrixError.httpStatus` which might not exist on all error variants. | `cinny/src/app/utils/matrix.ts` | FALSE POSITIVE — `MatrixError.httpStatus` is defined as `readonly httpStatus?: number` in `matrix-js-sdk/lib/http-api/errors.d.ts`. It is optional (not on all instances) but the `?.` optional chain already guards against undefined. No change needed. | -| Type Contract | Custom types in `cinny/src/types/matrix` mirror SDK types instead of using them, risking drift and contract mismatches. | `cinny/src/types/matrix/` | OPEN | - -## 🏗️ Architectural & Hygiene Audit - -| Category | Issue Description | File Path | Status | -| :------- | :--------------------------------------------------------------- | :-------- | :----- | -| Hygiene | No stale development notes or TypeScript strictness issues found | N/A | OPEN | - ---- - -## 🏗️ TDS Compliance & Styling Issues - -| Issue Description | File Path | -| :------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| 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` ⚠️ **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 ⚠️ **PATTERN CONTENT EXCEPTION** | `cinny/src/app/features/lotus/chatBackground.ts` — each background's base color is aesthetic content that defines the pattern identity; converting requires inventing 40+ CSS variables (violates TDS rule 3) or using CSS4 `relative-color-syntax` in inline styles (insufficient browser support); these are visual content, not UI chrome | -| 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` | - ---- - -## 🌐 Localization, Accessibility & Performance - -| Category | Issue Description | File Path | Status | -| :------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Localization | Hardcoded UI string: "Chat Room" | `src/app/components/create-room/CreateRoomTypeSelector.tsx` | FIXED (`1a5896ef`) — wrapped in `t()` + key added to `en.json` | -| Localization | Hardcoded UI string: "Messages, photos, and videos." | `src/app/components/create-room/CreateRoomTypeSelector.tsx` | FIXED (`1a5896ef`) — wrapped in `t()` + key added to `en.json` | -| Localization | Hardcoded UI string: "Voice Room" | `src/app/components/create-room/CreateRoomTypeSelector.tsx` | FIXED (`1a5896ef`) — wrapped in `t()` + key added to `en.json` | -| Localization | Hardcoded UI string: "Live audio and video conversations." | `src/app/components/create-room/CreateRoomTypeSelector.tsx` | FIXED (`1a5896ef`) — wrapped in `t()` + key added to `en.json` | -| Localization | Hardcoded UI string: "Download" | `src/app/components/image-viewer/ImageViewer.tsx` | FIXED (`1a5896ef`) — wrapped in `t()` + key added to `en.json` | -| Localization | Hardcoded UI string: "Open Location" | `src/app/components/message/MsgTypeRenderers.tsx` | FIXED (`1a5896ef`) — wrapped in `t()` + key added to `en.json` | -| Localization | Hardcoded UI string: "Thread" | `src/app/components/message/Reply.tsx` | FIXED (`1a5896ef`) — wrapped in `t()` + key added to `en.json` | -| Localization | Hardcoded UI string: "View" | `src/app/components/message/content/ImageContent.tsx` | FIXED (`1a5896ef`) — wrapped in `t()` + key added to `en.json` | -| Localization | Hardcoded UI string: "Spoiler" | `src/app/components/message/content/ImageContent.tsx` | FIXED (`1a5896ef`) — wrapped in `t()` + key added to `en.json` | -| Localization | Hardcoded UI string: "Retry" | `src/app/components/message/content/ImageContent.tsx` | FIXED (`1a5896ef`) — wrapped in `t()` + key added to `en.json` | -| Localization | Hardcoded UI string: "Close" | `src/app/components/DeviceVerification.tsx` | FIXED (`1a5896ef`) — wrapped in `t()` + key added to `en.json` | -| Localization | Hardcoded UI string: "Accept" | `src/app/components/DeviceVerification.tsx` | FIXED (`1a5896ef`) — wrapped in `t()` + key added to `en.json` | -| Localization | Hardcoded UI string: "They Match" | `src/app/components/DeviceVerification.tsx` | FIXED (`1a5896ef`) — wrapped in `t()` + key added to `en.json` | -| Localization | Hardcoded UI string: "Okay" | `src/app/components/DeviceVerification.tsx` | FIXED (`1a5896ef`) — wrapped in `t()` + key added to `en.json` | -| Localization | Hardcoded UI string: "Join Server" | `src/app/components/url-preview/UrlPreviewCard.tsx` | FIXED (`1a5896ef`) — wrapped in `t()` + key added to `en.json` | -| Localization | Hardcoded UI string: "Invite" | `src/app/components/invite-user-prompt/InviteUserPrompt.tsx` | FIXED (`1a5896ef`) — wrapped in `t()` + key added to `en.json` | -| Localization | Hardcoded UI string: "Files" | `src/app/components/upload-board/UploadBoard.tsx` | FIXED (`1a5896ef`) — wrapped in `t()` + key added to `en.json` | -| Localization | Hardcoded UI string: "Send" | `src/app/components/upload-board/UploadBoard.tsx` | FIXED (`1a5896ef`) — wrapped in `t()` + key added to `en.json` | -| Localization | Hardcoded UI string: "Upload Failed" | `src/app/components/upload-board/UploadBoard.tsx` | FIXED (`1a5896ef`) — wrapped in `t()` + key added to `en.json` | -| Localization | Hardcoded UI string: "Password" | `src/app/components/uia-stages/PasswordStage.tsx` | FIXED (`1a5896ef`) — wrapped in `t()` + key added to `en.json` | -| Bundle Size | Large unoptimized media asset (213KB) | `public/res/Lotus.png` | FIXED — see Asset Optimization above (213KB -> 20KB). | -| Matrix Logic | Inefficient repeated `mx.getRoom()` calls in component render loops | `src/app/features/lobby/Lobby.tsx` | FALSE POSITIVE — `Lobby` already routes its render loop through the memoized `useGetRoom(allJoinedRooms)`. The two remaining `mx.getRoom()` calls are inside drag/drop event handlers (not render loops) and are O(1) SDK map lookups. No change warranted. | -| Matrix Logic | Inefficient repeated `mx.getRoom()` calls in component render loops | `src/app/components/emoji-board/EmojiBoard.tsx` | FIXED (`b7e1f89c`) — pack-label `mx.getRoom()` lookups in `EmojiSidebar`/`StickerSidebar` hoisted into a `useMemo`'d `Map` built once per pack list. | -| Performance | Numerous event handlers (e.g., handleUserClick, handleReplyClick) lack `useCallback`, leading to unnecessary re-renders of message components. | `cinny/src/app/features/room/RoomTimeline.tsx` | FIXED (`b7e1f89c`) — `handleJumpToLatest`/`handleJumpToUnread`/`handleMarkAsRead` wrapped in `useCallback`. | -| Performance | The `submit` function and file handling callbacks (e.g., handleSendUpload) are re-created on every render, causing re-renders of the editor and toolbar components. | `cinny/src/app/features/room/RoomInput.tsx` | FIXED (`b7e1f89c`) — `handleCancelUpload`/`handleSendUpload`/`handleShareLocation`/`handleEmoticonSelect`/`handleStickerSelect` wrapped in `useCallback`. | -| Accessibility | `button` for edit history lacks `aria-label` | `cinny/src/app/components/message/content/FallbackContent.tsx` | FIXED ⚠️ UNTESTED — added `aria-label="View edit history"` | -| Accessibility | `button` for reaction lacks `aria-label` | `cinny/src/app/components/message/Reaction.tsx` | **FIXED ⚠️ UNTESTED** — `Reaction` component now computes `aria-label="{shortcode} reaction, N people"` internally using `getShortcodeFor`; custom (mxc://) emoji falls back to "custom emoji reaction". | -| Accessibility | `button` for ThreadIndicator lacks `aria-label` | `cinny/src/app/components/message/Reply.tsx` | FIXED ⚠️ UNTESTED — added `aria-label="View thread"` | -| Accessibility | `button` for ReplyLayout lacks `aria-label` | `cinny/src/app/components/message/Reply.tsx` | FIXED ⚠️ UNTESTED — added `aria-label="Jump to original message"` | - ---- - -## 🔧 Infrastructure, DevEx & Type Safety - -| Category | Issue Description | File Path | Status | -| :------------- | :----------------------------------------------------------------------------------------------------------- | :--------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Dependencies | `lodash` pinned to non-existent version `4.18.1` | `cinny/package.json` | FALSE POSITIVE / FIXED — 4.18.1 resolves from npm with a valid integrity hash and loads (not non-existent). It was an UNUSED direct dep; removed in `chore(deps): remove unused direct lodash dependency`. Stays transitively for slate. | -| Dependencies | Various pinned versions of `@atlaskit`, `matrix-js-sdk` | `cinny/package.json` | OPEN | -| Dependencies | `matrix-js-sdk` pinned to Release Candidate (`41.6.0-rc.0`) | `cinny/package.json` | OPEN | -| Dependencies | Unstable/experimental versions for build tools (`vite` 8.0.14, `typescript` 6.0.3, `eslint` 9.39.4) | `cinny/package.json` | OPEN | -| CI/CD | `package-manager-cache` set to `false` | `cinny/.github/workflows/build-pull-request.yml` | OPEN | -| CI/CD | Inefficient sequential execution in deployment | `cinny/.github/workflows/prod-deploy.yml` | OPEN | -| CI/CD | Aggressive 1-minute timeout for Netlify deploy | `cinny/.github/workflows/prod-deploy.yml` | OPEN | -| DevEx | Stale upstream bug tracker link/donations/CLA | `cinny/CONTRIBUTING.md` | OPEN | -| DevEx | Alignment issue between README and CONTRIBUTING | `cinny/README.md` | OPEN | -| Testing | No evident automated testing configuration/files | `cinny/src/` | OPEN | -| Type Safety | Extensive use of `as any` type assertions | `cinny/src/` | OPEN | -| Security | Hardcoded public CDN URL; consider moving to environment variable | /root/code/cinny/scripts/syncDecorations.mjs | OPEN | -| Architecture | Modifying node_modules directly is brittle; use patch-package instead | /root/code/cinny/scripts/patch-folds.mjs | OPEN | -| Robustness | Missing security headers (HSTS, CSP, etc.) and inefficient asset serving using rewrites instead of try_files | /root/code/cinny/contrib/nginx/cinny.domain.tld.conf | OPEN | -| Robustness | Incomplete documentation/placeholder path in Caddyfile | /root/code/cinny/contrib/caddy/caddyfile | OPEN | -| Matrix SDK | Inefficient listener management (`setMaxListeners: 150`) and incomplete SDK state transition handling. | `src/client/initMatrix.ts` | ASSESSED — 150 is deliberate headroom for the many legitimate hook subscriptions on the shared client; not a leak. Acceptable as-is. | -| PWA Robustness | Service worker lacks caching strategy for application assets, resulting in no offline capability. | `cinny/src/sw.ts` | OPEN | -| PWA Integrity | `manifest: false` in `vite.config.js` might prevent correct PWA installation if not handled externally. | `cinny/vite.config.js` | OPEN | -| PII Leakage | Potential PII exposure via console.error (parameter e likely contains event data). | `cinny/src/app/plugins/call/CallEmbed.ts` | VERIFIED COMPLIANT — reviewed during the logging pass (`203568c9`); the existing log path already records only `e.message`, not raw event payloads. No change needed. | -| PII Leakage | Potential PII exposure via console.warn (parameter imgError/videoError/thumbError object). | `cinny/src/app/features/room/msgContent.ts` | FIXED (`203568c9`) — media-error warnings now log only `error.name` + `error.message`, never the raw error/event object. | -| PII Leakage | Potential PII exposure via console.error (parameter e likely contains event data). | `cinny/src/app/features/room/RoomInput.tsx` | VERIFIED COMPLIANT — reviewed during the logging pass (`203568c9`); the existing log path already records only `e.message`. No change needed. | - -## 🏗️ Architectural & Resilience Audit - -| Category | Issue Description | File Path | Status | -| :----------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Element Call Integration | Lacks robust iframe failure monitoring beyond initial 'preparing' event; can result in a permanently hung 'Loading...' state with no user-visible error or recovery path. | `src/app/plugins/call/CallEmbed.ts` | FIXED (`0394fce9`) — added a `CALL_LOAD_WATCHDOG_MS` (25s) timeout that settles on ready/capabilities/joined and fails on iframe error/timeout, exposing a `loadFailed` getter + `onLoadError(cb)`. `CallView` renders a `CallLoadErrorMessage` overlay (Retry/Leave) instead of a permanent spinner. ⚠️ UNTESTED — needs a live call. | -| Component Resilience | `RoomTimeline` has no `ErrorBoundary` wrapper — a single malformed event crashing the renderer takes down the entire timeline with no fallback UI. | `src/app/features/room/RoomTimeline.tsx` | FALSE POSITIVE — `RoomView.tsx` (lines 113–137) already wraps `` in a react-error-boundary `ErrorBoundary` with a "Timeline unavailable" fallback. A wave-1 agent's redundant nested boundary was reverted. No change needed. | -| Component Resilience | `RoomInput` has no `ErrorBoundary` wrapper — a crash in the composer leaves users unable to send messages. | `src/app/features/room/RoomInput.tsx` | FALSE POSITIVE — `RoomView.tsx` (lines 151–171) already wraps `` in an `ErrorBoundary` with a "Message composer encountered an error" `RoomInputPlaceholder` fallback. No change needed. | -| Fallback Logic | No explicit empty/error fallback for Matrix SDK data calls in `RoomTimeline`; relies purely on SDK internal error propagation, meaning silent failures show a blank timeline. | `src/app/features/room/RoomTimeline.tsx` | ADDRESSED — the `RoomView` `ErrorBoundary` (above) provides the explicit render-error fallback; a thrown SDK/render error now surfaces "Timeline unavailable" rather than a blank timeline. | -| Dependency | Potential for complex dependency chains due to deep nesting in `src/app/features/` and `src/app/hooks/`. | `src/app/` | OPEN | -| Hydration/Race Condition | The SyncState listener registered by useSyncState may miss the initial 'PREPARED' event if the client initializes synchronously from IndexedDB before the effect runs, leading to an infinite loading state. | `cinny/src/app/pages/client/ClientRoot.tsx` | OPEN | -| Structure | High number of small, highly coupled utility hooks (`src/app/hooks/`) may obscure dependency graphs. | `src/app/hooks/` | OPEN | -| Dead Code | Potential for unused CSS modules or UI components in `src/app/features/`. | `src/app/` | OPEN | -| Security | Sensitive session data (access tokens, device ID) stored in `localStorage` is vulnerable to XSS. | `src/app/state/sessions.ts` | OPEN | -| Privacy | Sensitive user status messages and expiry timestamps are persisted in `localStorage`. | `src/app/features/settings/account/Profile.tsx` | OPEN | -| Privacy | Unsent composer drafts stored in `localStorage` without encryption could leak info on shared devices. | `src/app/features/room/RoomInput.tsx` | OPEN | -| Persistence | Scheduled messages relying on fragile `localStorage` parsing are prone to data loss on session expiry or error. | `src/app/state/scheduledMessages.ts` | OPEN | -| Bundle Bloat | Inefficient `lodash` import; risks including entire library instead of necessary utilities. | `cinny/package.json` | RESOLVED — lodash is not imported in src at all; the unused direct dependency was removed. | -| Bundle Bloat | Large `matrix-js-sdk` (RC version) dependency; high potential for tree-shaking overhead. | `cinny/package.json` | OPEN | -| Build-Time Overhead | `lotusDenoise` plugin performs heavy, sequential `fs` operations during `closeBundle`, significantly slowing build times. | `cinny/vite.config.js` | OPEN | -| Build-Time Overhead | Complex manual `viteStaticCopy` configuration requiring multiple renames and path manipulations; risks redundant processing. | `cinny/vite.config.js` | OPEN | -| Architectural Debt | Redundant style variant logic in `SpacingVariant` could be simplified. | `cinny/src/app/components/message/layout/layout.css.ts` | OPEN | -| Overhead Analysis | Potential CSS bloat from `DropTarget` composition across multiple recipes (`SidebarItem`, `SidebarFolder`). | `cinny/src/app/components/sidebar/Sidebar.css.ts` | OPEN | - -## 🏗️ Git Workflow & History Audit - -| Category | Issue Description | File Path | Status | -| :------- | :------------------------------------------------------------------------------------------------------ | :---------- | :----- | -| Workflow | Monolithic "Fix all bugs" commits (e.g., `10f6544e`, `aa48c9ef`) make `git bisect` difficult. | Git History | OPEN | -| Workflow | Inconsistent commit message prefixes (e.g., `fix`, `feat`, `docs`, `assets`). | Git History | OPEN | -| Workflow | Use of `fix` or `feat` for large-scale changes affecting multiple disparate systems (e.g., `938ead79`). | Git History | OPEN | - ---- - -## 🎨 Native UI/UX Consistency — Lotus vs. Cinny Baseline - -> Audit of every Lotus-custom UI feature against Cinny's native folds design-system conventions. "Native pattern" means the `folds` component library, vanilla-extract tokens (`color.*`, `config.radii.*`, `config.space.*`), and established Cinny component patterns. 52 findings, organized by severity. - ---- - -### 🔴 Major — Broken Styling / Functional Regressions - -#### N1. `ProfileDecoration` Save Button — Undefined `--accent-cyan` Variable (border invisible on all non-TDS themes) - -- **File:** `src/app/features/settings/account/ProfileDecoration.tsx`, lines 191–213 -- **Status:** **FIXED** — replaced raw `