fix: make control ui chat scroll page

This commit is contained in:
Peter Steinberger
2026-01-05 00:15:13 +00:00
parent bcdfe461d4
commit d6933b074a
5 changed files with 77 additions and 7 deletions

View File

@@ -429,6 +429,16 @@
background: rgba(0, 0, 0, 0.2);
}
.chat {
display: flex;
flex-direction: column;
min-height: 0;
}
.shell--chat .chat {
flex: 1;
}
.chat-header {
display: flex;
justify-content: space-between;
@@ -460,8 +470,9 @@
display: flex;
flex-direction: column;
gap: 12px;
max-height: 60vh;
overflow: auto;
flex: 1;
max-height: none;
overflow: visible;
padding: 14px 12px;
min-width: 0;
border-radius: 16px;
@@ -731,6 +742,16 @@
gap: 10px;
}
.shell--chat .chat-compose {
position: sticky;
bottom: 0;
z-index: 5;
margin-top: 0;
padding-top: 12px;
background: linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, var(--panel) 35%);
border-top: 1px solid var(--border);
}
.chat-compose__field {
gap: 4px;
}

View File

@@ -1,4 +1,6 @@
.shell {
--shell-pad: 18px;
--shell-gap: 18px;
min-height: 100vh;
display: grid;
grid-template-columns: minmax(220px, 280px) minmax(0, 1fr);
@@ -6,13 +8,16 @@
grid-template-areas:
"topbar topbar"
"nav content";
gap: 18px;
padding: 18px;
gap: var(--shell-gap);
padding: var(--shell-pad);
animation: dashboard-enter 0.6s ease-out;
}
.topbar {
grid-area: topbar;
position: sticky;
top: var(--shell-pad);
z-index: 20;
display: flex;
justify-content: space-between;
align-items: center;
@@ -51,6 +56,16 @@
.nav {
grid-area: nav;
position: sticky;
top: calc(
var(--shell-pad) + var(--topbar-height, 0px) + var(--shell-gap)
);
align-self: start;
max-height: calc(
100vh - var(--topbar-height, 0px) - var(--shell-gap) -
var(--shell-pad) - var(--shell-pad)
);
overflow: auto;
padding: 16px;
border: 1px solid var(--border);
border-radius: 20px;
@@ -132,6 +147,14 @@
display: flex;
flex-direction: column;
gap: 20px;
min-height: 0;
}
.shell--chat .content {
min-height: calc(
100vh - var(--topbar-height, 0px) - var(--shell-gap) -
var(--shell-pad) - var(--shell-pad)
);
}
.content-header {
@@ -204,16 +227,19 @@
@media (max-width: 1100px) {
.shell {
--shell-pad: 12px;
--shell-gap: 12px;
grid-template-columns: 1fr;
grid-template-rows: auto auto 1fr;
grid-template-areas:
"topbar"
"nav"
"content";
padding: 12px;
}
.nav {
position: static;
max-height: none;
display: flex;
gap: 16px;
overflow-x: auto;
@@ -234,6 +260,7 @@
}
.topbar {
position: static;
flex-direction: column;
align-items: flex-start;
gap: 12px;

View File

@@ -184,9 +184,10 @@ export function renderApp(state: AppViewState) {
const sessionsCount = state.sessionsResult?.count ?? null;
const cronNext = state.cronStatus?.nextWakeAtMs ?? null;
const chatDisabledReason = state.connected ? null : "Disconnected from gateway.";
const isChat = state.tab === "chat";
return html`
<div class="shell">
<div class="shell ${isChat ? "shell--chat" : ""}">
<header class="topbar">
<div class="brand">
<div class="brand-title">Clawdbot Control</div>
@@ -211,7 +212,7 @@ export function renderApp(state: AppViewState) {
`,
)}
</aside>
<main class="content">
<main class="content ${isChat ? "content--chat" : ""}">
<section class="content-header">
<div>
<div class="page-title">${titleForTab(state.tab)}</div>

View File

@@ -348,6 +348,7 @@ export class ClawdbotApp extends LitElement {
private popStateHandler = () => this.onPopState();
private themeMedia: MediaQueryList | null = null;
private themeMediaHandler: ((event: MediaQueryListEvent) => void) | null = null;
private topbarObserver: ResizeObserver | null = null;
createRenderRoot() {
return this;
@@ -365,10 +366,16 @@ export class ClawdbotApp extends LitElement {
this.startNodesPolling();
}
protected firstUpdated() {
this.observeTopbar();
}
disconnectedCallback() {
window.removeEventListener("popstate", this.popStateHandler);
this.stopNodesPolling();
this.detachThemeListener();
this.topbarObserver?.disconnect();
this.topbarObserver = null;
super.disconnectedCallback();
}
@@ -437,6 +444,19 @@ export class ClawdbotApp extends LitElement {
});
}
private observeTopbar() {
if (typeof ResizeObserver === "undefined") return;
const topbar = this.querySelector(".topbar");
if (!topbar) return;
const update = () => {
const { height } = topbar.getBoundingClientRect();
this.style.setProperty("--topbar-height", `${height}px`);
};
update();
this.topbarObserver = new ResizeObserver(() => update());
this.topbarObserver.observe(topbar);
}
private startNodesPolling() {
if (this.nodesPollInterval != null) return;
this.nodesPollInterval = window.setInterval(