CI / Build & Quality Checks (pull_request) Successful in 4m0s
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 14m50s
POST /rooms/{id}/upgrade copies only Synapse's fixed list of state, so an
upgrade silently dropped everything Lotus keeps in room state: an
audio-only voice channel became camera/screenshare-allowed, user limits
and bitrate caps vanished, the soundboard pack was lost, and a room set
to delete messages after 7 days kept new ones forever.
RoomUpgrade now reads the client-owned state first (utils/roomUpgradeState,
unit-tested: io.lotus.*, m.room.retention, role tags, room emoji packs)
and re-creates it in the replacement room; a partial copy is reported in
the dialog instead of closing silently.
Verified headless through the UI (v10 room with voice limit, quality
policy, a soundboard pack and retention → upgrade): before, none
survived; now all four are in the new room.
Follow-up (not here): for spaces, the children's m.space.parent still
point at the old space.
Fixes #327
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5
36 lines
1.5 KiB
TypeScript
36 lines
1.5 KiB
TypeScript
import { test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { isCarriedStateType, stateToCarry } from './roomUpgradeState';
|
|
|
|
const ev = (type: string, content: Record<string, unknown>, stateKey = '') => ({
|
|
getType: () => type,
|
|
getStateKey: () => stateKey,
|
|
getContent: () => content,
|
|
});
|
|
|
|
test('Lotus room state is carried; state Synapse already copies is not (#327)', () => {
|
|
assert.equal(isCarriedStateType('io.lotus.voice_limit'), true);
|
|
assert.equal(isCarriedStateType('io.lotus.room_quality'), true);
|
|
assert.equal(isCarriedStateType('io.lotus.soundboard'), true);
|
|
assert.equal(isCarriedStateType('m.room.retention'), true);
|
|
assert.equal(isCarriedStateType('in.cinny.room.power_level_tags'), true);
|
|
assert.equal(isCarriedStateType('im.ponies.room_emotes'), true);
|
|
assert.equal(isCarriedStateType('m.room.power_levels'), false);
|
|
assert.equal(isCarriedStateType('m.room.name'), false);
|
|
assert.equal(isCarriedStateType('m.room.member'), false);
|
|
});
|
|
|
|
test('stateToCarry keeps state keys and skips emptied events', () => {
|
|
const out = stateToCarry([
|
|
ev('io.lotus.voice_limit', { max_users: 5 }),
|
|
ev('io.lotus.soundboard', { clips: [1] }, 'pack-a'),
|
|
ev('io.lotus.room_quality', {}),
|
|
ev('m.room.name', { name: 'x' }),
|
|
ev('m.room.retention', { max_lifetime: 604800000 }),
|
|
]);
|
|
assert.deepEqual(
|
|
out.map((s) => `${s.type}|${s.stateKey}`),
|
|
['io.lotus.voice_limit|', 'io.lotus.soundboard|pack-a', 'm.room.retention|'],
|
|
);
|
|
});
|