feat: spacing tokens, actionable failure copy, unified focus ring, 44px touch targets, press feedback
ci / gitleaks (push) Successful in 8s
ci / docker (push) Successful in 3m36s

This commit is contained in:
2026-09-23 06:45:51 +08:00
parent 058f353965
commit 629e054823
25 changed files with 251 additions and 181 deletions
+29 -29
View File
@@ -79,10 +79,10 @@ describe('unified task due display', () => {
describe('approved cream solid button system', () => {
it('gives semantic action buttons a compact desktop size and a touch-safe mobile size', () => {
expect(css).toContain(':is(.primary,.primary-small,.soft-button,.secondary,.restore,.file-button,.danger-button){min-height:40px;display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:0 15px;border-radius:var(--radius-control);font-size:13px;font-weight:700;line-height:1;white-space:nowrap;transition:transform .14s ease,background-color .14s ease,border-color .14s ease,box-shadow .14s ease,color .14s ease}')
expect(css).toContain(':is(.primary,.primary-small,.soft-button,.secondary,.restore,.file-button,.danger-button){min-height:40px;display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:0 var(--space-14);border-radius:var(--radius-control);font-size:13px;font-weight:700;line-height:1;white-space:nowrap;transition:transform .14s ease,background-color .14s ease,border-color .14s ease,box-shadow .14s ease,color .14s ease}')
expect(css).toContain(':is(.primary,.primary-small,.soft-button,.secondary,.restore,.file-button,.danger-button)>svg{width:16px;height:16px;flex:0 0 auto}')
expect(css).toContain('@media(min-width:931px){main:has(>.mvp-view .settings-sections) :is(.soft-button,.primary-small,.danger-button,.file-button),.numeric-action .soft-button,.habit-history__message .soft-button,.backup-preflight .danger-button,.app-sheet__danger :is(.soft-button,.primary-small,.danger-button,.file-button,.secondary,.restore),.purge-list-dialog :is(.secondary,.danger-button){min-height:40px}}')
expect(css).toContain('@media(max-width:930px){:is(.primary,.primary-small,.soft-button,.secondary,.restore,.file-button,.danger-button){min-height:44px;padding-inline:16px;gap:7px}:is(.primary,.primary-small,.soft-button,.secondary,.restore,.file-button,.danger-button)>svg{width:17px;height:17px}}')
expect(css).toContain('@media(max-width:930px){:is(.primary,.primary-small,.soft-button,.secondary,.restore,.file-button,.danger-button){min-height:44px;padding-inline:var(--space-16);gap:7px}:is(.primary,.primary-small,.soft-button,.secondary,.restore,.file-button,.danger-button)>svg{width:17px;height:17px}}')
expect(css).toContain(':is(.primary,.primary-small){border:1px solid var(--accent);background:var(--accent);color:var(--white);box-shadow:inset 0 1px 0 rgba(255,255,255,.36),0 4px 10px rgba(241,90,41,.20)}')
expect(css).toContain(':is(.soft-button,.secondary,.restore,.file-button){border:1px solid var(--soft-button-border-2);background:var(--row-hover);color:var(--soft-button-ink);box-shadow:inset 0 1px 0 var(--white),0 2px 5px rgba(83,62,42,.06)}')
expect(css).toContain('.danger-button{border:1px solid var(--danger-button-border-3);background:var(--danger-btn-bg);color:var(--danger-button-ink);box-shadow:inset 0 1px 0 var(--white),0 2px 5px rgba(105,48,34,.05)}')
@@ -171,15 +171,15 @@ describe('approved Settings 01 paper ledger', () => {
it('uses the selected continuous-paper geometry on desktop and mobile', () => {
expect(css).toContain('main:has(>.mvp-view .settings-sections){background:var(--surface-raised)}')
expect(css).toContain('.settings-sections{width:min(100%,900px);margin:0 auto;display:grid;gap:0;padding:34px 0 64px}')
expect(css).toContain('.settings-sections{width:min(100%,900px);margin:0 auto;display:grid;gap:0;padding:var(--space-32) 0 64px}')
expect(css).toContain('.settings-heading h1{font-size:34px;line-height:1.1;font-weight:700;')
expect(css).toContain('.settings-group{min-width:0;background:transparent;border:0;border-radius:0;box-shadow:none;overflow:visible;padding-top:25px}')
expect(css).toContain('.settings-group{min-width:0;background:transparent;border:0;border-radius:0;box-shadow:none;overflow:visible;padding-top:var(--space-24)}')
expect(css).toContain('.settings-group>header{height:44px;padding:0;display:flex;align-items:center;border-bottom:1px solid var(--border-hairline)}')
expect(css).toContain('.settings-row{min-height:62px;padding:6px 0;border-top:0;border-bottom:1px solid var(--border-hairline);')
expect(css).toContain('.settings-row{min-height:62px;padding:var(--space-6) 0;border-top:0;border-bottom:1px solid var(--border-hairline);')
expect(css).toContain('.settings-topbar{margin-bottom:0}')
expect(css).not.toContain('.topbar>.settings-refresh{grid-area:filter}')
expect(css).toContain('@media(max-width:720px){main:has(>.mvp-view .settings-sections){padding-left:29px;padding-right:29px;padding-bottom:calc(102px + env(safe-area-inset-bottom))}')
expect(css).toContain('.settings-sections{width:100%;padding-top:23px;padding-bottom:0}')
expect(css).toContain('@media(max-width:720px){main:has(>.mvp-view .settings-sections){padding-left:var(--space-28);padding-right:var(--space-28);padding-bottom:calc(102px + env(safe-area-inset-bottom))}')
expect(css).toContain('.settings-sections{width:100%;padding-top:var(--space-24);padding-bottom:0}')
expect(css).toContain('.settings-heading h1{font-size:24px;line-height:1.1;font-weight:700}')
expect(css).toContain('.settings-row{min-height:64px}')
})
@@ -391,12 +391,12 @@ describe('approved UI detail direction', () => {
it('matches the approved paper-flow geometry and material', () => {
expect(css).toContain('.shell.detail-open{grid-template-columns:var(--sidebar-width,236px) minmax(330px,1fr) var(--detail-width,350px)}')
expect(css).toContain('@media(max-width:1050px) and (min-width:931px){main{padding-inline:24px}')
expect(css).toContain('@media(max-width:1050px) and (min-width:931px){main{padding-inline:var(--space-24)}')
expect(css).toContain('.detail{min-width:0;border-left:1px solid var(--border-cream);background:var(--surface-raised);overflow:hidden;display:flex;flex-direction:column}')
expect(css).toContain('.detail-head{height:58px;flex:0 0 58px;display:flex;align-items:center;justify-content:space-between;padding:0 18px;')
expect(css).toContain('.detail-form{min-width:0;border:0;margin:0;grid-template-columns:minmax(0,1fr);padding:18px;display:grid;gap:18px;overflow-y:auto;')
expect(css).toContain('.detail-head{height:58px;flex:0 0 58px;display:flex;align-items:center;justify-content:space-between;padding:0 var(--space-18);')
expect(css).toContain('.detail-form{min-width:0;border:0;margin:0;grid-template-columns:minmax(0,1fr);padding:var(--space-18);display:grid;gap:18px;overflow-y:auto;')
expect(css).toContain('.detail-title textarea{min-width:0;min-height:62px;padding:0;border:0;background:transparent;box-shadow:none;resize:none;outline:none;font-size:20px;line-height:27px;')
expect(css).toContain('.task-detail-arrangement{display:grid;gap:13px;padding-bottom:18px;border-bottom:1px solid var(--border-section)}')
expect(css).toContain('.task-detail-arrangement{display:grid;gap:13px;padding-bottom:var(--space-18);border-bottom:1px solid var(--border-section)}')
expect(css).toContain('.task-detail-date-time{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:10px}')
expect(css).toContain('.task-detail-field-input{width:100%!important;min-height:44px;border-radius:var(--radius-control)}')
expect(css).toContain('.detail-actions{position:sticky;bottom:0;z-index:3;min-height:69px;')
@@ -420,7 +420,7 @@ describe('approved UI detail direction', () => {
it('keeps custom recurrence controls at 44px without overflowing narrow screens', () => {
expect(css).toContain('.repeat-custom-fields input,.repeat-custom-fields select{min-height:44px;')
expect(css).toContain('.weekday-picker label{width:44px;height:44px;')
expect(css).toContain('@media(max-width:930px){.repeat-custom-fields{padding:10px;gap:8px}')
expect(css).toContain('@media(max-width:930px){.repeat-custom-fields{padding:var(--space-10);gap:8px}')
expect(css).toContain('.repeat-custom-fields>div,.repeat-custom-fields>label{min-width:0;flex-wrap:wrap;gap:6px}')
})
@@ -484,7 +484,7 @@ describe('completion feedback motion', () => {
})
it('stacks the desktop calendar copy to align with weather and gold', () => {
expect(css).toContain('@media(min-width:721px){.today-environment__calendar{flex-direction:column;align-items:flex-start;gap:0}.today-environment__calendar small{margin-top:2px;font-size:11px}}')
expect(css).toContain('@media(min-width:721px){.today-environment__calendar{flex-direction:column;align-items:flex-start;gap:0}.today-environment__calendar small{margin-top:var(--space-2);font-size:11px}}')
})
it('uses the approved internal-standard login composition and material tokens', () => {
@@ -495,12 +495,12 @@ describe('completion feedback motion', () => {
expect(app).toContain('<p v-if="initialized" class="auth-footnote">登录后继续你的清单</p>')
expect(app).toContain('<form class="auth-card" aria-labelledby="auth-title" @submit.prevent="submitAuth">')
expect(app).toContain('<button type="submit" class="primary">')
expect(css).toMatch(/\.auth-card\{[^}]*width:min\(400px,100%\)[^}]*padding:36px[^}]*border:1px solid var\(--border-cream\)[^}]*border-radius:var\(--radius-card\)/)
expect(css).toMatch(/\.auth-card\{[^}]*width:min\(400px,100%\)[^}]*padding:var\(--space-36\)[^}]*border:1px solid var\(--border-cream\)[^}]*border-radius:var\(--radius-card\)/)
expect(css).toContain('.auth-brand .brand{gap:9px;font-size:28px;letter-spacing:-2px}')
expect(css).toContain('.auth-brand .brand-logo{width:34px;height:34px}')
expect(css).toMatch(/\.auth-card input\{[^}]*min-height:46px[^}]*background:var\(--surface-raised\)/)
expect(css).toContain('.auth-card>.primary{min-height:46px}')
expect(css).toMatch(/@media\(max-width:930px\)\{\.auth-shell\{padding:18px\}\.auth-card\{padding:30px 24px\}/)
expect(css).toMatch(/@media\(max-width:930px\)\{\.auth-shell\{padding:var\(--space-18\)\}\.auth-card\{padding:var\(--space-28\) var\(--space-24\)\}/)
})
it('syncs browser IANA timezone before loading user task data', () => {
@@ -526,8 +526,8 @@ describe('completion feedback motion', () => {
expect(css).toContain('.markdown-toolbar{display:flex;')
expect(css).toContain('overflow-x:auto')
expect(css).toContain('.markdown-toolbar button{min-width:44px;min-height:44px;')
expect(css).toContain('.markdown-preview>.contains-task-list{list-style:none;padding-left:3px}')
expect(css).toContain('.markdown-preview .contains-task-list .contains-task-list{padding-left:24px}')
expect(css).toContain('.markdown-preview>.contains-task-list{list-style:none;padding-left:var(--space-2)}')
expect(css).toContain('.markdown-preview .contains-task-list .contains-task-list{padding-left:var(--space-24)}')
})
it('keeps all task detail changes on the unified save button', () => {
@@ -545,7 +545,7 @@ describe('completion feedback motion', () => {
it('keeps the desktop task composer compact and centered', () => {
expect(css).toContain('@media(min-width:931px){.task-compose-sheet:not(.habit-compose-sheet){width:min(560px,calc(100vw - 48px));height:min(520px,82dvh);max-height:none;padding:0;display:flex;flex-direction:column;gap:0;overflow:hidden}')
expect(css).toContain('.app-sheet-mask:has(>.task-compose-sheet:not(.habit-compose-sheet)){place-items:center;padding:24px}')
expect(css).toContain('.app-sheet-mask:has(>.task-compose-sheet:not(.habit-compose-sheet)){place-items:center;padding:var(--space-24)}')
})
it('keeps all task composer touch controls at least 44px on mobile without horizontal overflow', () => {
@@ -1072,7 +1072,7 @@ describe('task and habit row decoration', () => {
const mobileLayout = css.slice(css.indexOf('@media(max-width:930px){.shell'))
expect(mobileLayout).toContain('.completed-filter-pill{min-width:138px;height:44px')
expect(css).not.toContain('width:min(260px,100%)')
expect(css).not.toContain('.today-completed-toolbar{margin-top:8px}')
expect(css).not.toContain('.today-completed-toolbar{margin-top:var(--space-8)}')
expect(css).not.toContain('.habit-toolbar{')
})
@@ -1368,7 +1368,7 @@ describe('task detail layout', () => {
expect(app).toContain('v-model="selectedDueDate" class="task-detail-due-input task-detail-field-input"')
expect(app.match(/class="task-detail-field-input"/g)).toHaveLength(2)
expect(css).toContain('.task-detail-field-input{width:100%!important;min-height:44px;border-radius:var(--radius-control)}')
expect(css).toContain('.task-detail-due-input{padding-inline:9px!important}')
expect(css).toContain('.task-detail-due-input{padding-inline:var(--space-8)!important}')
})
})
@@ -1432,7 +1432,7 @@ describe('unified floating add interaction', () => {
const dueRemovalBlock = saveBlock.slice(saveBlock.indexOf('if (!taskSaved.due_at) {'), saveBlock.indexOf('} else {'))
expect(dueRemovalBlock).not.toContain('saveRepeat(')
expect(saveBlock).toContain("selectedRepeatError.value = ''")
expect(saveBlock).toContain("selectedRepeatError.value = reason instanceof Error ? reason.message : '保存失败'")
expect(saveBlock).toContain("selectedRepeatError.value = failureHint('保存失败', reason)")
expect(saveBlock.indexOf("toast('已保存')")).toBeGreaterThan(saveBlock.indexOf('await saveRepeat(taskSaved, repeatValue, repeatConfig, afterCompletionDays, recurrence)'))
expect(saveBlock).toContain('if (savingSelectedTask.value || recurrenceLoading.value) return')
expect(saveBlock).toContain('savingSelectedTask.value = true')
@@ -1614,8 +1614,8 @@ describe('sidebar information hierarchy', () => {
expect(archiveListBlock).toContain("confirmAction(`归档清单「${item.name}」?`, '任务会保留,可从“已归档”恢复')")
expect(archiveListBlock).not.toContain("askText(`归档清单")
expect(css).toContain('.list-editor-sheet{width:min(460px,calc(100vw - 24px));')
expect(css).toContain('@media(min-width:931px){.app-sheet-mask:has(.list-editor-sheet){place-items:center;padding:24px}.app-sheet-mask:has(.list-editor-sheet) .list-editor-sheet{margin:0}}')
expect(css).toContain('.list-editor-form{display:grid;gap:17px;padding:18px 20px}')
expect(css).toContain('@media(min-width:931px){.app-sheet-mask:has(.list-editor-sheet){place-items:center;padding:var(--space-24)}.app-sheet-mask:has(.list-editor-sheet) .list-editor-sheet{margin:0}}')
expect(css).toContain('.list-editor-form{display:grid;gap:17px;padding:var(--space-18) var(--space-20)}')
expect(css).toContain('.list-editor-danger{border-top:1px solid var(--border-cream);')
expect(app).toContain('@keydown.esc="sidebarCreateOpen=false;closeArchivedListAction();closeSidebarAction()"')
expect(app).toContain('sidebarCreateOpen.value = false; sidebarAction.value = null')
@@ -1631,8 +1631,8 @@ describe('quiet index sidebar parity', () => {
expect(css).toContain('.sidebar{border-right:1px solid var(--border-cream);background:var(--surface-canvas);')
expect(css).toContain('.brand-row{height:68px;')
expect(css).toContain('border-bottom:1px solid rgba(222,205,185,.75)')
expect(css).toContain('.primary-nav{display:grid;padding:10px 11px 8px;gap:2px}')
expect(css).toContain('@media(max-width:1050px) and (min-width:931px){main{padding-inline:24px}')
expect(css).toContain('.primary-nav{display:grid;padding:var(--space-10) var(--space-10) var(--space-8);gap:2px}')
expect(css).toContain('@media(max-width:1050px) and (min-width:931px){main{padding-inline:var(--space-24)}')
expect(css).not.toContain('grid-template-columns:220px')
expect(css).toContain('@media(max-width:930px){.shell')
expect(css).toContain('left:0;width:236px;max-width:86vw;')
@@ -1642,9 +1642,9 @@ describe('quiet index sidebar parity', () => {
expect(css).toContain('.primary-nav button.active{background:var(--nav-active-bg);color:var(--accent-ink);')
expect(css).toContain('.primary-nav button.active::before,.list-row.active::before,.sidebar-management button.active::before{')
expect(css).toContain('width:3px;height:18px;')
expect(css).toContain('.section-title{padding:14px 15px 5px 20px;')
expect(css).toContain('.folders{flex:1;min-height:0;padding:0 10px;')
expect(css).toContain('.folder-row>button,.list-row-main{flex:1;min-width:0;min-height:44px;border:0;background:transparent;padding:0 8px;')
expect(css).toContain('.section-title{padding:var(--space-14) var(--space-14) var(--space-4) var(--space-20);')
expect(css).toContain('.folders{flex:1;min-height:0;padding:0 var(--space-10);')
expect(css).toContain('.folder-row>button,.list-row-main{flex:1;min-width:0;min-height:44px;border:0;background:transparent;padding:0 var(--space-8);')
expect(css).toContain('.list-row.active{background:var(--list-row-bg);color:var(--accent-ink);box-shadow:inset 0 0 0 1px var(--list-row-shadow)}')
})
@@ -1790,7 +1790,7 @@ describe('sidebar layout', () => {
expect(app).toContain('@close="closePurgeList"')
expect(app).not.toContain('handlePurgeDialogKeydown')
expect(app).toContain('if (purgeListSubmitting.value) return')
expect(app).toContain('purgeListError.value = reason instanceof Error ? reason.message : \'永久删除失败\'')
expect(app).toContain("purgeListError.value = failureHint('永久删除失败', reason)")
expect(app).toContain(':disabled="purgeListSubmitting"')
expect(app).toContain('role="alert" class="purge-list-error"')
expect(css).toContain('.purge-list-dialog button{min-height:44px;')