Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# TODO

- [x] Web sidebar: add invisible non-selectable new-tab placeholder.
- [x] Web sidebar brand: make Codex Mate title slightly larger and keep app version always visible.
- [x] Verify with unit tests, lint, and a headless Chrome screenshot.
16 changes: 16 additions & 0 deletions tests/unit/config-tabs-ui.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ test('config template keeps expected config tabs in top and side navigation', ()
const baseTheme = readProjectFile('web-ui/styles/base-theme.css');
const controlsForms = readProjectFile('web-ui/styles/controls-forms.css');
const taskOrchestrationStyles = readProjectFile('web-ui/styles/task-orchestration.css');
const layoutShell = readProjectFile('web-ui/styles/layout-shell.css');
const bundledStyles = readBundledWebUiCss();
const sideRail = html.match(/<aside class="side-rail"[\s\S]*?<\/aside>/)?.[0] || '';
const sideTabModes = [...html.matchAll(/id="side-tab-config-([a-z]+)"/g)]
Expand Down Expand Up @@ -120,6 +121,21 @@ test('config template keeps expected config tabs in top and side navigation', ()
assert.match(styles, /\.task-action-row-right\s*\{[\s\S]*display:\s*flex;[\s\S]*flex-wrap:\s*wrap;/);
assert.match(styles, /\.task-runtime-item-actions\s*\{[\s\S]*flex-direction:\s*row;[\s\S]*align-items:\s*center;/);
}
const sideGhostTab = sideRail.match(/<div id="side-tab-new"[\s\S]*?<\/div>\s*<\/div>/)?.[0] || '';
assert.match(sideGhostTab, /class="side-item side-item-ghost"/);
assert.match(sideGhostTab, /tabindex="-1"/);
assert.match(sideGhostTab, /aria-hidden="true"/);
assert.doesNotMatch(sideGhostTab, /data-main-tab=/);
assert.doesNotMatch(sideGhostTab, /@click=/);
assert.doesNotMatch(sideGhostTab, /@keydown/);
assert.match(html, /<div class="brand-kicker">Codex Mate<span v-if="appVersion" class="brand-version"> v\{\{ appVersion \}\}<\/span><\/div>/);
assert.doesNotMatch(html, /appVersion && brandHovered/);
assert.doesNotMatch(html, /brandHovered = true/);
for (const styles of [layoutShell, bundledStyles]) {
assert.match(styles, /\.side-item-ghost\s*\{[\s\S]*opacity:\s*0;[\s\S]*pointer-events:\s*none;[\s\S]*user-select:\s*none;/);
assert.match(styles, /\.brand-kicker\s*\{[\s\S]*font-size:\s*15px;/);
assert.match(styles, /\.brand-version\s*\{[\s\S]*font-size:\s*13px;/);
}
assert.match(html, /id="side-tab-market"/);
assert.match(html, /id="tab-market"/);
assert.match(html, /id="side-tab-docs"/);
Expand Down
3 changes: 1 addition & 2 deletions tests/unit/web-ui-behavior-parity.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,6 @@ test('captured bundled app skeleton only exposes expected data key drift versus
const missingCurrentKeys = headDataKeys.filter((key) => !currentDataKeys.includes(key)).sort();
const allowedExtraCurrentKeys = parityAgainstHead ? [
'appVersion',
'brandHovered',
'sessionListInitialBatchSize',
'sessionListLoadStep',
'sessionListVisibleCount',
Expand Down Expand Up @@ -355,7 +354,6 @@ test('captured bundled app skeleton only exposes expected data key drift versus
'showEditProviderKey'
] : [
'appVersion',
'brandHovered',
'__mainTabSwitchState',
'openclawAuthProfilesByProvider',
'openclawPendingAuthProfileUpdates',
Expand Down Expand Up @@ -410,6 +408,7 @@ test('captured bundled app skeleton only exposes expected data key drift versus
'promptTemplateVarDraftError',
'promptTemplateVarDraftName',
'showPromptTemplateVarModal',
'brandHovered',
];
allowedExtraCurrentKeys.push(
'lang',
Expand Down
1 change: 0 additions & 1 deletion web-ui/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ document.addEventListener('DOMContentLoaded', () => {
const appOptions = {
data() {
return {
brandHovered: false,
lang: 'zh',
appVersion: '',
mainTab: 'dashboard',
Expand Down
8 changes: 6 additions & 2 deletions web-ui/partials/index/layout-header.html
Original file line number Diff line number Diff line change
Expand Up @@ -118,16 +118,20 @@

<div :class="['app-shell', { standalone: sessionStandalone }]">
<aside class="side-rail" v-if="!sessionStandalone">
<div class="brand-block" tabindex="0" @mouseenter="brandHovered = true" @mouseleave="brandHovered = false" @focus="brandHovered = true" @blur="brandHovered = false">
<div class="brand-block" tabindex="0">
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
<div class="brand-head">
<img class="brand-logo" src="/res/logo-pack.webp" alt="Codex Mate logo">
<div class="brand-copy">
<div class="brand-kicker">Codex Mate<transition name="brand-version-fade"><span v-if="appVersion && brandHovered" class="brand-version"> v{{ appVersion }}</span></transition></div>
<div class="brand-kicker">Codex Mate<span v-if="appVersion" class="brand-version"> v{{ appVersion }}</span></div>
</div>
</div>
</div>

<div class="side-rail-nav">
<div id="side-tab-new" class="side-item side-item-ghost" tabindex="-1" aria-hidden="true">
<div class="side-item-title">New Tab</div>
<div class="side-item-meta"><span>&nbsp;</span></div>
</div>
<div class="side-section" role="navigation" :aria-label="t('side.overview')">
<div class="side-section-title">{{ t('side.overview') }}</div>
<button
Expand Down
38 changes: 20 additions & 18 deletions web-ui/res/web-ui-render.precompiled.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
window.__CODEXMATE_WEB_UI_RENDER__ = (() => {
const { toDisplayString: _toDisplayString, normalizeClass: _normalizeClass, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock, createCommentVNode: _createCommentVNode, Transition: _Transition, withCtx: _withCtx, createVNode: _createVNode, createTextVNode: _createTextVNode, Fragment: _Fragment, renderList: _renderList, vShow: _vShow, withDirectives: _withDirectives, vModelSelect: _vModelSelect, vModelText: _vModelText, withKeys: _withKeys, withModifiers: _withModifiers, isMemoSame: _isMemoSame, withMemo: _withMemo, normalizeStyle: _normalizeStyle, vModelCheckbox: _vModelCheckbox, vModelDynamic: _vModelDynamic } = Vue
const { toDisplayString: _toDisplayString, normalizeClass: _normalizeClass, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock, createCommentVNode: _createCommentVNode, createTextVNode: _createTextVNode, Fragment: _Fragment, renderList: _renderList, vShow: _vShow, withDirectives: _withDirectives, vModelSelect: _vModelSelect, vModelText: _vModelText, withKeys: _withKeys, withModifiers: _withModifiers, isMemoSame: _isMemoSame, withMemo: _withMemo, normalizeStyle: _normalizeStyle, vModelCheckbox: _vModelCheckbox, vModelDynamic: _vModelDynamic } = Vue

return function render(_ctx, _cache) {
return (_openBlock(), _createElementBlock(_Fragment, null, [
Expand Down Expand Up @@ -167,11 +167,7 @@ return function render(_ctx, _cache) {
}, [
_createElementVNode("div", {
class: "brand-block",
tabindex: "0",
onMouseenter: $event => (_ctx.brandHovered = true),
onMouseleave: $event => (_ctx.brandHovered = false),
onFocus: $event => (_ctx.brandHovered = true),
onBlur: $event => (_ctx.brandHovered = false)
tabindex: "0"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
}, [
_createElementVNode("div", { class: "brand-head" }, [
_createElementVNode("img", {
Expand All @@ -182,22 +178,28 @@ return function render(_ctx, _cache) {
_createElementVNode("div", { class: "brand-copy" }, [
_createElementVNode("div", { class: "brand-kicker" }, [
_createTextVNode("Codex Mate"),
_createVNode(_Transition, { name: "brand-version-fade" }, {
default: _withCtx(() => [
(_ctx.appVersion && _ctx.brandHovered)
? (_openBlock(), _createElementBlock("span", {
key: 0,
class: "brand-version"
}, " v" + _toDisplayString(_ctx.appVersion), 1 /* TEXT */))
: _createCommentVNode("v-if", true)
]),
_: 1 /* STABLE */
})
(_ctx.appVersion)
? (_openBlock(), _createElementBlock("span", {
key: 0,
class: "brand-version"
}, " v" + _toDisplayString(_ctx.appVersion), 1 /* TEXT */))
: _createCommentVNode("v-if", true)
])
])
])
], 40 /* PROPS, NEED_HYDRATION */, ["onMouseenter", "onMouseleave", "onFocus", "onBlur"]),
]),
_createElementVNode("div", { class: "side-rail-nav" }, [
_createElementVNode("div", {
id: "side-tab-new",
class: "side-item side-item-ghost",
tabindex: "-1",
"aria-hidden": "true"
}, [
_createElementVNode("div", { class: "side-item-title" }, "New Tab"),
_createElementVNode("div", { class: "side-item-meta" }, [
_createElementVNode("span", null, " ")
])
]),
_createElementVNode("div", {
class: "side-section",
role: "navigation",
Expand Down
13 changes: 11 additions & 2 deletions web-ui/styles/layout-shell.css
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,14 @@ body::after {
word-break: break-word;
}

.side-item-ghost {
opacity: 0;
pointer-events: none;
user-select: none;
cursor: default;
flex: 0 0 auto;
}

@media (min-width: 721px) {
body:not(.force-compact) #app > .top-tabs {
display: none;
Expand Down Expand Up @@ -484,7 +492,7 @@ body::after {
}

.brand-kicker {
font-size: 13px;
font-size: 15px;
line-height: 1.2;
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", system-ui, sans-serif;
color: #1d1d1f;
Expand All @@ -502,10 +510,11 @@ body::after {
}

.brand-version {
font-size: 12px;
font-size: 13px;
font-weight: 400;
color: #8e8e93;
vertical-align: baseline;
-webkit-text-fill-color: #8e8e93;
}

.brand-version-fade-enter-active,
Expand Down
Loading