fix(home): reposition title and compact account entry

This commit is contained in:
鲨鱼辣椒 committed 2026-10-13 13:55:24 +08:00
1 parent 886b4589a1
commit a9fa221d7a
6 files changed
+80 -13

No files matched your search

@@ -8,7 +8,7 @@
- Worktree: /Users/chillishark/Makelore 麦洛/.codex-worktrees/makelore/20261013-combined-trial-update-c42d19a7 - Worktree: /Users/chillishark/Makelore 麦洛/.codex-worktrees/makelore/20261013-combined-trial-update-c42d19a7
- Base commit: 8289f27f3e53ef455b656f1c43d2620d235d5014 - Base commit: 8289f27f3e53ef455b656f1c43d2620d235d5014
- Owner: codex - Owner: codex
- Status: Ready for Integration - Status: Planning
## Scope ## Scope
@@ -17,6 +17,17 @@
- Follow-up: restore access to the existing account controls on the standalone module homepage and update the current trial. - Follow-up: restore access to the existing account controls on the standalone module homepage and update the current trial.
- Latest follow-up: restore the desktop title-left / three-card vertical layout with the Code card centered on the title; retain Robot hiding and account controls. - Latest follow-up: restore the desktop title-left / three-card vertical layout with the Code card centered on the title; retain Robot hiding and account controls.
## Homepage Position / Compact Account Follow-up · 2026-10-13
- Concurrent / Planning Gates: Passed on same-task resume, exact owner/task/worktree/branch/feature/base verified after successful start and structure check. Reread current task/source; retain unchanged startup/positioning/decisions/architecture/domain/evidence/reflection/commitment context. Peer delta review found only compatible un-deployed knowledge and Tool/Skill copy changes, outside this scope; no new homepage dependency and no automatic import. Latest current runtime remains Renderer/effective 383ce71 over Main 7c51bb4, PID 9968 / port 58003.
- User requests title “网友移动1.5cm”, interpreted and communicated as the homophone typo for “往右移动1.5cm”, and the pictured account entry at half its current visual size, moved left 1.5cm toward the lower-left corner. Preserve three-card equal geometry and Code/title vertical alignment, Robot hiding, account menu behavior and current recovery source.
- Plan: move the desktop intro right by CSS 1.5cm (~56.7px), measure the actual h1 text box rather than its full grid column; narrow widths retain centered heading to avoid overflow. Render the trigger surface at half scale in an unchanged 50px keyboard/click footprint; menu/wallet remain unscaled. Move the account controls left up to 1.5cm with a 16px window inset limit and down to a 16px bottom inset; retain the footer flow reservation and normal scrolling.
- Verification: focused account/module/wallet units, typecheck/lint/build, existing Electron account/layout scenarios plus visual geometry assertions at four widths and screenshots. Compare before/after live title and trigger rectangles in Renderer-only update, with complete Main/resource/package byte proof and current-pointer binding. Fresh saved-state guards remain; do not save/discard for the user or perform real account/payment/Agent operations.
- Source implemented and frozen: three product files only (ModuleSelection, globals, HomeAccountMenu), README and focused geometry assertions. Existing handlers, menus/dialog lifecycle, module permissions, Code alignment/card stack and Main are unchanged.
- Verification complete before activation: 25 focused units / 3 files, typecheck, scoped source/test ESLint and full build passed. Two Electron scenarios passed, with title displacement, actual text-range width, half-size surface/avatar, unchanged click footprint, full-size menu/gap, 16px bottom inset, desktop left shift/mobile screen safety and prior navigation/menu/wallet behavior. Layout measured at 1440/1024/768/375; desktop and mobile account screenshots plus 1024/375 layout screenshots visually inspected. Independent Main proof: dist-electron 8 files/resources 57 files/package exact original 7c snapshot.
- Renderer-only update prepared next, current-pointer baseline remains active 383ce71; source commit and final live receipt to be recorded after clean-state activation.
## Homepage Vertical Layout Follow-up · 2026-10-13 ## Homepage Vertical Layout Follow-up · 2026-10-13
- Concurrent / Planning Gates: Passed on same-task resume; successful start and matching feature / task / branch / absolute worktree / base ownership verified. Retain unchanged startup, positioning, architecture, domain, accepted decisions, evidence/reflection/commitment context from the previous gate; reread the active record, startup, planning gate and local AGENTS. Source task records stay read-only; primary checkout remains untouched. - Concurrent / Planning Gates: Passed on same-task resume; successful start and matching feature / task / branch / absolute worktree / base ownership verified. Retain unchanged startup, positioning, architecture, domain, accepted decisions, evidence/reflection/commitment context from the previous gate; reread the active record, startup, planning gate and local AGENTS. Source task records stay read-only; primary checkout remains untouched.
+1 -1
View File
@@ -8,7 +8,7 @@
Code 会话保存的模型被下架后,会提示重新选择可用模型;用户可在原会话中切换,恢复时使用新选择并保留会话与历史消息。切换不会自动重发消息或自动选择替代模型。 Code 会话保存的模型被下架后,会提示重新选择可用模型;用户可在原会话中切换,恢复时使用新选择并保留会话与历史消息。切换不会自动重发消息或自动选择替代模型。
Makelore 是一个面向软件、视觉创作、智能机器人与个人云智能体的 AI 桌面工作台。当前版本为 `2.0.0`,首页展示 Agents、Code、Canvas 三个模块入口,Robot 入口隐藏;云智能体需配套配置 WS/Yuxi 服务。桌面采用左侧问候与标题、右侧三个横向插画卡片竖排的布局,Code 入口中心与“今天,想创作什么?”标题中心对齐,三个入口等高、间距均匀;小窗口将标题放在卡片上方;Agents 标语为“打造你想象中的AI助手”。首页左下角保留账户入口,可查看本人词元点数与预占、打开词元点数与充值、个人资料、设置和退出登录;沿用侧栏的账号、钱包和资料规则。工作区左上角入口点击后返回模块入口页: Makelore 是一个面向软件、视觉创作、智能机器人与个人云智能体的 AI 桌面工作台。当前版本为 `2.0.0`,首页展示 Agents、Code、Canvas 三个模块入口,Robot 入口隐藏;云智能体需配套配置 WS/Yuxi 服务。桌面采用左侧问候与标题、右侧三个横向插画卡片竖排的布局,Code 入口中心与“今天,想创作什么?”标题中心对齐,三个入口等高、间距均匀;标题组在桌面向右偏移约 1.5cm,小窗口将标题放在卡片上方;Agents 标语为“打造你想象中的AI助手”。首页左下角保留半尺寸的账户入口,向左偏移约 1.5cm(小窗口保留边距)并靠近底部;展开菜单保持原尺寸,可查看本人词元点数与预占、打开词元点数与充值、个人资料、设置和退出登录;沿用侧栏的账号、钱包和资料规则。工作区左上角入口点击后返回模块入口页:
- `Makelore Code|AI 编程`:管理本地项目、项目智能体、对话、文件上下文、代码变更和运行时。 - `Makelore Code|AI 编程`:管理本地项目、项目智能体、对话、文件上下文、代码变更和运行时。
会话工作台采用浅蓝项目栏、彩色通用手柄图标与卡片选中态;中间对话/作品使用圆角标签,作品预览的后退、前进、刷新与网址输入统一放在标签右侧;只在作品页显示,窄窗口自动换行。右侧智能体咨询使用奶油色标题区。沿用真实项目和会话入口、侧栏伸缩与账户菜单,样式只作用于 Code 会话工作台。可编辑的空对话在输入区上方的可用区域中央显示童趣欢迎语;「去作品广场逛逛」入口紧贴输入框上方,两者一起位于底部。老师介绍以固定宽度浮在右上角,不占用中间布局;欢迎语和底部输入区始终相对项目栏右侧的整个内容区域居中;小窗口的欢迎内容独立滚动;发送首条消息后收起欢迎区。侧栏保留「作品广场」入口,只有点击后才在系统浏览器打开公共广场,不请求、摘取或内嵌展示广场作品。打开广场保留当前项目、对话、草稿和附件,不自动发送消息;老师求助按钮保留临时应用的最新文案「帮我整体看🧠」。 会话工作台采用浅蓝项目栏、彩色通用手柄图标与卡片选中态;中间对话/作品使用圆角标签,作品预览的后退、前进、刷新与网址输入统一放在标签右侧;只在作品页显示,窄窗口自动换行。右侧智能体咨询使用奶油色标题区。沿用真实项目和会话入口、侧栏伸缩与账户菜单,样式只作用于 Code 会话工作台。可编辑的空对话在输入区上方的可用区域中央显示童趣欢迎语;「去作品广场逛逛」入口紧贴输入框上方,两者一起位于底部。老师介绍以固定宽度浮在右上角,不占用中间布局;欢迎语和底部输入区始终相对项目栏右侧的整个内容区域居中;小窗口的欢迎内容独立滚动;发送首条消息后收起欢迎区。侧栏保留「作品广场」入口,只有点击后才在系统浏览器打开公共广场,不请求、摘取或内嵌展示广场作品。打开广场保留当前项目、对话、草稿和附件,不自动发送消息;老师求助按钮保留临时应用的最新文案「帮我整体看🧠」。
+9 -6
View File
@@ -48,23 +48,26 @@ export function HomeAccountMenu({ accountName, avatarUrl, onOpenProfile }: {
if (!user) return null; if (!user) return null;
return <div className="relative w-64 max-w-full" data-testid="home-account-controls"> return <div className="relative h-[3.125rem] w-32 max-w-full" data-testid="home-account-controls">
<button ref={trigger} type="button" data-testid="home-account-menu-trigger" <button ref={trigger} type="button" data-testid="home-account-menu-trigger"
aria-label={`打开账户菜单:${accountName}`} aria-haspopup="menu" aria-expanded={open} aria-controls="home-account-menu" aria-label={`打开账户菜单:${accountName}`} aria-haspopup="menu" aria-expanded={open} aria-controls="home-account-menu"
className="motion-press flex min-h-12 w-full items-center gap-3 rounded-2xl border border-border/80 bg-background px-4 py-2 text-left text-sm font-medium shadow-sm hover:bg-surface-subtle" className="group motion-press relative h-full w-full text-left"
onClick={() => setOpen(value => !value)} onClick={() => setOpen(value => !value)}
onKeyDown={event => { onKeyDown={event => {
if (event.key === 'ArrowDown' || event.key === 'ArrowUp') { if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
event.preventDefault(); setOpen(true); focusItem(event.key === 'ArrowUp'); event.preventDefault(); setOpen(true); focusItem(event.key === 'ArrowUp');
} }
}}> }}>
<UserAvatar src={avatarUrl} initial={initial} className="h-8 w-8 shrink-0" /> <span data-testid="home-account-trigger-surface"
<span className="min-w-0 flex-1 truncate">{accountName}</span> className="pointer-events-none absolute bottom-0 left-0 flex min-h-12 w-64 origin-bottom-left scale-50 items-center gap-3 rounded-2xl border border-border/80 bg-background px-4 py-2 text-sm font-medium shadow-sm group-hover:bg-surface-subtle">
<ChevronUp aria-hidden="true" className={`h-4 w-4 shrink-0 transition-transform ${open ? 'rotate-180' : ''}`} /> <UserAvatar src={avatarUrl} initial={initial} className="h-8 w-8 shrink-0" />
<span className="min-w-0 flex-1 truncate">{accountName}</span>
<ChevronUp aria-hidden="true" className={`h-4 w-4 shrink-0 transition-transform ${open ? 'rotate-180' : ''}`} />
</span>
</button> </button>
<DisclosureContent ref={menu} id="home-account-menu" open={open} role="menu" aria-label="账户菜单" <DisclosureContent ref={menu} id="home-account-menu" open={open} role="menu" aria-label="账户菜单"
data-testid="home-account-menu" data-testid="home-account-menu"
className="glass-surface absolute bottom-[calc(100%+0.5rem)] left-0 z-50 w-72 max-w-[calc(100vw-2rem)] rounded-2xl border border-border/80 p-3 text-foreground shadow-float" className="glass-surface absolute bottom-[2.0625rem] left-0 z-50 w-72 max-w-[calc(100vw-2rem)] rounded-2xl border border-border/80 p-3 text-foreground shadow-float"
innerClassName="max-h-[calc(100dvh-8rem)] overflow-y-auto" innerClassName="max-h-[calc(100dvh-8rem)] overflow-y-auto"
onKeyDown={event => { onKeyDown={event => {
if (!['ArrowDown', 'ArrowUp', 'Home', 'End'].includes(event.key)) return; if (!['ArrowDown', 'ArrowUp', 'Home', 'End'].includes(event.key)) return;
+1 -1
View File
@@ -116,7 +116,7 @@ export function ModuleSelection({ authRequired = true }: { authRequired?: boolea
})} })}
</section> </section>
</div> </div>
{authenticated && authUser && <footer className="relative shrink-0 pb-2" aria-label="账户"> {authenticated && authUser && <footer className="module-selection-account relative shrink-0 pb-2" aria-label="账户">
<HomeAccountMenu key={authUser.userId ?? authUser.username} accountName={userDisplayName || authUser.username} <HomeAccountMenu key={authUser.userId ?? authUser.username} accountName={userDisplayName || authUser.username}
avatarUrl={userProfile?.avatarUrl} onOpenProfile={() => { avatarUrl={userProfile?.avatarUrl} onOpenProfile={() => {
setDismissedSyncError(null); setProfileDialogOpen(true); setDismissedSyncError(null); setProfileDialogOpen(true);
+17
View File
@@ -424,10 +424,25 @@
} }
.module-selection-title { .module-selection-title {
width: fit-content;
margin-inline: auto;
margin-top: 0.75rem; margin-top: 0.75rem;
font-size: clamp(2rem, 3.3vw, 3.75rem); font-size: clamp(2rem, 3.3vw, 3.75rem);
} }
.module-selection-account [data-testid='home-account-controls'] {
transform: translateY(1rem);
}
@media (min-width: 640px) {
.module-selection-account [data-testid='home-account-controls'] {
transform: translate(
max(-1.5cm, calc(1rem - max(2rem, calc((100vw - 80rem) / 2)))),
2rem
);
}
}
.module-option-card { .module-option-card {
position: relative; position: relative;
z-index: 0; z-index: 0;
@@ -547,6 +562,7 @@
.module-selection-intro { .module-selection-intro {
text-align: left; text-align: left;
transform: translateX(1.5cm);
} }
.module-selection-welcome { .module-selection-welcome {
@@ -556,6 +572,7 @@
} }
.module-selection-title { .module-selection-title {
margin-inline: 0;
margin-top: 0; margin-top: 0;
} }
+40 -4
View File
@@ -100,6 +100,27 @@ test.describe('Makelore module navigation without setup flow', () => {
await page.goto(applicationUrl); await page.goto(applicationUrl);
const trigger = page.getByTestId('home-account-menu-trigger'); const trigger = page.getByTestId('home-account-menu-trigger');
const menu = page.getByTestId('home-account-menu'); const menu = page.getByTestId('home-account-menu');
const surface = page.getByTestId('home-account-trigger-surface');
const checkAccountGeometry = async (width: number) => {
await expect.poll(async () => (await menu.boundingBox())!.width).toBeCloseTo(288, 1);
const visible = (await surface.boundingBox())!;
const target = (await trigger.boundingBox())!;
const avatar = (await surface.locator('span').first().boundingBox())!;
const panel = (await menu.boundingBox())!;
expect(visible.width).toBeCloseTo(128, 1);
expect(visible.height).toBeCloseTo(25, 1);
expect(avatar.width).toBeCloseTo(16, 1);
expect(avatar.height).toBeCloseTo(16, 1);
expect(target.width).toBeCloseTo(128, 1);
expect(target.height).toBeCloseTo(50, 1);
expect(visible.x).toBeGreaterThanOrEqual(16);
expect(visible.x).toBeCloseTo(width === 1440 ? 80 - 1.5 * 96 / 2.54 : 16, 1);
expect(await page.evaluate(() => innerHeight) - visible.y - visible.height).toBeCloseTo(16, 0);
expect(panel.x).toBeCloseTo(visible.x, 1);
expect(visible.y - panel.y - panel.height).toBeCloseTo(8, 1);
expect(panel.y).toBeGreaterThanOrEqual(0);
expect(panel.x + panel.width).toBeLessThanOrEqual(width);
};
await expect(trigger).toContainText('小麦'); await expect(trigger).toContainText('小麦');
await expect(page.locator('[data-testid^="ai-module-option-"]')).toHaveCount(3); await expect(page.locator('[data-testid^="ai-module-option-"]')).toHaveCount(3);
const footer = await trigger.evaluate(element => element.closest('footer')!.getBoundingClientRect().top); const footer = await trigger.evaluate(element => element.closest('footer')!.getBoundingClientRect().top);
@@ -109,6 +130,7 @@ test.describe('Makelore module navigation without setup flow', () => {
await expect(menu.getByTestId('sidebar-total-token-points')).toHaveText('75 点'); await expect(menu.getByTestId('sidebar-total-token-points')).toHaveText('75 点');
await expect(menu.getByText('永久有效 · 预占 5 点', { exact: true })).toBeVisible(); await expect(menu.getByText('永久有效 · 预占 5 点', { exact: true })).toBeVisible();
await expect(menu.getByRole('menuitem', { name: '词元点数与充值', exact: true })).toBeVisible(); await expect(menu.getByRole('menuitem', { name: '词元点数与充值', exact: true })).toBeVisible();
await checkAccountGeometry(1440);
await page.screenshot({ path: testInfo.outputPath('home-account-menu-desktop.png') }); await page.screenshot({ path: testInfo.outputPath('home-account-menu-desktop.png') });
await page.keyboard.press('Escape'); await page.keyboard.press('Escape');
await expect(trigger).toHaveAttribute('aria-expanded', 'false'); await expect(trigger).toHaveAttribute('aria-expanded', 'false');
@@ -145,9 +167,7 @@ test.describe('Makelore module navigation without setup flow', () => {
await trigger.click(); await trigger.click();
await expect(menu.getByTestId('sidebar-total-token-points')).toHaveText('75 点'); await expect(menu.getByTestId('sidebar-total-token-points')).toHaveText('75 点');
await expect.poll(() => page.evaluate(() => document.documentElement.scrollWidth - innerWidth)).toBeLessThanOrEqual(1); await expect.poll(() => page.evaluate(() => document.documentElement.scrollWidth - innerWidth)).toBeLessThanOrEqual(1);
const menuBox = await menu.boundingBox(); await checkAccountGeometry(375);
expect(menuBox!.x).toBeGreaterThanOrEqual(0);
expect(menuBox!.x + menuBox!.width).toBeLessThanOrEqual(375);
await page.screenshot({ path: testInfo.outputPath('home-account-menu-375.png') }); await page.screenshot({ path: testInfo.outputPath('home-account-menu-375.png') });
await menu.getByRole('menuitem', { name: '退出登录', exact: true }).click(); await menu.getByRole('menuitem', { name: '退出登录', exact: true }).click();
await expect(page).toHaveURL(/\/login$/); await expect(page).toHaveURL(/\/login$/);
@@ -371,9 +391,25 @@ test.describe('Makelore module navigation without setup flow', () => {
expect(card.imageHeight).toBeGreaterThan(card.height * 0.75); expect(card.imageHeight).toBeGreaterThan(card.height * 0.75);
expect(card.contentLeft).toBeGreaterThanOrEqual(card.imageLeft + card.imageWidth - 1); expect(card.contentLeft).toBeGreaterThanOrEqual(card.imageLeft + card.imageWidth - 1);
} }
const heading = await page.getByRole('heading', { name: '今天,想创作什么?', exact: true }).boundingBox(); const title = page.getByRole('heading', { name: '今天,想创作什么?', exact: true });
const heading = await title.boundingBox();
expect(heading).not.toBeNull(); expect(heading).not.toBeNull();
const textWidth = await title.evaluate(element => {
const range = document.createRange(); range.selectNodeContents(element);
return range.getBoundingClientRect().width;
});
expect(Math.abs(heading!.width - textWidth)).toBeLessThan(1);
if (width >= 1024) { if (width >= 1024) {
const shift = await title.evaluate(element => {
const intro = element.parentElement!;
const original = intro.style.transform;
const shiftedLeft = element.getBoundingClientRect().left;
try {
intro.style.transform = 'none';
return shiftedLeft - element.getBoundingClientRect().left;
} finally { intro.style.transform = original; }
});
expect(shift).toBeCloseTo(1.5 * 96 / 2.54, 1);
expect(heading!.x + heading!.width).toBeLessThanOrEqual(cards[0]!.left); expect(heading!.x + heading!.width).toBeLessThanOrEqual(cards[0]!.left);
expect(Math.abs(heading!.y + heading!.height / 2 - cards[1]!.top - cards[1]!.height / 2)).toBeLessThan(1); expect(Math.abs(heading!.y + heading!.height / 2 - cards[1]!.top - cards[1]!.height / 2)).toBeLessThan(1);
expect(await moduleCards.evaluateAll(elements => elements.every(element => { expect(await moduleCards.evaluateAll(elements => elements.every(element => {