Commit 0f949b0d authored by edy's avatar edy

fix(ui): stabilize sidebar and knowledge layouts

parent 8645a0f7
Pipeline #18462 failed
......@@ -285,6 +285,7 @@ export default function App() {
const [expandedCategories, setExpandedCategories] = useState<Record<string, boolean>>({});
const [messageReactions, setMessageReactions] = useState<Record<string, MessageReaction | undefined>>({});
const [sidebarSessionTitles, setSidebarSessionTitles] = useState<Record<string, string>>({});
const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(false);
const [skillMenuOpen, setSkillMenuOpen] = useState(false);
const [copiedToken, setCopiedToken] = useState("");
const skillMenuRef = useRef<HTMLDivElement | null>(null);
......@@ -1553,7 +1554,7 @@ export default function App() {
} satisfies ComponentProps<typeof SettingsPanels>;
return (
<div className={"shell openclaw-theme" + (isConversationView ? " conversation-shell" : "") + (viewMode === "experts" ? " conversation-shell-experts" : "")}>
<div className={"shell openclaw-theme" + (isConversationView ? " conversation-shell" : "") + (viewMode === "experts" ? " conversation-shell-experts" : "") + (isSidebarCollapsed ? " sidebar-collapsed" : "")}>
<TopBar onMinimize={minimizeWindow} onMaximize={maximizeWindow} onClose={closeWindow} />
<AppSidebar
viewMode={viewMode}
......@@ -1569,6 +1570,8 @@ export default function App() {
activeStreamSessionId={activeStreamRef.current?.sessionId}
sidebarSessionLabel={sidebarSessionLabel}
formatSessionTitle={formatSessionTitle}
isCollapsed={isSidebarCollapsed}
onToggleCollapsed={() => setIsSidebarCollapsed((current) => !current)}
navIcon={(kind) => <NavIcon kind={kind} />}
onNavSelection={handleNavSelection}
sidebarNewSessionAction={sidebarNewSessionAction}
......
......@@ -268,10 +268,9 @@ export function NavIcon({ kind }: { kind: "chat" | "experts" | "tasks" | "plugin
case "knowledge":
return (
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
<path d="M6 4.5A2.5 2.5 0 0 1 8.5 2h7.1L20 6.4v11.1a2.5 2.5 0 0 1-2.5 2.5h-9A2.5 2.5 0 0 1 6 17.5v-13Z" fill="#DBEAFE" stroke="#2563EB" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.45" />
<path d="M15.45 2.2v3.1c0 .82.66 1.48 1.48 1.48h2.92" fill="#BFDBFE" stroke="#60A5FA" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.35" />
<path d="M9.1 10.2h5.8M9.1 13.15h4.25" fill="none" stroke="#1D4ED8" strokeLinecap="round" strokeWidth="1.35" />
<path d="m16.7 12.55.38.8.82.12-.6.58.15.82-.75-.4-.75.4.15-.82-.6-.58.82-.12.38-.8Z" fill="#F59E0B" />
<path d="M5.25 5.7A2.45 2.45 0 0 1 7.7 3.25H11c1.1 0 2.12.34 3 .92.88-.58 1.9-.92 3-.92h1.05c.66 0 1.2.54 1.2 1.2v13.6c0 .66-.54 1.2-1.2 1.2H17c-1.1 0-2.12.34-3 .92-.88-.58-1.9-.92-3-.92H7.7a2.45 2.45 0 0 1-2.45-2.45V5.7Z" fill="#DBEAFE" stroke="#2563EB" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.45" />
<path d="M12 6.65v11.8M8.45 7.95h2.15M8.45 11h2.15M15.4 7.95h1.95M15.4 11h1.95" fill="none" stroke="#1D4ED8" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.35" />
<path d="M14 4.17v16" fill="none" stroke="#60A5FA" strokeLinecap="round" strokeWidth="1.2" />
</svg>
);
case "settings":
......
......@@ -26,6 +26,8 @@ interface AppSidebarProps {
activeStreamSessionId?: string
sidebarSessionLabel: string
formatSessionTitle(title: string, index: number): string
isCollapsed: boolean
onToggleCollapsed(): void
navIcon(kind: ViewMode): ReactNode
onNavSelection(mode: ViewMode): void
sidebarNewSessionAction: ReactNode
......@@ -53,6 +55,8 @@ export function AppSidebar({
activeStreamSessionId,
sidebarSessionLabel,
formatSessionTitle,
isCollapsed,
onToggleCollapsed,
navIcon,
onNavSelection,
sidebarNewSessionAction,
......@@ -72,7 +76,6 @@ export function AppSidebar({
{ id: "chat" as const, label: "对话" },
{ id: "tasks" as const, label: "工作台" },
{ id: "knowledge" as const, label: ui.knowledge },
{ id: "plugins" as const, label: ui.plugins },
{ id: "settings" as const, label: ui.settings }
].map((item) => (
<button
......@@ -89,7 +92,9 @@ export function AppSidebar({
</button>
))}
</nav>
{!showBindEntry ? sidebarNewSessionAction : null}
<div className="conversation-sidebar-action">
{!showBindEntry ? sidebarNewSessionAction : null}
</div>
</>
)
......@@ -123,5 +128,12 @@ export function AppSidebar({
</>
)
return <Sidebar topContent={topContent} bottomContent={bottomContent} />
return (
<Sidebar
topContent={topContent}
bottomContent={bottomContent}
isCollapsed={isCollapsed}
onToggleCollapsed={onToggleCollapsed}
/>
)
}
import type { ReactNode } from "react"
import brandIcon from "../../assets/brand-icon.png"
interface SidebarProps {
topContent: ReactNode
bottomContent: ReactNode
isCollapsed: boolean
onToggleCollapsed(): void
}
export function Sidebar({ topContent, bottomContent }: SidebarProps) {
export function Sidebar({ topContent, bottomContent, isCollapsed, onToggleCollapsed }: SidebarProps) {
return (
<aside className="sidebar conversation-sidebar-layout app-drag-region">
<div className="sidebar-brand">
<img src={brandIcon} alt="" className="sidebar-brand-logo" />
<strong className="sidebar-brand-name">千匠问天</strong>
<button
type="button"
className="sidebar-collapse-button app-no-drag"
aria-label={isCollapsed ? "展开侧栏" : "折叠侧栏"}
aria-expanded={!isCollapsed}
onClick={onToggleCollapsed}
>
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
{isCollapsed ? (
<path d="m10 7 5 5-5 5" fill="none" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" />
) : (
<path d="m14 7-5 5 5 5" fill="none" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" />
)}
</svg>
</button>
</div>
<div className="sidebar-top">
{topContent}
</div>
......
......@@ -130,7 +130,7 @@
.conversation-sidebar-layout {
position: relative;
isolation: isolate;
grid-template-rows: auto minmax(0, 1fr);
grid-template-rows: auto auto minmax(0, 1fr);
gap: 14px;
padding: 18px 16px;
}
......
.knowledge-page-stack {
height: 100%;
min-height: 0;
display: flex;
flex-direction: column;
}
.knowledge-panel {
flex: 1;
min-height: 0;
display: flex;
flex-direction: column;
padding: 32px 40px;
gap: 32px;
padding: 28px 32px;
gap: 24px;
border-color: rgba(148, 163, 184, 0.24);
background: #ffffff;
box-shadow: 0 8px 18px rgba(15, 23, 42, 0.04);
}
.knowledge-panel-body {
......@@ -17,157 +22,186 @@
min-height: 0;
flex: 1;
flex-direction: column;
gap: 32px;
gap: 24px;
padding: 0;
overflow: auto;
}
.knowledge-header {
text-align: center;
margin-bottom: 16px;
text-align: left;
margin: 0;
}
.knowledge-title {
font-size: 32px;
margin: 0 0 8px;
color: #12355f;
font-size: 28px;
font-weight: 700;
margin: 0 0 8px 0;
background: linear-gradient(135deg, #7C3AED 0%, #A78BFA 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
line-height: 1.25;
}
.knowledge-subtitle {
font-size: 16px;
color: #94A3B8;
margin: 0;
color: #486985;
font-size: 14px;
line-height: 1.6;
}
.knowledge-upload-section {
display: flex;
justify-content: center;
margin: 32px 0;
margin: 8px 0;
}
.upload-card {
background: rgba(255, 255, 255, 0.08);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 2px dashed rgba(124, 58, 237, 0.3);
border-radius: 24px;
padding: 48px 64px;
width: min(520px, 100%);
padding: 36px 40px;
text-align: center;
max-width: 500px;
width: 100%;
transition: all 0.3s ease;
border: 1px dashed rgba(59, 130, 246, 0.34);
border-radius: 12px;
background: #ffffff;
box-shadow: 0 10px 24px rgba(37, 99, 235, 0.08);
transition: border-color 180ms ease, background 180ms ease, box-shadow 180ms ease;
}
.upload-card:hover {
border-color: rgba(124, 58, 237, 0.6);
background: rgba(255, 255, 255, 0.12);
transform: translateY(-4px);
box-shadow: 0 20px 40px rgba(124, 58, 237, 0.15);
border-color: rgba(37, 99, 235, 0.56);
background: #f8fbff;
box-shadow: 0 14px 30px rgba(37, 99, 235, 0.12);
}
.upload-icon {
margin-bottom: 24px;
width: 56px;
height: 56px;
display: inline-grid;
place-items: center;
margin-bottom: 20px;
border-radius: 12px;
background: #e0edff;
color: #2563eb;
box-shadow: inset 0 0 0 1px rgba(37, 99, 235, 0.14);
}
.upload-icon svg {
stroke: #A78BFA;
width: 34px;
height: 34px;
stroke: currentColor;
}
.upload-title {
font-size: 24px;
font-weight: 600;
margin: 0 0 12px 0;
color: #E2E8F0;
margin: 0 0 10px;
color: #12355f;
font-size: 20px;
font-weight: 700;
line-height: 1.35;
}
.upload-desc {
margin: 0 0 22px;
color: #64748b;
font-size: 14px;
color: #94A3B8;
margin: 0 0 24px 0;
line-height: 1.6;
}
.upload-button {
background: linear-gradient(135deg, #7C3AED 0%, #A78BFA 100%);
color: white;
border: none;
border-radius: 12px;
padding: 16px 32px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
margin-bottom: 16px;
min-height: 44px;
padding: 0 24px;
border: 1px solid rgba(29, 78, 216, 0.18);
border-radius: 10px;
background: linear-gradient(135deg, #2563eb, #0f6eea);
color: #ffffff;
font-size: 14px;
font-weight: 700;
box-shadow: 0 10px 22px rgba(37, 99, 235, 0.24);
transition: border-color 180ms ease, background 180ms ease, box-shadow 180ms ease;
margin-bottom: 14px;
}
.upload-button:hover {
opacity: 0.9;
transform: translateY(-2px);
box-shadow: 0 10px 20px rgba(124, 58, 237, 0.3);
.upload-button:hover:not(:disabled) {
border-color: rgba(29, 78, 216, 0.32);
background: linear-gradient(135deg, #1d4ed8, #075bd8);
box-shadow: 0 12px 24px rgba(37, 99, 235, 0.28);
}
.upload-hint {
font-size: 12px;
color: #64748B;
margin: 0;
color: #64748b;
font-size: 12px;
line-height: 1.5;
}
.knowledge-list-section {
margin-top: 48px;
display: grid;
gap: 16px;
margin-top: 8px;
}
.section-title {
font-size: 20px;
font-weight: 600;
margin: 0 0 24px 0;
color: #E2E8F0;
margin: 0;
color: #12355f;
font-size: 18px;
font-weight: 700;
line-height: 1.35;
}
.document-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 20px;
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
gap: 14px;
}
.document-card {
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(124, 58, 237, 0.2);
border-radius: 16px;
padding: 20px;
min-height: 76px;
display: flex;
align-items: center;
gap: 16px;
transition: all 0.3s ease;
gap: 14px;
padding: 16px;
border: 1px solid rgba(59, 130, 246, 0.22);
border-radius: 10px;
background: #ffffff;
color: #12355f;
transition: border-color 180ms ease, background 180ms ease, box-shadow 180ms ease;
cursor: pointer;
}
.document-card:hover {
background: rgba(255, 255, 255, 0.1);
border-color: rgba(124, 58, 237, 0.4);
transform: translateY(-4px);
box-shadow: 0 12px 24px rgba(124, 58, 237, 0.15);
border-color: rgba(37, 99, 235, 0.42);
background: #f8fbff;
box-shadow: 0 8px 16px rgba(37, 99, 235, 0.08);
}
.document-icon {
font-size: 32px;
width: 42px;
height: 42px;
flex: 0 0 auto;
display: inline-grid;
place-items: center;
border-radius: 10px;
background: #e0edff;
color: #2563eb;
font-size: 12px;
font-weight: 800;
}
.document-info {
min-width: 0;
}
.document-info h4 {
font-size: 16px;
font-weight: 600;
margin: 0 0 4px 0;
color: #E2E8F0;
margin: 0 0 4px;
color: #12355f;
font-size: 15px;
font-weight: 700;
line-height: 1.35;
}
.document-info p {
font-size: 12px;
color: #94A3B8;
margin: 0;
color: #64748b;
font-size: 12px;
line-height: 1.5;
}
h1, h2, h3, h4, h5, h6,
.knowledge-title,
.section-title,
......@@ -175,6 +209,3 @@ h1, h2, h3, h4, h5, h6,
font-family: var(--font-heading);
font-weight: 600;
}
/* gray-blue revamp: consolidated final theme layer */
......@@ -14,7 +14,7 @@
height: 100vh;
padding: var(--space-5) var(--space-4);
display: grid;
grid-template-rows: auto minmax(0, 1fr);
grid-template-rows: auto auto minmax(0, 1fr);
gap: 18px;
background:
linear-gradient(180deg, rgba(167, 139, 250, 0.15), rgba(124, 58, 237, 0.1)),
......
......@@ -28,6 +28,10 @@
color: var(--revamp-text);
}
.shell.openclaw-theme.sidebar-collapsed {
grid-template-columns: 64px minmax(0, 1fr);
}
.shell.openclaw-theme button,
.shell.openclaw-theme [role="button"],
.shell.openclaw-theme summary,
......@@ -95,11 +99,64 @@
.shell.openclaw-theme .conversation-sidebar-layout {
isolation: isolate;
grid-template-rows: auto minmax(0, 1fr);
grid-template-rows: auto auto minmax(0, 1fr);
gap: 12px;
padding: 16px 14px;
}
.shell.openclaw-theme .sidebar-brand {
position: relative;
z-index: 1;
display: grid;
grid-template-columns: 32px minmax(0, 1fr) 28px;
align-items: center;
gap: 10px;
min-height: 36px;
}
.shell.openclaw-theme .sidebar-brand-logo {
width: 32px;
height: 32px;
display: block;
object-fit: contain;
}
.shell.openclaw-theme .sidebar-brand-name {
min-width: 0;
overflow: hidden;
color: #12355f;
font-size: 15px;
font-weight: 800;
line-height: 1.3;
text-overflow: ellipsis;
white-space: nowrap;
}
.shell.openclaw-theme .sidebar-collapse-button {
width: 28px;
height: 28px;
padding: 0;
display: inline-grid;
place-items: center;
border: 1px solid rgba(96, 165, 250, 0.28);
border-radius: 8px;
background: rgba(255, 255, 255, 0.74);
color: #2563eb;
box-shadow: none;
transition: border-color 180ms ease, background 180ms ease, color 180ms ease;
}
.shell.openclaw-theme .sidebar-collapse-button:hover:not(:disabled) {
border-color: rgba(37, 99, 235, 0.48);
background: #ffffff;
color: #1d4ed8;
}
.shell.openclaw-theme .sidebar-collapse-button svg {
width: 18px;
height: 18px;
}
.shell.openclaw-theme .sidebar-top {
position: relative;
z-index: 1;
......@@ -162,9 +219,9 @@
.shell.openclaw-theme .sidebar-new-session.conversation-new-session {
width: 100%;
min-height: 44px;
min-height: 42px;
justify-content: center;
padding: 0 16px;
padding: 0 12px;
border-radius: 10px;
border-color: rgba(29, 78, 216, 0.18);
background: linear-gradient(135deg, #2563eb, #0f6eea);
......@@ -208,6 +265,69 @@
padding-right: 0;
}
.shell.openclaw-theme.sidebar-collapsed .conversation-sidebar-layout {
grid-template-rows: auto 1fr;
gap: 14px;
padding: 16px 10px;
}
.shell.openclaw-theme.sidebar-collapsed .sidebar-brand {
grid-template-columns: 44px;
justify-items: center;
justify-content: center;
gap: 0;
min-height: 44px;
}
.shell.openclaw-theme.sidebar-collapsed .sidebar-brand-logo,
.shell.openclaw-theme.sidebar-collapsed .sidebar-brand-name,
.shell.openclaw-theme.sidebar-collapsed .nav-item-label,
.shell.openclaw-theme.sidebar-collapsed .conversation-sidebar-action,
.shell.openclaw-theme.sidebar-collapsed .sidebar-bottom {
display: none;
}
.shell.openclaw-theme.sidebar-collapsed .sidebar-collapse-button {
width: 44px;
height: 44px;
border-radius: 10px;
}
.shell.openclaw-theme.sidebar-collapsed .sidebar-top {
min-height: 0;
align-content: start;
gap: 12px;
}
.shell.openclaw-theme.sidebar-collapsed .nav-list {
justify-items: center;
align-content: start;
gap: 8px;
}
.shell.openclaw-theme.sidebar-collapsed .nav-item {
width: 44px;
min-height: 44px;
justify-content: center;
padding: 0;
}
.shell.openclaw-theme.sidebar-collapsed .nav-item.active {
box-shadow:
inset 0 -3px 0 var(--revamp-blue),
0 8px 18px rgba(37, 99, 235, 0.12);
}
.shell.openclaw-theme.sidebar-collapsed .nav-item-icon {
display: inline-grid;
place-items: center;
}
.shell.openclaw-theme.sidebar-collapsed .nav-item-icon svg {
width: 20px;
height: 20px;
}
.shell.openclaw-theme .sidebar-experts-entry,
.shell.openclaw-theme .sidebar-session-section,
.shell.openclaw-theme .expert-category-item,
......
import test from "node:test"
import assert from "node:assert/strict"
import { readFileSync } from "node:fs"
const appSource = readFileSync(new URL("../src/App.tsx", import.meta.url), "utf8")
const appSidebarSource = readFileSync(new URL("../src/features/shell/AppSidebar.tsx", import.meta.url), "utf8")
const sidebarSource = readFileSync(new URL("../src/features/shell/Sidebar.tsx", import.meta.url), "utf8")
const iconSource = readFileSync(new URL("../src/components/icons/AppIcons.tsx", import.meta.url), "utf8")
const knowledgeStylesSource = readFileSync(new URL("../src/styles/knowledge.css", import.meta.url), "utf8")
const shellStylesSource = readFileSync(new URL("../src/styles/shell.css", import.meta.url), "utf8")
const chatStylesSource = readFileSync(new URL("../src/styles/chat.css", import.meta.url), "utf8")
const themeStylesSource = readFileSync(new URL("../src/styles/theme-openclaw.css", import.meta.url), "utf8")
function cssBlock(source: string, selector: string): string {
const start = source.indexOf(`${selector} {`)
assert.notEqual(start, -1, `Missing CSS selector: ${selector}`)
const end = source.indexOf("\n}", start)
assert.notEqual(end, -1, `Missing CSS block end: ${selector}`)
return source.slice(start, end + 2)
}
test("app shell wires a collapsible sidebar state into the sidebar components", () => {
assert.match(appSource, /const \[isSidebarCollapsed, setIsSidebarCollapsed\] = useState\(false\)/)
assert.match(appSource, /sidebar-collapsed/)
assert.match(appSource, /isCollapsed=\{isSidebarCollapsed\}/)
assert.match(appSource, /onToggleCollapsed=\{\(\) => setIsSidebarCollapsed\(\(current\) => !current\)\}/)
assert.match(appSidebarSource, /\bisCollapsed:\s*boolean/)
assert.match(appSidebarSource, /\bonToggleCollapsed\(\):\s*void/)
assert.match(appSidebarSource, /<Sidebar[\s\S]*?isCollapsed=\{isCollapsed\}[\s\S]*?onToggleCollapsed=\{onToggleCollapsed\}/)
})
test("sidebar nav omits plugins and keeps the new conversation action visible when expanded", () => {
const navItems = appSidebarSource.match(/\{\s*id:\s*"chat"[\s\S]*?\]\.map/)?.[0] ?? ""
assert.match(navItems, /id:\s*"chat"/)
assert.match(navItems, /id:\s*"tasks"/)
assert.match(navItems, /id:\s*"knowledge"/)
assert.match(navItems, /id:\s*"settings"/)
assert.doesNotMatch(navItems, /id:\s*"plugins"/)
assert.match(appSidebarSource, /<div className="conversation-sidebar-action">\s*\{!showBindEntry \? sidebarNewSessionAction : null\}\s*<\/div>/)
})
test("sidebar renders the brand row and hides non-icon content in collapsed mode", () => {
assert.match(sidebarSource, /brandIcon/)
assert.match(sidebarSource, /sidebar-brand/)
assert.match(sidebarSource, /千匠问天/)
assert.match(sidebarSource, /sidebar-collapse-button/)
assert.match(themeStylesSource, /\.shell\.openclaw-theme\.sidebar-collapsed\s*\{[\s\S]*?grid-template-columns:\s*64px minmax\(0, 1fr\);/m)
assert.match(themeStylesSource, /\.shell\.openclaw-theme\.sidebar-collapsed \.sidebar-brand-logo,\s*\n\.shell\.openclaw-theme\.sidebar-collapsed \.sidebar-brand-name/)
assert.match(themeStylesSource, /\.shell\.openclaw-theme\.sidebar-collapsed \.sidebar-brand\s*\{[\s\S]*?grid-template-columns:\s*44px;/m)
assert.match(themeStylesSource, /\.shell\.openclaw-theme\.sidebar-collapsed \.sidebar-collapse-button\s*\{[\s\S]*?width:\s*44px;[\s\S]*?height:\s*44px;/m)
assert.match(themeStylesSource, /\.shell\.openclaw-theme\.sidebar-collapsed \.sidebar-top\s*\{[\s\S]*?align-content:\s*start;/m)
assert.match(themeStylesSource, /\.shell\.openclaw-theme\.sidebar-collapsed \.nav-item-label/)
assert.match(themeStylesSource, /\.shell\.openclaw-theme\.sidebar-collapsed \.sidebar-bottom/)
})
test("sidebar keeps brand, navigation, and lower content in fixed grid rows", () => {
assert.match(cssBlock(shellStylesSource, ".sidebar"), /grid-template-rows:\s*auto auto minmax\(0, 1fr\);/)
assert.match(cssBlock(chatStylesSource, ".conversation-sidebar-layout"), /grid-template-rows:\s*auto auto minmax\(0, 1fr\);/)
assert.match(cssBlock(themeStylesSource, ".shell.openclaw-theme .conversation-sidebar-layout"), /grid-template-rows:\s*auto auto minmax\(0, 1fr\);/)
assert.match(cssBlock(themeStylesSource, ".shell.openclaw-theme .sidebar-bottom"), /grid-template-rows:\s*minmax\(0, 56fr\) minmax\(150px, 44fr\);[\s\S]*?overflow:\s*hidden;/)
})
test("new conversation action matches the sidebar navigation control size", () => {
assert.match(cssBlock(themeStylesSource, ".shell.openclaw-theme .nav-item"), /min-height:\s*42px;[\s\S]*?border-radius:\s*10px;/)
assert.match(cssBlock(themeStylesSource, ".shell.openclaw-theme .sidebar-new-session.conversation-new-session"), /width:\s*100%;[\s\S]*?min-height:\s*42px;[\s\S]*?justify-content:\s*center;[\s\S]*?border-radius:\s*10px;/)
})
test("knowledge navigation uses a book icon instead of the old document icon", () => {
const knowledgeIconCase = iconSource.match(/case "knowledge":[\s\S]*?case "settings":/)?.[0] ?? ""
assert.match(knowledgeIconCase, /M5\.25 5\.7/)
assert.match(knowledgeIconCase, /M12 6\.65/)
assert.doesNotMatch(knowledgeIconCase, /M6 4\.5A2\.5 2\.5/)
})
test("knowledge page uses the OpenClaw blue-white theme instead of purple glass styling", () => {
assert.doesNotMatch(knowledgeStylesSource, /#7C3AED|#A78BFA|backdrop-filter|rgba\(255,\s*255,\s*255,\s*0\.0[58]\)/)
assert.match(knowledgeStylesSource, /\.upload-card\s*\{[\s\S]*?background:\s*#ffffff;/m)
assert.match(knowledgeStylesSource, /\.upload-card\s*\{[\s\S]*?border:\s*1px dashed rgba\(59, 130, 246, 0\.34\);/m)
assert.match(knowledgeStylesSource, /\.knowledge-title\s*\{[\s\S]*?color:\s*#12355f;/m)
assert.match(knowledgeStylesSource, /\.upload-button\s*\{[\s\S]*?background:\s*linear-gradient\(135deg, #2563eb, #0f6eea\);/m)
})
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment