atagen

malice

public
Star0
Watch0

Commit 1d319c602324

tarball

atagen <boss@atagen.co>2026-10-24 09:15 UTC

2 files changed+147-129
web: put the room header across the top of the whole layout

The header lived inside the chat column, so the pane toggles and room actions shifted sideways whenever the rail or channels pane folded and vanished entirely with no room open. It now spans every column on its own grid row and shows the space or Home name when no room is selected.
Modified file: web/src/App.svelte+141 additions−125 deletions
@@ -1497,6 +1497,147 @@
14971497 style:--msg-gap={`${$messageSpacing}px`}
14981498 style:--msg-line-height={$lineHeight}
14991499 >
1500+ <header class="chat-head">
1501+ <button
1502+ type="button"
1503+ class="icon-btn only-mobile"
1504+ aria-label="Open room list"
1505+ on:click={() => (sidebarOpen = true)}
1506+ >
1507+ <span aria-hidden="true">☰</span>
1508+ </button>
1509+ <button
1510+ type="button"
1511+ class="icon-btn only-desktop pane-toggle"
1512+ aria-pressed={!$railFolded}
1513+ aria-label={$railFolded ? 'Show spaces' : 'Hide spaces'}
1514+ title={$railFolded ? 'Show spaces' : 'Hide spaces'}
1515+ on:click={() => railFolded.update((v) => !v)}
1516+ >
1517+ <svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true">
1518+ <rect x="3" y="4" width="18" height="16" rx="2" fill="none" stroke="currentColor" stroke-width="2" />
1519+ <circle cx="7" cy="9" r="1.4" fill="currentColor" />
1520+ <circle cx="7" cy="13" r="1.4" fill="currentColor" />
1521+ <circle cx="7" cy="17" r="1.4" fill="currentColor" />
1522+ </svg>
1523+ </button>
1524+ <button
1525+ type="button"
1526+ class="icon-btn only-desktop pane-toggle"
1527+ aria-pressed={!$navFolded}
1528+ aria-label={$navFolded ? 'Show channels' : 'Hide channels'}
1529+ title={$navFolded ? 'Show channels' : 'Hide channels'}
1530+ on:click={() => navFolded.update((v) => !v)}
1531+ >
1532+ <svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true">
1533+ <rect x="3" y="4" width="18" height="16" rx="2" fill="none" stroke="currentColor" stroke-width="2" />
1534+ <path d="M10 4v16" stroke="currentColor" stroke-width="2" />
1535+ </svg>
1536+ </button>
1537+ {#if selectedRoom}
1538+ <div class="head-title">
1539+ <div class="head-room">
1540+ <MxcAvatar mxc={selectedRoom.avatar} label={selectedRoom.name} size={36} shape={selectedRoom.space ? 'rounded' : 'circle'} />
1541+ <div class="head-room-text">
1542+ <h1>{selectedRoom.name}</h1>
1543+ <div class="head-meta">
1544+ {#if selectedRoom.space}
1545+ <span class="pill">space</span>
1546+ {/if}
1547+ {#if selectedRoom.encrypted}
1548+ <span class="pill" title="End-to-end encrypted">🔒 encrypted</span>
1549+ {/if}
1550+ {#if selectedRoom.direct}
1551+ <span class="pill">direct</span>
1552+ {/if}
1553+ <span class="conn" title={`Sync state: ${$connection}`}>
1554+ <span class="dot {connectionClass($connection)}" aria-hidden="true"></span>
1555+ {$connection}
1556+ </span>
1557+ </div>
1558+ </div>
1559+ </div>
1560+ </div>
1561+ {:else}
1562+ <div class="head-title">
1563+ <div class="head-room">
1564+ {#if selectedSpace}
1565+ <MxcAvatar mxc={selectedSpace.avatar} label={selectedSpace.name} size={36} shape="rounded" />
1566+ {/if}
1567+ <div class="head-room-text">
1568+ <h1>{selectedSpace?.name ?? 'Home'}</h1>
1569+ </div>
1570+ </div>
1571+ </div>
1572+ {/if}
1573+ <div class="head-actions">
1574+ {#if selectedRoom}
1575+ {#if peeking === selectedRoom.room_id}
1576+ <button type="button" class="icon-btn" aria-pressed="true" aria-label="Stop peeking" title="Stop peeking" on:click={stopPeeking}>
1577+ <svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true">
1578+ <path d="M2 12s3.5-6 10-6 10 6 10 6-3.5 6-10 6S2 12 2 12z" fill="none" stroke="currentColor" stroke-width="2" />
1579+ <circle cx="12" cy="12" r="3" fill="currentColor" />
1580+ </svg>
1581+ </button>
1582+ {/if}
1583+ {#if selectedRoom.membership !== 'Joined'}
1584+ <button
1585+ type="button"
1586+ class="primary-btn"
1587+ disabled={joinBusy}
1588+ on:click={() => void join(selectedRoom.room_id)}
1589+ >
1590+ {joinBusy ? 'Joining…' : 'Join'}
1591+ </button>
1592+ {/if}
1593+ <button
1594+ type="button"
1595+ class="icon-btn"
1596+ aria-pressed={membersOpen}
1597+ aria-label={membersOpen ? 'Hide members' : 'Show members'}
1598+ title="Members"
1599+ on:click={toggleMembers}
1600+ >
1601+ <svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true">
1602+ <circle cx="12" cy="8" r="4" fill="none" stroke="currentColor" stroke-width="2" />
1603+ <path d="M4 20c0-4 3.6-6 8-6s8 2 8 6" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
1604+ </svg>
1605+ </button>
1606+ <div class="room-menu-wrap" bind:this={roomMenuEl}>
1607+ <button
1608+ type="button"
1609+ class="icon-btn"
1610+ aria-haspopup="menu"
1611+ aria-expanded={roomMenuOpen}
1612+ aria-label="Room options"
1613+ title="Room options"
1614+ on:click={() => (roomMenuOpen = !roomMenuOpen)}
1615+ >
1616+ <span aria-hidden="true">⋯</span>
1617+ </button>
1618+ {#if roomMenuOpen}
1619+ <div class="room-menu" role="menu">
1620+ <button type="button" role="menuitem" on:click={() => roomMenuAction(() => (dialog = 'security'))}>
1621+ Security
1622+ </button>
1623+ {#if selectedRoom.membership === 'Joined'}
1624+ <button
1625+ type="button"
1626+ role="menuitem"
1627+ class="danger"
1628+ disabled={leaveBusy}
1629+ on:click={() => roomMenuAction(() => void leave())}
1630+ >
1631+ {leaveBusy ? 'Leaving…' : 'Leave room'}
1632+ </button>
1633+ {/if}
1634+ </div>
1635+ {/if}
1636+ </div>
1637+ {/if}
1638+ </div>
1639+ </header>
1640+
15001641 <nav class="rail" aria-label="Servers and spaces">
15011642 <button
15021643 type="button"
@@ -1839,131 +1980,6 @@
18391980 </p>
18401981 </div>
18411982 {:else}
1842- <header class="chat-head">
1843- <button
1844- type="button"
1845- class="icon-btn only-mobile"
1846- aria-label="Open room list"
1847- on:click={() => (sidebarOpen = true)}
1848- >
1849- <span aria-hidden="true">☰</span>
1850- </button>
1851- <button
1852- type="button"
1853- class="icon-btn only-desktop pane-toggle"
1854- aria-pressed={!$railFolded}
1855- aria-label={$railFolded ? 'Show spaces' : 'Hide spaces'}
1856- title={$railFolded ? 'Show spaces' : 'Hide spaces'}
1857- on:click={() => railFolded.update((v) => !v)}
1858- >
1859- <svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true">
1860- <rect x="3" y="4" width="18" height="16" rx="2" fill="none" stroke="currentColor" stroke-width="2" />
1861- <circle cx="7" cy="9" r="1.4" fill="currentColor" />
1862- <circle cx="7" cy="13" r="1.4" fill="currentColor" />
1863- <circle cx="7" cy="17" r="1.4" fill="currentColor" />
1864- </svg>
1865- </button>
1866- <button
1867- type="button"
1868- class="icon-btn only-desktop pane-toggle"
1869- aria-pressed={!$navFolded}
1870- aria-label={$navFolded ? 'Show channels' : 'Hide channels'}
1871- title={$navFolded ? 'Show channels' : 'Hide channels'}
1872- on:click={() => navFolded.update((v) => !v)}
1873- >
1874- <svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true">
1875- <rect x="3" y="4" width="18" height="16" rx="2" fill="none" stroke="currentColor" stroke-width="2" />
1876- <path d="M10 4v16" stroke="currentColor" stroke-width="2" />
1877- </svg>
1878- </button>
1879- <div class="head-title">
1880- <div class="head-room">
1881- <MxcAvatar mxc={selectedRoom.avatar} label={selectedRoom.name} size={36} shape={selectedRoom.space ? 'rounded' : 'circle'} />
1882- <div class="head-room-text">
1883- <h1>{selectedRoom.name}</h1>
1884- <div class="head-meta">
1885- {#if selectedRoom.space}
1886- <span class="pill">space</span>
1887- {/if}
1888- {#if selectedRoom.encrypted}
1889- <span class="pill" title="End-to-end encrypted">🔒 encrypted</span>
1890- {/if}
1891- {#if selectedRoom.direct}
1892- <span class="pill">direct</span>
1893- {/if}
1894- <span class="conn" title={`Sync state: ${$connection}`}>
1895- <span class="dot {connectionClass($connection)}" aria-hidden="true"></span>
1896- {$connection}
1897- </span>
1898- </div>
1899- </div>
1900- </div>
1901- </div>
1902- <div class="head-actions">
1903- {#if peeking === selectedRoom.room_id}
1904- <button type="button" class="icon-btn" aria-pressed="true" aria-label="Stop peeking" title="Stop peeking" on:click={stopPeeking}>
1905- <svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true">
1906- <path d="M2 12s3.5-6 10-6 10 6 10 6-3.5 6-10 6S2 12 2 12z" fill="none" stroke="currentColor" stroke-width="2" />
1907- <circle cx="12" cy="12" r="3" fill="currentColor" />
1908- </svg>
1909- </button>
1910- {/if}
1911- {#if selectedRoom.membership !== 'Joined'}
1912- <button
1913- type="button"
1914- class="primary-btn"
1915- disabled={joinBusy}
1916- on:click={() => void join(selectedRoom.room_id)}
1917- >
1918- {joinBusy ? 'Joining…' : 'Join'}
1919- </button>
1920- {/if}
1921- <button
1922- type="button"
1923- class="icon-btn"
1924- aria-pressed={membersOpen}
1925- aria-label={membersOpen ? 'Hide members' : 'Show members'}
1926- title="Members"
1927- on:click={toggleMembers}
1928- >
1929- <svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true">
1930- <circle cx="12" cy="8" r="4" fill="none" stroke="currentColor" stroke-width="2" />
1931- <path d="M4 20c0-4 3.6-6 8-6s8 2 8 6" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
1932- </svg>
1933- </button>
1934- <div class="room-menu-wrap" bind:this={roomMenuEl}>
1935- <button
1936- type="button"
1937- class="icon-btn"
1938- aria-haspopup="menu"
1939- aria-expanded={roomMenuOpen}
1940- aria-label="Room options"
1941- title="Room options"
1942- on:click={() => (roomMenuOpen = !roomMenuOpen)}
1943- >
1944- <span aria-hidden="true">⋯</span>
1945- </button>
1946- {#if roomMenuOpen}
1947- <div class="room-menu" role="menu">
1948- <button type="button" role="menuitem" on:click={() => roomMenuAction(() => (dialog = 'security'))}>
1949- Security
1950- </button>
1951- {#if selectedRoom.membership === 'Joined'}
1952- <button
1953- type="button"
1954- role="menuitem"
1955- class="danger"
1956- disabled={leaveBusy}
1957- on:click={() => roomMenuAction(() => void leave())}
1958- >
1959- {leaveBusy ? 'Leaving…' : 'Leave room'}
1960- </button>
1961- {/if}
1962- </div>
1963- {/if}
1964- </div>
1965- </div>
1966- </header>
19671983
19681984
19691985 {#if recoveryBanner}
Modified file: web/src/app.css+6 additions−4 deletions
@@ -139,6 +139,7 @@ input, textarea { font: inherit; }
139139 --pane-ease: 0.22s cubic-bezier(0.2, 0.8, 0.2, 1);
140140 display: grid;
141141 grid-template-columns: var(--rail-w) var(--side-w) minmax(0, 1fr) var(--members-w);
142+ grid-template-rows: auto minmax(0, 1fr);
142143 height: 100dvh;
143144 overflow: hidden;
144145 transition: grid-template-columns var(--pane-ease);
@@ -146,10 +147,11 @@ input, textarea { font: inherit; }
146147 .app.rail-folded { --rail-w: 0px; }
147148 .app.nav-folded { --side-w: 0px; }
148149 .app.members-open { --members-w: 260px; }
149-.app > .rail { grid-column: 1; }
150-.app > .sidebar { grid-column: 2; }
151-.app > .chat { grid-column: 3; }
152-.app > .members { grid-column: 4; }
150+.app > .chat-head { grid-column: 1 / -1; grid-row: 1; }
151+.app > .rail { grid-column: 1; grid-row: 2; }
152+.app > .sidebar { grid-column: 2; grid-row: 2; }
153+.app > .chat { grid-column: 3; grid-row: 2; }
154+.app > .members { grid-column: 4; grid-row: 2; }
153155 .app > .rail, .app > .sidebar, .app > .members {
154156 min-width: 0;
155157 transition: visibility 0s linear 0s;