Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
4 changes: 2 additions & 2 deletions CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ _Avoid_: 归档时机(「归档」专指 Archived Logbook)
_Avoid_: 待归档、已完成未归档

**Log Completed(移入已了结)**:
把账号所有 Unlogged Item 一次移入 Logbook 的命令:把 `loggedThrough` 推进到当前时刻。入口:`⇧⌘Y`(可改绑)、任务 / 项目右键菜单、Logbook 页顶部按钮(手机端的入口);立即模式下全部隐藏。执行后 toast 可撤销,撤销把水位线退回执行前的值(水位线唯一的回退)。
把账号所有 Unlogged Item 一次移入 Logbook 的命令:把 `loggedThrough` 推进到当前时刻。入口:`⇧⌘Y`(可改绑)、任务 / 项目右键菜单、Logbook 页顶部按钮(手机端的入口);立即模式下全部隐藏。执行后可撤销(Undo),撤销把水位线退回执行前的值(水位线唯一的回退)。
_Avoid_: 归档已完成、清理已完成

**Trash(废纸篓)**:
Expand Down Expand Up @@ -265,7 +265,7 @@ _Avoid_: Selection(键盘专属)、编辑模式、批量选择
_Avoid_: FAB(实现名,可用于代码)、加号按钮、浮动按钮

**Undo(撤销)**:
撤销最近一步编辑(对齐 Things 3):手机端摇一摇弹出「撤销 …?」确认,确认即撤销;可连续撤销更早的步骤,不支持重做。一步(Undo Step)= 两次用户输入(按下、点击、按键)之间发起的全部编辑,所以一个动作内部的多次写入(完成并派生下一次重复实例、多选批量改期、整组拖拽)一起撤销。撤销把这一步改过的字段写回原值(之后又被改过的字段不动),新建的条目被删除,删除的 Subtask 按原内容重建;回写是普通编辑,照常同步到各设备。无法还原的物理删除(清空 Trash、删除区域 / 标签 / Heading / 附件、转换为项目)清空撤销历史。撤销历史只在本机内存里,重启或登出即清空;通知上的动作、其他设备同步来的编辑不进撤销历史。目前只有手机端提供入口(摇一摇)。
撤销最近一步编辑(对齐 Things 3):桌面端与 Web 按 ⌘Z / Ctrl+Z 直接撤销(焦点在输入框里时是原生文字撤销),手机端摇一摇弹出「撤销 …?」确认,确认即撤销;可连续撤销更早的步骤,不支持重做。一步(Undo Step)= 两次用户输入(按下、点击、按键)之间发起的全部编辑,所以一个动作内部的多次写入(完成并派生下一次重复实例、多选批量改期、整组拖拽)一起撤销。撤销把这一步改过的字段写回原值(之后又被改过的字段不动),新建的条目被删除,删除的 Subtask 按原内容重建,Log Completed 退回水位线;回写是普通编辑,照常同步到各设备。无法还原的物理删除(清空 Trash、删除区域 / 标签 / Heading / 附件、转换为项目)清空撤销历史。撤销历史只在本机内存里,重启或登出即清空;通知上的动作、其他设备同步来的编辑不进撤销历史。桌面端每个窗口、Web 每个标签页各有一份撤销历史。
_Avoid_: 回滚、历史版本(不是数据版本管理)、Redo(不支持)

**Sidebar Drop(拖到侧边栏)**:
Expand Down
9 changes: 6 additions & 3 deletions docs/adr/0024-undo-recorded-at-engine-writes.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ group drag.
- Writes that bypass the wrapper, such as the reminder coordinator (notification
actions) and sync merges, never become undo steps. A new non-UI writer must
use the raw Engine for the same reason.
- History lives in memory only and has no Redo. Today only the Android shell
attaches it (shake to undo). Desktop and web can attach the same wrapper
later for ⌘Z.
- History lives in memory only and has no Redo. Every shell attaches it: shake
to undo on Android, ⌘Z on desktop and web. Each desktop window and each web
tab keeps its own history, attached to that window's `TabEngine` or Engine.
- A write outside the Engine (Log Completed advances the `loggedThrough`
account preference) brings its own inverse through `UndoHistory.record` and
joins the current step like any Engine write.
10 changes: 9 additions & 1 deletion docs/keyboard-shortcuts.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ Grouped View(今天/随时/某天按项目/区域分组)补充规则:

取消与完成同型:Logbook 中 ⌘K 撤销了结(已完成的撤销完成,已取消的撤销取消)。

