Commit Graph

3615 Commits

Author SHA1 Message Date
jared 0b307037e0 docs(todo): P4-6 OIDC client-side built, awaiting live verification
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 16:13:40 -04:00
jared 67bd05fc96 feat(auth): OIDC phase 4/5/6 — token refresh, logout revocation, account link
- initMatrix.ts: import the shared Session type; when a session has a refresh
  token + oidc metadata, wire a LotusOidcTokenRefresher via createClient's
  refreshToken + tokenRefreshFunction (reactive 401 refresh). Rust crypto is
  unaffected (still keyed on userId/deviceId).
- client/oidcTokenRefresher.ts: OidcTokenRefresher subclass that persists rotated
  tokens back to the fallback session.
- client/oidcLogout.ts + logoutClient: best-effort revoke access+refresh tokens at
  the issuer's revocation_endpoint on logout (tolerant of failure).
- settings/account/OidcManageAccount.tsx: MSC2965 "Manage account" deep-link,
  shown only when authMetadata is present (OIDC servers); mirrors OtherDevices.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 16:12:13 -04:00
jared dd6b0bccb3 feat(auth): OIDC phase 3 — authorization-code callback route
- oidc/OidcCallback.tsx: standalone page that exchanges code+state via
  completeAuthorizationCodeGrant (SDK validates state = CSRF), derives
  user_id/device_id from the new access token via whoami(), persists the OIDC
  session (refresh token + expiry + issuer/clientId/redirectUri/idTokenClaims),
  then full-page-reloads at the app root. Minimal UI (no Overlay/portal) so it
  needs no app providers.
- App.tsx: short-circuit — render OidcCallback before the RouterProvider when the
  path is the OIDC callback (redirect_uris can't contain a fragment, so it must
  live outside the hash router). The nginx SPA catch-all already serves index.html
  for it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 16:05:22 -04:00
jared a50d3e7ca7 feat(auth): OIDC phase 2 — login initiation (discover/register/authorize)
- oidc/oidcState.ts (pure, +3 tests): dynamic-registration cache (by issuer +
  redirectUri, corrupt-tolerant) and parseOidcCallbackParams (success/error/invalid).
- oidc/oidcLoginUtil.ts: getOrRegisterClientId (cache + registerOidcClient) and
  startOidcLogin (discoverAndValidateOIDCIssuerWellKnown -> generateOidcAuthorization
  Url -> redirect; invalidates the cache on failure). redirectUri is the
  deterministic getOidcCallbackUrl(), and the SDK returns clientId/issuer on
  callback, so no hand-rolled transient state is needed.
- login/OidcLogin.tsx: native-OIDC button mirroring SSOLogin + TokenLogin async/error.
- login/Login.tsx: issuer-gated — when discovery advertises an issuer, render
  OidcLogin and suppress password/legacy-SSO; non-OIDC servers unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 16:01:35 -04:00
jared d3d2f9a448 feat(auth): OIDC phase 4a — session persistence for refresh/expiry/oidc metadata
setFallbackSession gains an optional `extra` arg (password call sites unchanged)
persisting cinny_refresh_token, cinny_expires_at (absolute), and
cinny_oidc_{issuer,client_id,redirect_uri,id_token_claims}. getFallbackSession
reads them back (expiry as remaining lifetime); removeFallbackSession + re-save
clear stale OIDC keys. Session type gains `oidc?: OidcSessionMeta`. +2 tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 15:55:30 -04:00
jared 98ad5674a8 feat(auth): OIDC phase 0+1 — discovery, flow detection, client config
Toward MSC3861/MSC2965 next-gen-auth login (P4-6), client-only.
- cs-api.ts: type the stable `m.authentication` well-known key + getOidcIssuer()
  (stable preferred over the unstable msc2965 key; {} for non-OIDC servers).
- useParsedLoginFlows.ts: getOidcCompatibilityFlag() (MSC3824 oauth_aware_preferred
  / delegated_oidc_compatibility) as a secondary OIDC hint.
