From c17af0fd01f8ad90fc83467b96bf6c18ff38f57f Mon Sep 17 00:00:00 2001 From: bboysoul Date: Sat, 19 Sep 2026 17:41:50 +0800 Subject: [PATCH] fix: refine Today mobile hierarchy --- frontend/e2e/today-plain-list.spec.ts | 39 +++++++++++++++++++++------ frontend/src/App.vue | 10 ++++--- frontend/src/TodayEnvironment.test.ts | 18 ++++++++----- frontend/src/style.css | 15 ++++++++--- 4 files changed, 61 insertions(+), 21 deletions(-) diff --git a/frontend/e2e/today-plain-list.spec.ts b/frontend/e2e/today-plain-list.spec.ts index a16f2a5..9a13694 100644 --- a/frontend/e2e/today-plain-list.spec.ts +++ b/frontend/e2e/today-plain-list.spec.ts @@ -51,8 +51,10 @@ test('Today plain-list layout matches the approved responsive geometry', async ( })).toEqual({ media720: width <= 720, contentWidth: width >= 1440 ? 1080 : width === 721 ? 630 : width - 58, - environmentHeight: width >= 721 ? 55 : width === 375 ? 90 : 93, + environmentHeight: width >= 721 ? 55 : width === 375 ? 79 : 81, }) + await expect(page.locator('.today-heading')).toHaveCount(1) + await expect(page.locator('.today-inline-add')).toHaveCount(1) await expect(taskRowLocator).toHaveCount(1) await expect(habitRowLocator).toHaveCount(1) await expect(page.locator('.today-environment')).toBeVisible() @@ -69,6 +71,8 @@ test('Today plain-list layout matches the approved responsive geometry', async ( const remaining = document.querySelector('.today-remaining')! const filter = document.querySelector('.today-inline-filter')! const firstSection = document.querySelector('.today-section-toggle')! + const heading = document.querySelector('.today-heading')! + const inlineAdd = document.querySelector('.today-inline-add')! const main = document.querySelector('main.today-main')! const content = rect('.today-context') const sectionTitle = firstSection.querySelector('.today-section-title')! @@ -84,8 +88,8 @@ test('Today plain-list layout matches the approved responsive geometry', async ( })) const taskRow = document.querySelector(`#today-tasks .task-row[data-task-id="${taskId}"]`)! const habitRow = document.querySelector(`.today-habit-row[data-habit-id="${habitId}"]`)! - const fab = document.querySelector('.unified-fab')! - const orderedElements = [environment, title, remaining, filter, firstSection] + const fab = document.querySelector('.unified-fab') + const orderedElements = [environment, heading, firstSection] const ordered = orderedElements.map((element) => { const box = element.getBoundingClientRect() return { left: box.left, right: box.right, top: box.top, bottom: box.bottom, width: box.width, height: box.height } @@ -136,6 +140,15 @@ test('Today plain-list layout matches the approved responsive geometry', async ( summaries, styles: { mainPaddingLeft: getComputedStyle(main).paddingLeft, + contextHeight: content.height, + headingDisplay: getComputedStyle(heading).display, + headingAlignItems: getComputedStyle(heading).alignItems, + filterTop: filter.getBoundingClientRect().top, + filterRight: filter.getBoundingClientRect().right, + inlineAddPosition: getComputedStyle(inlineAdd).position, + inlineAddWidth: inlineAdd.getBoundingClientRect().width, + inlineAddBottom: inlineAdd.getBoundingClientRect().bottom, + viewportHeight: innerHeight, titleFontSize: getComputedStyle(title).fontSize, titleFontWeight: getComputedStyle(title).fontWeight, filterWidth: filter.getBoundingClientRect().width, @@ -154,8 +167,8 @@ test('Today plain-list layout matches the approved responsive geometry', async ( habitHeight: habitRow.getBoundingClientRect().height, habitBackground: getComputedStyle(habitRow).backgroundColor, habitBorderBottom: getComputedStyle(habitRow).borderBottomWidth, - fabShadow: getComputedStyle(fab).boxShadow, - fabIconWidth: fab.querySelector('svg')?.getBoundingClientRect().width, + fabShadow: fab ? getComputedStyle(fab).boxShadow : '', + fabIconWidth: fab?.querySelector('svg')?.getBoundingClientRect().width ?? 0, }, } }, { taskId, habitId }) @@ -188,8 +201,18 @@ test('Today plain-list layout matches the approved responsive geometry', async ( expect(metrics.styles.filterVisualHeight).toBeLessThanOrEqual(36) expect(metrics.styles.filterBackground).toBe('rgba(0, 0, 0, 0)') expect(metrics.styles.filterBorderRadius).toBe('0px') - expect(metrics.styles.fabShadow).toContain('8px 18px') - expect(metrics.styles.fabIconWidth).toBeCloseTo(30, 0) + expect(metrics.styles.headingDisplay).toBe('grid') + expect(metrics.styles.headingAlignItems).toBe('center') + expect(metrics.styles.filterTop).toBeGreaterThanOrEqual(metrics.ordered[1].top) + expect(metrics.styles.filterTop).toBeLessThanOrEqual(metrics.ordered[1].bottom - metrics.styles.filterHeight) + expect(metrics.styles.filterRight).toBeCloseTo(metrics.content.right, 0) + expect(metrics.styles.inlineAddPosition).toBe('relative') + expect(metrics.styles.inlineAddWidth).toBeCloseTo(metrics.content.width, 0) + expect(metrics.styles.inlineAddBottom).toBeGreaterThanOrEqual(metrics.ordered[metrics.ordered.length - 1].bottom) + expect(metrics.styles.inlineAddBottom).toBeLessThanOrEqual(metrics.styles.viewportHeight + 1) + if (width <= 720) expect(metrics.styles.contextHeight).toBeLessThanOrEqual(210) + expect(metrics.styles.fabShadow).toBe('') + expect(metrics.styles.fabIconWidth).toBe(0) if (width >= 721) { expect(metrics.uniqueRows).toHaveLength(1) expect(metrics.content.width).toBeCloseTo(width >= 1440 ? 1080 : 630, 0) @@ -214,7 +237,7 @@ test('Today plain-list layout matches the approved responsive geometry', async ( expect(metrics.content.left).toBeCloseTo(29, 0) expect(metrics.styles.mainPaddingLeft).toBe('29px') expect(metrics.styles.titleFontSize).toBe('24px') - expect(metrics.environment.height).toBeCloseTo(width === 375 ? 90 : 93, 0) + expect(metrics.environment.height).toBeCloseTo(width === 375 ? 79 : 81, 0) } } if (width >= 721) { diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 807f494..b61cb07 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -1708,9 +1708,10 @@ onUnmounted(() => { + @@ -1789,7 +1791,7 @@ onUnmounted(() => { - +

{{ taskComposeTitle }}

diff --git a/frontend/src/TodayEnvironment.test.ts b/frontend/src/TodayEnvironment.test.ts index 2101aef..9ad3a5a 100644 --- a/frontend/src/TodayEnvironment.test.ts +++ b/frontend/src/TodayEnvironment.test.ts @@ -49,6 +49,7 @@ describe('Today environment integration', () => { it('renders the approved plain-list composition without a board or progress tracks', () => { expect(app).toContain("
') + expect(app).toContain('
') expect(app).toContain('

还有 {{ todayTaskRemaining + todayHabitRemaining }} 项待完成

') expect(app).not.toContain('class="today-board"') expect(app).not.toContain('today-board-title') @@ -57,29 +58,34 @@ describe('Today environment integration', () => { expect(app).not.toContain('today-habit-track') expect(app).not.toContain('role="progressbar"') expect(app).toContain("'today-active': activeView==='today'") - expect(css).toContain('.shell.today-active .unified-fab{box-shadow:0 8px 18px rgba(174,65,29,.22)}') - expect(css).not.toContain('\n.unified-fab{box-shadow:0 8px 18px rgba(174,65,29,.22)}') + expect(app).toContain('class="today-inline-add"') + expect(app).toContain("activeView!=='today' && ['tasks','upcoming','habits','countdowns','memos'].includes(activeView)") + expect(css).toContain('.today-inline-add{') + expect(css).toContain('position:relative') expect(css).toContain('main.today-main{padding-left:max(44px,calc((100% - 1080px)/2));padding-right:max(44px,calc((100% - 1080px)/2))}') expect(css).toContain('.today-environment{grid-template-columns:repeat(3,minmax(0,1fr));') expect(css).toContain('@media(max-width:930px){main.today-main{padding-left:max(44px,calc((100% - 630px)/2));padding-right:max(44px,calc((100% - 630px)/2))}}') - expect(css).toContain('@media(max-width:720.98px){main.today-main{padding-left:29px!important;padding-right:29px!important}') + expect(css).toContain('@media(max-width:720.98px){main.today-main{padding-left:29px!important;padding-right:29px!important;padding-bottom:calc(78px + var(--safe-area-bottom))}') expect(css).toContain('.today-context{margin:0 0 14px;border-bottom:0}') - expect(css).toContain('.today-remaining{margin:0 0 24px;color:var(--muted);font-size:13px}') + expect(css).toContain('.today-heading{display:grid;') + expect(css).toContain('.today-heading .today-remaining{margin:6px 0 0}') expect(css).not.toContain('.today-board{') expect(css).not.toContain('.today-board__progress{') expect(css).not.toContain('.today-track{') expect(css).not.toContain('.today-track-rail{') }) - it('renders the approved plain-list DOM in prototype order without duplicating the Today title', () => { + it('renders the approved plain-list DOM with the Today title and filter in one heading row', () => { const main = app.slice(app.indexOf('
')) const environment = main.indexOf('') const title = main.indexOf('

今天

') const remaining = main.indexOf('

') const filter = main.indexOf('') const overdue = main.indexOf('id="today-overdue-heading"') expect(environment).toBeGreaterThan(-1) - expect(title).toBeGreaterThan(environment) + expect(heading).toBeGreaterThan(environment) + expect(title).toBeGreaterThan(heading) expect(remaining).toBeGreaterThan(title) expect(filter).toBeGreaterThan(remaining) expect(overdue).toBeGreaterThan(filter) diff --git a/frontend/src/style.css b/frontend/src/style.css index a18a619..596ff6e 100644 --- a/frontend/src/style.css +++ b/frontend/src/style.css @@ -152,7 +152,16 @@ main.list-main>.mvp-view>.habit-archive-section{border-top:1px solid #e8e0d5} @media(max-width:720px){main.list-main{padding-left:29px;padding-right:29px}main.list-main>.list-page-context,main.list-main>.list-search-reveal,main.list-main>.list-section-heading,main.list-main>.task-list,main.list-main>.list-page-meta,main.list-main>.pager,main.list-main>.mvp-view{width:100%}.list-page-title{font-size:24px}.list-search-reveal>.task-search-toggle{display:grid;position:relative;justify-self:end}.list-search-reveal:not(.mobile-search-open){min-height:0}.list-search-reveal:not(.mobile-search-open)>.list-search,.list-search-reveal:not(.mobile-search-open)>.list-search-clear{display:none}.list-search kbd{display:none}} /* Approved Today plain-list prototype parity. */ -main.today-main .topbar{margin-bottom:18px} +main.today-main .topbar{margin-bottom:12px} +.today-heading{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;column-gap:16px;margin-top:20px} +.today-heading>div{min-width:0} +.today-heading .today-page-title{margin:0} +.today-heading .today-remaining{margin:6px 0 0} +.today-heading .today-inline-filter{align-self:center;justify-self:end} +.today-inline-add{position:relative;width:100%;height:44px;margin:18px auto 0;display:flex;align-items:center;justify-content:center;gap:7px;border:1px solid #dfc9b2;border-radius:11px;background:var(--surface-raised);color:var(--accent);box-shadow:var(--highlight-inner),0 5px 16px rgba(88,67,42,.1);font-size:13px;font-weight:700} +.today-inline-add svg{width:17px;height:17px} +.today-inline-add:active{transform:scale(.985)} +.today-inline-add:focus-visible{outline:3px solid var(--focus-ring);outline-offset:2px} .today-environment{grid-template-columns:repeat(3,minmax(0,1fr));align-items:center;gap:0;padding:0 0 18px;border-bottom:1px solid #e8e0d5;color:#8a8178} .today-environment__item{height:36px;padding:0 0 0 18px;justify-content:flex-start} .today-environment__item:first-child{padding-left:0} @@ -166,8 +175,8 @@ main.today-main .topbar{margin-bottom:18px} .today-main .task-tail{padding-left:12px;font-size:12px}.today-main .habit-row{gap:0}.today-main .habit-main{display:flex;align-items:center;min-width:0;padding:0} .shell.today-active .unified-fab{box-shadow:0 8px 18px rgba(174,65,29,.22)}.shell.today-active .unified-fab>svg{width:30px;height:30px;stroke-width:1.5} @media(max-width:930px){main.today-main{padding-left:max(44px,calc((100% - 630px)/2));padding-right:max(44px,calc((100% - 630px)/2))}} -@media(max-width:720.98px){main.today-main{padding-left:29px!important;padding-right:29px!important}.today-environment{grid-template-columns:minmax(0,1fr) minmax(0,1fr);grid-template-rows:21px auto;align-items:stretch;column-gap:0;row-gap:11px;padding-bottom:14px}.today-environment__calendar{grid-column:1/-1;grid-row:1;height:21px!important;padding:0;justify-content:space-between;align-items:start;border-left:0}.today-environment__weather,.today-environment__gold{grid-row:2;height:clamp(42px,calc(25vw - 51.75px),45.75px);padding-top:10px;border-top:1px solid #e8e0d5}.today-environment__weather{grid-column:1;padding-left:0;padding-right:10px;border-left:0}.today-environment__gold{grid-column:2;padding-left:12px}.today-environment__weather strong,.today-environment__gold strong,.today-environment__gold-primary{line-height:16px}.today-environment__item small{line-height:13px}.today-page-title{margin-top:23px;font-size:24px}.today-main .task-row,.today-main .habit-row{height:58px;min-height:58px;max-height:58px}.today-context .completed-filter-pill{width:87px;height:44px;min-height:44px}.today-context .completed-filter-pill__track{width:30px;height:18px}.today-context .completed-filter-pill__thumb{width:14px;height:14px}.today-context .completed-filter-pill[aria-checked="true"] .completed-filter-pill__thumb{transform:translateX(12px)}} -@media(max-width:380px){.today-environment__weather,.today-environment__gold{height:43px;padding-top:7px;padding-bottom:7px}} +@media(max-width:720.98px){main.today-main{padding-left:29px!important;padding-right:29px!important;padding-bottom:calc(78px + var(--safe-area-bottom))}.today-environment{grid-template-columns:minmax(0,1fr) minmax(0,1fr);grid-template-rows:21px auto;align-items:stretch;column-gap:0;row-gap:7px;padding-bottom:10px}.today-environment__calendar{grid-column:1/-1;grid-row:1;height:21px!important;padding:0;justify-content:space-between;align-items:start;border-left:0}.today-environment__weather,.today-environment__gold{grid-row:2;height:clamp(38px,calc(25vw - 55.75px),42px);padding-top:7px;border-top:1px solid #e8e0d5}.today-environment__weather{grid-column:1;padding-left:0;padding-right:10px;border-left:0}.today-environment__gold{grid-column:2;padding-left:12px}.today-environment__weather strong,.today-environment__gold strong,.today-environment__gold-primary{line-height:16px}.today-environment__item small{line-height:13px}.today-heading{margin-top:16px}.today-page-title{font-size:24px}.today-heading .today-remaining{margin-top:4px}.today-main .task-row,.today-main .habit-row{height:58px;min-height:58px;max-height:58px}.today-context .completed-filter-pill{width:87px;height:44px;min-height:44px}.today-context .completed-filter-pill__track{width:30px;height:18px}.today-context .completed-filter-pill__thumb{width:14px;height:14px}.today-context .completed-filter-pill[aria-checked="true"] .completed-filter-pill__thumb{transform:translateX(12px)}.today-inline-add{margin-top:14px}} +@media(max-width:380px){.today-environment__weather,.today-environment__gold{height:40px;padding-top:5px;padding-bottom:5px}} /* Approved Settings 01: continuous paper ledger. */ main:has(>.mvp-view .settings-sections){background:#fffdf8}