Compare commits

...
Author SHA1 Message Date
Lotus CIandClaude Opus 5.5 ed494d2ae5 fix(room): one width for every right-hand panel (#263)
CI / Build & Quality Checks (pull_request) Successful in 1m43s
CI / Trigger Desktop Build (pull_request) Skipped
CI / Docker image build & smoke test (pull_request) Skipped
CI / Secret scan (gitleaks) (pull_request) Successful in 8s
CI / Playwright smoke (e2e) (pull_request) Successful in 13m12s
Members and bookmarks were 266px, the gallery 320, the threads list 340,
a thread and widgets 360. Swapping panels resized the room view, so the
header icons slid sideways (74px for members -> threads) and a second
click on the same spot opened Widgets instead of closing the panel. All
six now use SIDE_PANEL_WIDTH (340px, styles/sidePanel.ts).

Fixes #263

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5
2026-10-19 14:14:26 -04:00
jared 0eccf9bea9 Merge pull request 'feat(lotus): load avatar decorations from chat.lotusguild.org (matrix #22)' (#425) from feat/decorations-host into lotus
CI / Build & Quality Checks (push) Successful in 1m54s
CI / Docker image build & smoke test (push) Skipped
CI / Secret scan (gitleaks) (push) Successful in 11s
CI / Trigger Desktop Build (push) Successful in 2s
CI / Playwright smoke (e2e) (push) Successful in 12m13s
2026-10-19 13:43:54 -04:00
Lotus CIandClaude Opus 5.5 62c9ac8436 feat(lotus): load avatar decorations from chat.lotusguild.org (matrix #22)
CI / Build & Quality Checks (pull_request) Successful in 1m43s
CI / Trigger Desktop Build (pull_request) Skipped
CI / Docker image build & smoke test (pull_request) Skipped
CI / Secret scan (gitleaks) (pull_request) Successful in 9s
CI / Playwright smoke (e2e) (pull_request) Successful in 13m27s
DECORATION_CDN moves from Nextcloud's public DAV (a PHP session and four
cookies per image, no cache headers) to https://chat.lotusguild.org/decorations,
static files from the versioned avatar-decorations package with a one-week
cache. Pin @lotusguild/element-call-embedded 0.25.0-lotus.26, whose call
tiles accept the new origin.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5
2026-10-19 12:54:23 -04:00
jared aaf75fe74a Merge pull request 'fix(calls): the call bar shows "Call disconnected" and reconnects when sync is back (#318)' (#418) from fix/call-disconnected-bar into lotus
CI / Build & Quality Checks (push) Successful in 1m46s
CI / Docker image build & smoke test (push) Skipped
CI / Secret scan (gitleaks) (push) Successful in 9s
CI / Trigger Desktop Build (push) Successful in 6s
CI / Playwright smoke (e2e) (push) Successful in 11m54s
2026-10-19 12:42:07 -04:00
11 changed files with 40 additions and 24 deletions
+1 -1
View File
@@ -233,7 +233,7 @@ All decoration files are 256×256 APNGs. They animate natively in all modern bro
Decoration preference is stored in the public Matrix profile field `io.lotus.avatar_decoration` (a slug string, e.g. `lotus_flower`). Any Lotus Chat user viewing your profile sees your current decoration. Decoration preference is stored in the public Matrix profile field `io.lotus.avatar_decoration` (a slug string, e.g. `lotus_flower`). Any Lotus Chat user viewing your profile sees your current decoration.
**CDN:** **CDN:**
Files are self-hosted on the Lotus Nextcloud instance. Direct access: `https://drive.lotusguild.org/public.php/dav/files/{token}/cinny-decorations/{slug}.png`. `<img>` elements load cross-origin freely — no CORS headers needed. Static files at `https://chat.lotusguild.org/decorations/{slug}.png` (thumbnails: `thumbs/{slug}.webp`), cached for a week. They come from the versioned `avatar-decorations` package in Gitea's package registry, installed by the matrix repo's LXC 106 deploy (see its README, "Avatar decorations"). To add or replace decorations, publish a new package version there, then update the catalog in `avatarDecorations.ts`. They used to be served from Nextcloud (matrix #22). `<img>` elements load cross-origin freely, so no CORS headers are needed.
**Module-level cache with in-flight deduplication:** **Module-level cache with in-flight deduplication:**
`useAvatarDecoration(userId)` fetches the profile field once per user per session. A `Map<userId, slug|null>` cache prevents redundant requests; a second `pending` waiters map ensures multiple components requesting the same userId simultaneously share one HTTP request rather than firing duplicates. `useAvatarDecoration(userId)` fetches the profile field once per user per session. A `Map<userId, slug|null>` cache prevents redundant requests; a second `pending` waiters map ensures multiple components requesting the same userId simultaneously share one HTTP request rather than firing duplicates.
+4 -4
View File
@@ -81,7 +81,7 @@
}, },
"devDependencies": { "devDependencies": {
"@axe-core/playwright": "4.13.0", "@axe-core/playwright": "4.13.0",
"@lotusguild/element-call-embedded": "0.25.0-lotus.25", "@lotusguild/element-call-embedded": "0.25.0-lotus.26",
"@playwright/test": "1.63.0", "@playwright/test": "1.63.0",
"@rollup/plugin-inject": "5.0.5", "@rollup/plugin-inject": "5.0.5",
"@rollup/plugin-wasm": "6.2.2", "@rollup/plugin-wasm": "6.2.2",
@@ -2695,9 +2695,9 @@
"integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==" "integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA=="
}, },
"node_modules/@lotusguild/element-call-embedded": { "node_modules/@lotusguild/element-call-embedded": {
"version": "0.25.0-lotus.25", "version": "0.25.0-lotus.26",
"resolved": "https://code.lotusguild.org/api/packages/LotusGuild/npm/%40lotusguild%2Felement-call-embedded/-/0.25.0-lotus.25/element-call-embedded-0.25.0-lotus.25.tgz", "resolved": "https://code.lotusguild.org/api/packages/LotusGuild/npm/%40lotusguild%2Felement-call-embedded/-/0.25.0-lotus.26/element-call-embedded-0.25.0-lotus.26.tgz",
"integrity": "sha512-Vvw6B+QoG0/qFJcBzhlAbE+vnk83DikpeeF2xbygpoHfmG9r1H/mS47V91jAMH2vmNbGiALwFlLbJjEET43A2g==", "integrity": "sha512-u0gFV/ne61MHVkl2JFY6TjC0C/vY2oYTz6dFaDtAmdLUBH6gU+6ar0QOr5xs9bhr4+HT8CSBibU8mLTR+ByjKA==",
"dev": true "dev": true
}, },
"node_modules/@matrix-org/matrix-sdk-crypto-wasm": { "node_modules/@matrix-org/matrix-sdk-crypto-wasm": {
+1 -1
View File
@@ -108,7 +108,7 @@
}, },
"devDependencies": { "devDependencies": {
"@axe-core/playwright": "4.13.0", "@axe-core/playwright": "4.13.0",
"@lotusguild/element-call-embedded": "0.25.0-lotus.25", "@lotusguild/element-call-embedded": "0.25.0-lotus.26",
"@playwright/test": "1.63.0", "@playwright/test": "1.63.0",
"@rollup/plugin-inject": "5.0.5", "@rollup/plugin-inject": "5.0.5",
"@rollup/plugin-wasm": "6.2.2", "@rollup/plugin-wasm": "6.2.2",
@@ -1,10 +1,11 @@
import { style } from '@vanilla-extract/css'; import { style } from '@vanilla-extract/css';
import { config, toRem } from 'folds'; import { config } from 'folds';
import { SIDE_PANEL_WIDTH } from '../../styles/sidePanel';
// Mirrors MembersDrawer: a 266px side panel on desktop that becomes a // Mirrors MembersDrawer: a side panel (shared width) on desktop that becomes a
// full-screen fixed panel on narrow viewports — the app's canonical drawer. // full-screen fixed panel on narrow viewports — the app's canonical drawer.
export const BookmarksPanel = style({ export const BookmarksPanel = style({
width: toRem(266), width: SIDE_PANEL_WIDTH,
'@media': { '@media': {
'(max-width: 750px)': { '(max-width: 750px)': {
position: 'fixed', position: 'fixed',
+4 -2
View File
@@ -3,8 +3,10 @@
// of this file at runtime (by regex) instead of re-declaring it, so the two can // of this file at runtime (by regex) instead of re-declaring it, so the two can
// never drift. If you migrate the CDN, change it here ONLY — keep the // never drift. If you migrate the CDN, change it here ONLY — keep the
// `export const DECORATION_CDN = '...'` shape so the sync script can still parse it. // `export const DECORATION_CDN = '...'` shape so the sync script can still parse it.
export const DECORATION_CDN = // matrix #22: static files on chat.lotusguild.org (a versioned package installed
'https://drive.lotusguild.org/public.php/dav/files/bHswJ9pNKp2t26N/cinny-decorations'; // by the matrix repo's LXC 106 deploy), not Nextcloud's public DAV. Absolute, so
// the desktop app and the Element Call fork's origin allowlist both work.
export const DECORATION_CDN = 'https://chat.lotusguild.org/decorations';
// Runtime base. A deployment can repoint the decorations at a different host // Runtime base. A deployment can repoint the decorations at a different host
// without editing the catalog literal above (which scripts/syncDecorations.mjs // without editing the catalog literal above (which scripts/syncDecorations.mjs
+5 -5
View File
@@ -1,13 +1,13 @@
import { style } from '@vanilla-extract/css'; import { style } from '@vanilla-extract/css';
import { color, config, toRem } from 'folds'; import { color, config, toRem } from 'folds';
import { SIDE_PANEL_WIDTH } from '../../styles/sidePanel';
// Right-side drawer DOCKED into the room layout row (a flex sibling of the // Right-side drawer DOCKED into the room layout row (a flex sibling of the
// timeline), exactly like MembersDrawer — not a floating overlay. 320px is a // timeline), exactly like MembersDrawer — not a floating overlay. Same width as
// little wider than the 266px member/bookmark drawers because it hosts a media // every other right-hand panel (styles/sidePanel.ts, #263). On narrow viewports
// grid. On narrow viewports it becomes a full-screen fixed panel, matching the // it becomes a full-screen fixed panel, matching the app's other drawers.
// app's other drawers.
export const MediaGalleryDrawer = style({ export const MediaGalleryDrawer = style({
width: toRem(320), width: SIDE_PANEL_WIDTH,
overflow: 'hidden', overflow: 'hidden',
'@media': { '@media': {
'(max-width: 750px)': { '(max-width: 750px)': {
+3 -2
View File
@@ -1,8 +1,9 @@
import { keyframes, style } from '@vanilla-extract/css'; import { keyframes, style } from '@vanilla-extract/css';
import { config, toRem } from 'folds'; import { config } from 'folds';
import { SIDE_PANEL_WIDTH } from '../../styles/sidePanel';
export const MembersDrawer = style({ export const MembersDrawer = style({
width: toRem(266), width: SIDE_PANEL_WIDTH,
'@media': { '@media': {
'(max-width: 750px)': { '(max-width: 750px)': {
position: 'fixed', position: 'fixed',
@@ -1,8 +1,9 @@
import { style } from '@vanilla-extract/css'; import { style } from '@vanilla-extract/css';
import { config, toRem } from 'folds'; import { config } from 'folds';
import { SIDE_PANEL_WIDTH } from '../../../styles/sidePanel';
export const ThreadPanel = style({ export const ThreadPanel = style({
width: toRem(360), width: SIDE_PANEL_WIDTH,
'@media': { '@media': {
'(max-width: 750px)': { '(max-width: 750px)': {
position: 'fixed', position: 'fixed',
@@ -1,8 +1,9 @@
import { style } from '@vanilla-extract/css'; import { style } from '@vanilla-extract/css';
import { color, config, toRem } from 'folds'; import { color, config } from 'folds';
import { SIDE_PANEL_WIDTH } from '../../../styles/sidePanel';
export const ThreadsListPanel = style({ export const ThreadsListPanel = style({
width: toRem(340), width: SIDE_PANEL_WIDTH,
'@media': { '@media': {
'(max-width: 750px)': { '(max-width: 750px)': {
position: 'fixed', position: 'fixed',
@@ -1,8 +1,9 @@
import { style } from '@vanilla-extract/css'; import { style } from '@vanilla-extract/css';
import { config, toRem } from 'folds'; import { config } from 'folds';
import { SIDE_PANEL_WIDTH } from '../../../styles/sidePanel';
export const WidgetsPanel = style({ export const WidgetsPanel = style({
width: toRem(360), width: SIDE_PANEL_WIDTH,
'@media': { '@media': {
'(max-width: 750px)': { '(max-width: 750px)': {
position: 'fixed', position: 'fixed',
+9
View File
@@ -0,0 +1,9 @@
import { toRem } from 'folds';
/**
* One width for every right-hand room panel (members, bookmarks, threads, a
* thread, media gallery, widgets). Swapping one panel for another must not
* resize the room view: when the widths differed, the header icons slid
* sideways and a second click on the same spot hit a different button (#263).
*/
export const SIDE_PANEL_WIDTH = toRem(340);