From 0e93b15797ddcd736ec32c1c92f3317fbffc6a00 Mon Sep 17 00:00:00 2001 From: bboysoul Date: Tue, 15 Sep 2026 11:09:35 +0800 Subject: [PATCH] fix: prevent accidental subtask completion --- frontend/src/App.vue | 12 ++++++++++-- frontend/src/style.css | 2 +- frontend/src/style.test.ts | 14 +++++++++++++- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/frontend/src/App.vue b/frontend/src/App.vue index fcf84bd..5dfb519 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -654,14 +654,22 @@ async function loadTodayView() { await loadAll() } function applyTaskUpdate(task: Task, updated: Task, updateSelected = true) { + const updateNestedSubtasks = (items: Task[]) => items.map((item) => ({ + ...item, + subtasks: item.subtasks?.map((subtask) => subtask.id === task.id ? { ...subtask, ...updated } : subtask), + })) const index = tasks.value.findIndex((item) => item.id === task.id) if (index >= 0) tasks.value[index] = { ...tasks.value[index], ...updated } + else tasks.value = updateNestedSubtasks(tasks.value) const overdueIndex = overdueTasks.value.findIndex((item) => item.id === task.id) if (overdueIndex >= 0) { if (updated.completed && !completionExitingTaskIds.value.has(task.id)) overdueTasks.value.splice(overdueIndex, 1) else overdueTasks.value[overdueIndex] = { ...overdueTasks.value[overdueIndex], ...updated } - } + } else overdueTasks.value = updateNestedSubtasks(overdueTasks.value) if (updateSelected && selectedTask.value?.id === task.id) selectedTask.value = { ...selectedTask.value, ...updated } + else if (updateSelected && selectedTask.value?.subtasks) { + selectedTask.value.subtasks = selectedTask.value.subtasks.map((subtask) => subtask.id === task.id ? { ...subtask, ...updated } : subtask) + } } async function patchTask(task: Task, patch: Partial, updateSelected = true) { const updated = await api(`/tasks/${task.id}`, { method: 'PATCH', body: JSON.stringify({ ...patch, version: task.version }) }) as Task @@ -1528,7 +1536,7 @@ onUnmounted(() => {
-
子任务
把这件事拆成更小的步骤
+
子任务
{{subtask.title}}
把这件事拆成更小的步骤
更多设置
diff --git a/frontend/src/style.css b/frontend/src/style.css index fa1c557..92a16bf 100644 --- a/frontend/src/style.css +++ b/frontend/src/style.css @@ -9,7 +9,7 @@ main{container-type:inline-size;min-width:0;padding:27px 34px 50px;overflow:auto .drag-handle{width:44px;min-width:44px;height:44px;flex:0 0 44px;display:grid;place-items:center;border:0;background:transparent;color:#b3a795;cursor:grab;touch-action:none;border-radius:8px}.drag-handle:active{cursor:grabbing}.drag-handle:disabled{opacity:.3;cursor:not-allowed}.drag-handle svg{width:17px;height:17px}.reordering{opacity:.78;z-index:4;box-shadow:0 12px 28px rgba(78,58,34,.18)!important;transition:none!important;pointer-events:none}.reorder-target{box-shadow:inset 0 2px 0 var(--accent)} .list-drag-handle{width:44px;min-width:44px;height:44px;display:grid;place-items:center;border:0;background:transparent;color:#ad9f8c;cursor:grab;touch-action:none;border-radius:8px}.list-drag-handle svg{width:15px;height:15px}.list-row{touch-action:pan-y}.list-row.list-dragging{position:relative;z-index:8;opacity:.8;box-shadow:0 10px 24px rgba(78,58,34,.2);transform:translateY(var(--list-drag-y));transition:none;pointer-events:none;background:#fffaf4}.folder-row.list-drop-target{background:var(--accent-soft);box-shadow:inset 3px 0 0 var(--accent)}.list-root-drop.list-drop-target{background:var(--accent-soft);color:#b7421e;border-radius:9px}.list-row.list-reorder-target{box-shadow:inset 0 2px 0 var(--accent)}.list-move-menu{display:grid;gap:2px;padding:0}.list-move-menu button{min-height:44px}.list-move-menu button:disabled,.sidebar-action-sheet .app-sheet__body button:disabled{opacity:.55;cursor:default}.sidebar-action-sheet{width:min(320px,calc(100vw - 24px));margin:12px;border:1px solid var(--border-cream);border-radius:18px;box-shadow:var(--highlight-inner),var(--shadow-raised);overflow:hidden}.sidebar-action-header.app-sheet__header,.sidebar-action-move-view.app-sheet__header{align-items:flex-start;min-height:64px;padding:13px 14px 12px}.sidebar-action-header>div,.sidebar-action-move-view>div{min-width:0;display:grid;gap:2px}.sidebar-action-header b,.sidebar-action-move-view b{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:15px}.sidebar-action-kind,.sidebar-action-group-title{font-size:11px;font-weight:750;letter-spacing:.04em;color:var(--muted)}.sidebar-action-body.app-sheet__body{gap:8px;padding:10px}.sidebar-action-group{display:grid;gap:2px;padding:6px;border:1px solid var(--border-cream);border-radius:14px;background:#fffaf4}.sidebar-action-group button,.sidebar-action-danger button,.list-move-menu button{width:100%;min-height:44px;display:grid;grid-template-columns:auto minmax(0,1fr) auto;align-items:center;gap:10px;border:0;border-radius:11px;background:transparent;padding:0 10px;text-align:left}.sidebar-action-group button:hover:not(:disabled),.list-move-menu button:hover:not(:disabled){background:#fff3e6}.sidebar-action-group button span,.sidebar-action-danger button span,.list-move-menu button span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sidebar-action-chevron{color:#b3a795}.sidebar-action-up{transform:rotate(180deg)}.sidebar-action-danger{border-top:1px solid #efd6cf;padding:9px 6px 6px;display:grid;gap:7px}.sidebar-action-danger>span{font-size:11px;line-height:1.4;color:#9a6a5f;padding:0 4px}.sidebar-action-danger button.danger{color:var(--danger);background:#fff6f2}.sidebar-action-move-back{min-height:44px;width:44px;border:0;border-radius:12px;background:transparent;color:#8a7e70;display:grid;place-items:center}.sidebar-action-move-back svg{transform:rotate(180deg)}.list-move-current{background:#fbe6dc!important;color:#7b351e;font-weight:750} .archived-lists{position:relative;margin-top:4px}.archived-lists-toggle{min-height:44px;width:100%;display:flex;align-items:center;gap:8px;border:0;border-radius:9px;background:transparent;padding:0 12px;color:#81786c;text-align:left;font-size:12px;font-weight:650}.archived-lists-toggle:hover:not(:disabled){background:rgba(255,255,255,.52)}.archived-lists-toggle:focus-visible,.archived-row-menu-trigger:focus-visible,.archived-row-actions button:focus-visible{outline:2px solid var(--accent);outline-offset:2px}.archived-lists-toggle:disabled{cursor:default;color:#aaa196}.archived-lists-toggle svg{width:15px;height:15px;transition:transform .16s ease}.archived-lists-toggle svg.expanded{transform:rotate(90deg)}.archived-list-items{display:grid;transition:opacity .16s ease}.archived-row{min-height:44px;display:grid;grid-template-columns:minmax(0,1fr) 44px;align-items:center;padding-left:35px;color:#746c61}.archived-row-label{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px}.archived-row-menu{display:grid;place-items:center}.archived-row-menu-trigger{width:44px;height:44px;display:grid;place-items:center;border:0;border-radius:9px;background:transparent;color:#8e8477}.archived-row-menu-trigger svg{width:15px;height:15px}.archived-action-mask{display:contents}.archived-row-actions{position:fixed;z-index:70;width:164px;display:grid;gap:2px;padding:6px;background:#fffdf8;border:1px solid var(--line);border-radius:12px;box-shadow:var(--shadow)}.archived-row-actions button{min-height:44px;display:flex;align-items:center;gap:9px;border:0;border-radius:8px;background:transparent;padding:8px 10px;text-align:left;font-size:12px}.archived-row-actions button:hover{background:#f8f1e8}.archived-row-actions svg{width:15px;height:15px}@media(max-width:930px){.archived-action-mask{display:block;position:fixed;z-index:60;inset:0;background:rgba(45,38,31,.3)}.archived-row-actions{position:fixed;z-index:61;left:0;right:0;top:auto;bottom:0;width:100%;padding:12px 16px calc(16px + env(safe-area-inset-bottom));border-radius:22px 22px 0 0}.archived-row-actions button{font-size:14px}}@media(prefers-reduced-motion:reduce){.archived-lists-toggle svg,.archived-list-items{transition:none}} -.detail{min-width:0;border-left:1px solid var(--line);background:#faf7f0;overflow:auto}.detail-head{height:57px;display:flex;align-items:center;justify-content:space-between;padding:0 21px;border-bottom:1px solid var(--line);font-size:12px;font-weight:700;color:#80766a;text-transform:uppercase;letter-spacing:.08em}.paper{margin:22px;padding:28px 20px;min-height:180px;background:#fff;border:1px solid var(--line);border-radius:11px;box-shadow:0 4px 18px rgba(76,57,34,.05);display:grid;place-items:center;align-content:center;text-align:center;color:#8f8578}.paper svg{width:32px;height:32px;color:#ceb8a4;margin-bottom:12px}.paper b{color:#625b50}.paper p{font-size:13px;line-height:1.6}.detail-form{min-width:0;grid-template-columns:minmax(0,1fr);padding:19px;display:grid;gap:12px}.detail-form>*{min-width:0}.detail-title{display:flex;align-items:flex-start;gap:10px}.check.large{margin-top:8px;width:22px;height:22px;flex-basis:22px}.detail-title textarea{flex:1;border:0;background:transparent;resize:none;outline:none;font-size:19px;line-height:1.4;font-weight:700}.detail-form>label{grid-template-columns:80px 1fr;align-items:center}.detail-form>label input,.detail-form>label select{padding:8px}.task-detail-field-input{width:190px!important;max-width:100%;justify-self:end}.task-detail-due-input{padding-inline:9px!important}.field{display:grid;gap:7px}.field-label{display:flex;justify-content:space-between;align-items:center;font-size:12px;font-weight:700;color:#756d61}.hint{font-size:12px;color:#a49a8d}.markdown-mode-switch{display:flex;background:#eee7dc;padding:2px;border-radius:7px}.markdown-mode-switch button{min-height:30px;border:0;background:transparent;padding:4px 9px;border-radius:6px;font-size:11px}.markdown-mode-switch button.active{background:#fff;color:var(--accent);box-shadow:0 1px 3px rgba(78,58,34,.08)}.markdown-editor-shell{min-width:0;border:1px solid var(--line);border-radius:11px;background:#fff;overflow:hidden}.markdown-toolbar{display:flex;align-items:center;gap:2px;overflow-x:auto;padding:4px;background:#f7f0e5;border-bottom:1px solid var(--line);scrollbar-width:none}.markdown-toolbar::-webkit-scrollbar{display:none}.markdown-toolbar button{min-width:44px;min-height:44px;flex:0 0 44px;border:0;border-radius:8px;background:transparent;color:#70675c;display:grid;place-items:center;padding:0}.markdown-toolbar button:hover{background:#fff;color:var(--accent)}.markdown-toolbar button svg{width:16px;height:16px}.markdown-toolbar .markdown-codeblock{font:700 12px/1 ui-monospace,SFMono-Regular,Menlo,monospace}.markdown textarea{width:100%;min-height:180px;border:0;background:#fff;border-radius:0;padding:12px;resize:vertical;outline:none;font:13px/1.65 ui-monospace,SFMono-Regular,Menlo,monospace}.markdown textarea:focus{box-shadow:inset 0 0 0 2px rgba(241,90,41,.15)}.markdown-preview{min-height:180px;padding:12px 14px;background:#fff;border:1px solid var(--line);border-radius:11px;font-size:13px;line-height:1.65;overflow-wrap:anywhere}.markdown-preview-empty{color:var(--muted)}.markdown-preview h1{margin:4px 0 12px;font-size:20px}.markdown-preview h2{margin:12px 0 8px;font-size:16px}.markdown-preview p{margin:8px 0}.markdown-preview ul,.markdown-preview ol{margin:8px 0;padding-left:22px}.markdown-preview blockquote{margin:10px 0;padding:7px 11px;border-left:3px solid #d7aa8f;background:#fff7ed;color:#665c51}.markdown-preview code{background:#f2ece2;padding:2px 4px;border-radius:4px}.markdown-preview pre{max-width:100%;overflow:auto;padding:11px;background:#332f2b;color:#f8f1e7;border-radius:9px}.markdown-preview pre code{padding:0;background:transparent;color:inherit}.markdown-preview a{color:var(--accent)}.markdown-preview hr{border:0;border-top:1px solid var(--line);margin:14px 0}.markdown-preview .task-list-item{list-style:none;margin-left:-20px;display:flex;align-items:flex-start;gap:7px}.markdown-preview .task-list-item input{margin-top:5px;accent-color:var(--accent)}.subtasks{display:grid;gap:5px}.subtask-detail{width:100%;min-width:0;max-width:100%;overflow-wrap:anywhere;word-break:break-word;white-space:normal;display:flex;align-items:center;gap:8px;border:0;background:#fff;padding:8px;border-radius:7px;text-align:left}.subtask-detail .check{pointer-events:none}.strike{text-decoration:line-through;color:var(--muted)}.detail-actions{display:flex;justify-content:space-between;align-items:center;padding-top:10px;border-top:1px solid var(--line)}.secondary{border:1px solid var(--line);background:#fff;padding:8px 11px;border-radius:8px;font-weight:650}.danger-text{border:0;background:transparent;color:var(--danger);display:flex;align-items:center;gap:5px;font-size:12px}.danger-text svg{width:14px} +.detail{min-width:0;border-left:1px solid var(--line);background:#faf7f0;overflow:auto}.detail-head{height:57px;display:flex;align-items:center;justify-content:space-between;padding:0 21px;border-bottom:1px solid var(--line);font-size:12px;font-weight:700;color:#80766a;text-transform:uppercase;letter-spacing:.08em}.paper{margin:22px;padding:28px 20px;min-height:180px;background:#fff;border:1px solid var(--line);border-radius:11px;box-shadow:0 4px 18px rgba(76,57,34,.05);display:grid;place-items:center;align-content:center;text-align:center;color:#8f8578}.paper svg{width:32px;height:32px;color:#ceb8a4;margin-bottom:12px}.paper b{color:#625b50}.paper p{font-size:13px;line-height:1.6}.detail-form{min-width:0;grid-template-columns:minmax(0,1fr);padding:19px;display:grid;gap:12px}.detail-form>*{min-width:0}.detail-title{display:flex;align-items:flex-start;gap:10px}.check.large{margin-top:8px;width:22px;height:22px;flex-basis:22px}.detail-title textarea{flex:1;border:0;background:transparent;resize:none;outline:none;font-size:19px;line-height:1.4;font-weight:700}.detail-form>label{grid-template-columns:80px 1fr;align-items:center}.detail-form>label input,.detail-form>label select{padding:8px}.task-detail-field-input{width:190px!important;max-width:100%;justify-self:end}.task-detail-due-input{padding-inline:9px!important}.field{display:grid;gap:7px}.field-label{display:flex;justify-content:space-between;align-items:center;font-size:12px;font-weight:700;color:#756d61}.hint{font-size:12px;color:#a49a8d}.markdown-mode-switch{display:flex;background:#eee7dc;padding:2px;border-radius:7px}.markdown-mode-switch button{min-height:30px;border:0;background:transparent;padding:4px 9px;border-radius:6px;font-size:11px}.markdown-mode-switch button.active{background:#fff;color:var(--accent);box-shadow:0 1px 3px rgba(78,58,34,.08)}.markdown-editor-shell{min-width:0;border:1px solid var(--line);border-radius:11px;background:#fff;overflow:hidden}.markdown-toolbar{display:flex;align-items:center;gap:2px;overflow-x:auto;padding:4px;background:#f7f0e5;border-bottom:1px solid var(--line);scrollbar-width:none}.markdown-toolbar::-webkit-scrollbar{display:none}.markdown-toolbar button{min-width:44px;min-height:44px;flex:0 0 44px;border:0;border-radius:8px;background:transparent;color:#70675c;display:grid;place-items:center;padding:0}.markdown-toolbar button:hover{background:#fff;color:var(--accent)}.markdown-toolbar button svg{width:16px;height:16px}.markdown-toolbar .markdown-codeblock{font:700 12px/1 ui-monospace,SFMono-Regular,Menlo,monospace}.markdown textarea{width:100%;min-height:180px;border:0;background:#fff;border-radius:0;padding:12px;resize:vertical;outline:none;font:13px/1.65 ui-monospace,SFMono-Regular,Menlo,monospace}.markdown textarea:focus{box-shadow:inset 0 0 0 2px rgba(241,90,41,.15)}.markdown-preview{min-height:180px;padding:12px 14px;background:#fff;border:1px solid var(--line);border-radius:11px;font-size:13px;line-height:1.65;overflow-wrap:anywhere}.markdown-preview-empty{color:var(--muted)}.markdown-preview h1{margin:4px 0 12px;font-size:20px}.markdown-preview h2{margin:12px 0 8px;font-size:16px}.markdown-preview p{margin:8px 0}.markdown-preview ul,.markdown-preview ol{margin:8px 0;padding-left:22px}.markdown-preview blockquote{margin:10px 0;padding:7px 11px;border-left:3px solid #d7aa8f;background:#fff7ed;color:#665c51}.markdown-preview code{background:#f2ece2;padding:2px 4px;border-radius:4px}.markdown-preview pre{max-width:100%;overflow:auto;padding:11px;background:#332f2b;color:#f8f1e7;border-radius:9px}.markdown-preview pre code{padding:0;background:transparent;color:inherit}.markdown-preview a{color:var(--accent)}.markdown-preview hr{border:0;border-top:1px solid var(--line);margin:14px 0}.markdown-preview .task-list-item{list-style:none;margin-left:-20px;display:flex;align-items:flex-start;gap:7px}.markdown-preview .task-list-item input{margin-top:5px;accent-color:var(--accent)}.subtasks{display:grid;gap:5px}.subtask-detail{width:100%;min-width:0;max-width:100%;overflow-wrap:anywhere;word-break:break-word;white-space:normal;display:flex;align-items:center;gap:8px;border:0;background:#fff;padding:8px;border-radius:7px;text-align:left}.subtask-detail .subtask-check{width:44px;height:44px;flex:0 0 44px;margin:-8px 0 -8px -8px;cursor:pointer;pointer-events:auto}.strike{text-decoration:line-through;color:var(--muted)}.detail-actions{display:flex;justify-content:space-between;align-items:center;padding-top:10px;border-top:1px solid var(--line)}.secondary{border:1px solid var(--line);background:#fff;padding:8px 11px;border-radius:8px;font-weight:650}.danger-text{border:0;background:transparent;color:var(--danger);display:flex;align-items:center;gap:5px;font-size:12px}.danger-text svg{width:14px} .field-hint{color:var(--muted);font-size:11px;font-weight:400}.after-completion-fields{display:grid;gap:5px;padding:10px 12px;border:1px solid var(--line);border-radius:10px;background:#fffaf4;color:#675f54;font-size:12px}.after-completion-fields>div{display:flex;align-items:center;gap:7px;min-height:44px}.after-completion-fields input{width:76px;min-height:44px;border:1px solid var(--line);border-radius:8px;background:#fff;padding:0 10px}.after-completion-fields small{color:var(--muted);line-height:1.5}.repeat-save{width:100%;min-height:44px}.repeat-custom-fields{display:grid;gap:10px;padding:12px;border:1px solid var(--line);border-radius:11px;background:#fffaf4}.repeat-custom-fields>div,.repeat-custom-fields>label{display:flex;align-items:center;gap:8px;color:#675f54;font-size:12px}.repeat-custom-fields input[type="number"]{width:72px}.repeat-custom-fields input,.repeat-custom-fields select{min-height:40px;border:1px solid var(--line);border-radius:8px;background:#fff;padding:7px 9px}.weekday-picker{display:flex;gap:4px;flex-wrap:wrap}.weekday-picker label{width:34px;height:34px;display:grid;place-items:center;border:1px solid var(--line);border-radius:50%;background:#fff}.weekday-picker input{position:absolute;opacity:0;pointer-events:none}.weekday-picker label:has(input:checked){background:var(--accent);border-color:var(--accent);color:#fff}.toast,.error-toast{position:fixed;z-index:50;left:50%;bottom:24px;transform:translateX(-50%);background:#322d28;color:#fff;border-radius:9px;padding:10px 15px;box-shadow:var(--shadow);font-size:13px}.error-toast{background:var(--danger);display:flex;align-items:center;gap:10px}.error-toast button{border:0;background:transparent;color:#fff;padding:0}.toast-enter-active,.toast-leave-active{transition:.2s}.toast-enter-from,.toast-leave-to{opacity:0;transform:translate(-50%,8px)} @media(max-width:1050px) and (min-width:931px){.shell.detail-open{grid-template-columns:220px minmax(400px,1fr) minmax(280px,30vw)}.shell.sidebar-collapsed.detail-open{grid-template-columns:0 minmax(400px,1fr) minmax(280px,30vw)}main{padding-inline:24px}} @media(max-width:930px){.shell{height:100dvh;display:block;overflow:auto}.shell.mobile-sidebar-open{overflow:hidden}.shell.mobile-sidebar-open main{overflow:hidden;touch-action:none}.sidebar{position:fixed;z-index:50;display:flex;top:0;bottom:0;transition:transform .22s ease;box-shadow:var(--shadow);left:0;width:min(300px,86vw);transform:translateX(-105%);overscroll-behavior:contain}.sidebar.open{transform:none}.detail{position:fixed;z-index:40;display:block;left:0;right:0;top:auto;bottom:0;width:100%;max-height:min(88dvh,760px);overflow:auto;background:#fffdf8;border:1px solid var(--line);border-bottom:0;border-radius:22px 22px 0 0;box-shadow:0 -14px 38px rgba(56,40,24,.2);transform:translateY(105%);transition:transform .22s ease;padding-bottom:max(16px,env(safe-area-inset-bottom))}.detail.open{transform:none}.detail-head{position:sticky;z-index:2;top:0;height:62px;background:#fffdf8;border-bottom:1px solid var(--line);padding:0 18px}.detail-head .icon{display:grid;width:42px;height:42px;background:var(--accent-soft);color:var(--accent);border-radius:50%}.detail-form{padding:18px}.detail-title textarea{font-size:21px}.more-mask{position:fixed;z-index:45;inset:0;background:rgba(45,38,31,.32);display:flex;align-items:flex-end}.more-sheet{width:100%;background:#fffdf8;border-radius:22px 22px 0 0;padding:14px 16px max(22px,env(safe-area-inset-bottom));display:grid;gap:6px}.more-sheet-head{display:flex;align-items:center;justify-content:space-between;padding:2px 4px 8px}.more-sheet>button{display:flex;align-items:center;gap:12px;border:0;background:#fff;padding:13px;border-radius:12px;text-align:left}.scrim{position:fixed;z-index:35;inset:0;background:rgba(45,38,31,.32);display:block}.mobile-only{display:grid}main{min-height:100dvh;padding:20px 17px 112px}.topbar{grid-template-rows:auto auto;row-gap:0}.topbar h1{font-size:24px}.search-reveal{max-height:0;opacity:0;overflow:hidden;visibility:hidden;pointer-events:none;transform:translateY(-8px);transition:max-height .2s ease,opacity .16s ease,transform .2s ease}.search-reveal.mobile-search-open{max-height:52px;opacity:1;visibility:visible;pointer-events:auto;transform:none;overflow:visible}.search-reveal.mobile-search-pulling{max-height:var(--search-pull);opacity:var(--search-pull-opacity);visibility:visible;transform:translateY(calc(var(--search-pull) - 56px))}.search-pull-hint{display:block;height:16px;margin-top:-16px;text-align:center;color:var(--muted);font-size:11px;line-height:16px;opacity:0;pointer-events:none}.search-reveal.mobile-search-pulling .search-pull-hint{opacity:1}.completed-filter-pill{min-width:138px;height:44px}.completed-filter-pill__track{width:36px;height:22px}.completed-filter-pill__thumb{width:18px;height:18px}.today-board{gap:1px;margin:14px 0 4px;padding:5px}.today-track{min-height:58px;padding:8px 9px;gap:7px}.today-track-head{align-items:flex-start;flex-direction:column;gap:2px}.today-track-head strong{font-size:13px}.search{width:100%;margin:0;padding:8px}.search input{width:100%}.search kbd{display:none}.list-toolbar{height:auto;min-height:36px}.section-heading{margin:12px 0 8px}.empty{min-height:190px}.today-empty-panel{min-height:104px}.row-actions{position:static;opacity:1;pointer-events:auto}.row-actions button{padding:7px;min-height:34px}.list-row{padding:6px 4px}.archived-row{padding:9px 7px;border:1px dashed #e0cfae;border-radius:8px;color:#8a6d3b;background:#fbf3e2}.archived-row .list-row-main{padding-left:14px}.archived-row .list-row-main>svg{color:#c9a45c}.bottom{display:flex;position:fixed;z-index:15;left:0;right:0;bottom:0;justify-content:space-around;background:rgba(255,253,248,.96);border-top:1px solid var(--line);padding:8px 5px max(8px,env(safe-area-inset-bottom));box-shadow:0 -5px 18px rgba(79,59,34,.06)}.bottom button{min-height:44px;min-width:60px;border:0;background:transparent;color:#81786d;display:grid;place-items:center;gap:2px;font-size:10px}.bottom button.active{color:var(--accent);font-weight:700}.bottom svg{width:20px}.toast,.error-toast{bottom:142px}.task-row{padding-inline:2px}.subtask{padding-left:35px}.ghost{opacity:.45}} diff --git a/frontend/src/style.test.ts b/frontend/src/style.test.ts index 779094b..d7f38b2 100644 --- a/frontend/src/style.test.ts +++ b/frontend/src/style.test.ts @@ -607,12 +607,24 @@ describe('task and habit row decoration', () => { expect(app).toContain('class="task-check"') expect(app.match(/class="task-check"/g)?.length).toBe(2) expect(app).toContain('class="task-check-mark"') - expect(app).toContain('class="subtask-detail" @click="toggle(subtask)"') + const subtaskRow = app.slice(app.indexOf('
]*@click=/) + expect(subtaskRow).toContain('class="check subtask-check"') + expect(subtaskRow).toContain('@click="toggle(subtask)"') + expect(css).toContain('.subtask-detail .subtask-check{width:44px;height:44px;flex:0 0 44px;') expect(css).toContain('.task-check{') expect(css).toContain('.task-check-mark{') expect(css).toContain('.task-check:focus-visible{') }) + it('updates a nested subtask locally after its completion changes', () => { + const updateBlock = app.slice(app.indexOf('function applyTaskUpdate'), app.indexOf('async function patchTask')) + expect(updateBlock).toContain('subtasks: item.subtasks?.map') + expect(updateBlock).toContain('subtask.id === task.id ? { ...subtask, ...updated } : subtask') + expect(updateBlock).toContain('selectedTask.value.subtasks = selectedTask.value.subtasks.map') + }) + it('reconciles concurrent completion mutations without suppressing their feedback', () => { const toggleBlock = app.slice(app.indexOf('async function toggle(task: Task)'), app.indexOf('function isInteractiveTarget')) expect(toggleBlock).toContain('const completing = !task.completed')