Add release builds, classic and stacked layouts

- electron-builder packaging: AppImage, deb and tar.gz for Linux, NSIS
  installer for Windows, app icon, desktop entry
- Gitea workflow builds Linux and Windows on version tags and attaches
  the files to the release
- Layout setting in Appearance: Modern, Classic (chat left, channels
  right) and Stacked (channels on top, chat below, resizable split),
  like the desktop Mumble client
- No message box while not connected; Manage identities on the welcome
  page
- Public list: a list that arrives after the source changed no longer
  replaces the new one

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-01 01:31:51 +02:00
co-authored by Claude Opus 5.5
parent b44b8230d4
commit 2c7a94441d
15 changed files with 286 additions and 43 deletions
+37
View File
@@ -85,6 +85,13 @@ try {
await page.getByRole('dialog', { name: 'Set up your identity' }).waitFor({ state: 'detached' });
console.log('ok: identity wizard creates an identity');
// Welcome page: no message box while offline, identities one click away
assert.equal(await page.locator('.chat .composer').count(), 0);
await page.getByRole('button', { name: 'Manage identities' }).click();
await page.getByRole('tab', { name: 'Identities', selected: true }).waitFor();
await page.getByRole('button', { name: 'Done', exact: true }).click();
console.log('ok: welcome page without composer, manage identities');
// Settings: upload host + key
if (process.env.UPLOAD_HOST && process.env.UPLOAD_KEY) {
await page.getByTitle('Settings').click();
@@ -307,6 +314,36 @@ try {
await page.getByRole('button', { name: 'Done', exact: true }).click();
console.log('ok: color schemes');
// Layouts like the desktop client: classic (channels right of chat), stacked (channels above)
const box = (sel: string) => page.locator(sel).first().boundingBox().then(b => b!);
await page.getByTitle('Settings').click();
await page.getByRole('tab', { name: 'Appearance' }).click();
await page.getByRole('radio', { name: 'Classic' }).click();
await page.getByRole('button', { name: 'Done', exact: true }).click();
await page.locator('.app.classic').waitFor();
assert.ok((await box('.sidebar')).x > (await box('.chat')).x, 'classic: channel list right of chat');
await page.getByTitle('Settings').click();
await page.getByRole('tab', { name: 'Appearance' }).click();
await page.getByRole('radio', { name: 'Stacked' }).click();
await page.getByRole('button', { name: 'Done', exact: true }).click();
await page.locator('.app.stacked').waitFor();
const chatBox = await box('.chat'), treeBox = await box('.sidebar');
assert.ok(chatBox.y >= treeBox.y + treeBox.height - 1, 'stacked: chat below channel list');
// The split between chat and channel list drags
const handle = page.getByRole('separator', { name: 'Resize channel list' });
const hb = (await handle.boundingBox())!;
await page.mouse.move(hb.x + hb.width / 2, hb.y + hb.height / 2);
await page.mouse.down();
await page.mouse.move(hb.x + hb.width / 2, hb.y + 60, { steps: 4 });
await page.mouse.up();
assert.ok((await box('.sidebar')).height > treeBox.height + 40, 'stacked: split resizes');
await page.getByTitle('Settings').click();
await page.getByRole('tab', { name: 'Appearance' }).click();
await page.getByRole('radio', { name: 'Modern' }).click();
await page.getByRole('button', { name: 'Done', exact: true }).click();
await page.locator('.app.side').waitFor();
console.log('ok: classic and stacked layouts');
// Public server list, from a local list in the official XML format
const http = await import('node:http');
const listServer = http.createServer((_req, res) => {