Log Completed 只在移入时机为「每天」或「手动」时生效(立即模式下没有留在列表里的已了结条目),作用于整个账号,执行后 toast 可撤销;规则见 CONTEXT.md「Log Completed」与 ADR 0022。任务 / 项目右键菜单与 Logbook 页顶部按钮同样提供。Web 的 Ctrl+Shift+Y 被浏览器占用,降级为 Alt 系。
Log Completed 只在移入时机为「每天」或「手动」时生效(立即模式下没有留在列表里的已了结条目),作用于整个账号,执行后可用 ⌘Z 撤销;规则见 CONTEXT.md「Log Completed」与 ADR 0022。任务 / 项目右键菜单与 Logbook 页顶部按钮同样提供。Web 的 Ctrl+Shift+Y 被浏览器占用,降级为 Alt 系。

### 标签

Expand All @@ -92,6 +92,14 @@ Log Completed 只在移入时机为「每天」或「手动」时生效(立即

作用于选中的任务行和项目行(组头、Heading 不算),多选时按显示顺序逐个复制;Trash 中不响应。副本紧跟来源(任务在列表中、项目在侧边栏中),完成后选中副本;Logbook 中复制出的任务是未完成的,落回原列表,选中不变。规则见 CONTEXT.md「Duplicate」。右键菜单与项目页「…」菜单同样提供「复制」(项目页复制后打开副本);触控设备在左滑多选工具栏的「更多」里复制勾选的任务,复制后退出多选。Web 的 Ctrl+D 是浏览器「加入书签」,可以拦截,与桌面同键。

### 撤销

| 动作 | macOS 桌面 | Windows 桌面 | Web |
|---|---|---|---|
| 撤销最近一步 | ⌘Z | Ctrl+Z | Ctrl+Z |

直接撤销,不弹确认(对齐 Things Mac;手机端是摇一摇加确认);可连续撤销更早的步骤,不支持重做。焦点在输入框里时是原生文字撤销;有弹层(卡片、菜单、对话框)打开时不响应。每个窗口 / 标签页各有一份撤销历史。规则见 CONTEXT.md「Undo」与 ADR 0024。Web 的 Ctrl+Z 可以拦截,与桌面同键。

### 剪贴板

| 动作 | macOS 桌面 | Windows 桌面 | Web |
Expand Down
17 changes: 10 additions & 7 deletions packages/api/src/hooks/useLogging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { updatePreferences } from '@/api/users.api';
import { hydrateFromServer, usePreferencesStore } from '@/stores/preferences.store';
import { currentLegacyDateTimeZone, currentTimeZone } from '@/utils/date';
import { currentLogging } from '@/utils/logging';
import { undoHistory } from '../undo/undo-history';
import type { QueryCacheFacade } from '../engine/live-queries';
import { refreshAfterWrite, useQueryCache } from './cache-patches';
import { useCalendarDay } from './useCalendarDay';
Expand Down Expand Up @@ -43,10 +44,10 @@ export interface LoggingActions {
/** 切换移入时机;水位线按 loggedThroughAfterModeChange 推进。 */
setLoggingMode: (mode: LoggingMode) => Promise<void>;
/**
* Log Completed:水位线推进到现在,所有 Unlogged Item 移入 Logbook。返回
* 撤销函数(把水位线写回执行前的值);立即模式下无操作,返回 null。
* Log Completed:水位线推进到现在,所有 Unlogged Item 移入 Logbook。记入
* 撤销历史,撤销即把水位线写回执行前的值;立即模式下无操作。
*/
logCompleted: () => Promise<(() => Promise<void>) | null>;
logCompleted: () => Promise<void>;
}

export function useLoggingActions(): LoggingActions {
Expand All @@ -67,10 +68,12 @@ export function useLoggingActions(): LoggingActions {
);
const logCompleted = useCallback(async () => {
const before = currentLogging();
if (before.mode === 'IMMEDIATE') return null;
await persistLogging({ ...before, loggedThrough: new Date().toISOString() }, cache);
return () =>
persistLogging({ ...currentLogging(), loggedThrough: before.loggedThrough }, cache);
if (before.mode === 'IMMEDIATE') return;
await undoHistory.record('logCompleted', async () => {
await persistLogging({ ...before, loggedThrough: new Date().toISOString() }, cache);
return () =>
persistLogging({ ...currentLogging(), loggedThrough: before.loggedThrough }, cache);
});
}, [cache]);
return { setLoggingMode, logCompleted };
}
Expand Down
2 changes: 0 additions & 2 deletions packages/api/src/i18n/locales/en/auth.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@
"changePassword": "Change Password",
"currentPassword": "Current Password",
"newPassword": "New Password",
"profileSaved": "Profile saved",
"passwordSaved": "Password changed",
"passwordChangeFailed": "Failed to change password",
"sessionExpired": "Session expired, please log in again",
"sessionSaveFailed": "Could not save your session. Check local storage and try again.",
Expand Down
1 change: 0 additions & 1 deletion packages/api/src/i18n/locales/en/calendar.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
"subscriptionNamePlaceholder": "Defaults to the calendar's own name",
"addSubscription": "Add",
"addingSubscription": "Fetching calendar…",
"subscriptionAdded": "Added calendar \"{{name}}\"",
"noSubscriptions": "No calendars subscribed yet",
"subscriptionEnabled": "Show events from this calendar",
"subscriptionColor": "Color",
Expand Down
2 changes: 1 addition & 1 deletion packages/api/src/i18n/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@
"emptyTrashConfirmTitle": "Empty Trash?",
"emptyTrashConfirmDescription": "This permanently deletes all tasks and projects in Trash. This cannot be undone.",
"emptyTrashConfirmAction": "Empty",
"emptyTrashSuccess": "Trash emptied",
"emptyTrashFailed": "Failed to empty trash",
"add": "New",
"settings": "Settings",
Expand Down Expand Up @@ -74,6 +73,7 @@
"undoActionEdit": "Edit",
"undoActionReorder": "Reorder",
"undoActionChange": "Change",
"undoActionLogCompleted": "Log Completed",
"windowMinimize": "Minimize",
"windowMaximize": "Maximize",
"windowRestore": "Restore Down",
Expand Down
4 changes: 0 additions & 4 deletions packages/api/src/i18n/locales/en/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,15 @@
"moveHeading": "Move",
"moveHeadingSearchPlaceholder": "Search projects…",
"moveHeadingNoResults": "No matching projects",
"moveHeadingSuccess": "Moved to {{project}}",
"moveHeadingFailed": "Failed to move heading",
"convertSuccess": "Converted to project",
"convertFailed": "Failed to convert to project",
"notePlaceholder": "Note…",
"settled": "Settled",
"archive": "Archive",
"unarchive": "Unarchive",
"archiveHeading": "Archive heading",
"unarchiveHeading": "Unarchive heading",
"archiveSuccess": "Archived",
"archiveFailed": "Failed to archive",
"unarchiveSuccess": "Unarchived",
"unarchiveFailed": "Failed to unarchive",
"archivedHeadingCount": "{{count}} archived headings",
"laterProjects": "Later Projects",
Expand Down
1 change: 0 additions & 1 deletion packages/api/src/i18n/locales/en/review.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
"everySingle_year": "Every year",
"every_year": "Every {{count}} years",
"emptyTitle": "Nothing to review",
"doneTitle": "Review finished",
"upcoming_one": "Next review on {{date}} — {{count}} item",
"upcoming_other": "Next review on {{date}} — {{count}} items",
"noUpcoming": "Nothing is scheduled for review.",
Expand Down
4 changes: 1 addition & 3 deletions packages/api/src/i18n/locales/en/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@
"exportDescription": "Export all your data as a JSON file for backup or migration.",
"exportButton": "Export",
"exporting": "Exporting…",
"exportSuccess": "Export successful",
"exportFailed": "Export failed",
"deleteAccount": "Delete Account",
"deleteAccountDescription": "Permanently delete your account and all data. This cannot be undone.",
Expand Down Expand Up @@ -95,11 +94,9 @@
"assistantModelIdHint": "Model name on the endpoint, e.g. deepseek-chat",
"assistantTest": "Test connection",
"assistantTesting": "Testing…",
"assistantTestOk": "Connection successful",
"assistantTestFailed": "Connection failed",
"assistantSave": "Save",
"assistantSaving": "Saving…",
"assistantSaved": "Saved",
"assistantConfigured": "Configured",
"assistantNotConfigured": "Not configured",
"assistantSaveFailed": "Failed to save",
Expand Down Expand Up @@ -145,6 +142,7 @@
"shortcut_tags": "Edit tags",
"shortcut_duplicate": "Duplicate selected items",
"shortcut_logCompleted": "Log completed items",
"shortcut_undo": "Undo",
"shortcut_expand": "Open selected task",
"shortcut_search": "Quick Find",
"shortcut_toggleAssistantPanel": "Toggle assistant panel",
Expand Down
4 changes: 2 additions & 2 deletions packages/api/src/i18n/locales/en/tag.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"titlePlaceholder": "Tag name",
"deleteConfirm": "Delete tag \"{{name}}\"?",
"defaultTitle": "Tag",
"deleted": "Tag deleted",
"deleteDescription": "The tag will be removed from all items. This can't be undone.",
"deleteDescriptionWithChildren": "The tag will be removed from all items and its child tags moved to the top level. This can't be undone.",
"created": "Tag created",
"selectColor": "Select color {{color}}",
"noTasks": "No tasks with this tag",
Expand All @@ -23,6 +24,5 @@
"moveToTop": "Top level",
"expand": "Expand",
"collapse": "Collapse",
"deletedChildrenPromoted": "Tag deleted; its child tags moved to the top level",
"parentPath": "Parent tags"
}
4 changes: 0 additions & 4 deletions packages/api/src/i18n/locales/en/task.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,6 @@
"move": "Move",
"duplicate": "Duplicate",
"logCompleted": "Log Completed",
"logCompletedDone": "Moved completed items to the Logbook",
"duplicateProjectDone": "Project duplicated",
"placement": "Located in {{place}}",
"placementGoToProject": "Go to project",
"placementChangeProject": "Change project",
Expand All @@ -72,7 +70,6 @@
"whenQueryPlaceholder": "Type a date, e.g. next fri",
"deadlineQueryPlaceholder": "Type a deadline, e.g. in 2 weeks",
"whenQueryNoResults": "Couldn't understand that date",
"convertSuccess": "Converted to project",
"convertFailed": "Conversion failed",
"quickAddPlaceholder": "Add a task to Inbox…",
"quickAddNotSignedIn": "Sign in from the main window to use quick add",
Expand Down Expand Up @@ -116,7 +113,6 @@
"skipOccurrence": "Skip Occurrence",
"skipOccurrenceLast": "This is the last occurrence",
"skipOccurrenceNextExists": "The next occurrence already exists",
"skipOccurrenceDone": "Skipped to {{date}}",
"repeatPreviewHint": "Next occurrence of a repeating task",
"quickAddTitlePlaceholder": "New task",
"quickAddPlacement": "Where",
Expand Down
2 changes: 0 additions & 2 deletions packages/api/src/i18n/locales/zh/auth.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@
"changePassword": "修改密码",
"currentPassword": "当前密码",
"newPassword": "新密码",
"profileSaved": "资料已保存",
"passwordSaved": "密码已修改",
"passwordChangeFailed": "修改密码失败",
"sessionExpired": "登录会话已过期,请重新登录",
"sessionSaveFailed": "无法保存登录状态,请检查本地存储后重试。",
Expand Down
1 change: 0 additions & 1 deletion packages/api/src/i18n/locales/zh/calendar.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
"subscriptionNamePlaceholder": "缺省使用日历自带的名称",
"addSubscription": "添加",
"addingSubscription": "正在获取日历…",
"subscriptionAdded": "已添加日历「{{name}}」",
"noSubscriptions": "还没有订阅任何日历",
"subscriptionEnabled": "显示该日历的日程",
"subscriptionColor": "颜色",
Expand Down
2 changes: 1 addition & 1 deletion packages/api/src/i18n/locales/zh/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@
"emptyTrashConfirmTitle": "倾倒废纸篓?",
"emptyTrashConfirmDescription": "将永久删除废纸篓中的所有任务和项目,此操作不可恢复。",
"emptyTrashConfirmAction": "倾倒",
"emptyTrashSuccess": "废纸篓已倾倒",
"emptyTrashFailed": "倾倒废纸篓失败",
"add": "新增",
"settings": "设置",
Expand Down Expand Up @@ -74,6 +73,7 @@
"undoActionEdit": "编辑",
"undoActionReorder": "排序",
"undoActionChange": "修改",
"undoActionLogCompleted": "移入 Logbook",
"windowMinimize": "最小化",
"windowMaximize": "最大化",
"windowRestore": "向下还原",
Expand Down
4 changes: 0 additions & 4 deletions packages/api/src/i18n/locales/zh/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,15 @@
"moveHeading": "移动",
"moveHeadingSearchPlaceholder": "搜索项目…",
"moveHeadingNoResults": "没有匹配的项目",
"moveHeadingSuccess": "已移到「{{project}}」",
"moveHeadingFailed": "移动分组失败",
"convertSuccess": "已转换为项目",
"convertFailed": "转换为项目失败",
"notePlaceholder": "备注…",
"settled": "已了结",
"archive": "归档",
"unarchive": "取消归档",
"archiveHeading": "归档项目分组标题",
"unarchiveHeading": "取消归档项目分组标题",
"archiveSuccess": "已归档",
"archiveFailed": "归档失败",
"unarchiveSuccess": "已取消归档",
"unarchiveFailed": "取消归档失败",
"archivedHeadingCount": "{{count}} 个已归档项目分组标题",
"laterProjects": "稍后项目",
Expand Down
1 change: 0 additions & 1 deletion packages/api/src/i18n/locales/zh/review.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
"everySingle_year": "每年",
"every_year": "每 {{count}} 年",
"emptyTitle": "没有待回顾的项目或区域",
"doneTitle": "本轮回顾已结束",
"upcoming_one": "下次回顾:{{date}},共 {{count}} 个",
"upcoming_other": "下次回顾:{{date}},共 {{count}} 个",
"noUpcoming": "暂无排定的回顾。",
Expand Down
4 changes: 1 addition & 3 deletions packages/api/src/i18n/locales/zh/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@
"exportDescription": "将你的所有数据导出为 JSON 文件,用于备份或迁移。",
"exportButton": "导出",
"exporting": "导出中…",
"exportSuccess": "导出成功",
"exportFailed": "导出失败",
"deleteAccount": "删除账号",
"deleteAccountDescription": "永久删除你的账号及所有数据,此操作不可恢复。",
Expand Down Expand Up @@ -95,11 +94,9 @@
"assistantModelIdHint": "端点上的模型名称,例如 deepseek-chat",
"assistantTest": "测试连接",
"assistantTesting": "测试中…",
"assistantTestOk": "连接成功",
"assistantTestFailed": "连接失败",
"assistantSave": "保存",
"assistantSaving": "保存中…",
"assistantSaved": "已保存",
"assistantConfigured": "已配置",
"assistantNotConfigured": "未配置",
"assistantSaveFailed": "保存失败",
Expand Down Expand Up @@ -145,6 +142,7 @@
"shortcut_tags": "编辑标签",
"shortcut_duplicate": "复制选中项",
"shortcut_logCompleted": "把完成的条目移入 Logbook",
"shortcut_undo": "撤销",
"shortcut_expand": "展开选中任务",
"shortcut_search": "快速查找",
"shortcut_toggleAssistantPanel": "开关助手面板",
Expand Down
4 changes: 2 additions & 2 deletions packages/api/src/i18n/locales/zh/tag.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"titlePlaceholder": "标签名称",
"deleteConfirm": "确认删除标签「{{name}}」?",
"defaultTitle": "标签",
"deleted": "标签已删除",
"deleteDescription": "标签会从所有条目上移除,此操作无法撤销。",
"deleteDescriptionWithChildren": "标签会从所有条目上移除,其子标签移到顶层。此操作无法撤销。",
"created": "标签已创建",
"selectColor": "选择颜色 {{color}}",
"noTasks": "该标签下没有任务",
Expand All @@ -23,6 +24,5 @@
"moveToTop": "顶层",
"expand": "展开",
"collapse": "收起",
"deletedChildrenPromoted": "标签已删除,其子标签移到顶层",
"parentPath": "上级标签"
}
4 changes: 0 additions & 4 deletions packages/api/src/i18n/locales/zh/task.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,6 @@
"move": "移动",
"duplicate": "复制",
"logCompleted": "移入 Logbook",
"logCompletedDone": "已把完成的条目移入 Logbook",
"duplicateProjectDone": "已复制项目",
"placement": "所属:{{place}}",
"placementGoToProject": "前往项目",
"placementChangeProject": "更改项目",
Expand All @@ -71,7 +69,6 @@
"whenQueryPlaceholder": "输入日期,如「下周五」",
"deadlineQueryPlaceholder": "输入截止日期,如「两周后」",
"whenQueryNoResults": "无法识别的日期",
"convertSuccess": "已转换为项目",
"convertFailed": "转换失败",
"quickAddPlaceholder": "添加任务到收件箱…",
"quickAddNotSignedIn": "请先在主窗口登录后再使用快速添加",
Expand Down Expand Up @@ -115,7 +112,6 @@
"skipOccurrence": "跳过本次",
"skipOccurrenceLast": "已是最后一次,无法跳过",
"skipOccurrenceNextExists": "下一次已存在,无法跳过",
"skipOccurrenceDone": "已跳到 {{date}}",
"repeatPreviewHint": "重复任务的下一次",
"quickAddTitlePlaceholder": "新任务",
"quickAddPlacement": "放在哪",
Expand Down
Loading
Loading