mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-27 20:16:53 +02:00
Polish Control UI quick settings layout
Polish the Control UI quick settings dashboard layout. - Rework quick settings into a 12-column desktop grid with matched top-row card heights. - Pair Personal with a right-side Appearance/Automations stack on large screens while preserving tablet/mobile ordering. - Add render/style guards plus an Unreleased changelog entry crediting @BunsDev. Validated with focused UI tests, formatting, git diff checks, local changed gate, and full PR CI.
This commit is contained in:
@@ -6,6 +6,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Changes
|
||||
|
||||
- Control UI: polish the quick settings dashboard grid so common cards align across desktop, tablet, and mobile layouts without wasting horizontal space. Thanks @BunsDev.
|
||||
- Matrix/E2EE: add `openclaw matrix encryption setup` to enable Matrix encryption, bootstrap recovery, and print verification status from one setup flow. Thanks @gumadeiras.
|
||||
- Agents/compaction: add an opt-in `agents.defaults.compaction.maxActiveTranscriptBytes` preflight trigger that runs normal local compaction when the active JSONL grows too large, requiring transcript rotation so successful compaction moves future turns onto a smaller successor file instead of raw byte-splitting history. Thanks @vincentkoc.
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
.qs-container {
|
||||
width: 100%;
|
||||
max-width: none;
|
||||
margin: 0;
|
||||
padding: 32px 0 56px;
|
||||
max-width: 1520px;
|
||||
margin: 0 auto;
|
||||
padding: 32px 16px 56px;
|
||||
}
|
||||
|
||||
.qs-header {
|
||||
@@ -44,14 +44,16 @@
|
||||
|
||||
.qs-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
align-items: start;
|
||||
grid-template-columns: repeat(12, minmax(0, 1fr));
|
||||
align-items: stretch;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.qs-stack {
|
||||
.qs-side-stack {
|
||||
display: grid;
|
||||
align-content: start;
|
||||
grid-column: span 4;
|
||||
grid-template-rows: auto 1fr;
|
||||
align-self: stretch;
|
||||
gap: 14px;
|
||||
min-width: 0;
|
||||
}
|
||||
@@ -78,8 +80,14 @@
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.qs-card--model,
|
||||
.qs-card--channels,
|
||||
.qs-card--security {
|
||||
grid-column: span 4;
|
||||
}
|
||||
|
||||
.qs-card--personal {
|
||||
grid-column: 1 / -1;
|
||||
grid-column: span 8;
|
||||
}
|
||||
|
||||
.qs-card--personal .qs-identity-grid {
|
||||
@@ -144,7 +152,7 @@
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 9px 16px;
|
||||
min-height: 38px;
|
||||
min-height: 42px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
@@ -156,6 +164,8 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
flex: 1 1 auto;
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 450;
|
||||
color: var(--text);
|
||||
@@ -165,9 +175,12 @@
|
||||
.qs-row__value {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
font-size: 0.8125rem;
|
||||
color: var(--muted);
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.qs-row__value--action {
|
||||
@@ -226,8 +239,8 @@
|
||||
.qs-identity-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(min(220px, 100%), 1fr));
|
||||
gap: 10px;
|
||||
padding: 14px 16px 10px;
|
||||
gap: 12px;
|
||||
padding: 14px 16px 16px;
|
||||
}
|
||||
|
||||
.qs-identity-card {
|
||||
@@ -240,23 +253,13 @@
|
||||
padding: 12px;
|
||||
border: 1px solid color-mix(in srgb, var(--border) 60%, transparent);
|
||||
border-radius: var(--radius-md);
|
||||
background:
|
||||
radial-gradient(
|
||||
circle at 18% 18%,
|
||||
color-mix(in srgb, var(--accent) 10%, transparent),
|
||||
transparent 46%
|
||||
),
|
||||
color-mix(in srgb, var(--bg-elevated) 42%, var(--card) 58%);
|
||||
background: color-mix(in srgb, var(--bg-elevated) 42%, var(--card) 58%);
|
||||
box-shadow: inset 3px 0 0 color-mix(in srgb, var(--accent) 42%, transparent);
|
||||
}
|
||||
|
||||
.qs-identity-card--assistant {
|
||||
background:
|
||||
radial-gradient(
|
||||
circle at 82% 12%,
|
||||
color-mix(in srgb, var(--accent) 14%, transparent),
|
||||
transparent 48%
|
||||
),
|
||||
color-mix(in srgb, var(--bg-elevated) 52%, var(--card) 48%);
|
||||
background: color-mix(in srgb, var(--bg-elevated) 50%, var(--card) 50%);
|
||||
box-shadow: inset 3px 0 0 color-mix(in srgb, var(--border-strong) 70%, transparent);
|
||||
}
|
||||
|
||||
.qs-identity-card__copy {
|
||||
@@ -414,7 +417,10 @@
|
||||
|
||||
.qs-segmented {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
gap: 2px;
|
||||
max-width: 100%;
|
||||
background: color-mix(in srgb, var(--bg) 80%, var(--bg-elevated) 20%);
|
||||
border: 1px solid color-mix(in srgb, var(--border) 50%, transparent);
|
||||
border-radius: var(--radius-md);
|
||||
@@ -1071,6 +1077,56 @@
|
||||
@media (max-width: 1100px) {
|
||||
.qs-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.qs-side-stack {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
.qs-card,
|
||||
.qs-card--span-all,
|
||||
.qs-card--personal,
|
||||
.qs-card--model,
|
||||
.qs-card--channels,
|
||||
.qs-card--security,
|
||||
.qs-card--appearance,
|
||||
.qs-card--automations {
|
||||
grid-column: span 1;
|
||||
}
|
||||
|
||||
.qs-card--personal,
|
||||
.qs-card--span-all {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.qs-card--model {
|
||||
order: 1;
|
||||
}
|
||||
|
||||
.qs-card--channels {
|
||||
order: 2;
|
||||
}
|
||||
|
||||
.qs-card--security {
|
||||
order: 3;
|
||||
}
|
||||
|
||||
.qs-card--appearance {
|
||||
order: 4;
|
||||
}
|
||||
|
||||
.qs-card--personal {
|
||||
order: 5;
|
||||
}
|
||||
|
||||
.qs-card--automations {
|
||||
grid-column: 1 / -1;
|
||||
order: 6;
|
||||
}
|
||||
|
||||
.qs-card--span-all {
|
||||
order: 7;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,12 +16,23 @@ describe("config-quick styles", () => {
|
||||
expect(css).toContain(".qs-card--personal");
|
||||
});
|
||||
|
||||
it("includes the stacked quick-settings density layout", () => {
|
||||
expect(css).toContain(".qs-stack");
|
||||
it("includes the dashboard quick-settings density layout", () => {
|
||||
expect(css).toContain(".qs-card--model");
|
||||
expect(css).toContain(".qs-card--automations");
|
||||
expect(css).toContain(".qs-side-stack");
|
||||
expect(css).toContain("grid-template-rows: auto 1fr;");
|
||||
expect(css).toContain(".qs-identity-card__actions");
|
||||
expect(css).toContain("grid-template-columns: repeat(3, minmax(0, 1fr));");
|
||||
expect(css).toContain("grid-template-columns: repeat(12, minmax(0, 1fr));");
|
||||
expect(css).toContain("grid-column: 1 / -1;");
|
||||
expect(css).toContain("grid-column: span 4;");
|
||||
expect(css).toContain("grid-template-columns: repeat(2, minmax(0, 1fr));");
|
||||
expect(css).toContain("@media (max-width: 760px)");
|
||||
expect(css).toContain("align-items: stretch;");
|
||||
expect(css).toContain("display: contents;");
|
||||
expect(css).toContain(".qs-card--appearance {\n order: 4;");
|
||||
expect(css).toContain(".qs-card--appearance");
|
||||
expect(css).toContain("order: 4");
|
||||
expect(css).toContain(".qs-card--automations");
|
||||
expect(css).toContain("order: 6");
|
||||
});
|
||||
|
||||
it("includes explicit context profile layout hooks", () => {
|
||||
|
||||
@@ -62,12 +62,18 @@ function createProps(overrides: Partial<QuickSettingsProps> = {}): QuickSettings
|
||||
}
|
||||
|
||||
describe("renderQuickSettings", () => {
|
||||
it("uses stacked columns for the compact settings layout", () => {
|
||||
it("uses direct dashboard cards for the compact settings layout", () => {
|
||||
const container = document.createElement("div");
|
||||
|
||||
render(renderQuickSettings(createProps()), container);
|
||||
|
||||
expect(container.querySelectorAll(".qs-stack")).toHaveLength(2);
|
||||
expect(container.querySelector(".qs-card--model")).not.toBeNull();
|
||||
expect(container.querySelector(".qs-card--channels")).not.toBeNull();
|
||||
expect(container.querySelector(".qs-card--security")).not.toBeNull();
|
||||
expect(container.querySelector(".qs-card--appearance")).not.toBeNull();
|
||||
expect(container.querySelector(".qs-card--automations")).not.toBeNull();
|
||||
expect(container.querySelector(".qs-side-stack .qs-card--appearance")).not.toBeNull();
|
||||
expect(container.querySelector(".qs-side-stack .qs-card--automations")).not.toBeNull();
|
||||
expect(container.querySelector(".qs-card--personal")).not.toBeNull();
|
||||
expect(container.querySelectorAll(".qs-card--span-all")).toHaveLength(1);
|
||||
});
|
||||
|
||||
@@ -376,7 +376,7 @@ function renderCardHeader(icon: TemplateResult, title: string, action?: Template
|
||||
|
||||
function renderModelCard(props: QuickSettingsProps) {
|
||||
return html`
|
||||
<div class="qs-card">
|
||||
<div class="qs-card qs-card--model">
|
||||
${renderCardHeader(icons.brain, "Model & Thinking")}
|
||||
<div class="qs-card__body">
|
||||
<div class="qs-row">
|
||||
@@ -426,7 +426,7 @@ function renderChannelsCard(props: QuickSettingsProps) {
|
||||
: undefined;
|
||||
|
||||
return html`
|
||||
<div class="qs-card">
|
||||
<div class="qs-card qs-card--channels">
|
||||
${renderCardHeader(icons.send, "Channels", badge)}
|
||||
<div class="qs-card__body">
|
||||
${props.channels.length === 0
|
||||
@@ -460,7 +460,7 @@ function renderAutomationsCard(props: QuickSettingsProps) {
|
||||
const { cronJobCount, skillCount, mcpServerCount } = props.automation;
|
||||
|
||||
return html`
|
||||
<div class="qs-card">
|
||||
<div class="qs-card qs-card--automations">
|
||||
${renderCardHeader(icons.zap, "Automations")}
|
||||
<div class="qs-card__body">
|
||||
<div class="qs-row">
|
||||
@@ -490,7 +490,7 @@ function renderSecurityCard(props: QuickSettingsProps) {
|
||||
const { gatewayAuth, execPolicy, deviceAuth } = props.security;
|
||||
|
||||
return html`
|
||||
<div class="qs-card">
|
||||
<div class="qs-card qs-card--security">
|
||||
${renderCardHeader(
|
||||
icons.eye,
|
||||
"Security",
|
||||
@@ -525,7 +525,7 @@ function renderSecurityCard(props: QuickSettingsProps) {
|
||||
function renderAppearanceCard(props: QuickSettingsProps) {
|
||||
const themeOptions: ThemeOption[] = [...BUILTIN_THEME_OPTIONS, { id: "custom", label: "Custom" }];
|
||||
return html`
|
||||
<div class="qs-card">
|
||||
<div class="qs-card qs-card--appearance">
|
||||
${renderCardHeader(icons.spark, "Appearance")}
|
||||
<div class="qs-card__body">
|
||||
<div class="qs-row">
|
||||
@@ -976,10 +976,6 @@ function renderConnectionFooter(props: QuickSettingsProps) {
|
||||
`;
|
||||
}
|
||||
|
||||
function renderStack(...cards: TemplateResult[]) {
|
||||
return html`<div class="qs-stack">${cards}</div>`;
|
||||
}
|
||||
|
||||
// ── Main render ──
|
||||
|
||||
export function renderQuickSettings(props: QuickSettingsProps) {
|
||||
@@ -993,9 +989,11 @@ export function renderQuickSettings(props: QuickSettingsProps) {
|
||||
</div>
|
||||
|
||||
<div class="qs-grid">
|
||||
${renderStack(renderModelCard(props), renderSecurityCard(props))}
|
||||
${renderChannelsCard(props)} ${renderPersonalCard(props)}
|
||||
${renderStack(renderAppearanceCard(props), renderAutomationsCard(props))}
|
||||
${renderModelCard(props)} ${renderChannelsCard(props)} ${renderSecurityCard(props)}
|
||||
${renderPersonalCard(props)}
|
||||
<div class="qs-side-stack">
|
||||
${renderAppearanceCard(props)} ${renderAutomationsCard(props)}
|
||||
</div>
|
||||
${renderPresetsCard(props)}
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user