- New pages/auth/oidc/oidcConfig.ts: dynamic-registration client metadata + the
  non-hash callback URL (redirect_uris can't contain a fragment).
- paths.ts: OIDC_CALLBACK_PATH.
- 8 unit tests for the pure helpers.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 15:51:23 -04:00
jared 30d0331174 fix(ui): isMacOS always returned false on Macs + plugin-logic tests (+49)
Coverage work found a 3rd real bug: isMacOS() compared os.name against the
legacy 'Mac OS' string, but ua-parser-js v2 reports 'macOS' — so it was dead,
and Mac users saw "Ctrl + k" instead of "⌘ + k" in the editor toolbar, search,
and settings shortcut hints. Now accepts both 'macOS' and 'Mac OS'.

Suites (via subagent, verified): via-servers (10 — power/popularity server
selection), bad-words (9), syntaxHighlight tokenize (14), plugins/utils
getEmoticonSearchStr (5), imageCompression formatFileSize/isCompressible (5),
user-agent (6, now asserting the fixed behavior).

Full suite now 501 tests, all passing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 14:58:06 -04:00
jared 24662fa994 test: localStorage-backed state modules (+38)
CI / Build & Quality Checks (push) Successful in 11m15s
CI / Trigger Desktop Build (push) Successful in 10s
Via subagent, no bugs:
- state/utils/atomWithLocalStorage (9): get/set helpers + atom write-through.
- state/scheduledMessages (6): Map<->Record round-trip, persistence, mount-gated
  hydration (atomWithStorage w/o getOnInit — modeled with a subscription).
- state/spaceRooms (9): Set dedupe + no-write-when-unchanged + serialization.
- state/navToActivePath (8): per-user Map<->Object serialization.
- state/callPreferences (6): the privacy rule forcing video=false on load+persist.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 14:53:52 -04:00
jared 230ef8ed7c test: markdown parser subsystem (58) + custom-emoji readers (32)
Via subagents, probe-verified against real output, no bugs:
- markdown: internal/utils (11), inline/runner (7), inline/parser (21 — bold/
  italic/underline/strike/code/spoiler/link, nesting, precedence, URL lookbehind),
  block/parser (19 — headings/code-fences/quotes/lists/<br>/escapes). Closes the
  biggest coverage hole (core message rendering).
- custom-emoji: PackMetaReader (6), PackImageReader (7), PackImagesReader (4),
  utils equality+makeImagePacks (5), recent-emoji promote/increment/100-cap (10).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 14:52:48 -04:00
jared 160c09e525 test: add suites for 8 simple state reducers + msgContent (+50)
Via subagent, all verified, no bugs:
- state/toast (7), room-list/roomList (6), inviteList (6), room-list/utils
  compareRoomsEqual (6), backupRestore (6), callEmbed dispose-on-replace (6),
  closedNavCategories factory + makeNavCategoryId (8).
- features/room/msgContent (5): getAudioMsgContent/getFileMsgContent incl.
  encrypted (content.file) vs plain (content.url) branch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 14:43:33 -04:00
jared 589d45e0a0 test: add suites for list, roomToParents, roomToUnread reducers (+43)
Via subagent, verified against real behavior (all use jotai store + enableMapSet):
- state/list (11): createListAtom PUT/DELETE/REPLACE (single + array, identity).
- state/room/roomToParents (10): INITIALIZE/PUT/DELETE incl. cycle-skip and
  orphan-cleanup pruning of zero-parent children.
- state/room/roomToUnread (22): unreadInfoToUnread, unreadEqual, and the
  roomToUnreadAtom reducer — leaf/overwrite/equal-guard, multi-level parent
  roll-up with `from` recording, RESET rebuild, DELETE subtract/prune.

No bugs (noted a latent never-hit string-spread in deleteUnreadInfo's `from ??
roomId` fallback; left as-is). Suite growing toward full pure-logic coverage.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 14:42:36 -04:00
jared acd355bb5a docs(bugs): test suite at 231 tests; 2 real bugs caught by coverage
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 14:35:05 -04:00
jared 6e59395fb8 test: lotus decorations, call caps, crypto, featureCheck, typing, markdown (+34)
Subagent batch (no bugs found) + markdown:
- lotus/avatarDecorations (8): decorationUrl, CDN shape, ALL_DECORATIONS
  flattening, data invariants (unique category ids + slugs, slug charset).
- plugins/call/utils (7): getCallCapabilities — static caps + room/user/device
  scoped state-keys.
- utils/matrix-crypto (3): verifiedDevice via a stubbed CryptoApi.
- utils/featureCheck (3): checkIndexedDBSupport success/error/throw paths.
- state/typingMembers (8): add/dedup-by-latest-ts/per-room-scope/delete reducer
  via a jotai store (enableMapSet, mirroring app startup).
- plugins/markdown/utils (5): inline + block escape/unescape round-trips.

Full suite now 231 tests, all passing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 14:32:53 -04:00
jared 9f4516c6a8 test: add suites for state/sessions, recentSearches, upload (+17)
Via subagent, all verified against real behavior:
- state/sessions (5): fallback-session round-trip across the four cinny_* keys,
  missing-key → undefined for each required key, removeFallbackSession clears all.
- state/recentSearches (6): addRecentSearch prepend, case-sensitive dedupe +
  move-to-front, trim, ignore empty/whitespace, cap at 10.
- state/upload (6): the createUploadAtom reducer driven through a real jotai
  store — idle→loading→progress(gated)→success/error, file ref preserved.

No bugs found.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 14:29:36 -04:00
jared 0bd2273bee test: add suites for utils/room (40) + plugins/matrix-to (7)
- utils/room (40, via subagent): 28 helpers — state-event accessors, m.direct
  parsing, space/room classification, parent/child graph (incl. cycle safety),
  mute-rule + notification logic, unread info, reply trimming, member display/
  avatar/search, reaction/edit/mention extraction, room-icon branches. SDK/
  crypto-heavy helpers skipped. No bugs found.
- plugins/matrix-to (7): matrix.to permalink build + parse for user/room/event
  including via-server round-trips and negative cases.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 14:28:32 -04:00
jared d37fa1584c test: add suite for utils/keyboard handlers (+4)
Covers onTabPress (Tab-only), preventScrollWithArrowKey (arrows-only),
onEnterOrSpace (Enter/Space gate the callback), and stopPropagation's
editable-element check (does not swallow keys when an input/textarea/
contenteditable is focused) via mock events + a document.activeElement stub.
Full suite now 133 tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 13:50:55 -04:00
jared e17cb09269 fix(settings): don't crash on load when localStorage is blocked + tests (+6)
Prevention work found a real bug: getSettings() runs at module load, and its
catch block called localStorage.removeItem() — but we often reach that catch
*because* localStorage access threw (blocked storage / private mode / sandboxed
context). The removeItem then re-threw, producing an uncaught error that crashed
the whole app at startup. Guarded the cleanup in its own try/catch.

New state/settings suite (6) covers the legacy-boolean callNoiseSuppression
migration, denoise-model/ringtone-id coercion of unknown values, default merge,
malformed JSON, and the blocked-storage regression.

Full suite now 129 tests, all passing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 13:46:51 -04:00
jared 4d55e45962 docs(bugs): test suite at 123 tests, now a hard CI gate
CI / Build & Quality Checks (push) Successful in 10m49s
CI / Trigger Desktop Build (push) Successful in 11s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 13:26:58 -04:00
jared e3532064b5 test: add suites for accentColor (color math) + matrix-uia (auth flows) (+15)
- utils/accentColor (8): hexToRgb parsing, lighten/darken channel math, rgba
  clamping, WCAG relativeLuminance (black=0/white=1), contrastingText threshold,
  varNameFromToken, and derivePrimaryPalette's full 10-token output.
- utils/matrix-uia (7): UIA flow helpers — getSupportedUIAFlows,
  completed/params/session/errcode/error accessors, getUIAFlowForStages
  (incl. the single-extra-dummy rule), has/requiredStageInFlows, and
  getLoginTermUrl language fallback.

Full suite now 123 tests, all passing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 13:23:36 -04:00
jared 1e37b20c6a ci: promote unit tests to a hard gate
108 deterministic pure-logic tests now block the build job (and thus deploy) on
failure, alongside the Build step. Moved out of the informational quality-checks
section and dropped continue-on-error.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 13:17:20 -04:00
jared 4f03775e04 docs(bugs): test suite at 108 tests across 11 modules
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 13:07:41 -04:00
jared 9678b02aba test: add suite for utils/sort room-list comparators (+5)
Covers byTsOldToNew, byOrderKey (undefined-last + the no-equality-branch
quirk for two present keys), and the factory comparators
factoryRoomIdByUnreadCount / factoryRoomIdByActivity / factoryRoomIdByAtoZ
(activity-desc, unread-desc, A–Z case-insensitive with leading-# stripped)
using minimal MatrixClient mocks. Full suite now 108 tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 13:04:45 -04:00
jared a926487f5e fix(utils): findAndReplace infinite loop on non-global regex + tests (+28)
Prevention work surfaced a real latent bug: findAndReplace looped forever
(OOM) on any non-global regex with a match — `match` was only reassigned
inside `if (regex.global)`, so a non-global regex never advanced. Fixed by
treating a non-global regex as a single match (`match = regex.global ?
regex.exec(text) : null`) and added a regression test. Latent in practice
(all current callers pass global regexes), but a crash waiting to happen.

New suites (tsx + node:test), verified empirically:
- utils/findAndReplace (10, incl. the regression)
- utils/AsyncSearch (9): normalize + matchQuery (the timer-based class is
  skipped — needs window.performance/setTimeout, unavailable in node)
- utils/ASCIILexicalTable (10): orderKeys gap-filling + invariants

Full suite now 103 tests, all passing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 11:15:39 -04:00
jared ae1d30bc5a test: add suites for time, matrix, mimeTypes, and search filters (+47 tests)
Expands pure-logic coverage (harness: tsx + node:test):
- utils/time (21): date/time formatters — exact values where timezone-independent,
  structure/regex where locale/tz-sensitive (written via subagent).
- utils/matrix (13): pure id/mxc helpers (isUserId/isRoomId/isRoomAlias/
  getMxIdLocalPart/getMxIdServer/isServerName + room-version gates). (subagent)
- utils/mimeTypes (7): getBlobSafeMimeType allowlist+remap, safeFile rewrap,
  mimeTypeToExt, getFileNameExt/WithoutExt edge cases.
- message-search filters (6): filterGroupsByMsgType (union, empty-group drop,
  non-string msgtype) + filterGroupsByPinned (disabled passthrough, pinned-only).

All assertions verified against actual runtime behavior. Suite now 74 tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 10:27:57 -04:00
jared a7d145aa70 docs(bugs): manifest:false verified OK (not a bug)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 09:39:06 -04:00
jared 472d4ba008 test: add XSS-prevention suite for utils/sanitize
8 tests locking in security-critical behavior of sanitizeCustomHtml /
sanitizeText: script-content removal, event-handler stripping, javascript:
link neutralization, anchor hardening (noreferrer/noopener/_blank), non-mxc
<img> → link conversion, and the N100 <pre class> language-* restriction.
Verified against actual sanitize-html behavior. Suite now 27 tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 09:29:08 -04:00
jared 2a0478cad8 docs(bugs): N105 fixed → Needs Verification
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 09:13:34 -04:00
jared cee0c591e2 fix(pwa): N105 — notification clicks work after the tab is closed
OS notifications were shown via page-level `new Notification()` whose onclick
only works while the originating tab is alive — clicking a notification after
closing the tab did nothing.

- New `showOsNotification()` (utils/dom) prefers `registration.showNotification()`
  so the notification is service-worker-owned and persists; falls back to
  `new Notification()` (with the previous onclick) when no SW is available, so
  worst case is unchanged behaviour.
- sw.ts gains a `notificationclick` handler: focuses an existing app window and
  forwards the target path, or opens the app if none is open.
- ClientNonUIFeatures forwards the SW `notificationClick` message to react-router
  `navigate()` (works for both hash and browser router configs), and uses a
  per-room `tag` to coalesce notifications (replacing the old notifRef.close()
  dedup a SW notification can't hold).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 09:09:57 -04:00
jared 68b6ffffd7 docs(bugs): test-suite gap now seeded (harness + 19 tests)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 08:50:50 -04:00
jared 9bc8c4b47f test: add suite for utils/regex (sanitizeForRegex, URL/EMAIL/JUMBO_EMOJI)
Second pure-logic suite — another zero-import module. 4 tests covering regex
metacharacter escaping (with round-trip), the http(s) URL pattern, email
validation, and the jumbo-emoji matcher. Total suite now 19 tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 08:49:54 -04:00
jared e80ebd35cb test: add unit-test harness (tsx + node:test) + first suite for utils/common
Addresses the "no automated test suite" gap. Chose Node's built-in test runner
via tsx rather than vitest: the project is on Vite 8.0.14, ahead of vitest's
supported Vite range, so vitest would fight peer deps. tsx is build-independent.

- `npm test` → `node --import tsx --test $(find src -name '*.test.ts')` (works on
  Node 20 local + 24 CI without relying on --test glob support).
- src/app/utils/common.test.ts: 15 tests covering the pure helpers (bytesToSize,
  time formatters, binarySearch, parseGeoUri, slash trimmers, nameInitials,
  randomStr, suffixRename, splitWithSpace, promise-settled helpers, etc.) —
  asserts actual behavior, traced from source.
- common.ts: folds import made `import type` (it's types only) so the module is
  pure and testable without loading folds/CSS.
- tsconfig excludes *.test.ts (tsx transpiles tests; eslint isn't type-aware so
  it still lints them); added an informational CI "Unit tests" step (promote to a
  hard gate by dropping continue-on-error).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 08:45:22 -04:00
jared 36343baecc call: lint/format cleanup for lotus EC wiring
CI / Build & Quality Checks (push) Successful in 10m27s
CI / Trigger Desktop Build (push) Successful in 25s
Resolve the eslint/prettier failures from the previous commit (non-blocking
in CI, but real): drop the banned `void` operator on fire-and-forget
transport.send().catch() calls, prefix the now-unused _denoiseNativeNS
param, and run prettier on the touched files.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 01:52:45 -04:00
jared 89cf171efc call: consume self-built Element Call fork + activate Lotus features
CI / Build & Quality Checks (push) Successful in 11m5s
CI / Trigger Desktop Build (push) Successful in 25s
Switch to @lotusguild/element-call-embedded@0.20.1-lotus.1 (our self-built
fork) and turn on the source-level features it adds:

- #1 denoise CUTOVER: in-source ML denoise (lotusDenoiseSource=1) replaces
  the build-time getUserMedia shim — removed the shim injection from
  vite.config.js (denoise/ assets still shipped; the processor loads them).
  Survives reconnects (fixes A7).
- #2 call-state: CallEmbed consumes io.lotus.call_state; useCallSpeakers /
  useRemoteAllMuted prefer it over scraping EC's DOM (DOM fallback kept;
  empty payloads ignored).
- #4 focus: CallControl.focusCameraParticipant sends io.lotus.focus_participant
  (works during screenshare), replacing the DOM tile-click hack.
- #5 theming: lotusTransparent=1 (native transparent background).
- #6 decorations: LotusDecorationPusher sends each member's decoration URL
  via io.lotus.decorations -> rendered on in-call tiles.

#3 soundboard / #7 quality ship dormant (EC-ready; no host UI sends them yet).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 01:33:52 -04:00
jared 149ec8e4e4 docs: add Element Call fork handoff + tag all EC-FORK references
CI / Build & Quality Checks (push) Successful in 10m28s
CI / Trigger Desktop Build (push) Successful in 7s
Captures the plan to fork element-hq/element-call and build it from source for
true ownership of the in-call experience (decorations, focus/screenshare,
reconnect mic, native theming, call-audio injection) — none of which are fixable
against the prebuilt @element-hq/element-call-embedded bundle we ship today.

- New HANDOFF_ELEMENT_CALL_FORK.md: self-contained plan for a fresh session
  (current architecture, full file inventory, phases, new-repo decision, the
  denoise-shim interaction, doc corrections).
- Tagged every related note with [EC-FORK] + links: README (For Developers),
  LOTUS_BUGS (EC limitations), LOTUS_TODO (soundboard, denoise, soundboard
  cross-origin correction), LOTUS_FEATURES (call section).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 20:50:10 -04:00
jared d1cd963e4b docs(bugs): record live-test results + EC iframe limitations
Verified passing: A2, B1-B4, C1, C3, D. Re-fixed and awaiting re-test: A1
(ringtone loudness), A3/A4 (caller decline notice), G1 (All-muted badge).
Documented A5/A6/A7 as known Element Call iframe-boundary limitations.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 19:23:13 -04:00
jared 5ef0a1fd3e fix(call): ringtone loudness, caller decline notice, All-Muted badge
Three issues from live testing:
- A1: the 'classic' ringtone (call.ogg, mastered near full scale) was much
  louder than the synthesized styles. Attenuate it (CLASSIC_GAIN 0.45) so all
  ringtones sit at a comparable level.
- A3/A4: the caller had no indication when a DM/group callee declined — their
  UI kept "ringing" until the notification lifetime expired. IncomingCallListener
  now listens for RTCDecline events for a call we're hosting in the room and
  toasts the caller ("<name> declined your call").
- G1: the PiP "All muted" badge fired when any single remote participant muted.
  useRemoteAllMuted now returns true only when there is >=1 remote and every
  remote participant is muted.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 19:13:40 -04:00
jared 6ace96f2cf docs(bugs): native-cinny audit fully closed (nits done)
CI / Build & Quality Checks (push) Successful in 10m32s
CI / Trigger Desktop Build (push) Successful in 20s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 16:15:49 -04:00
jared 2d71f2ce30 refactor(ui): name the global overlay z-index layers (native-cinny nit)
Centralized the global floating-UI stacking values into styles/zIndex.ts
(inCallBanner 9990 < seasonalEffect 9997 < nightLight 9998 < toast 10001;
folds modals sit at 9999 between). Same values, no behavior change — just
removes the magic numbers and documents the layering so future overlays don't
collide. Component-internal small z-index stays local.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 16:10:29 -04:00
jared 2c3dba55e6 fix(ui): use folds Text priority instead of raw opacity (native-cinny nit)
Replaced raw style={{ opacity: N }} de-emphasis on folds <Text> with the
`priority` prop across search, schedule, profile, and tray UI. Left the cases
that aren't Text-priority candidates (an Icon opacity, a Box-row opacity, and a
Text with an explicit color token).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 15:44:57 -04:00
jared c7a04dcc70 fix(ui): poll checkmark uses folds Icon instead of Unicode glyph (native-cinny nit)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 15:20:52 -04:00
jared 4b14c15518 docs(bugs): timezone select + lightbox done; only native-cinny nits remain
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 14:49:26 -04:00
jared c68ef346bf fix(ui): MediaGallery lightbox uses folds Overlay + FocusTrap (native-cinny audit 8/N)
The full-screen media viewer was a raw <div role="dialog"> rendered in place
with manual focus. Wrapped it in folds Overlay (portal + backdrop, proper
stacking) and FocusTrap (focus management), keeping its own arrow/Escape key
handling. The light-on-dark chrome (#fff over the forced-black media stage) is
kept — it's a justified, always-dark media-viewer scrim, not theme chrome.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 14:49:24 -04:00
jared c5d7fcc303 fix(ui): timezone picker uses folds SettingsSelect (native-cinny audit 7/N)
Replaced the last raw native <select> (Profile timezone, colorScheme:'dark')
with SettingsSelect. Added an optional `disabled` prop to SettingsSelect for
the saving state. handleSubmit reads the `timezone` state (not the native form
field) so submission is unaffected; the now-unused handleSelectChange was
removed. No raw <select> elements remain in the settings UI.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 22:51:13 -04:00
jared 9bf56d5748 docs(bugs): track remaining native-cinny polish items
CI / Build & Quality Checks (push) Successful in 10m31s
CI / Trigger Desktop Build (push) Successful in 29s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 22:44:27 -04:00
jared d5ce56930b refactor(ui): extract shared SettingsSelect; replace raw <select> (native-cinny audit 6/N)
Extracted the folds-native dropdown (Button+PopOut+Menu) from General.tsx into a
shared components/settings-select/SettingsSelect.tsx, and used it to replace raw
native <select> elements (which render OS-styled and broke under non-default
themes via colorScheme:'dark'):
- Profile "auto-clear after" select
- PushRuleEditor add-rule mode select (dropped the now-unused handleModeChange)

The form-tied timezone <select> in Profile is left for a follow-up (it's wired
to native form submission + a disabled state and needs more care).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 22:43:18 -04:00
jared 349194e7e5 fix(ui): folds primitives for RouteError + PiP fullscreen button (native-cinny audit 5/N)
CI / Build & Quality Checks (push) Successful in 10m33s
CI / Trigger Desktop Build (push) Successful in 21s
- RouteError: raw <div>/<h2>/<p>/<button> (sans-serif, raw px) -> folds
  Box/Text/Button with config tokens.
- CallEmbedProvider PiP fullscreen control: raw <button> with ⊡/⛶ glyphs ->
  folds IconButton reusing the exported FullscreenIcon/ExitFullscreenIcon SVGs
  from Controls (consistent with the main fullscreen button). The intentional
  dark over-video scrim is kept.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 22:32:19 -04:00
jared 24d6460e4c chore: remove Sentry.io entirely
We no longer use Sentry. Removed:
- @sentry/react + @sentry/vite-plugin (package.json + lockfile)
- Sentry.init in index.tsx and the VITE_SENTRY_DSN env (.env.production)
- @sentry/vite-plugin + the SENTRY_AUTH_TOKEN sourcemap-upload path in
  vite.config.js (sourcemap now always false) and the CI env var
- Sentry.ErrorBoundary in App.tsx -> react-error-boundary's ErrorBoundary with a
  folds-native fallback (Box/Text/Button + config tokens), which also resolves
  the native-cinny audit's raw-#hex/#5865f2 fallback finding.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 22:21:09 -04:00
jared 127e783f66 fix(ui): toast cards render on stock themes; gate TDS glow (native-cinny audit 4/N)
LotusToastContainer was styled entirely with --lt-* CSS vars but rendered
unconditionally (not gated on lotusTerminal). Those vars only exist inside the
Lotus Terminal theme's scoped block with no global fallback, so in-app toast
notifications rendered with undefined background/border/colors on every stock
Cinny theme. Now the card uses folds tokens (color.Surface.*/Primary.*,
config.radii/space/borderWidth, color.Other.Shadow) by default, keeping the TDS
--lt-* glow/accents only when lotusTerminal is active. The raw <button> dismiss
control is now a folds IconButton.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 22:06:33 -04:00
jared 198fd12bb2 fix(ui): folds tokens for ML-denoise panel + screenshare popover (native-cinny audit 3/N)
- General ML noise-suppression panel: ungated --border-color/--bg-card/--bg-input/
  --accent-orange -> color.Surface.ContainerLine/Container, SurfaceVariant.Container,
  Primary.Main. (The lotusTerminal-gated Boot button keeps its TDS --accent-orange.)
- CallControls "Share your screen?" popover: --bg-surface/--bg-surface-border ->
  color.Surface.Container / ContainerLine.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 22:01:17 -04:00
jared 34d5209165 fix(ui): folds tokens for settings/profile/glass invented vars (native-cinny audit 2/N)
- DenoiseTester: --bg-card/--border-color/--accent-green/--accent-orange -> color.Surface.*/Success/Primary
- ProfileDecoration: --accent-cyan/--bg-surface-variant -> color.Primary.Main/SurfaceVariant.Container
- Profile: --tc-critical/warning-normal -> color.Critical/Warning.Main
- UserRoomProfile: --tc-positive/warning-normal/--tc-surface-low-contrast/--bg-surface-variant -> color tokens
- Sidebar glass: hardcoded rgba bg/border -> color-mix on color.Surface.Container + SurfaceVariant.ContainerLine
  (also fixes the glass looking wrong on light themes — was always near-black)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 21:58:57 -04:00