diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 00000000..d6cb2884 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1 @@ +npm run build diff --git a/design/focus.md b/design/focus.md deleted file mode 100644 index 42b4b03d..00000000 --- a/design/focus.md +++ /dev/null @@ -1,769 +0,0 @@ -# 视图配置弹窗 (View Configuration Dialog) - 功能设计文档 - -## 1. 概览 (Overview) - -### 1.1. 功能名称 (Feature Name) -视图配置弹窗 (View Configuration Dialog) - -### 1.2. 目标 (Goal) -提供一个集中式的、用户友好的界面,允许用户定义和管理任务的筛选和排序规则。这些规则将应用于插件内的所有相关任务视图,从而统一和简化用户查看和组织任务的方式。 - -### 1.3. 核心价值 (Core Value) -- **易用性**: 通过图形界面简化复杂的筛选和排序逻辑配置。 -- **一致性**: 应用统一的视图配置,确保在不同地方查看任务时行为一致。 -- **灵活性**: 支持多种筛选条件、条件组和排序规则的组合。 -- **效率**: 通过预设功能,快速切换不同的视图配置,适应不同工作场景。 - -## 2. 用户界面 (UI) 设计 - -### 2.1. 入口 (Access Point) -- 在任务视图的主界面(例如,某个全局视图控制区域或特定视图的设置入口),提供一个按钮或菜单项,如"配置视图"、"筛选与排序"或一个设置图标。 -- 点击该入口将打开一个模态弹窗。 - -### 2.2. 弹窗布局 (Pop-up Layout) -弹窗从上到下主要分为以下区域: - -``` -+------------------------------------------------------+ -| 视图配置 [ X ] 关闭 | -+------------------------------------------------------+ -| 预设 (Presets) | -| [选择一个预设 v] [保存] [另存为...] [删除] | -+------------------------------------------------------+ -| 筛选 (Filters) | -| [ 所有/任一 v ] 条件满足 | -| +------------------------------------------------+ | -| | [属性 v] [操作符 v] [值输入欄] [ ] [🗑️] | -| | [ AND/OR ] | -| | +-- Group -----------------------------------+ | -| | | [属性 v] [操作符 v] [值输入欄] [ ] [🗑️] | -| | +--------------------------------------------+ | -| +------------------------------------------------+ | -| [+ 添加条件] [+ 添加条件组] | -+------------------------------------------------------+ -| 排序 (Sorting) | -| +------------------------------------------------+ | -| | 排序依据: [属性 v] 顺序: [升序/降序 v] [⬆️][⬇️][🗑️] | -| +------------------------------------------------+ | -| [+ 添加排序规则] | -+------------------------------------------------------+ -| [ 应用/保存配置 ] [ 取消 ] | -+------------------------------------------------------+ -``` - -**图例说明:** -- `[ 关闭 ]`: 关闭弹窗按钮。 -- `[选择一个预设 v]`: 下拉菜单选择已保存的预设。 -- `[保存]`: 保存对当前选中预设的修改。 -- `[另存为...]`: 将当前配置保存为一个新的预设。 -- `[删除]`: 删除当前选中的预设。 -- `[ 所有/任一 v ]`: 筛选条件组的逻辑操作符(AND/OR)。 -- `[属性 v]`: 选择任务的属性(如:内容、状态、优先级、截止日期、标签等)。 -- `[操作符 v]`: 选择筛选操作符(如:包含、不包含、等于、不等于、大于、小于、为空、不为空等)。 -- `[值输入欄]`: 输入筛选条件的值。 -- `[ ]`: (可选) 切换到高级/表达式模式编辑该条件。 -- `[🗑️]`: 删除该条件或排序规则。 -- `[+ 添加条件]`: 添加一个新的筛选条件行。 -- `[+ 添加条件组]`: 添加一个嵌套的筛选条件组。 -- `[升序/降序 v]`: 选择排序方向。 -- `[⬆️][⬇️]`: 调整排序规则的优先级。 -- `[+ 添加排序规则]`: 添加一个新的排序规则行。 -- `[ 应用/保存配置 ]`: 保存当前弹窗中的筛选和排序设置,并应用到所有视图。 -- `[ 取消 ]`: 关闭弹窗,不保存任何更改。 - -### 2.3. UI 元素详解 (Detailed UI Elements) - -#### 2.3.1. 预设 (Presets) -- **下拉菜单**: 列出所有已保存的预设名称。选择一项会加载其对应的筛选和排序配置到下方区域。包含一个"创建新预设"或"无预设"(即自定义配置)的选项。 -- **保存按钮**: 如果当前选中的是一个已存在的预设,则此按钮启用,点击后用当前界面中的配置覆盖该预设。 -- **另存为按钮**: 弹出一个输入框,要求用户输入新预设的名称,然后将当前界面中的配置保存为新的预设。 -- **删除按钮**: 如果当前选中的是一个已存在的预设,则此按钮启用,点击后会提示用户确认删除该预设。 - -#### 2.3.2. 筛选区域 (Filtering Area) -- **顶层逻辑操作符**: 一个下拉菜单,允许用户选择顶层筛选条件是"所有条件都满足 (AND)"还是"任一条件满足 (OR)"。 -- **筛选条件行 (Filter Condition Row)**: - - **属性下拉框**: 列出可供筛选的任务属性,例如: - - `内容 (Content)` (文本) - - `状态 (Status)` (特定值列表或文本) - - `优先级 (Priority)` (特定值列表或文本,如 高,中,低 或 🔺, 🔼, 🔽) - - `截止日期 (Due Date)` (日期) - - `开始日期 (Start Date)` (日期) - - `计划日期 (Scheduled Date)` (日期) - - `标签 (Tags)` (文本,特殊处理包含逻辑) - - `路径 (File Path)` (文本) - - `已完成 (Completed)` (布尔值) - - **操作符下拉框**: 根据所选"属性"的类型动态更新可用的操作符。 - - 文本: `包含 (contains)`, `不包含 (does not contain)`, `等于 (is)`, `不等于 (is not)`, `开头是 (starts with)`, `结尾是 (ends with)`, `为空 (is empty)`, `不为空 (is not empty)` - - 数字/日期: `等于 (=)`, `不等于 (!=)`, `大于 (>)`, `小于 (<)`, `大于等于 (>=)`, `小于等于 (<=)`, `为空 (is empty)`, `不为空 (is not empty)` - - 标签: `包含 (contains / has tag)`, `不包含 (does not contain / does not have tag)` - - 布尔: `是 (is true)`, `否 (is false)` - - **值输入区**: - - 文本输入框 (用于文本、部分数字属性)。 - - 日期选择器 (用于日期属性)。 - - 特定值下拉框 (例如用于状态、优先级等预定义值的属性)。 - - **高级编辑按钮 `[ ]` (可选)**: 对于复杂条件,允许用户切换到文本模式,直接编写类似 `filterUtils.ts` 中的表达式片段。 - - **删除按钮 `[🗑️]`**: 删除此筛选条件行。 -- **筛选条件组 (Filter Condition Group)**: - - 用户可以通过点击 `[+ 添加条件组]` 来创建一个嵌套的条件组。 - - 每个组内部也拥有自己的逻辑操作符(AND/OR)和一系列条件/子组。 - - 视觉上通过缩进和边框与父级条件区分。 -- **添加按钮**: - - `[+ 添加条件]`: 在当前层级(或选定的组内)添加一个新的筛选条件行。 - - `[+ 添加条件组]`: 在当前层级(或选定的组内)添加一个新的筛选条件组。 - -#### 2.3.3. 排序区域 (Sorting Area) -- **排序规则行 (Sort Criterion Row)**: - - **排序依据下拉框**: 列出可供排序的任务属性,与筛选属性类似,但通常是具有可比较性的属性(如:`截止日期`, `优先级`, `内容`, `创建日期`等)。 - - **顺序下拉框**: `升序 (Ascending)` 或 `降序 (Descending)`。 - - **调整优先级按钮 `[⬆️][⬇️]`**: 允许用户上下移动排序规则,决定排序的优先顺序(首要排序依据、次要排序依据等)。 - - **删除按钮 `[🗑️]`**: 删除此排序规则。 -- **添加按钮 `[+ 添加排序规则]`**: 添加一个新的排序规则行。 - -## 3. 交互模型 (Interaction Model) - -### 3.1. 打开弹窗 (Opening the Pop-up) -- 点击入口后,弹窗显示。 -- 默认情况下,弹窗可能加载当前全局应用的筛选和排序配置,或者上一次在弹窗中编辑但未保存的临时配置,或者一个默认的空配置。 - -### 3.2. 预设管理 (Preset Management) -- **选择预设**: 从下拉菜单选择一个预设。界面下方的筛选和排序区域将更新以反映所选预设的配置。 -- **保存/更新预设**: - - 如果当前选择的是一个已存在的预设,并且用户修改了筛选或排序配置,"保存"按钮将变为可用。 - - 点击"保存",当前配置将覆盖所选预设。 -- **另存为新预设**: - - 用户点击"另存为..."按钮。 - - 弹出对话框要求输入新预设的名称。 - - 确认后,当前的筛选和排序配置将保存为一个新的预设条目,并自动选中这个新预设。 -- **删除预设**: - - 用户选择一个预设,然后点击"删除"按钮。 - - 弹出确认对话框。 - - 确认后,该预设从列表中移除。如果被删除的是当前加载的预设,则界面可能清空或加载一个默认状态。 - -### 3.3. 筛选配置 (Filter Configuration) -- **添加条件/条件组**: 点击相应按钮,在当前焦点所在的层级(顶层或某个组内)添加新的条件行或条件组。 -- **删除条件/条件组**: 点击条件行或条件组旁边的 `[🗑️]` 图标。如果删除组,则其内部所有条件一并删除。 -- **修改条件**: 用户直接在条件行的属性、操作符、值输入区进行修改。操作符列表会根据属性类型动态变化。 -- **修改组逻辑**: 更改条件组头部的"所有/任一 (AND/OR)"选择。 - -### 3.4. 排序配置 (Sort Configuration) -- **添加排序规则**: 点击 `[+ 添加排序规则]` 按钮,在列表末尾添加一个新的排序规则行。 -- **删除排序规则**: 点击规则行旁边的 `[🗑️]` 图标。 -- **修改排序规则**: 用户直接在规则行的"排序依据"和"顺序"下拉框中进行选择。 -- **调整排序优先级**: 点击 `[⬆️]` 或 `[⬇️]` 按钮,改变规则在列表中的位置。列表顶部的规则具有最高排序优先级。 - -### 3.5. 保存与应用 (Saving and Applying) -- 用户完成配置后,点击 `[ 应用/保存配置 ]` 按钮。 -- 当前弹窗内的筛选和排序配置(无论是否属于某个预设)将被保存为全局/默认的视图配置。 -- 触发一个事件或机制,通知所有相关的任务视图更新其显示,根据新的配置重新筛选和排序任务。 -- 弹窗关闭。 -- 如果用户点击 `[ 取消 ]`,则所有未通过预设"保存"或未点击 `[ 应用/保存配置 ]` 的更改都将丢失,弹窗关闭。 - -## 4. 数据结构与配置 (Data Structures and Configuration) - -### 4.1. 预设对象结构 (Preset Object Structure) -```typescript -interface ViewPreset { - id: string; // Unique identifier for the preset - name: string; // User-defined name for the preset - filterConfig: FilterConfig; // Structure defined below - sortConfig: SortConfigItem[]; // Array of sort criteria -} -``` - -### 4.2. 筛选配置结构 (Filter Configuration Structure) -此结构需要能够映射到 `filterUtils.ts` 中的 `FilterNode`。UI上的配置将转换为 `FilterNode` 树。 - -```typescript -// Represents a single filter condition UI row -interface FilterConditionItem { - property: string; // e.g., 'content', 'dueDate', 'priority', 'tags.myTag' - operator: string; // e.g., 'contains', 'is', '>=', 'isEmpty' - value?: any; // Value for the condition, type depends on property and operator - // For advanced mode, could store a raw expression string - // rawExpression?: string; -} - -// Represents a group of filter conditions in the UI -interface FilterGroupItem { - logicalOperator: 'AND' | 'OR'; // How conditions/groups within this group are combined - items: (FilterConditionItem | FilterGroupItem)[]; // Can contain conditions or nested groups -} - -// Top-level filter configuration from the UI -type FilterConfig = FilterGroupItem; -``` -**转换逻辑**: -- `FilterGroupItem` 将递归地转换为 `FilterNode` 的 `AND` 或 `OR` 类型。 -- `FilterConditionItem` 将转换为 `FilterNode` 的 `TEXT`, `TAG`, `PRIORITY`, `DATE` 等类型,具体取决于 `property` 和 `operator`。 - - 例如: `{ property: 'content', operator: 'contains', value: 'test' }` -> `{ type: 'TEXT', value: 'test' }` (简化示例,实际转换会更复杂,例如处理大小写,或根据操作符调整节点类型或值) - - `{ property: 'priority', operator: '=', value: 'High' }` -> `{ type: 'PRIORITY', op: '=', value: 'High' }` - - `{ property: 'dueDate', operator: '<', value: '2024-12-31' }` -> `{ type: 'DATE', op: '<', value: '2024-12-31' }` - -### 4.3. 排序配置结构 (Sort Configuration Structure) -此结构直接对应 `sortTaskCommands.ts` 中的 `SortCriterion`。 - -```typescript -interface SortConfigItem { - field: string; // Property to sort by (e.g., 'dueDate', 'priority', 'content') - order: 'asc' | 'desc'; // Sort order -} - -// The overall sort configuration will be an array of these items: -// type SortConfiguration = SortConfigItem[]; -``` - -### 4.4. 存储 (Storage) -- **预设列表 (`ViewPreset[]`)**: 存储在插件的设置 (`settings.json`) 中。 -- **当前全局配置**: 当前应用的筛选 (`FilterConfig`) 和排序 (`SortConfigItem[]`) 配置也应存储在插件设置中,作为所有视图的默认配置。预设仅仅是快速加载这些配置的一种方式。 - -## 5. 与现有系统集成 (Integration with Existing Systems) - -### 5.1. `filterUtils.ts` -- **UI 到 `FilterNode` 转换**: 需要编写逻辑将用户在筛选区域创建的 `FilterConfig` (嵌套的 `FilterGroupItem` 和 `FilterConditionItem`) 转换为 `filterUtils.ts` 可以理解的 `FilterNode` 树结构。 -- **应用筛选**: 一旦 `FilterNode` 树生成,视图将使用 `evaluateFilterNode` 函数来判断每个任务是否满足筛选条件。 -- **属性和操作符**: 需要确保UI中提供的属性和操作符能够有效地映射到 `filterUtils.ts` 中各种 `FilterNode` 类型的判断逻辑。例如,`PRIORITY` 节点需要 `op` 和 `value`,`DATE` 节点也类似。 - -### 5.2. `sortTaskCommands.ts` -- **UI 到 `SortCriterion[]` 转换**: UI 排序区域的配置 (`SortConfigItem[]`) 可以直接用作 `sortTaskCommands.ts` 中 `sortTasks` 函数所需的 `criteria` 参数。 -- **应用排序**: 视图将使用 `sortTasks` 函数(或其核心比较逻辑 `compareTasks`),传入从UI配置生成的 `SortConfigItem[]` 数组和插件设置,对筛选后的任务列表进行排序。 -- **可用排序字段**: UI 中"排序依据"下拉框应列出 `compareTasks` 函数支持的排序字段。 - -### 5.3. 视图更新机制 (View Update Mechanism) -- 当用户点击 `[ 应用/保存配置 ]` 按钮并成功保存新的全局筛选/排序配置后: - - 插件需要将新的配置(转换后的 `FilterNode` 和 `SortCriterion[]`)存储到其全局设置中。 - - 插件需要触发一个全局事件或调用一个方法,通知所有当前打开的、依赖此配置的任务视图进行刷新。 - - 各视图在收到通知后,会重新获取任务数据,应用新的全局筛选条件和排序规则,然后重新渲染其内容。 - -## 6. 未来展望 (Future Enhancements) - -- **共享预设**: 允许用户导入/导出预设配置。 -- **更高级的筛选操作符**: 在UI中直接支持更复杂的筛选逻辑,如正则表达式匹配。 -- **实时预览**: 在弹窗中配置时,下方或侧边有一个小区域实时显示符合当前筛选/排序条件的部分任务预览。 -- **视图特定配置**: 除了全局配置外,允许用户为单个特定视图覆盖全局配置,并拥有独立的预设(这会增加复杂性,需要权衡)。 -- **自然语言输入筛选**: 允许用户通过类似 "tasks due this week with high priority" 的自然语言短语创建筛选。 - -## 7. 待定问题 (Open Questions) - -- **属性列表的来源**: "属性"下拉列表是硬编码的,还是动态生成的(例如,基于用户在 frontmatter 中定义的属性)?初期可以硬编码核心属性,未来可考虑扩展。 -- **"无值"的具体实现**: 筛选操作符 "为空 (is empty)" / "不为空 (is not empty)" 如何准确对应到任务数据的实际空值情况 (e.g., `undefined`, `null`, 空字符串)。 -- **性能**: 对于非常大的任务列表,频繁更改筛选和排序配置并实时更新所有视图可能会有性能影响,需要关注和优化。 -- **错误处理和用户反馈**: 当用户输入无效的筛选值或配置冲突时,如何提供清晰的错误提示。 - -示例代码: - -```HTML - - - - - - 可堆叠筛选器 UI - 紧凑型 - - - - - - -
-
-
- - - 筛选器组满足条件 -
- -
- - - -
- -
- -
-
-
-

当前筛选器状态 (JSON):

-
 
-        
-
- - - - -``` \ No newline at end of file diff --git a/design/habit.md b/design/habit.md deleted file mode 100644 index 01d56335..00000000 --- a/design/habit.md +++ /dev/null @@ -1,509 +0,0 @@ -# Habit Tracking 功能设计文档 - -## 1. 概述 - -Habit Tracking 是 Task Genius 插件的一个扩展功能模块,旨在利用 Obsidian 的日记功能,提供习惯追踪和可视化能力。它允许用户定义习惯,并通过每日笔记中的元数据自动记录完成情况,与现有的任务管理系统互补。 - -## 2. 核心功能 - -- 习惯定义与管理 -- 基于日记元数据的习惯完成情况自动索引 -- 习惯日历视图 -- 习惯统计与 streaks (连续完成天数) 展示 -- 与 Task View 的潜在集成 (例如,将习惯打卡显示为特殊任务) - -## 3. 数据结构 - -### 3.1 习惯定义 - -```typescript -interface Habit { - id: string; // unique identifier (e.g., 'habit-meditation') - name: string; // Display name (e.g., "Meditation") - description?: string; // Optional description - goal?: string; // Description of the goal (e.g., "Meditate for 10 minutes daily") - // Frequency definition - determines expected occurrences. - // 'daily' is simple. 'weekly' might imply checking specific weekdays. 'monthly' specific month days. - // number could mean 'every N days'. Needs careful definition for streak calculation. - frequency: 'daily' | 'weekly' | 'monthly' | number; - metadataKey: string; // The frontmatter key used to track this habit (e.g., 'meditation-done') - // Define the type of habit, influencing how data might be stored and interpreted. - // Corresponds to the HabitProps types defined later for UI/consumption. - type: 'daily' | 'count' | 'scheduled' | 'mapping'; - // Defines how to determine if the habit is 'completed' based on the metadata value. - // Default: { condition: 'exists' } - any value present means completed. - completionCondition?: { - condition: 'exists' | 'equals' | 'greaterThan' | 'lessThan' | 'contains'; // Type of comparison - value?: any; // The value to compare against (for 'equals', 'greaterThan', 'lessThan', 'contains') - }; - // Potential future fields: icon, color, target value (e.g., for count type) -} -``` - -### 3.2 习惯日志条目 (内部索引结构) - -```typescript -interface HabitLogEntry { - habitId: string; // Reference to Habit.id - date: number; // Timestamp (representing the start of the day) of the log entry - // Determined based on the Habit's completionCondition and the raw value found in metadata. - completed: boolean; - filePath: string; // Path to the daily note file - // Stores the raw value found in the frontmatter associated with the habit's metadataKey for this date. - // Useful for 'count', 'mapping', or 'scheduled' types, or showing specific recorded data. - value?: any; -} -``` - -### 3.3 习惯缓存结构 - -```typescript -interface HabitCache { - habits: Map; // habitId -> Habit definition - logs: Map; // habitId -> sorted array of log entries - // Index for quick lookup by date might be needed - logsByDate: Map; // dateTimestamp -> entries for that day - // Index for file path to related habit logs - logsByFile: Map; // filePath -> entries in that file -} -``` - -## 4. 索引方案 - -### 4.1 挑战 - -与 Task View 不同,习惯数据并非来自特定的文本行格式 (`- [ ]`), 而是分散在大量日记文件的 Frontmatter 元数据中。这要求一个不同的索引策略。 - -### 4.2 索引流程 - -1. **习惯定义加载**: 从插件设置或指定配置文件中加载用户定义的 `Habit` 列表。`metadataKey` 是关键,用于关联元数据。 -2. **初始扫描**: - * 识别日记文件 (基于 Obsidian 日历插件设置或用户自定义的路径/格式)。 - * 使用 `app.metadataCache` 访问每个日记文件的 Frontmatter。 - * 遍历已定义的 `Habit`,检查每个日记文件的 Frontmatter 是否包含对应的 `metadataKey`。 - * 如果找到 `metadataKey`,解析其值 (通常是 `true` 或具体数值),并创建 `HabitLogEntry`。 - * 构建 `HabitCache` 中的 `logs`, `logsByDate`, 和 `logsByFile` 索引。 -3. **实时更新**: - * 监听 `app.metadataCache.on('changed', (file, data, cache) => ...)` 事件。 - * 当一个文件的元数据变化时,检查该文件是否是日记文件。 - * 如果 是日记文件,重新解析其 Frontmatter 中与已定义习惯相关的 `metadataKey`。 - * 更新 `HabitCache` 中与该文件和日期相关的 `HabitLogEntry` 以及 `logsByDate`, `logsByFile` 索引。 - * **注意**: 需要高效地处理 `metadataCache` 事件,避免对非日记文件或无关元数据变化的过多处理。可能需要维护一个已知日记文件的集合。 -4. **习惯定义变更**: 如果用户添加、删除或修改了 `Habit` 定义 (特别是 `metadataKey`),可能需要触发一次部分或全部的重新扫描来更新索引。 - -### 4.3 实现细节 - -```typescript -class HabitIndexer extends Component { - private habitCache: HabitCache; - private dailyNoteFormat: string; // Store the daily note format (e.g., YYYY-MM-DD) - - constructor(plugin: TaskGeniusPlugin) { - this.habitCache = this.initEmptyCache(); - // Load habit definitions from settings - this.loadHabitDefinitions(plugin.settings.habits); - // Determine daily note format/location - this.dailyNoteFormat = this.getDailyNoteFormat(); - this.setupMetadataListener(plugin); - } - - loadHabitDefinitions(definitions: Habit[]): void { - this.habitCache.habits.clear(); - definitions.forEach(habit => { - this.habitCache.habits.set(habit.id, habit); - // Initialize log arrays if not present - if (!this.habitCache.logs.has(habit.id)) { - this.habitCache.logs.set(habit.id, []); - } - }); - } - - async initialScan(plugin: TaskGeniusPlugin): Promise { - const files = plugin.app.vault.getMarkdownFiles(); - // Clear existing logs before scan - this.clearLogs(); - - for (const file of files) { - if (this.isDailyNote(file.path)) { - await this.indexFileMetadata(file, plugin.app.metadataCache); - } - } - this.sortAllLogsByDate(); // Ensure logs are sorted after initial scan - } - - private async indexFileMetadata(file: TFile, metadataCache: MetadataCache): Promise { - const fileCache = metadataCache.getFileCache(file); - const frontmatter = fileCache?.frontmatter; - const dateFromName = this.getDateFromPath(file.path); // Extract date from filename/path - - if (!frontmatter || !dateFromName) return; - - const dateTimestamp = dateFromName.getTime(); - - // Remove existing entries for this file before adding new ones - this.removeLogsForFile(file.path); - let fileLogs: { habitId: string, date: number }[] = []; - - - for (const [habitId, habit] of this.habitCache.habits.entries()) { - if (frontmatter.hasOwnProperty(habit.metadataKey)) { - const metadataValue = frontmatter[habit.metadataKey]; - - // Determine completion status based on the configured condition - let completed = false; - const conditionConfig = habit.completionCondition || { condition: 'exists' }; // Default to 'exists' - - if (metadataValue !== undefined && metadataValue !== null) { - switch (conditionConfig.condition) { - case 'exists': - completed = true; // Value exists - break; - case 'equals': - // Use strict equality for predictability - completed = metadataValue === conditionConfig.value; - break; - case 'greaterThan': - completed = typeof metadataValue === 'number' && typeof conditionConfig.value === 'number' && metadataValue > conditionConfig.value; - break; - case 'lessThan': - completed = typeof metadataValue === 'number' && typeof conditionConfig.value === 'number' && metadataValue < conditionConfig.value; - break; - case 'contains': - // Basic check - might need refinement based on expected data types (string, array) - if (typeof metadataValue === 'string' && typeof conditionConfig.value === 'string') { - completed = metadataValue.includes(conditionConfig.value); - } else if (Array.isArray(metadataValue) && conditionConfig.value) { - completed = metadataValue.includes(conditionConfig.value); - } - break; - default: - // Fallback or default behavior if condition is unknown - treat as 'exists' - completed = true; - } - } - // If metadataValue is null/undefined, 'completed' remains false. - - const logEntry: HabitLogEntry = { - habitId, - date: dateTimestamp, - completed, // Use the calculated completion status - filePath: file.path, - value: metadataValue // Store the raw value found in frontmatter - }; - - // Add to main logs map - const logs = this.habitCache.logs.get(habitId) || []; - logs.push(logEntry); - this.habitCache.logs.set(habitId, logs); // Re-set in case it was new - - // Add to logsByDate index - Note: we store `completed` status here too - const dateLogs = this.habitCache.logsByDate.get(dateTimestamp) || []; - // Consider if the structure of logsByDate needs the raw 'value' as well - dateLogs.push({ habitId, filePath: file.path, completed }); - this.habitCache.logsByDate.set(dateTimestamp, dateLogs); - - // Add to logsByFile index (for efficient removal/update) - fileLogs.push({ habitId, date: dateTimestamp }); - } - } - if (fileLogs.length > 0) { - this.habitCache.logsByFile.set(file.path, fileLogs); - } - } - - private setupMetadataListener(plugin: TaskGeniusPlugin) { - plugin.registerEvent( - plugin.app.metadataCache.on('changed', async (file, _, cache) => { - if (this.isDailyNote(file.path) && this.containsRelevantMetadata(cache.frontmatter)) { - console.log(`Metadata changed for daily note: ${file.path}, re-indexing habit data.`); - await this.indexFileMetadata(file, plugin.app.metadataCache); - this.sortLogsForHabitsInFile(file.path); // Re-sort affected logs - // Trigger UI update if necessary - plugin.eventBus.emit('habit-index-updated'); - } - }) - ); - } - - // --- Helper methods --- - - private isDailyNote(filePath: string): boolean { - // Implementation depends on daily note settings (e.g., regex match path) - // Placeholder: Assumes YYYY-MM-DD.md format in root - return /^\d{4}-\d{2}-\d{2}\.md$/.test(filePath.split('/').pop() || ''); - } - - private getDateFromPath(filePath: string): Date | null { - // Extract date based on isDailyNote logic - const match = filePath.match(/(\d{4}-\d{2}-\d{2})\.md$/); - if (match) { - const date = new Date(match[1] + 'T00:00:00'); // Use T00:00:00 for consistency - return isNaN(date.getTime()) ? null : date; - } - return null; - } - - private containsRelevantMetadata(frontmatter: any): boolean { - if (!frontmatter) return false; - for (const habit of this.habitCache.habits.values()) { - if (frontmatter.hasOwnProperty(habit.metadataKey)) { - return true; - } - } - return false; - } - - private initEmptyCache(): HabitCache { - return { - habits: new Map(), - logs: new Map(), - logsByDate: new Map(), - logsByFile: new Map(), - }; - } - - private clearLogs(): void { - this.habitCache.logs.clear(); - this.habitCache.logsByDate.clear(); - this.habitCache.logsByFile.clear(); - // Re-initialize empty arrays for known habits - this.habitCache.habits.forEach(habit => { - this.habitCache.logs.set(habit.id, []); - }); - } - - private removeLogsForFile(filePath: string): void { - const fileEntries = this.habitCache.logsByFile.get(filePath); - if (!fileEntries) return; - - fileEntries.forEach(({ habitId, date }) => { - // Remove from main logs - const habitLogs = this.habitCache.logs.get(habitId); - if (habitLogs) { - const index = habitLogs.findIndex(log => log.filePath === filePath && log.date === date); - if (index > -1) { - habitLogs.splice(index, 1); - } - } - // Remove from logsByDate - const dateLogs = this.habitCache.logsByDate.get(date); - if (dateLogs) { - const index = dateLogs.findIndex(log => log.filePath === filePath && log.habitId === habitId); - if (index > -1) { - dateLogs.splice(index, 1); - } - if(dateLogs.length === 0) { - this.habitCache.logsByDate.delete(date); - } - } - }); - this.habitCache.logsByFile.delete(filePath); - } - - private sortLogsForHabitsInFile(filePath: string): void { - const fileEntries = this.habitCache.logsByFile.get(filePath); - if (!fileEntries) return; - const affectedHabitIds = new Set(fileEntries.map(e => e.habitId)); - affectedHabitIds.forEach(habitId => { - const logs = this.habitCache.logs.get(habitId); - if (logs) { - logs.sort((a, b) => a.date - b.date); - } - }); - } - - private sortAllLogsByDate(): void { - this.habitCache.logs.forEach(logs => { - logs.sort((a, b) => a.date - b.date); - }); - } - - // Other methods: getDailyNoteFormat, etc. -} -``` - -### 习惯的种类和类型 - -```typescript -// 基础习惯类型 -interface BaseHabitProps { - id: string; - name: string; - icon: string | React.ReactNode; - completions: Record; - - properties?: string[]; -} - -// 日常习惯类型 -export interface DailyHabitProps extends BaseHabitProps { - type: 'daily'; -} - -// 计数习惯类型 -export interface CountHabitProps extends BaseHabitProps { - type: 'count'; - maxCount: number; - notice?: string; -} - -export interface ScheduledEvent { - name: string; - details: string; -} - -export interface ScheduledHabitProps extends BaseHabitProps { - type: 'scheduled'; - events: ScheduledEvent[]; - completions: Record>; -} - -export interface MappingHabitProps extends BaseHabitProps { - type: 'mapping'; - mapping: Record; - completions: Record; -} - -// 所有习惯类型的联合 -export type HabitProps = DailyHabitProps | CountHabitProps | ScheduledHabitProps | MappingHabitProps; - -// 习惯卡片属性 -export interface HabitCardProps { - habit: HabitProps; - toggleCompletion: (habitId: string) => void; - triggerConfetti?: (pos: { - x: number - y: number - width?: number - height?: number - }) => void; - children?: React.ReactNode; -} - -interface MappingHabitCardProps extends HabitCardProps { - toggleCompletion: (habitId: string, value: number) => void; -} - -interface ScheduledHabitCardProps extends HabitCardProps { - toggleCompletion: (habitId: string, { - id, - details - }: { - id: string; - details: string; - }) => void; -} - -``` - -### 设置和相关类型 - -```typescript -import { LucideIcon } from "lucide-react"; - -// 基础习惯类型 -interface BaseHabitProps { - id: string; - name: string; - icon: string | React.ReactNode; - completions: Record; - - properties?: string[]; -} - -// 日常习惯类型 -export interface DailyHabitProps extends BaseHabitProps { - type: 'daily'; -} - -// 计数习惯类型 -export interface CountHabitProps extends BaseHabitProps { - type: 'count'; - maxCount: number; - notice?: string; -} - -export interface ScheduledEvent { - name: string; - details: string; -} - -export interface ScheduledHabitProps extends BaseHabitProps { - type: 'scheduled'; - events: ScheduledEvent[]; - completions: Record>; -} - -export interface MappingHabitProps extends BaseHabitProps { - type: 'mapping'; - mapping: Record; - completions: Record; -} - -// 所有习惯类型的联合 -export type HabitProps = DailyHabitProps | CountHabitProps | ScheduledHabitProps | MappingHabitProps; - -// 习惯卡片属性 -export interface HabitCardProps { - habit: HabitProps; - toggleCompletion: (habitId: string) => void; - triggerConfetti?: (pos: { - x: number - y: number - width?: number - height?: number - }) => void; - children?: React.ReactNode; -} - -interface MappingHabitCardProps extends HabitCardProps { - toggleCompletion: (habitId: string, value: number) => void; -} - -interface ScheduledHabitCardProps extends HabitCardProps { - toggleCompletion: (habitId: string, { - id, - details - }: { - id: string; - details: string; - }) => void; -} - -``` - - -## 5. 习惯视图 UI - -- **日历视图**: 显示一个日历(例如,月视图),其中每个日期单元格通过视觉方式(如颜色编码的点)指示所选习惯的完成状态。点击某一天可以导航到对应的日记笔记。 -- **列表/统计视图**: 显示已定义习惯的列表。对于每个习惯,展示: - - 当前连续完成天数(连续完成的天数/周期) - - 最长连续完成记录 - - 完成百分比(例如,过去30天内) - - 最近活动日志 -- **筛选/选择**: 允许用户选择在视图中显示哪些习惯。 -- **与任务视图集成**: 如果某个习惯当天尚未记录,可能在相关的任务视图透视图中将其显示为"为今天记录习惯X"的循环任务。 - -## 6. 设置 - -- **习惯定义**: 专门的部分用于添加、编辑和删除习惯(`id`、`name`、`metadataKey`、`frequency`等)。 -- **日记笔记配置**: 允许用户指定路径模式或依赖周期性笔记/日历插件设置来识别日记笔记。 -- **视觉设置**: 日历外观、颜色等选项。 -- **数据管理**: 触发习惯数据完全重新扫描/重新索引的按钮。 - -## 7. 性能与可扩展性 - -- **元数据缓存依赖**: 严重依赖Obsidian的`metadataCache`。性能取决于其效率。 -- **高效更新**: `metadataCache.on('changed')`处理程序必须高效,快速过滤无关变更并仅更新`HabitCache`中必要的部分。 -- **初始扫描时间**: 对于包含数千个日记笔记的保险库,初始扫描可能需要时间。考虑后台处理或进度指示。 -- **缓存大小**: `HabitCache`大小可能会显著增长。确保高效的数据结构,并考虑如果持久化存储,可能的序列化/反序列化性能。更新时需要谨慎管理日志排序。 - -## 8. 开放问题与未来考虑 - -- 如何处理非每日频率的习惯(例如,每周)?基于`date`的索引可能需要调整。 - - 怎么实现每天 -- 如何为非每日习惯定义*预期*完成情况,以准确计算连续完成天数? -- 支持数值型习惯值(例如,跟踪分钟数、阅读页数)并将其可视化。 -- 更复杂的连续完成天数计算(处理非每日习惯的跳过天数)。 -- 导出/导入习惯数据和统计信息。 -- 习惯数据的高级查询/筛选。 diff --git a/design/progressbar.md b/design/progressbar.md deleted file mode 100644 index ad3efd72..00000000 --- a/design/progressbar.md +++ /dev/null @@ -1,599 +0,0 @@ -# Progress Bar Text Formatter Design Document - -## 1. Overview - -当前进度条实现在自定义方面存在局限性,特别是对于非百分比显示。我们将设计一个更灵活的文本格式化系统,允许: - -1. 任何显示模式下的完全文本自定义 -2. 自定义任务计数的格式 -3. 基于进度百分比范围的动态文本 -4. 数据计算和文本呈现的更好分离 -5. 与现有任务状态标记的集成 - -## 2. 数据模型 - -```typescript -interface ProgressData { - completed: number; - total: number; - inProgress: number; - abandoned: number; - notStarted: number; - planned: number; - - // 派生数据(按需计算) - percentages: { - completed: number; - inProgress: number; - abandoned: number; - planned: number; - notStarted: number; - }; -} - -interface ProgressFormatOptions { - // 显示模式 - displayMode: "percentage" | "fraction" | "custom" | "range-based"; - - // 自定义显示模式 - customFormat: string; // 使用占位符如 {{COMPLETED}}, {{TOTAL}} 等 - - // 根据百分比范围的自定义文本模板(保留原有设计) - progressRanges: Array<{ - min: number; - max: number; - text: string; // 带占位符如 {{PROGRESS}} - }>; - - // 不同状态的显示符号(默认使用相应taskStatus的第一个字符) - statusDisplaySymbols: { - completed: string; // 默认: "✓" - inProgress: string; // 默认: "⟳" - abandoned: string; // 默认: "✗" - planned: string; // 默认: "?" - notStarted: string; // 默认: " " - }; -} -``` - -## 3. 实现结构 - -### 3.1 数据计算层 - -```typescript -class ProgressCalculator { - // 从原始任务计数计算所有派生数据 - static calculateProgressData(data: Partial): ProgressData { - // 为缺失值填充默认值 - const fullData: ProgressData = { - completed: data.completed || 0, - total: data.total || 0, - inProgress: data.inProgress || 0, - abandoned: data.abandoned || 0, - notStarted: data.notStarted || 0, - planned: data.planned || 0, - percentages: { completed: 0, inProgress: 0, abandoned: 0, planned: 0, notStarted: 0 } - }; - - // 如果总数 > 0,计算百分比 - if (fullData.total > 0) { - fullData.percentages = { - completed: Math.round((fullData.completed / fullData.total) * 10000) / 100, - inProgress: Math.round((fullData.inProgress / fullData.total) * 10000) / 100, - abandoned: Math.round((fullData.abandoned / fullData.total) * 10000) / 100, - planned: Math.round((fullData.planned / fullData.total) * 10000) / 100, - notStarted: Math.round((fullData.notStarted / fullData.total) * 10000) / 100 - }; - } - - return fullData; - } -} -``` - -### 3.2 文本格式化器 - -```typescript -class ProgressTextFormatter { - // 从任务状态初始化显示符号 - static initStatusDisplaySymbols( - taskStatuses: { - completed: string; - inProgress: string; - abandoned: string; - notStarted: string; - planned: string; - }, - customSymbols?: Partial - ): ProgressFormatOptions['statusDisplaySymbols'] { - // 从每个任务状态提取第一个字符作为默认符号 - const getDefaultSymbol = (statusStr: string, defaultSymbol: string): string => { - const parts = statusStr.split('|'); - return parts[0].trim().charAt(0) || defaultSymbol; - }; - - return { - completed: customSymbols?.completed || getDefaultSymbol(taskStatuses.completed, "✓"), - inProgress: customSymbols?.inProgress || getDefaultSymbol(taskStatuses.inProgress, "⟳"), - abandoned: customSymbols?.abandoned || getDefaultSymbol(taskStatuses.abandoned, "✗"), - planned: customSymbols?.planned || getDefaultSymbol(taskStatuses.planned, "?"), - notStarted: customSymbols?.notStarted || getDefaultSymbol(taskStatuses.notStarted, " ") - }; - } - - // 替换模板字符串中的所有占位符 - static formatTemplate( - template: string, - data: ProgressData, - options: ProgressFormatOptions, - taskStatuses: { - completed: string; - inProgress: string; - abandoned: string; - notStarted: string; - planned: string; - } - ): string { - // 确保我们有显示符号 - const displaySymbols = this.initStatusDisplaySymbols(taskStatuses, options.statusDisplaySymbols); - - // 基本替换 - let result = template - .replace(/{{COMPLETED}}/g, data.completed.toString()) - .replace(/{{TOTAL}}/g, data.total.toString()) - .replace(/{{IN_PROGRESS}}/g, data.inProgress.toString()) - .replace(/{{ABANDONED}}/g, data.abandoned.toString()) - .replace(/{{PLANNED}}/g, data.planned.toString()) - .replace(/{{NOT_STARTED}}/g, data.notStarted.toString()) - .replace(/{{PERCENT}}/g, data.percentages.completed.toString()) - .replace(/{{PROGRESS}}/g, data.percentages.completed.toString()) // 兼容原有占位符 - .replace(/{{PERCENT_IN_PROGRESS}}/g, data.percentages.inProgress.toString()) - .replace(/{{PERCENT_ABANDONED}}/g, data.percentages.abandoned.toString()) - .replace(/{{PERCENT_PLANNED}}/g, data.percentages.planned.toString()) - .replace(/{{COMPLETED_SYMBOL}}/g, displaySymbols.completed) - .replace(/{{IN_PROGRESS_SYMBOL}}/g, displaySymbols.inProgress) - .replace(/{{ABANDONED_SYMBOL}}/g, displaySymbols.abandoned) - .replace(/{{PLANNED_SYMBOL}}/g, displaySymbols.planned) - .replace(/{{NOT_STARTED_SYMBOL}}/g, displaySymbols.notStarted); - - // 支持简单的表达式计算,例如进度条文本生成 - // 处理形如 ${=expression} 的模式 - result = result.replace(/\${=(.+?)}/g, (match, expr) => { - try { - // 使用Function构造器安全地执行表达式,提供data和displaySymbols作为上下文 - return new Function('data', 'displaySymbols', `return ${expr}`)(data, displaySymbols); - } catch (e) { - console.error("Error evaluating expression:", expr, e); - return match; // 出错时返回原始匹配 - } - }); - - return result; - } - - // 基于进度范围获取文本模板 - 保留原有设计 - static getRangeBasedTemplate(data: ProgressData, options: ProgressFormatOptions): string { - const percent = data.percentages.completed; - - // 检查是否有匹配的范围 - if (options.progressRanges && options.progressRanges.length > 0) { - for (const range of options.progressRanges) { - if (percent >= range.min && percent <= range.max) { - return range.text; - } - } - } - - // 如果没有匹配的范围,返回默认格式 - return "{{PROGRESS}}%"; - } - - // 基于显示模式获取适当的文本模板 - static getTextTemplate(data: ProgressData, options: ProgressFormatOptions): string { - // 基于显示模式的默认选项 - switch(options.displayMode) { - case "percentage": - return "{{PERCENT}}%"; - case "fraction": - return "[{{COMPLETED}}/{{TOTAL}}]"; - case "range-based": - return this.getRangeBasedTemplate(data, options); - case "custom": - return options.customFormat; - default: - // 保持向后兼容性:如果启用了范围或百分比,使用相应格式 - if (options.progressRanges && options.progressRanges.length > 0) { - return this.getRangeBasedTemplate(data, options); - } else { - return "[{{COMPLETED}}/{{TOTAL}}]"; - } - } - } - - // 主要格式化函数:计算数据并生成最终的文本表示 - static formatProgressText( - rawData: Partial, - options: ProgressFormatOptions, - taskStatuses: { - completed: string; - inProgress: string; - abandoned: string; - notStarted: string; - planned: string; - } - ): string { - // 计算完整数据 - const data = ProgressCalculator.calculateProgressData(rawData); - - // 获取适当的模板 - const template = this.getTextTemplate(data, options); - - // 使用模板生成最终文本 - return this.formatTemplate(template, data, options, taskStatuses); - } -} -``` - -## 4. 设置界面 - -```typescript -// 在 TaskProgressBarSettingTab 类中 -addProgressBarTextSettings() { - const { containerEl } = this; - - new Setting(containerEl) - .setName(t("进度条文本格式")) - .setHeading(); - - new Setting(containerEl) - .setName(t("显示模式")) - .setDesc(t("选择如何显示任务进度")) - .addDropdown(dropdown => { - dropdown - .addOption("percentage", t("百分比")) - .addOption("fraction", t("分数")) - .addOption("range-based", t("基于进度范围")) - .addOption("custom", t("自定义格式")) - .setValue(this.plugin.settings.progressBarFormat.displayMode || "fraction") - .onChange(async (value) => { - this.plugin.settings.progressBarFormat.displayMode = value; - this.applySettingsUpdate(); - // 有条件地显示自定义格式设置 - this.display(); - }); - }); - - // 仅在选择自定义格式时显示 - if (this.plugin.settings.progressBarFormat.displayMode === "custom") { - new Setting(containerEl) - .setName(t("自定义格式")) - .setDesc(t("使用占位符如 {{COMPLETED}}, {{TOTAL}}, {{PERCENT}} 等")) - .addText(text => { - text.setValue(this.plugin.settings.progressBarFormat.customFormat || "[{{COMPLETED}}/{{TOTAL}}]") - .setPlaceholder("[{{COMPLETED}}/{{TOTAL}}]") - .onChange(async (value) => { - this.plugin.settings.progressBarFormat.customFormat = value; - this.applySettingsUpdate(); - }); - }); - - // 添加占位符的帮助提示 - containerEl.createEl("div", { - cls: "setting-item-description", - text: t("可用占位符: {{COMPLETED}}, {{TOTAL}}, {{IN_PROGRESS}}, {{ABANDONED}}, {{PLANNED}}, {{NOT_STARTED}}, {{PERCENT}}, {{COMPLETED_SYMBOL}}, {{IN_PROGRESS_SYMBOL}}, {{ABANDONED_SYMBOL}}, {{PLANNED_SYMBOL}}, {{NOT_STARTED_SYMBOL}}") - }); - - // 高级表达式示例 - containerEl.createEl("div", { - cls: "setting-item-description", - text: t("高级用法: 您可以使用 ${= } 包裹JavaScript表达式,比如: ${=\"=\".repeat(Math.floor(data.percentages.completed/10))}") - }); - } - - // 基于范围的进度文本 (保留原有设计) - if (this.plugin.settings.progressBarFormat.displayMode === "range-based" || this.plugin.settings.progressBarFormat.displayMode === undefined) { - this.addProgressRangesSettings(); - } - - // 显示符号设置 - new Setting(containerEl) - .setName(t("显示符号")) - .setDesc(t("自定义进度条文本中使用的符号(默认使用任务状态标记)")); - - // 从任务状态获取默认符号 - const displaySymbols = ProgressTextFormatter.initStatusDisplaySymbols(this.plugin.settings.taskStatuses); - - const statusTypes = [ - { id: "completed", name: t("已完成"), default: displaySymbols.completed }, - { id: "inProgress", name: t("进行中"), default: displaySymbols.inProgress }, - { id: "abandoned", name: t("已放弃"), default: displaySymbols.abandoned }, - { id: "notStarted", name: t("未开始"), default: displaySymbols.notStarted }, - { id: "planned", name: t("已计划"), default: displaySymbols.planned } - ]; - - for (const statusType of statusTypes) { - new Setting(containerEl) - .setName(statusType.name) - .addText(text => { - const currentValue = this.plugin.settings.progressBarFormat.statusDisplaySymbols?.[statusType.id]; - text.setValue(currentValue || statusType.default) - .setPlaceholder(statusType.default) - .onChange(async (value) => { - if (!this.plugin.settings.progressBarFormat.statusDisplaySymbols) { - this.plugin.settings.progressBarFormat.statusDisplaySymbols = {} as any; - } - this.plugin.settings.progressBarFormat.statusDisplaySymbols[statusType.id] = value; - this.applySettingsUpdate(); - }); - }); - } - - // 添加进度条文本预览 - new Setting(containerEl) - .setName(t("预览")) - .setDesc(t("当前设置的进度条文本预览")); - - const previewContainer = containerEl.createDiv({ cls: "progress-bar-text-preview-container" }); - - // 创建示例数据用于预览 - const sampleData = { - completed: 3, - total: 5, - inProgress: 1, - abandoned: 0, - notStarted: 0, - planned: 1, - percentages: { - completed: 60, - inProgress: 20, - abandoned: 0, - planned: 20, - notStarted: 0 - } - }; - - // 渲染预览文本 - const previewText = ProgressTextFormatter.formatProgressText( - sampleData, - this.plugin.settings.progressBarFormat, - this.plugin.settings.taskStatuses - ); - - previewContainer.setText(previewText); -} - -// 保留原有的进度范围设置 - 与当前实现保持兼容 -addProgressRangesSettings() { - new Setting(this.containerEl) - .setName(t("进度范围")) - .setDesc( - t( - "定义进度范围及其对应的文本表示形式。使用 {{PROGRESS}} 作为百分比值的占位符。" - ) - ) - .setHeading(); - - // 显示现有范围 - this.plugin.settings.progressRanges.forEach((range, index) => { - new Setting(this.containerEl) - .setName(`范围 ${index + 1}: ${range.min}%-${range.max}%`) - .setDesc( - `使用 {{PROGRESS}} 作为百分比值的占位符` - ) - .addText((text) => - text - .setPlaceholder( - "包含 {{PROGRESS}} 占位符的模板文本" - ) - .setValue(range.text) - .onChange(async (value) => { - this.plugin.settings.progressRanges[index].text = - value; - this.applySettingsUpdate(); - }) - ) - .addButton((button) => { - button.setButtonText("删除").onClick(async () => { - this.plugin.settings.progressRanges.splice(index, 1); - this.applySettingsUpdate(); - this.display(); - }); - }); - }); - - new Setting(this.containerEl) - .setName(t("添加新范围")) - .setDesc(t("添加新的进度百分比范围及自定义文本")); - - // 添加新范围 - const newRangeSetting = new Setting(this.containerEl); - newRangeSetting.infoEl.detach(); - - newRangeSetting - .addText((text) => - text - .setPlaceholder(t("最小百分比 (0-100)")) - .setValue("") - .onChange(async (value) => { - // 将在用户点击添加按钮时处理 - }) - ) - .addText((text) => - text - .setPlaceholder(t("最大百分比 (0-100)")) - .setValue("") - .onChange(async (value) => { - // 将在用户点击添加按钮时处理 - }) - ) - .addText((text) => - text - .setPlaceholder(t("文本模板 (使用 {{PROGRESS}})")) - .setValue("") - .onChange(async (value) => { - // 将在用户点击添加按钮时处理 - }) - ) - .addButton((button) => { - button.setButtonText("添加").onClick(async () => { - const settingsContainer = button.buttonEl.parentElement; - if (!settingsContainer) return; - - const inputs = settingsContainer.querySelectorAll("input"); - if (inputs.length < 3) return; - - const min = parseInt(inputs[0].value); - const max = parseInt(inputs[1].value); - const text = inputs[2].value; - - if (isNaN(min) || isNaN(max) || !text) { - return; - } - - this.plugin.settings.progressRanges.push({ - min, - max, - text, - }); - - // 清空输入 - inputs[0].value = ""; - inputs[1].value = ""; - inputs[2].value = ""; - - this.applySettingsUpdate(); - this.display(); - }); - }); - - // 重置为默认值 - new Setting(this.containerEl) - .setName(t("重置为默认值")) - .setDesc(t("将进度范围重置为默认值")) - .addButton((button) => { - button.setButtonText(t("重置")).onClick(async () => { - this.plugin.settings.progressRanges = [ - { - min: 0, - max: 20, - text: t("刚刚开始 {{PROGRESS}}%"), - }, - { - min: 20, - max: 40, - text: t("正在推进 {{PROGRESS}}%"), - }, - { min: 40, max: 60, text: t("进行一半 {{PROGRESS}}%") }, - { - min: 60, - max: 80, - text: t("进展良好 {{PROGRESS}}%"), - }, - { - min: 80, - max: 100, - text: t("即将完成 {{PROGRESS}}%"), - }, - ]; - this.applySettingsUpdate(); - this.display(); - }); - }); -} -``` - -## 5. 实现步骤 - -1. **添加新设置到插件设置接口**: - - 创建新的 `progressBarFormat` 对象 - - 为所有自定义选项添加默认值 - - 确保与现有 taskStatuses 集成 - - 保留现有的 progressRanges 设计 - -2. **实现文本格式化器**: - - 实现 `ProgressCalculator` 用于数据处理 - - 创建 `ProgressTextFormatter` 用于文本模板处理 - - 添加与任务状态系统的集成 - - 维护对基于范围模板的支持 - -3. **更新设置界面**: - - 添加新的设置部分 - - 确保与现有设置的向后兼容性 - - 保留现有的进度范围设置界面 - - 添加实时预览功能 - -4. **与现有实现的桥接**: - - 支持旧设置格式 - - 自动将现有设置转换为新格式 - - 为用户提供平滑过渡 - -## 6. 迁移策略 - -```typescript -function migrateOldProgressBarSettings(oldSettings: any): ProgressFormatOptions { - // 检测是否使用百分比或范围显示 - const usesPercentage = oldSettings.showPercentage; - const usesRanges = oldSettings.customizeProgressRanges && oldSettings.progressRanges && oldSettings.progressRanges.length > 0; - - return { - // 根据现有配置自动选择最合适的显示模式 - displayMode: usesRanges ? "range-based" : (usesPercentage ? "percentage" : "fraction"), - customFormat: "[{{COMPLETED}}/{{TOTAL}}]", - progressRanges: oldSettings.progressRanges || [ - { min: 0, max: 20, text: t("刚刚开始 {{PROGRESS}}%") }, - { min: 20, max: 40, text: t("正在推进 {{PROGRESS}}%") }, - { min: 40, max: 60, text: t("进行一半 {{PROGRESS}}%") }, - { min: 60, max: 80, text: t("进展良好 {{PROGRESS}}%") }, - { min: 80, max: 100, text: t("即将完成 {{PROGRESS}}%") }, - ], - statusDisplaySymbols: ProgressTextFormatter.initStatusDisplaySymbols(oldSettings.taskStatuses) - }; -} -``` - -## 7. 自定义格式示例 - -以下是可以通过自定义格式实现的一些例子: - -1. **带括号的简单分数**: - `[{{COMPLETED}}/{{TOTAL}}]` - -2. **自定义符号**: - `【{{COMPLETED}}⭐ / {{TOTAL}}⭐】` - -3. **基于任务状态的进度计量**: - `{{COMPLETED}}{{COMPLETED_SYMBOL}} {{IN_PROGRESS}}{{IN_PROGRESS_SYMBOL}} {{ABANDONED}}{{ABANDONED_SYMBOL}} / {{TOTAL}}` - -4. **表情符号进度条**: - `${="⬛".repeat(Math.floor(data.percentages.completed/10)) + "⬜".repeat(10-Math.floor(data.percentages.completed/10))}` - -5. **文本进度条**: - `[${="=".repeat(Math.floor(data.percentages.completed/10)) + " ".repeat(10-Math.floor(data.percentages.completed/10))}]` - -6. **状态感知自定义格式**: - `[{{COMPLETED_SYMBOL}}:{{COMPLETED}} {{IN_PROGRESS_SYMBOL}}:{{IN_PROGRESS}} {{PLANNED_SYMBOL}}:{{PLANNED}} / {{TOTAL}}]` - -7. **彩色文本**: - `{{COMPLETED}}/{{TOTAL}} 完成率: ${=data.percentages.completed < 30 ? '🔴低' : data.percentages.completed < 70 ? '🟠中' : '🟢高'}` - -8. **范围示例** (基于progressRanges配置): - - 0-20%: "刚刚开始 15%" - - 20-40%: "正在推进 35%" - - 40-60%: "进行一半 50%" - - 60-80%: "进展良好 75%" - - 80-100%: "即将完成 90%" - -## 8. 性能考虑 - -1. **懒计算**: - - 仅在需要时计算百分比 - - 对于重复渲染,尽可能缓存结果 - -2. **表达式处理**: - - 对于常用格式,预编译模板 - - 缓存处理过的模板 - -3. **向后兼容性**: - - 确保现有的进度范围设置仍然可用 - - 无缝支持从旧版本升级 - diff --git a/design/reward.md b/design/reward.md deleted file mode 100644 index 01f9e474..00000000 --- a/design/reward.md +++ /dev/null @@ -1,160 +0,0 @@ -# Reward 功能设计文档 - -## 1. 概述 - -Reward (奖励) 功能是 Task Genius 插件的一个激励模块,旨在通过在用户完成任务时提供随机或有条件的奖励,提升用户的积极性和任务完成动力。用户可以自定义奖励列表、触发条件和概率,使得完成任务更具趣味性。 - -## 2. 核心功能 - -- **奖励定义**: 用户可以在指定的 Markdown 文件中定义奖励列表,每行一个奖励。 -- **奖励属性**: - - **名称 (Name)**: 奖励的描述性文字 (例如, "喝杯好茶", "看一集喜欢的剧")。 - - **稀有度/出现率 (Occurrence)**: 定义奖励出现的频率 (例如, `common`, `rare`, `legendary`)。允许自定义稀有度等级及其概率。默认为 `common`。 - - **库存 (Inventory)**: 定义奖励可用的次数。每次获得奖励后,库存会自动减少。库存为 0 后该奖励不再出现。默认为无限。 - - **图片 (Image)**: 可选,指定一个图片 URL (本地或网络),在获得奖励时显示。 - - **条件 (Condition)**: 可选,指定奖励触发的条件,例如要求任务包含特定标签 (`#difficult`, `#project`) 或满足特定优先级。支持简单的逻辑组合 (AND, OR, NOT)。 -- **触发机制**: 监听 Task View 或者 Task Genius 本身的任务完成事件。 -- **奖励抽取**: - - 当任务完成时,根据任务属性 (标签、优先级、内容) 筛选符合条件的奖励。 - - 根据符合条件的奖励的稀有度进行加权随机抽取。 - - 考虑奖励库存。 -- **奖励通知**: 通过 Obsidian 的通知系统或者自定义模态框向用户显示获得的奖励信息(名称、图片)。 -- **跳过奖励**: 用户可以选择跳过当前获得的奖励,跳过后不消耗库存。 -- **库存管理**: 如果奖励被接受(未跳过)且有库存限制,自动更新奖励定义文件中的库存数量。 -- **配置管理**: 提供设置界面,用于配置奖励文件路径、稀有度等级、条件语法等。 -- **快速开始**: 跳转到设置中的奖励设置页面。 - -## 3. 数据结构 - -### 3.1 奖励定义 (Reward Item) - -奖励定义存储在一个 JSON 文件中 (例如 `rewards.json`)。该文件包含一个 JSON 数组,每个数组元素是一个代表奖励的 JSON 对象。 - -*示例 (`rewards.json`):* - -```json -[ - { - "id": "reward-tea", // 用户定义的唯一 ID - "name": "喝杯好茶", - "occurrence": "common" - // inventory 默认为无限 - }, - { - "id": "reward-series-episode", - "name": "看一集喜欢的剧", - "occurrence": "rare", - "inventory": 20 - }, - { - "id": "reward-champagne-project", - "name": "打开那瓶珍藏的香槟", - "occurrence": "legendary", - "inventory": 1, - "condition": "#project AND #milestone" // 条件仍可定义 - }, - { - "id": "reward-chocolate-quick", - "name": "吃块巧克力", - "occurrence": "common", - "inventory": 10, - "condition": "#quickwin", - "imageUrl": "app://local/C:/images/chocolate.png" // 图片 URL - } -] -``` - -### 3.2 内部奖励对象 (Parsed Reward Object) - -```typescript -interface RewardCondition { - raw: string; // e.g., "#project AND #milestone" - // Parsed structure for evaluation, e.g.,: - // { type: 'AND', conditions: [{ type: 'TAG', value: 'project' }, { type: 'TAG', value: 'milestone' }] } - // Or a function: (task: Task) => boolean -} - -interface Reward { - id: string; // Unique identifier (e.g., generated hash or line number) - name: string; // The reward text - occurrence: string; // Name of the occurrence level (e.g., "common", "rare"). Needs mapping to probability. - probability?: number; // Calculated probability based on occurrence level - inventory: number; // Remaining count (Infinity for unlimited) - imageUrl?: string; // Optional image URL - condition?: RewardCondition; // Optional condition for triggering -} -``` - -### 3.3 奖励设置 (Reward Settings) - -```typescript -interface OccurrenceLevel { - name: string; - chance: number; // Probability percentage (e.g., 70 for 70%) -} - -interface RewardSettings { - rewardFilePath: string; // Path to the rewards JSON file (default: rewards.json) - occurrenceLevels: OccurrenceLevel[]; // e.g., [{ name: 'common', chance: 70 }, { name: 'rare', chance: 25 }, { name: 'legendary', chance: 5 }] - conditionSyntax: 'tags' | 'dataview' | 'simple_keywords'; // How conditions are defined and parsed - enableRewards: boolean; // Master switch - // Tag condition logic (default AND?) - Future enhancement - // Formatting for reward attributes in file (default {}) - Future enhancement -} -``` - -### 3.4 奖励缓存 (Internal Cache) - -```typescript -interface RewardCache { - rewards: Reward[]; // Parsed list of all available rewards - filePath: string; // Path of the file the cache is based on - lastModified: number; // Timestamp of the reward file when last parsed -} -``` - -## 4. 实现方案 - -1. **加载与解析**: - * 使用 Task Genius 的设置中的 Reward 部分,读取为 RewardItem 列表。 -2. **任务完成挂钩 (Hook)**: - * 监听 Task Genius 内部的任务完成事件 ( Task Genius 提供事件总线 `this.app.workspace.on('task-genius:task-completed', task => ...)` )。 - * 获取完成的任务对象 (`task`),包含其文本、标签、优先级等信息。 -3. **奖励筛选**: - * 遍历上述流程中的 RewardItem 列表。 - * 对于每个 `RewardItem`,检查其 `inventory` 是否大于 0 (或为无限)。 - * 如果 `RewardItem` 有 `condition`,则使用任务信息 (`task`) 对其进行评估。只保留条件满足的奖励。 (例如, `condition.evaluate(task)` 返回 `true`)。 -4. **奖励抽取**: - * 从筛选后的奖励列表中,根据各自的 `occurrence` (对应的 `chance`) 进行加权随机抽取。 - * 例如,如果剩下 Common (70%), Rare (25%), Legendary (5%) 的奖励,按此概率分布随机选择一个。 -5. **通知与交互**: - * 如果抽中奖励,显示 Obsidian 通知 (`new Notice(...)`) 或一个更丰富的模态框,包含奖励名称、图片(若有),以及 "领取" (隐式关闭) 和 "跳过" 按钮。 -6. **库存更新**: - * 如果用户 **没有** 点击 "跳过",并且抽中的奖励 `inventory` 不是无限 (`Infinity`): - * 将 `RewardItem` 中对应奖励的 `inventory` 减 1。 - * **更新奖励文件**: 更新 Task Genius 的设置中的 Reward 部分对应的 `RewardItem` 的 `inventory` 字段。 - -## 5. UI 设计 - -- **奖励通知**: - - 使用 Obsidian 的 `Notice` API 显示简短通知,包含奖励名称和可选的 "跳过" 按钮。 - - 或者,使用 Obsidian 的 `Modal` API 创建一个更醒目的弹窗,可以展示图片和更清晰的按钮。 -- **设置界面**: - - 在 Task Genius 的设置面板中增加 "Rewards" 标签页。 - - 包含字段:启用/禁用开关、奖励文件路径输入框、稀有度等级配置(允许增删改名称和概率)、条件解析方式选择等。 - -## 6. 设置 - -- **Enable Rewards**: 总开关,启用或禁用奖励功能。 -- **Reward Items**: 可以新增条目来配置奖励项目。 -- **Occurrence Levels**: 点击每个 Reward Item 能配置稀有度等级及其对应的抽取概率(分成三档,默认是 common, rare, legendary)。 -- **Condition Settings**: 配置每个 Reward Item 的触发条件,置空则默认加入队列随机触发。 - -## 7. 开放问题与未来考虑 - -- **复杂的条件逻辑**: 如何优雅地支持 AND, OR, NOT 组合,甚至 Dataview 查询作为奖励条件? -- **奖励历史/统计**: 记录用户获得的奖励历史。 -- **与其他插件的集成**: 能否从其他插件(如 Habitica、游戏化插件)获取奖励定义或触发奖励? -- **奖励分组/分类**: 允许用户将奖励分组(例如,按项目、按类型),并可能根据任务的上下文优先选择某个组的奖励。 -- **UI/UX**: 如何使奖励通知既有效又不打扰用户流程?是否需要更丰富的奖励展示界面? -- **与 Habit 功能联动**: 能否在完成某个习惯打卡时也触发奖励? diff --git a/design/side-handler.md b/design/side-handler.md deleted file mode 100644 index 6d212a23..00000000 --- a/design/side-handler.md +++ /dev/null @@ -1,140 +0,0 @@ -# Side Handler 功能设计文档 - -## 1. 概述 - -Side Handler (侧边栏处理器或行号栏交互器) 是 Task Genius 插件中的一个增强交互功能。它利用编辑器 (CodeMirror) 的行号栏 (gutter) 区域,在用户点击特定任务相关的标记时,提供一个包含该任务详细信息的弹出层 (Popover 或 Modal)。用户可以直接在此弹出层中查看和快速修改任务的某些属性,旨在提升任务管理的便捷性和效率。 - -## 2. 核心功能 - -- **Gutter 标记**: 在编辑器的行号栏为识别出的任务行显示一个可交互的标记。 -- **任务信息展示**: 点击 Gutter 标记后,根据平台类型(桌面或移动端)弹出相应的界面(Popover 或 Modal)。 - - **桌面端**: 默认显示一个紧凑的 Popover 菜单。 - - **移动端**: 默认显示一个功能更全面的 Modal 弹窗。 -- **快速信息概览**: 弹出层清晰展示任务的核心信息,例如:内容、状态、截止日期、优先级、标签等。 -- **便捷信息编辑**: 允许用户在弹出层内直接修改任务的多个属性,如状态、优先级、日期等。编辑功能参考 `TaskDetailsComponent` (`details.ts`) 的实现。 -- **动态界面切换**: 根据 `Platform.isDesktop` 自动判断并切换 Popover 与 Modal 的显示。 -- **上下文操作**: - - 提供 "在文件中编辑" 的快捷入口,跳转到任务所在行。 - - 提供 "标记完成/未完成" 的快捷操作。 - -## 3. 交互设计 - -### 3.1 Gutter 交互 - -- 当鼠标悬停在 Gutter 中的任务标记上时,标记高亮,并可显示 Tooltip 提示 (例如 "查看/编辑任务")。 -- 单击 Gutter 中的任务标记,触发弹出层(Popover 或 Modal)。 - -### 3.2 桌面端: Popover 菜单 - -- 在桌面环境下 (`Platform.isDesktop === true`),点击 Gutter 标记后,在标记附近弹出一个非模态的 Popover。 -- Popover 内容区域将集成 `TaskDetailsComponent` 的核心展示和编辑能力。 -- Popover 应包含以下元素: - - 任务内容预览 (只读或截断显示)。 - - 任务状态切换器 (使用 `StatusComponent`)。 - - 关键元数据展示与编辑 (例如:优先级、截止日期)。可参考 `details.ts` 中的 `showEditForm` 方法提供的字段。 - - 操作按钮: - - "编辑详细信息" (可选,如果 Popover 只提供部分编辑,此按钮可打开一个更全面的 Modal 或跳转至任务详情视图)。 - - "在文件中编辑"。 - - "切换完成状态"。 - - 点击 Popover 外部区域或按下 `Esc` 键可关闭 Popover。 - -### 3.3 移动端: Modal 弹窗 - -- 在非桌面环境(如移动端,`Platform.isDesktop === false`)下,点击 Gutter 标记后,屏幕中央弹出一个模态对话框 (Modal)。 -- Modal 的设计和实现可以参考 `QuickCaptureModal.ts` 的结构和交互模式,但内容主要用于展示和编辑现有任务,而非创建新任务。 -- Modal 内容将更全面地集成 `TaskDetailsComponent` 的功能,提供比 Popover 更丰富的编辑选项。 -- Modal 应包含: - - 清晰的标题,如 "编辑任务"。 - - 完整的任务内容展示 (可编辑,参考 `details.ts` 的 `contentInput`)。 - - 任务状态选择 (参考 `StatusComponent`)。 - - 各项可编辑的任务元数据字段(如项目、标签、上下文、优先级、各项日期、重复规则等),布局和控件参考 `details.ts` 的 `showEditForm`。 - - 底部操作按钮: - - "保存" 或 "应用更改"。 - - "取消"。 - - "在文件中编辑" (打开对应文件并定位)。 - - "切换完成状态"。 - -## 4. 数据展示与编辑 - -弹出层 (Popover/Modal) 中展示和允许编辑的任务信息主要基于 `Task`对象的属性,其实现逻辑参考 `src/components/task-view/details.ts` 中的 `TaskDetailsComponent`。 - -### 4.1 展示信息 - -- 任务原始内容 ( `task.content` ) -- 任务状态 ( `task.status`, 通过 `getStatus` 或 `StatusComponent` 展示) -- 项目 ( `task.project` ) -- 截止日期 ( `task.dueDate` ) -- 开始日期 ( `task.startDate` ) -- 计划日期 ( `task.scheduledDate` ) -- 完成日期 ( `task.completedDate` ) -- 优先级 ( `task.priority` ) -- 标签 ( `task.tags` ) -- 上下文 ( `task.context` ) -- 重复规则 ( `task.recurrence` ) -- 文件路径 ( `task.filePath` ) - -### 4.2 可编辑信息 - -以下字段应允许用户在 Popover 或 Modal 中直接修改,修改逻辑和UI组件参考 `TaskDetailsComponent` 的 `showEditForm` 方法: - -- 任务内容 (`contentInput`) -- 项目 (`projectInput` 与 `ProjectSuggest`) -- 标签 (`tagsInput` 与 `TagSuggest`) -- 上下文 (`contextInput` 与 `ContextSuggest`) -- 优先级 (`priorityDropdown`) -- 截止日期 (`dueDateInput`) -- 开始日期 (`startDateInput`) -- 计划日期 (`scheduledDateInput`) -- 重复规则 (`recurrenceInput`) -- 状态 (通过 `StatusComponent` 或类似的机制) - -保存更新后的任务数据将调用 `onTaskUpdate` 回调,与 `TaskDetailsComponent` 中的保存逻辑类似,可能包含防抖处理。 - -## 5. UI 设计 - -### 5.1 Gutter Marker (行号栏标记) - -- 在任务行的行号栏显示一个简洁、直观的图标 (例如:一个小圆点、任务勾选框图标的变体、或者插件特有的图标)。 -- 标记的颜色或形态可以根据任务状态(例如,未完成、已完成)有细微变化。 -- 鼠标悬停时标记有视觉反馈(如放大、改变颜色)。 - -### 5.2 Popover (桌面端) - -- 设计应紧凑,避免遮挡过多编辑器内容。 -- 风格与 Obsidian 主题保持一致。 -- 包含任务核心信息和常用编辑字段。 -- 字段布局参考 `TaskDetailsComponent` 中非编辑状态下的信息排布,但控件为编辑形态。 - -### 5.3 Modal (移动端 / 详细编辑) - -- Modal 弹窗的设计参考 `QuickCaptureModal.ts` 的全功能模式 (`createFullFeaturedModal`),但侧重于编辑而非捕获。 -- 移除或调整文件目标选择器等不适用于编辑现有任务的元素。 -- 表单布局清晰,易于在小屏幕上操作。 -- 包含 `TaskDetailsComponent` `showEditForm` 中几乎所有的可编辑字段。 -- 提供明确的 "保存" 和 "取消" 按钮。 - -## 6. 实现要点 - -1. **CodeMirror Gutter API**: - * 使用 CodeMirror 6 的 Gutter API (`gutter`, `lineMarker` 等) 来添加和管理行号栏标记。 - * 需要监听 Gutter 标记的点击事件。 -2. **任务识别**: - * 需要一种机制来确定哪些行是任务行,以便在这些行旁边显示 Gutter 标记。这可能依赖插件已有的任务解析逻辑。 -3. **动态 UI 加载**: - * 根据 `Platform.isDesktop` 的值,在点击事件回调中动态创建和显示 Popover (可能使用 Obsidian 的 `Menu` 或自定义浮动元素) 或 Modal (继承 Obsidian `Modal` 类)。 -4. **组件复用**: - * 尽可能复用 `TaskDetailsComponent` (`details.ts`) 中的任务信息展示逻辑、表单字段创建逻辑 (`createFormField`) 以及数据更新逻辑 (`onTaskUpdate`, `saveTask`)。 - * 对于 Modal 的基础框架,可以借鉴 `QuickCaptureModal.ts` 的结构,特别是其参数化配置和内容组织方式。 -5. **状态管理**: - * 确保 Popover/Modal 中的任务数据与原始任务数据同步。 - * 修改后正确更新任务对象,并通过事件或回调通知其他组件(如任务列表视图)刷新。 -6. **性能考虑**: - * Gutter 标记的渲染不应对编辑器性能产生显著影响,尤其是在处理大量任务时。 - -## 7. 开放问题与未来考虑 - -- **Gutter 标记自定义**: 是否允许用户自定义 Gutter 标记的图标或行为? -- **Popover/Modal 内容可配置性**: 是否允许用户选择在 Popover/Modal 中显示或编辑哪些字段? -- **键盘可访问性**: 如何确保通过键盘也能方便地触发 Gutter 标记和操作弹出层? -- **与其他视图的交互**: 编辑后,如何更流畅地更新其他打开的任务视图或日历视图? -- **右键菜单集成**: 除了左键点击,是否考虑在 Gutter 标记上支持右键菜单,提供更多上下文操作(如复制任务链接、快速设置提醒等)? diff --git a/design/task-view-calendar-gantt.md b/design/task-view-calendar-gantt.md deleted file mode 100644 index 213afa93..00000000 --- a/design/task-view-calendar-gantt.md +++ /dev/null @@ -1,149 +0,0 @@ -# PRD:Obsidian 插件的日历与甘特图视图 (原生实现侧重) - -**版本:** 1.0 -**日期:** 2025-04-15 - -## 1. 引言 - -本文档概述了在 Obsidian 插件中实现日历(Calendar)和甘特图(Gantt chart)视图的需求。这些视图旨在为用户提供其任务的可视化表示,从而在 Obsidian 内部改善计划、跟踪和整体任务管理工作流程。**核心目标是尽可能使用原生 Web 技术 (HTML, CSS, SVG/Canvas) 进行实现,** 并将利用 `TaskIndex.ts` 已索引的任务数据。 - -## 2. 目标 - -* 为用户提供直观的任务日历和甘特图可视化。 -* 实现更好的基于日期的计划和进度跟踪。 -* 高效利用现有的 `Task` 数据结构 (`src/utils/types/TaskIndex.ts`)。 -* 提供交互式元素用于任务检查,并可能支持基本修改(例如,重新安排)。 -* 即使存在大量任务,也能确保良好的性能 **(需要特别关注手动优化)**。 -* 将视图无缝集成到 Obsidian 用户界面中。 - -## 3. 目标用户 - -在笔记中管理任务并希望使用可视化工具来规划和跟踪项目时间表和截止日期的 Obsidian 用户。这包括项目经理、学生、开发人员、作家以及任何使用 Obsidian 进行任务管理的人员。 - -## 4. 核心功能 - -### 4.1. 数据基础 - -* **来源:** 所有任务数据将来源于 `TaskIndexer` 组件及其缓存。 -* **相关的 `Task` 字段:** - * `id`: 唯一标识符。 - * `content`: 任务描述。 - * `filePath`, `line`: 用于链接回源文件。 - * `completed`, `status`: 用于可视化状态指示器。 - * `startDate`, `scheduledDate`, `dueDate`, `completedDate`: 用于在时间线/日历上放置任务的主要字段。 - * `project`: 用于过滤和分组。 - * `tags`: 用于过滤。 - * `priority`: 用于可视化高亮或排序。 - * `parent`, `children`: 用于甘特图中的层级显示。 - -### 4.2. 日历视图 - -* **显示模式:** 月、周、日视图。 -* **任务放置:** - * 任务将主要根据其 `dueDate`(截止日期)放置。 - * 提供一个选项(用户设置)以使用 `scheduledDate`(计划日期)或 `startDate`(开始日期)作为放置的主要日期。 - * 跨越多天的任务(如果 `startDate` 和 `dueDate` 都可用)应在视觉上跨越这些天(在月视图中可能受限)。 - * 没有相关日期的任务可能会根据用户偏好显示在一个单独的"未安排"面板中或隐藏。 -* **可视化表示:** - * 在日历单元格内显示任务 `content`(如有必要则截断)。 - * 使用视觉提示(颜色、图标)来指示状态 (`completed`)、优先级或项目。 - * 考虑根据任务映射到的日期字段(`dueDate`, `scheduledDate`, `startDate`)使用不同的视觉样式。 -* **交互性:** - * 鼠标悬停在任务上时,在工具提示中显示完整的 `content` 和关键细节(日期、项目、状态)。 - * 点击任务可以导航到源文件/行或打开一个详细信息模态框。 - * **(可选 V2)** 拖放重新安排:允许将任务拖动到不同的日期以更新其 `dueDate` / `scheduledDate` / `startDate`(需要调用 `TaskIndexer.updateTask`)。 -* **导航与过滤:** - * 用于在上一/下一周期(月、周、日)之间导航的控件。 - * 一个"今天"按钮。 - * 基于 `project`, `tags`, `status`, `filePath` 的过滤选项。 - -### 4.3. 甘特图视图 - -* **时间线显示:** - * 水平轴代表时间(日、周、月缩放级别)。 - * 垂直轴列出任务。 -* **任务表示:** - * 同时具有 `startDate` 和 `dueDate` 的任务显示为跨越该持续时间的条形。 - * 只有一个相关日期(`dueDate` 或 `startDate`)的任务在该日期显示为里程碑(例如,菱形)。 - * 任务条应显示 `content`(截断)。 - * 根据 `project` 或 `status` 对条形进行颜色编码。 - * 在条形上视觉指示完成进度(例如,填充部分)。 -* **层级结构:** - * 利用 `parent` 和 `children` 字段以层级树结构显示任务(例如,使用缩进和折叠/展开控件)。子任务应在视觉上嵌套在父任务下。 -* **依赖关系:** - * **(可选 V2/V3)** 如果建立了可靠的定义依赖关系的机制(例如,特定的链接语法 `dependsOn:[[task-id]]` 或 `blocks:[[task-id]]`),则可视化任务依赖关系。在依赖的任务之间绘制箭头。*由于数据模型的限制,初始版本可能会省略明确的依赖线。* -* **交互性:** - * 鼠标悬停在任务条/里程碑上时,在工具提示中显示完整细节。 - * 点击可导航到源文件或打开详细信息模态框。 - * 时间线的水平滚动和缩放(放大/缩小)。 - * 任务列表的垂直滚动。 - * **(可选 V2)** 拖放调整 `startDate` / `dueDate`。拖动条形的两端或整个条形。 -* **过滤与排序:** - * 类似于日历视图的过滤选项 (`project`, `tags`, `status`, `filePath`)。 - * 任务列表的排序选项(例如,按 `startDate`, `dueDate`, `priority`, `content`)。 - -## 5. 非目标(初期) - -* 直接从日历/甘特图视图创建新任务(首先关注可视化)。 -* 高级资源分配或关键路径分析。 -* 高度复杂的依赖类型(完成-开始,开始-开始等),除非可以轻松推导。 -* 实时多用户协作功能。 - -## 6. 实现步骤 - -1. **技术选型与基础:** - * **主要技术:** 使用 TypeScript、HTML、CSS 以及可能的 SVG 或 Canvas 进行核心渲染和交互逻辑。如果插件已使用 Svelte,则优先利用 Svelte 构建组件。 - * **第三方库:** **原则上避免使用大型 UI/图表库 (如 FullCalendar, Frappe Gantt 等)。** 只有在遇到极其复杂且独立的算法问题(例如,高效的二维空间重叠检测、复杂的布局算法)且自研成本过高时,才考虑引入 **小型、专注** 的辅助库。任何引入的库都需要仔细评估其大小、性能和许可证。 -2. **视图搭建:** 为日历和甘特图创建新的 Obsidian `ItemView` 类。使用选定的基础技术(原生 DOM 或 Svelte)构建视图的基本结构和布局。 -3. **数据获取与转换:** - * 访问 `TaskIndexer` 实例。 - * 在每个视图中,使用 `TaskIndexer.getCache()` 或 `TaskIndexer.queryTasks()` 获取任务数据。 - * **手动** 将 `Task` 对象转换为适合在视图中渲染的结构(例如,计算日历单元格位置、甘特图条形坐标和尺寸)。密切关注日期对象的处理和转换。 -4. **渲染逻辑:** - * **手动实现渲染逻辑。** 基于转换后的数据,使用 DOM 操作、SVG 元素创建或 Canvas 绘图 API 来绘制日历网格、任务条目、甘特图时间轴和任务。 - * 实现切换视图模式(月/周/日)或缩放级别时 **重新计算布局和重新渲染** 的逻辑。 -5. **交互性实现:** - * **手动** 绑定事件监听器 (e.g., `mouseover`, `click`, `mousedown`, `mousemove`, `mouseup`) 来实现工具提示、点击导航/模态框以及拖放功能。 - * 对于拖放,需要手动计算拖动过程中的元素位置、检测放置目标、更新 `Task` 对象并调用 `TaskIndexer.updateTask`,最后刷新视图。对高频事件(如 `mousemove`)使用防抖/节流。 -6. **过滤/排序 UI:** 添加用于过滤和排序的 UI 控件(原生 HTML 或 Svelte 组件)。实现当控件更改时重新获取/重新过滤数据并 **触发视图重新渲染** 的逻辑。 -7. **样式:** 应用 CSS 以确保视图与 Obsidian 的主题匹配并具有视觉吸引力。利用 Obsidian CSS 变量。重点关注布局、定位和自定义元素的视觉样式。 -8. **优化:** **这是原生实现的关键环节。** - * 使用大量任务对视图进行性能分析。 - * **手动实现性能优化:** - * **DOM 优化:** 减少 DOM 操作次数,使用 `requestAnimationFrame` 调度更新,考虑 DOM diffing 或 Svelte 的响应式更新(如果使用)。 - * **渲染优化:** 对于甘特图等可能包含大量元素的视图,实现 **视口虚拟化**(仅渲染可见区域内的元素)。对于 Canvas,优化绘图调用。对于 SVG,管理元素数量。 - * **事件处理优化:** 对滚动、缩放、拖动等高频事件使用防抖/节流。 - * **数据处理优化:** 确保数据转换和布局计算高效。 - -## 7. 关键考虑因素 - -* **性能:** **(更加关键)** 由于缺少库的内置优化,需要开发者投入大量精力进行手动性能调优,尤其是在处理大量任务时。 -* **开发复杂度:** 从零开始构建复杂的日历和甘特图 UI 比使用现有库需要更多的时间和精力,特别是在处理布局、交互和跨浏览器/平台一致性方面。 -* **日期处理:** 使用健壮的日期库(例如 `moment.js` 或 `date-fns` - 检查 Obsidian 或其他插件可能已包含哪些以最小化体积)。一致地处理时区(Obsidian/系统默认或 UTC)。明确定义如何处理缺少 `startDate`/`dueDate` 的任务。 -* **状态管理:** 有效管理视图的状态(当前日期范围、过滤器、缩放级别)。 -* **错误处理:** 优雅地处理数据获取、转换或渲染过程中的错误。 -* **任务更新:** 确保通过 `TaskIndexer.updateTask`(例如,通过拖放)进行的更新能正确地将更改持久化回 Markdown 文件。向用户提供成功/失败的反馈。 -* **代码可维护性:** 自定义实现的 UI 代码可能比使用标准化库更难维护,需要良好的代码结构和文档。 -* **配置:** 提供用户设置来自定义视图行为(例如,默认日期字段、未安排任务的可见性、日期格式)。 - -## 8. 潜在挑战与解决方案 - -* **挑战:** **从零开始构建 UI 的复杂性与工作量。** - * **解决方案:** - * **分阶段实现:** 从最核心的功能(例如,基本的月视图、无交互的甘特图条)开始,逐步迭代添加更复杂的功能(周/日视图、缩放、拖放、层级)。 - * **抽象与组件化:** 即使不使用外部框架,也要将 UI 逻辑分解为可重用的函数或类/组件(如果使用 Svelte)。 - * **专注核心价值:** 优先实现对用户最有价值的功能,对于复杂但次要的功能(如复杂的依赖线绘制)可以推迟或简化。 -* **挑战:** **手动实现高性能渲染和虚拟化。** - * **解决方案:** - * **深入理解渲染瓶颈:** 使用浏览器开发者工具分析性能,找出是 DOM 操作、计算还是绘制过程的瓶颈。 - * **学习虚拟滚动技术:** 研究常见的虚拟滚动实现模式,并将其应用于任务列表和时间轴。 - * **按需渲染:** 确保仅在数据或视图状态实际更改时才重新渲染,并尽可能只更新变化的部分。 -* **挑战:** 处理各种交互(拖放、缩放、滚动)的细节和边缘情况。 - * **解决方案:** - * **仔细规划交互逻辑:** 在编码前明确定义每次交互的状态转换和预期行为。 - * **单元测试:** 为交互逻辑编写测试用例,覆盖各种场景。 - * **参考现有实现:** 研究开源项目或文章中类似交互的实现方式(即使不直接使用代码)。 -* **挑战:** 双向数据同步(UI <-> Markdown)。 - * **解决方案:** 依赖 `TaskIndexer` 的 `updateTask` 和 `deleteTask`。确保更新是具体的,并针对正确的行/任务。处理在视图外部可能发生的文件修改或竞态条件。在索引器成功更新后刷新视图。 -* **挑战:** 处理多样化的日期格式和部分日期。 - * **解决方案:** 解析后内部统一使用 ISO 8601 / Unix 时间戳。直接使用 `TaskIndexer` 的日期字段,因为它们应该已经被解析为 `number`(时间戳)。为缺少 `startDate` 或 `dueDate` 的任务定义清晰的逻辑(例如,视为里程碑,根据可用日期放置,省略)。 diff --git a/design/task-view.md b/design/task-view.md deleted file mode 100644 index 0b77f004..00000000 --- a/design/task-view.md +++ /dev/null @@ -1,412 +0,0 @@ -# Task View 功能设计文档 - -## 1. 概述 - -Task View 是 Task Genius 插件的核心功能模块,旨在为 Obsidian 提供统一的任务管理界面,不破坏原生文本记录体验的同时,提供类似 OmniFocus 的任务管理功能,并支持与现有 Tasks 插件的兼容集成。 - -## 2. 核心功能 - -- 任务收集与索引 -- 自定义视图 (Perspectives) -- 任务过滤和分组 -- 任务编辑 -- 任务状态追踪 -- Tasks 插件兼容支持 - -## 3. 技术架构 - -### 3.1 基础组件 - -- **ItemView**: 使用 Obsidian 提供的 `ItemView` 创建任务视图 -- **TypeScript**: 使用原生 TypeScript 实现界面渲染 -- **EventEmitter**: 处理视图更新和数据变化 -- **Parser**: 解析 Tasks 插件兼容的任务语法 - -### 3.2 数据缓存方案 - -```typescript -interface TaskCache { - tasks: Map; // taskId -> Task - files: Map>; // filePath -> Set - tags: Map>; // tag -> Set - projects: Map>; // project -> Set - contexts: Map>; // context -> Set - dueDate: Map>; // dueDate -> Set - startDate: Map>; // startDate -> Set - scheduledDate: Map>; // scheduledDate -> Set -} - -interface Task { - id: string; // unique identifier - content: string; // task content - filePath: string; // file path - line: number; // line number - completed: boolean; // completion status - createdDate?: number; // creation date - startDate?: number; // start date (Tasks plugin compatible) - scheduledDate?: number; // scheduled date (Tasks plugin compatible) - dueDate?: number; // due date - completedDate?: number; // completion date - recurrence?: string; // recurrence rule (Tasks plugin compatible) - tags: string[]; // tags - project?: string; // project - context?: string; // context - priority?: number; // priority - parent?: string; // parent task ID - children: string[]; // child task ID list - originalMarkdown: string; // original markdown text - estimatedTime?: number; // estimated time in minutes - actualTime?: number; // actual time spent in minutes -} -``` - -### 3.3 任务解析器 - -专门处理 Tasks 插件兼容的语法解析: - -```typescript -class TaskParser { - // Regular expressions for Tasks plugin syntax - private readonly startDateRegex = /📅 (\d{4}-\d{2}-\d{2})/; - private readonly completedDateRegex = /✅ (\d{4}-\d{2}-\d{2})/; - private readonly dueDateRegex = /⏳ (\d{4}-\d{2}-\d{2})/; - private readonly scheduledDateRegex = /⏰ (\d{4}-\d{2}-\d{2})/; - private readonly recurrenceRegex = /🔁 (.*?)(?=\s|$)/; - private readonly priorityRegex = /🔼|⏫|🔽/; - - parseTask(text: string, filePath: string, lineNum: number): Task { - // Basic task info - const task: Task = { - id: generateUniqueId(), - content: text.replace(/- \[.\] /, ''), - filePath, - line: lineNum, - completed: text.includes('- [x]'), - tags: [], - children: [], - originalMarkdown: text - }; - - // Parse Tasks plugin syntax - const startDateMatch = text.match(this.startDateRegex); - if (startDateMatch) { - task.startDate = new Date(startDateMatch[1]).getTime(); - } - - // Parse other metadata... - - return task; - } - - generateMarkdown(task: Task): string { - // Convert task object back to markdown format - // ... - } -} -``` - -### 3.4 索引方案 - -1. **初始化索引**: - - 使用 Obsidian 的 `vault.getMarkdownFiles()` 获取所有 Markdown 文件 - - 解析文件中的任务,构建初始缓存 - - 识别 Tasks 插件语法,提取元数据 - -2. **实时更新**: - - 监听 Obsidian 的 `modify` 事件更新缓存 - - 使用 `InlineWorker` 在后台处理大型文件更新 - - 增量更新策略,只更新修改的行 - -```typescript -class TaskIndexer { - private taskCache: TaskCache; - private worker: Worker | null = null; - private parser: TaskParser; - private lastIndexTime: Map = new Map(); - - constructor(plugin: TaskGeniusPlugin) { - this.taskCache = this.initEmptyCache(); - this.parser = new TaskParser(); - this.setupEventListeners(plugin); - - if (window.Worker) { - this.worker = new Worker('indexer-worker.js'); - this.worker.onmessage = this.handleWorkerMessage.bind(this); - } - } - - async indexFile(file: TFile, plugin: TaskGeniusPlugin): Promise { - const fileContent = await plugin.app.vault.read(file); - const lines = fileContent.split('\n'); - const taskIds: Set = new Set(); - - for (let i = 0; i < lines.length; i++) { - const line = lines[i]; - if (this.isTaskLine(line)) { - const task = this.parser.parseTask(line, file.path, i); - this.taskCache.tasks.set(task.id, task); - taskIds.add(task.id); - - // Update index maps - this.updateIndexMaps(task); - } - } - - // Update file index - this.taskCache.files.set(file.path, taskIds); - this.lastIndexTime.set(file.path, Date.now()); - } - - private updateIndexMaps(task: Task): void { - // Add to tag index - task.tags.forEach(tag => { - const tasks = this.taskCache.tags.get(tag) || new Set(); - tasks.add(task.id); - this.taskCache.tags.set(tag, tasks); - }); - - // Add to date indexes - if (task.startDate) { - const dateStr = this.formatDate(task.startDate); - const tasks = this.taskCache.startDate.get(dateStr) || new Set(); - tasks.add(task.id); - this.taskCache.startDate.set(dateStr, tasks); - } - - // Update other indexes... - } - - // Helper methods... -} -``` - -## 4. 设置项 - -1. **基本设置**: - - 任务识别格式 (默认: `- [ ]`) - - 完成任务格式 (默认: `- [x]`) - - 排除文件夹列表 - - Tasks 插件兼容模式开关 - -2. **视图设置**: - - 默认视图 (今日/收件箱/项目等) - - 显示列 (标签/截止日期/优先级等) - - 分组方式 (按项目/日期/标签等) - - 排序方式 (按优先级/创建时间/名称等) - -3. **日期格式设置**: - - 起始日期表示方式 (`📅`, `start:` 等) - - 截止日期表示方式 (`⏳`, `due:` 等) - - 计划日期表示方式 (`⏰`, `scheduled:` 等) - - 日期格式 (YYYY-MM-DD, MM/DD/YYYY 等) - -4. **元数据设置**: - - 特殊标签前缀 (如项目标签、上下文标签) - - 优先级表示方式 (`🔼`, `⏫`, `priority:` 等) - - 时间估算表示方式 (`estimate:` 等) - -5. **快捷键**: - - 打开任务视图 - - 快速添加任务 - - 任务完成/取消 - - 视图切换 - -## 5. 自定义视图 (Perspectives) - -类似 OmniFocus 的 Perspectives,允许用户创建自定义视图: - -```typescript -interface Perspective { - id: string; - name: string; - icon?: string; - filters: TaskFilter[]; - groupBy?: GroupingMethod; - sortBy: SortingCriteria[]; - columns: ColumnDefinition[]; - savedSearches?: SavedSearch[]; -} - -interface TaskFilter { - type: 'tag' | 'project' | 'context' | 'dueDate' | 'startDate' | - 'scheduledDate' | 'status' | 'priority' | 'recurrence'; - operator: '=' | '!=' | '<' | '>' | 'contains' | 'empty' | 'not-empty' | 'before' | 'after'; - value: any; - conjunction?: 'AND' | 'OR'; -} - -interface SavedSearch { - id: string; - name: string; - filters: TaskFilter[]; -} -``` - -默认视图: -- 收件箱 (无项目/上下文的任务) -- 今日任务 (今日截止或标记为今日) -- 已规划 (已分配项目的任务) -- 即将开始 (有起始日期的任务) -- 已安排 (有计划日期的任务) -- 已完成 (最近完成的任务) - -## 6. 数据查询与过滤引擎 - -```typescript -class TaskQueryEngine { - constructor(private taskCache: TaskCache) {} - - query(filters: TaskFilter[], sortBy: SortingCriteria[]): Task[] { - // Initial set is all tasks - let taskIds = new Set(); - - // Get initial task set - if (filters.length === 0) { - this.taskCache.tasks.forEach((_, id) => taskIds.add(id)); - } else { - // Apply each filter - filters.forEach((filter, index) => { - const filteredSet = this.applyFilter(filter); - - if (index === 0) { - taskIds = filteredSet; - } else { - // Apply conjunction (AND/OR) with previous results - if (filter.conjunction === 'OR') { - // Union sets - filteredSet.forEach(id => taskIds.add(id)); - } else { - // Intersection (AND is default) - taskIds = new Set([...taskIds].filter(id => filteredSet.has(id))); - } - } - }); - } - - // Convert to task array - const tasks = [...taskIds].map(id => this.taskCache.tasks.get(id)!); - - // Apply sorting - return this.applySorting(tasks, sortBy); - } - - private applyFilter(filter: TaskFilter): Set { - switch (filter.type) { - case 'dueDate': - return this.filterByDate(this.taskCache.dueDate, filter); - case 'startDate': - return this.filterByDate(this.taskCache.startDate, filter); - case 'scheduledDate': - return this.filterByDate(this.taskCache.scheduledDate, filter); - // Other filter types... - } - } - - private filterByDate(dateMap: Map>, filter: TaskFilter): Set { - // Date filter implementation - // ... - } - - private applySorting(tasks: Task[], sortBy: SortingCriteria[]): Task[] { - // Sorting implementation - // ... - } -} -``` - -## 7. 数据持久化 - -1. **缓存持久化**: - - 将任务索引存储在 `.obsidian/plugins/task-genius/cache` 目录 - - 启动时快速加载缓存,然后在后台验证/更新 - - 定期自动保存以防数据丢失 - -2. **设置与视图持久化**: - - 使用 Obsidian 的 `saveData` 和 `loadData` API - - 将自定义视图和设置存储在 `.obsidian/plugins/task-genius/data.json` - - 支持导入/导出自定义视图配置 - -3. **数据迁移**: - - 支持从 Tasks 插件迁移设置和数据 - - 版本升级自动数据迁移机制 - -## 8. 性能考量 - -1. **增量更新**: - - 只更新变更的文件,避免全局重新索引 - - 使用文件修改时间戳判断是否需要更新 - - 行级别的差异检测,只处理修改的任务 - -2. **延迟加载**: - - 应用启动时只加载基本视图结构 - - 按需加载详细任务数据 - - 视图滚动时动态加载更多任务 - -3. **分批处理**: - - 对大型库使用分批处理避免界面冻结 - - 使用 `requestIdleCallback` 优化处理时机 - - 基于用户交互优先级调整处理队列 - -4. **缓存策略**: - - 多级缓存策略:内存、IndexedDB 和文件 - - LRU 缓存策略清理不常用数据 - - 压缩持久化数据减少存储需求 - -## 9. 用户界面 - -基于 OmniFocus 风格设计: -- 左侧视图切换栏(自定义视图列表) -- 上方过滤和搜索栏(高级过滤选项) -- 中间任务列表区域(支持分组和折叠) -- 右侧任务详情区域(元数据编辑) -- 底部信息栏(统计和快速操作) - -UI 组件: -- 任务列表组件(支持嵌套、分组、批量操作) -- 任务编辑器(支持快速编辑任务元数据) -- 日期选择器(适配 Tasks 插件日期格式) -- 快速过滤栏(预设过滤条件) -- 拖放支持(重新排序和组织任务) - -## 10. 与 Tasks 插件兼容 - -1. **语法兼容**: - - 完全支持 Tasks 插件的任务语法 - - 兼容 Tasks 的日期格式 (📅, ⏳, ⏰) - - 支持 Tasks 的优先级标记 (🔼, ⏫, 🔽) - - 支持 Tasks 的重复任务语法 (🔁) - -2. **功能兼容**: - - 提供 Tasks 插件主要功能的超集 - - 可与 Tasks 插件并存,互不干扰 - - 可读取 Tasks 插件的设置和任务 - -3. **迁移工具**: - - 提供从 Tasks 插件迁移配置的向导 - - 任务格式双向转换支持 - -## 11. 开发路线图 - -1. 第一阶段: 基础功能与 Tasks 兼容 - - 任务索引与缓存系统 - - Tasks 插件语法兼容 - - 基本视图与过滤 - - 任务编辑 - -2. 第二阶段: 高级功能 - - 自定义视图 (Perspectives) - - 高级查询语言 - - 批量编辑功能 - - 任务依赖关系 - -3. 第三阶段: 性能优化与扩展 - - 大型库优化 - - 移动端支持 - - API 供其他插件使用 - - 插件集成能力 - -4. 第四阶段: 自动化与智能功能 - - 任务自动分类 - - 智能排序建议 - - 时间估算和提醒 - - 进度跟踪和报告 diff --git a/design/tasks-filter.md b/design/tasks-filter.md deleted file mode 100644 index 8a28fa6d..00000000 --- a/design/tasks-filter.md +++ /dev/null @@ -1,81 +0,0 @@ -# Test Tasks for Advanced Filtering - -## Basic Tasks - -- [ ] A simple task -- [x] A completed task -- [>] An in-progress task -- [-] An abandoned task -- [?] A planned task - -## Tasks with Tags - -- [ ] Task with #tag1 -- [ ] Task with #tag2 and #tag3 -- [x] Completed task with #tag1 and #tag2 -- [ ] Task with #important #work - -## Tasks with Priorities - -- [ ] [#A] High priority task -- [ ] [#B] Medium priority task -- [ ] [#C] Low priority task -- [x] [#A] Completed high priority task -- [ ] 🔺 Highest priority task (priorityPicker.ts标准) -- [ ] ⏫ High priority task (priorityPicker.ts标准) -- [ ] 🔼 Medium priority task (priorityPicker.ts标准) -- [ ] 🔽 Low priority task (priorityPicker.ts标准) -- [ ] ⏬️ Lowest priority task (priorityPicker.ts标准) -- [ ] 🔴 High priority task (颜色优先级) -- [ ] 🟠 Medium priority task (颜色优先级) -- [ ] 🟡 Medium-low priority task (颜色优先级) -- [ ] 🟢 Low priority task (颜色优先级) -- [ ] 🔵 Low-lowest priority task (颜色优先级) -- [ ] ⚪️ Lowest priority task (颜色优先级) -- [ ] ⚫️ Below lowest priority task (颜色优先级) - -## Tasks with Dates - -- [ ] Task due on 2023-05-15 -- [ ] Task due on 2023-08-22 -- [x] Completed task from 2022-01-10 -- [ ] Task planned for 2024-01-01 -- [ ] Meeting on 2023-07-15 with John #meeting - -## Complex Tasks - -- [ ] [#A] Important task with #project1 due on 2023-06-30 -- [x] [#B] Completed task with #project1 and #project2 from 2023-04-15 -- [>] ⏫ In-progress high priority task with #urgent due tomorrow 2023-05-10 -- [ ] 🔽 Low priority task with #waiting #followup for 2023-09-01 -- [-] 🔼 Abandoned medium priority task from 2023-02-28 #cancelled - -## Nested Tasks - -- [ ] Parent task 1 - - [ ] Child task 1.1 - - [x] Child task 1.2 - - [ ] Child task 1.3 - - [ ] Grandchild task 1.3.1 - - [>] Grandchild task 1.3.2 #inprogress -- [ ] Parent task 2 [#A] with #important tag - - [ ] Child task 2.1 due on 2023-07-20 - - [x] Child task 2.2 completed on 2023-06-15 -- [ ] Parent task 3 - - [-] Abandoned child task 3.1 - - [?] Planned child task 3.2 for 2023-10-01 - -## Advanced Filter Examples - -Here are some example filters you can try: - -1. Find all highest priority tasks: `PRIORITY:🔺` -2. Find all high priority tasks: `PRIORITY:#A` or `PRIORITY:⏫` or `PRIORITY:🔴` -3. Find all tasks with medium priority or higher: `PRIORITY:<=#B` or `PRIORITY:<=🔼` -4. Find all tasks not with low priority: `PRIORITY:!=🔽` or `PRIORITY:!=🟢` -5. Find tasks due before August 2023: `DATE:<2023-08-01` -6. Find tasks due on or after January 1, 2024: `DATE:>=2024-01-01` -7. Find high priority tasks about projects: `(PRIORITY:⏫ OR PRIORITY:🔴) AND project` -8. Find tasks with tag1 that aren't completed: `#tag1 AND NOT [x]` -9. Find all high priority tasks that contain "important" or have the #urgent tag: `(PRIORITY:#A OR PRIORITY:⏫ OR PRIORITY:🔴) AND (important OR #urgent)` -10. Complex filter: `(#project1 OR #project2) AND (PRIORITY:<=🔼 OR PRIORITY:<=#B) AND DATE:>=2023-01-01 AND NOT (abandoned OR cancelled)` diff --git a/design/workflow.md b/design/workflow.md deleted file mode 100644 index 0dbc71b0..00000000 --- a/design/workflow.md +++ /dev/null @@ -1,285 +0,0 @@ -# Task Progress Bar Plugin - TODO List - -## 特性开发计划 - -### 任务状态循环增强 -- [ ] 实现可配置的任务工作流及自定义循环 -- [ ] 添加任务状态变更时间戳记录功能 - - [ ] 时间戳使用日期体现 -- [ ] 创建工作流配置设置界面 -- [ ] 支持右键菜单跳转到特定状态 - - [ ] 跳转至特定状态后再增加子任务 - -### 工作流系统 -- [ ] 设计工作流配置架构 - - [ ] 定义工作流模板(如专利流程、项目管理) - - [ ] 允许自定义工作流创建 -- [ ] 实现工作流状态持久化 -- [ ] 添加工作流进度可视化 - -### 专利流程工作流示例 -- [ ] 创建专利工作流模板,包含以下阶段: - - [ ] 开案 - - [ ] 交流交底(循环) - - [ ] 等待提问 - - [ ] 等待回答 - - [ ] 撰写(可重复) - - [ ] 审核(可重复) -- [ ] 为每个阶段转换添加时间戳记录 -- [ ] 实现当前阶段状态指示器 - -## 实现方案 - -本插件提供两种核心实现方式:基于JSON配置的结构化实现和基于纯文本Markdown的轻量级实现。 - -### 实现方案一:结构化JSON配置 - -#### 配置结构 -- [ ] 创建工作流定义的JSON架构 -- [ ] 设计工作流管理的设置UI -- [ ] 实现工作流模板的导入/导出功能 - -#### 用户界面 -- [ ] 在任务上下文菜单中添加工作流选择下拉框 -- [ ] 创建当前工作流阶段的视觉指示器 -- [ ] 实现进度可视化(时间线或进度条) -- [ ] 添加显示阶段历史和时间戳的悬停提示 - -#### 交互模型 -- [ ] 左键点击:按顺序进入下一阶段 -- [ ] 右键点击:打开跳转到任意阶段的上下文菜单 -- [ ] Shift+点击:标记为循环(稍后返回此阶段) -- [ ] Alt+点击:为阶段转换添加注释/备注 - -#### 专利流程工作流JSON配置示例 - -```json -{ - "workflowId": "patent_process", - "name": "专利处理流程", - "description": "标准专利处理工作流程", - "stages": [ - { - "id": "case_opening", - "name": "开案", - "type": "linear", - "next": "disclosure_communication" - }, - { - "id": "disclosure_communication", - "name": "交流交底", - "type": "cycle", - "subStages": [ - { - "id": "waiting_questions", - "name": "等待提问", - "next": "waiting_answers" - }, - { - "id": "waiting_answers", - "name": "等待回答", - "next": "waiting_questions" - } - ], - "canProceedTo": ["drafting", "case_closed"] - }, - { - "id": "drafting", - "name": "撰写", - "type": "cycle", - "canProceedTo": ["review", "disclosure_communication"] - }, - { - "id": "review", - "name": "审核", - "type": "cycle", - "canProceedTo": ["drafting", "case_closed"] - }, - { - "id": "case_closed", - "name": "结案", - "type": "terminal" - } - ], - "metadata": { - "version": "1.0", - "created": "2024-03-20", - "lastModified": "2024-03-20" - } -} -``` - -#### 数据结构设计 -1. 工作流定义(WorkflowDefinition) - - 基本信息(ID、名称、描述) - - 阶段列表 - - 元数据(版本、创建时间等) - -2. 阶段定义(StageDefinition) - - 基本信息(ID、名称) - - 类型(linear/cycle/terminal) - - 子阶段(针对循环类型) - - 可跳转目标(canProceedTo) - -3. 任务状态(TaskState) - - 当前阶段 - - 阶段历史记录 - - 时间戳记录 - - 备注信息 - -### 实现方案二:基于纯文本Markdown的工作流实现 - -#### 核心设计原则 -- 利用现有Markdown任务语法(- [ ] 和 - [x]) -- 最小化额外标记 -- 使用原生日期标记和Obsidian块引用 - -#### 状态表示方法 - -##### 1. 基本状态表示 -```markdown -- [ ] 任务描述 #workflow/专利 ^task-123 -``` - -在任务后添加工作流标签和块引用ID,用于状态追踪。 - -##### 2. 阶段标记 -```markdown -- [ ] 任务描述 #workflow/专利/开案 ^task-123 -``` - -工作流标签使用嵌套结构表示工作流类型和当前阶段。 - -##### 3. 历史记录与时间戳 -```markdown -- [ ] 任务描述 #workflow/专利/撰写 ^task-123 - - [x] #workflow/专利/开案 (2024-05-01) - - [x] #workflow/专利/交流交底 (2024-05-03 → 2024-05-10) - - [x] 等待提问 (2024-05-03) - - [x] 等待回答 (2024-05-05) - - [x] 等待提问 (2024-05-07) - - [x] 等待回答 (2024-05-10) -``` - -使用子任务记录历史阶段,日期标记表示完成时间,箭头表示周期性阶段的开始和结束。 - -##### 4. 循环阶段处理 -```markdown -- [ ] 任务描述 #workflow/专利/交流交底/等待回答 ^task-123 - - [x] 等待提问 (2024-05-03) -``` - -对于循环子阶段,在标签中添加子阶段名称,并用子任务记录循环历史。 - -#### 实现示例:专利工作流 - -```markdown -## 专利任务 - -### 当前任务 -- [ ] 量子计算专利 #workflow/专利/撰写 ^patent-001 - - [x] #workflow/专利/开案 (2024-01-15) - - [x] #workflow/专利/交流交底 (2024-01-20 → 2024-02-15) - - [x] 等待提问 (2024-01-20) - - [x] 等待回答 (2024-01-25) - - [x] 等待提问 (2024-02-05) - - [x] 等待回答 (2024-02-15) - -- [ ] AI推理加速器专利 #workflow/专利/交流交底/等待回答 ^patent-002 - - [x] #workflow/专利/开案 (2024-03-10) - - [ ] #workflow/专利/交流交底 (2024-03-15 →) - - [x] 等待提问 (2024-03-15) - - [ ] 等待回答 -``` - -#### 插件交互设计 - -##### 工作流解析规则 -1. 通过标签 `#workflow/{工作流类型}/{阶段}[/{子阶段}]` 识别工作流 -2. 通过块引用 `^task-id` 唯一标识任务 -3. 子任务表示历史记录 -4. 日期标记表示时间戳 - -##### 命令与快捷键 -1. 推进到下一阶段: 单击任务复选框 - - 更新当前任务的工作流标签 - - 添加已完成子任务记录历史阶段 - - 自动添加日期时间戳 - -2. 跳转到特定阶段: 右键菜单 - - 右键点击任务,显示可用阶段 - - 选择目标阶段,更新标签和添加历史记录 - -3. 添加注释: Alt+单击 - ```markdown - - [ ] 任务描述 #workflow/专利/撰写 ^task-123 - - [x] #workflow/专利/开案 (2024-01-15) - - [x] #workflow/专利/交流交底 (2024-01-20 → 2024-02-15) - 客户需求变更多次 - ``` - -##### 视觉增强 -1. 使用CSS添加视觉指示器 - - 显示当前阶段图标 - - 根据工作流阶段使用不同颜色 - -2. 悬停信息 - - 显示完整历史记录 - - 显示预计完成时间 - -#### 配置示例 -预定义工作流使用YAML前置元数据: - -```yaml ---- -workflows: - - id: patent_process - name: 专利处理流程 - stages: - - id: case_opening - name: 开案 - next: disclosure_communication - - id: disclosure_communication - name: 交流交底 - type: cycle - subStages: - - id: waiting_questions - name: 等待提问 - - id: waiting_answers - name: 等待回答 - next: ["drafting","case_closed"] - - id: drafting - name: 撰写 - next: ["review", "disclosure_communication"] - - id: review - name: 审核 - type: cycle - next: ["drafting", "case_closed"] - - id: case_closed - name: 结案 - type: terminal ---- -``` - -#### 兼容性与优势 -1. 无插件环境下仍可读取和手动更新 -2. 历史记录作为常规Markdown列表存在 -3. 与其他任务插件兼容 -4. 使用原生Obsidian功能(标签、块引用)易于查询和链接 - -## 后续优化方向 - -1. 工作流分析 - - 阶段耗时统计 - - 瓶颈分析 - - 效率报告 - -2. 协作功能 - - 多用户支持 - - 阶段分配 - - 通知系统 - -3. 自动化 - - 条件触发 - - 定时提醒 - - 自动推进规则 - diff --git a/manifest.json b/manifest.json index 38a3cebf..7a5e61d9 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "obsidian-task-progress-bar", "name": "Task Genius", - "version": "9.1.5", + "version": "9.2.0", "minAppVersion": "0.15.2", "description": "Comprehensive task management that includes progress bars, task status cycling, and advanced task tracking features.", "author": "Boninall", diff --git a/package.json b/package.json index d40a5bd5..48050c2a 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,8 @@ "e-t": "cross-env node scripts/extract-translations.cjs", "g-l": "cross-env node scripts/generate-locale-files.cjs", "test": "jest", - "test:watch": "jest --watch" + "test:watch": "jest --watch", + "prepare": "husky" }, "keywords": [ "obsidian", @@ -40,6 +41,7 @@ "cross-env": "^7.0.3", "esbuild": "0.13.12", "esbuild-plugin-inline-worker": "https://github.com/mitschabaude/esbuild-plugin-inline-worker", + "husky": "^9.1.7", "jest": "^29.5.0", "jest-environment-jsdom": "^29.5.0", "monkey-around": "^3.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 39acf8b6..53df39fb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -72,6 +72,9 @@ importers: esbuild-plugin-inline-worker: specifier: https://github.com/mitschabaude/esbuild-plugin-inline-worker version: https://codeload.github.com/mitschabaude/esbuild-plugin-inline-worker/tar.gz/d1aaffc721a62a3fe33f59f8f69b462c7dd05f45(esbuild@0.13.12) + husky: + specifier: ^9.1.7 + version: 9.1.7 jest: specifier: ^29.5.0 version: 29.7.0(@types/node@16.18.126) @@ -1240,6 +1243,11 @@ packages: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} engines: {node: '>=10.17.0'} + husky@9.1.7: + resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==} + engines: {node: '>=18'} + hasBin: true + iconv-lite@0.6.3: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} @@ -3460,6 +3468,8 @@ snapshots: human-signals@2.1.0: {} + husky@9.1.7: {} + iconv-lite@0.6.3: dependencies: safer-buffer: 2.1.2 diff --git a/src/__tests__/TaskTimerSegments.test.ts b/src/__tests__/TaskTimerSegments.test.ts new file mode 100644 index 00000000..3131557d --- /dev/null +++ b/src/__tests__/TaskTimerSegments.test.ts @@ -0,0 +1,204 @@ +import { TaskTimerManager, TimerState, TimeSegment } from "../utils/TaskTimerManager"; +import { TaskTimerSettings } from "../common/setting-definition"; + +// Mock localStorage +const localStorageMock = (() => { + let store: { [key: string]: string } = {}; + return { + getItem: (key: string) => store[key] || null, + setItem: (key: string, value: string) => { + store[key] = value; + }, + removeItem: (key: string) => { + delete store[key]; + }, + clear: () => { + store = {}; + } + }; +})(); + +Object.defineProperty(window, 'localStorage', { + value: localStorageMock +}); + +describe("TaskTimerManager - Time Segments", () => { + let manager: TaskTimerManager; + const mockSettings: TaskTimerSettings = { + enabled: true, + blockRefPrefix: "timer", + timeFormat: "{h}hrs{m}mins{s}s", + metadataDetection: { + frontmatter: "", + folders: [], + tags: [] + } + }; + + beforeEach(() => { + localStorageMock.clear(); + manager = new TaskTimerManager(mockSettings); + }); + + test("should create initial segment when starting timer", () => { + const blockId = manager.startTimer("test.md"); + const taskId = `taskTimer_test.md#${blockId}`; + const state = manager.getTimerState(taskId); + + expect(state).toBeTruthy(); + expect(state!.segments).toHaveLength(1); + expect(state!.segments[0].startTime).toBeDefined(); + expect(state!.segments[0].endTime).toBeUndefined(); + expect(state!.status).toBe('running'); + }); + + test("should close segment when pausing timer", () => { + const blockId = manager.startTimer("test.md"); + const taskId = `taskTimer_test.md#${blockId}`; + + // Pause the timer + manager.pauseTimer(taskId); + + const state = manager.getTimerState(taskId); + expect(state!.segments).toHaveLength(1); + expect(state!.segments[0].endTime).toBeDefined(); + expect(state!.segments[0].duration).toBeDefined(); + expect(state!.status).toBe('paused'); + }); + + test("should create new segment when resuming timer", () => { + const blockId = manager.startTimer("test.md"); + const taskId = `taskTimer_test.md#${blockId}`; + + // Pause and resume + manager.pauseTimer(taskId); + manager.resumeTimer(taskId); + + const state = manager.getTimerState(taskId); + expect(state!.segments).toHaveLength(2); + expect(state!.segments[0].endTime).toBeDefined(); + expect(state!.segments[1].startTime).toBeDefined(); + expect(state!.segments[1].endTime).toBeUndefined(); + expect(state!.status).toBe('running'); + }); + + test("should calculate total duration across multiple segments", () => { + const blockId = manager.startTimer("test.md"); + const taskId = `taskTimer_test.md#${blockId}`; + + // Mock segments with known durations + const state = manager.getTimerState(taskId)!; + state.segments = [ + { startTime: 1000, endTime: 2000, duration: 1000 }, + { startTime: 3000, endTime: 4500, duration: 1500 }, + { startTime: 5000 } // Current running segment + ]; + + // Mock current time + const originalNow = Date.now; + Date.now = jest.fn(() => 6000); + + // Save state + localStorage.setItem(taskId, JSON.stringify(state)); + + // Get duration + const duration = manager.getCurrentDuration(taskId); + + // Should be 1000 + 1500 + 1000 = 3500 + expect(duration).toBe(3500); + + // Restore Date.now + Date.now = originalNow; + }); + + test("should migrate legacy format to segments", () => { + const taskId = "taskTimer_test.md#legacy-123"; + + // Store legacy format + const legacyState = { + taskId, + filePath: "test.md", + blockId: "legacy-123", + startTime: 1000, + pausedTime: 5000, + totalPausedDuration: 1000, + status: "paused", + createdAt: 1000 + }; + + localStorage.setItem(taskId, JSON.stringify(legacyState)); + + // Get state (should trigger migration) + const state = manager.getTimerState(taskId); + + expect(state).toBeTruthy(); + expect(state!.segments).toHaveLength(1); + expect(state!.segments[0].startTime).toBe(2000); // startTime + totalPausedDuration + expect(state!.segments[0].endTime).toBe(5000); + expect(state!.segments[0].duration).toBe(3000); + expect(state!.legacyStartTime).toBe(1000); + expect(state!.legacyPausedTime).toBe(5000); + expect(state!.legacyTotalPausedDuration).toBe(1000); + }); + + test("should handle multiple pause/resume cycles", () => { + const blockId = manager.startTimer("test.md"); + const taskId = `taskTimer_test.md#${blockId}`; + + // Simulate multiple work sessions + for (let i = 0; i < 3; i++) { + manager.pauseTimer(taskId); + manager.resumeTimer(taskId); + } + + const state = manager.getTimerState(taskId); + expect(state!.segments).toHaveLength(4); // Initial + 3 resume segments + + // First 3 segments should be closed + for (let i = 0; i < 3; i++) { + expect(state!.segments[i].endTime).toBeDefined(); + expect(state!.segments[i].duration).toBeDefined(); + } + + // Last segment should be open + expect(state!.segments[3].endTime).toBeUndefined(); + }); + + test("should get segment count correctly", () => { + const blockId = manager.startTimer("test.md"); + const taskId = `taskTimer_test.md#${blockId}`; + + expect(manager.getSegmentCount(taskId)).toBe(1); + + manager.pauseTimer(taskId); + manager.resumeTimer(taskId); + + expect(manager.getSegmentCount(taskId)).toBe(2); + }); + + test("should complete timer and calculate final duration", () => { + // Mock time progression + const originalNow = Date.now; + let currentTime = 1000; + Date.now = jest.fn(() => currentTime); + + // Start timer + const blockId = manager.startTimer("test.md"); + const taskId = `taskTimer_test.md#${blockId}`; + + // Work for 5 seconds + currentTime = 6000; + + // Complete timer + const formattedDuration = manager.completeTimer(taskId); + + // Should format 5 seconds + expect(formattedDuration).toContain("5"); + + // Timer should be removed + expect(manager.getTimerState(taskId)).toBeNull(); + + // Restore Date.now + Date.now = originalNow; + }); +}); \ No newline at end of file diff --git a/src/__tests__/autoDateManager.debug.test.ts b/src/__tests__/autoDateManager.debug.test.ts new file mode 100644 index 00000000..0d3d936e --- /dev/null +++ b/src/__tests__/autoDateManager.debug.test.ts @@ -0,0 +1,115 @@ +// @ts-ignore +import { describe, it, expect } from "@jest/globals"; +import { + findMetadataInsertPosition, +} from "../editor-ext/autoDateManager"; +import TaskProgressBarPlugin from "../index"; + +// Mock the plugin +const mockPlugin: Partial = { + settings: { + autoDateManager: { + enabled: true, + manageStartDate: true, + manageCompletedDate: true, + manageCancelledDate: true, + startDateFormat: "YYYY-MM-DD", + completedDateFormat: "YYYY-MM-DD", + cancelledDateFormat: "YYYY-MM-DD", + startDateMarker: "🛫", + completedDateMarker: "✅", + cancelledDateMarker: "❌", + }, + preferMetadataFormat: "emoji", + taskStatuses: { + completed: "x|X", + inProgress: "/|-", + abandoned: "_", + planned: "!", + notStarted: " ", + }, + }, +} as unknown as TaskProgressBarPlugin; + +describe("autoDateManager - Debug Specific Issue", () => { + it("should insert cancelled date BEFORE block reference, not after", () => { + // This is the exact line from the user's example + const lineText = "- [-] 交流交底 🚀 2025-07-30 [stage::disclosure_communication] 🛫 2025-04-20 ^timer-161940-4775"; + + console.log("Original line:", lineText); + console.log("Line length:", lineText.length); + console.log("Index of 🛫:", lineText.indexOf("🛫")); + console.log("Index of ^timer:", lineText.indexOf("^timer")); + + const position = findMetadataInsertPosition( + lineText, + mockPlugin as TaskProgressBarPlugin, + "cancelled" + ); + + console.log("Insert position:", position); + console.log("Text before position:", lineText.substring(0, position)); + console.log("Text after position:", lineText.substring(position)); + + // The cancelled date should be inserted BEFORE the block reference + expect(lineText.substring(position)).toBe(" ^timer-161940-4775"); + + // Simulate inserting the cancelled date + const cancelledDate = " ❌ 2025-07-31"; + const newLine = lineText.substring(0, position) + cancelledDate + lineText.substring(position); + + console.log("New line after insertion:", newLine); + + // Verify the block reference is still at the end + expect(newLine.endsWith("^timer-161940-4775")).toBe(true); + + // Verify the cancelled date is before the block reference + expect(newLine).toBe("- [-] 交流交底 🚀 2025-07-30 [stage::disclosure_communication] 🛫 2025-04-20 ❌ 2025-07-31 ^timer-161940-4775"); + }); + + it("should find correct position with 🛫 emoji", () => { + const lineText = "- [-] Task with 🛫 2025-04-20 ^block-id"; + + const position = findMetadataInsertPosition( + lineText, + mockPlugin as TaskProgressBarPlugin, + "cancelled" + ); + + console.log("Position for cancelled date:", position); + console.log("Text after position:", lineText.substring(position)); + + // Should insert after the 🛫 date but before block reference + expect(lineText.substring(0, position)).toContain("🛫 2025-04-20"); + expect(lineText.substring(position)).toBe(" ^block-id"); + }); + + it("should handle MISMATCHED start date emoji (🚀 in settings but 🛫 in text)", () => { + // Create a plugin with 🚀 as start marker + const mismatchedPlugin: Partial = { + settings: { + ...mockPlugin.settings!, + autoDateManager: { + ...mockPlugin.settings!.autoDateManager!, + startDateMarker: "🚀", // Different from what's in the text! + }, + }, + } as unknown as TaskProgressBarPlugin; + + // But the text has 🛫 + const lineText = "- [-] 交流交底 🚀 2025-07-30 [stage::disclosure_communication] 🛫 2025-04-20 ^timer-161940-4775"; + + const position = findMetadataInsertPosition( + lineText, + mismatchedPlugin as TaskProgressBarPlugin, + "cancelled" + ); + + console.log("Position with mismatched emoji:", position); + console.log("Text after position:", lineText.substring(position)); + + // Even with mismatched emoji, it should still find the date pattern + // because 🛫 is followed by a date pattern + expect(lineText.substring(position)).not.toContain("❌ 2025-07-31"); + }); +}); \ No newline at end of file diff --git a/src/__tests__/autoDateManager.final.test.ts b/src/__tests__/autoDateManager.final.test.ts new file mode 100644 index 00000000..eeae0c80 --- /dev/null +++ b/src/__tests__/autoDateManager.final.test.ts @@ -0,0 +1,105 @@ +// @ts-ignore +import { describe, it, expect } from "@jest/globals"; +import { + findMetadataInsertPosition, + findCompletedDateInsertPosition, +} from "../editor-ext/autoDateManager"; +import TaskProgressBarPlugin from "../index"; + +describe("autoDateManager - Final Verification", () => { + it("should correctly place cancelled date for user's exact scenario", () => { + // User's exact configuration + const mockPlugin: Partial = { + settings: { + autoDateManager: { + enabled: true, + startDateMarker: "🛫", // User's marker + completedDateMarker: "✅", + cancelledDateMarker: "❌", + }, + preferMetadataFormat: "emoji", + }, + } as unknown as TaskProgressBarPlugin; + + // User's exact line + const lineText = "- [-] 交流交底 🚀 2025-07-30 [stage::disclosure_communication] 🛫 2025-04-20 ^timer-161940-4775"; + + // Test cancelled date position + const cancelledPos = findMetadataInsertPosition( + lineText, + mockPlugin as TaskProgressBarPlugin, + "cancelled" + ); + + // For comparison, test completed date position + const completedPos = findCompletedDateInsertPosition( + lineText, + mockPlugin as TaskProgressBarPlugin + ); + + // Use throw to see the output + throw new Error(` +FINAL TEST DEBUG: +- Line: ${lineText} +- Cancelled date position: ${cancelledPos} +- Text before cancelled: "${lineText.substring(0, cancelledPos)}" +- Text after cancelled: "${lineText.substring(cancelledPos)}" +- Completed date position: ${completedPos} +- Block ref index: ${lineText.indexOf("^timer")} +- 🛫 index: ${lineText.indexOf("🛫")} +- 🛫 date end: ${lineText.indexOf("2025-04-20") + "2025-04-20".length} +`); + + // Simulate insertion + const cancelledDate = " ❌ 2025-07-31"; + const newLineWithCancelled = + lineText.substring(0, cancelledPos) + + cancelledDate + + lineText.substring(cancelledPos); + + console.log("\nAfter cancelled date insertion:"); + console.log(newLineWithCancelled); + + // Verify structure is correct + expect(newLineWithCancelled).toMatch(/🛫 2025-04-20 ❌ 2025-07-31 \^timer-161940-4775$/); + }); + + it("should handle case with no 🚀 emoji in text", () => { + const mockPlugin: Partial = { + settings: { + autoDateManager: { + enabled: true, + startDateMarker: "🛫", + completedDateMarker: "✅", + cancelledDateMarker: "❌", + }, + preferMetadataFormat: "emoji", + }, + } as unknown as TaskProgressBarPlugin; + + // Line without 🚀 emoji + const lineText = "- [-] 交流交底 [stage::disclosure_communication] 🛫 2025-04-20 ^timer-161940-4775"; + + const position = findMetadataInsertPosition( + lineText, + mockPlugin as TaskProgressBarPlugin, + "cancelled" + ); + + console.log("\nSimpler case without 🚀:"); + console.log("Line:", lineText); + console.log("Position:", position); + console.log("Text after position:", lineText.substring(position)); + + // Debug output + const dateEndPos = lineText.indexOf("2025-04-20") + "2025-04-20".length; + console.log("Date ends at:", dateEndPos); + console.log("Character at dateEndPos:", lineText[dateEndPos]); + console.log("Insert position:", position); + console.log("Character at position:", lineText[position]); + + // Should be after 🛫 date (allowing for immediate insertion after date) + expect(position).toBeGreaterThanOrEqual(lineText.indexOf("2025-04-20") + "2025-04-20".length); + expect(position).toBeLessThan(lineText.indexOf("^timer")); + }); +}); \ No newline at end of file diff --git a/src/__tests__/autoDateManager.integration.test.ts b/src/__tests__/autoDateManager.integration.test.ts new file mode 100644 index 00000000..d3c93f08 --- /dev/null +++ b/src/__tests__/autoDateManager.integration.test.ts @@ -0,0 +1,101 @@ +// @ts-ignore +import { describe, it, expect } from "@jest/globals"; +import { EditorState, Transaction } from "@codemirror/state"; +import { + handleAutoDateManagerTransaction, + findTaskStatusChange, + determineDateOperations, + applyDateOperations, +} from "../editor-ext/autoDateManager"; +import TaskProgressBarPlugin from "../index"; +import { App } from "obsidian"; + +// Mock the plugin +const mockPlugin: Partial = { + settings: { + autoDateManager: { + enabled: true, + manageStartDate: true, + manageCompletedDate: true, + manageCancelledDate: true, + startDateFormat: "YYYY-MM-DD", + completedDateFormat: "YYYY-MM-DD", + cancelledDateFormat: "YYYY-MM-DD", + startDateMarker: "🛫", + completedDateMarker: "✅", + cancelledDateMarker: "❌", + }, + preferMetadataFormat: "emoji", + taskStatuses: { + completed: "x|X", + inProgress: "/|-", + abandoned: "_", + planned: "!", + notStarted: " ", + }, + }, +} as unknown as TaskProgressBarPlugin; + +const mockApp = {} as App; + +describe("autoDateManager - Integration Test", () => { + it("should handle cancelled date insertion with real transaction", () => { + // User's exact line - task status changing from ' ' to '_' (abandoned) + const originalLine = "- [ ] 交流交底 🚀 2025-07-30 [stage::disclosure_communication] 🛫 2025-04-20 ^timer-161940-4775"; + const modifiedLine = "- [_] 交流交底 🚀 2025-07-30 [stage::disclosure_communication] 🛫 2025-04-20 ^timer-161940-4775"; + + // Create an editor state + const startState = EditorState.create({ + doc: originalLine, + }); + + // Create a transaction that changes [ ] to [_] + const tr = startState.update({ + changes: { + from: 3, + to: 4, + insert: "_", + }, + }); + + console.log("Original:", originalLine); + console.log("Modified:", modifiedLine); + console.log("Transaction newDoc:", tr.newDoc.toString()); + + // Find the task status change + const statusChange = findTaskStatusChange(tr); + if (!statusChange) { + throw new Error("No status change found"); + } + + console.log("Status change:", statusChange); + + // Determine date operations + const operations = determineDateOperations( + statusChange.oldStatus, + statusChange.newStatus, + mockPlugin as TaskProgressBarPlugin, + tr.newDoc.line(1).text + ); + + console.log("Operations:", operations); + + // Apply date operations + const result = applyDateOperations( + tr, + tr.newDoc, + 1, + operations, + mockPlugin as TaskProgressBarPlugin + ); + + // This would throw if there's an issue + throw new Error(` +INTEGRATION TEST DEBUG: +- Original: ${originalLine} +- Modified: ${modifiedLine} +- Operations: ${JSON.stringify(operations)} +- Result changes: ${JSON.stringify(result.changes)} +`); + }); +}); \ No newline at end of file diff --git a/src/__tests__/autoDateManager.pause-conflict.test.ts b/src/__tests__/autoDateManager.pause-conflict.test.ts new file mode 100644 index 00000000..2c38d970 --- /dev/null +++ b/src/__tests__/autoDateManager.pause-conflict.test.ts @@ -0,0 +1,147 @@ +// @ts-ignore +import { describe, it, expect } from "@jest/globals"; +import { + findTaskStatusChange, + determineDateOperations, + getStatusType, +} from "../editor-ext/autoDateManager"; +import TaskProgressBarPlugin from "../index"; + +// Mock the plugin +const mockPlugin: Partial = { + settings: { + autoDateManager: { + enabled: true, + manageStartDate: true, + manageCompletedDate: true, + manageCancelledDate: true, + startDateFormat: "YYYY-MM-DD", + completedDateFormat: "YYYY-MM-DD", + cancelledDateFormat: "YYYY-MM-DD", + startDateMarker: "🛫", + completedDateMarker: "✅", + cancelledDateMarker: "❌", + }, + preferMetadataFormat: "emoji", + taskStatuses: { + completed: "x|X", + inProgress: "/|-|>", + abandoned: "_|-", // Note: '-' is used for both paused and abandoned + planned: "!", + notStarted: " ", + }, + }, +} as unknown as TaskProgressBarPlugin; + +describe("autoDateManager - Pause Timer Conflict", () => { + it("should identify conflict when pausing timer changes status to abandoned", () => { + // When timer is paused, status changes from '/' to '-' + const oldStatus = "/"; + const newStatus = "-"; + const lineText = "- [-] Task with timer 🛫 2025-04-20 ^timer-123"; + + // Check what autoDateManager will do + const oldType = getStatusType(oldStatus, mockPlugin as TaskProgressBarPlugin); + const newType = getStatusType(newStatus, mockPlugin as TaskProgressBarPlugin); + + console.log(`Status change: "${oldStatus}" (${oldType}) -> "${newStatus}" (${newType})`); + + // Both '/' and '-' are configured, so types should be identified + expect(oldType).toBe("inProgress"); + expect(newType).toBe("abandoned"); + + // Determine what date operations would be triggered + const operations = determineDateOperations( + oldStatus, + newStatus, + mockPlugin as TaskProgressBarPlugin, + lineText + ); + + console.log("Date operations:", operations); + + // PROBLEM: When pausing, autoDateManager will try to add a cancelled date + expect(operations).toHaveLength(1); + expect(operations[0]).toMatchObject({ + type: "add", + dateType: "cancelled", + }); + + // This is the conflict: pause operation triggers date insertion + }); + + it("should show that '-' marker is ambiguous (used for both pause and abandoned)", () => { + // The '-' marker is used for both: + // 1. Paused tasks (temporary state while timer is paused) + // 2. Abandoned/cancelled tasks (permanent state) + + const pausedTaskStatus = "-"; + const abandonedTaskStatus = "-"; + + const pausedType = getStatusType(pausedTaskStatus, mockPlugin as TaskProgressBarPlugin); + const abandonedType = getStatusType(abandonedTaskStatus, mockPlugin as TaskProgressBarPlugin); + + // Both resolve to the same type + expect(pausedType).toBe("abandoned"); + expect(abandonedType).toBe("abandoned"); + + // This ambiguity causes autoDateManager to treat paused tasks as abandoned + // and insert a cancelled date, which may not be desired for temporary pauses + }); + + it("should demonstrate the specific user scenario", () => { + // User's exact scenario + const taskBeforePause = "- [/] 交流交底 🚀 2025-07-30 [stage::disclosure_communication] 🛫 2025-04-20 ^timer-161940-4775"; + const taskAfterPause = "- [-] 交流交底 🚀 2025-07-30 [stage::disclosure_communication] 🛫 2025-04-20 ^timer-161940-4775"; + + // Status change from '/' to '-' + const operations = determineDateOperations( + "/", + "-", + mockPlugin as TaskProgressBarPlugin, + taskAfterPause + ); + + // AutoDateManager will add a cancelled date + expect(operations).toContainEqual({ + type: "add", + dateType: "cancelled", + format: "YYYY-MM-DD", + }); + + // Expected result after autoDateManager processes it: + // The cancelled date (❌ 2025-07-31) would be inserted + const expectedResult = "- [-] 交流交底 🚀 2025-07-30 [stage::disclosure_communication] 🛫 2025-04-20 ❌ 2025-07-31 ^timer-161940-4775"; + + console.log("Task before pause:", taskBeforePause); + console.log("Task after pause:", taskAfterPause); + console.log("Expected with date:", expectedResult); + }); + + it("should suggest solutions for the conflict", () => { + // Potential solutions: + + // Solution 1: Check for timer-specific annotations + const isTimerOperation = (annotation: string) => { + return annotation === "taskTimer" || annotation.includes("timer"); + }; + + // Solution 2: Use different status markers for pause vs abandoned + const alternativeStatuses = { + paused: "p", // New marker specifically for paused + abandoned: "_", // Keep _ for truly abandoned tasks + inProgress: "/", + }; + + // Solution 3: Add configuration to skip date management for timer operations + const skipDateManagementForTimers = true; + + // Solution 4: Check for timer-related block references + const hasTimerBlockRef = (text: string) => { + return /\^timer-\d+/.test(text); + }; + + expect(isTimerOperation("taskTimer")).toBe(true); + expect(hasTimerBlockRef("^timer-123")).toBe(true); + }); +}); \ No newline at end of file diff --git a/src/__tests__/autoDateManager.realworld.test.ts b/src/__tests__/autoDateManager.realworld.test.ts new file mode 100644 index 00000000..e7cd11ba --- /dev/null +++ b/src/__tests__/autoDateManager.realworld.test.ts @@ -0,0 +1,101 @@ +// @ts-ignore +import { describe, it, expect } from "@jest/globals"; +import { + findMetadataInsertPosition, +} from "../editor-ext/autoDateManager"; +import TaskProgressBarPlugin from "../index"; + +describe("autoDateManager - Real World Test", () => { + it("should handle user's exact case", () => { + // Mock plugin with user's actual settings + const mockPlugin: Partial = { + settings: { + autoDateManager: { + enabled: true, + manageStartDate: true, + manageCompletedDate: true, + manageCancelledDate: true, + startDateFormat: "YYYY-MM-DD", + completedDateFormat: "YYYY-MM-DD", + cancelledDateFormat: "YYYY-MM-DD", + startDateMarker: "🛫", // User's actual marker + completedDateMarker: "✅", + cancelledDateMarker: "❌", + }, + preferMetadataFormat: "emoji", + }, + } as unknown as TaskProgressBarPlugin; + + // User's exact line + const lineText = "- [-] 交流交底 🚀 2025-07-30 [stage::disclosure_communication] 🛫 2025-04-20 ^timer-161940-4775"; + + console.log("\n=== REAL WORLD TEST ==="); + console.log("User's line:", lineText); + console.log("Line length:", lineText.length); + console.log("Block ref starts at:", lineText.indexOf("^timer")); + + const position = findMetadataInsertPosition( + lineText, + mockPlugin as TaskProgressBarPlugin, + "cancelled" + ); + + // Use throw to output debug info + throw new Error(` +DEBUG INFO: +- Line: ${lineText} +- Position: ${position} +- Text before: "${lineText.substring(0, position)}" +- Text after: "${lineText.substring(position)}" +- Character at position: "${lineText[position]}" +- Block ref index: ${lineText.indexOf("^timer")} +`); + + // The cancelled date should be inserted after 🛫 2025-04-20 but before ^timer + const expectedPosition = lineText.indexOf(" ^timer"); + console.log("\nExpected position:", expectedPosition); + console.log("Expected text after:", lineText.substring(expectedPosition)); + + // Simulate insertion + const cancelledDate = " ❌ 2025-07-31"; + const newLine = lineText.substring(0, position) + cancelledDate + lineText.substring(position); + console.log("\nNew line after insertion:", newLine); + + // Verify the block reference is preserved + expect(newLine).toContain("^timer-161940-4775"); + expect(newLine).not.toMatch(/\^timer.*❌/); // Cancelled date should not be after block ref + }); + + it("should test with debugging enabled", () => { + const mockPlugin: Partial = { + settings: { + autoDateManager: { + enabled: true, + startDateMarker: "🛫", + completedDateMarker: "✅", + cancelledDateMarker: "❌", + }, + preferMetadataFormat: "emoji", + }, + } as unknown as TaskProgressBarPlugin; + + // Simpler test case + const lineText = "- [-] Task 🛫 2025-04-20 ^block-123"; + + console.log("\n=== SIMPLE TEST ==="); + console.log("Line:", lineText); + + const position = findMetadataInsertPosition( + lineText, + mockPlugin as TaskProgressBarPlugin, + "cancelled" + ); + + console.log("Position:", position); + console.log("Should insert at:", lineText.substring(0, position) + " " + lineText.substring(position)); + + // Should be after the date but before block ref + expect(position).toBeLessThan(lineText.indexOf("^block")); + expect(position).toBeGreaterThan(lineText.indexOf("2025-04-20") + "2025-04-20".length); + }); +}); \ No newline at end of file diff --git a/src/__tests__/autoDateManager.research.test.ts b/src/__tests__/autoDateManager.research.test.ts new file mode 100644 index 00000000..df3a590d --- /dev/null +++ b/src/__tests__/autoDateManager.research.test.ts @@ -0,0 +1,156 @@ +// @ts-ignore +import { describe, it, expect } from "@jest/globals"; +import { + findMetadataInsertPosition, +} from "../editor-ext/autoDateManager"; +import TaskProgressBarPlugin from "../index"; + +describe("autoDateManager - Research: Why cancelled date goes to end", () => { + it("should trace execution path for cancelled date insertion", () => { + // Create plugin with 🚀 as configured emoji + const mockPlugin: Partial = { + settings: { + autoDateManager: { + enabled: true, + manageStartDate: true, + manageCompletedDate: true, + manageCancelledDate: true, + startDateFormat: "YYYY-MM-DD", + completedDateFormat: "YYYY-MM-DD", + cancelledDateFormat: "YYYY-MM-DD", + startDateMarker: "🚀", // Configured emoji + completedDateMarker: "✅", + cancelledDateMarker: "❌", + }, + preferMetadataFormat: "emoji", + taskStatuses: { + completed: "x|X", + inProgress: "/|-", + abandoned: "_", + planned: "!", + notStarted: " ", + }, + }, + } as unknown as TaskProgressBarPlugin; + + // Test case 1: User's exact line with 🛫 (not configured) + const lineText = "- [-] 交流交底 🚀 2025-07-30 [stage::disclosure_communication] 🛫 2025-04-20 ^timer-161940-4775"; + + console.log("\n=== RESEARCH: Cancelled Date Insertion Logic ==="); + console.log("Line text:", lineText); + console.log("Configured start emoji: 🚀"); + console.log("Actual start emoji in text: 🛫"); + + // Create a version without common emoji support for comparison + const oldPlugin: Partial = { + settings: { + ...mockPlugin.settings!, + autoDateManager: { + ...mockPlugin.settings!.autoDateManager!, + startDateMarker: "🚀", // Only looks for this + }, + }, + } as unknown as TaskProgressBarPlugin; + + // Test with old logic (before fix) + console.log("\n--- Without common emoji support ---"); + // This would fail to find 🛫 and insert at the end + + // Test with new logic (after fix) + console.log("\n--- With common emoji support ---"); + const position = findMetadataInsertPosition( + lineText, + mockPlugin as TaskProgressBarPlugin, + "cancelled" + ); + + console.log("Calculated position:", position); + console.log("Text before position:", lineText.substring(0, position)); + console.log("Text after position:", lineText.substring(position)); + + // Analyze what happens at each step + console.log("\n=== Step-by-step analysis ==="); + + // Step 1: Extract block reference + const blockRefMatch = lineText.match(/\s*\^[\w-]+\s*$/); + if (blockRefMatch) { + console.log("1. Block reference found:", blockRefMatch[0]); + console.log(" Block ref starts at:", lineText.length - blockRefMatch[0].length); + } + + // Step 2: Look for start date with configured emoji + const configuredPattern = /🚀\s*\d{4}-\d{2}-\d{2}/; + const configuredMatch = lineText.match(configuredPattern); + console.log("2. Configured emoji (🚀) match:", configuredMatch ? configuredMatch[0] : "NOT FOUND"); + + // Step 3: Look for start date with 🛫 + const actualPattern = /🛫\s*\d{4}-\d{2}-\d{2}/; + const actualMatch = lineText.match(actualPattern); + console.log("3. Actual emoji (🛫) match:", actualMatch ? actualMatch[0] : "NOT FOUND"); + + // Step 4: What happens when start date is not found + if (!configuredMatch) { + console.log("4. Since configured emoji not found in expected position:"); + console.log(" - Code falls back to finding end of all metadata"); + console.log(" - This could lead to position at end of line (before block ref)"); + } + + // The code now correctly finds 🚀 2025-07-30 as a start date (even though config says 🚀) + // So cancelled date will be inserted after that, not after 🛫 + // This is actually correct behavior - if there are multiple start dates, use the first one + expect(lineText.substring(position)).toBe(" [stage::disclosure_communication] 🛫 2025-04-20 ^timer-161940-4775"); + }); + + it("should analyze metadata detection patterns", () => { + const testCases = [ + { + name: "Simple task with 🛫", + text: "- [ ] Task 🛫 2025-01-20 ^block-id", + expectedAfter: " ^block-id" + }, + { + name: "Task with multiple emojis", + text: "- [ ] Task 📅 2025-01-15 🛫 2025-01-20 ^block-id", + expectedAfter: " ^block-id" + }, + { + name: "Task with dataview and 🛫", + text: "- [ ] Task [due::2025-01-25] 🛫 2025-01-20 ^block-id", + expectedAfter: " ^block-id" + } + ]; + + const mockPlugin: Partial = { + settings: { + autoDateManager: { + enabled: true, + startDateMarker: "🚀", // Different from test emojis + completedDateMarker: "✅", + cancelledDateMarker: "❌", + }, + preferMetadataFormat: "emoji", + }, + } as unknown as TaskProgressBarPlugin; + + console.log("\n=== Metadata Detection Analysis ==="); + + testCases.forEach(testCase => { + console.log(`\nTest: ${testCase.name}`); + console.log(`Text: ${testCase.text}`); + + const position = findMetadataInsertPosition( + testCase.text, + mockPlugin as TaskProgressBarPlugin, + "cancelled" + ); + + const actualAfter = testCase.text.substring(position); + console.log(`Position: ${position}`); + console.log(`Text after: "${actualAfter}"`); + console.log(`Expected after: "${testCase.expectedAfter}"`); + console.log(`Match: ${actualAfter === testCase.expectedAfter ? "✓" : "✗"}`); + + expect(actualAfter).toBe(testCase.expectedAfter); + }); + }); +}); \ No newline at end of file diff --git a/src/__tests__/autoDateManager.simple.test.ts b/src/__tests__/autoDateManager.simple.test.ts new file mode 100644 index 00000000..0d4b5b66 --- /dev/null +++ b/src/__tests__/autoDateManager.simple.test.ts @@ -0,0 +1,67 @@ +// @ts-ignore +import { describe, it, expect } from "@jest/globals"; +import { + findMetadataInsertPosition, +} from "../editor-ext/autoDateManager"; +import TaskProgressBarPlugin from "../index"; + +describe("autoDateManager - Simple Cancelled Date Test", () => { + it("should insert cancelled date after 🛫 date", () => { + const mockPlugin: Partial = { + settings: { + autoDateManager: { + enabled: true, + startDateMarker: "🛫", + completedDateMarker: "✅", + cancelledDateMarker: "❌", + }, + preferMetadataFormat: "emoji", + }, + } as unknown as TaskProgressBarPlugin; + + // Simple case: just 🛫 date and block ref + const lineText = "- [-] Task 🛫 2025-04-20 ^block-id"; + + const position = findMetadataInsertPosition( + lineText, + mockPlugin as TaskProgressBarPlugin, + "cancelled" + ); + + console.log("Position:", position); + console.log("Text after position:", lineText.substring(position)); + + // Should insert after 🛫 date + expect(lineText.substring(position)).toBe(" ^block-id"); + }); + + it("should handle complex line with dataview", () => { + const mockPlugin: Partial = { + settings: { + autoDateManager: { + enabled: true, + startDateMarker: "🛫", + completedDateMarker: "✅", + cancelledDateMarker: "❌", + }, + preferMetadataFormat: "emoji", + }, + } as unknown as TaskProgressBarPlugin; + + const lineText = "- [-] 交流交底 🚀 2025-07-30 [stage::disclosure_communication] 🛫 2025-04-20 ^timer-161940-4775"; + + const position = findMetadataInsertPosition( + lineText, + mockPlugin as TaskProgressBarPlugin, + "cancelled" + ); + + console.log("Complex line position:", position); + console.log("Text before:", lineText.substring(0, position)); + console.log("Text after:", lineText.substring(position)); + + // Should insert after 🛫 2025-04-20 + const expectedAfter = " ^timer-161940-4775"; + expect(lineText.substring(position)).toBe(expectedAfter); + }); +}); \ No newline at end of file diff --git a/src/__tests__/autoDateManager.test.ts b/src/__tests__/autoDateManager.test.ts new file mode 100644 index 00000000..5b85c57c --- /dev/null +++ b/src/__tests__/autoDateManager.test.ts @@ -0,0 +1,258 @@ +// @ts-ignore +import { describe, it, expect, beforeEach, jest } from "@jest/globals"; +import { + handleAutoDateManagerTransaction, + findTaskStatusChange, + determineDateOperations, + getStatusType, + applyDateOperations, + isMoveOperation, + findMetadataInsertPosition, +} from "../editor-ext/autoDateManager"; +import { Transaction, Text, EditorState } from "@codemirror/state"; +import TaskProgressBarPlugin from "../index"; +import { App } from "obsidian"; + +// Mock the plugin +const mockPlugin: Partial = { + settings: { + autoDateManager: { + enabled: true, + manageStartDate: true, + manageCompletedDate: true, + manageCancelledDate: true, + startDateFormat: "YYYY-MM-DD", + completedDateFormat: "YYYY-MM-DD", + cancelledDateFormat: "YYYY-MM-DD", + startDateMarker: "🚀", + completedDateMarker: "✅", + cancelledDateMarker: "❌", + }, + preferMetadataFormat: "emoji", + taskStatuses: { + completed: "x|X", + inProgress: "/|-", + abandoned: "_", + planned: "!", + notStarted: " ", + }, + }, +} as unknown as TaskProgressBarPlugin; + +// Mock the App +const mockApp = {} as App; + +describe("autoDateManager - Block Reference Support", () => { + describe("Block Reference Detection", () => { + it("should detect simple block reference at end of line", () => { + const lineText = "- [ ] Task with block reference ^task-123"; + const position = findMetadataInsertPosition( + lineText, + mockPlugin as TaskProgressBarPlugin, + "completed" + ); + // Should insert before the block reference + expect(lineText.substring(position)).toBe(" ^task-123"); + }); + + it("should detect block reference with trailing spaces", () => { + const lineText = "- [ ] Task with block reference ^task-123 "; + const position = findMetadataInsertPosition( + lineText, + mockPlugin as TaskProgressBarPlugin, + "completed" + ); + // Should insert before the block reference + expect(lineText.substring(position)).toBe(" ^task-123 "); + }); + + it("should detect block reference with underscores and hyphens", () => { + const lineText = "- [ ] Task with block reference ^task_123-abc"; + const position = findMetadataInsertPosition( + lineText, + mockPlugin as TaskProgressBarPlugin, + "completed" + ); + // Should insert before the block reference + expect(lineText.substring(position)).toBe(" ^task_123-abc"); + }); + + it("should not confuse caret in middle of text with block reference", () => { + const lineText = "- [ ] Task with ^caret in middle and more text"; + const position = findMetadataInsertPosition( + lineText, + mockPlugin as TaskProgressBarPlugin, + "completed" + ); + // Should insert at the end since ^caret is not at the end + expect(position).toBe(lineText.length); + }); + }); + + describe("Date Insertion with Block References", () => { + it("should insert completed date before block reference", () => { + const lineText = "- [ ] Task to complete ^task-123"; + const position = findMetadataInsertPosition( + lineText, + mockPlugin as TaskProgressBarPlugin, + "completed" + ); + // Position should be before the block reference + expect(lineText.substring(0, position)).toBe("- [ ] Task to complete"); + expect(lineText.substring(position)).toBe(" ^task-123"); + }); + + it("should insert start date before block reference", () => { + const lineText = "- [ ] Task to start ^task-456"; + const position = findMetadataInsertPosition( + lineText, + mockPlugin as TaskProgressBarPlugin, + "start" + ); + // Position should be before the block reference + expect(lineText.substring(0, position)).toBe("- [ ] Task to start"); + expect(lineText.substring(position)).toBe(" ^task-456"); + }); + + it("should insert cancelled date after start date but before block reference", () => { + const lineText = "- [ ] Task with start date 🚀 2024-01-15 ^task-789"; + const position = findMetadataInsertPosition( + lineText, + mockPlugin as TaskProgressBarPlugin, + "cancelled" + ); + // Position should be after start date but before block reference + expect(lineText.substring(0, position)).toBe("- [ ] Task with start date 🚀 2024-01-15"); + expect(lineText.substring(position)).toBe(" ^task-789"); + }); + + it("should handle multiple metadata before block reference", () => { + const lineText = "- [ ] Task with tags #important #urgent ^task-999"; + const position = findMetadataInsertPosition( + lineText, + mockPlugin as TaskProgressBarPlugin, + "completed" + ); + // Position should be after tags but before block reference + expect(lineText.substring(position)).toBe(" ^task-999"); + }); + }); + + describe("Date Removal with Block References", () => { + it("should preserve block reference when removing completed date", () => { + // This test would require mocking the full transaction system + // For now, we test that the position calculation is correct + const lineText = "- [x] Completed task ✅ 2024-01-20 ^task-123"; + const position = findMetadataInsertPosition( + lineText, + mockPlugin as TaskProgressBarPlugin, + "completed" + ); + // The position should still respect the block reference + expect(lineText.substring(position)).toBe(" ^task-123"); + }); + }); + + describe("Complex Block Reference Scenarios", () => { + it("should handle task with dataview fields and block reference", () => { + const lineText = "- [ ] Task [due::2024-01-25] [priority::high] ^complex-123"; + const position = findMetadataInsertPosition( + lineText, + mockPlugin as TaskProgressBarPlugin, + "completed" + ); + // Should insert after dataview fields but before block reference + expect(lineText.substring(position)).toBe(" ^complex-123"); + }); + + it("should handle task with emojis and block reference", () => { + const lineText = "- [ ] Task with emoji 📅 2024-01-25 ^emoji-task"; + const position = findMetadataInsertPosition( + lineText, + mockPlugin as TaskProgressBarPlugin, + "completed" + ); + // Should insert after date emoji but before block reference + expect(lineText.substring(position)).toBe(" ^emoji-task"); + }); + + it("should handle task with wikilinks and block reference", () => { + const lineText = "- [ ] Task mentioning [[Some Page]] ^wiki-task"; + const position = findMetadataInsertPosition( + lineText, + mockPlugin as TaskProgressBarPlugin, + "completed" + ); + // Should insert after wikilink but before block reference + expect(lineText.substring(position)).toBe(" ^wiki-task"); + }); + }); + + describe("Edge Cases", () => { + it("should handle empty task with only block reference", () => { + const lineText = "- [ ] ^empty-task"; + const position = findMetadataInsertPosition( + lineText, + mockPlugin as TaskProgressBarPlugin, + "completed" + ); + // Should insert before block reference + expect(lineText.substring(position)).toBe(" ^empty-task"); + }); + + it("should handle block reference without space", () => { + const lineText = "- [ ] Task^no-space"; + const position = findMetadataInsertPosition( + lineText, + mockPlugin as TaskProgressBarPlugin, + "completed" + ); + // Should insert before block reference + expect(lineText.substring(position)).toBe("^no-space"); + }); + + it("should handle very long block reference IDs", () => { + const lineText = "- [ ] Task ^very-long-block-reference-id-that-might-be-generated-automatically"; + const position = findMetadataInsertPosition( + lineText, + mockPlugin as TaskProgressBarPlugin, + "completed" + ); + // Should insert before block reference + expect(lineText.substring(position)).toBe(" ^very-long-block-reference-id-that-might-be-generated-automatically"); + }); + }); +}); + +describe("autoDateManager - Dataview Format with Block References", () => { + const mockPluginDataview: Partial = { + ...mockPlugin, + settings: { + ...mockPlugin.settings!, + preferMetadataFormat: "dataview", + }, + } as unknown as TaskProgressBarPlugin; + + it("should insert dataview date before block reference", () => { + const lineText = "- [ ] Task with dataview format ^dataview-123"; + const position = findMetadataInsertPosition( + lineText, + mockPluginDataview as TaskProgressBarPlugin, + "completed" + ); + // Should insert before block reference + expect(lineText.substring(position)).toBe(" ^dataview-123"); + }); + + it("should handle existing dataview fields with block reference", () => { + const lineText = "- [ ] Task [start::2024-01-15] ^dataview-456"; + const position = findMetadataInsertPosition( + lineText, + mockPluginDataview as TaskProgressBarPlugin, + "cancelled" + ); + // Should insert after start date but before block reference + expect(lineText.substring(0, position)).toBe("- [ ] Task [start::2024-01-15]"); + expect(lineText.substring(position)).toBe(" ^dataview-456"); + }); +}); \ No newline at end of file diff --git a/src/__tests__/autoDateManager.tracing.test.ts b/src/__tests__/autoDateManager.tracing.test.ts new file mode 100644 index 00000000..83889d93 --- /dev/null +++ b/src/__tests__/autoDateManager.tracing.test.ts @@ -0,0 +1,100 @@ +// @ts-ignore +import { describe, it, expect } from "@jest/globals"; + +// Import the internal functions we need to test +import { Text, EditorState } from "@codemirror/state"; + +describe("autoDateManager - Execution Tracing", () => { + it("should trace the exact problem with user's text", () => { + const lineText = "- [-] 交流交底 🚀 2025-07-30 [stage::disclosure_communication] 🛫 2025-04-20 ^timer-161940-4775"; + + console.log("\n=== DETAILED EXECUTION TRACE ==="); + console.log("Original line:", lineText); + console.log("Line length:", lineText.length); + + // Step 1: Manually extract block reference + const blockRefPattern = /\s*(\^[\w-]+)\s*$/; + const blockRefMatch = lineText.match(blockRefPattern); + + if (blockRefMatch) { + console.log("\n1. Block Reference Detection:"); + console.log(" - Full match:", JSON.stringify(blockRefMatch[0])); + console.log(" - Block ID:", blockRefMatch[1]); + console.log(" - Match index:", blockRefMatch.index); + console.log(" - Match length:", blockRefMatch[0].length); + + const cleanedText = lineText.substring(0, blockRefMatch.index).trimEnd(); + console.log(" - Cleaned text:", JSON.stringify(cleanedText)); + console.log(" - Cleaned length:", cleanedText.length); + } + + // Step 2: Check task pattern + const taskMatch = lineText.match(/^[\s|\t]*([-*+]|\d+\.)\s\[.\]\s*/); + if (taskMatch) { + console.log("\n2. Task Pattern:"); + console.log(" - Task prefix:", JSON.stringify(taskMatch[0])); + console.log(" - Initial position:", taskMatch[0].length); + } + + // Step 3: Look for 🚀 emoji (configured) + console.log("\n3. Configured Emoji Search (🚀):"); + const rocketPattern = /🚀\s*\d{4}-\d{2}-\d{2}/g; + let match; + while ((match = rocketPattern.exec(lineText)) !== null) { + console.log(" - Found at index:", match.index); + console.log(" - Match:", JSON.stringify(match[0])); + console.log(" - End position:", match.index + match[0].length); + } + + // Step 4: Look for 🛫 emoji (actual) + console.log("\n4. Actual Emoji Search (🛫):"); + const planePattern = /🛫\s*\d{4}-\d{2}-\d{2}/g; + while ((match = planePattern.exec(lineText)) !== null) { + console.log(" - Found at index:", match.index); + console.log(" - Match:", JSON.stringify(match[0])); + console.log(" - End position:", match.index + match[0].length); + } + + // Step 5: Find all metadata + console.log("\n5. All Metadata Search:"); + const metadataPatterns = [ + { name: "Tags", pattern: /#[\w-]+/g }, + { name: "Dataview", pattern: /\[[a-zA-Z]+::[^\]]*\]/g }, + { name: "Date markers", pattern: /[📅🚀✅❌🛫▶️⏰🏁]\s*\d{4}-\d{2}-\d{2}(?:\s+\d{2}:\d{2}(?::\d{2})?)?/g } + ]; + + metadataPatterns.forEach(({ name, pattern }) => { + console.log(` - ${name}:`); + pattern.lastIndex = 0; + while ((match = pattern.exec(lineText)) !== null) { + console.log(` * Index ${match.index}: ${JSON.stringify(match[0])}`); + } + }); + + // Step 6: Determine where cancelled date would go + console.log("\n6. Position Analysis:"); + + // If we found 🛫 2025-04-20 + const planeMatch = lineText.match(/🛫\s*\d{4}-\d{2}-\d{2}/); + if (planeMatch) { + const afterPlanePos = planeMatch.index! + planeMatch[0].length; + console.log(" - After 🛫 date:", afterPlanePos); + console.log(" - Text after that position:", JSON.stringify(lineText.substring(afterPlanePos))); + } + + // Find the last metadata item + let lastMetadataEnd = 0; + [/#[\w-]+/g, /\[[a-zA-Z]+::[^\]]*\]/g, /[📅🚀✅❌🛫▶️⏰🏁]\s*\d{4}-\d{2}-\d{2}/g].forEach(pattern => { + pattern.lastIndex = 0; + while ((match = pattern.exec(lineText)) !== null) { + const end = match.index + match[0].length; + if (end > lastMetadataEnd) { + lastMetadataEnd = end; + } + } + }); + + console.log(" - Last metadata ends at:", lastMetadataEnd); + console.log(" - Text after last metadata:", JSON.stringify(lineText.substring(lastMetadataEnd))); + }); +}); \ No newline at end of file diff --git a/src/__tests__/taskTimer.pause-fix.test.ts b/src/__tests__/taskTimer.pause-fix.test.ts new file mode 100644 index 00000000..67047385 --- /dev/null +++ b/src/__tests__/taskTimer.pause-fix.test.ts @@ -0,0 +1,105 @@ +// @ts-ignore +import { describe, it, expect } from "@jest/globals"; + +describe("Task Timer - Pause Fix", () => { + it("should document the new pause behavior", () => { + // OLD BEHAVIOR (problematic): + // 1. User clicks "Pause" on timer + // 2. Timer widget changes task status from [/] to [-] + // 3. This triggers autoDateManager + // 4. autoDateManager adds cancelled date (even if disabled) + // 5. Multiple plugins might conflict over the status change + + // NEW BEHAVIOR (fixed): + // 1. User clicks "Pause" on timer + // 2. Timer state is updated in localStorage (paused) + // 3. Task status remains unchanged (still [/]) + // 4. No transaction is dispatched, so autoDateManager is not triggered + // 5. Timer shows as paused in UI but task status reflects it's still in-progress + + const oldBehavior = { + pauseAction: "Update task status to [-]", + sideEffect: "Triggers autoDateManager", + conflict: "Even with cancelled date disabled, transaction is processed" + }; + + const newBehavior = { + pauseAction: "Only update timer state in localStorage", + sideEffect: "No transaction, no autoDateManager trigger", + benefit: "Clean separation between timer state and task status" + }; + + expect(newBehavior.sideEffect).toBe("No transaction, no autoDateManager trigger"); + }); + + it("should explain the timer state vs task status distinction", () => { + // Timer State (in localStorage): + // - running: Timer is actively counting + // - paused: Timer is temporarily stopped but preserves elapsed time + // - stopped: Timer is reset to 00:00 + + // Task Status (in markdown): + // - [ ]: Not started + // - [/]: In progress + // - [-]: Abandoned/Cancelled + // - [x]: Completed + + // Key insight: Timer state and task status are independent + // A task can be "in progress" with a paused timer + // This is actually more accurate - the task isn't abandoned, just temporarily paused + + const scenarios = [ + { + action: "Start timer", + timerState: "running", + taskStatus: "[/]", // Changes to in-progress + statusChange: true + }, + { + action: "Pause timer", + timerState: "paused", + taskStatus: "[/]", // Remains in-progress + statusChange: false + }, + { + action: "Resume timer", + timerState: "running", + taskStatus: "[/]", // Still in-progress + statusChange: false + }, + { + action: "Complete timer", + timerState: "stopped", + taskStatus: "[x]", // Changes to completed + statusChange: true + }, + { + action: "Reset timer", + timerState: "stopped", + taskStatus: "[/]", // Remains as is (user manages) + statusChange: false + } + ]; + + // Only Start and Complete should change task status + const statusChangingActions = scenarios.filter(s => s.statusChange); + expect(statusChangingActions).toHaveLength(2); + expect(statusChangingActions[0].action).toBe("Start timer"); + expect(statusChangingActions[1].action).toBe("Complete timer"); + }); + + it("should list benefits of the new approach", () => { + const benefits = [ + "No conflicts with autoDateManager", + "No conflicts with other status-monitoring plugins", + "Cleaner separation of concerns", + "Timer state persists independently of task status", + "User has full control over task status", + "Pause is truly temporary - doesn't imply task abandonment", + "Multiple pauses/resumes don't create multiple date entries" + ]; + + expect(benefits).toContain("No conflicts with autoDateManager"); + expect(benefits).toContain("Pause is truly temporary - doesn't imply task abandonment"); + }); +}); \ No newline at end of file diff --git a/src/common/setting-definition.ts b/src/common/setting-definition.ts index 6d13d7ae..d5f5174f 100644 --- a/src/common/setting-definition.ts +++ b/src/common/setting-definition.ts @@ -520,6 +520,21 @@ export interface TimelineSidebarSettings { quickInputShowQuickActions: boolean; } +/** Task Timer Metadata Detection Settings */ +export interface TaskTimerMetadataDetection { + frontmatter: string; + folders: string[]; + tags: string[]; +} + +/** Task Timer Settings */ +export interface TaskTimerSettings { + enabled: boolean; + metadataDetection: TaskTimerMetadataDetection; + timeFormat: string; + blockRefPrefix: string; +} + /** OnCompletion Settings */ export interface OnCompletionSettings { /** Whether onCompletion functionality is enabled */ @@ -686,6 +701,9 @@ export interface TaskProgressBarSettings { // Time Parsing Settings timeParsing: TimeParsingConfig; + // Task Timer Settings + taskTimer: TaskTimerSettings; + // Onboarding Settings onboarding?: { completed: boolean; @@ -1365,6 +1383,18 @@ export const DEFAULT_SETTINGS: TaskProgressBarSettings = { realTimeReplacement: true, }, + // Task Timer Defaults + taskTimer: { + enabled: false, + metadataDetection: { + frontmatter: "task-timer", + folders: [], + tags: ["timer", "tracked"] + }, + timeFormat: "{h}hrs {m}mins", + blockRefPrefix: "timer" + }, + // Onboarding Defaults onboarding: { completed: false, diff --git a/src/components/gantt/task-renderer.ts b/src/components/gantt/task-renderer.ts index 033471bd..5a5ea1a9 100644 --- a/src/components/gantt/task-renderer.ts +++ b/src/components/gantt/task-renderer.ts @@ -7,6 +7,7 @@ import { import { GanttTaskItem, PlacedGanttTaskItem, Timescale } from "./gantt"; // 添加PlacedGanttTaskItem导入 import { Task } from "../../types/task"; import { MarkdownRendererComponent } from "../MarkdownRenderer"; +import { sanitizePriorityForClass } from "../../utils/priorityUtils"; // Constants from GanttComponent (consider moving to a shared config/constants file) const ROW_HEIGHT = 24; @@ -142,13 +143,11 @@ export class TaskRendererComponent extends Component { if (task.status && task.status.trim()) { taskElement.classList.add(`status-${task.status.trim()}`); } - if ( - task.metadata.priority && - String(task.metadata.priority).trim() - ) { - taskElement.classList.add( - `priority-${String(task.metadata.priority).trim()}` - ); + if (task.metadata.priority) { + const sanitizedPriority = sanitizePriorityForClass(task.metadata.priority); + if (sanitizedPriority) { + taskElement.classList.add(`priority-${sanitizedPriority}`); + } } // Add text label to the right @@ -225,13 +224,11 @@ export class TaskRendererComponent extends Component { if (task.status && task.status.trim()) { taskElement.classList.add(`status-${task.status.trim()}`); } - if ( - task.metadata.priority && - String(task.metadata.priority).trim() - ) { - taskElement.classList.add( - `priority-${String(task.metadata.priority).trim()}` - ); + if (task.metadata.priority) { + const sanitizedPriority = sanitizePriorityForClass(task.metadata.priority); + if (sanitizedPriority) { + taskElement.classList.add(`priority-${sanitizedPriority}`); + } } // Add tooltip for bar diff --git a/src/components/kanban/kanban-card.ts b/src/components/kanban/kanban-card.ts index 239a3b8d..8d2a1b11 100644 --- a/src/components/kanban/kanban-card.ts +++ b/src/components/kanban/kanban-card.ts @@ -5,6 +5,7 @@ import TaskProgressBarPlugin from "../../index"; // Adjust path import { KanbanSpecificConfig } from "../../common/setting-definition"; import { createTaskCheckbox } from "../task-view/details"; import { getEffectiveProject } from "../../utils/taskUtil"; +import { sanitizePriorityForClass } from "../../utils/priorityUtils"; export class KanbanCardComponent extends Component { public element: HTMLElement; @@ -49,7 +50,10 @@ export class KanbanCardComponent extends Component { } const metadata = this.task.metadata || {}; if (metadata.priority) { - this.element.classList.add(`priority-${metadata.priority}`); + const sanitizedPriority = sanitizePriorityForClass(metadata.priority); + if (sanitizedPriority) { + this.element.classList.add(`priority-${sanitizedPriority}`); + } } // --- Card Content --- @@ -267,13 +271,12 @@ export class KanbanCardComponent extends Component { private renderPriority() { const metadata = this.task.metadata || {}; - const priorityEl = this.metadataEl.createDiv({ - cls: [ - "task-priority", - `priority-${metadata.priority}`, - "clickable-metadata", - ], - }); + const sanitizedPriority = sanitizePriorityForClass(metadata.priority); + const classes = ["task-priority", "clickable-metadata"]; + if (sanitizedPriority) { + classes.push(`priority-${sanitizedPriority}`); + } + const priorityEl = this.metadataEl.createDiv({ cls: classes }); priorityEl.textContent = `${"!".repeat(metadata.priority || 0)}`; priorityEl.setAttribute("aria-label", `Priority ${metadata.priority}`); @@ -341,12 +344,18 @@ export class KanbanCardComponent extends Component { this.element.classList.toggle("task-completed", newTask.completed); } if (oldMetadata.priority !== newMetadata.priority) { - if (oldMetadata.priority) - this.element.classList.remove( - `priority-${oldMetadata.priority}` - ); - if (newMetadata.priority) - this.element.classList.add(`priority-${newMetadata.priority}`); + if (oldMetadata.priority) { + const oldSanitized = sanitizePriorityForClass(oldMetadata.priority); + if (oldSanitized) { + this.element.classList.remove(`priority-${oldSanitized}`); + } + } + if (newMetadata.priority) { + const newSanitized = sanitizePriorityForClass(newMetadata.priority); + if (newSanitized) { + this.element.classList.add(`priority-${newSanitized}`); + } + } } // Re-render content and metadata if needed diff --git a/src/components/quadrant/quadrant-card.ts b/src/components/quadrant/quadrant-card.ts index 49254a25..9609ce67 100644 --- a/src/components/quadrant/quadrant-card.ts +++ b/src/components/quadrant/quadrant-card.ts @@ -4,6 +4,7 @@ import { Task } from "../../types/task"; import { createTaskCheckbox } from "../task-view/details"; import { MarkdownRendererComponent } from "../MarkdownRenderer"; import { t } from "../../translations/helper"; +import { sanitizePriorityForClass } from "../../utils/priorityUtils"; export class QuadrantCardComponent extends Component { plugin: TaskProgressBarPlugin; @@ -217,12 +218,12 @@ export class QuadrantCardComponent extends Component { numericPriority = this.task.metadata.priority; } - const priorityEl = el.createDiv({ - cls: [ - "tg-quadrant-card-priority", - `priority-${numericPriority}`, - ], - }); + const sanitizedPriority = sanitizePriorityForClass(numericPriority); + const classes = ["tg-quadrant-card-priority"]; + if (sanitizedPriority) { + classes.push(`priority-${sanitizedPriority}`); + } + const priorityEl = el.createDiv({ cls: classes }); // 根据优先级数字显示不同数量的感叹号 let icon = "!".repeat(numericPriority); diff --git a/src/components/task-view/InlineEditor.ts b/src/components/task-view/InlineEditor.ts index e23b64a0..35817eac 100644 --- a/src/components/task-view/InlineEditor.ts +++ b/src/components/task-view/InlineEditor.ts @@ -10,6 +10,7 @@ import { import "../../styles/inline-editor.css"; import { getEffectiveProject, isProjectReadonly } from "../../utils/taskUtil"; import { t } from "../../translations/helper"; +import { sanitizePriorityForClass } from "../../utils/priorityUtils"; export interface InlineEditorOptions { onTaskUpdate: (task: Task, updatedTask: Task) => Promise; @@ -1344,7 +1345,8 @@ export class InlineEditor extends Component { targetEl.textContent = "!".repeat( this.task.metadata.priority ); - targetEl.className = `task-priority priority-${this.task.metadata.priority}`; + const sanitizedPriority = sanitizePriorityForClass(this.task.metadata.priority); + targetEl.className = sanitizedPriority ? `task-priority priority-${sanitizedPriority}` : "task-priority"; } break; case "onCompletion": diff --git a/src/components/task-view/listItem.ts b/src/components/task-view/listItem.ts index 72b795a4..a294a6a5 100644 --- a/src/components/task-view/listItem.ts +++ b/src/components/task-view/listItem.ts @@ -9,6 +9,7 @@ import TaskProgressBarPlugin from "../../index"; import { TaskProgressBarSettings } from "../../common/setting-definition"; import { InlineEditor, InlineEditorOptions } from "./InlineEditor"; import { InlineEditorManager } from "./InlineEditorManager"; +import { sanitizePriorityForClass } from "../../utils/priorityUtils"; export class TaskListItemComponent extends Component { public element: HTMLElement; @@ -227,9 +228,12 @@ export class TaskListItemComponent extends Component { numericPriority = this.task.metadata.priority; } - const priorityEl = createDiv({ - cls: ["task-priority", `priority-${numericPriority}`], - }); + const sanitizedPriority = sanitizePriorityForClass(numericPriority); + const classes = ["task-priority"]; + if (sanitizedPriority) { + classes.push(`priority-${sanitizedPriority}`); + } + const priorityEl = createDiv({ cls: classes }); // Priority icon based on level let icon = "•"; diff --git a/src/components/task-view/treeItem.ts b/src/components/task-view/treeItem.ts index 4933027c..9a37d563 100644 --- a/src/components/task-view/treeItem.ts +++ b/src/components/task-view/treeItem.ts @@ -14,6 +14,7 @@ import { t } from "../../translations/helper"; import TaskProgressBarPlugin from "../../index"; import { InlineEditor, InlineEditorOptions } from "./InlineEditor"; import { InlineEditorManager } from "./InlineEditorManager"; +import { sanitizePriorityForClass } from "../../utils/priorityUtils"; export class TaskTreeItemComponent extends Component { public element: HTMLElement; @@ -286,12 +287,12 @@ export class TaskTreeItemComponent extends Component { // Priority indicator if available if (this.task.metadata.priority) { - const priorityEl = createDiv({ - cls: [ - "task-priority", - `priority-${this.task.metadata.priority}`, - ], - }); + const sanitizedPriority = sanitizePriorityForClass(this.task.metadata.priority); + const classes = ["task-priority"]; + if (sanitizedPriority) { + classes.push(`priority-${sanitizedPriority}`); + } + const priorityEl = createDiv({ cls: classes }); // Priority icon based on level let icon = "•"; diff --git a/src/editor-ext/autoDateManager.ts b/src/editor-ext/autoDateManager.ts index 75d35fe4..b9794d87 100644 --- a/src/editor-ext/autoDateManager.ts +++ b/src/editor-ext/autoDateManager.ts @@ -1,4 +1,4 @@ -import { App, Editor } from "obsidian"; +import { App } from "obsidian"; import { EditorState, Text, @@ -71,7 +71,7 @@ function handleAutoDateManagerTransaction( return tr; } - const { doc, lineNumber, oldStatus, newStatus } = taskStatusChangeInfo; + const {doc, lineNumber, oldStatus, newStatus} = taskStatusChangeInfo; // Determine what date operations need to be performed const dateOperations = determineDateOperations( @@ -458,10 +458,13 @@ function applyDateOperations( operations: DateOperation[], plugin: TaskProgressBarPlugin ): TransactionSpec { - const line = doc.line(lineNumber); + // IMPORTANT: Use the NEW document state, not the old one + const line = tr.newDoc.line(lineNumber); let lineText = line.text; const changes = []; + console.log(`[AutoDateManager] applyDateOperations - Working with line: "${lineText}"`); + for (const operation of operations) { if (operation.type === "add") { // Add a new date @@ -496,6 +499,13 @@ function applyDateOperations( const absolutePosition = line.from + insertPosition; + console.log(`[AutoDateManager] Inserting ${operation.dateType} date:`); + console.log(` - Insert position (relative): ${insertPosition}`); + console.log(` - Line.from: ${line.from}`); + console.log(` - Absolute position: ${absolutePosition}`); + console.log(` - Date text: "${dateText}"`); + console.log(` - Text at insert point: "${lineText.substring(insertPosition)}"`); + changes.push({ from: absolutePosition, to: absolutePosition, @@ -519,8 +529,8 @@ function applyDateOperations( operation.dateType === "completed" ? "completion" : operation.dateType === "cancelled" - ? "cancelled" - : "unknown"; + ? "cancelled" + : "unknown"; datePattern = new RegExp( `\\s*\\[${fieldName}::\\s*\\d{4}-\\d{2}-\\d{2}(?:\\s+\\d{2}:\\d{2}(?::\\d{2})?)?\\]`, "g" @@ -537,6 +547,7 @@ function applyDateOperations( } // Find all matches and remove them (there might be multiple instances) + // Work with the full lineText let match; const matchesToRemove = []; datePattern.lastIndex = 0; // Reset regex state @@ -649,53 +660,162 @@ function findMetadataInsertPosition( plugin: TaskProgressBarPlugin, dateType: string ): number { + // Work with the full line text, don't extract block reference yet + const blockRef = detectBlockReference(lineText); + // Find the end of the task content, right after the task description const taskMatch = lineText.match(/^[\s|\t]*([-*+]|\d+\.)\s\[.\]\s*/); - if (!taskMatch) return lineText.length; + if (!taskMatch) return blockRef ? blockRef.index : lineText.length; let position = taskMatch[0].length; - // Find the main task content (description) before any metadata - // FIXED: Removed @ from metadata detection since @ mentions (like @陈烽) are part of content, not metadata - // Only actual metadata markers are: [ (dataview), # (tags), and emoji markers (📅🚀✅❌) - const contentMatch = lineText - .slice(position) - .match(/^[^\[#📅🚀✅❌]*?(?=\s*[\[#📅🚀✅❌]|\s*$)/); - - if (contentMatch) { - position += contentMatch[0].trimEnd().length; - } - - // If we're inserting a cancelled date, we need to find the position after existing start dates + // For cancelled date, we need special handling to insert after all metadata and start dates if (dateType === "cancelled") { - const useDataviewFormat = - plugin.settings.preferMetadataFormat === "dataview"; + const useDataviewFormat = plugin.settings.preferMetadataFormat === "dataview"; - // Look for existing start dates and position after them - const remainingText = lineText.slice(position); - let startDateEnd = 0; + // Find the last occurrence of either: + // 1. Start date marker (🛫 or [start::) + // 2. If no start date, find the end of all metadata + // Look for start date first + let startDateFound = false; if (useDataviewFormat) { - const startDateMatch = remainingText.match(/^\s*\[start::[^\]]*\]/); - if (startDateMatch) { - startDateEnd = startDateMatch[0].length; + const startDateMatch = lineText.match(/\[start::[^\]]*\]/); + if (startDateMatch && startDateMatch.index !== undefined) { + position = startDateMatch.index + startDateMatch[0].length; + startDateFound = true; } } else { + // First try with the configured start marker const startMarker = getDateMarker("start", plugin); + const escapedStartMarker = escapeRegex(startMarker); const startDatePattern = new RegExp( - `^\\s*${escapeRegex( - startMarker - )}\\s*\\d{4}-\\d{2}-\\d{2}(?:\\s+\\d{2}:\\d{2}(?::\\d{2})?)?` + `${escapedStartMarker}\\s*\\d{4}-\\d{2}-\\d{2}(?:\\s+\\d{2}:\\d{2}(?::\\d{2})?)?` ); - const startDateMatch = remainingText.match(startDatePattern); - if (startDateMatch) { - startDateEnd = startDateMatch[0].length; + let startDateMatch = lineText.match(startDatePattern); + + // If not found, look for any common start date emoji patterns + if (!startDateMatch) { + // Common start date emojis: 🚀, 🛫, ▶️, ⏰, 🏁 + const commonStartEmojis = ["🚀", "🛫", "▶️", "⏰", "🏁"]; + for (const emoji of commonStartEmojis) { + const pattern = new RegExp( + `${escapeRegex(emoji)}\\s*\\d{4}-\\d{2}-\\d{2}(?:\\s+\\d{2}:\\d{2}(?::\\d{2})?)?` + ); + startDateMatch = lineText.match(pattern); + if (startDateMatch) { + console.log(`[AutoDateManager] Found start date with emoji ${emoji}`); + break; + } + } + } + + if (startDateMatch && startDateMatch.index !== undefined) { + position = startDateMatch.index + startDateMatch[0].length; + startDateFound = true; + console.log(`[AutoDateManager] Found start date at index ${startDateMatch.index}, length ${startDateMatch[0].length}, new position: ${position}`); + } + } + + console.log(`[AutoDateManager] Start date found: ${startDateFound}, position: ${position}`); + + if (!startDateFound) { + // No start date found, find the end of all metadata + // This includes tags (#), dataview fields ([field::]), and other date markers + let lastMetadataEnd = position; + + // Find all metadata occurrences + const metadataRegexes = [ + /#[\w-]+/g, // Tags + /\[[a-zA-Z]+::[^\]]*\]/g, // Dataview fields + /[📅🚀✅❌🛫▶️⏰🏁]\s*\d{4}-\d{2}-\d{2}(?:\s+\d{2}:\d{2}(?::\d{2})?)?/g // Date markers (including all common start emojis) + ]; + + for (const regex of metadataRegexes) { + let match; + while ((match = regex.exec(lineText)) !== null) { + if (match.index >= position) { + const matchEnd = match.index + match[0].length; + if (matchEnd > lastMetadataEnd) { + lastMetadataEnd = matchEnd; + } + } + } + } + + position = lastMetadataEnd; + } + + // Ensure we have a space before the cancelled date + if (position > 0 && lineText[position - 1] !== ' ') { + // No need to add space here, it will be added with the date + } + } else if (dateType === "completed") { + // For completed date, we want to go to the end of the line (before block reference) + // This is different from cancelled/start dates which go after content/metadata + position = lineText.length; + + // If there's a block reference, insert before it + if (blockRef) { + position = blockRef.index; + // Remove trailing space if exists + if (position > 0 && lineText[position - 1] === ' ') { + position--; + } + } + } else { + // For start date, find the end of main content before metadata + let contentEnd = position; + let inBrackets = false; + const chars = lineText.slice(position).split(''); + + for (let i = 0; i < chars.length; i++) { + const char = chars[i]; + + // Track if we're inside dataview brackets + if (char === '[' && !inBrackets) { + // Check if this is a dataview field like [due::...] + const remainingText = lineText.slice(position + i); + if (remainingText.match(/^\[[a-zA-Z]+::/)) { + // This is metadata, stop here + break; + } + inBrackets = true; + contentEnd = position + i + 1; + } else if (char === ']' && inBrackets) { + inBrackets = false; + contentEnd = position + i + 1; + } else if (!inBrackets) { + // Check for metadata markers when not inside brackets + if (char === '#' || char === '📅' || char === '🚀' || char === '✅' || char === '❌' || char === '🛫' || char === '▶️' || char === '⏰' || char === '🏁') { + // This is metadata, stop here + break; + } + contentEnd = position + i + 1; + } else { + // Inside brackets, keep going + contentEnd = position + i + 1; } } - position += startDateEnd; + position = contentEnd; + + // Trim any trailing whitespace from position + while (position > 0 && lineText[position - 1] === ' ') { + position--; + } + } + + // Ensure position doesn't exceed the block reference position + if (blockRef && position > blockRef.index) { + position = blockRef.index; + // Remove trailing space if it exists + if (position > 0 && lineText[position - 1] === ' ') { + position--; + } } + console.log(`[AutoDateManager] Final insert position for ${dateType}: ${position}`); return position; } @@ -709,11 +829,16 @@ function findCompletedDateInsertPosition( lineText: string, plugin: TaskProgressBarPlugin ): number { - // Look for block reference ID pattern (^block-id) at the end - const blockRefMatch = lineText.match(/\s*\^[\w-]+\s*$/); - if (blockRefMatch) { + // Use centralized block reference detection + const blockRef = detectBlockReference(lineText); + if (blockRef) { // Insert before the block reference ID - return lineText.length - blockRefMatch[0].length; + // Remove trailing space if exists + let position = blockRef.index; + if (position > 0 && lineText[position - 1] === ' ') { + position--; + } + return position; } // If no block reference, insert at the very end @@ -729,6 +854,56 @@ function escapeRegex(string: string): string { return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); } +/** + * Detects block reference ID in the text + * @param text The text to check + * @returns Object with block reference info or null if not found + */ +function detectBlockReference(text: string): { + blockId: string; + index: number; + length: number; + fullMatch: string; +} | null { + // More comprehensive block reference pattern: + // - Matches ^block-id format + // - Can have optional whitespace before and after + // - Block ID can contain letters, numbers, hyphens, and underscores + // - Must be at the end of the line + const blockRefPattern = /\s*(\^[a-zA-Z0-9-]+)$/; + const match = text.match(blockRefPattern); + + if (match && match.index !== undefined) { + return { + blockId: match[1], + index: match.index, + length: match[0].length, + fullMatch: match[0] + }; + } + + return null; +} + +/** + * Removes block reference from text temporarily + * @param text The text containing block reference + * @returns Object with cleaned text and block reference info + */ +function extractBlockReference(text: string): { + cleanedText: string; + blockRef: ReturnType; +} { + const blockRef = detectBlockReference(text); + + if (blockRef) { + const cleanedText = text.substring(0, blockRef.index).trimEnd(); + return {cleanedText, blockRef}; + } + + return {cleanedText: text, blockRef: null}; +} + /** * Interface for date operations */ @@ -745,4 +920,6 @@ export { getStatusType, applyDateOperations, isMoveOperation, + findMetadataInsertPosition, + findCompletedDateInsertPosition, }; diff --git a/src/editor-ext/cycleCompleteStatus.ts b/src/editor-ext/cycleCompleteStatus.ts index cfb5e6cc..b9d147cb 100644 --- a/src/editor-ext/cycleCompleteStatus.ts +++ b/src/editor-ext/cycleCompleteStatus.ts @@ -10,6 +10,7 @@ import { taskStatusChangeAnnotation } from "./taskStatusSwitcher"; import { getTasksAPI } from "../utils"; import { priorityChangeAnnotation } from "./priorityPicker"; import { parseTaskLine } from "../utils/taskUtil"; + /** * Creates an editor extension that cycles through task statuses when a user clicks on a task marker * @param app The Obsidian app instance @@ -68,7 +69,7 @@ function isValidTaskMarkerReplacement( // Check if user actively selected text before replacement const startSelection = tr.startState.selection.main; const hasUserSelection = startSelection && !startSelection.empty; - + // If user had a selection that covers the replacement range, this is intentional replacement if (hasUserSelection && startSelection && fromA >= startSelection.from && toA <= startSelection.to) { console.log( @@ -78,13 +79,13 @@ function isValidTaskMarkerReplacement( } // Get valid task status marks from plugin settings - const { marks } = getTaskStatusConfig(plugin); + const {marks} = getTaskStatusConfig(plugin); const validMarks = Object.values(marks); - + // Check if both the original and inserted characters are valid task status marks const isOriginalValidMark = validMarks.includes(originalText) || originalText === ' '; const isInsertedValidMark = validMarks.includes(insertedText) || insertedText === ' '; - + // If either character is not a valid task mark, this is likely manual input if (!isOriginalValidMark || !isInsertedValidMark) { return false; @@ -93,7 +94,7 @@ function isValidTaskMarkerReplacement( // Check if the replacement position is at a task marker location const taskRegex = /^[\s|\t]*([-*+]|\d+\.)\s+\[(.)]/; const match = newLineText.match(taskRegex); - + if (!match) { return false; } @@ -173,9 +174,9 @@ export function findTaskStatusChanges( change.text === " " || (change.text === "" && (tr.startState.doc.sliceString( - change.fromA, - change.toA - ) === "\t" || + change.fromA, + change.toA + ) === "\t" || tr.startState.doc.sliceString( change.fromA, change.toA @@ -330,7 +331,7 @@ export function findTaskStatusChanges( // Check if this is a replacement operation and validate if it's a valid task marker replacement if (fromA !== toA) { const originalText = tr.startState.doc.sliceString(fromA, toA); - + // Only perform validation if plugin is provided if (plugin) { const isValidReplacement = isValidTaskMarkerReplacement( @@ -343,14 +344,14 @@ export function findTaskStatusChanges( newLineText, plugin ); - + if (!isValidReplacement) { console.log( `Detected invalid task marker replacement (fromA=${fromA}, toA=${toA}). User manually input '${insertedText}' (original: '${originalText}'), skipping automatic cycling.` ); return; // Skip this change, don't add to taskChanges } - + console.log( `Detected valid task marker replacement (fromA=${fromA}, toA=${toA}). Original: '${originalText}' -> New: '${insertedText}', proceeding with automatic cycling.` ); @@ -412,13 +413,13 @@ export function findTaskStatusChanges( wasCompleteTask: wasCompleteTask, tasksInfo: triggerByTasks ? { - isTaskChange: triggerByTasks, - originalFromA: fromA, - originalToA: toA, - originalFromB: fromB, - originalToB: toB, - originalInsertedText: insertedText, - } + isTaskChange: triggerByTasks, + originalFromA: fromA, + originalToA: toA, + originalFromB: fromB, + originalToB: toB, + originalInsertedText: insertedText, + } : null, }); } @@ -526,7 +527,7 @@ export function handleCycleCompleteStatusTransaction( } // Get the task cycle and marks from plugin settings - const { cycle, marks, excludeMarksFromCycle } = getTaskStatusConfig(plugin); + const {cycle, marks, excludeMarksFromCycle} = getTaskStatusConfig(plugin); const remainingCycle = cycle.filter( (state) => !excludeMarksFromCycle.includes(state) ); @@ -660,7 +661,7 @@ export function handleCycleCompleteStatusTransaction( // Process each task status change for (const taskStatusInfo of taskStatusChanges) { - const { position, currentMark, wasCompleteTask, tasksInfo } = + const {position, currentMark, wasCompleteTask, tasksInfo} = taskStatusInfo; if (tasksInfo?.isTaskChange) { @@ -729,10 +730,8 @@ export function handleCycleCompleteStatusTransaction( // If so, we may choose to leave it as is rather than immediately cycling it if (wasCompleteTask) { // Find the corresponding status for this mark - let foundStatus = null; - for (const [status, mark] of Object.entries(marks)) { + for (const [_, mark] of Object.entries(marks)) { if (mark === currentMark) { - foundStatus = status; break; } } diff --git a/src/editor-ext/taskTimer.ts b/src/editor-ext/taskTimer.ts new file mode 100644 index 00000000..25984c98 --- /dev/null +++ b/src/editor-ext/taskTimer.ts @@ -0,0 +1,1122 @@ +import { + Decoration, + DecorationSet, + EditorView, + WidgetType, +} from "@codemirror/view"; +import { EditorState, Range, StateField, Transaction, Facet } from "@codemirror/state"; +import { MetadataCache, editorInfoField, editorEditorField, MarkdownView } from "obsidian"; +import type TaskProgressBarPlugin from "../index"; +import { TaskTimerSettings } from "../common/setting-definition"; +import { TaskTimerMetadataDetector } from "../utils/TaskTimerMetadataDetector"; +import { TaskTimerManager, TimerState } from "../utils/TaskTimerManager"; +import { TaskTimerFormatter } from "../utils/TaskTimerFormatter"; +import { taskStatusChangeAnnotation } from "./taskStatusSwitcher"; +import "../styles/task-timer.css"; + +// Extension configuration for StateField access +interface TaskTimerConfig { + settings: TaskTimerSettings; + metadataCache: MetadataCache; + plugin?: TaskProgressBarPlugin; // Add plugin reference +} + +// Define a Facet to pass configuration to the StateField +const taskTimerConfigFacet = Facet.define({ + combine: (values) => values[0] || null +}); + +/** + * Widget for displaying task timer controls above parent tasks + */ +class TaskTimerWidget extends WidgetType { + private dom: HTMLElement | null = null; + private updateInterval: number | null = null; + private timerState: TimerState | null = null; + private currentTaskStatus: string | null = null; + + constructor( + private readonly state: EditorState, + private readonly settings: TaskTimerSettings, + private readonly timerManager: TaskTimerManager, + private readonly lineFrom: number, + private readonly lineTo: number, + private readonly filePath: string, + private readonly plugin?: TaskProgressBarPlugin, + private existingBlockId?: string + ) { + super(); + // If we have a block ID, try to load existing timer state + if (this.existingBlockId) { + this.loadTimerState(); + } + } + + eq(other: TaskTimerWidget) { + // Get current task status from the line + const line = this.state.doc.lineAt(this.lineFrom); + const currentStatus = this.getTaskStatus(line.text); + + // Force widget recreation if task status has changed + if (this.currentTaskStatus && this.currentTaskStatus !== currentStatus) { + console.log("[TaskTimer] Task status changed from", this.currentTaskStatus, "to", currentStatus); + return false; + } + + // Force widget recreation if task becomes completed + if (currentStatus === 'completed') { + console.log("[TaskTimer] Task is completed, forcing widget removal"); + return false; + } + + return ( + this.lineFrom === other.lineFrom && + this.lineTo === other.lineTo && + this.filePath === other.filePath && + this.existingBlockId === other.existingBlockId + ); + } + + toDOM(): HTMLElement { + if (this.dom) { + this.refreshUI(); + return this.dom; + } + + // Create a simple text-based widget + this.dom = createDiv({cls: 'task-timer-widget'}); + + // Get and store current task status + const line = this.state.doc.lineAt(this.lineFrom); + this.currentTaskStatus = this.getTaskStatus(line.text); + + // Add debug info + console.log("[TaskTimer] Creating widget for line", this.lineFrom, "status:", this.currentTaskStatus, "blockId:", this.existingBlockId); + + // Load timer state if we have a block ID + if (this.existingBlockId) { + this.loadTimerState(); + } else { + this.updateTimerState(); + } + + this.createContent(); + return this.dom; + } + + /** + * Get task status from line text + */ + private getTaskStatus(lineText: string): 'pending' | 'in-progress' | 'completed' | 'cancelled' { + // Extract the task marker + const match = lineText.match(/\[([^\]]+)\]/); + if (!match) return 'pending'; + + const marker = match[1]; + const statuses = this.plugin?.settings?.taskStatuses || { + completed: "x|X", + inProgress: ">|/", + abandoned: "-", + planned: "?", + notStarted: " " + }; + + // Check against configured markers + if (statuses.completed.split('|').includes(marker)) { + return 'completed'; + } else if (statuses.inProgress.split('|').includes(marker)) { + return 'in-progress'; + } else if (statuses.abandoned.split('|').includes(marker)) { + return 'cancelled'; + } else if (statuses.notStarted.split('|').includes(marker) || marker === ' ') { + return 'pending'; + } else { + // Default to pending for unknown markers + return 'pending'; + } + } + + /** + * Create content based on timer state + */ + private createContent(): void { + if (!this.dom) return; + + this.dom.empty(); + + // Always get fresh task status from current document + const line = this.state.doc.lineAt(this.lineFrom); + const taskStatus = this.getTaskStatus(line.text); + this.currentTaskStatus = taskStatus; // Update stored status + console.log("[TaskTimer] createContent - current task status:", taskStatus); + + // Don't show timer for completed tasks + if (taskStatus === 'completed') { + return; + } + + // If we have a block ID and a timer state, show the timer regardless of task marker + if (this.existingBlockId && this.timerState && this.timerState.status !== 'idle') { + console.log("[TaskTimer] Found active timer for task with block ID"); + // Show timer based on existing state + // Get total duration from timer manager + const taskId = this.getTaskId(); + const elapsedMs = taskId ? this.timerManager.getCurrentDuration(taskId) : 0; + + const formattedTime = TaskTimerFormatter.formatDuration(elapsedMs, this.settings.timeFormat); + const timeSpan = this.dom.createSpan(); + + // Show paused state clearly + if (this.timerState.status === 'paused') { + timeSpan.setText(`⏸ ${formattedTime} (Paused) `); + } else { + timeSpan.setText(`⏱ ${formattedTime} `); + } + + // Add action links based on timer state + if (this.timerState.status === 'running') { + this.addActionLink('Pause', () => this.pauseTimer()); + this.dom.appendText(' | '); + this.addActionLink('Complete', () => this.completeTimer()); + // Start real-time updates + this.startRealtimeUpdates(); + } else if (this.timerState.status === 'paused') { + this.addActionLink('Resume', () => this.resumeTimer()); + this.dom.appendText(' | '); + this.addActionLink('Complete', () => this.completeTimer()); + } + this.dom.appendText(' | '); + this.addActionLink('Reset', () => this.resetTimer()); + return; + } + + // For in-progress tasks with existing block IDs + if (taskStatus === 'in-progress' && this.existingBlockId) { + // If there's an existing timer state, use it + if (this.timerState && this.timerState.status !== 'idle') { + // Show existing timer state + // Get total duration from timer manager + const taskId = this.getTaskId(); + const elapsedMs = taskId ? this.timerManager.getCurrentDuration(taskId) : 0; + + const formattedTime = TaskTimerFormatter.formatDuration(elapsedMs, this.settings.timeFormat); + const timeSpan = this.dom.createSpan(); + + // Show paused state clearly + if (this.timerState.status === 'paused') { + timeSpan.setText(`⏸ ${formattedTime} (Paused) `); + } else { + timeSpan.setText(`⏱ ${formattedTime} `); + } + + // Add action links based on timer state + if (this.timerState.status === 'running') { + this.addActionLink('Pause', () => this.pauseTimer()); + this.dom.appendText(' | '); + this.addActionLink('Complete', () => this.completeTimer()); + } else if (this.timerState.status === 'paused') { + this.addActionLink('Resume', () => this.resumeTimer()); + this.dom.appendText(' | '); + this.addActionLink('Complete', () => this.completeTimer()); + } + this.dom.appendText(' | '); + this.addActionLink('Reset', () => this.resetTimer()); + } else { + // Task is in-progress with block ID but no timer state - auto-start timer + console.log("[TaskTimer] In-progress task with block ID but no timer state, auto-starting"); + this.startTimer(); + // Timer will be shown after state update + return; + } + } else if (!this.timerState || this.timerState.status === 'idle') { + // Create text-style start button + const startSpan = this.dom.createSpan({cls: 'task-timer-start'}); + startSpan.setText('Start Task'); + startSpan.addEventListener('click', (e) => { + e.preventDefault(); + e.stopPropagation(); + console.log("[TaskTimer] Start button clicked"); + this.startTimer(); + }); + } else { + // Show elapsed time + // Get total duration from timer manager + const taskId = this.getTaskId(); + const elapsedMs = taskId ? this.timerManager.getCurrentDuration(taskId) : 0; + + const formattedTime = TaskTimerFormatter.formatDuration(elapsedMs, this.settings.timeFormat); + const timeSpan = this.dom.createSpan(); + + // Show paused state clearly + if (this.timerState.status === 'paused') { + timeSpan.setText(`⏸ ${formattedTime} (Paused) `); + } else { + timeSpan.setText(`⏱ ${formattedTime} `); + } + + // Add action links + if (this.timerState.status === 'running') { + this.addActionLink('Pause', () => this.pauseTimer()); + this.dom.appendText(' | '); + this.addActionLink('Complete', () => this.completeTimer()); + } else if (this.timerState.status === 'paused') { + this.addActionLink('Resume', () => this.resumeTimer()); + this.dom.appendText(' | '); + this.addActionLink('Complete', () => this.completeTimer()); + } + this.dom.appendText(' | '); + this.addActionLink('Reset', () => this.resetTimer()); + } + } + + /** + * Add clickable action link + */ + private addActionLink(text: string, action: () => void): void { + if (!this.dom) return; + + const link = this.dom.createSpan({cls: 'task-timer-action'}); + link.setText(text); + link.addEventListener('click', (e) => { + e.preventDefault(); + e.stopPropagation(); + action(); + }); + } + + /** + * Get the CodeMirror EditorView from various sources + */ + private getEditorView(): EditorView | null { + // Try to get from state field first + const view = this.state.field(editorEditorField, false); + if (view) { + console.log("[TaskTimer] Got EditorView from editorEditorField"); + return view; + } + + // Try from the plugin's app workspace + if (this.plugin?.app) { + const activeView = this.plugin.app.workspace.getActiveViewOfType(MarkdownView); + if (activeView?.editor) { + const editor = activeView.editor; + // For CM6 editor + if ((editor as any).cm) { + console.log("[TaskTimer] Got EditorView from activeView.editor.cm"); + return (editor as any).cm; + } + // Some versions might have it as cm6 + if ((editor as any).cm6) { + console.log("[TaskTimer] Got EditorView from activeView.editor.cm6"); + return (editor as any).cm6; + } + } + } + + // Try from the app's active editor + const app = (window as any).app; + const activeLeaf = app?.workspace?.activeLeaf; + + if (activeLeaf?.view?.editor) { + const editor = activeLeaf.view.editor; + // Check for CodeMirror 6 + if (editor.cm) { + console.log("[TaskTimer] Got EditorView from editor.cm"); + return editor.cm; + } + if (editor.cm6) { + console.log("[TaskTimer] Got EditorView from editor.cm6"); + return editor.cm6; + } + } + + // Try to find the view through the widget's DOM element + if (this.dom && this.dom.parentElement) { + const cmContent = this.dom.closest('.cm-content'); + if (cmContent) { + const cmEditor = cmContent.closest('.cm-editor'); + if (cmEditor && (cmEditor as any).cmView) { + console.log("[TaskTimer] Got EditorView from DOM element"); + return (cmEditor as any).cmView.view; + } + } + } + + console.error("[TaskTimer] Could not find EditorView through any method"); + return null; + } + + /** + * Update task status marker in the document + */ + private updateTaskStatus(newStatus: string): boolean { + const line = this.state.doc.lineAt(this.lineFrom); + const lineText = line.text; + + // Check if this line contains a task + const taskRegex = /^[\s|\t]*([-*+]|\d+\.)\s+\[[^]]*\]/; + const match = lineText.match(taskRegex); + + if (!match) { + // Not a task line + return false; + } + + // Replace task status marker - match any character(s) inside brackets + const updatedText = lineText.replace(/\[([^\]]*)\]/, newStatus); + + console.log(`[TaskTimer] updateTaskStatus - original: "${lineText}"`); + console.log(`[TaskTimer] updateTaskStatus - newStatus: "${newStatus}"`); + console.log(`[TaskTimer] updateTaskStatus - updated: "${updatedText}"`); + + if (updatedText === lineText) { + // No change needed + console.log("[TaskTimer] updateTaskStatus - no change needed"); + return false; + } + + // Get the editor view to use CodeMirror's dispatch + const view = this.getEditorView(); + if (!view) { + // Fallback to Obsidian API if no CodeMirror view + const activeView = this.plugin?.app?.workspace?.getActiveViewOfType(MarkdownView); + if (!activeView?.editor || !activeView.file) { + return false; + } + + // Check if we're updating the correct file + if (activeView.file.path !== this.filePath) { + return false; + } + + // Update the line using Obsidian's editor API + const lineNum = line.number - 1; // Convert to 0-based + activeView.editor.setLine(lineNum, updatedText); + return true; + } + + // Use CodeMirror dispatch with annotation to prevent cycleCompleteStatus interference + view.dispatch({ + changes: { + from: line.from, + to: line.to, + insert: updatedText + }, + annotations: taskStatusChangeAnnotation.of("taskTimer") + }); + + return true; + } + + /** + * Start timer + */ + private startTimer(): void { + try { + let taskId = this.getTaskId(); + + // If no existing block ID, generate one and insert it + if (!taskId) { + const blockId = this.timerManager.generateBlockId(this.settings.blockRefPrefix); + + // Get EditorView using our helper method + const view = this.getEditorView(); + console.log("[TaskTimer] EditorView:", view); + + if (view) { + const line = this.state.doc.lineAt(this.lineFrom); + const lineText = line.text; + const blockRef = ` ^${blockId}`; + + // Also update task status to in-progress + const inProgressMarkers = (this.plugin?.settings?.taskStatuses?.inProgress || ">|/").split('|'); + const inProgressMarker = inProgressMarkers[0] || '/'; + const updatedText = lineText + .replace(/\[([^\]]+)\]/, `[${inProgressMarker}]`) + .trimEnd() + blockRef; + + console.log(`[TaskTimer] Updating line ${line.number}`); + console.log("[TaskTimer] Original text:", lineText); + console.log("[TaskTimer] Updated text:", updatedText); + + try { + // Replace the entire line with updated status and block reference + view.dispatch({ + changes: { + from: line.from, + to: line.to, + insert: updatedText + }, + annotations: taskStatusChangeAnnotation.of("taskTimer") + }); + + // Update our local reference + this.existingBlockId = blockId; + taskId = `${this.filePath}#^${blockId}`; + + // Start the timer after inserting block ID + console.log(`[TaskTimer] Starting timer for newly created task: ${taskId}`); + this.timerManager.startTimer(this.filePath, blockId); + this.startRealtimeUpdates(); + this.updateTimerState(); + + // Decorations will refresh automatically after text change + } catch (err) { + console.error("[TaskTimer] Error dispatching change:", err); + return; + } + } else { + console.error("[TaskTimer] No EditorView available"); + // Fallback: try to get editor from editorInfoField + const editorInfo = this.state.field(editorInfoField); + console.log("[TaskTimer] Trying editorInfo:", editorInfo); + + if (editorInfo?.editor) { + const line = this.state.doc.lineAt(this.lineFrom); + const lineText = line.text; + + // Also update task status to in-progress + const inProgressMarkers = (this.plugin?.settings?.taskStatuses?.inProgress || ">|/").split('|'); + const inProgressMarker = inProgressMarkers[0] || '/'; + const updatedText = lineText + .replace(/\[([^\]]+)\]/, `[${inProgressMarker}]`) + .trimEnd() + ` ^${blockId}`; + + try { + editorInfo.editor.replaceRange(updatedText, + {line: line.number - 1, ch: 0}, + {line: line.number - 1, ch: lineText.length} + ); + + this.existingBlockId = blockId; + taskId = `${this.filePath}#^${blockId}`; + + // Start timer for the fallback path as well + console.log(`[TaskTimer] Starting timer for newly created task (fallback): ${taskId}`); + this.timerManager.startTimer(this.filePath, blockId); + this.startRealtimeUpdates(); + this.updateTimerState(); + this.refreshUI(); + } catch (err) { + console.error("[TaskTimer] Fallback also failed:", err); + return; + } + } + return; + } + } + + // If we already have a task ID, just update the status if needed + if (taskId && this.existingBlockId) { + // Check current task status + const line = this.state.doc.lineAt(this.lineFrom); + const currentStatus = this.getTaskStatus(line.text); + + // Keep status update for start timer - it makes sense to mark as in-progress + // This is different from pause/resume where status doesn't necessarily reflect timer state + if (currentStatus !== 'in-progress') { + const inProgressMarkers = (this.plugin?.settings?.taskStatuses?.inProgress || ">|/").split('|'); + const inProgressMarker = inProgressMarkers[0] || '/'; + this.updateTaskStatus(`[${inProgressMarker}]`); + } + + // Start or resume the timer + console.log(`[TaskTimer] Starting/resuming timer for task: ${taskId}`); + this.timerManager.startTimer(this.filePath, this.existingBlockId); + this.updateTimerState(); + this.refreshUI(); // This will start real-time updates if needed + console.log("[TaskTimer] Timer started successfully"); + } + } catch (error) { + console.error("[TaskTimer] Error starting timer:", error); + this.updateTimerState(); + } + } + + /** + * Pause timer + */ + private pauseTimer(): void { + try { + // First check if the task is completed - should not pause completed tasks + const line = this.state.doc.lineAt(this.lineFrom); + const currentStatus = this.getTaskStatus(line.text); + if (currentStatus === 'completed') { + console.warn("[TaskTimer] Cannot pause a completed task"); + return; + } + + const taskId = this.getTaskId(); + if (!taskId) { + console.warn("[TaskTimer] Cannot pause timer - no task ID found"); + return; + } + + console.log(`[TaskTimer] Pausing timer for task: ${taskId}`); + this.timerManager.pauseTimer(taskId); + + // DON'T update task status - just pause the timer + // The timer state is stored in localStorage and will persist + // This avoids conflicts with autoDateManager and other plugins + console.log("[TaskTimer] Timer paused without changing task status"); + + // Stop updates immediately + this.stopRealtimeUpdates(); + this.updateTimerState(); + this.refreshUI(); // Refresh UI to show paused state + console.log("[TaskTimer] Timer paused successfully"); + } catch (error) { + console.error("[TaskTimer] Error pausing timer:", error); + this.updateTimerState(); + } + } + + /** + * Resume timer + */ + private resumeTimer(): void { + try { + // First check if the task is completed - should not resume completed tasks + const line = this.state.doc.lineAt(this.lineFrom); + const currentStatus = this.getTaskStatus(line.text); + if (currentStatus === 'completed') { + console.warn("[TaskTimer] Cannot resume a completed task"); + return; + } + + const taskId = this.getTaskId(); + if (!taskId) { + console.warn("[TaskTimer] Cannot resume timer - no task ID found"); + return; + } + + console.log(`[TaskTimer] Resuming timer for task: ${taskId}`); + this.timerManager.resumeTimer(taskId); + + // DON'T update task status - just resume the timer + // The user can manually change status if needed + console.log("[TaskTimer] Timer resumed without changing task status"); + + this.startRealtimeUpdates(); + this.updateTimerState(); + this.refreshUI(); // Refresh UI to show running state immediately + console.log("[TaskTimer] Timer resumed successfully"); + } catch (error) { + console.error("[TaskTimer] Error resuming timer:", error); + this.stopRealtimeUpdates(); + this.updateTimerState(); + } + } + + /** + * Reset timer + */ + private resetTimer(): void { + try { + const taskId = this.getTaskId(); + if (!taskId) { + console.warn("[TaskTimer] Cannot reset timer - no task ID found"); + return; + } + + console.log(`[TaskTimer] Resetting timer for task: ${taskId}`); + this.timerManager.resetTimer(taskId); + + // DON'T update task status - just reset the timer + // Let user manually manage task status + console.log("[TaskTimer] Timer reset without changing task status"); + + this.stopRealtimeUpdates(); + this.updateTimerState(); + this.refreshUI(); // Refresh UI to show reset state + console.log("[TaskTimer] Timer reset successfully"); + } catch (error) { + console.error("[TaskTimer] Error resetting timer:", error); + this.updateTimerState(); + } + } + + /** + * Complete timer and update task + */ + private completeTimer(): void { + try { + // First check if the task is already completed + const line = this.state.doc.lineAt(this.lineFrom); + const currentStatus = this.getTaskStatus(line.text); + if (currentStatus === 'completed') { + console.warn("[TaskTimer] Task is already completed"); + return; + } + + const taskId = this.getTaskId(); + if (!taskId) { + console.warn("[TaskTimer] Cannot complete timer - no task ID found"); + return; + } + + console.log(`[TaskTimer] Completing timer for task: ${taskId}`); + + // Get the timer state before completing + const timerState = this.timerManager.getTimerState(taskId); + if (!timerState) { + console.warn("[TaskTimer] No timer state found for task:", taskId); + return; + } + + // Complete the timer and get the formatted duration + const formattedDuration = this.timerManager.completeTimer(taskId); + + // Get EditorView to modify document + const view = this.getEditorView(); + + if (view) { + const line = this.state.doc.lineAt(this.lineFrom); + const lineText = line.text; + + // Create the updated text using configured completed marker + const completedMarkers = (this.plugin?.settings?.taskStatuses?.completed || "x|X").split('|'); + const completedMarker = completedMarkers[0] || 'x'; + + // First update the task status + let updatedText = lineText.replace(/\[([^\]]+)\]/, `[${completedMarker}]`); + + // Check for block reference ID at the end + const blockRefMatch = updatedText.match(/\s*\^[\w-]+\s*$/); + if (blockRefMatch) { + // Insert duration before the block reference ID + const insertPosition = updatedText.length - blockRefMatch[0].length; + updatedText = updatedText.slice(0, insertPosition) + ` (${formattedDuration})` + updatedText.slice(insertPosition); + } else { + // No block reference, add duration at the end + updatedText = updatedText.replace(/\s*$/, ` (${formattedDuration})`); + } + + console.log("[TaskTimer] Completing task - original:", lineText); + console.log("[TaskTimer] Completing task - updated:", updatedText); + + try { + // Use dispatch to replace the entire line + view.dispatch({ + changes: { + from: line.from, + to: line.to, + insert: updatedText + } + }); + } catch (err) { + console.error("[TaskTimer] Error updating task:", err); + // Try fallback + const editorInfo = this.state.field(editorInfoField); + if (editorInfo?.editor) { + editorInfo.editor.replaceRange(updatedText, + {line: line.number - 1, ch: 0}, + {line: line.number - 1, ch: lineText.length} + ); + } + } + } else { + console.error("[TaskTimer] No view available to complete task"); + return; + } + + this.stopRealtimeUpdates(); + this.updateTimerState(); + console.log(`[TaskTimer] Timer completed successfully: ${formattedDuration}`); + } catch (error) { + console.error("[TaskTimer] Error completing timer:", error); + this.updateTimerState(); + } + } + + /** + * Load timer state from localStorage + */ + private loadTimerState(): void { + if (!this.existingBlockId) return; + + // Use TaskTimerManager to get the timer state + const taskId = this.getTaskId(); + if (taskId) { + this.timerState = this.timerManager.getTimerState(taskId); + if (this.timerState) { + console.log("[TaskTimer] Loaded timer state for", this.filePath, this.existingBlockId, ":", this.timerState); + // If timer is running, start real-time updates immediately + if (this.timerState.status === 'running') { + this.startRealtimeUpdates(); + } + } + } + } + + /** + * Update timer state from localStorage + */ + private updateTimerState(): void { + const taskId = this.getTaskId(); + if (taskId) { + this.timerState = this.timerManager.getTimerState(taskId); + } + } + + /** + * Get task ID for this widget + */ + private getTaskId(): string | null { + if (this.existingBlockId) { + // Use the same format as TaskTimerManager.getStorageKey + return `taskTimer_${this.filePath}#${this.existingBlockId}`; + } + return null; + } + + /** + * Start real-time updates for running timer + */ + private startRealtimeUpdates(): void { + if (this.updateInterval) { + clearInterval(this.updateInterval); + } + + this.updateInterval = window.setInterval(() => { + this.createContent(); // Update the entire content + }, 1000); // Update every second + } + + /** + * Stop real-time updates + */ + private stopRealtimeUpdates(): void { + if (this.updateInterval) { + clearInterval(this.updateInterval); + this.updateInterval = null; + } + } + + /** + * Refresh the entire UI (used when state changes significantly) + */ + private refreshUI(): void { + if (!this.dom) return; + + // Reload timer state if we have a block ID + if (this.existingBlockId) { + this.loadTimerState(); + } else { + this.updateTimerState(); + } + this.createContent(); + } + + destroy() { + this.stopRealtimeUpdates(); + if (this.dom) { + this.dom.remove(); + this.dom = null; + } + } +} + +/** + * StateField for managing task timer decorations + * This handles block-level decorations properly in CodeMirror + */ +const taskTimerStateField = StateField.define({ + create(state: EditorState): DecorationSet { + return createTaskTimerDecorations(state); + }, + update(decorations: DecorationSet, transaction: Transaction): DecorationSet { + // Check if this is an undo/redo operation + const isUndoRedo = transaction.isUserEvent("undo") || transaction.isUserEvent("redo"); + + // Recreate decorations on doc changes, state effects, or undo/redo + if (transaction.docChanged || transaction.effects.length > 0 || isUndoRedo) { + // Monitor all task status changes, not just undo/redo + if (transaction.docChanged) { + handleTaskStatusChange(transaction); + } + return createTaskTimerDecorations(transaction.state); + } + return decorations; + }, + provide: (field: StateField) => EditorView.decorations.from(field) +}); + +/** + * Create task timer decorations for the current state + */ +function createTaskTimerDecorations(state: EditorState): DecorationSet { + // Get configuration from facet + const timerConfig = state.facet(taskTimerConfigFacet); + console.log("[TaskTimer] Creating decorations, timerConfig:", timerConfig); + + if (!timerConfig?.settings?.enabled) { + console.log("[TaskTimer] Timer not enabled or no config"); + return Decoration.none; + } + + // Get editor info to access app and file information + const editorInfo = state.field(editorInfoField); + if (!editorInfo?.app) { + console.log("[TaskTimer] No editor info or app"); + return Decoration.none; + } + + const file = editorInfo.app.workspace.getActiveFile(); + if (!file) { + console.log("[TaskTimer] No active file"); + return Decoration.none; + } + + console.log("[TaskTimer] Processing file:", file.path); + + const metadataDetector = new TaskTimerMetadataDetector( + timerConfig.settings, + timerConfig.metadataCache + ); + + if (!metadataDetector.isTaskTimerEnabled(file)) { + console.log("[TaskTimer] Timer not enabled for file:", file.path); + return Decoration.none; + } + + console.log("[TaskTimer] Timer enabled for file, processing..."); + + const timerManager = new TaskTimerManager(timerConfig.settings); + const decorations: Range[] = []; + const doc = state.doc; + + console.log("[TaskTimer] Document has", doc.lines, "lines"); + + // Process all lines in the document + for (let i = 1; i <= doc.lines; i++) { + const line = doc.line(i); + const lineText = line.text; + + // Check if this line contains a task + if (isTaskLine(lineText)) { + console.log("[TaskTimer] Found task line:", lineText.trim()); + + // Check if this task has any subtasks (not just immediate next line) + const currentIndent = lineText.match(/^(\s*)/)?.[1].length || 0; + let hasSubtasks = false; + + // Look ahead for subtasks with greater indentation + for (let j = i + 1; j <= doc.lines; j++) { + const checkLine = doc.line(j); + const checkLineText = checkLine.text; + const checkIndent = checkLineText.match(/^(\s*)/)?.[1].length || 0; + + // If we hit a line with same or less indentation, stop checking + if (checkLineText.trim() && checkIndent <= currentIndent) { + break; + } + + // If we find a task line with greater indentation, this is a parent + if (checkIndent > currentIndent && isTaskLine(checkLineText)) { + hasSubtasks = true; + break; + } + } + + if (hasSubtasks) { + // Check task status - only skip completed tasks without existing timers + const taskStatusMatch = lineText.match(/^\s*[-*+]\s+\[([^\]]+)\]/); + if (taskStatusMatch) { + const statusChar = taskStatusMatch[1]; + const taskStatuses = timerConfig?.plugin?.settings?.taskStatuses || { + completed: "x|X", + abandoned: "-" + }; + + // Skip completed tasks only + const completedMarkers = taskStatuses.completed.split('|'); + + if (completedMarkers.includes(statusChar)) { + console.log("[TaskTimer] Skipping completed task at line", i); + continue; + } + + // For abandoned tasks, check if they have an existing block ID with timer data + const abandonedMarkers = taskStatuses.abandoned.split('|'); + if (abandonedMarkers.includes(statusChar)) { + const blockId = extractBlockRef(lineText); + if (!blockId) { + console.log("[TaskTimer] Skipping abandoned task without block ID at line", i); + continue; + } + // If abandoned task has a block ID, let it continue to show timer + console.log("[TaskTimer] Abandoned task with block ID found, checking for timer state"); + } + } + + console.log("[TaskTimer] Found parent task with subtasks at line", i); + // Extract existing block reference if present + const existingBlockId = extractBlockRef(lineText); + + // Create block-level timer widget decoration + const timerDeco = Decoration.widget({ + widget: new TaskTimerWidget( + state, + timerConfig.settings, + timerManager, + line.from, + line.to, + file.path, + timerConfig.plugin, + existingBlockId + ), + side: -1, // Place before the line + block: true // This is now allowed in StateField + }); + + // Add decoration at the start of the line (this will appear above the task) + decorations.push(timerDeco.range(line.from)); + console.log("[TaskTimer] Added timer decoration for line:", i); + } + } + } + + console.log("[TaskTimer] Created", decorations.length, "timer decorations"); + return Decoration.set(decorations, true); +} + +/** + * Helper functions + */ +function isTaskLine(lineText: string): boolean { + // Match any character inside square brackets + return /^\s*[-*+]\s+\[[^\]]*\]/.test(lineText); +} + + +function extractBlockRef(lineText: string): string | undefined { + // Match block reference anywhere in the line, not just at the end + const match = lineText.match(/\^([a-zA-Z0-9\-_]+)/); + return match ? match[1] : undefined; +} + +/** + * Handle timer state updates when task status changes + * This monitors all status changes and automatically manages timers accordingly + */ +function handleTaskStatusChange(transaction: Transaction): void { + // Get configuration from transaction state + const timerConfig = transaction.state.facet(taskTimerConfigFacet); + if (!timerConfig?.settings?.enabled || !timerConfig.plugin) { + return; + } + + const editorInfo = transaction.state.field(editorInfoField); + if (!editorInfo?.app) { + return; + } + + const file = editorInfo.app.workspace.getActiveFile(); + if (!file) { + return; + } + + const timerManager = new TaskTimerManager(timerConfig.settings); + const doc = transaction.state.doc; + + // Check each changed line for task status changes + transaction.changes.iterChangedRanges((fromA: number, toA: number, fromB: number, toB: number) => { + const startLine = doc.lineAt(fromB).number; + const endLine = doc.lineAt(toB).number; + + for (let lineNum = startLine; lineNum <= endLine; lineNum++) { + if (lineNum > doc.lines) continue; + + const line = doc.line(lineNum); + const lineText = line.text; + + // Check if this is a task line + if (!isTaskLine(lineText)) continue; + + // Extract block reference + const blockId = extractBlockRef(lineText); + if (!blockId) continue; + + // Check task status + const statusMatch = lineText.match(/^\s*[-*+]\s+\[([^\]]+)\]/); + if (!statusMatch) continue; + + const statusChar = statusMatch[1]; + const taskStatuses = timerConfig?.plugin?.settings?.taskStatuses || { + completed: "x|X", + inProgress: ">|/", + abandoned: "-", + notStarted: " " + }; + + // Determine what to do based on the new status + const inProgressMarkers = taskStatuses.inProgress.split('|'); + const abandonedMarkers = taskStatuses.abandoned.split('|'); + const completedMarkers = taskStatuses.completed.split('|'); + const notStartedMarkers = taskStatuses.notStarted.split('|'); + + const taskId = `taskTimer_${file.path}#${blockId}`; + const existingTimer = timerManager.getTimerState(taskId); + + console.log(`[TaskTimer] Status change detected: "${statusChar}" for task ${taskId}`); + console.log(`[TaskTimer] Existing timer:`, existingTimer); + + if (inProgressMarkers.includes(statusChar)) { + // Task is now in progress + if (!existingTimer || existingTimer.status === 'idle') { + console.log("[TaskTimer] Status -> In Progress: Starting new timer"); + timerManager.startTimer(file.path, blockId); + } else if (existingTimer.status === 'paused') { + console.log("[TaskTimer] Status -> In Progress: Resuming paused timer"); + timerManager.resumeTimer(taskId); + } else if (existingTimer.status === 'running') { + console.log("[TaskTimer] Status -> In Progress: Timer already running"); + } + } else if (abandonedMarkers.includes(statusChar)) { + // Task is now abandoned - pause timer if running + if (existingTimer && existingTimer.status === 'running') { + console.log("[TaskTimer] Status -> Abandoned: Pausing running timer"); + timerManager.pauseTimer(taskId); + } else if (existingTimer && existingTimer.status === 'paused') { + console.log("[TaskTimer] Status -> Abandoned: Timer already paused"); + } + } else if (completedMarkers.includes(statusChar)) { + // Task is completed - stop and save timer + if (existingTimer && (existingTimer.status === 'running' || existingTimer.status === 'paused')) { + console.log("[TaskTimer] Status -> Completed: Stopping timer and saving time"); + // Stop timer but preserve the elapsed time + timerManager.pauseTimer(taskId); + } + } else if (notStartedMarkers.includes(statusChar)) { + // Task is reset to not started - reset timer + if (existingTimer) { + console.log("[TaskTimer] Status -> Not Started: Resetting timer"); + timerManager.resetTimer(taskId); + } + } + } + }); +} + + +/** + * Main task timer extension function + * Creates a StateField-based extension for proper block decorations + */ +export function taskTimerExtension( + plugin: TaskProgressBarPlugin +) { + // Create configuration object + const config: TaskTimerConfig = { + settings: plugin.settings.taskTimer, + metadataCache: plugin.app.metadataCache, + plugin + }; + + // Return both the facet configuration and the state field + return [ + taskTimerConfigFacet.of(config), + taskTimerStateField + ]; +} diff --git a/src/index.ts b/src/index.ts index 1b0ac3c4..34ae0f31 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,6 +10,7 @@ import { requireApiVersion, } from "obsidian"; import { taskProgressBarExtension } from "./editor-ext/progressBarWidget"; +import { taskTimerExtension } from "./editor-ext/taskTimer"; import { updateProgressBarInElement } from "./components/readModeProgressbarWidget"; import { applyTaskTextMarks } from "./components/readModeTextMark"; import { @@ -102,6 +103,8 @@ import { RebuildProgressManager } from "./utils/RebuildProgressManager"; import { OnboardingConfigManager } from "./utils/OnboardingConfigManager"; import { SettingsChangeDetector } from "./utils/SettingsChangeDetector"; import { OnboardingView, ONBOARDING_VIEW_TYPE } from "./components/onboarding/OnboardingView"; +import { TaskTimerExporter } from "./utils/TaskTimerExporter"; +import { TaskTimerManager } from "./utils/TaskTimerManager"; class TaskProgressBarPopover extends HoverPopover { plugin: TaskProgressBarPlugin; @@ -188,6 +191,10 @@ export default class TaskProgressBarPlugin extends Plugin { habitManager: HabitManager; + // Task timer manager and exporter + taskTimerManager: TaskTimerManager; + taskTimerExporter: TaskTimerExporter; + // ICS manager instance icsManager: IcsManager; @@ -1018,12 +1025,184 @@ export default class TaskProgressBarPlugin extends Plugin { }, }); } + + // Task timer export/import commands + if (this.settings.taskTimer?.enabled && this.taskTimerExporter) { + this.addCommand({ + id: "export-task-timer-data", + name: "Export task timer data", + callback: async () => { + try { + const stats = this.taskTimerExporter.getExportStats(); + if (stats.activeTimers === 0) { + new Notice("No timer data to export"); + return; + } + + const jsonData = this.taskTimerExporter.exportToJSON(true); + + // Create a blob and download link + const blob = new Blob([jsonData], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `task-timer-data-${new Date().toISOString().split('T')[0]}.json`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + + new Notice(`Exported ${stats.activeTimers} timer records`); + } catch (error) { + console.error("Error exporting timer data:", error); + new Notice("Failed to export timer data"); + } + }, + }); + + this.addCommand({ + id: "import-task-timer-data", + name: "Import task timer data", + callback: async () => { + try { + // Create file input for JSON import + const input = document.createElement('input'); + input.type = 'file'; + input.accept = '.json'; + + input.onchange = async (e) => { + const file = (e.target as HTMLInputElement).files?.[0]; + if (!file) return; + + try { + const text = await file.text(); + const success = this.taskTimerExporter.importFromJSON(text); + + if (success) { + new Notice("Timer data imported successfully"); + } else { + new Notice("Failed to import timer data - invalid format"); + } + } catch (error) { + console.error("Error importing timer data:", error); + new Notice("Failed to import timer data"); + } + }; + + input.click(); + } catch (error) { + console.error("Error setting up import:", error); + new Notice("Failed to set up import"); + } + }, + }); + + this.addCommand({ + id: "export-task-timer-yaml", + name: "Export task timer data (YAML)", + callback: async () => { + try { + const stats = this.taskTimerExporter.getExportStats(); + if (stats.activeTimers === 0) { + new Notice("No timer data to export"); + return; + } + + const yamlData = this.taskTimerExporter.exportToYAML(true); + + // Create a blob and download link + const blob = new Blob([yamlData], { type: 'text/yaml' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `task-timer-data-${new Date().toISOString().split('T')[0]}.yaml`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + + new Notice(`Exported ${stats.activeTimers} timer records to YAML`); + } catch (error) { + console.error("Error exporting timer data to YAML:", error); + new Notice("Failed to export timer data to YAML"); + } + }, + }); + + this.addCommand({ + id: "backup-task-timer-data", + name: "Create task timer backup", + callback: async () => { + try { + const backupData = this.taskTimerExporter.createBackup(); + + // Create a blob and download link + const blob = new Blob([backupData], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `task-timer-backup-${new Date().toISOString().replace(/[:.]/g, '-')}.json`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + + new Notice("Task timer backup created"); + } catch (error) { + console.error("Error creating timer backup:", error); + new Notice("Failed to create timer backup"); + } + }, + }); + + this.addCommand({ + id: "show-task-timer-stats", + name: "Show task timer statistics", + callback: () => { + try { + const stats = this.taskTimerExporter.getExportStats(); + + let message = `Task Timer Statistics:\n`; + message += `Active timers: ${stats.activeTimers}\n`; + message += `Total duration: ${Math.round(stats.totalDuration / 60000)} minutes\n`; + + if (stats.oldestTimer) { + message += `Oldest timer: ${stats.oldestTimer}\n`; + } + if (stats.newestTimer) { + message += `Newest timer: ${stats.newestTimer}`; + } + + new Notice(message, 10000); + } catch (error) { + console.error("Error getting timer stats:", error); + new Notice("Failed to get timer statistics"); + } + }, + }); + } } registerEditorExt() { this.registerEditorExtension([ taskProgressBarExtension(this.app, this), ]); + + // Add task timer extension + if (this.settings.taskTimer?.enabled) { + // Initialize task timer manager and exporter + if (!this.taskTimerManager) { + this.taskTimerManager = new TaskTimerManager(this.settings.taskTimer); + } + if (!this.taskTimerExporter) { + this.taskTimerExporter = new TaskTimerExporter(this.taskTimerManager); + } + + this.registerEditorExtension([ + taskTimerExtension(this), + ]); + } + this.settings.taskGutter.enableTaskGutter && this.registerEditorExtension([taskGutterExtension(this.app, this)]); this.settings.enableTaskStatusSwitcher && diff --git a/src/setting.ts b/src/setting.ts index 8761877b..94e80018 100644 --- a/src/setting.ts +++ b/src/setting.ts @@ -117,6 +117,12 @@ export class TaskProgressBarSettingTab extends PluginSettingTab { icon: "zap", category: "workflow", }, + { + id: "task-timer", + name: "Task Timer", + icon: "timer", + category: "workflow", + }, { id: "time-parsing", name: t("Time Parsing"), @@ -428,6 +434,10 @@ export class TaskProgressBarSettingTab extends PluginSettingTab { const quickCaptureSection = this.createTabSection("quick-capture"); this.displayQuickCaptureSettings(quickCaptureSection); + // Task Timer Tab + const taskTimerSection = this.createTabSection("task-timer"); + this.displayTaskTimerSettings(taskTimerSection); + // Time Parsing Tab const timeParsingSection = this.createTabSection("time-parsing"); this.displayTimeParsingSettings(timeParsingSection); @@ -507,6 +517,10 @@ export class TaskProgressBarSettingTab extends PluginSettingTab { renderQuickCaptureSettingsTab(this, containerEl); } + private displayTaskTimerSettings(containerEl: HTMLElement): void { + this.renderTaskTimerSettingsTab(containerEl); + } + private displayTimeParsingSettings(containerEl: HTMLElement): void { renderTimeParsingSettingsTab(this, containerEl); } @@ -555,4 +569,199 @@ export class TaskProgressBarSettingTab extends PluginSettingTab { private displayBetaTestSettings(containerEl: HTMLElement): void { renderBetaTestSettingsTab(this, containerEl); } + + private renderTaskTimerSettingsTab(containerEl: HTMLElement): void { + // Create task timer settings section + const timerSection = containerEl.createDiv(); + timerSection.addClass("task-timer-settings-section"); + + // Main enable/disable setting + new Setting(timerSection) + .setName("Enable Task Timer") + .setDesc("Enable task timer functionality for tracking time spent on tasks") + .addToggle((toggle) => { + toggle + .setValue(this.plugin.settings.taskTimer?.enabled || false) + .onChange(async (value) => { + if (!this.plugin.settings.taskTimer) { + this.plugin.settings.taskTimer = { + enabled: false, + metadataDetection: { + frontmatter: "task-timer", + folders: [], + tags: [] + }, + timeFormat: "{h}hrs{m}mins", + blockRefPrefix: "timer" + }; + } + this.plugin.settings.taskTimer.enabled = value; + this.applySettingsUpdate(); + + // Re-render the section to show/hide additional options + this.display(); + }); + }); + + // Show additional settings only if timer is enabled + if (this.plugin.settings.taskTimer?.enabled) { + // Metadata detection section + const metadataSection = timerSection.createDiv(); + metadataSection.addClass("task-timer-metadata-section"); + + const metadataHeading = metadataSection.createEl("h3"); + metadataHeading.setText("Metadata Detection"); + metadataHeading.addClass("task-timer-section-heading"); + + // Frontmatter field setting + new Setting(metadataSection) + .setName("Frontmatter field") + .setDesc("Field name in frontmatter to check for enabling task timer (e.g., 'task-timer: true')") + .addText((text) => { + text + .setValue(this.plugin.settings.taskTimer?.metadataDetection?.frontmatter || "task-timer") + .onChange(async (value) => { + if (this.plugin.settings.taskTimer?.metadataDetection) { + this.plugin.settings.taskTimer.metadataDetection.frontmatter = value; + this.applySettingsUpdate(); + } + }); + }); + + // Folder paths setting + new Setting(metadataSection) + .setName("Folder paths") + .setDesc("Comma-separated list of folder paths where task timer should be enabled") + .addTextArea((textArea) => { + textArea + .setValue(this.plugin.settings.taskTimer?.metadataDetection?.folders?.join(", ") || "") + .onChange(async (value) => { + if (this.plugin.settings.taskTimer?.metadataDetection) { + this.plugin.settings.taskTimer.metadataDetection.folders = + value.split(",").map(f => f.trim()).filter(f => f); + this.applySettingsUpdate(); + } + }); + textArea.inputEl.rows = 3; + }); + + // Tags setting + new Setting(metadataSection) + .setName("Tags") + .setDesc("Comma-separated list of tags that enable task timer") + .addTextArea((textArea) => { + textArea + .setValue(this.plugin.settings.taskTimer?.metadataDetection?.tags?.join(", ") || "") + .onChange(async (value) => { + if (this.plugin.settings.taskTimer?.metadataDetection) { + this.plugin.settings.taskTimer.metadataDetection.tags = + value.split(",").map(t => t.trim()).filter(t => t); + this.applySettingsUpdate(); + } + }); + textArea.inputEl.rows = 3; + }); + + // Time format section + const formatSection = timerSection.createDiv(); + formatSection.addClass("task-timer-format-section"); + + const formatHeading = formatSection.createEl("h3"); + formatHeading.setText("Time Format"); + formatHeading.addClass("task-timer-section-heading"); + + // Time format template setting + new Setting(formatSection) + .setName("Time format template") + .setDesc("Template for displaying completed task time. Use {h} for hours, {m} for minutes, {s} for seconds") + .addText((text) => { + text + .setValue(this.plugin.settings.taskTimer?.timeFormat || "{h}hrs{m}mins") + .onChange(async (value) => { + if (this.plugin.settings.taskTimer) { + this.plugin.settings.taskTimer.timeFormat = value; + this.applySettingsUpdate(); + } + }); + }); + + // Format examples + const examplesDiv = formatSection.createDiv(); + examplesDiv.addClass("task-timer-examples"); + + const examplesTitle = examplesDiv.createDiv(); + examplesTitle.addClass("task-timer-examples-title"); + examplesTitle.setText("Format Examples:"); + + const examplesList = examplesDiv.createEl("ul"); + + const examples = [ + { format: "{h}hrs{m}mins", result: "2hrs30mins" }, + { format: "{h}h {m}m {s}s", result: "2h 30m 45s" }, + { format: "{h}:{m}:{s}", result: "2:30:45" }, + { format: "({m}mins)", result: "(150mins)" } + ]; + + examples.forEach(example => { + const listItem = examplesList.createEl("li"); + const codeEl = listItem.createEl("code"); + codeEl.setText(example.format); + listItem.appendText(" → " + example.result); + }); + + // Block reference section + const blockRefSection = timerSection.createDiv(); + blockRefSection.addClass("task-timer-blockref-section"); + + const blockRefHeading = blockRefSection.createEl("h3"); + blockRefHeading.setText("Block References"); + blockRefHeading.addClass("task-timer-section-heading"); + + // Block reference prefix setting + new Setting(blockRefSection) + .setName("Block reference prefix") + .setDesc("Prefix for generated block reference IDs (e.g., 'timer' creates ^timer-123456-7890)") + .addText((text) => { + text + .setValue(this.plugin.settings.taskTimer?.blockRefPrefix || "timer") + .onChange(async (value) => { + if (this.plugin.settings.taskTimer) { + this.plugin.settings.taskTimer.blockRefPrefix = value; + this.applySettingsUpdate(); + } + }); + }); + + // Commands section + const commandsSection = timerSection.createDiv(); + commandsSection.addClass("task-timer-commands-section"); + + const commandsHeading = commandsSection.createEl("h3"); + commandsHeading.setText("Data Management"); + commandsHeading.addClass("task-timer-section-heading"); + + const commandsDesc = commandsSection.createDiv(); + commandsDesc.addClass("task-timer-commands-desc"); + + const descParagraph = commandsDesc.createEl("p"); + descParagraph.setText("Use the command palette to access timer data management:"); + + const commandsList = commandsDesc.createEl("ul"); + + const commands = [ + { name: "Export task timer data", desc: "Export all timer data to JSON" }, + { name: "Import task timer data", desc: "Import timer data from JSON file" }, + { name: "Export task timer data (YAML)", desc: "Export to YAML format" }, + { name: "Create task timer backup", desc: "Create a backup of active timers" }, + { name: "Show task timer statistics", desc: "Display timer usage statistics" } + ]; + + commands.forEach(command => { + const listItem = commandsList.createEl("li"); + const strongEl = listItem.createEl("strong"); + strongEl.setText(command.name); + listItem.appendText(" - " + command.desc); + }); + } + } } diff --git a/src/styles/task-timer.css b/src/styles/task-timer.css new file mode 100644 index 00000000..0bd1db6b --- /dev/null +++ b/src/styles/task-timer.css @@ -0,0 +1,23 @@ +/* Task Timer Widget Styles - Simple Text-Based */ + +.task-timer-widget { + display: block; + margin: 4px 0; + padding: 2px 0; + font-size: 0.9em; + color: var(--text-muted); + line-height: 1.4; +} + +/* Start button and action links */ +.task-timer-start, +.task-timer-action { + cursor: pointer; + text-decoration: underline; + color: var(--text-accent); +} + +.task-timer-start:hover, +.task-timer-action:hover { + color: var(--text-accent-hover); +} \ No newline at end of file diff --git a/src/utils/TaskTimerExporter.ts b/src/utils/TaskTimerExporter.ts new file mode 100644 index 00000000..cb9c8aff --- /dev/null +++ b/src/utils/TaskTimerExporter.ts @@ -0,0 +1,415 @@ +import { TaskTimerManager, TimerState } from "./TaskTimerManager"; +import { TaskTimerFormatter } from "./TaskTimerFormatter"; + +/** + * Data structure for timer export/import + */ +export interface TimerExportData { + version: string; + exportDate: string; + timers: { + taskId: string; + filePath: string; + blockId: string; + startTime: number; + endTime?: number; + duration: number; + status: string; + createdAt: number; + totalPausedDuration: number; + }[]; +} + +/** + * Export and import functionality for task timer data + */ +export class TaskTimerExporter { + private timerManager: TaskTimerManager; + private readonly EXPORT_VERSION = "1.0.0"; + + constructor(timerManager: TaskTimerManager) { + this.timerManager = timerManager; + } + + /** + * Export all timer data to JSON format + * @param includeActive Whether to include currently active timers + * @returns JSON string of exported data + */ + exportToJSON(includeActive: boolean = false): string { + const exportData = this.prepareExportData(includeActive); + return JSON.stringify(exportData, null, 2); + } + + /** + * Export all timer data to YAML format + * @param includeActive Whether to include currently active timers + * @returns YAML string of exported data + */ + exportToYAML(includeActive: boolean = false): string { + const exportData = this.prepareExportData(includeActive); + return this.convertToYAML(exportData); + } + + /** + * Import timer data from JSON string + * @param jsonData JSON string containing export data + * @returns true if import was successful + */ + importFromJSON(jsonData: string): boolean { + try { + const data = JSON.parse(jsonData) as TimerExportData; + return this.processImportData(data); + } catch (error) { + console.error("Error importing JSON data:", error); + return false; + } + } + + /** + * Import timer data from YAML string + * @param yamlData YAML string containing export data + * @returns true if import was successful + */ + importFromYAML(yamlData: string): boolean { + try { + const data = this.parseYAML(yamlData) as TimerExportData; + return this.processImportData(data); + } catch (error) { + console.error("Error importing YAML data:", error); + return false; + } + } + + /** + * Export active timers to a temporary backup + * @returns Backup data as JSON string + */ + createBackup(): string { + const activeTimers = this.timerManager.getAllActiveTimers(); + const backupData = { + version: this.EXPORT_VERSION, + backupDate: new Date().toISOString(), + activeTimers: activeTimers.map(timer => ({ + ...timer, + currentDuration: this.timerManager.getCurrentDuration(timer.taskId) + })) + }; + + return JSON.stringify(backupData, null, 2); + } + + /** + * Restore timers from backup data + * @param backupData Backup data as JSON string + * @returns true if restore was successful + */ + restoreFromBackup(backupData: string): boolean { + try { + const backup = JSON.parse(backupData); + + if (!backup.activeTimers || !Array.isArray(backup.activeTimers)) { + return false; + } + + // Restore each timer + for (const timerData of backup.activeTimers) { + // Recreate timer state in localStorage + // Convert old format to new segments format + const segments = []; + if (timerData.startTime) { + segments.push({ + startTime: timerData.startTime, + endTime: timerData.pausedTime, + duration: timerData.pausedTime ? + timerData.pausedTime - timerData.startTime - (timerData.totalPausedDuration || 0) : + undefined + }); + } + + const restoredTimer: TimerState = { + taskId: timerData.taskId, + filePath: timerData.filePath, + blockId: timerData.blockId, + segments: segments, + status: timerData.status as 'idle' | 'running' | 'paused', + createdAt: timerData.createdAt, + // Keep legacy fields for reference + legacyStartTime: timerData.startTime, + legacyPausedTime: timerData.pausedTime, + legacyTotalPausedDuration: timerData.totalPausedDuration || 0 + }; + + localStorage.setItem(timerData.taskId, JSON.stringify(restoredTimer)); + } + + return true; + } catch (error) { + console.error("Error restoring from backup:", error); + return false; + } + } + + /** + * Get export statistics + * @returns Statistics about exportable data + */ + getExportStats(): { + activeTimers: number; + totalDuration: number; + oldestTimer: string | null; + newestTimer: string | null; + } { + const activeTimers = this.timerManager.getAllActiveTimers(); + + let totalDuration = 0; + let oldestTime = Number.MAX_SAFE_INTEGER; + let newestTime = 0; + let oldestTimer: string | null = null; + let newestTimer: string | null = null; + + for (const timer of activeTimers) { + const duration = this.timerManager.getCurrentDuration(timer.taskId); + totalDuration += duration; + + if (timer.createdAt < oldestTime) { + oldestTime = timer.createdAt; + oldestTimer = new Date(timer.createdAt).toLocaleString(); + } + + if (timer.createdAt > newestTime) { + newestTime = timer.createdAt; + newestTimer = new Date(timer.createdAt).toLocaleString(); + } + } + + return { + activeTimers: activeTimers.length, + totalDuration, + oldestTimer, + newestTimer + }; + } + + /** + * Prepare data for export + * @param includeActive Whether to include active timers + * @returns Export data structure + */ + private prepareExportData(includeActive: boolean): TimerExportData { + const activeTimers = this.timerManager.getAllActiveTimers(); + const exportTimers = []; + + for (const timer of activeTimers) { + // Skip active timers if not requested + if (!includeActive && (timer.status === 'running' || timer.status === 'paused')) { + continue; + } + + const currentDuration = this.timerManager.getCurrentDuration(timer.taskId); + + // Get the first and last segments for export + const firstSegment = timer.segments[0]; + const lastSegment = timer.segments[timer.segments.length - 1]; + + exportTimers.push({ + taskId: timer.taskId, + filePath: timer.filePath, + blockId: timer.blockId, + startTime: firstSegment ? firstSegment.startTime : timer.createdAt, + endTime: lastSegment && lastSegment.endTime ? lastSegment.endTime : undefined, + duration: currentDuration, + status: timer.status, + createdAt: timer.createdAt, + totalPausedDuration: timer.legacyTotalPausedDuration || 0 + }); + } + + return { + version: this.EXPORT_VERSION, + exportDate: new Date().toISOString(), + timers: exportTimers + }; + } + + /** + * Process imported data and validate structure + * @param data Imported data structure + * @returns true if processing was successful + */ + private processImportData(data: TimerExportData): boolean { + if (!this.validateImportData(data)) { + return false; + } + + let importedCount = 0; + + for (const timerData of data.timers) { + try { + // Only import completed timers to avoid conflicts + if (timerData.status === 'idle' || timerData.endTime) { + // Store as historical data (could be extended for analytics) + const historyKey = `taskTimer_history_${timerData.blockId}_${timerData.startTime}`; + localStorage.setItem(historyKey, JSON.stringify({ + ...timerData, + importedAt: Date.now() + })); + importedCount++; + } + } catch (error) { + console.warn("Failed to import timer:", timerData.taskId, error); + } + } + + console.log(`Successfully imported ${importedCount} timer records`); + return importedCount > 0; + } + + /** + * Validate imported data structure + * @param data Data to validate + * @returns true if data is valid + */ + private validateImportData(data: any): data is TimerExportData { + if (!data || typeof data !== 'object') { + return false; + } + + if (!data.version || !data.exportDate || !Array.isArray(data.timers)) { + return false; + } + + // Validate each timer entry + for (const timer of data.timers) { + if (!timer.taskId || !timer.filePath || !timer.blockId || + typeof timer.startTime !== 'number' || + typeof timer.duration !== 'number') { + return false; + } + } + + return true; + } + + /** + * Convert object to YAML format (simple implementation) + * @param obj Object to convert + * @returns YAML string + */ + private convertToYAML(obj: any, indent: number = 0): string { + const spaces = ' '.repeat(indent); + let yaml = ''; + + if (Array.isArray(obj)) { + for (const item of obj) { + yaml += `${spaces}- ${this.convertToYAML(item, indent + 1).trim()}\n`; + } + } else if (obj !== null && typeof obj === 'object') { + for (const [key, value] of Object.entries(obj)) { + if (Array.isArray(value)) { + yaml += `${spaces}${key}:\n`; + yaml += this.convertToYAML(value, indent + 1); + } else if (value !== null && typeof value === 'object') { + yaml += `${spaces}${key}:\n`; + yaml += this.convertToYAML(value, indent + 1); + } else { + yaml += `${spaces}${key}: ${this.yamlEscape(value)}\n`; + } + } + } else { + return this.yamlEscape(obj); + } + + return yaml; + } + + /** + * Parse YAML string to object (simple implementation) + * @param yamlString YAML string to parse + * @returns Parsed object + */ + private parseYAML(yamlString: string): any { + // This is a very basic YAML parser for our specific use case + // For production use, consider using a proper YAML library + const lines = yamlString.split('\n'); + const result: any = {}; + let currentObject = result; + const objectStack: any[] = [result]; + let currentKey = ''; + + for (const line of lines) { + const trimmedLine = line.trim(); + if (!trimmedLine || trimmedLine.startsWith('#')) continue; + + const indent = line.length - line.trimLeft().length; + const colonIndex = trimmedLine.indexOf(':'); + + if (colonIndex > 0) { + const key = trimmedLine.substring(0, colonIndex).trim(); + const value = trimmedLine.substring(colonIndex + 1).trim(); + + if (value === '') { + // This is a parent key + currentObject[key] = {}; + currentKey = key; + } else if (value === '[]') { + currentObject[key] = []; + } else { + // This is a key-value pair + currentObject[key] = this.parseYAMLValue(value); + } + } else if (trimmedLine.startsWith('- ')) { + // This is an array item + if (!Array.isArray(currentObject[currentKey])) { + currentObject[currentKey] = []; + } + const item = this.parseYAMLValue(trimmedLine.substring(2)); + currentObject[currentKey].push(item); + } + } + + return result; + } + + /** + * Parse individual YAML value + * @param value String value to parse + * @returns Parsed value + */ + private parseYAMLValue(value: string): any { + value = value.trim(); + + if (value === 'true') return true; + if (value === 'false') return false; + if (value === 'null') return null; + + // Try to parse as number + const numValue = Number(value); + if (!isNaN(numValue) && isFinite(numValue)) { + return numValue; + } + + // Remove quotes if present + if ((value.startsWith('"') && value.endsWith('"')) || + (value.startsWith("'") && value.endsWith("'"))) { + return value.slice(1, -1); + } + + return value; + } + + /** + * Escape value for YAML output + * @param value Value to escape + * @returns Escaped string + */ + private yamlEscape(value: any): string { + if (typeof value === 'string') { + // Quote strings that contain special characters + if (value.includes(':') || value.includes('\n') || value.includes('#')) { + return `"${value.replace(/"/g, '\\"')}"`; + } + } + return String(value); + } +} \ No newline at end of file diff --git a/src/utils/TaskTimerFormatter.ts b/src/utils/TaskTimerFormatter.ts new file mode 100644 index 00000000..955a2856 --- /dev/null +++ b/src/utils/TaskTimerFormatter.ts @@ -0,0 +1,259 @@ +/** + * Task Timer Formatter - Handles time duration formatting with template support + */ + +export interface TimeComponents { + hours: number; + minutes: number; + seconds: number; + totalMilliseconds: number; +} + +/** + * Utility class for formatting time durations using template strings + */ +export class TaskTimerFormatter { + /** + * Parse duration in milliseconds to time components + * @param duration Duration in milliseconds + * @returns Time components object + */ + static parseTimeComponents(duration: number): TimeComponents { + const totalSeconds = Math.floor(duration / 1000); + const hours = Math.floor(totalSeconds / 3600); + const minutes = Math.floor((totalSeconds % 3600) / 60); + const seconds = totalSeconds % 60; + + return { + hours, + minutes, + seconds, + totalMilliseconds: duration + }; + } + + /** + * Format duration using a template string + * @param duration Duration in milliseconds + * @param template Template string with placeholders + * @returns Formatted duration string + */ + static formatDuration(duration: number, template: string): string { + if (duration < 0) { + duration = 0; + } + + const components = this.parseTimeComponents(duration); + let result = template; + + // Replace all placeholders + result = result.replace(/\{h\}/g, components.hours.toString()); + result = result.replace(/\{m\}/g, components.minutes.toString()); + result = result.replace(/\{s\}/g, components.seconds.toString()); + result = result.replace(/\{ms\}/g, components.totalMilliseconds.toString()); + + // Handle zero cleanup - remove segments that are zero + result = this.cleanupZeroValues(result); + + // Clean up whitespace + result = result.replace(/\s+/g, ' ').trim(); + + // Return "0s" if result is empty + return result || "0s"; + } + + /** + * Format duration with smart unit selection + * @param duration Duration in milliseconds + * @returns Formatted duration string with appropriate units + */ + static formatDurationSmart(duration: number): string { + const components = this.parseTimeComponents(duration); + + if (components.hours > 0) { + if (components.minutes > 0) { + return `${components.hours}hrs${components.minutes}mins`; + } else { + return `${components.hours}hrs`; + } + } else if (components.minutes > 0) { + if (components.seconds > 30) { // Round up if seconds > 30 + return `${components.minutes + 1}mins`; + } else { + return `${components.minutes}mins`; + } + } else if (components.seconds > 0) { + return `${components.seconds}s`; + } else { + return "0s"; + } + } + + /** + * Format duration for display in different contexts + * @param duration Duration in milliseconds + * @param context Context for formatting ('compact', 'detailed', 'precise') + * @returns Formatted duration string + */ + static formatForContext(duration: number, context: 'compact' | 'detailed' | 'precise'): string { + const components = this.parseTimeComponents(duration); + + switch (context) { + case 'compact': + return this.formatDurationSmart(duration); + + case 'detailed': + const parts: string[] = []; + if (components.hours > 0) parts.push(`${components.hours}h`); + if (components.minutes > 0) parts.push(`${components.minutes}m`); + if (components.seconds > 0) parts.push(`${components.seconds}s`); + return parts.join(' ') || '0s'; + + case 'precise': + if (components.hours > 0) { + return `${components.hours}:${components.minutes.toString().padStart(2, '0')}:${components.seconds.toString().padStart(2, '0')}`; + } else { + return `${components.minutes}:${components.seconds.toString().padStart(2, '0')}`; + } + + default: + return this.formatDurationSmart(duration); + } + } + + /** + * Validate template string + * @param template Template string to validate + * @returns true if template is valid + */ + static validateTemplate(template: string): boolean { + // Check for valid placeholders + const validPlaceholders = /\{[hms]\}/g; + const invalidPlaceholders = /\{[^hms\}]*\}/g; + + // Template should have at least one valid placeholder + const hasValidPlaceholders = validPlaceholders.test(template); + + // Template should not have invalid placeholders + const hasInvalidPlaceholders = invalidPlaceholders.test(template); + + return hasValidPlaceholders && !hasInvalidPlaceholders; + } + + /** + * Get default template suggestions + * @returns Array of template suggestions with descriptions + */ + static getTemplateSuggestions(): Array<{ template: string; description: string; example: string }> { + const sampleDuration = 2 * 3600000 + 35 * 60000 + 42 * 1000; // 2h 35m 42s + + return [ + { + template: "{h}hrs{m}mins", + description: "Hours and minutes (default)", + example: this.formatDuration(sampleDuration, "{h}hrs{m}mins") + }, + { + template: "{h}h {m}m {s}s", + description: "Full time with spaces", + example: this.formatDuration(sampleDuration, "{h}h {m}m {s}s") + }, + { + template: "{h}:{m}:{s}", + description: "Clock format", + example: this.formatDuration(sampleDuration, "{h}:{m}:{s}") + }, + { + template: "{m}mins", + description: "Minutes only", + example: this.formatDuration(sampleDuration, "{m}mins") + }, + { + template: "({h}h{m}m)", + description: "Parentheses format", + example: this.formatDuration(sampleDuration, "({h}h{m}m)") + } + ]; + } + + /** + * Clean up zero values from formatted string + * @param formatted Formatted string with potential zero values + * @returns Cleaned string + */ + private static cleanupZeroValues(formatted: string): string { + // Remove zero hours, minutes, seconds if they appear at the start + formatted = formatted.replace(/^0hrs?\b/i, ''); + formatted = formatted.replace(/^0mins?\b/i, ''); + formatted = formatted.replace(/^0secs?\b/i, ''); + formatted = formatted.replace(/^0s\b/i, ''); + + // Remove zero values that appear after spaces (use word boundaries) + formatted = formatted.replace(/\s+0hrs?\b/gi, ''); + formatted = formatted.replace(/\s+0mins?\b/gi, ''); + formatted = formatted.replace(/\s+0secs?\b/gi, ''); + formatted = formatted.replace(/\s+0s\b/gi, ''); + + // Handle patterns like "0h 0m 15s" -> "15s" + formatted = formatted.replace(/\b0[hm]\b\s*/g, ''); + + return formatted; + } + + /** + * Parse human-readable time string back to milliseconds + * @param timeString Human-readable time string (e.g., "2hrs30mins") + * @returns Duration in milliseconds, or 0 if parsing fails + */ + static parseTimeString(timeString: string): number { + let totalMs = 0; + + // Match hours + const hoursMatch = timeString.match(/(\d+)hrs?/i); + if (hoursMatch) { + totalMs += parseInt(hoursMatch[1]) * 3600000; + } + + // Match minutes + const minutesMatch = timeString.match(/(\d+)mins?/i); + if (minutesMatch) { + totalMs += parseInt(minutesMatch[1]) * 60000; + } + + // Match seconds + const secondsMatch = timeString.match(/(\d+)s(?:ecs?)?/i); + if (secondsMatch) { + totalMs += parseInt(secondsMatch[1]) * 1000; + } + + return totalMs; + } + + /** + * Format duration for export/import purposes + * @param duration Duration in milliseconds + * @returns ISO 8601 duration string + */ + static formatForExport(duration: number): string { + const components = this.parseTimeComponents(duration); + return `PT${components.hours}H${components.minutes}M${components.seconds}S`; + } + + /** + * Parse ISO 8601 duration string + * @param isoDuration ISO 8601 duration string + * @returns Duration in milliseconds + */ + static parseFromExport(isoDuration: string): number { + const match = isoDuration.match(/PT(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?/); + if (!match) { + return 0; + } + + const hours = parseInt(match[1] || '0'); + const minutes = parseInt(match[2] || '0'); + const seconds = parseInt(match[3] || '0'); + + return hours * 3600000 + minutes * 60000 + seconds * 1000; + } +} \ No newline at end of file diff --git a/src/utils/TaskTimerManager.ts b/src/utils/TaskTimerManager.ts new file mode 100644 index 00000000..c53f0a8d --- /dev/null +++ b/src/utils/TaskTimerManager.ts @@ -0,0 +1,584 @@ +import { TaskTimerSettings } from "../common/setting-definition"; + +/** + * Time segment interface - represents a single work session + */ +export interface TimeSegment { + startTime: number; + endTime?: number; // undefined means still running + duration?: number; // cached duration for completed segments +} + +/** + * Timer state interface + */ +export interface TimerState { + taskId: string; + filePath: string; + blockId: string; + segments: TimeSegment[]; // Array of time segments + status: 'idle' | 'running' | 'paused'; + createdAt: number; + // Legacy fields for backward compatibility + legacyStartTime?: number; + legacyPausedTime?: number; + legacyTotalPausedDuration?: number; +} + +/** + * Legacy timer state interface for migration + */ +export interface LegacyTimerState { + taskId: string; + filePath: string; + blockId: string; + startTime: number; + pausedTime?: number; + totalPausedDuration: number; + status: 'idle' | 'running' | 'paused'; + createdAt: number; +} + +/** + * Manager for task timer state and localStorage operations + */ +export class TaskTimerManager { + private settings: TaskTimerSettings; + private readonly STORAGE_PREFIX = "taskTimer_"; + private readonly TIMER_LIST_KEY = "taskTimer_activeList"; + + constructor(settings: TaskTimerSettings) { + this.settings = settings; + } + + /** + * Generate a unique block reference ID + * @param prefix Optional prefix to use (defaults to settings) + * @returns Generated block ID + */ + public generateBlockId(prefix?: string): string { + const actualPrefix = prefix || this.settings.blockRefPrefix; + const timestamp = Date.now().toString().slice(-6); // Last 6 digits of timestamp + const random = Math.floor(Math.random() * 10000).toString().padStart(4, '0'); + return `${actualPrefix}-${timestamp}-${random}`; + } + + /** + * Generate storage key for a timer + * @param filePath File path + * @param blockId Block reference ID + * @returns Storage key + */ + private getStorageKey(filePath: string, blockId: string): string { + return `${this.STORAGE_PREFIX}${filePath}#${blockId}`; + } + + /** + * Start a timer for a task + * @param filePath Path of the file containing the task + * @param existingBlockId Optional existing block ID to resume + * @returns Generated or used block ID + */ + startTimer(filePath: string, existingBlockId?: string): string { + try { + console.log(`[TaskTimerManager] Starting timer for file: ${filePath}, blockId: ${existingBlockId || 'new'}`); + + const blockId = existingBlockId || this.generateBlockId(); + const taskId = this.getStorageKey(filePath, blockId); + const now = Date.now(); + + if (!blockId) { + console.error("[TaskTimerManager] Failed to generate or use block ID"); + throw new Error("Block ID generation failed"); + } + + // Check if timer already exists + const existingTimer = this.getTimerState(taskId); + + if (existingTimer) { + console.log(`[TaskTimerManager] Found existing timer with status: ${existingTimer.status}`); + // Resume existing timer + if (existingTimer.status === 'paused') { + this.resumeTimer(taskId); + } + return blockId; + } + + // Create new timer with initial segment + const timerState: TimerState = { + taskId, + filePath, + blockId, + segments: [{ + startTime: now + }], + status: 'running', + createdAt: now + }; + + // Save timer state + try { + localStorage.setItem(taskId, JSON.stringify(timerState)); + this.addToActiveList(taskId); + console.log(`[TaskTimerManager] Successfully created new timer: ${taskId}`); + } catch (storageError) { + console.error("[TaskTimerManager] Failed to save timer to localStorage:", storageError); + throw new Error("Failed to save timer state - localStorage may be full or unavailable"); + } + + return blockId; + } catch (error) { + console.error("[TaskTimerManager] Critical error starting timer:", error); + throw error; // Re-throw to let caller handle + } + } + + /** + * Pause a timer + * @param taskId Timer task ID + */ + pauseTimer(taskId: string): void { + const timerState = this.getTimerState(taskId); + if (!timerState || timerState.status !== 'running') { + return; + } + + const now = Date.now(); + + // Close the current segment + const currentSegment = timerState.segments[timerState.segments.length - 1]; + if (currentSegment && !currentSegment.endTime) { + currentSegment.endTime = now; + currentSegment.duration = now - currentSegment.startTime; + } + + timerState.status = 'paused'; + + localStorage.setItem(taskId, JSON.stringify(timerState)); + } + + /** + * Resume a paused timer + * @param taskId Timer task ID + */ + resumeTimer(taskId: string): void { + const timerState = this.getTimerState(taskId); + if (!timerState || timerState.status !== 'paused') { + return; + } + + const now = Date.now(); + + // Create a new segment for the resumed work + timerState.segments.push({ + startTime: now + }); + + timerState.status = 'running'; + + localStorage.setItem(taskId, JSON.stringify(timerState)); + } + + /** + * Reset a timer + * @param taskId Timer task ID + */ + resetTimer(taskId: string): void { + const timerState = this.getTimerState(taskId); + if (!timerState) { + return; + } + + const now = Date.now(); + + // Clear all segments and start fresh + timerState.segments = [{ + startTime: now + }]; + timerState.status = 'running'; + + localStorage.setItem(taskId, JSON.stringify(timerState)); + } + + /** + * Complete a timer and return formatted duration + * @param taskId Timer task ID + * @returns Formatted duration string + */ + completeTimer(taskId: string): string { + try { + console.log(`[TaskTimerManager] Completing timer: ${taskId}`); + + const timerState = this.getTimerState(taskId); + if (!timerState) { + console.warn(`[TaskTimerManager] Timer not found for completion: ${taskId}`); + return ""; + } + + const now = Date.now(); + + // Close the current segment if running + if (timerState.status === 'running') { + const currentSegment = timerState.segments[timerState.segments.length - 1]; + if (currentSegment && !currentSegment.endTime) { + currentSegment.endTime = now; + currentSegment.duration = now - currentSegment.startTime; + } + } + + // Calculate total duration from all segments + const totalDuration = this.calculateTotalDuration(timerState); + console.log(`[TaskTimerManager] Total duration from ${timerState.segments.length} segments: ${totalDuration}ms`); + + // Validate duration + if (totalDuration < 0) { + console.error(`[TaskTimerManager] Invalid duration calculated: ${totalDuration}ms`); + return this.formatDuration(0); + } + + // Remove from storage + try { + this.removeTimer(taskId); + console.log(`[TaskTimerManager] Successfully removed completed timer from storage`); + } catch (removalError) { + console.error("[TaskTimerManager] Failed to remove timer from storage:", removalError); + // Continue anyway - we can still return the duration + } + + // Format and return duration + const formattedDuration = this.formatDuration(totalDuration); + console.log(`[TaskTimerManager] Timer completed successfully, duration: ${formattedDuration}`); + return formattedDuration; + } catch (error) { + console.error("[TaskTimerManager] Critical error completing timer:", error); + // Return empty string to prevent crashes, but log the issue + return ""; + } + } + + /** + * Get current timer state + * @param taskId Timer task ID + * @returns Timer state or null if not found + */ + getTimerState(taskId: string): TimerState | null { + try { + const stored = localStorage.getItem(taskId); + if (!stored) { + return null; + } + + const parsed = JSON.parse(stored); + + // Check if this is a legacy format that needs migration + if (this.isLegacyFormat(parsed)) { + console.log(`[TaskTimerManager] Migrating legacy timer state for ${taskId}`); + const migrated = this.migrateLegacyState(parsed as LegacyTimerState); + // Save migrated state + localStorage.setItem(taskId, JSON.stringify(migrated)); + return migrated; + } + + // Validate the parsed state structure + if (!this.validateTimerState(parsed)) { + console.error(`[TaskTimerManager] Invalid timer state structure for ${taskId}:`, parsed); + // Clean up corrupted data + localStorage.removeItem(taskId); + return null; + } + + return parsed as TimerState; + } catch (error) { + console.error(`[TaskTimerManager] Error retrieving timer state for ${taskId}:`, error); + // Clean up corrupted data + try { + localStorage.removeItem(taskId); + } catch (cleanupError) { + console.error("[TaskTimerManager] Failed to clean up corrupted timer data:", cleanupError); + } + return null; + } + } + + /** + * Check if the state is in legacy format + * @param state State to check + * @returns true if legacy format + */ + private isLegacyFormat(state: any): boolean { + return ( + state && + typeof state.startTime === 'number' && + !state.segments && + typeof state.totalPausedDuration === 'number' + ); + } + + /** + * Migrate legacy timer state to new format + * @param legacy Legacy timer state + * @returns Migrated timer state + */ + private migrateLegacyState(legacy: LegacyTimerState): TimerState { + const segments: TimeSegment[] = []; + + // Create segment from legacy data + if (legacy.status === 'running') { + // Running timer - create an open segment + segments.push({ + startTime: legacy.startTime + legacy.totalPausedDuration + }); + } else if (legacy.status === 'paused' && legacy.pausedTime) { + // Paused timer - create a closed segment + segments.push({ + startTime: legacy.startTime + legacy.totalPausedDuration, + endTime: legacy.pausedTime, + duration: legacy.pausedTime - legacy.startTime - legacy.totalPausedDuration + }); + } + + return { + taskId: legacy.taskId, + filePath: legacy.filePath, + blockId: legacy.blockId, + segments, + status: legacy.status, + createdAt: legacy.createdAt, + // Keep legacy fields for reference + legacyStartTime: legacy.startTime, + legacyPausedTime: legacy.pausedTime, + legacyTotalPausedDuration: legacy.totalPausedDuration + }; + } + + /** + * Validate timer state structure + * @param state Parsed timer state to validate + * @returns true if valid, false otherwise + */ + private validateTimerState(state: any): state is TimerState { + return ( + state && + typeof state.taskId === 'string' && + typeof state.filePath === 'string' && + typeof state.blockId === 'string' && + Array.isArray(state.segments) && + typeof state.createdAt === 'number' && + ['idle', 'running', 'paused'].includes(state.status) + ); + } + + /** + * Get all active timers + * @returns Array of active timer states + */ + getAllActiveTimers(): TimerState[] { + const activeList = this.getActiveList(); + const timers: TimerState[] = []; + + for (const taskId of activeList) { + const timer = this.getTimerState(taskId); + if (timer) { + timers.push(timer); + } else { + // Clean up orphaned references + this.removeFromActiveList(taskId); + } + } + + return timers; + } + + /** + * Get timer by file path and block ID + * @param filePath File path + * @param blockId Block ID + * @returns Timer state or null + */ + getTimerByFileAndBlock(filePath: string, blockId: string): TimerState | null { + const taskId = this.getStorageKey(filePath, blockId); + return this.getTimerState(taskId); + } + + /** + * Remove a timer from storage + * @param taskId Timer task ID + */ + removeTimer(taskId: string): void { + localStorage.removeItem(taskId); + this.removeFromActiveList(taskId); + } + + /** + * Calculate total duration from all segments + * @param timerState Timer state + * @returns Total duration in milliseconds + */ + private calculateTotalDuration(timerState: TimerState): number { + const now = Date.now(); + + return timerState.segments.reduce((total, segment) => { + let segmentDuration: number; + + if (segment.duration) { + // Use cached duration if available + segmentDuration = segment.duration; + } else if (segment.endTime) { + // Calculate duration for completed segment + segmentDuration = segment.endTime - segment.startTime; + } else { + // Calculate duration for running segment + segmentDuration = now - segment.startTime; + } + + return total + segmentDuration; + }, 0); + } + + /** + * Get current running time for a timer + * @param taskId Timer task ID + * @returns Current duration in milliseconds, or 0 if not found/running + */ + getCurrentDuration(taskId: string): number { + const timerState = this.getTimerState(taskId); + if (!timerState) { + return 0; + } + + return this.calculateTotalDuration(timerState); + } + + /** + * Get the number of time segments (sessions) for a timer + * @param taskId Timer task ID + * @returns Number of segments + */ + getSegmentCount(taskId: string): number { + const timerState = this.getTimerState(taskId); + if (!timerState) { + return 0; + } + + return timerState.segments.length; + } + + /** + * Get all time segments for a timer + * @param taskId Timer task ID + * @returns Array of time segments + */ + getSegments(taskId: string): TimeSegment[] { + const timerState = this.getTimerState(taskId); + if (!timerState) { + return []; + } + + return timerState.segments; + } + + /** + * Format duration in milliseconds to readable string + * @param duration Duration in milliseconds + * @returns Formatted duration string + */ + formatDuration(duration: number): string { + const seconds = Math.floor(duration / 1000); + const minutes = Math.floor(seconds / 60); + const hours = Math.floor(minutes / 60); + + const remainingMinutes = minutes % 60; + const remainingSeconds = seconds % 60; + + // Use template format from settings + let template = this.settings.timeFormat; + + // Replace placeholders + template = template.replace("{h}", hours.toString()); + template = template.replace("{m}", remainingMinutes.toString()); + template = template.replace("{s}", remainingSeconds.toString()); + template = template.replace("{ms}", duration.toString()); + + // Clean up zero values (remove 0hrs, 0mins if they are zero) + // Use word boundaries to avoid matching 10hrs, 20mins etc. + template = template.replace(/\b0hrs\b/g, ""); + template = template.replace(/\b0mins\b/g, ""); + + // Clean up leading/trailing spaces and multiple spaces + template = template.replace(/\s+/g, " ").trim(); + + return template || "0s"; + } + + /** + * Get active timer list from localStorage + * @returns Array of active timer task IDs + */ + private getActiveList(): string[] { + const stored = localStorage.getItem(this.TIMER_LIST_KEY); + if (!stored) { + return []; + } + + try { + return JSON.parse(stored) as string[]; + } catch (error) { + console.error("Error parsing active timer list:", error); + return []; + } + } + + /** + * Add timer to active list + * @param taskId Timer task ID + */ + private addToActiveList(taskId: string): void { + const activeList = this.getActiveList(); + if (!activeList.includes(taskId)) { + activeList.push(taskId); + localStorage.setItem(this.TIMER_LIST_KEY, JSON.stringify(activeList)); + } + } + + /** + * Remove timer from active list + * @param taskId Timer task ID + */ + private removeFromActiveList(taskId: string): void { + const activeList = this.getActiveList(); + const filtered = activeList.filter(id => id !== taskId); + localStorage.setItem(this.TIMER_LIST_KEY, JSON.stringify(filtered)); + } + + /** + * Update settings for this manager instance + * @param settings New settings to use + */ + updateSettings(settings: TaskTimerSettings): void { + this.settings = settings; + } + + /** + * Clean up expired or orphaned timers + * @param maxAgeHours Maximum age in hours for keeping completed timers + */ + cleanup(maxAgeHours: number = 24): void { + const activeList = this.getActiveList(); + const now = Date.now(); + const maxAge = maxAgeHours * 60 * 60 * 1000; // Convert to milliseconds + + for (const taskId of activeList) { + const timer = this.getTimerState(taskId); + if (!timer) { + // Remove orphaned reference + this.removeFromActiveList(taskId); + continue; + } + + // Remove very old timers + if (now - timer.createdAt > maxAge) { + this.removeTimer(taskId); + } + } + } +} \ No newline at end of file diff --git a/src/utils/TaskTimerMetadataDetector.ts b/src/utils/TaskTimerMetadataDetector.ts new file mode 100644 index 00000000..99c38da4 --- /dev/null +++ b/src/utils/TaskTimerMetadataDetector.ts @@ -0,0 +1,117 @@ +import { TFile, MetadataCache } from "obsidian"; +import { TaskTimerSettings } from "../common/setting-definition"; + +/** + * Service for detecting whether task timer functionality should be enabled + * for a specific file based on metadata conditions + */ +export class TaskTimerMetadataDetector { + private settings: TaskTimerSettings; + private metadataCache: MetadataCache; + + constructor(settings: TaskTimerSettings, metadataCache: MetadataCache) { + this.settings = settings; + this.metadataCache = metadataCache; + } + + /** + * Check if task timer is enabled for the given file + * @param file The file to check + * @returns true if task timer should be enabled for this file + */ + isTaskTimerEnabled(file: TFile): boolean { + if (!this.settings.enabled) { + return false; + } + + if (!file) { + return false; + } + + // Check all enabled detection methods + return ( + this.checkFrontmatterCondition(file) || + this.checkFolderCondition(file) || + this.checkTagCondition(file) + ); + } + + /** + * Check if frontmatter condition is met + * @param file The file to check + * @returns true if frontmatter condition is satisfied + */ + checkFrontmatterCondition(file: TFile): boolean { + if (!this.settings.metadataDetection.frontmatter) { + return false; + } + + const fileCache = this.metadataCache.getFileCache(file); + if (!fileCache || !fileCache.frontmatter) { + return false; + } + + const frontmatterKey = this.settings.metadataDetection.frontmatter; + const frontmatterValue = fileCache.frontmatter[frontmatterKey]; + + // Check if the frontmatter field exists and is truthy + return Boolean(frontmatterValue); + } + + /** + * Check if folder condition is met + * @param file The file to check + * @returns true if folder condition is satisfied + */ + checkFolderCondition(file: TFile): boolean { + const folders = this.settings.metadataDetection.folders; + if (!folders || folders.length === 0) { + return false; + } + + const filePath = file.path; + + // Check if file path starts with any of the configured folders + return folders.some((folder) => { + if (!folder.trim()) { + return false; + } + // Normalize folder path (ensure it ends with /) + const normalizedFolder = folder.endsWith("/") ? folder : folder + "/"; + return filePath.startsWith(normalizedFolder) || filePath.startsWith(folder); + }); + } + + /** + * Check if tag condition is met + * @param file The file to check + * @returns true if tag condition is satisfied + */ + checkTagCondition(file: TFile): boolean { + const tags = this.settings.metadataDetection.tags; + if (!tags || tags.length === 0) { + return false; + } + + const fileCache = this.metadataCache.getFileCache(file); + if (!fileCache || !fileCache.tags) { + return false; + } + + const fileTags = fileCache.tags.map((t) => t.tag.replace("#", "")); + + // Check if any of the configured tags exist in the file + return tags.some((configTag) => { + const normalizedConfigTag = configTag.replace("#", ""); + return fileTags.includes(normalizedConfigTag); + }); + } + + /** + * Update settings for this detector instance + * @param settings New settings to use + */ + updateSettings(settings: TaskTimerSettings): void { + this.settings = settings; + } +} \ No newline at end of file diff --git a/src/utils/priorityUtils.ts b/src/utils/priorityUtils.ts new file mode 100644 index 00000000..371ca960 --- /dev/null +++ b/src/utils/priorityUtils.ts @@ -0,0 +1,49 @@ +/** + * Utility functions for handling task priorities + */ + +/** + * Sanitizes a priority value to make it safe for use in CSS class names. + * Removes spaces and special characters that are invalid in CSS tokens. + * + * @param priority - The priority value to sanitize (can be string or number) + * @returns A sanitized string safe for CSS class names, or empty string if invalid + */ +export function sanitizePriorityForClass(priority: string | number | undefined | null): string { + if (priority === undefined || priority === null) { + return ''; + } + + // Convert to string and trim + const priorityStr = String(priority).trim(); + + // If it's a numeric priority (1-5), return as-is + const numericPriority = parseInt(priorityStr, 10); + if (!isNaN(numericPriority) && numericPriority >= 1 && numericPriority <= 5) { + return String(numericPriority); + } + + // For non-numeric priorities, remove all spaces and special characters + // Only keep alphanumeric characters and hyphens + const sanitized = priorityStr + .replace(/\s+/g, '-') // Replace spaces with hyphens + .replace(/[^\w-]/g, '') // Remove non-word characters except hyphens + .replace(/--+/g, '-') // Replace multiple hyphens with single hyphen + .replace(/^-|-$/g, ''); // Remove leading/trailing hyphens + + return sanitized; +} + +/** + * Checks if a priority value is valid for use in DOM operations + * @param priority - The priority value to check + * @returns true if the priority is valid, false otherwise + */ +export function isValidPriority(priority: string | number | undefined | null): boolean { + if (priority === undefined || priority === null) { + return false; + } + + const sanitized = sanitizePriorityForClass(priority); + return sanitized.length > 0; +} \ No newline at end of file diff --git a/styles.css b/styles.css index 09872c70..1e717b8b 100644 --- a/styles.css +++ b/styles.css @@ -247,4 +247,4 @@ settings: default-dark: '#f1f1f1' */ -.cm-task-progress-bar{display:inline-block;position:relative;margin-left:5px;margin-bottom:1px}.no-progress-bar .cm-task-progress-bar{display:none!important}.HyperMD-header .cm-task-progress-bar{display:inline-block;position:relative;margin-left:5px;margin-bottom:5px}.progress-bar-inline{height:8px;position:relative}.progress-bar-inline-empty{background-color:var(--progress-background-color)}.progress-bar-inline-0{background-color:var(--progress-0-color)}.progress-bar-inline-1{background-color:var(--progress-25-color)}.progress-bar-inline-2{background-color:var(--progress-50-color)}.progress-bar-inline-3{background-color:var(--progress-75-color)}.progress-bar-inline-complete{background-color:var(--progress-100-color)}.progress-completed{background-color:var(--task-completed-color);z-index:3}.progress-in-progress{background-color:var(--task-in-progress-color);z-index:2;position:absolute;top:0;height:100%}.progress-abandoned{background-color:var(--task-abandoned-color);z-index:1;position:absolute;top:0;height:100%}.progress-planned{background-color:var(--task-planned-color);z-index:1;position:absolute;top:0;height:100%}.progress-bar-inline-background{color:#000!important;background-color:var(--progress-background-color);border-radius:10px;flex-direction:row;justify-content:flex-start;align-items:center;width:85px;position:relative;overflow:hidden}.progress-bar-inline-background.hidden{display:none}.cm-task-progress-bar .task-status-indicator{display:inline-block;margin-right:2px}.cm-task-progress-bar .completed-indicator{color:var(--task-completed-color)}.cm-task-progress-bar .in-progress-indicator{color:var(--task-in-progress-color)}.cm-task-progress-bar .abandoned-indicator{color:var(--task-abandoned-color)}.cm-task-progress-bar .planned-indicator{color:var(--task-planned-color)}.cm-task-progress-bar.with-number{display:inline-flex;align-items:center}.HyperMD-header .cm-task-progress-bar.with-number .progress-bar-inline-background,.HyperMD-header .cm-task-progress-bar.with-number .progress-status{margin-bottom:5px}.cm-task-progress-bar.with-number .progress-bar-inline-background{margin-bottom:-2px;width:42px}.cm-task-progress-bar.with-number .progress-status{font-size:13px;margin-left:3px}.theme-dark .progress-completed{background-color:var(--task-completed-color)}.theme-dark .progress-in-progress{background-color:var(--task-in-progress-color)}.theme-dark .progress-abandoned{background-color:var(--task-abandoned-color)}.theme-dark .progress-planned{background-color:var(--task-planned-color)}.task-progress-bar-popover{width:400px}.task-states-container{margin:10px 0;border:1px solid var(--background-modifier-border);border-radius:5px;padding:10px}.task-state-row{margin-bottom:8px}.task-state-row .setting-item{border:none;padding:6px;border-radius:4px}.task-state-row .setting-item-info{margin-right:10px}.task-state-row .setting-item-control{display:flex;align-items:center;justify-content:flex-end;flex-wrap:nowrap}.task-state-row .setting-item-control input[type=text]{margin-right:8px}.task-state-row .extra-setting-button{padding:4px;width:24px;height:24px;border-radius:4px;margin-left:4px;display:flex;align-items:center;justify-content:center}.task-state-row .setting-item-control button{white-space:nowrap}.task-state-container{margin-inline-start:calc(var(--checkbox-size) * -1)}.task-state-container .task-state{padding-inline-start:var(--size-2-1);padding-inline-end:var(--size-2-2);text-decoration:none!important;cursor:pointer}.task-states-container{margin:10px 0;border:1px solid var(--background-modifier-border);border-radius:5px;padding:10px}.task-state-row{margin-bottom:8px}.task-state-row .setting-item{border:none;padding:6px;border-radius:4px}.task-state-row .setting-item-info{margin-right:10px}.task-state-row .setting-item-control{display:flex;align-items:center;justify-content:flex-end;flex-wrap:nowrap}.task-state-row .setting-item-control input[type=text]{margin-right:8px}.task-state-row .extra-setting-button{padding:4px;width:24px;height:24px;border-radius:4px;margin-left:4px;display:flex;align-items:center;justify-content:center}.task-state-row .setting-item-control button{white-space:nowrap}.task-state-container{margin-inline-start:calc(var(--checkbox-size) * -1)}.task-state-container .task-state{padding-inline-start:var(--size-2-1);padding-inline-end:var(--size-2-2);text-decoration:none!important;cursor:pointer}.task-genius-settings .settings-tabs-categorized-container{margin-top:var(--size-4-4);margin-bottom:var(--size-4-4);display:flex;flex-direction:column;gap:var(--size-4-6)}.task-genius-settings .settings-category-section{display:flex;flex-direction:column;gap:var(--size-4-2)}.task-genius-settings .settings-category-header{font-size:var(--font-ui-small);font-weight:var(--font-weight-semibold);color:var(--text-muted);text-transform:uppercase;letter-spacing:.05em;padding:0 var(--size-4-2);border-bottom:1px solid var(--background-modifier-border);padding-bottom:var(--size-4-1)}.task-genius-settings .settings-category-tabs{display:grid;grid-template-columns:repeat(3,minmax(200px,1fr));gap:var(--size-4-2)}@media (max-width: 1200px){.task-genius-settings .settings-category-tabs{grid-template-columns:repeat(2,minmax(200px,1fr))}}@media (max-width: 768px){.task-genius-settings .settings-category-tabs{grid-template-columns:1fr}}.task-genius-settings .settings-tabs-container{display:grid;grid-template-columns:repeat(2,1fr);grid-auto-rows:var(--size-4-18);margin-top:var(--size-4-4);margin-bottom:var(--size-4-4);height:fit-content;gap:var(--size-4-4)}@media (max-width: 768px){.task-genius-settings .settings-tabs-container{grid-template-columns:repeat(1,1fr)}}.task-genius-settings .settings-tab{padding:var(--size-4-3) var(--size-4-4);border-radius:var(--radius-m);cursor:pointer;display:flex;align-items:center;gap:var(--size-4-2);min-height:var(--size-4-12);border:1px solid var(--background-modifier-border);background:var(--background-primary);position:relative;overflow:hidden;transition:all .2s ease}.task-genius-settings .settings-tab:after{content:"";position:absolute;top:10px;right:-80px;width:200px;height:200px;background-color:var(--background-secondary-alt);transform:rotate(-15deg);z-index:0;opacity:.7;transition:all .3s ease;border-radius:var(--radius-m)}.task-genius-settings .settings-tab:hover:after{transform:rotate(-10deg);opacity:.9}.task-genius-settings .settings-tab-active:after{background-color:var(--interactive-accent);opacity:.3}.task-genius-settings .settings-tab-icon,.task-genius-settings .settings-tab span,.task-genius-settings .settings-tab-label{position:relative;z-index:1}.task-genius-settings .settings-category-tabs .settings-tab-icon{display:flex;align-items:center;justify-content:center;width:var(--size-4-4);height:var(--size-4-4);flex-shrink:0}.task-genius-settings .settings-category-tabs .settings-tab-icon svg{width:var(--icon-s);height:var(--icon-s)}.task-genius-settings .settings-category-tabs .settings-tab-label{font-size:var(--font-ui-small);font-weight:var(--font-weight-medium);flex:1;text-align:left}.task-genius-settings .settings-category-tabs .settings-tab:hover{background:var(--background-modifier-hover);border-color:var(--background-modifier-border-hover);transform:translateY(-1px);box-shadow:var(--shadow-m)}.task-genius-settings .settings-category-tabs .settings-tab-active{background:var(--interactive-accent);color:var(--text-on-accent);border-color:var(--interactive-accent);box-shadow:var(--shadow-m);font-weight:var(--font-weight-semibold)}.task-genius-settings .settings-category-tabs .settings-tab-active:hover{background:var(--interactive-accent-hover);border-color:var(--interactive-accent-hover);transform:translateY(-1px)}.task-genius-settings .settings-tab:hover{background-color:var(--background-modifier-hover)}.task-genius-settings .settings-tab-active{background-color:var(--background-modifier-border-hover);font-weight:bold}.task-genius-settings .settings-tab-sections{overflow:hidden}.task-genius-settings .settings-tab-section{display:none}.task-genius-settings .settings-tab-section-active{display:block}.task-genius-settings .settings-tab-section-header{display:flex;align-items:center;justify-content:flex-end;margin-top:var(--size-4-2);margin-bottom:var(--size-4-2)}.task-genius-settings .settings-tab-section-header .header-button{display:flex;align-items:center;justify-content:center;gap:4px;font-size:var(--font-ui-small)}.task-genius-settings .settings-tab-section-header .header-button-icon{--icon-size: 16px;display:flex;align-items:center;justify-content:center}.task-genius-settings .settings-tab[data-tab-id=general]{display:none}.task-genius-settings .settings-tabs-categorized-container{display:flex}.task-genius-settings:has(.settings-tab-section-active:not([data-tab-id="general"])) .settings-tabs-categorized-container{display:none}.task-genius-settings .settings-tabs-container{display:none}.task-genius-settings:has(.settings-tab-active[data-tab-id="general"]) .settings-tabs-container{display:grid}.task-genius-settings-header{display:block}.task-genius-settings:has(.settings-tab-section-active:not([data-tab-id="general"])) .task-genius-settings-header{display:none}.expression-examples{margin-top:8px;border-radius:5px}.expression-example-item{margin-bottom:var(--size-4-3);padding:var(--size-4-2);padding-left:var(--size-4-3);padding-right:var(--size-4-3);border-radius:var(--radius-s);display:flex;flex-direction:column;gap:6px;border:1px solid var(--background-modifier-border)}.expression-example-name{font-weight:bold}.expression-example-code{padding:4px 8px;background-color:var(--background-secondary);border-radius:4px;font-family:var(--font-monospace);font-size:.9em;overflow-wrap:break-word;user-select:text}.expression-example-use{align-self:flex-end;margin-top:4px}.custom-format-textarea{height:200px;width:100%;font-family:var(--font-monospace);resize:vertical}.custom-format-preview-container{margin-bottom:var(--size-4-3);padding:var(--size-4-3);border-radius:var(--radius-s);background-color:var(--background-secondary);display:flex;flex-direction:column}.custom-format-preview-label{font-weight:bold;margin-bottom:var(--size-4-2);color:var(--text-muted)}.custom-format-preview-content{padding:var(--size-4-2);background-color:var(--background-primary);border-radius:var(--radius-s);font-family:var(--font-interface)}.custom-format-placeholder-info{margin-top:var(--size-4-2);margin-bottom:var(--size-4-2);user-select:text}.custom-format-preview-error,.expression-preview-error{color:var(--text-error)}.expression-example-preview{margin-top:var(--size-4-2);padding:var(--size-4-2);background-color:var(--background-primary-alt);border-radius:var(--radius-s);font-size:.9em}.preset-filters-container{margin-top:10px;padding:8px;border-radius:5px;border:1px solid var(--background-modifier-border)}.preset-filter-row{margin-bottom:5px;border-radius:4px;padding-top:var(--size-4-2);padding-left:var(--size-4-2);padding-right:var(--size-4-2);transition:background-color .2s ease}.preset-filter-row:hover{background-color:var(--background-secondary-alt)}.no-presets-message{font-style:italic;color:var(--text-muted);text-align:center;padding:15px}.preset-saved-message{color:var(--text-accent);font-weight:bold;text-align:center;padding:5px;margin-top:5px;animation:fadeIn .3s ease-in-out}.task-filter-save-preset{margin-top:15px;padding:10px;border-radius:5px;background-color:var(--background-secondary-alt)}.tg-modal-button-container{display:flex;justify-content:flex-end;gap:10px;margin-top:20px}.tg-modal-button-container button{padding:6px 12px;border-radius:4px;font-size:14px;font-weight:500;cursor:pointer}.tg-modal-button-container button.mod-warning{background-color:var(--background-modifier-error);color:#fff}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}.modal-workflow-definition{max-width:800px;width:90vw}.modal-stage-definition{max-width:800px;width:90vw}.workflow-container{border:1px solid var(--background-modifier-border);border-radius:5px;padding:15px;max-height:500px;overflow-y:auto;background-color:var(--background-primary);box-shadow:0 1px 4px #0000000d}.workflow-row{margin-bottom:15px;padding:12px;border-radius:6px;background-color:var(--background-secondary-alt);box-shadow:0 1px 3px #00000014;border-left:3px solid var(--interactive-accent)}.workflow-row .setting-item{border:none;padding:0}.workflow-row .setting-item-info{padding:0!important}.workflow-row .setting-item-name{font-size:16px;font-weight:600;color:var(--text-normal)}.workflow-row .setting-item-description{font-size:13px;color:var(--text-muted);margin-top:4px}.workflow-stages-info{margin-top:12px;padding:8px 0 0;border-top:1px solid var(--background-modifier-border)}.workflow-stages-list{list-style-type:none;display:flex;flex-wrap:wrap;gap:var(--size-2-2);padding:0;margin:0}.workflow-stage-item{padding:4px 8px;border-radius:4px;font-size:12px;display:inline-flex;align-items:center;background-color:var(--background-modifier-border)}.workflow-stage-cycle{background-color:var(--task-in-progress-color);color:var(--text-on-accent)}.workflow-stage-terminal{background-color:var(--task-completed-color);color:var(--text-on-accent)}.no-workflows-message{font-style:italic;color:var(--text-muted);text-align:center;padding:15px}.workflow-form{margin-bottom:20px}.workflow-stages-section{margin-top:20px;border-top:1px solid var(--background-modifier-border);padding-top:15px}.workflow-stages-section h2{margin-top:0;margin-bottom:15px;font-size:1.3em;color:var(--text-normal)}.workflow-stages-container{margin-top:15px}.workflow-stages-container .workflow-stages-list{display:block;flex-wrap:unset;gap:unset}.workflow-stages-container .workflow-stage-item{display:block;margin-bottom:10px;padding:0;background-color:transparent}.workflow-buttons{display:flex;justify-content:flex-end;gap:10px;margin-top:20px;padding-top:10px;border-top:1px solid var(--background-modifier-border)}.workflow-save-button,.workflow-cancel-button,.workflow-add-stage-button{padding:6px 12px;border-radius:4px;cursor:pointer}.workflow-save-button.mod-cta{background-color:var(--interactive-accent);color:var(--text-on-accent)}.workflow-cancel-button{background-color:var(--background-modifier-border);color:var(--text-normal)}.workflow-add-stage-button{background-color:var(--interactive-accent);color:var(--text-on-accent);margin-top:10px}.no-stages-message{font-style:italic;color:var(--text-muted);text-align:center;padding:15px}.workflow-stage-header{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;background-color:var(--background-secondary);border-radius:4px;margin-bottom:8px;box-shadow:0 1px 3px #0000001a}.workflow-stage-name{font-weight:600;flex:1;margin-right:10px}.workflow-stage-actions{display:flex;gap:5px}.workflow-stage-edit,.workflow-stage-move-up,.workflow-stage-move-down,.workflow-stage-delete{padding:3px 8px;border-radius:3px;background-color:var(--background-modifier-border);cursor:pointer;font-size:12px;border:none}.workflow-stage-edit:hover,.workflow-stage-move-up:hover,.workflow-stage-move-down:hover{background-color:var(--interactive-accent);color:var(--text-on-accent)}.workflow-stage-type-badge{display:inline-block;padding:2px 6px;margin-left:8px;border-radius:3px;font-size:10px;text-transform:uppercase;font-weight:600}.workflow-stage-type-linear{background-color:var(--background-modifier-border)}.workflow-stage-type-cycle{background-color:var(--task-in-progress-color);color:var(--text-on-accent)}.workflow-stage-type-terminal{background-color:var(--task-completed-color);color:var(--text-on-accent)}.workflow-substages-list{padding:0 0 0 var(--size-4-6);margin-top:var(--size-4-2);margin-bottom:var(--size-4-2);border-left:2px solid var(--background-modifier-border)}.substage-settings-container{width:100%}.stage-type-settings{margin-top:20px;border:1px solid var(--background-modifier-border);border-radius:4px;padding:15px;background-color:var(--background-primary)}.substages-section,.can-proceed-to-section{margin-top:20px;padding-top:15px;border-top:1px solid var(--background-modifier-border)}.substages-container,.can-proceed-to-container{margin-top:15px;padding:10px;border-radius:4px}.substages-list,.can-proceed-list{list-style-type:none;padding:0;margin:0}.substage-name-container{display:flex;gap:10px;align-items:center;flex:1}.substage-name-container input{padding:4px 8px;border-radius:3px;border:1px solid var(--background-modifier-border);background-color:var(--background-primary)}.substage-next-container{display:flex;align-items:center;gap:5px;margin-left:10px}.substage-remove-button,.can-proceed-remove-button{color:var(--text-normal);border-radius:3px;padding:2px 5px;cursor:pointer;border:none}.substage-remove-button:hover,.can-proceed-remove-button:hover{background-color:var(--background-modifier-error);color:var(--text-on-accent)}.add-substage-button,.add-can-proceed-button{background-color:var(--interactive-accent);color:var(--text-on-accent);padding:4px 10px;border-radius:4px;margin-top:10px;cursor:pointer;border:none}.add-can-proceed-container{display:flex;gap:10px;align-items:flex-end}.add-can-proceed-select{flex:1;padding:4px 8px;border-radius:3px;border:1px solid var(--background-modifier-border)}.stage-buttons{display:flex;justify-content:flex-end;gap:10px;margin-top:20px;padding-top:10px;border-top:1px solid var(--background-modifier-border)}.stage-save-button,.stage-cancel-button{padding:6px 12px;border-radius:4px;cursor:pointer;border:none}.stage-save-button.mod-cta{background-color:var(--interactive-accent);color:var(--text-on-accent)}.stage-cancel-button{background-color:var(--background-modifier-border);color:var(--text-normal)}.stage-error-message{color:var(--background-modifier-error);font-weight:bold;text-align:center;margin-top:10px;padding:8px;border-radius:4px}.task-workflow-tag{display:inline-block;padding:2px 5px;border-radius:3px;margin-left:5px;font-size:12px;background-color:var(--background-secondary-alt)}.task-workflow-stage{margin-left:5px;color:var(--text-accent)}.task-workflow-substage{font-size:11px;color:var(--text-muted)}.task-workflow-history{margin-left:20px;font-size:12px;color:var(--text-muted)}.task-workflow-timestamp{color:var(--text-faint)}.setting-item-control span[class^=workflow-stage-name-]{display:inline-block;padding:2px 6px;border-radius:3px;font-size:12px;font-weight:500;margin-right:5px}.setting-item-control .workflow-stage-name-cycle{background-color:var(--task-in-progress-color);color:var(--text-on-accent)}.setting-item-control .workflow-stage-name-terminal{background-color:var(--task-completed-color);color:var(--text-on-accent)}.workflow-stage-item{margin-right:4px}.workflow-stages-container .workflow-stage-header{padding:8px 12px;background-color:var(--background-secondary);border-radius:4px;box-shadow:0 1px 3px #0000001a;margin-bottom:8px}.workflow-stages-container .workflow-stage-type-badge{display:inline-block;padding:2px 6px;margin-left:8px;border-radius:3px;font-size:10px;text-transform:uppercase;font-weight:600}.workflow-substages-list{list-style-type:none;padding:0 0 0 20px;margin:5px 0 10px;border-left:2px solid var(--background-modifier-border)}.workflow-add-stage-button,.stage-save-button.mod-cta,.workflow-save-button.mod-cta{background-color:var(--interactive-accent);color:var(--text-on-accent);padding:6px 15px;border-radius:4px;font-weight:500;border:none;cursor:pointer;box-shadow:0 2px 4px #0000001a;transition:all .2s ease;text-align:center}.workflow-add-stage-button:hover,.stage-save-button.mod-cta:hover,.workflow-save-button.mod-cta:hover{background-color:var(--interactive-accent-hover);box-shadow:0 3px 6px #00000026;transform:translateY(-1px)}.workflow-stage-move-up,.workflow-stage-move-down,.workflow-stage-edit,.workflow-stage-delete{border:none;background-color:var(--background-modifier-border);padding:3px 8px;border-radius:3px;font-size:12px;cursor:pointer;transition:all .2s ease}.workflow-stage-move-up:hover,.workflow-stage-move-down:hover,.workflow-stage-edit:hover{background-color:var(--interactive-accent);color:var(--text-on-accent)}.workflow-stage-delete:hover{background-color:var(--background-modifier-error);color:var(--text-on-accent)}.substage-item{display:flex;justify-content:flex-end;align-items:center;padding:6px 0;margin-bottom:5px;border-radius:4px}.substage-name-container input{background-color:var(--background-primary);border:1px solid var(--background-modifier-border);padding:4px 8px;border-radius:3px;font-size:13px}.substage-name-container input:focus{border-color:var(--interactive-accent);outline:none}.no-stages-message,.no-workflows-message,.no-substages-message,.no-can-proceed-message{font-style:italic;color:var(--text-muted);padding:15px;text-align:center;background-color:var(--background-secondary-alt);border-radius:5px;margin:10px 0}.rewards-levels-container,.rewards-items-container{margin-top:10px;padding:15px;border-radius:5px;border:1px solid var(--background-modifier-border);background-color:var(--background-secondary)}.rewards-level-row .setting-item-info,.rewards-item-row .setting-item-info{display:none}.rewards-item-row.setting-item{border-top:0}.rewards-level-row .setting-item-control,.rewards-item-row .setting-item-control{display:flex;flex-wrap:wrap;gap:10px;align-items:center}.rewards-level-row .setting-item-control input[type=text]{flex:1;min-width:100px}.rewards-item-row .setting-item-control .input-container{flex:1;min-width:150px}.rewards-item-row .setting-item-control textarea{width:100%;min-height:40px;resize:vertical}.rewards-item-row .setting-item-control .dropdown{min-width:120px}.rewards-level-row .setting-item-control button,.rewards-item-row .setting-item-control button{margin-left:auto}.rewards-item-divider{border:none;height:1px;background-color:var(--background-modifier-border);margin-top:15px;margin-bottom:15px}.setting-item.sort-criterion-row .setting-item-info{display:none}.setting-item.sort-criterion-row select.dropdown{flex:1}.view-management-list{margin:10px 0;border:1px solid var(--background-modifier-border);border-radius:5px;padding:10px}.view-edit-button,.view-copy-button,.view-order-button,.view-delete-button{padding:4px;width:24px;height:24px;border-radius:4px;margin-left:4px;display:flex;align-items:center;justify-content:center}.view-copy-button{color:var(--interactive-accent)}.view-copy-button:hover{background-color:var(--interactive-accent);color:var(--text-on-accent)}.view-delete-button{color:var(--text-error)}.view-delete-button:hover{background-color:var(--background-modifier-error);color:var(--text-on-accent)}.view-icon{margin-right:8px;--icon-size: 16px}.copy-mode-info{margin:10px 0;padding:12px;background-color:var(--background-secondary-alt);border-radius:5px;border-left:3px solid var(--interactive-accent)}.copy-mode-info p{margin:4px 0}.tasks-compatibility-warning{display:flex;align-items:flex-start;gap:var(--size-4-3);padding:var(--size-4-4);margin-bottom:var(--size-4-4);background-color:hsl(var(--accent-h),var(--accent-s),var(--accent-l),.5);border:1px solid hsl(var(--accent-h),var(--accent-s),var(--accent-l),.5);border-radius:var(--radius-m);color:var(--text-on-accent)}.tasks-warning-icon{font-size:20px;line-height:1;flex-shrink:0}.tasks-warning-content{flex:1;display:flex;flex-direction:column;gap:var(--size-2-2)}.tasks-warning-title{font-weight:600;font-size:var(--font-ui-medium)}.tasks-warning-text{color:var(--text-on-accent);font-size:var(--font-ui-small);line-height:1.4}.tasks-warning-text a{color:var(--text-on-accent);text-decoration:underline}.tasks-warning-text a:hover{color:var(--text-on-accent)}.task-genius-format-examples{display:flex;flex-direction:column;gap:var(--size-2-3);padding:var(--size-4-3);margin:var(--size-4-3) 0;border-radius:var(--radius-m);background-color:var(--background-secondary-alt);border:1px solid var(--background-modifier-border)}.task-genius-format-examples strong{font-size:var(--font-ui-medium);font-weight:600;color:var(--text-normal);margin-bottom:var(--size-2-1)}.task-genius-format-examples span{font-family:var(--font-monospace);font-size:var(--font-ui-smaller);line-height:1.5;color:var(--text-muted);padding:var(--size-2-1) var(--size-2-3);background-color:var(--background-primary);border-radius:var(--radius-s);border:1px solid var(--background-modifier-border);margin:var(--size-2-1) 0}.task-genius-format-examples span:first-of-type{margin-top:0}.task-genius-format-examples span:last-of-type{margin-bottom:0}.project-path-mappings-container,.project-metadata-mappings-container{margin-top:10px}.project-path-mapping-row,.project-metadata-mapping-row{border:1px solid var(--background-modifier-border);border-radius:6px;margin-bottom:10px;padding:10px}.no-mappings-message{color:var(--text-muted);font-style:italic;text-align:center;padding:20px}.task-project-tg{opacity:.8;font-style:italic;border-left:2px solid var(--color-accent);padding-left:4px}.task-project-tg:before{content:"\1f517";margin-right:2px;font-size:.8em}.project-readonly{opacity:.8}.project-readonly input{background-color:var(--background-modifier-border);cursor:not-allowed}.project-source-indicator{font-size:var(--font-ui-smaller);color:var(--text-muted);font-style:italic;margin-top:4px}.tg-status-icon{display:inline-flex;align-items:center;vertical-align:middle;margin-right:var(--size-2-3);margin-top:calc(-1 * var(--size-2-1))}.tg-icons-container{display:flex;gap:var(--size-2-2);flex-wrap:wrap;align-items:center;justify-content:center}.tg-icons-container .tg-status-icon{margin-right:0;margin-top:0}.global-filter-container{margin-bottom:20px;padding:10px;border:1px solid var(--background-modifier-border);border-radius:6px;background-color:var(--background-secondary)}.beta-test-warning-banner{display:flex;align-items:flex-start;gap:12px;padding:16px;margin-bottom:20px;background-color:var(--background-modifier-warning);border:1px solid var(--color-orange);border-radius:8px}.beta-warning-icon{font-size:20px;line-height:1;flex-shrink:0;margin-top:2px}.beta-warning-content{flex:1;min-width:0}.beta-warning-title{font-weight:600;font-size:14px;color:var(--text-normal);margin-bottom:8px}.beta-warning-text{font-size:13px;line-height:1.4;color:var(--text-muted)}.task-details .panel-toggle-container{left:10px}.task-details{width:300px;flex-shrink:0;border-left:1px solid var(--background-modifier-border);height:100%;overflow-y:auto;display:flex;flex-direction:column;transition:all .3s ease-in-out;position:relative;min-width:250px;max-width:400px;background-color:var(--background-secondary);order:1}.task-genius-container.details-hidden .task-details{width:0;opacity:0;margin-right:-300px;overflow:hidden}.task-genius-container.details-visible .task-details{width:350px;opacity:1;margin-right:0}.is-phone .task-details{position:absolute;right:0;top:0;height:100%;width:100%;max-width:100%;z-index:10;transform:translate(100%)}.is-phone .task-genius-container.details-hidden .task-details{width:100%;margin-right:0;transform:translate(100%)}.is-phone .task-genius-container.details-visible .task-details{width:calc(100% - var(--size-4-12));transform:translate(0)}.is-phone .task-genius-container.details-visible:before{content:"";position:absolute;top:0;left:0;width:100%;height:100%;background-color:var(--background-modifier-cover);opacity:.5;z-index:5;transition:opacity .3s ease-in-out}.is-phone .details-close-btn{width:24px;height:24px;display:flex;align-items:center;justify-content:center}.is-phone .details-header{padding:var(--size-4-4)}.details-empty{display:flex;height:100%;align-items:center;justify-content:center;text-align:center;color:var(--text-muted);padding:20px}.details-header{padding:var(--size-4-4);padding-bottom:var(--size-4-3);padding-top:var(--size-4-3);font-weight:600;border-bottom:1px solid var(--background-modifier-border);display:flex;justify-content:space-between;align-items:center;font-size:1.1em}.details-content{padding:var(--size-4-4);display:flex;flex-direction:column;gap:var(--size-4-2);overflow-y:auto;padding-bottom:max(var(--safe-area-inset-bottom),var(--size-4-8))}.details-name{margin:0 0 8px;padding:0;font-size:1.3em;line-height:1.3}.details-status-container{display:flex;justify-content:space-between;align-items:center}.details-status-label{text-transform:uppercase;font-size:var(--font-ui-small)}.details-status{display:inline-block;padding:4px 8px;border-radius:4px;background-color:var(--color-accent);color:var(--text-on-accent);font-size:var(--font-ui-small)}.details-status-selector{display:flex;justify-content:space-evenly;align-items:center}.menu-item-title:has(.status-option){display:flex;align-items:center;gap:4px}.menu-item:has(.status-option-checkbox) .menu-item-icon{display:none}.menu-item:has(.status-option-icon) .menu-item-icon{display:none}.status-option-icon{display:flex;align-items:center;justify-content:center;margin-right:var(--size-2-2)}.status-option-checkbox{display:flex;align-items:center;justify-content:center}.status-option{display:flex;justify-content:center;text-transform:uppercase}.status-option.current{outline-offset:2px;outline:1px solid hsl(var(--accent-h),var(--accent-s),var(--accent-l),.3);outline-style:dashed}.status-option:not(.current){opacity:.8}.status-option:not(.current):hover{opacity:1}.status-option input.task-list-item-checkbox{margin-inline-end:0}.details-metadata{display:flex;flex-direction:column;gap:var(--size-4-2);margin-top:var(--size-4-2);margin-bottom:var(--size-4-2)}.metadata-field{display:flex;flex-direction:column;gap:2px}.metadata-label{font-size:.8em;color:var(--text-muted)}.metadata-value{word-break:break-word;font-size:.95em}.details-actions{display:flex;align-items:center;justify-content:flex-start;gap:8px;margin-bottom:var(--size-4-4)}.details-edit-btn,.details-toggle-btn{background-color:var(--interactive-normal);border:1px solid var(--background-modifier-border);border-radius:4px;padding:6px 12px;color:var(--text-normal);cursor:pointer;font-size:var(--font-ui-small)}.details-edit-btn:hover,.details-toggle-btn:hover{background-color:var(--interactive-hover)}.details-toggle-btn{background-color:var(--interactive-accent);color:var(--text-on-accent)}.details-edit-form{display:flex;flex-direction:column;gap:12px}.details-form-field{display:flex;flex-direction:column;gap:4px}.details-form-label{font-size:.8em;color:var(--text-muted);font-weight:500}.details-form-input{width:100%}.details-edit-content{font-weight:500}.details-form-input input,.details-form-input select{width:100%;padding:6px 8px;border-radius:4px;border:1px solid var(--background-modifier-border);background-color:var(--background-primary)}.date-input{width:100%;padding:6px 8px;border-radius:4px;border:1px solid var(--background-modifier-border);background-color:var(--background-primary);color:var(--text-normal)}.field-description{font-size:.7em;color:var(--text-muted);margin-top:2px}.details-form-buttons{display:flex;justify-content:space-between;margin-top:16px;gap:8px}.details-form-buttons button{flex:1;justify-content:center}.details-form-error{color:var(--text-error);font-size:.8em;margin-top:8px;padding:8px;background-color:var(--background-modifier-error);border-radius:4px}.details-edit-file-btn{background-color:var(--interactive-normal);border:1px solid var(--background-modifier-border);border-radius:4px;padding:6px 12px;color:var(--text-normal);cursor:pointer;font-size:var(--font-ui-small)}.details-edit-file-btn:hover{background-color:var(--interactive-hover)}@media screen and (max-width: 768px){.task-omnifocus-container{flex-direction:column}.task-sidebar{width:100%;max-width:100%;height:auto;border-right:none;border-bottom:1px solid var(--background-modifier-border)}.task-content{width:100%;flex:1}.task-details{width:100%;max-width:100%;border-left:none}}.project-source-indicator{display:flex;align-items:center;gap:4px;margin-top:4px;padding:4px 8px;border-radius:4px;font-size:.85em;line-height:1.2}.project-source-indicator .indicator-icon{font-size:.9em}.project-source-indicator .indicator-text{color:var(--text-muted)}.project-source-indicator.readonly-indicator{border:1px solid var(--background-modifier-error)}.project-source-indicator.readonly-indicator .indicator-text{color:var(--text-error);font-weight:500}.project-source-indicator.override-indicator{border:1px solid var(--background-modifier-accent)}.project-source-indicator.override-indicator .indicator-text{color:var(--text-accent)}.field-description.readonly-description{color:var(--text-error);font-size:.8em;margin-top:4px;font-style:italic}.field-description.override-description{color:var(--text-accent);font-size:.8em;margin-top:4px;font-style:italic}.project-source-indicator.inline-indicator{position:absolute;top:100%;left:0;right:0;z-index:10;margin-top:2px;padding:2px 6px;font-size:.75em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.project-source-indicator.table-indicator{position:absolute;top:2px;right:2px;padding:1px 3px;font-size:.7em;border-radius:2px;z-index:5}.project-source-indicator.table-indicator .indicator-icon{font-size:.8em}.task-table-cell.readonly-cell{background-color:var(--background-modifier-error-hover);opacity:.8}.project-container.project-readonly{position:relative}.project-container.project-readonly .project-source-indicator{margin-top:8px}.oncompletion-configurator{display:flex;flex-direction:column;gap:12px;padding:12px;border:1px solid var(--background-modifier-border);border-radius:6px;background-color:var(--background-secondary)}.oncompletion-action-type{display:flex;flex-direction:column;gap:6px}.oncompletion-label{font-weight:600;color:var(--text-normal);font-size:.9em}.oncompletion-config{display:flex;flex-direction:column;gap:10px;margin-top:8px;padding-top:8px;border-top:1px solid var(--background-modifier-border-hover)}.oncompletion-field{display:flex;flex-direction:column;gap:4px}.oncompletion-description{font-size:.8em;color:var(--text-muted);font-style:italic;margin-top:2px}.oncompletion-action-type .dropdown{width:100%}.oncompletion-field .text-input{width:100%;padding:6px 8px;border:1px solid var(--background-modifier-border);border-radius:4px;background-color:var(--background-primary);color:var(--text-normal)}.oncompletion-field .text-input:focus{border-color:var(--interactive-accent);outline:none;box-shadow:0 0 0 2px var(--interactive-accent-hover)}.oncompletion-field .checkbox-container{display:flex;align-items:center;gap:8px}.task-id-suggestion{font-weight:600;color:var(--text-accent)}.task-content-preview{font-size:.85em;color:var(--text-muted);margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:300px}.file-name{font-weight:500;color:var(--text-normal)}.file-path{font-size:.8em;color:var(--text-muted);margin-top:2px}.action-type-suggestion{font-weight:600;color:var(--text-accent)}.action-description{font-size:.8em;color:var(--text-muted);margin-top:2px}.oncompletion-configurator.invalid{border-color:var(--text-error);background-color:var(--background-modifier-error)}.oncompletion-configurator.valid{border-color:var(--text-success)}.oncompletion-validation-message{font-size:.8em;margin-top:4px;padding:4px 6px;border-radius:3px}.oncompletion-validation-message.error{color:var(--text-error);background-color:var(--background-modifier-error)}.oncompletion-validation-message.success{color:var(--text-success);background-color:var(--background-modifier-success)}.task-details .oncompletion-configurator{margin-top:8px;border:none;background-color:transparent;padding:0}.task-details .oncompletion-field{margin-bottom:8px}@media (max-width: 768px){.oncompletion-configurator{padding:8px;gap:8px}.oncompletion-config{gap:8px}.task-content-preview{max-width:200px}}.theme-dark .oncompletion-configurator{background-color:var(--background-primary-alt)}.theme-dark .oncompletion-field .text-input{background-color:var(--background-secondary);border-color:var(--background-modifier-border-hover)}@media (prefers-contrast: high){.oncompletion-configurator{border-width:2px}.oncompletion-field .text-input{border-width:2px}.oncompletion-field .text-input:focus{box-shadow:0 0 0 3px var(--interactive-accent-hover)}}.oncompletion-config{transition:all .2s ease-in-out}.oncompletion-field{opacity:1;transform:translateY(0);transition:opacity .2s ease-in-out,transform .2s ease-in-out}.oncompletion-field.entering{opacity:0;transform:translateY(-10px)}.oncompletion-field.exiting{opacity:0;transform:translateY(10px)}.oncompletion-modal{--dialog-width: 600px;--dialog-max-width: 90vw;--dialog-max-height: 80vh}.oncompletion-modal .modal-content{padding:0;max-height:var(--dialog-max-height);overflow-y:auto}.oncompletion-modal-content{padding:20px;max-height:60vh;overflow-y:auto}.oncompletion-modal-buttons{display:flex;justify-content:flex-end;gap:8px;padding:16px 20px;border-top:1px solid var(--background-modifier-border);background-color:var(--background-secondary)}.oncompletion-modal-buttons button{min-width:80px}.inline-oncompletion-button-container{display:inline-flex;align-items:center}.inline-oncompletion-config-button{padding:4px 8px;border:1px solid var(--background-modifier-border);border-radius:4px;background-color:var(--background-primary);color:var(--text-normal);font-family:inherit;font-size:var(--font-ui-small);cursor:pointer;transition:all .15s ease;min-width:100px;text-align:left}.inline-oncompletion-config-button:hover{background-color:var(--background-modifier-hover);border-color:var(--interactive-accent)}.inline-oncompletion-config-button:focus{outline:none;border-color:var(--interactive-accent);box-shadow:0 0 0 2px var(--interactive-accent-hover)}.inline-oncompletion-config-button:active{background-color:var(--background-modifier-active);transform:scale(.98)}@media (max-width: 768px){.oncompletion-modal{--dialog-width: 95vw;--dialog-max-height: 85vh}.oncompletion-modal-content{padding:16px;max-height:65vh}.oncompletion-modal-buttons{padding:12px 16px;flex-direction:column-reverse}.oncompletion-modal-buttons button{width:100%;min-width:unset}}.universal-suggest-item{display:flex;align-items:center;cursor:pointer;border-radius:4px;transition:background-color .1s ease}.universal-suggest-item:hover{background-color:var(--background-modifier-hover)}.universal-suggest-item.is-selected{background-color:var(--background-modifier-active-hover)}.universal-suggest-container{display:flex;flex-direction:row;align-items:center;justify-content:flex-start;overflow:hidden}.universal-suggest-icon{display:flex;align-items:center;justify-content:center;width:20px;height:20px;margin-right:12px;color:var(--text-muted);flex-shrink:0}.universal-suggest-content{flex:1;min-width:0}.universal-suggest-label{font-weight:500;color:var(--text-normal);margin-bottom:2px}.universal-suggest-description{font-size:.85em;color:var(--text-muted);line-height:1.3}.cm-editor .cm-line .universal-suggest-trigger{background-color:var(--background-modifier-accent);color:var(--text-accent);border-radius:2px;padding:1px 2px}.suggestion-container .universal-suggest-item{border-bottom:1px solid var(--background-modifier-border)}.suggestion-container .universal-suggest-item:last-child{border-bottom:none}.theme-dark .universal-suggest-item:hover{background-color:var(--background-modifier-hover)}.theme-dark .universal-suggest-item.is-selected{background-color:var(--background-modifier-active-hover)}@media (prefers-contrast: high){.universal-suggest-item{border:1px solid var(--background-modifier-border);margin-bottom:2px}.universal-suggest-item:hover,.universal-suggest-item.is-selected{border-color:var(--text-accent)}}.confirm-modal-buttons{display:flex;gap:var(--size-4-3);justify-content:flex-end;margin-top:var(--size-4-3)}.habit-edit-dialog{max-width:600px;width:100%}.habit-edit-dialog .modal-content{padding:20px}.habit-edit-dialog .habit-type-selector{margin-bottom:20px}.habit-edit-dialog .habit-type-description{font-weight:600;margin-bottom:10px}.habit-edit-dialog .habit-type-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:10px}@media (max-width: 500px){.habit-edit-dialog .habit-type-grid{grid-template-columns:1fr}}.habit-edit-dialog .habit-type-item{display:flex;padding:12px;border-radius:var(--radius-m);border:1px solid var(--background-modifier-border);background-color:var(--background-secondary);cursor:pointer;transition:all .2s ease}.habit-edit-dialog .habit-type-item:hover{background-color:var(--background-modifier-hover)}.habit-edit-dialog .habit-type-item.selected{border-color:var(--interactive-accent);background-color:var(--interactive-accent-hover)}.habit-edit-dialog .habit-type-icon{display:flex;align-items:center;justify-content:center;width:40px;height:40px;border-radius:50%;background-color:var(--background-primary);margin-right:10px}.habit-edit-dialog .habit-type-icon svg{width:20px;height:20px;color:var(--text-normal)}.habit-edit-dialog .habit-type-text{flex:1;display:flex;flex-direction:column}.habit-edit-dialog .habit-type-name{font-weight:600;margin-bottom:4px}.habit-edit-dialog .habit-type-desc{font-size:.85em;color:var(--text-muted)}.habit-edit-dialog .habit-common-form,.habit-edit-dialog .habit-type-form{margin-bottom:20px}.habit-edit-dialog .habit-icon-preview{display:flex;align-items:center;justify-content:center;width:30px;height:30px;margin-left:10px;background-color:var(--background-primary);border-radius:50%}.habit-edit-dialog .habit-icon-preview svg{width:18px;height:18px}.habit-edit-dialog .habit-mapping-container{border:1px solid var(--background-modifier-border);border-radius:var(--radius-s);padding:10px;margin-bottom:10px;margin-top:5px}.habit-edit-dialog .habit-mapping-row{display:flex;align-items:center;margin-bottom:8px}.habit-edit-dialog .habit-mapping-key{width:80px;margin-right:5px;font-size:.9em}.habit-edit-dialog .habit-mapping-arrow{margin:0 10px;color:var(--text-muted)}.habit-edit-dialog .habit-mapping-value{flex:1;font-size:.9em;margin-right:var(--size-4-4)}.habit-edit-dialog .habit-mapping-delete{background:none;border:none;color:var(--text-error);cursor:pointer;font-size:1.2em;padding:0 8px}.habit-edit-dialog .habit-add-mapping-button{background-color:var(--interactive-accent);color:var(--text-on-accent);border:none;border-radius:var(--radius-s);padding:6px 12px;cursor:pointer;font-size:.9em}.habit-edit-dialog .habit-events-container{border:1px solid var(--background-modifier-border);border-radius:var(--radius-s);padding:10px;margin-bottom:10px;margin-top:5px}.habit-edit-dialog .habit-event-row{display:flex;margin-bottom:8px;gap:5px}.habit-edit-dialog .habit-event-name{width:120px;font-size:.9em}.habit-edit-dialog .habit-event-details{flex:1;font-size:.9em}.habit-edit-dialog .habit-event-property{width:120px;font-size:.9em}.habit-edit-dialog .habit-event-delete{background:none;border:none;color:var(--text-error);cursor:pointer;font-size:1.2em;padding:0 8px}.habit-edit-dialog .habit-add-event-button{background-color:var(--interactive-accent);color:var(--text-on-accent);border:none;border-radius:var(--radius-s);padding:6px 12px;cursor:pointer;font-size:.9em}.habit-edit-dialog .habit-edit-buttons{display:flex;justify-content:flex-end;gap:10px;margin-top:20px}.habit-edit-dialog .habit-cancel-button{background-color:var(--background-modifier-hover);color:var(--text-normal);border:1px solid var(--background-modifier-border);border-radius:var(--radius-s);padding:8px 16px;cursor:pointer}.habit-edit-dialog .habit-save-button{background-color:var(--interactive-accent);color:var(--text-on-accent);border:none;border-radius:var(--radius-s);padding:8px 16px;cursor:pointer}.habit-edit-dialog input[type=text],.habit-edit-dialog input[type=number]{background-color:var(--background-primary);border:1px solid var(--background-modifier-border);border-radius:var(--radius-s);padding:6px;color:var(--text-normal)}.habit-edit-dialog .habit-type-item.selected .habit-type-desc,.habit-edit-dialog .habit-type-item.selected .habit-type-name{color:var(--text-on-accent)}.habit-list-container{padding:12px;width:100%}.habit-settings-container{padding-top:12px;border-top:1px solid var(--background-modifier-border)}.habit-add-button-container{display:flex;justify-content:flex-end;margin-bottom:16px}.habit-add-button{display:flex;align-items:center;gap:6px;padding:6px 12px;background-color:var(--interactive-accent);color:var(--text-on-accent);border-radius:var(--radius-s);cursor:pointer;font-size:14px}.habit-add-button svg{width:16px;height:16px}.habit-empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:200px;text-align:center;padding:20px;border:1px dashed var(--background-modifier-border);border-radius:var(--radius-m);background-color:var(--background-secondary)}.habit-empty-state h2{margin:0 0 10px;font-size:1.2em;color:var(--text-normal)}.habit-empty-state p{margin:0;color:var(--text-muted)}.habit-items-container{display:flex;flex-direction:column;gap:10px}.habit-item{display:flex;align-items:center;padding:12px;border-radius:var(--radius-m);background-color:var(--background-secondary);border:1px solid var(--background-modifier-border);transition:background-color .2s ease;cursor:pointer;height:7.5rem}.habit-item:hover{background-color:var(--background-modifier-hover)}.habit-item-icon{--icon-size: 20px;display:flex;align-items:center;justify-content:center;width:48px;height:48px;border-radius:50%;background-color:var(--background-primary);margin-right:12px}.habit-item-icon svg{color:var(--text-normal)}.habit-item-info{flex:1;min-width:0}.habit-item-name{font-weight:600;margin-bottom:4px;font-size:16px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.habit-item-description{color:var(--text-muted);font-size:12px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-bottom:4px}.habit-item-type{display:inline-block;font-size:11px;padding:2px 6px;border-radius:var(--radius-s);background-color:var(--background-modifier-border);color:var(--text-muted)}.habit-item-actions{display:flex;gap:8px;margin-left:12px}.habit-edit-button,.habit-delete-button{display:flex;align-items:center;justify-content:center;width:32px;height:32px;border-radius:50%;background-color:var(--background-primary);cursor:pointer;padding:0;border:1px solid var(--background-modifier-border)}.habit-edit-button:hover,.habit-delete-button:hover{background-color:var(--background-modifier-hover)}.habit-edit-button svg,.habit-delete-button svg{width:16px;height:16px;color:var(--text-muted)}.habit-delete-button:hover svg{color:var(--text-error)}.habit-delete-modal-buttons{display:flex;justify-content:flex-end;gap:10px;margin-top:20px}.habit-delete-button-confirm{background-color:var(--text-error);color:#fff;border:none;border-radius:var(--radius-s);padding:8px 16px;cursor:pointer}.ics-settings-container{max-width:800px;margin:0 auto}.ics-header-container{margin-bottom:2rem;border-bottom:1px solid var(--background-modifier-border);padding-bottom:1rem}.ics-back-button{background:var(--interactive-accent);color:var(--text-on-accent);border:none;padding:.5rem 1rem;border-radius:6px;cursor:pointer;margin-bottom:1rem;font-size:.9em;transition:all .2s ease}.ics-back-button:hover{background:var(--interactive-accent-hover);transform:translateY(-1px)}.ics-description{color:var(--text-muted);margin-top:.5rem;line-height:1.5}.ics-global-settings{margin-bottom:2rem;padding:1.5rem;border:1px solid var(--background-modifier-border);border-radius:8px;background:var(--background-secondary)}.ics-sources-list{margin-top:1.5rem}.ics-sources-list h3{margin-bottom:1rem;color:var(--text-normal)}.ics-source-item{margin-bottom:1rem;padding:1.5rem;border:1px solid var(--background-modifier-border);border-radius:8px;background:var(--background-primary);transition:all .2s ease}.ics-source-item:hover{border-color:var(--interactive-accent);box-shadow:0 2px 8px #0000001a}.ics-source-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:1rem}.ics-source-title strong{font-size:1.1em;color:var(--text-normal)}.ics-source-status{padding:.3rem .8rem;border-radius:12px;font-size:.75em;font-weight:600;text-transform:uppercase;letter-spacing:.5px}.status-enabled{background:var(--color-green);color:#fff}.status-disabled{background:var(--color-red);color:#fff}.ics-source-details{margin-bottom:1.5rem;font-size:.9em;color:var(--text-muted);line-height:1.4}.ics-source-details div{margin-bottom:.4rem}.ics-source-actions{display:flex;justify-content:space-between;align-items:center;gap:1rem}.primary-actions,.secondary-actions{display:flex;gap:.5rem}.ics-source-actions button{padding:.5rem 1rem;border:1px solid var(--background-modifier-border);border-radius:6px;background:var(--background-secondary);color:var(--text-normal);font-size:.85em;cursor:pointer;transition:all .2s ease;min-width:80px;white-space:nowrap}.ics-source-actions button:hover{background:var(--background-modifier-hover);border-color:var(--interactive-accent);transform:translateY(-1px)}.ics-source-actions button.mod-cta{background:var(--interactive-accent);color:var(--text-on-accent);border-color:var(--interactive-accent)}.ics-source-actions button.mod-cta:hover{background:var(--interactive-accent-hover)}.ics-source-actions button.mod-warning{background:var(--color-red);color:#fff;border-color:var(--color-red)}.ics-source-actions button.mod-warning:hover{background:var(--color-red);opacity:.8}.ics-source-actions button:disabled{opacity:.5;cursor:not-allowed;transform:none}.ics-source-actions button.syncing{color:var(--interactive-accent)}.ics-source-actions button.success{background:var(--color-green);color:#fff;border-color:var(--color-green)}.ics-source-actions button.error{background:var(--color-red);color:#fff;border-color:var(--color-red)}.ics-add-source-container{margin-top:2rem;text-align:center;padding:2rem;border:2px dashed var(--background-modifier-border);border-radius:8px;background:var(--background-secondary);transition:all .2s ease}.ics-add-source-container:hover{border-color:var(--interactive-accent);background:var(--background-modifier-hover)}.ics-add-source-container button{background:var(--interactive-accent);color:var(--text-on-accent);border:none;padding:.8rem 1.5rem;border-radius:6px;font-weight:500;cursor:pointer;transition:all .2s ease;font-size:.95em}.ics-add-source-container button:hover{background:var(--interactive-accent-hover);transform:translateY(-2px)}.ics-test-container{margin-top:1rem;text-align:center;padding:1rem;border:1px solid var(--background-modifier-border);border-radius:8px;background:var(--background-modifier-form-field)}.ics-test-button{background:var(--color-orange);color:#fff;border:none;padding:.6rem 1.2rem;border-radius:6px;font-weight:500;cursor:pointer;transition:all .2s ease;font-size:.9em}.ics-test-button:hover{background:var(--color-orange);opacity:.8;transform:translateY(-1px)}.ics-empty-state{text-align:center;padding:3rem 2rem;color:var(--text-muted);font-style:italic;background:var(--background-secondary);border-radius:8px;border:1px solid var(--background-modifier-border)}.ics-source-modal .modal-content{max-width:600px;max-height:80vh;overflow-y:auto}.auth-field{margin-top:.5rem}.modal-button-container{display:flex;gap:.5rem;justify-content:flex-end;margin-top:1.5rem;padding-top:1rem;border-top:1px solid var(--background-modifier-border)}.modal-button-container button{padding:.5rem 1rem;border-radius:6px;font-size:.9em;min-width:80px}@media (max-width: 768px){.ics-source-header{flex-direction:column;align-items:flex-start;gap:.5rem}.ics-source-actions{flex-direction:column;gap:.5rem}.primary-actions,.secondary-actions{width:100%;justify-content:space-between}.ics-source-actions button{flex:1;min-width:auto}}@media (max-width: 480px){.ics-source-item{padding:1rem}.primary-actions,.secondary-actions{flex-direction:column}.ics-source-actions button{width:100%;margin-bottom:.3rem}.modal-button-container{flex-direction:column}.modal-button-container button{width:100%}}.text-replacements-list{margin:1rem 0}.text-replacements-empty{text-align:center;padding:2rem;color:var(--text-muted);font-style:italic;background:var(--background-secondary);border-radius:6px;border:1px dashed var(--background-modifier-border)}.text-replacement-rule{margin-bottom:1rem;padding:1rem;border:1px solid var(--background-modifier-border);border-radius:6px;background:var(--background-primary);transition:all .2s ease}.text-replacement-rule:hover{border-color:var(--interactive-accent);box-shadow:0 2px 4px #0000001a}.text-replacement-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:.8rem}.text-replacement-header strong{color:var(--text-normal);font-size:1em}.text-replacement-status{padding:.2rem .6rem;border-radius:10px;font-size:.7em;font-weight:600;text-transform:uppercase;letter-spacing:.5px}.text-replacement-status.enabled{background:var(--color-green);color:#fff}.text-replacement-status.disabled{background:var(--color-red);color:#fff}.text-replacement-details{margin-bottom:1rem;font-size:.85em;color:var(--text-muted);line-height:1.4}.text-replacement-details div{margin-bottom:.3rem}.text-replacement-pattern{font-family:var(--font-monospace);background:var(--background-modifier-form-field);padding:.2rem .4rem;border-radius:3px;display:inline-block;margin-left:.5rem}.text-replacement-replacement{font-family:var(--font-monospace);background:var(--background-modifier-form-field);padding:.2rem .4rem;border-radius:3px;display:inline-block;margin-left:.5rem}.text-replacement-actions{display:flex;gap:.5rem;flex-wrap:wrap}.text-replacement-actions button{padding:.4rem .8rem;border:1px solid var(--background-modifier-border);border-radius:4px;background:var(--background-secondary);color:var(--text-normal);font-size:.8em;cursor:pointer;transition:all .2s ease}.text-replacement-actions button:hover{background:var(--background-modifier-hover);border-color:var(--interactive-accent)}.text-replacement-actions button.mod-cta{background:var(--interactive-accent);color:var(--text-on-accent);border-color:var(--interactive-accent)}.text-replacement-actions button.mod-warning{background:var(--color-red);color:#fff;border-color:var(--color-red)}.text-replacement-add{margin-top:1rem;text-align:center}.text-replacement-add button{background:var(--interactive-accent);color:var(--text-on-accent);border:none;padding:.6rem 1.2rem;border-radius:6px;font-weight:500;cursor:pointer;transition:all .2s ease}.text-replacement-add button:hover{background:var(--interactive-accent-hover);transform:translateY(-1px)}.text-replacement-modal .modal-content{max-width:700px;max-height:85vh;overflow-y:auto}.test-output{margin-top:.5rem;padding:.8rem;background:var(--background-modifier-form-field);border-radius:4px;border:1px solid var(--background-modifier-border);font-family:var(--font-monospace);font-size:.9em}.test-result{font-weight:500}.text-replacement-modal ul{margin:.5rem 0;padding-left:1.5rem}.text-replacement-modal li{margin-bottom:.5rem;line-height:1.4}.text-replacement-modal code{background:var(--background-modifier-form-field);padding:.1rem .3rem;border-radius:3px;font-family:var(--font-monospace);font-size:.85em}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.ics-source-actions button.syncing:before{content:"";display:inline-block;margin-right:.3rem;animation:spin 1s linear infinite}.ics-text-replacement-modal,.ics-source-modal{max-width:1000px;max-height:90vh;padding-right:0}.ics-text-replacement-modal .modal-content,.ics-source-modal .modal-content{padding-right:var(--size-4-2)}.task-filter-panel{padding:var(--size-4-4) var(--size-4-4);padding-bottom:var(--size-2-2);padding-left:var(--size-4-8);background-color:var(--background-primary);border-top:1px solid var(--background-modifier-border);display:flex;flex-direction:column;max-height:300px;overflow-y:auto}.task-filter-active{color:var(--color-accent-2);font-weight:bold}.task-filter-panel>.setting-item{border-top:unset}.task-filter-header-container{display:flex;align-items:center;justify-content:flex-end}.task-filter-title{font-size:var(--font-ui-small);color:var(--text-normal)}.task-filter-options{display:flex;flex-direction:column;gap:10px}.task-filter-section{display:flex;flex-direction:column}.task-filter-section h3{font-size:14px;margin:5px 0;color:var(--text-muted)}.task-filter-section:last-child{border-bottom:unset}.task-filter-option{display:flex;align-items:center;gap:6px}.task-filter-option input[type=checkbox]{margin:0}.task-filter-option label{font-size:13px;color:var(--text-normal)}.task-filter-buttons{display:flex;justify-content:flex-end;gap:8px;margin-top:8px;padding-top:8px;border-top:1px solid var(--background-modifier-border)}.task-filter-apply,.task-filter-close{padding:6px 12px;border-radius:4px;font-size:12px;cursor:pointer}.task-filter-apply{background-color:var(--interactive-accent);color:var(--text-on-accent)}.task-filter-reset{background-color:var(--background-modifier-border);color:var(--text-normal)}.task-filter-close{background-color:var(--background-secondary);color:var(--text-normal)}.task-filter-query-input{width:100%;min-width:250px;border-radius:4px;padding:8px 12px;font-family:var(--font-monospace);font-size:14px}.task-filter-query-input:focus{box-shadow:0 0 0 2px var(--interactive-accent);outline:none}.task-filter-section .setting-item-description{margin-top:5px;margin-bottom:10px;font-size:12px;color:var(--text-muted);line-height:1.4}.task-filter-options{max-height:70vh;overflow-y:auto;padding-right:5px}.task-filter-options{margin-bottom:10px;padding-top:var(--size-4-4)}.filter-group-separator{display:flex;align-items:center;justify-content:center;margin:var(--size-2-2) 0;color:var(--text-muted);font-size:var(--font-ui-smaller)}.filter-group-separator:before,.filter-group-separator:after{content:"";flex-grow:1;height:1px;background-color:var( --background-modifier-border );margin:0 var(--size-2-1)}.drag-handle{cursor:grab;display:flex;align-items:center;justify-content:center}.compact-btn{padding:var(--size-2-1) var(--size-2-2);box-shadow:unset!important;border:unset!important;--icon-size: var(--size-4-4);display:flex;justify-content:center;-webkit-app-region:no-drag;display:inline-flex;overflow:hidden;align-items:center;color:var(--text-muted);font-size:var(--font-ui-small);border-radius:var(--button-radius);padding:var(--size-2-2);font-weight:var(--input-font-weight);cursor:var(--cursor);font-family:inherit;gap:var(--size-2-2);min-height:30px}.compact-btn:hover{box-shadow:none;opacity:var(--icon-opacity-hover);background-color:var(--background-modifier-hover);color:var(--text-normal)}.compact-input,.compact-select{font-size:var(--font-ui-smaller);height:var(--input-height);border:1px solid var(--background-modifier-border);box-shadow:none}.compact-select:hover{box-shadow:none}.compact-text{font-size:var(--font-ui-smaller)}.dragging-placeholder{opacity:.5;background-color:var( --background-modifier-hover )}.task-filter-root-container.task-popover-content{padding:var(--size-2-2);max-width:100%;max-height:100%}.task-filter-main-panel{max-width:100%;padding:var(--size-2-2);border-radius:var(--radius-m)}.filter-menu{z-index:50;min-width:600px;background-color:var(--background-primary);border-radius:var(--radius-m);box-shadow:var(--shadow-s);border:1px solid var(--background-modifier-border)}.root-filter-setup-section{display:flex;flex-direction:column;gap:.75rem}.root-condition-section{display:flex;align-items:center;gap:.5rem;padding:.5rem;background-color:var( --background-secondary-alt, var(--background-modifier-hover) );border-radius:var(--radius-m);border:1px solid var(--background-modifier-border)}.root-condition-label{font-weight:500;color:var(--text-normal)}.root-condition-select{width:auto;border:1px solid var(--input-border-color, var(--background-modifier-border))}.root-condition-select:focus{border-color:var(--interactive-accent);box-shadow:0 0 0 1px var(--interactive-accent)}.root-condition-span{color:var(--text-normal)}.filter-groups-container{display:flex;flex-direction:column;gap:var(--size-2-3);max-height:50vh;overflow:auto}.filter-group{padding:var(--size-2-3);border:1px solid var(--background-modifier-border);border-radius:var(--radius-m);background-color:var(--background-primary);display:flex;flex-direction:column;gap:var(--size-4-2)}.filter-group-header{display:flex;align-items:center;justify-content:space-between}.filter-group-header-left{display:flex;align-items:center;gap:.375rem}.filter-group-header-left .drag-handle-container .svg-icon{color:var(--text-faint)}.filter-group-header-left .drag-handle-container:hover .svg-icon{color:var(--text-muted)}.filter-group-header-left .drag-handle-container{padding-right:var(--size-2-1)}.filter-group-header-left>.compact-text,.filter-group-header-left>span.compact-text{font-weight:500;color:var(--text-normal)}.filter-group-header-left .group-condition-select.compact-select{border:1px solid var(--input-border-color, var(--background-modifier-border))}.filter-group-header-left .group-condition-select.compact-select:focus{border-color:var(--interactive-accent);box-shadow:0 0 0 1px var(--interactive-accent)}.filter-group-header-right{display:flex;align-items:center;gap:.25rem}.filter-group-header-right .duplicate-group-btn.compact-icon-btn,.filter-group-header-right .remove-group-btn.compact-icon-btn{border-radius:var(--radius-s)}.filter-group-header-right .duplicate-group-btn.compact-icon-btn .svg-icon{color:var(--text-muted)}.filter-group-header-right .duplicate-group-btn.compact-icon-btn:hover .svg-icon{color:var(--interactive-accent)}.filter-group-header-right .duplicate-group-btn.compact-icon-btn:hover{background-color:var(--background-modifier-hover)}.filter-group-header-right .remove-group-btn.compact-icon-btn .svg-icon{color:var(--text-muted)}.filter-group-header-right .remove-group-btn.compact-icon-btn:hover .svg-icon{color:var(--text-error)}.filter-group-header-right .remove-group-btn.compact-icon-btn:hover{background-color:var( --background-error-hover, var(--background-modifier-error-hover) )}.filters-list{display:flex;flex-direction:column;gap:var(--size-2-2);padding-left:1rem;border-left:2px solid var(--background-modifier-border);margin-left:var(--size-4-2)}.filters-list:empty{display:none}.group-footer{padding-left:.375rem;margin-top:.375rem}.add-filter-btn-icon{display:flex;align-items:center;justify-content:center}.filter-item{display:flex;align-items:center;gap:var(--size-2-2);padding:var(--size-4-2);padding-top:0;padding-bottom:0}.filter-item .filter-conjunction{font-size:var(--font-ui-smaller);font-weight:600;color:var(--text-faint);align-self:center}.filter-item .filter-property-select.compact-select{flex-basis:30%;flex-grow:0;flex-shrink:0;border:1px solid var(--input-border-color, var(--background-modifier-border));box-shadow:none}.filter-item .filter-property-select.compact-select:focus{border-color:var(--interactive-accent);box-shadow:0 0 0 1px var(--interactive-accent)}.filter-item .filter-condition-select.compact-select{width:auto;border:1px solid var(--input-border-color, var(--background-modifier-border));box-shadow:none}.filter-item .filter-condition-select.compact-select:focus{border-color:var(--interactive-accent);box-shadow:0 0 0 1px var(--interactive-accent)}.filter-item .filter-value-input.compact-input{flex-grow:1;border:1px solid var(--input-border-color, var(--background-modifier-border));width:100%}.filter-item .filter-value-input.compact-input:focus{border-color:var(--interactive-accent);box-shadow:0 0 0 1px var(--interactive-accent)}.filter-item .remove-filter-btn.compact-icon-btn .svg-icon{color:var(--text-muted)}.filter-item .remove-filter-btn.compact-icon-btn:hover .svg-icon{color:var(--text-error)}.filter-item .remove-filter-btn.compact-icon-btn:hover{background-color:var( --background-error-hover, var(--background-modifier-error-hover) )}.add-group-section{margin-top:var(--size-2-1);margin-bottom:var(--size-2-1);margin-left:var(--size-2-1);display:flex;justify-content:space-between}.add-filter-group-btn-icon{display:flex;align-items:center;justify-content:center}.filter-config-section{display:flex;gap:var(--size-4-2)}.save-filter-config-btn,.load-filter-config-btn{flex:1}.save-filter-config-btn-icon,.load-filter-config-btn-icon{display:flex;align-items:center;justify-content:center}.save-filter-config-btn:hover{background-color:var(--interactive-accent-hover);color:var(--text-on-accent)}.load-filter-config-btn:hover{background-color:var(--background-modifier-hover)}.filter-config-details{margin-top:var(--size-4-3);padding:var(--size-4-3);border:1px solid var(--background-modifier-border);border-radius:var(--radius-l);background:linear-gradient(135deg,var(--background-secondary) 0%,var(--background-primary-alt) 100%);box-shadow:0 2px 8px #0000001a;transition:all .2s ease-in-out}.filter-config-details:hover{box-shadow:0 4px 12px #00000026;transform:translateY(-1px)}.filter-config-details h3{margin:0 0 var(--size-4-2) 0;font-size:var(--font-ui-medium);font-weight:600;color:var(--text-accent);display:flex;align-items:center;gap:var(--size-2-2)}.filter-config-details p{margin:var(--size-2-2) 0;line-height:1.5;color:var(--text-normal)}.filter-config-meta{font-size:var(--font-ui-smaller);color:var(--text-muted);margin:var(--size-2-1) 0;padding:var(--size-2-1) var(--size-2-2);background-color:var(--background-modifier-form-field);border-radius:var(--radius-s);border-left:3px solid var(--interactive-accent)}.filter-config-summary{margin-top:var(--size-4-3);padding:var(--size-4-2) 0 0 0;border-top:2px solid var(--background-modifier-border)}.filter-config-summary h4{margin:0 0 var(--size-2-3) 0;font-size:var(--font-ui-small);font-weight:600;color:var(--text-normal);display:flex;align-items:center;gap:var(--size-2-1)}.filter-config-summary p{margin:var(--size-2-1) 0;font-size:var(--font-ui-smaller);color:var(--text-muted);padding:var(--size-2-1) var(--size-2-2);background-color:var(--background-primary-alt);border-radius:var(--radius-s)}.filter-config-buttons{margin-top:var(--size-4-3);padding-top:var(--size-4-2)}.filter-config-name-highlight{background-color:var(--text-accent);color:var(--text-on-accent);padding:.125rem .25rem;border-radius:var(--radius-s);font-weight:500}.advanced-filter-container{margin-top:var(--size-4-2);padding:var(--size-4-3);border:1px solid var(--background-modifier-border);border-radius:var(--radius-m);background-color:var(--background-secondary)}.advanced-filter-container .task-filter-root-container{background-color:transparent;border:none;padding:0}.advanced-filter-container .task-filter-main-panel{background-color:transparent;border:none;padding:0}.task-genius-view-config-modal .advanced-filter-container .filter-group{padding:var(--size-4-2);margin-bottom:var(--size-4-2)}.task-genius-view-config-modal .advanced-filter-container .filter-item{padding:var(--size-2-2);gap:var(--size-2-2)}.task-genius-view-config-modal .advanced-filter-container .compact-btn{padding:var(--size-2-1) var(--size-2-2);min-height:26px}.task-genius-view-config-modal .advanced-filter-container .compact-select,.task-genius-view-config-modal .advanced-filter-container .compact-input{font-size:var(--font-ui-smaller);height:28px}.file-filter-rules-container{margin-top:1rem;border:1px solid var(--background-modifier-border);border-radius:6px;padding:1rem;background:var(--background-secondary)}.file-filter-rule{display:flex;align-items:center;gap:1rem;padding:.75rem;margin-bottom:.5rem;border:1px solid var(--background-modifier-border);border-radius:4px;background:var(--background-primary)}.file-filter-rule:last-child{margin-bottom:0}.file-filter-rule-type,.file-filter-rule-path,.file-filter-rule-enabled{display:flex;flex-direction:column;gap:.25rem}.file-filter-rule-type{min-width:80px}.file-filter-rule-path{flex:1}.file-filter-rule-enabled{min-width:60px}.file-filter-rule label{font-size:.8rem;font-weight:500;color:var(--text-muted)}.file-filter-rule input[type=text]{padding:.25rem .5rem;border:1px solid var(--background-modifier-border);border-radius:3px;background:var(--background-primary);color:var(--text-normal);font-size:.9rem}.file-filter-rule input[type=checkbox]{width:16px;height:16px}.file-filter-rule-delete{padding:.25rem;border:none;border-radius:3px;background:var(--interactive-accent);color:var(--text-on-accent);cursor:pointer;display:flex;align-items:center;justify-content:center;min-width:28px;height:28px}.file-filter-add-rule{margin-top:1rem}.file-filter-add-rule .setting-item{border:none;padding:0}.file-filter-add-rule .setting-item-control{gap:.5rem}.file-filter-add-rule+.setting-item{border-top:none}.file-filter-stats{margin-top:1.5rem;padding:1rem;border:1px solid var(--background-modifier-border);border-radius:6px;background:var(--background-secondary)}.file-filter-stat{display:flex;justify-content:space-between;align-items:center;padding:.25rem 0}.file-filter-stat:not(:last-child){border-bottom:1px solid var(--background-modifier-border);margin-bottom:.25rem;padding-bottom:.5rem}.stat-label{font-weight:500;color:var(--text-normal)}.stat-value{font-weight:600;color:var(--interactive-accent)}.file-filter-stat.error{background-color:var(--background-modifier-error);border-left:3px solid var(--text-error)}.file-filter-stat.error .stat-label{color:var(--text-error)}.setting-item .setting-item-control button[aria-label*=refresh]{transition:transform .2s ease}.setting-item .setting-item-control button[aria-label*=refresh]:hover{transform:rotate(90deg)}@keyframes refresh-spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.setting-item .setting-item-control button[disabled] .lucide-refresh-cw{animation:refresh-spin 1s linear infinite}@media (max-width: 768px){.file-filter-rule{flex-direction:column;align-items:stretch;gap:.5rem}.file-filter-rule-type,.file-filter-rule-path,.file-filter-rule-enabled{min-width:auto}.file-filter-rule-delete{align-self:flex-end;margin-top:.5rem}}.theme-dark .file-filter-rule input[type=text]{background:var(--background-primary-alt);border-color:var(--background-modifier-border-hover)}.theme-dark .file-filter-rule input[type=text]:focus{border-color:var(--interactive-accent);box-shadow:0 0 0 2px var(--interactive-accent-hover)}.file-filter-rules-container:empty:before{content:"No filter rules configured. Add rules below to start filtering files and folders.";display:block;text-align:center;color:var(--text-muted);font-style:italic;padding:2rem}.file-filter-preset-container{margin-top:1rem;padding:1rem;border:1px solid var(--background-modifier-border);border-radius:6px;background:var(--background-secondary)}.file-filter-preset-container .setting-item{border:none;padding:.5rem 0}.file-filter-preset-container .setting-item:not(:last-child){border-bottom:1px solid var(--background-modifier-border)}.file-filter-preset-container button{position:relative;transition:all .2s ease}.file-filter-preset-container button:disabled{opacity:.6;cursor:not-allowed;background:var(--background-modifier-border);color:var(--text-muted)}.file-filter-preset-container button:not(:disabled):hover{transform:translateY(-1px);box-shadow:0 2px 8px #0000001a}.file-filter-preset-container button[disabled]{background:var(--color-green);color:var(--text-on-accent);border-color:var(--color-green)}.theme-dark .file-filter-preset-container button[disabled]{background:var(--color-green-rgb);opacity:.8}.cm-workflow-stage-indicator{display:inline-block;margin-left:4px;font-size:12px;cursor:pointer;opacity:.7;transition:opacity .2s ease;user-select:none;align-items:center;vertical-align:middle}.cm-workflow-stage-indicator span{display:inline-flex;justify-content:center;align-items:center}.cm-workflow-stage-indicator:hover{opacity:1}.cm-workflow-stage-indicator[data-stage-type=linear]{color:var(--text-accent)}.cm-workflow-stage-indicator[data-stage-type=cycle]{color:var(--task-in-progress-color)}.cm-workflow-stage-indicator[data-stage-type=terminal]{color:var(--task-completed-color)}.theme-dark .cm-workflow-stage-indicator[data-stage-type=linear]{color:var(--text-accent)}.theme-dark .cm-workflow-stage-indicator[data-stage-type=cycle]{color:var(--task-in-progress-color)}.theme-dark .cm-workflow-stage-indicator[data-stage-type=terminal]{color:var(--task-completed-color)}.date-picker-root-container{display:flex;flex-direction:column;width:100%;min-width:500px;max-width:600px}.date-picker-root-container .date-picker-main-panel{display:flex;gap:var(--size-2-3);padding:var(--size-2-3)}.date-picker-root-container .date-picker-left-panel{flex:1;min-width:200px;border-right:1px solid var(--background-modifier-border)}.date-picker-root-container .date-picker-right-panel{flex:1;min-width:250px}.date-picker-root-container .date-picker-section-title{font-size:var(--font-ui-medium);font-weight:var(--font-bold);margin-bottom:var(--size-4-2);color:var(--text-normal)}.date-picker-root-container .quick-options-container{display:flex;flex-direction:column;gap:var(--size-2-1);max-height:195px;overflow:auto;overflow-x:hidden}.date-picker-root-container .quick-option-item{display:flex;justify-content:space-between;align-items:center;padding:var(--size-2-2) var(--size-4-2);cursor:pointer;transition:background-color .2s ease}.date-picker-root-container .quick-option-item:hover{background-color:var(--background-modifier-hover)}.date-picker-root-container .quick-option-item.selected{background-color:var(--interactive-accent);color:var(--text-on-accent)}.date-picker-root-container .quick-option-item.clear-option{border-top:1px solid var(--background-modifier-border);margin-top:var(--size-2-2);padding-top:var(--size-2-3);color:var(--text-error)}.date-picker-root-container .quick-option-item.clear-option:hover{color:var(--text-on-accent);background-color:var(--background-modifier-error-hover)}.date-picker-root-container .quick-option-label{font-size:var(--font-ui-small);font-weight:var(--font-medium)}.date-picker-root-container .quick-option-date{font-size:var(--font-ui-smaller);color:var(--text-muted);font-family:var(--font-monospace)}.date-picker-root-container .quick-option-item.selected .quick-option-date{color:var(--text-on-accent)}.date-picker-root-container .calendar-container{display:flex;flex-direction:column}.date-picker-root-container .calendar-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:var(--size-4-2);padding:0 var(--size-2-2)}.date-picker-root-container .calendar-nav-btn{display:flex;align-items:center;justify-content:center;width:32px;height:32px;border-radius:var(--radius-s);cursor:pointer;transition:background-color .2s ease}.date-picker-root-container .calendar-nav-btn:hover{background-color:var(--background-modifier-hover)}.date-picker-root-container .calendar-month-year{font-size:var(--font-ui-medium);font-weight:var(--font-bold);color:var(--text-normal)}.date-picker-root-container .calendar-grid{display:grid;grid-template-columns:repeat(7,1fr);gap:1px;background-color:var(--background-modifier-border);border-radius:var(--radius-s);overflow:hidden}.date-picker-root-container .calendar-day-header{background-color:var(--background-secondary);padding:var(--size-2-2);text-align:center;font-size:var(--font-ui-smaller);font-weight:var(--font-bold);color:var(--text-muted)}.date-picker-root-container .calendar-day{background-color:var(--background-primary);padding:var(--size-2-2);text-align:center;font-size:var(--font-ui-small);cursor:pointer;transition:background-color .2s ease;min-height:32px;display:flex;align-items:center;justify-content:center}.date-picker-root-container .calendar-day:hover{background-color:var(--background-modifier-hover)}.date-picker-root-container .calendar-day.other-month{color:var(--text-faint);background-color:var(--background-secondary)}.date-picker-root-container .calendar-day.today{background-color:var(--interactive-accent-hover);color:var(--text-on-accent);font-weight:var(--font-bold)}.date-picker-root-container .calendar-day.selected{background-color:var(--interactive-accent);color:var(--text-on-accent);font-weight:var(--font-bold)}.date-picker-root-container .calendar-day.today.selected{background-color:var(--interactive-accent);box-shadow:inset 0 0 0 2px var(--text-on-accent)}.date-picker-popover.tg-menu{z-index:20;position:fixed;background-color:var(--background-primary);border:1px solid var(--background-modifier-border);border-radius:var(--radius-s);box-shadow:var(--shadow-l);max-height:80vh;overflow:auto}.date-picker-popover.tg-menu .date-picker-popover-content{padding:0}@media (max-width: 768px){.date-picker-root-container .date-picker-main-panel{flex-direction:column;gap:var(--size-4-2)}.date-picker-root-container .date-picker-left-panel{border-right:none;border-bottom:1px solid var(--background-modifier-border);padding-right:0;padding-bottom:var(--size-4-2)}.date-picker-root-container{min-width:300px;max-width:400px}.date-picker-root-container .calendar-day{min-height:40px;font-size:var(--font-ui-medium)}}.date-picker-root-container .date-picker-widget-error{color:var(--text-error);background-color:var(--background-modifier-error);padding:var(--size-2-1) var(--size-2-2);border-radius:var(--radius-s);font-size:var(--font-ui-smaller)}.quick-capture-panel{padding:var(--size-4-2);background-color:var(--background-primary);border-top:1px solid var(--background-modifier-border);display:flex;flex-direction:column;gap:var(--size-4-2)}.quick-capture-modal.minimal{max-width:600px;min-width:500px;max-height:200px}.quick-capture-minimal-editor-container{padding:var(--size-4-2);min-height:50px}.quick-capture-minimal-editor-container .cm-editor{font-size:var(--font-text-size);min-height:40px;border:1px solid var(--background-modifier-border);border-radius:var(--radius-s);padding:var(--size-2-1)}.quick-capture-minimal-editor-container .cm-editor.cm-focused{border-color:var(--interactive-accent);box-shadow:0 0 0 2px var(--interactive-accent-alpha)}.quick-capture-minimal-buttons{display:flex;justify-content:space-between;align-items:center;padding:var(--size-4-2)}.quick-actions-left{display:flex;gap:var(--size-2-1)}.quick-actions-right{display:flex;gap:var(--size-2-1)}.quick-action-button.active{background-color:var(--interactive-accent);color:var(--text-on-accent);border-color:var(--interactive-accent)}.quick-action-save{padding:var(--size-2-1) var(--size-4-2);min-width:80px;height:32px;border-radius:var(--radius-s)}.quick-capture-tag-input{position:absolute;bottom:60px;left:50%;transform:translate(-50%);width:300px;padding:var(--size-2-1);border:1px solid var(--background-modifier-border);border-radius:var(--radius-s);background-color:var(--background-primary);color:var(--text-normal);font-size:var(--font-text-size);z-index:1000}.minimal-quick-capture-suggestion{padding:var(--size-2-1) var(--size-4-2);border-radius:var(--radius-s);cursor:pointer;transition:background-color .2s ease;min-height:40px;display:flex;align-items:center}.minimal-quick-capture-suggestion:hover{background-color:var(--background-modifier-hover)}.minimal-quick-capture-suggestion.is-selected{background-color:var(--interactive-accent);color:var(--text-on-accent)}.minimal-quick-capture-suggestion.is-selected .suggestion-label{color:var(--text-on-accent)}.minimal-quick-capture-suggestion.is-selected .suggestion-description{color:var(--text-on-accent);opacity:.8}.suggestion-icon{font-size:16px;min-width:20px;text-align:center}.suggestion-content{flex:1}.suggestion-label{font-size:var(--font-text-size);font-weight:500;color:var(--text-normal)}.suggestion-description{font-size:var(--font-ui-small);color:var(--text-muted);margin-top:2px}.quick-capture-header-container{display:flex;align-items:center;margin-bottom:var(--size-4-2);gap:var(--size-4-2);font-size:var(--font-ui-medium);font-weight:bold;color:var(--text-normal);padding:var(--size-2-1) var(--size-4-2)}.quick-capture-title{color:var(--text-normal);white-space:nowrap}.quick-capture-target{flex:1;border-radius:var(--radius-s);color:var(--text-accent);font-size:var(--font-text-size);font-weight:normal;min-width:100px;max-width:500px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.quick-capture-target:focus{outline:none}.quick-capture-hint{font-size:12px;color:var(--text-muted);margin-bottom:8px;margin-top:-4px;text-align:right}.quick-capture-editor{min-height:200px;background-color:var(--background-primary)}.quick-capture-file-suggest{max-width:500px}.quick-capture-buttons{display:flex;justify-content:flex-end;gap:8px}.quick-capture-submit,.quick-capture-cancel{padding:6px 12px;border-radius:4px;cursor:pointer}.quick-capture-submit{background-color:var(--interactive-accent);color:var(--text-on-accent)}.quick-capture-cancel{background-color:var(--background-modifier-border);color:var(--text-normal)}.quick-capture-modal .modal-title{display:flex;align-items:center;flex-direction:row;gap:10px;font-size:var(--font-ui-medium);font-weight:bold}.quick-capture-modal-editor{min-height:150px;margin-bottom:20px}.quick-capture-modal-buttons{display:flex;justify-content:flex-end;gap:10px}.quick-capture-modal.full{width:80vw;max-width:900px}.quick-capture-layout{display:flex;height:100%;gap:16px;margin-bottom:16px}.quick-capture-config-panel{flex:1;border-right:1px solid var(--background-modifier-border);padding-right:16px;overflow-y:auto;max-width:40%}.quick-capture-editor-panel{flex:1.5;display:flex;flex-direction:column}.quick-capture-section-title{font-weight:bold;margin-bottom:8px;font-size:var(--font-ui-medium);color:var(--text-normal)}.quick-capture-target-container{margin-bottom:16px}.quick-capture-modal.full .quick-capture-modal-editor{min-height:200px;flex:1;overflow-y:auto;border:1px solid var(--background-modifier-border);border-radius:var(--radius-s);padding:8px;margin-top:8px}@media (max-width: 768px){.quick-capture-modal.full{width:95vw}.quick-capture-layout{flex-direction:column}.quick-capture-config-panel{max-width:100%;border-right:none;border-bottom:1px solid var(--background-modifier-border);padding-right:0;padding-bottom:16px;margin-bottom:16px;max-height:40%}}.quick-capture-config-panel .details-status-selector{display:flex;flex-direction:row;justify-content:space-between;margin-bottom:var(--size-4-2);margin-top:var(--size-4-2)}.quick-capture-config-panel .quick-capture-status-selector{display:flex;flex-direction:row;justify-content:space-between;gap:var(--size-4-3)}.task-list{flex:1;overflow-y:auto;padding:0}.task-item{display:flex;align-items:flex-start;padding:8px 16px;border-bottom:1px solid var(--background-modifier-border);cursor:pointer;gap:var(--size-2-3)}.task-item:hover{background-color:var(--background-secondary-alt)}.task-children-container .task-item:hover{background-color:var(--background-secondary)}.task-item.selected{background-color:var(--background-secondary-alt)}.task-item.task-completed .task-item-content{text-decoration:line-through;color:var(--text-muted)}.task-item .markdown-block.markdown-renderer>p:only-child{padding:0;margin:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.task-checkbox{width:16px;height:16px;display:flex;align-items:center;justify-content:center;color:var(--text-normal);cursor:pointer;flex-shrink:0}.task-item.task-completed .task-checkbox{color:var(--text-on-accent)}.task-item-content{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.task-item-container{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.task-item-metadata{display:flex;align-items:center;gap:var(--size-4-2);margin-top:var(--size-2-2)}.task-item-metadata:empty{display:none}.task-date{font-size:var(--font-ui-small);color:var(--text-faint);white-space:nowrap;background-color:var(--background-modifier-active-hover);padding:var(--size-2-1) var(--size-2-3);border-radius:var(--radius-s);opacity:.8}.task-item:hover .task-date{opacity:1}.task-date:before{display:inline-block;margin-right:var(--size-2-2);font-size:xx-small;display:inline-flex;transform:translateY(-1px)}.tg-kanban-view .task-date:before{transform:translateY(0)}.task-date.task-due-date:before{content:"\1f4c5"}.task-date.task-overdue{color:var(--text-error);font-weight:600}.task-date.task-due-today{color:var(--task-doing-color);font-weight:600}.task-date.task-due-soon{color:var(--text-warning);font-weight:600}.task-date.task-start-date:before{content:"\1f6eb"}.task-date.task-created-date:before{content:"\2795"}.task-date.task-scheduled-date:before{content:"\23f3"}.task-date.task-done-date:before{content:"\2705"}.task-date.task-cancelled-date:before{content:"\274c"}.task-date.task-recurrence:before{content:"\1f501"}.task-date.task-on-completion:before{content:"\1f3c1"}.task-project{font-size:var(--font-ui-small);color:var(--text-on-accent);background-color:var(--color-accent);border-radius:var(--radius-s);padding:var(--size-2-1) var(--size-2-3);white-space:nowrap;opacity:.5}.task-project:has(input){background-color:var(--background-modifier-active-hover);color:var(--text-normal)}.task-item:hover .task-project{opacity:1}.task-project:before{content:"\1f5c2\fe0f";margin-right:var(--size-4-2);display:inline-flex;align-items:center;justify-content:center;font-size:var(--font-ui-small)}.task-project:hover{background-color:var(--background-modifier-active-hover);color:var(--text-accent-hover)}.task-priority{margin-left:8px;font-size:.9em;white-space:nowrap}.task-priority.priority-5{color:var(--text-error);font-weight:600}.task-priority.priority-4{color:var(--text-warning);font-weight:600}.task-priority.priority-3{color:var(--text-warning);font-weight:600}.task-priority.priority-2{color:var(--text-warning)}.task-priority.priority-1{color:var(--text-accent)}.task-oncompletion{display:inline-flex;align-items:center;padding:2px 6px;margin-left:4px;border-radius:3px;font-size:var(--font-ui-small);color:var(--text-muted);white-space:nowrap}.task-oncompletion:hover{color:var(--text-normal)}.task-dependson{display:inline-flex;align-items:center;padding:2px 6px;margin-left:4px;background-color:var(--background-modifier-error);border-radius:3px;font-size:var(--font-ui-small);color:var(--text-error);white-space:nowrap}.task-dependson:hover{background-color:var(--background-modifier-error-hover);color:var(--text-error)}.task-id{display:inline-flex;align-items:center;padding:2px 6px;margin-left:4px;background-color:var(--background-modifier-accent);border-radius:3px;font-size:var(--font-ui-small);color:var(--text-accent);white-space:nowrap}.task-id:hover{background-color:var(--background-modifier-accent-hover);color:var(--text-accent-hover)}.task-tags-container{display:flex;flex-wrap:wrap;gap:var(--size-2-2)}.task-tags-container:empty{display:none}.task-tag{font-size:var(--font-ui-small);color:var(--text-normal);background-color:var(--background-modifier-hover);border-radius:var(--radius-s);padding:var(--size-2-1) var(--size-2-3);white-space:nowrap;opacity:.75}.task-item:hover .task-tag{opacity:1}.task-item-content p:has(img) img{display:block;width:min(50%,200px)}.inline-editor{position:relative;display:inline-block;width:100%}.inline-content-editor{width:100%;min-height:18px;border:none;border-bottom:1px solid var(--interactive-accent);border-radius:0;padding:2px 4px;background-color:transparent;color:var(--text-normal);font-family:inherit;font-size:inherit;line-height:inherit;resize:none;outline:none;transition:border-color .15s ease,background-color .15s ease}.inline-content-editor:focus{border-bottom-color:var(--interactive-accent-hover);background-color:var(--background-primary-alt);box-shadow:0 1px 0 0 var(--interactive-accent-hover)}.inline-embedded-editor-container{width:100%;min-height:18px;border:none;border-radius:0;background-color:transparent}.inline-embedded-editor{width:100%;min-height:18px;background-color:transparent}.inline-embedded-editor .cm-editor{border:none!important;outline:none!important;background-color:transparent!important;border-bottom:1px solid var(--interactive-accent)!important}.inline-embedded-editor .cm-focused{outline:none!important;border-bottom-color:var(--interactive-accent-hover)!important;background-color:var(--background-primary-alt)!important}.inline-embedded-editor .cm-content{padding:2px 4px;min-height:18px;font-family:inherit;font-size:inherit;line-height:inherit}.inline-embedded-editor .cm-line{padding:0}.inline-metadata-editor{display:inline-flex;align-items:center;gap:4px;padding:2px 6px;background-color:var(--background-primary-alt);border:1px solid var(--interactive-accent);border-radius:var(--radius-s);box-shadow:0 1px 3px #0000001a;min-width:120px;max-width:300px;position:relative;z-index:100}.inline-metadata-editor input{border:unset;outline:unset;padding:0;height:var(--line-height);background-color:transparent;background:transparent;border-radius:var(--radius-s)}.inline-metadata-editor input:focus{outline:unset;padding:0;background-color:transparent}.inline-metadata-editor:has(input){outline:unset;border:0;padding:0;background-color:transparent;border-radius:unset}.inline-project-input,.inline-tags-input,.inline-context-input,.inline-date-input,.inline-recurrence-input{flex:1;padding:2px 4px;border:none;border-radius:2px;background-color:transparent;color:var(--text-normal);font-family:inherit;font-size:var(--font-ui-small);outline:none;min-width:80px;transition:background-color .15s ease}.inline-project-input:focus,.inline-tags-input:focus,.inline-context-input:focus,.inline-date-input:focus,.inline-recurrence-input:focus{background-color:var(--background-primary);box-shadow:inset 0 0 0 1px var(--interactive-accent)}.inline-priority-select{padding:2px 4px;border:none;border-radius:2px;background-color:transparent;color:var(--text-normal);font-family:inherit;font-size:var(--font-ui-small);outline:none;cursor:pointer;min-width:80px}.inline-priority-select:focus{background-color:var(--background-primary);box-shadow:inset 0 0 0 1px var(--interactive-accent)}.add-metadata-container{display:inline-flex;align-items:center;margin-left:4px}.task-list .task-item:not(.tree-task-item):hover .add-metadata-btn{opacity:1}.tree-task-item .task-item-container:hover .add-metadata-btn{opacity:1}.add-metadata-btn{display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;border:none;border-radius:2px;background-color:var(--background-secondary);color:var(--text-muted);cursor:pointer;transition:all .15s ease;--icon-size: 10px;opacity:0;padding:0;margin:0}.add-metadata-btn:hover{background-color:var(--background-modifier-hover);color:var(--text-normal);opacity:1}.add-metadata-btn:active{background-color:var(--background-modifier-active);transform:scale(.95)}.add-metadata-btn svg{width:10px;height:10px}.inline-editor *{transition:border-color .15s ease,background-color .15s ease,box-shadow .15s ease}.inline-editor input:focus,.inline-editor textarea:focus,.inline-editor select:focus{outline:none}.task-item-metadata .task-date,.task-item-metadata .task-project,.task-item-metadata .task-tag{cursor:pointer;transition:background-color .15s ease,transform .15s ease;position:relative}.task-item-metadata .task-date:hover,.task-item-metadata .task-project:hover,.task-item-metadata .task-tag:hover{background-color:var(--background-modifier-hover);transform:none}.task-item-metadata .task-date:hover:after,.task-item-metadata .task-project:hover:after,.task-item-metadata .task-tag:hover:after{display:none}.task-item-content{cursor:pointer;transition:background-color .15s ease}.inline-metadata-editor{animation:fadeInScale .15s ease-out}@keyframes fadeInScale{0%{opacity:0;transform:scale(.95)}to{opacity:1;transform:scale(1)}}.inline-editor-placeholder{min-height:1em;display:inline-block}@media (max-width: 768px){.inline-project-input,.inline-tags-input,.inline-context-input,.inline-recurrence-input{min-width:100px;font-size:var(--font-ui-smaller)}.inline-metadata-editor{max-width:250px}}@media (prefers-contrast: high){.inline-content-editor,.inline-embedded-editor .cm-editor{border-bottom-width:2px}.inline-metadata-editor{border-width:2px}}@media (prefers-reduced-motion: reduce){.inline-editor *,.task-item-metadata .task-date,.task-item-metadata .task-project,.task-item-metadata .task-tag,.task-item-content,.add-metadata-btn{transition:none}.inline-metadata-editor{animation:none}}.inline-dependson-input,.inline-id-input{width:100%;min-width:200px;padding:4px 8px;border:1px solid var(--background-modifier-border);border-radius:4px;background-color:var(--background-primary);color:var(--text-normal);font-family:inherit;font-size:var(--font-ui-small);outline:none;transition:border-color .15s ease,box-shadow .15s ease}.inline-dependson-input:focus,.inline-id-input:focus{border-color:var(--interactive-accent);box-shadow:0 0 0 2px var(--interactive-accent-hover)}.inline-dependson-input::placeholder,.inline-id-input::placeholder{color:var(--text-faint)}.tree-task-item{position:relative;display:flex;flex-direction:column;padding:8px 16px;transition:background-color .2s ease}.task-children-container .task-item.tree-task-item{border-bottom:unset;padding-top:var(--size-2-2);padding-bottom:var(--size-2-2);gap:0}.task-item.tree-task-item{gap:0}.tree-task-item:hover{background-color:var(--background-secondary-alt)}.tree-task-item.selected{background-color:var(--background-modifier-active)}.tree-task-item.completed{opacity:.7}.tree-task-item>div:first-of-type{width:100%;display:flex;align-items:flex-start;gap:6px}.task-indent{flex-shrink:0}.task-item.tree-task-item .task-expand-toggle{padding-top:var(--size-2-2)}.task-item .task-checkbox{padding-top:var(--size-2-2)}.task-expand-toggle{cursor:pointer;display:flex;align-items:center;justify-content:center;width:16px;height:16px;flex-shrink:0;color:var(--text-muted)}.task-expand-toggle:hover{color:var(--text-normal)}.task-item.tree-task-item .task-checkbox{cursor:pointer;flex-shrink:0;color:var(--text-muted);width:16px;height:16px;display:flex;align-items:center;justify-content:center}.task-item.tree-task-item .task-checkbox:hover{color:var(--text-accent)}.task-item.tree-task-item .task-checkbox.checked{color:var(--text-accent)}.task-content{flex-grow:1;line-height:1.4}.tree-task-item.completed .task-content{text-decoration:line-through;color:var(--text-muted)}.task-metadata{display:flex;gap:8px;margin-top:4px;font-size:.85em;color:var(--text-muted)}.task-metadata:empty{display:none}.task-due-date.overdue{color:var(--text-error);font-weight:bold}.task-item.tree-task-item .task-project{display:inline-block;padding:1px 6px;border-radius:4px}.task-priority.priority-3{color:var(--text-error)}.task-priority.priority-2{color:var(--text-warning)}.task-priority.priority-1{color:var(--text-accent)}.tree-task-item .task-oncompletion{display:inline-flex;align-items:center;padding:2px 6px;margin-left:4px;background-color:var(--background-modifier-border);border-radius:3px;font-size:var(--font-ui-small);color:var(--text-muted);white-space:nowrap}.tree-task-item .task-oncompletion:hover{color:var(--text-normal)}.tree-task-item .task-dependson{display:inline-flex;align-items:center;padding:2px 6px;margin-left:4px;background-color:var(--background-modifier-error);border-radius:3px;font-size:var(--font-ui-small);color:var(--text-error);white-space:nowrap}.tree-task-item .task-dependson:hover{background-color:var(--background-modifier-error-hover);color:var(--text-error)}.tree-task-item .task-id{display:inline-flex;align-items:center;padding:2px 6px;margin-left:4px;background-color:var(--background-modifier-accent);border-radius:3px;font-size:var(--font-ui-small);color:var(--text-accent);white-space:nowrap}.tree-task-item .task-id:hover{background-color:var(--background-modifier-accent-hover);color:var(--text-accent-hover)}.task-children-container{margin-top:4px;width:100%}.view-toggle-btn{cursor:pointer;display:flex;align-items:center;justify-content:center;width:24px;height:24px;color:var(--text-muted);border-radius:4px}.view-toggle-btn:hover{background-color:var(--background-modifier-hover);color:var(--text-normal)}.task-children-container:empty{display:none!important}.forecast-container{display:flex;flex-direction:column;height:100%;overflow:hidden;flex:1}.forecast-header{display:flex;justify-content:space-between;align-items:center;padding:15px;border-bottom:1px solid var(--background-modifier-border)}.forecast-title-container{display:flex;flex-direction:column}.forecast-title{font-weight:600;font-size:1.2em}.forecast-count{font-size:.8em;color:var(--text-muted);margin-top:4px}.forecast-actions{display:flex;gap:var(--size-4-2);align-items:center;justify-content:center}.forecast-settings{cursor:pointer;opacity:.7;transition:opacity .2s ease;display:flex;align-items:center;justify-content:center}.forecast-settings:hover{opacity:1}.forecast-focus-bar{display:flex;padding:10px 15px;border-bottom:1px solid var(--background-modifier-border);gap:10px;align-items:center}.focus-input{flex:1;padding:6px 12px;border-radius:4px;border:1px solid var(--interactive-accent);background-color:var(--background-primary);color:var(--text-normal)}.unfocus-button{padding:6px 12px;border-radius:4px;background-color:var(--interactive-accent);color:var(--text-on-accent);cursor:pointer;border:none}.unfocus-button:hover{background-color:var(--interactive-accent-hover)}.forecast-content{display:flex;flex:1;overflow:hidden}.forecast-left-column{width:360px;min-width:360px;border-right:1px solid var(--background-modifier-border);display:flex;flex-direction:column;overflow-y:auto;background-color:var(--background-secondary-alt)}.forecast-right-column{flex:1;display:flex;flex-direction:column;background-color:var(--background-primary)}.forecast-task-list{overflow-y:auto}.forecast-calendar-section{padding:10px 0;margin-top:var(--size-4-4);flex-shrink:0;border-top:1px solid var(--background-modifier-border)}.forecast-stats{display:flex}.stat-item{flex:1;display:flex;flex-direction:column;align-items:center;padding:10px;cursor:pointer;transition:background-color .2s ease;position:relative}.stat-item:after{content:"";position:absolute;bottom:0;left:10%;width:80%;height:3px;background-color:transparent;transition:background-color .2s ease}.stat-item:hover{background-color:var(--background-modifier-hover)}.stat-item.active:after{background-color:var(--interactive-accent);animation:color-pulse 1.5s infinite alternate}@keyframes color-pulse{0%{background-color:var(--color-accent-1)!important;opacity:.7}to{background-color:var(--color-accent-2)!important;opacity:1}}.stat-item.tg-past-due:after{background-color:var(--text-error);opacity:.7}.stat-item.tg-today:after{background-color:var(--interactive-accent);opacity:.7}.stat-item.tg-future:after{background-color:var(--text-accent);opacity:.7}.stat-count{font-size:1.5em;font-weight:600}.stat-item.tg-past-due .stat-count{color:var(--text-error)}.stat-label{font-size:.8em;color:var(--text-muted)}.forecast-due-soon-section{display:flex;flex-direction:column;padding-bottom:var(--size-4-3)}.due-soon-header{font-size:.8em;font-weight:600;padding:5px 15px;color:var(--text-muted);text-transform:uppercase;letter-spacing:.05em}.due-soon-item{display:flex;justify-content:space-between;padding:8px 15px;cursor:pointer;border-left:3px solid transparent;transition:background-color .2s ease}.due-soon-item:hover{background-color:var(--background-modifier-hover);border-left-color:var(--interactive-accent)}.due-soon-date{font-size:.9em}.due-soon-count{font-size:.8em;background-color:var(--background-modifier-border);padding:2px 6px;border-radius:10px;color:var(--text-muted)}.due-soon-empty{text-align:center;padding:15px;color:var(--text-muted);font-style:italic;font-size:.9em}.date-section-header{display:flex;align-items:center;padding:8px 15px;cursor:pointer;border-bottom:1px solid var(--background-modifier-border);background-color:var(--background-secondary-alt)}.date-section-header .section-toggle{margin-right:8px;display:flex;align-items:center;justify-content:center}.date-section-header .section-title{flex:1;font-weight:500}.date-section-header .section-count{font-size:.8em;color:var(--text-muted);background-color:var(--background-modifier-border);border-radius:10px;height:var(--size-4-5);width:var(--size-4-5);display:inline-flex;align-items:center;justify-content:center}.task-date-section.overdue .date-section-header{border-left:3px solid var(--text-error)}.task-date-section.overdue .section-title{color:var(--text-error)}.task-date-section.overdue .section-count{background-color:var(--text-error);color:#fff}.section-tasks{display:flex;flex-direction:column}.forecast-empty-state{display:flex;height:100px;align-items:center;justify-content:center;color:var(--text-muted);font-style:italic}.forecast-sidebar-toggle{position:absolute}.is-phone .forecast-header:has(.forecast-sidebar-toggle) .forecast-title-container{padding-left:var(--size-4-10)}.is-phone .forecast-container{position:relative;overflow:hidden}.is-phone .forecast-left-column{position:absolute;left:0;top:0;height:100%;z-index:10;background-color:var(--background-secondary);width:100%;transform:translate(-100%);transition:transform .3s ease-in-out;border-right:1px solid var(--background-modifier-border)}.is-phone .forecast-left-column.is-visible{transform:translate(0)}.is-phone .forecast-sidebar-toggle{display:flex;align-items:center;justify-content:center;margin-right:8px}.is-phone .forecast-sidebar-close{position:absolute;top:10px;right:10px;z-index:15;width:28px;height:28px;display:flex;align-items:center;justify-content:center;border-radius:50%;background-color:var(--background-primary);box-shadow:0 2px 4px #0000001a}.is-phone .task-genius-container:has(.forecast-left-column.is-visible):before{content:"";position:absolute;top:0;left:0;width:100%;height:100%;background-color:var(--background-modifier-cover);opacity:.5;z-index:5;transition:opacity .3s ease-in-out}.task-genius-view .mini-calendar-container{display:flex;flex-direction:column;width:100%;border-bottom:1px solid var(--background-modifier-border);padding-bottom:10px}.task-genius-view .mini-calendar-container .calendar-header{display:flex;justify-content:space-between;align-items:center;padding:8px 15px;margin-bottom:8px}.task-genius-view .mini-calendar-container .calendar-title{font-weight:600;display:flex;gap:5px}.task-genius-view .mini-calendar-container .calendar-month{margin-right:5px}.task-genius-view .mini-calendar-container .calendar-year{color:var(--text-muted)}.task-genius-view .mini-calendar-container .calendar-nav{display:flex;align-items:center;gap:8px}.task-genius-view .mini-calendar-container .calendar-nav-btn{display:flex;align-items:center;justify-content:center;width:24px;height:24px;border-radius:4px;background-color:var(--background-modifier-hover);cursor:pointer;opacity:.7;transition:opacity .2s ease}.task-genius-view .mini-calendar-container .calendar-nav-btn:hover{opacity:1;background-color:var(--background-modifier-border-hover)}.task-genius-view .mini-calendar-container .calendar-today-btn{padding:2px 8px;border-radius:4px;background-color:var(--background-modifier-hover);cursor:pointer;font-size:.8em;transition:background-color .2s ease}.task-genius-view .mini-calendar-container .calendar-today-btn:hover{background-color:var(--background-modifier-border-hover)}.task-genius-view .mini-calendar-container .calendar-grid{display:grid;grid-template-columns:repeat(7,1fr);gap:1px;padding:0 10px}.task-genius-view .mini-calendar-container .calendar-day-header{text-align:center;font-size:.8em;color:var(--text-muted);padding:3px 0;border-bottom:1px solid var(--background-modifier-border);margin-bottom:3px}.task-genius-view .mini-calendar-container .calendar-day-header.calendar-weekend{color:var(--text-accent)}.task-genius-view .mini-calendar-container.hide-weekends .calendar-grid{grid-template-columns:repeat(5,1fr)}.task-genius-view .mini-calendar-container .calendar-day{border-radius:4px;padding:1px;cursor:pointer;position:relative;display:flex;flex-direction:column;transition:background-color .2s ease;height:auto;min-height:var(--size-4-12)}.task-genius-view .mini-calendar-container .calendar-day:hover{background-color:var(--background-modifier-hover)}.task-genius-view .mini-calendar-container .calendar-day.selected{background-color:var(--background-modifier-border-hover)}.task-genius-view .mini-calendar-container .calendar-day.today{background-color:var(--interactive-accent-hover);color:var(--text-on-accent)}.task-genius-view .mini-calendar-container .calendar-day.past-due{color:var(--text-error)}.task-genius-view .mini-calendar-container .calendar-day.other-month{opacity:.5}.task-genius-view .mini-calendar-container .calendar-day-number{text-align:center;font-size:.9em;font-weight:500;padding:1px}.task-genius-view .mini-calendar-container .calendar-day-count{background-color:var(--background-modifier-border);color:var(--text-normal);border-radius:8px;font-size:.7em;padding:1px 4px;margin:1px auto 0;text-align:center;width:fit-content}.task-genius-view .mini-calendar-container .calendar-day-count.has-priority{background-color:var(--text-accent);color:var(--text-on-accent)}@media (max-width: 1400px){.task-genius-container:has(.task-details.visible) .mini-calendar-container .forecast-left-column{display:none}}.tags-container{display:flex;flex-direction:column;height:100%;width:100%;overflow:hidden;flex:1}.task-genius-view:has(.task-details.visible) .tags-left-column{display:none}.tags-content{display:flex;flex-direction:row;flex:1;overflow:hidden}.multi-select-mode .tags-multi-select-btn{color:var(--color-accent)}.tags-left-column{width:max(120px,30%);min-width:min(120px,30%);max-width:400px;display:flex;flex-direction:column;border-right:1px solid var(--background-modifier-border);overflow:hidden}.tags-right-column{flex:1;display:flex;flex-direction:column;overflow:hidden}.tags-sidebar-header{display:flex;justify-content:space-between;align-items:center;padding:var(--size-4-2) var(--size-4-4);border-bottom:1px solid var(--background-modifier-border);height:var(--size-4-10)}.tags-sidebar-title{font-weight:600;font-size:14px}.tags-multi-select-btn{cursor:pointer;color:var(--text-muted);display:flex;align-items:center;justify-content:center}.tags-multi-select-btn:hover{color:var(--text-normal)}.tags-sidebar-list{flex:1;overflow-y:auto;padding:var(--size-4-2);display:flex;flex-direction:column;gap:var(--size-2-1)}.tag-list-item{display:flex;align-items:center;padding:4px 12px;cursor:pointer;position:relative;border-radius:var(--radius-s)}.tag-list-item:hover{background-color:var(--background-modifier-hover)}.tag-list-item.selected{background-color:var(--background-modifier-active)}.tag-indent{flex-shrink:0}.tag-icon{margin-right:var(--size-2-2);color:var(--text-muted);display:flex;--icon-size: var(--size-4-4)}.tag-name{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tag-count{margin-left:8px;font-size:.8em;color:var(--text-muted);background-color:var(--background-modifier-border);border-radius:10px;padding:1px 6px}.tag-children{width:100%}.tags-task-header{display:flex;justify-content:space-between;align-items:center;padding:var(--size-4-2) var(--size-4-4);border-bottom:1px solid var(--background-modifier-border);height:var(--size-4-10)}.tags-task-title{font-weight:600;font-size:16px}.tags-task-count{color:var(--text-muted)}.tags-task-list{flex:1;overflow-y:auto}.tags-empty-state{display:flex;align-items:center;justify-content:center;height:100%;color:var(--text-muted);font-style:italic;padding:16px}.tag-section-header{display:flex;align-items:center;padding:8px 15px;cursor:pointer;border-bottom:1px solid var(--background-modifier-border);background-color:var(--background-secondary-alt)}.tag-section-header .section-toggle{margin-right:8px;display:flex;align-items:center;justify-content:center}.tag-section-header .section-title{flex:1;font-weight:500}.tag-section-header .section-count{font-size:.8em;color:var(--text-muted);background-color:var(--background-modifier-border);padding:2px 6px;border-radius:10px;height:var(--size-4-5);width:var(--size-4-5)}.is-phone .tags-container{position:relative;overflow:hidden}.is-phone .tags-left-column{position:absolute;left:0;top:0;height:100%;z-index:10;background-color:var(--background-secondary);width:100%;transform:translate(-100%);transition:transform .3s ease-in-out;border-right:1px solid var(--background-modifier-border)}.is-phone .tags-left-column.is-visible{transform:translate(0)}.is-phone .tags-sidebar-toggle{display:flex;align-items:center;justify-content:center;margin-right:8px}.is-phone .tags-sidebar-close{--icon-size: var(--size-4-4);position:absolute;top:var(--size-4-2);right:10px;z-index:15;display:flex;align-items:center;justify-content:center}.is-phone .tags-container:has(.tags-left-column.is-visible):before{content:"";position:absolute;top:0;left:0;width:100%;height:100%;background-color:var(--background-modifier-cover);opacity:.5;z-index:5;transition:opacity .3s ease-in-out}.is-phone .tags-sidebar-header:has(.tags-sidebar-close){padding-right:var(--size-4-12)}.projects-container{display:flex;flex-direction:column;height:100%;width:100%;overflow:hidden}.projects-content{display:flex;flex-direction:row;flex:1;overflow:hidden}.projects-left-column{width:max(120px,30%);min-width:min(120px,30%);max-width:300px;display:flex;flex-direction:column;border-right:1px solid var(--background-modifier-border);overflow:hidden}.is-phone .projects-left-column{max-width:100%}.projects-right-column{flex:1;display:flex;flex-direction:column;overflow:hidden}.projects-sidebar-header{display:flex;justify-content:space-between;align-items:center;padding:var(--size-4-2) var(--size-4-4);border-bottom:1px solid var(--background-modifier-border);height:var(--size-4-10)}.projects-sidebar-title{font-weight:600;font-size:14px}.multi-select-mode .projects-multi-select-btn{color:var(--color-accent)}.projects-multi-select-btn{cursor:pointer;color:var(--text-muted);display:flex;align-items:center;justify-content:center}.projects-multi-select-btn:hover{color:var(--text-normal)}.projects-sidebar-list{flex:1;overflow-y:auto;padding:var(--size-4-2)}.project-list-item{display:flex;align-items:center;padding:4px 12px;cursor:pointer;border-radius:var(--radius-s)}.project-list-item:hover{background-color:var(--background-modifier-hover)}.project-list-item.selected{background-color:var(--background-modifier-active)}.project-icon{margin-right:8px;color:var(--text-muted);display:flex;align-items:center;justify-content:center}.project-name{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.project-count{margin-left:8px;font-size:.8em;color:var(--text-muted);background-color:var(--background-modifier-border);border-radius:10px;padding:1px 6px}.projects-task-header{display:flex;justify-content:space-between;align-items:center;padding:var(--size-4-2) var(--size-4-4);border-bottom:1px solid var(--background-modifier-border);height:var(--size-4-10)}.projects-task-title{font-weight:600;font-size:16px}.projects-task-count{color:var(--text-muted)}.projects-task-list{flex:1;overflow-y:auto}.projects-empty-state{display:flex;align-items:center;justify-content:center;height:100%;color:var(--text-muted);font-style:italic;padding:16px}.is-phone .projects-left-column{position:absolute;left:0;top:0;height:100%;z-index:10;background-color:var(--background-secondary);width:100%;transform:translate(-100%);transition:transform .3s ease-in-out;border-right:1px solid var(--background-modifier-border)}.is-phone .projects-left-column.is-visible{transform:translate(0)}.is-phone .projects-sidebar-toggle{display:flex;align-items:center;justify-content:center;margin-right:8px}.is-phone .projects-sidebar-close{--icon-size: var(--size-4-4);position:absolute;top:var(--size-4-2);right:10px;z-index:15;display:flex;align-items:center;justify-content:center}.is-phone .projects-container:has(.projects-left-column.is-visible):before{content:"";position:absolute;top:0;left:0;width:100%;height:100%;background-color:var(--background-modifier-cover);opacity:.5;z-index:5;transition:opacity .3s ease-in-out}.is-phone .projects-container{position:relative;overflow:hidden}.is-phone .projects-sidebar-header:has(.projects-sidebar-close){padding-right:var(--size-4-12)}.review-container{display:flex;flex-direction:column;height:100%;width:100%;overflow:hidden}.review-content{display:flex;flex-direction:row;flex:1;overflow:hidden}.review-left-column{width:250px;min-width:200px;max-width:300px;display:flex;flex-direction:column;border-right:1px solid var(--background-modifier-border);overflow:hidden}.is-phone .review-left-column{max-width:100%}.review-right-column{flex:1;display:flex;flex-direction:column;overflow:hidden}.review-sidebar-header{display:flex;justify-content:space-between;align-items:center;padding:var(--size-4-2) var(--size-4-4);border-bottom:1px solid var(--background-modifier-border);height:var(--size-4-10)}.review-sidebar-title{font-weight:600;font-size:14px}.review-multi-select-btn{cursor:pointer;color:var(--text-muted);display:flex;align-items:center;justify-content:center}.review-multi-select-btn:hover{color:var(--text-normal)}.review-sidebar-list{flex:1;overflow-y:auto;padding:var(--size-4-2)}.review-projects-group-header{font-size:10px;font-weight:600;color:var(--text-faint);text-transform:uppercase;padding:4px 8px;margin-top:12px;letter-spacing:.5px}.review-projects-group-header:first-child{margin-top:4px}.review-project-item{--icon-size: var(--size-4-4);display:flex;align-items:center;padding:4px 8px;cursor:pointer;border-radius:var(--radius-s);margin-bottom:2px}.review-project-item:hover{background-color:var(--background-modifier-hover)}.review-project-item.selected{background-color:var(--background-modifier-active)}.review-project-item.has-review-settings .review-project-icon{color:var(--text-accent)}.review-project-item.has-review-settings .review-project-name{font-weight:500}.review-project-item:not(.has-review-settings) .review-project-icon{color:var(--text-muted)}.review-project-icon{margin-right:8px;display:flex;align-items:center;justify-content:center}.review-project-name{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.review-task-header{display:flex;flex-direction:column;padding:var(--size-4-4);border-bottom:1px solid var(--background-modifier-border)}.is-phone .review-task-header{flex-direction:row;align-items:flex-start}.review-header-content h3{margin:0 0 8px;padding:0}.review-info{display:flex;align-items:center;color:var(--text-muted);font-size:.9em}.review-separator{margin:0 8px}.review-frequency{color:var(--text-accent)}.review-frequency:hover{color:var(--text-normal);text-decoration:underline}.review-last-date{color:var(--text-normal)}.review-no-settings{font-style:italic}.review-filter-info{margin-top:10px;padding:6px 10px;background-color:var(--background-secondary);border-radius:var(--radius-s);font-size:.85em;color:var(--text-muted);border-left:3px solid var(--text-accent)}.review-filter-toggle{cursor:pointer;text-decoration:underline;color:var(--text-accent);margin-left:5px}.review-filter-toggle:hover{color:var(--text-accent-hover)}.review-task-list{flex:1;overflow-y:auto;padding:var(--size-4-2)}.review-empty-state{display:flex;align-items:center;justify-content:center;height:100%;color:var(--text-muted);font-style:italic;padding:16px;text-align:center}.review-button-container{margin-top:12px;display:flex;justify-content:flex-start}.review-complete-btn,.review-configure-btn{padding:6px 12px;border-radius:var(--radius-s);cursor:pointer;font-size:.9em;border:1px solid var(--background-modifier-border);background-color:var(--background-secondary)}.review-complete-btn{color:var(--text-accent)}.review-complete-btn:hover{background-color:var(--background-modifier-hover);color:var(--text-accent)}.review-configure-btn{color:var(--text-muted)}.review-configure-btn:hover{background-color:var(--background-modifier-hover);color:var(--text-normal)}.review-edit-btn{color:var(--text-accent-hover);margin-left:8px}.review-edit-btn:hover{background-color:var(--background-modifier-hover);color:var(--text-accent-hover)}.review-modal-title{margin-top:0;margin-bottom:20px;font-size:1.5em;color:var(--text-normal);border-bottom:1px solid var(--background-modifier-border);padding-bottom:10px}.review-modal-form{margin-bottom:20px}.review-modal-field{margin-bottom:16px}.review-modal-label{display:block;font-weight:600;margin-bottom:4px;color:var(--text-normal)}.review-modal-description{font-size:.9em;color:var(--text-muted);margin-bottom:8px}.review-modal-select{width:100%;border-radius:var(--radius-s);border:1px solid var(--background-modifier-border);background-color:var(--background-primary);color:var(--text-normal);font-size:14px}.review-modal-custom-frequency{margin-top:8px}.review-modal-input{width:100%;padding:8px;border-radius:var(--radius-s);border:1px solid var(--background-modifier-border);background-color:var(--background-primary);color:var(--text-normal);font-size:14px}.review-modal-last-reviewed{padding:8px;font-size:14px;color:var(--text-normal);background-color:var(--background-secondary);border-radius:var(--radius-s)}.review-modal-buttons{display:flex;justify-content:flex-end;margin-top:24px;border-top:1px solid var(--background-modifier-border);padding-top:16px}.review-modal-button{padding:8px 16px;border-radius:var(--radius-s);font-size:14px;cursor:pointer;border:1px solid var(--background-modifier-border)}.review-modal-button-cancel{background-color:var(--background-secondary);color:var(--text-muted);margin-right:8px}.review-modal-button-cancel:hover{background-color:var(--background-modifier-hover);color:var(--text-normal)}.review-modal-button-save{background-color:var(--interactive-accent);color:var(--text-on-accent)}.review-modal-button-save:hover{background-color:var(--interactive-accent-hover)}.is-phone .review-container{position:relative;overflow:hidden}.is-phone .review-left-column{position:absolute;left:0;top:0;height:100%;z-index:10;background-color:var(--background-secondary);width:100%;transform:translate(-100%);transition:transform .3s ease-in-out;border-right:1px solid var(--background-modifier-border)}.is-phone .review-left-column.is-visible{transform:translate(0)}.is-phone .review-sidebar-toggle{display:flex;align-items:center;justify-content:center;margin-right:8px}.is-phone .review-sidebar-close{position:absolute;top:var(--size-2-2);right:10px;z-index:15;display:flex;align-items:center;justify-content:center}.is-phone .review-container:has(.review-left-column.is-visible):before{content:"";position:absolute;top:0;left:0;width:100%;height:100%;background-color:var(--background-modifier-cover);opacity:.5;z-index:5;transition:opacity .3s ease-in-out}.task-sidebar.collapsed{width:48px;overflow:hidden}.panel-toggle-btn{display:flex;align-items:center;justify-content:center;border-radius:4px;cursor:pointer;opacity:.6;transition:opacity .2s ease}.panel-toggle-btn:hover{opacity:1}.task-sidebar.collapsed .sidebar-nav{align-items:center}.sidebar-nav{display:flex;flex-direction:column;padding:20px 0 10px;gap:5px}.sidebar-nav-spacer{height:1px;background-color:var(--background-modifier-border);margin:auto 15px 8px;opacity:.7}.sidebar-nav-item{display:flex;align-items:center;padding:8px 15px;cursor:pointer;border-radius:4px;margin:0 5px;transition:background-color .2s ease}.sidebar-nav-item:hover{background-color:var(--background-modifier-active)}.sidebar-nav-item.is-active{font-weight:600;--background-modifier-hover: var(--interactive-accent);--icon-color: var(--text-on-accent);background-color:var(--interactive-accent);color:var(--text-on-accent)}.nav-item-icon{--icon-size: var(--size-4-4);display:flex;align-items:center;justify-content:center;margin-right:var(--size-4-2)}.nav-item-label{flex:1;font-size:var(--font-ui-medium);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.nav-item-label.hidden{opacity:0;width:0;overflow:hidden;margin:0}.task-sidebar.collapsed .sidebar-nav-item{padding:8px 10px;justify-content:center;width:var(--size-4-9);flex-shrink:0;transition:width .3s ease-in-out,flex-shrink .3s ease-in-out}.task-sidebar.collapsed .nav-item-icon{margin-right:0}.task-content{flex:1;display:flex;flex-direction:column;overflow:hidden;min-width:0;transition:margin .3s ease}.task-sidebar.collapsed .task-content{margin-left:-200px;transition:margin .3s ease}.task-genius-view .project-tree{padding:10px 0;transition:opacity .3s ease}.task-genius-view .tree-root{display:flex;flex-direction:column}.task-genius-view .task-genius-view .tree-item{display:flex;align-items:center;padding:6px 8px;cursor:pointer;transition:background-color .2s ease;border-radius:4px;margin:0 5px}.task-genius-view .tree-item.selected{background-color:var(--background-modifier-border-hover)}.task-genius-view .tree-item-icon{display:flex;align-items:center;justify-content:center;width:20px;height:20px;margin-right:8px;color:var(--text-muted)}.task-genius-view .tree-item-name{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.task-genius-view .tree-item-count{font-size:.8em;color:var(--text-muted);margin-left:5px;background-color:var(--background-modifier-hover);padding:1px 6px;border-radius:10px}.task-genius-view .tree-item-toggle,.task-genius-view .tree-item-indent{width:20px;display:flex;align-items:center;justify-content:center;margin-right:5px}.task-genius-view .tree-item-toggle{cursor:pointer}.content-header{padding:15px;border-bottom:1px solid var(--background-modifier-border);display:flex;align-items:center;flex-shrink:0}.task-count{font-size:.8em;color:var(--text-muted);margin-right:10px}.focus-filter{margin-left:10px}.workspace-leaf-content .task-genius-view{padding:0}.task-genius-container{display:flex;flex-direction:row;height:100%;width:100%;background-color:var(--background-primary);border-top:1px solid var(--background-modifier-border);color:var(--text-normal);position:relative;overflow:hidden}.task-sidebar{display:flex;flex-direction:column;border-right:1px solid var(--background-modifier-border);background-color:var(--background-secondary);overflow-y:auto;width:240px;transition:width .3s ease-in-out;position:relative}.task-content{display:flex;flex-direction:column;flex:1;min-width:300px;height:100%;overflow:hidden}.task-sidebar .sidebar-nav{display:flex;flex-direction:column;padding:8px 0;height:100%}.project-tree{display:flex;flex-direction:column;padding:8px 0;overflow-y:auto}.tree-root{display:flex;flex-direction:column}.task-genius-view .tree-item{display:flex;align-items:center;padding:4px 12px;cursor:pointer;border-radius:4px;margin:2px 8px}.task-genius-view .tree-item:hover{background-color:var(--background-modifier-border-hover)}.task-genius-view .tree-item.selected{background-color:var(--background-modifier-border-hover);color:var(--text-accent)}.task-genius-view .tree-item-toggle{width:16px;height:16px;display:flex;align-items:center;justify-content:center;margin-right:4px}.task-genius-view .tree-item-indent{width:16px;height:16px;margin-right:4px}.task-genius-view .tree-item-icon{margin-right:8px;width:16px;height:16px;display:flex;align-items:center;justify-content:center;color:var(--text-muted)}.task-genius-view .tree-item-name{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.task-genius-view .tree-item-count{font-size:.8em;color:var(--text-muted);background-color:var(--background-modifier-hover);border-radius:10px;padding:2px 6px;min-width:16px;text-align:center}.task-genius-view .tree-item.expanded>.tree-item-children{display:flex}.task-genius-view .tree-item-children{display:none;flex-direction:column;margin-left:16px;width:100%}.task-genius-view .content-header{display:flex;align-items:center;padding:10px 16px;border-bottom:1px solid var(--background-modifier-border);min-height:50px}.task-genius-view .content-title{font-size:1.2em;font-weight:600;margin-right:12px;flex:1}@media screen and (max-width: 768px){.task-genius-view .content-title{display:none}.task-genius-view .task-count{flex:1}.task-genius-view .focus-filter{flex:1}}.task-genius-view .content-filter{display:flex;align-items:center;margin-right:12px}.task-genius-view .filter-input{border:1px solid var(--background-modifier-border);border-radius:4px;padding:4px 8px;width:200px;background-color:var(--background-primary)}.task-genius-view .focus-button{background-color:var(--interactive-normal);border:1px solid var(--background-modifier-border);border-radius:4px;padding:4px 10px;color:var(--text-normal);cursor:pointer}.task-genius-view .focus-button:hover{background-color:var(--interactive-hover)}.task-genius-view .focus-button.focused{background-color:var(--interactive-accent);color:var(--text-on-accent)}.mod-root .task-genius-action-btn{--icon-size: 16px}.mod-left-split .task-genius-action-btn{display:none}.mod-left-split .workspace-tab-header-status-container:has(.task-genius-action-btn){display:none}.mod-right-split .workspace-tab-header-status-container:has(.task-genius-action-btn){display:none}.task-genius-view .task-empty-state{width:100%;height:100%;flex:1;display:flex;align-items:center;justify-content:center}.mod-root .task-genius-tab-header{container-type:inline-size!important}@container (max-width: 120px){.mod-root .task-genius-action-btn {display: none;}}.quick-workflow-modal{max-width:600px;min-height:400px}.workflow-template-section{margin-bottom:20px;padding:15px;border:1px solid var(--background-modifier-border);border-radius:8px}.template-description{margin-top:10px}.template-desc-text{font-style:italic;color:var(--text-muted);margin:0}.workflow-form-section{margin-bottom:20px}.workflow-stages-preview{margin-top:15px}.stages-preview-list{margin-top:10px}.stage-preview-item{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;margin:4px 0;background:var(--background-secondary);border-radius:6px;border:1px solid var(--background-modifier-border)}.stage-info{display:flex;align-items:center;gap:8px}.stage-name{font-weight:500}.stage-type{color:var(--text-muted);font-size:.9em}.stage-actions{display:flex;gap:4px}.no-stages-message{text-align:center;color:var(--text-muted);font-style:italic;padding:20px;border:2px dashed var(--background-modifier-border);border-radius:8px;margin-top:10px}.workflow-modal-buttons{display:flex;justify-content:flex-end;gap:10px;margin-top:20px;padding-top:15px;border-top:1px solid var(--background-modifier-border)}.workflow-progress-indicator{background:var(--background-secondary);border:1px solid var(--background-modifier-border);border-radius:8px;padding:15px;margin:10px 0}.workflow-progress-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:15px}.workflow-name{font-weight:600;font-size:1.1em}.workflow-progress-text{color:var(--text-muted);font-size:.9em}.workflow-progress-bar-container{display:flex;align-items:center;gap:10px;margin-bottom:15px}.workflow-progress-bar{flex:1;height:8px;background:var(--background-modifier-border);border-radius:4px;overflow:hidden}.workflow-progress-fill{height:100%;background:var(--interactive-accent);transition:width .3s ease}.workflow-progress-percentage{font-size:.9em;font-weight:500;min-width:35px;text-align:right}.workflow-stage-list{display:flex;flex-direction:column;gap:8px}.workflow-stage-item{display:flex;align-items:flex-start;gap:12px;padding:10px;border-radius:6px;transition:background-color .2s ease}.workflow-stage-item.completed{background:var(--background-modifier-success)}.workflow-stage-item.current{background:var(--background-modifier-accent);border:1px solid var(--interactive-accent)}.workflow-stage-item.pending{background:var(--background-primary);opacity:.7}.workflow-stage-icon{width:20px;height:20px;display:flex;align-items:center;justify-content:center;margin-top:2px}.workflow-stage-icon.completed-icon{color:var(--text-success)}.workflow-stage-icon.current-icon{color:var(--interactive-accent)}.workflow-stage-icon.pending-icon{color:var(--text-muted)}.workflow-stage-content{flex:1}.workflow-stage-name{font-weight:500;margin-bottom:2px}.workflow-stage-type{font-size:.8em;color:var(--text-muted)}.workflow-stage-number{width:24px;height:24px;border-radius:50%;background:var(--background-modifier-border);display:flex;align-items:center;justify-content:center;font-size:.8em;font-weight:600;margin-top:2px}.workflow-stage-item.completed .workflow-stage-number{background:var(--text-success);color:var(--background-primary)}.workflow-stage-item.current .workflow-stage-number{background:var(--interactive-accent);color:var(--text-on-accent)}.workflow-substage-container{margin-top:8px;padding-left:16px;border-left:2px solid var(--background-modifier-border)}.workflow-substage-item{display:flex;align-items:center;gap:8px;padding:4px 0}.workflow-substage-icon{width:12px;height:12px;color:var(--text-muted)}.workflow-substage-name{font-size:.9em;color:var(--text-muted)}.full-calendar-container{container-type:inline-size;display:flex;flex-direction:column;height:100%;overflow:hidden;flex-grow:1}.full-calendar-container .calendar-header{display:flex;justify-content:space-between;align-items:center;padding:var(--size-2-3) var(--size-4-4);border-bottom:1px solid var(--background-modifier-border);flex-shrink:0;margin-bottom:0}.full-calendar-container .calendar-header button{margin:0 var(--size-4-1)}.full-calendar-container .calendar-nav,.full-calendar-container .calendar-view-switcher{display:flex;gap:var(--size-2-2)}.full-calendar-container .calendar-nav button{border-radius:var(--radius-s);text-transform:uppercase}.full-calendar-container .calendar-view-switcher button{border-radius:var(--radius-s);text-transform:uppercase}.full-calendar-container .calendar-view-switcher button:not(.is-active),.full-calendar-container .calendar-nav button:not(.is-active){box-shadow:var(--shadow-xs);border:1px solid var(--background-modifier-border)}.full-calendar-container .calendar-current-date{font-weight:var(--font-semibold);font-size:var(--font-ui-large);text-align:center;flex-grow:1;max-width:max(120px,40%);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.full-calendar-container .calendar-view-switcher button.is-active{background-color:var(--interactive-accent);color:var(--text-on-accent);border-color:var(--interactive-accent-hover)}.full-calendar-container .calendar-view-container{flex-grow:1;overflow-y:auto;padding:var(--size-4-2);position:relative;display:flex;flex-direction:column}.full-calendar-container .calendar-weekday-header{display:grid;grid-template-columns:repeat(7,1fr);text-align:center;font-size:var(--font-ui-small);color:var(--text-muted);padding:var(--size-4-1) 0;border-bottom:1px solid var(--background-modifier-border);margin-bottom:-1px;background-color:var(--background-secondary)}.full-calendar-container .calendar-weekday{padding:var(--size-4-1)}.full-calendar-container .calendar-view-container.view-month{padding:0}.full-calendar-container .calendar-month-grid{display:grid;grid-template-columns:repeat(7,1fr);grid-auto-rows:minmax(100px,auto);gap:1px;background-color:var(--background-modifier-border);height:100%}.full-calendar-container .calendar-day-cell{background-color:var(--background-primary);padding:var(--size-4-1);position:relative;display:flex;flex-direction:column;min-width:0}.full-calendar-container .calendar-day-cell:hover{background-color:hsl(var(--color-accent-h),var(--color-accent-s),var(--color-accent-l),.8)!important}.full-calendar-container .calendar-day-cell.is-today{background-color:var(--background-secondary-alt)!important;border:1px solid hsl(var(--accent-h),var(--accent-s),var(--accent-l),.5)}.full-calendar-container .calendar-day-cell.is-today .calendar-day-number{color:hsl(var(--accent-h),var(--accent-s),var(--accent-l),1)}.full-calendar-container .calendar-day-header{width:100%;display:flex;flex-direction:row-reverse;justify-content:space-between;align-items:center;gap:var(--size-4-1)}.full-calendar-container .calendar-day-cell:not(.is-today){opacity:.7}.full-calendar-container .calendar-day-cell.is-other-month{background-color:var(--background-secondary);opacity:.7}.full-calendar-container .calendar-day-cell.is-weekend{background-color:var( --background-secondary )}.full-calendar-container .calendar-day-number{font-size:var(--font-ui-small);text-align:center;margin-bottom:var(--size-4-1);flex-shrink:0;align-self:flex-end}.full-calendar-container .calendar-events-container{flex-grow:1;overflow:hidden;position:relative}.full-calendar-container .calendar-event{background-color:var(--interactive-accent);color:var(--text-on-accent);border-radius:var(--radius-s);padding:2px 4px;font-size:var(--font-ui-smaller);margin-bottom:3px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;cursor:pointer;display:block}.full-calendar-container .calendar-event:has(.task-list-item-checkbox){display:flex;flex-direction:row;align-items:center}.full-calendar-container .calendar-event:has(.task-list-item-checkbox).calendar-event-week-allday{display:flex}.full-calendar-container .calendar-event:has(.task-list-item-checkbox).calendar-event-month{display:flex}.full-calendar-container .full-calendar-container .calendar-event:hover{opacity:.8}.full-calendar-container .calendar-event.is-completed{background-color:var( --background-modifier-success-hover );text-decoration:line-through;opacity:.7}.full-calendar-container .calendar-event.calendar-event-month{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:block;width:100%;box-sizing:border-box}.full-calendar-container .calendar-view-container.view-day{display:flex;flex-direction:column;padding:0}.full-calendar-container .calendar-timeline-section{flex-grow:1;border-top:1px solid var(--background-modifier-border);overflow-y:auto;padding:var(--size-4-4)}.full-calendar-container .calendar-timeline-events-container{display:flex;flex-direction:column;gap:var(--size-4-2)}.full-calendar-container .calendar-event-timed{border:1px solid var(--background-modifier-border);overflow:hidden;display:flex;flex-direction:column;width:100%}.full-calendar-container .calendar-event-time{font-size:var(--font-ui-smaller);font-weight:bold;padding:1px 3px;background-color:#0000001a}.full-calendar-container .calendar-event-title{font-size:var(--font-ui-small);padding:2px 3px;flex-grow:1;white-space:normal;word-wrap:break-word;display:flex;align-items:center}.full-calendar-container .calendar-view-container.view-week{display:flex;flex-direction:column;padding:0}.full-calendar-container .calendar-week-header{display:grid;grid-template-columns:repeat(7,1fr);border-bottom:1px solid var(--background-modifier-border);flex-shrink:0;text-align:center;background-color:var(--background-secondary);font-size:var(--font-ui-medium)}.full-calendar-container .calendar-header-cell{padding:var(--size-4-1) 0;border-left:1px solid var(--background-modifier-border-hover)}.full-calendar-container .calendar-header-cell:first-child{border-left:none}.full-calendar-container .calendar-header-cell.is-today .calendar-day-number{background-color:var(--interactive-accent);color:var(--text-on-accent);border-radius:50%;display:inline-block;width:1.5em;height:1.5em;line-height:1.5em;margin:auto;display:flex;align-items:center;justify-content:center}.full-calendar-container .calendar-weekday{font-size:var(--font-ui-small);color:var(--text-muted)}.full-calendar-container .calendar-day-number{font-size:var(--font-ui-medium)}.full-calendar-container .calendar-week-grid-section{flex-grow:1;display:flex;flex-direction:column;overflow-y:auto;border-bottom:1px solid var(--background-modifier-border)}.full-calendar-container .calendar-week-grid{flex-grow:1;display:grid;grid-template-columns:repeat(7,1fr);grid-template-rows:1fr;gap:1px;background-color:var(--background-modifier-border);border-top:1px solid var(--background-modifier-border)}.full-calendar-container .calendar-day-column{background-color:var(--background-primary);padding:var(--size-4-1);border-left:none;display:flex;flex-direction:column;gap:var(--size-4-1);overflow:hidden;min-width:0}.full-calendar-container .calendar-day-column.is-weekend{background-color:var(--background-secondary)}.full-calendar-container .calendar-view-container.hide-weekends .calendar-weekday-header{grid-template-columns:repeat(5,1fr)!important}.full-calendar-container .calendar-view-container.hide-weekends .calendar-month-grid{grid-template-columns:repeat(5,1fr)!important}.full-calendar-container .calendar-view-container.hide-weekends .calendar-week-header{grid-template-columns:repeat(5,1fr)!important}.full-calendar-container .calendar-view-container.hide-weekends .calendar-week-grid{grid-template-columns:repeat(5,1fr)!important}.full-calendar-container .calendar-view-container.hide-weekends .mini-month-grid{grid-template-columns:repeat(5,1fr)!important}.full-calendar-container .calendar-day-events-container{flex-grow:1;display:flex;flex-direction:column;gap:3px}.full-calendar-container .calendar-event.calendar-event-week-allday{display:block;width:100%;position:relative;left:auto;top:auto;height:auto;margin-bottom:3px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.full-calendar-container .calendar-view-container.view-year{padding:var(--size-4-4)}.full-calendar-container .calendar-year-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:var(--size-4-4)}.full-calendar-container .calendar-mini-month{border:1px solid var(--background-modifier-border);border-radius:var(--radius-m);background-color:var(--background-secondary);overflow:hidden}.full-calendar-container .mini-month-header{text-align:center;font-weight:var(--font-semibold);padding:var(--size-4-2);background-color:var(--background-secondary-alt);border-bottom:1px solid var(--background-modifier-border)}.full-calendar-container .mini-month-body{padding:var(--size-4-2)}.full-calendar-container .mini-month-grid{display:grid;grid-template-columns:repeat(7,1fr);gap:2px;text-align:center}.full-calendar-container .mini-weekday-header{display:contents;font-size:var(--font-ui-smaller);color:var(--text-faint);font-weight:bold}.full-calendar-container .mini-weekday{padding-bottom:var(--size-4-1)}.full-calendar-container .mini-day-cell{font-size:var(--font-ui-small);padding:1px;border-radius:var(--radius-s);line-height:1.5em}.full-calendar-container .mini-day-cell.is-other-month{color:var(--text-faint);opacity:.6}.full-calendar-container .mini-day-cell.is-today{font-weight:bold;background-color:var(--interactive-accent-hover);color:var(--text-on-accent)}.full-calendar-container .mini-day-cell.has-events{font-weight:bold}.agenda-day-section{display:flex;width:100%;border:1px solid var(--background-modifier-border);padding-top:var(--size-4-2);padding-bottom:var(--size-4-2);padding-left:var(--size-4-2);padding-right:var(--size-4-2)}.agenda-day-date-column{width:20%;display:flex;flex-direction:column;justify-content:flex-start;align-items:center}.agenda-day-events-column{flex:1}.full-calendar-container input.task-list-item-checkbox{scale:.9}.full-calendar-container .calendar-view-switcher-selector{display:none}.calendar-event-ghost{background-color:var(--background-secondary-alt)!important;border:2px dashed var(--background-modifier-border)!important;opacity:.5!important;box-shadow:none!important}.calendar-event-dragging{opacity:.9!important;box-shadow:var(--shadow-l)!important;transform:rotate(2deg)!important;z-index:1000!important}.calendar-events-container .calendar-event{cursor:grab;transition:transform .2s ease,box-shadow .2s ease}.calendar-events-container .calendar-event:hover{transform:translateY(-1px);box-shadow:var(--shadow-s)}.calendar-events-container .calendar-event:active{cursor:grabbing}.calendar-events-container,.calendar-day-events-container{min-height:20px;border-radius:var(--radius-s);transition:background-color .2s ease}@container (max-width: 600px){.full-calendar-container .calendar-view-switcher button {display: none;} .calendar-nav .prev-button {display: none;} .calendar-nav .next-button {display: none;} .full-calendar-container .calendar-view-switcher-selector {display: block;}}.full-calendar-container .calendar-event-title-container p{padding-inline-start:0;padding-inline-end:0;margin-block-start:0;margin-block-end:0}.full-calendar-container .calendar-event-title-container{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:100%}.full-calendar-container .calendar-event-title p{margin-block-start:0;margin-block-end:0}.calendar-badges-container{display:flex;flex-direction:row;gap:4px;pointer-events:none;z-index:10}.calendar-badge{color:var(--text-muted);display:flex;font-size:10px;padding:var(--size-2-1);border-radius:var(--radius-s);max-width:80px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.calendar-day-cell{position:relative}.tg-kanban-view{display:flex;flex-direction:column;height:100%;width:100%;overflow:hidden}.tg-kanban-filters{border-bottom:1px solid var(--background-modifier-border);flex-shrink:0;display:flex;flex-direction:row-reverse;gap:8px;padding:0 8px}.tg-kanban-controls-container{display:flex;align-items:center;gap:12px;flex-wrap:wrap}.tg-kanban-sort-container{display:flex;align-items:center;gap:4px}.tg-kanban-sort-button{padding:4px 8px;border:1px solid var(--background-modifier-border);border-radius:var(--radius-s);background-color:var(--background-primary);color:var(--text-normal);cursor:pointer;display:flex;align-items:center;gap:4px;font-size:var(--font-ui-small)}.tg-kanban-sort-button:hover{background-color:var(--background-modifier-hover);border-color:var(--background-modifier-border-hover)}.tg-kanban-toggle-container{display:flex;align-items:center;gap:4px}.tg-kanban-toggle-label{display:flex;align-items:center;gap:6px;font-size:var(--font-ui-small);color:var(--text-normal);cursor:pointer}.tg-kanban-toggle-checkbox{margin:0}.tg-kanban-filter-input{flex-grow:1;padding:6px 10px;font-size:var(--font-ui-small);border:1px solid var(--background-modifier-border);border-radius:var(--radius-s);background-color:var(--background-primary);margin-right:10px}.tg-kanban-filter-input:focus{outline:none;border-color:var(--interactive-accent);box-shadow:0 0 0 1px var(--interactive-accent)}.tg-kanban-column-container{display:flex;flex-grow:1;overflow-x:auto;overflow-y:hidden;padding:10px;gap:10px;height:100%;-webkit-overflow-scrolling:touch;overscroll-behavior-x:auto;scroll-snap-type:x proximity;scroll-behavior:smooth}@media (hover: hover) and (pointer: fine){.tg-kanban-column-container{overscroll-behavior-x:none;scroll-snap-type:none}}.tg-kanban-column{flex:0 0 280px;display:flex;flex-direction:column;background-color:var(--background-secondary);border-radius:var(--radius-m);height:100%;max-height:100%;overflow:hidden;border:1px solid var(--background-modifier-border);scroll-snap-align:start}@media (hover: hover) and (pointer: fine){.tg-kanban-column{scroll-snap-align:none}}.tg-kanban-column-header{padding:8px 12px;font-size:var(--font-ui-mediumn);font-weight:600;border-bottom:1px solid var(--background-modifier-border);flex-shrink:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;text-transform:uppercase;display:flex;align-items:center}.tg-kanban-column-content{flex-grow:1;overflow-y:auto;padding:8px;display:flex;flex-direction:column;gap:8px;background-color:var(--background-secondary-alt);-webkit-overflow-scrolling:touch;overscroll-behavior:contain;scroll-behavior:smooth}@media (hover: hover) and (pointer: fine){.tg-kanban-column-content{overscroll-behavior:none}}.tg-kanban-card{background-color:var(--background-primary);border-radius:var(--radius-s);padding:10px 12px;border:1px solid var(--background-modifier-border);font-size:var(--font-ui-small);cursor:grab;transition:box-shadow .2s ease-in-out,background-color .2s ease-in-out;max-width:100%;box-sizing:border-box;white-space:nowrap;text-overflow:ellipsis;touch-action:manipulation;user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none}.tg-kanban-card .tg-kanban-card-content{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:100%}.tg-kanban-card:hover{border-color:var(--background-modifier-border-hover);box-shadow:var(--shadow-m)}.tg-kanban-card.task-completed{background-color:var(--background-secondary);opacity:.7}.tg-kanban-card.task-completed .tg-kanban-card-content{text-decoration:line-through;color:var(--text-muted)}.tg-kanban-card-container{display:flex;align-items:flex-start;margin-bottom:6px}.tg-kanban-card-content p:last-child{margin-bottom:0;margin-block-end:0;margin-block-start:0}.tg-kanban-card-metadata{display:flex;flex-wrap:wrap;gap:4px 8px;font-size:var(--font-ui-small);color:var(--text-muted)}.tg-kanban-card-metadata .task-date,.tg-kanban-card-metadata .task-tags-container,.tg-kanban-card-metadata .task-priority{display:flex;align-items:center;gap:4px;padding:2px 5px;background-color:var(--background-secondary);border-radius:var(--radius-s);margin-inline-start:0;margin-inline-end:0;margin-left:0;margin-right:0}.tg-kanban-card-metadata .task-tag{background-color:var( --background-modifier-accent-hover );color:var(--text-accent);padding:1px 4px;border-radius:var(--radius-s);font-size:calc(var(--font-ui-small) * .9)}.tg-kanban-card-metadata .task-due-date.task-overdue{color:var(--text-error);background-color:var(--background-error)}.tg-kanban-card-metadata .task-due-date.task-due-today{color:var(--text-warning);background-color:var(--background-warning)}.tg-kanban-card-metadata .task-priority.priority-1{color:var(--text-accent)}.tg-kanban-card-metadata .task-priority.priority-2{color:var(--text-warning)}.tg-kanban-card-metadata .task-priority.priority-3{color:var(--text-error);font-weight:bold}.tg-kanban-card-dragging{box-shadow:var(--shadow-l)}.tg-kanban-card-ghost{background-color:var(--background-secondary-alt);border:1px dashed var(--background-modifier-border);box-shadow:none}.tg-kanban-column-content.tg-kanban-drop-target-active{outline:2px dashed var(--background-modifier-accent-hover);outline-offset:-2px}.tg-kanban-column-content.tg-kanban-drop-target-hover{background-color:var(--background-modifier-accent-hover)}.tg-kanban-card--drop-indicator-before{margin-top:10px;border-top:2px dashed var(--interactive-accent);transition:margin-top .1s ease-out,border-top .1s ease-out}.tg-kanban-card--drop-indicator-after{margin-bottom:10px;border-bottom:2px dashed var(--interactive-accent);transition:margin-bottom .1s ease-out,border-bottom .1s ease-out}.tg-kanban-column-content--drop-indicator-empty{border:2px dashed var(--interactive-accent);min-height:50px;box-sizing:border-box;margin-top:5px;margin-bottom:5px}.tg-kanban-card{transition:margin .1s ease-out,padding .1s ease-out,border .1s ease-out,transform .2s ease-out,box-shadow .2s ease-in-out,background-color .2s ease-in-out}.drop-target-active{background-color:#00800033;outline:2px dashed green}.tg-kanban-add-card-container{padding:8px;border-top:1px solid var(--background-modifier-border);flex-shrink:0}.task-genius-add-card-container{padding:8px;margin-top:auto;text-align:center}.tg-kanban-add-card-button{--icon-size: 16px;width:100%;padding:6px 12px;border:none;background-color:transparent;color:var(--text-muted);border-radius:var(--radius-s);cursor:pointer;font-size:var(--font-ui-small);text-align:left;transition:background-color .2s ease-in-out,color .2s ease-in-out}.tg-kanban-add-card-button:hover{background-color:var(--background-modifier-hover);color:var(--text-normal)}.tg-kanban-column-dragging{transform:rotate(5deg);opacity:.8;box-shadow:var(--shadow-xl);z-index:1000}.tg-kanban-column-ghost{background-color:var(--background-modifier-border);border:2px dashed var(--background-modifier-accent);opacity:.5}.tg-kanban-column-header{cursor:grab}.tg-kanban-column-header:active{cursor:grabbing}.filter-component{display:flex;flex-wrap:wrap;align-items:center;gap:var(--size-4-2);padding:var(--size-4-2) var(--size-4-3);background-color:var(--background-primary);min-height:48px;flex:1}.filter-pills-container{display:flex;flex-wrap:wrap;gap:var(--size-4-2);flex:1}.filter-controls{display:flex;align-items:center;gap:var(--size-4-2);margin-left:auto}.filter-pill{display:flex;align-items:center;gap:var(--size-4-1);padding:5px 8px;border:1px solid var(--background-modifier-border);border-radius:var(--radius-m);font-size:var(--font-ui-small);animation:filter-pill-appear .2s ease-out;transition:background-color var(--duration-fast),transform var(--duration-fast)}.filter-pill-remove .clickable-icon:hover{background-color:unset}.filter-pill:hover{background-color:var(--background-tertiary)}.filter-pill-category{font-weight:500;color:var(--text-muted)}.filter-pill-value{color:var(--text-normal)}.filter-pill-remove{display:flex;align-items:center;justify-content:center;width:16px;height:16px;border-radius:50%;background:transparent;border:none;padding:0;margin-left:var(--size-4-1);cursor:pointer;color:var(--text-faint);font-size:14px;line-height:1;transition:background-color var(--duration-fast),color var(--duration-fast)}.filter-pill-remove:hover{background-color:var(--background-modifier-hover);color:var(--text-normal)}.filter-pill-remove-icon{font-size:16px;display:flex;align-items:center;justify-content:center}.filter-add-button,.filter-clear-all-button{display:flex;align-items:center;padding:6px 10px;font-size:var(--font-ui-small);cursor:pointer}.filter-add-button{gap:var(--size-4-1);color:var(--text-muted)}.filter-add-icon{font-weight:var(--font-bold);display:flex;align-items:center;justify-content:center}.filter-dropdown{position:fixed;width:220px;background-color:var(--background-primary);border-radius:var(--radius-m);box-shadow:var(--shadow-l);border:1px solid var(--background-modifier-border);z-index:var(--layer-popover);max-height:400px;display:flex;flex-direction:column;opacity:0;transform:translateY(-8px);transition:opacity var(--duration-normal),transform var(--duration-normal);overflow:hidden}.filter-dropdown-visible{opacity:1;transform:translateY(0)}.filter-dropdown-header{padding:var(--size-4-2);border-bottom:1px solid var(--background-modifier-border)}.filter-dropdown-search{width:100%;padding:var(--size-4-2);border:1px solid var(--background-modifier-border);border-radius:var(--radius-m);background-color:var(--background-secondary);font-size:var(--font-ui-small);outline:none}.filter-dropdown-search:focus{border-color:var(--interactive-accent);box-shadow:0 0 0 2px var(--focus-ring-color)}.filter-dropdown-list{overflow-y:auto;max-height:350px}.filter-dropdown-item{display:flex;align-items:center;padding:var(--size-4-2) var(--size-4-3);cursor:pointer;font-size:var(--font-ui-small);color:var(--text-normal);transition:background-color var(--duration-fast)}.filter-dropdown-item:hover{background-color:var(--background-secondary)}.filter-dropdown-item-label{flex:1}.filter-dropdown-item-arrow{color:var(--text-faint);font-size:18px}.filter-dropdown-item-arrow.back{margin-right:var(--size-4-2);display:flex;align-items:center;justify-content:center}.filter-dropdown-back{color:var(--text-muted)}.filter-dropdown-separator{height:1px;background-color:var(--divider-color);margin:var(--size-4-1) 0}.filter-dropdown-empty{padding:var(--size-4-4);text-align:center;color:var(--text-faint);font-size:var(--font-ui-small)}.filter-dropdown-value-item{padding-left:var(--size-4-4)}.filter-dropdown-category{padding:var(--size-4-2) 0;color:var(--text-muted);font-weight:500}.filter-dropdown-value-preview{padding:var(--size-4-1) var(--size-4-4);cursor:pointer;transition:background-color var(--duration-fast);font-size:var(--font-ui-small);color:var(--text-normal)}.filter-dropdown-value-preview:hover{background-color:var(--background-secondary)}@keyframes filter-pill-appear{0%{opacity:0;transform:scale(.9)}to{opacity:1;transform:scale(1)}}.filter-pill-removing{opacity:0;transform:scale(.9);transition:opacity .15s ease-out,transform .15s ease-out}.gantt-chart-container{width:100%;height:100%;overflow:auto;position:relative;background-color:var(--background-secondary);--gantt-header-height: 50px;--gantt-row-height: 40px;--gantt-bar-height: 20px;--gantt-bar-radius: 3px;--gantt-bg-color: var(--background-secondary);--gantt-grid-color: var(--background-modifier-border);--gantt-row-color: var(--background-secondary);--gantt-bar-color: var(--color-blue);--gantt-milestone-color: var(--color-purple);--gantt-progress-color: var(--color-blue);--gantt-today-color: var(--color-accent)}.gantt-svg{display:block;font-family:var(--font-interface);font-size:var(--font-ui-small);user-select:none}.gantt-header-bg{fill:var(--background-primary);stroke:var(--background-modifier-border);stroke-width:1px}.gantt-header-text{fill:var(--text-muted);font-weight:500}.gantt-grid-bg{fill:transparent;stroke:var(--background-modifier-border);stroke-width:0}.gantt-grid-line-vertical{stroke:var(--background-modifier-border);stroke-width:1px;stroke-dasharray:2,2}.gantt-task-item{cursor:pointer}.gantt-task-bar{fill:var(--interactive-accent);stroke:var(--interactive-accent-hover);stroke-width:1px;transition:fill .1s ease-in-out}.gantt-task-item:hover .gantt-task-bar{fill:var(--interactive-accent-hover)}.gantt-task-milestone{fill:var(--color-orange);stroke:var(--color-orange-border);stroke-width:1px}.gantt-task-label{fill:var(--text-on-accent);font-size:calc(var(--font-ui-small) * .9);pointer-events:none;white-space:pre}.gantt-task-bar.status-completed{fill:var(--color-green);stroke:var(--color-green-border)}.gantt-header{position:sticky;top:0;left:0;right:0;z-index:10;height:var(--gantt-header-height);border-bottom:1px solid var(--gantt-grid-color);user-select:none;background-color:var(--gantt-bg-color);pointer-events:none;width:100%;overflow:hidden}.gantt-header-row{position:relative;height:50%;width:100%}.gantt-header-row.primary{border-bottom:1px solid var(--gantt-grid-color);font-weight:600}.gantt-header-cell{position:absolute;height:100%;display:flex;align-items:center;justify-content:center;text-align:center;font-size:12px;color:var(--text-normal);border-right:1px solid var(--gantt-grid-color);box-sizing:border-box;background-color:var(--gantt-bg-color);pointer-events:auto}.gantt-body{position:relative;overflow:auto;height:100%;padding-top:var(--gantt-header-height);margin-top:calc(var(--gantt-header-height) * -1)}.gantt-grid{position:absolute;top:var(--gantt-header-height);left:0;height:calc(100% - var(--gantt-header-height));min-width:100%}.gantt-grid-column{position:absolute;top:0;height:100%;border-right:1px solid var(--gantt-grid-color);box-sizing:border-box}.gantt-grid-column.today{background-color:var(--gantt-today-color)}.gantt-grid-row{position:absolute;left:0;border-bottom:1px solid var(--gantt-grid-color);box-sizing:border-box;background-color:var(--gantt-row-color)}.gantt-grid-row:nth-child(odd){background-color:var(--gantt-bg-color)}.gantt-bars{position:absolute;top:var(--gantt-header-height);left:0;height:calc(100% - var(--gantt-header-height));min-width:100%;pointer-events:none}.gantt-task-container{position:absolute;box-sizing:border-box;pointer-events:auto;cursor:pointer;transition:transform .1s ease}.gantt-task-container:hover{z-index:10;transform:translateY(-2px)}.gantt-task-bar.milestone{background-color:var(--gantt-milestone-color);width:15px!important;height:15px!important;border-radius:50%;transform:rotate(45deg);top:50%;margin-top:-7.5px;left:50%;margin-left:-7.5px}.gantt-task-progress{position:absolute;top:0;left:0;height:100%;background-color:var(--gantt-progress-color);opacity:.7}.gantt-task-label{position:absolute;left:calc(100% + 8px);top:0;white-space:nowrap;font-size:12px;color:var(--text-normal);line-height:var(--gantt-bar-height)}.gantt-task-container.right-aligned .gantt-task-label{left:auto;right:calc(100% + 8px);text-align:right}@media (max-width: 680px){.gantt-header-cell{font-size:10px}.gantt-task-label{font-size:10px}}.gantt-chart-container{display:flex;flex-direction:column;height:100%;overflow:hidden;position:relative}.gantt-header-container{height:40px;flex-shrink:0;overflow:hidden;position:relative;border-bottom:1px solid var(--background-modifier-border);background-color:var(--background-secondary)}.gantt-header-svg{display:block}.gantt-header-tick-major,.gantt-header-tick-minor,.gantt-header-tick-day,.gantt-header-today-marker{stroke:var(--background-modifier-border);stroke-width:1}.gantt-header-tick-major{stroke-width:1.5}.gantt-header-today-marker{stroke:var(--color-orange);stroke-width:1.5;stroke-dasharray:4,2}.gantt-header-label-major,.gantt-header-label-minor,.gantt-header-label-day{font-size:var(--font-ui-small);fill:var(--text-muted);user-select:none;pointer-events:none}.gantt-header-label-major{font-weight:500;fill:var(--text-normal)}.gantt-scroll-container{flex-grow:1;overflow:auto;position:relative}.gantt-content-wrapper{position:relative;background:var(--background-primary)}.gantt-grid-line-major,.gantt-grid-line-minor{stroke:var(--background-modifier-border-hover);stroke-width:.5}.gantt-grid-line-major{stroke-width:1}.gantt-grid-line-horizontal{stroke:var(--background-modifier-border);stroke-width:1}.gantt-grid-today-marker{stroke:var(--color-orange);stroke-width:1;stroke-dasharray:4,2}.gantt-task-item{cursor:pointer}.gantt-task-bar{fill:var(--color-blue);stroke:var(--color-blue-hover);stroke-width:.5;transition:fill .1s ease}.gantt-task-item:hover .gantt-task-bar{fill:var(--color-accent)}.gantt-task-milestone{fill:var(--color-purple);stroke:var(--color-purple);stroke-width:1;transition:fill .1s ease}.gantt-task-item:hover .gantt-task-milestone{fill:var(--color-accent)}.gantt-task-item.status-done .gantt-task-bar,.gantt-task-item.status-done .gantt-task-milestone{fill:var(--color-green);stroke:var(--color-green);opacity:.7}.gantt-task-item.status-cancelled .gantt-task-bar,.gantt-task-item.status-cancelled .gantt-task-milestone{fill:var(--color-red);stroke:var(--color-red);opacity:.6;text-decoration:line-through}.gantt-task-label-fo{pointer-events:none;overflow:hidden;user-select:none}.gantt-task-label-markdown{color:var(--text-on-accent);font-size:var(--font-ui-smaller);line-height:1.3;padding:0 2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:flex;align-items:center;height:100%}.gantt-task-label-markdown p{margin:0!important}.gantt-milestone-label-container p{margin-block-start:0;margin-block-end:0;margin-inline-start:0;margin-inline-end:0;color:var(--text-normal);font-size:var(--font-ui-smaller);line-height:1.3;padding:0 2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:flex;align-items:center;height:100%}.gantt-task-item.status-done .gantt-task-label-markdown{color:var(--text-on-accent)}.gantt-task-item.status-cancelled .gantt-task-label-markdown{color:var(--text-on-accent);text-decoration:line-through}.gantt-milestone-label{fill:var(--text-normal)}.gantt-filter-area{display:flex;align-items:center;justify-content:flex-end;width:100%;padding-left:var(--size-2-2);padding-right:var(--size-4-2);background-color:var(--background-primary)}.gantt-filter-area .filter-component{flex:1}.gantt-offscreen-indicator{position:absolute;top:calc(50% + 20px);transform:translateY(-50%);width:8px;height:8px;background-color:#80808099;border-radius:50%;z-index:10;pointer-events:none;display:none;transition:opacity .2s ease-in-out;opacity:1}.gantt-offscreen-indicator[style*="display: none"]{opacity:0}.gantt-offscreen-indicator-left{left:5px}.gantt-offscreen-indicator-right{right:5px}.gantt-indicator-container{position:absolute;top:0;bottom:0;width:var(--size-4-3);z-index:10;pointer-events:none;overflow:hidden}.gantt-indicator-container-left{left:0}.gantt-indicator-container-right{right:0}.gantt-single-indicator{position:absolute;left:var(--size-2-1);width:var(--size-4-2);height:var(--size-4-2);border-radius:50%;background-color:var(--text-faint);pointer-events:auto;cursor:default}.gantt-single-indicator:hover{background-color:var(--text-accent)}.gantt-chart-container .gantt-indicator-container{top:calc(var(--header-height, 40px) + var(--filter-height, 0px));bottom:15px}.gantt-chart-container .gantt-indicator-container-right{right:15px}.gantt-task-label p{margin:0;line-height:var(--gantt-bar-height);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.task-property-container{display:flex;flex-direction:column;height:100%;width:100%;overflow:hidden}.task-property-content{display:flex;flex-direction:row;flex:1;overflow:hidden}.task-property-left-column{width:max(120px,30%);min-width:min(120px,30%);max-width:300px;display:flex;flex-direction:column;border-right:1px solid var(--background-modifier-border);overflow:hidden}.is-phone .task-property-left-column{max-width:100%}.task-property-right-column{flex:1;display:flex;flex-direction:column;overflow:hidden}.task-property-sidebar-header{display:flex;justify-content:space-between;align-items:center;padding:var(--size-4-2) var(--size-4-4);border-bottom:1px solid var(--background-modifier-border);height:var(--size-4-10)}.task-property-sidebar-title{font-weight:600;font-size:14px}.multi-select-mode .task-property-multi-select-btn{color:var(--color-accent)}.task-property-multi-select-btn{cursor:pointer;color:var(--text-muted);display:flex;align-items:center;justify-content:center}.task-property-multi-select-btn:hover{color:var(--text-normal)}.task-property-sidebar-list{flex:1;overflow-y:auto;padding:var(--size-4-2)}.task-property-list-item{display:flex;align-items:center;padding:4px 12px;cursor:pointer;border-radius:var(--radius-s)}.task-property-list-item:hover{background-color:var(--background-modifier-hover)}.task-property-list-item.selected{background-color:var(--background-modifier-active)}.task-property-icon{margin-right:8px;color:var(--text-muted);display:flex;align-items:center;justify-content:center}.task-property-name{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.task-property-count{margin-left:8px;font-size:.8em;color:var(--text-muted);background-color:var(--background-modifier-border);border-radius:10px;padding:1px 6px}.task-property-task-header{display:flex;justify-content:space-between;align-items:center;padding:var(--size-4-2) var(--size-4-4);border-bottom:1px solid var(--background-modifier-border);height:var(--size-4-10)}.task-property-task-title{font-weight:600;font-size:16px}.task-property-task-count{color:var(--text-muted)}.task-property-task-list{flex:1;overflow-y:auto}.task-property-empty-state{display:flex;align-items:center;justify-content:center;height:100%;color:var(--text-muted);font-style:italic;padding:16px}.is-phone .task-property-left-column{position:absolute;left:0;top:0;height:100%;z-index:10;background-color:var(--background-secondary);width:100%;transform:translate(-100%);transition:transform .3s ease-in-out;border-right:1px solid var(--background-modifier-border)}.is-phone .task-property-left-column.is-visible{transform:translate(0)}.is-phone .task-property-sidebar-toggle{display:flex;align-items:center;justify-content:center;margin-right:8px}.is-phone .task-property-sidebar-close{--icon-size: var(--size-4-4);position:absolute;top:var(--size-4-2);right:10px;z-index:15;display:flex;align-items:center;justify-content:center}.is-phone .task-property-container:has(.task-property-left-column.is-visible):before{content:"";position:absolute;top:0;left:0;width:100%;height:100%;background-color:var(--background-modifier-cover);opacity:.5;z-index:5;transition:opacity .3s ease-in-out}.is-phone .task-property-container{position:relative;overflow:hidden}.is-phone .task-property-sidebar-header:has(.task-property-sidebar-close){padding-right:var(--size-4-12)}.table-view-adapter{width:100%;display:flex;flex-direction:column;gap:0;height:100%;overflow:hidden}.task-table-container{display:flex;flex-direction:column;height:100%;overflow:hidden;position:relative;background-color:var(--background-primary)}.task-table{width:100%;border-collapse:collapse;table-layout:fixed;font-size:var(--font-ui-small);flex:1;min-height:0;min-width:max-content}.task-table-wrapper{flex:1;overflow:auto;min-height:0;position:relative;overflow-x:auto;overflow-y:auto;scroll-behavior:smooth}.task-table-header{position:sticky;top:0;z-index:10;background-color:var(--background-secondary);border-bottom:2px solid var(--background-modifier-border);min-width:max-content}.task-table-header-row{height:40px}.task-table-header-cell{padding:8px 12px;text-align:left;font-weight:600;color:var(--text-muted);border-right:1px solid var(--background-modifier-border);position:relative;user-select:none;background-color:var(--background-secondary);white-space:nowrap}.task-table-header-cell:last-child{border-right:none}.task-table-header-cell.sortable{cursor:pointer}.task-table-header-cell.sortable:hover{background-color:var(--background-modifier-hover)}.task-table-header-content{display:flex;align-items:center;justify-content:space-between;gap:4px}.task-table-header-title{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.task-table-sort-icon{font-size:12px;opacity:.5;transition:opacity .2s;display:flex;align-items:center;width:16px;height:16px}.task-table-sort-icon.asc,.task-table-sort-icon.desc{opacity:1;color:var(--text-accent)}.task-table-resize-handle{position:absolute;top:0;right:0;width:4px;height:100%;cursor:col-resize;background-color:transparent;transition:background-color .2s}.task-table-resize-handle:hover{background-color:var(--text-accent)}.task-table-body{background-color:var(--background-primary)}.task-table-row{height:40px;border-bottom:1px solid var(--background-modifier-border);transition:background-color .2s}.task-table-row:hover{background-color:var(--background-modifier-hover)}.task-table-row.selected{background-color:var(--background-modifier-active-hover)}.task-table-row:nth-child(even){background-color:var(--background-secondary-alt)}.task-table-row:nth-child(even):hover{background-color:var(--background-modifier-hover)}.task-table-row:nth-child(even).selected{background-color:var(--background-modifier-active-hover)}.task-table-cell{padding:8px 12px;vertical-align:middle;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.task-table-cell:last-child{border-right:none}.task-table-cell.editing{padding:0}.task-table-tree-indent{display:inline-flex;align-items:center;gap:4px}.task-table-cell:has(.task-table-expand-btn){padding-left:0}.task-table-row.task-table-subtask{background-color:var(--background-secondary)}.task-table-expand-btn{cursor:pointer;user-select:none;width:20px;height:20px;padding:0;display:flex;align-items:center;justify-content:center;border-radius:2px;font-size:10px;transition:background-color .2s}.task-table-expand-btn:hover{background-color:var(--background-modifier-hover)}.task-table-row-level-1 .task-table-cell:first-child{padding-left:32px}.task-table-row-level-2 .task-table-cell:first-child{padding-left:52px}.task-table-row-level-3 .task-table-cell:first-child{padding-left:72px}.task-table-row-level-4 .task-table-cell:first-child{padding-left:92px}.task-table-row-level-5 .task-table-cell:first-child{padding-left:112px}.task-table-text{color:var(--text-normal)}.task-table-number{text-align:right;color:var(--text-muted);font-variant-numeric:tabular-nums}.task-table-status{display:flex;align-items:center;gap:6px}.task-table-status-icon{font-size:14px;display:flex;align-items:center;width:16px;height:16px}.task-table-status-text{flex:1;overflow:hidden;text-overflow:ellipsis}.task-table-status.completed .task-table-status-icon{color:var(--text-success)}.task-table-status.in-progress .task-table-status-icon{color:var(--text-warning)}.task-table-status.abandoned .task-table-status-icon{color:var(--text-error)}.task-table-status.planned .task-table-status-icon{color:var(--text-muted)}.task-table-status.not-started .task-table-status-icon{color:var(--text-faint)}.task-table-priority{display:flex;align-items:center;gap:6px}.task-table-priority.clickable-priority{cursor:pointer;padding:4px;border-radius:4px;transition:background-color .2s}.task-table-priority.clickable-priority:hover{background-color:var(--background-modifier-hover)}.task-table-priority-icon{font-size:14px;display:flex;align-items:center;width:16px;height:16px}.task-table-priority-icon.high{color:var(--text-error)}.task-table-priority-icon.medium{color:var(--text-warning)}.task-table-priority-icon.low{color:var(--text-muted)}.task-table-priority-text{flex:1;overflow:hidden;text-overflow:ellipsis}.task-table-priority-empty{color:var(--text-faint);font-style:italic}.task-table-date{display:flex;flex-direction:column;gap:2px;cursor:pointer;transition:background-color .2s;padding:4px;border-radius:4px}.task-table-date:hover{background-color:var(--background-modifier-hover)}.task-table-date-text{font-size:var(--font-ui-small);color:var(--text-normal)}.task-table-date-relative{font-size:var(--font-ui-smaller);font-weight:500}.task-table-date-relative.today{color:var(--text-success)}.task-table-date-relative.tomorrow{color:var(--text-accent)}.task-table-date-relative.yesterday{color:var(--text-muted)}.task-table-date-relative.overdue{color:var(--text-error)}.task-table-date-relative.upcoming{color:var(--text-warning)}.task-table-date-empty{color:var(--text-faint);font-style:italic}.task-table-tags{display:flex;flex-wrap:wrap;gap:4px;align-items:center}.task-table-tag-chip{background-color:var(--background-modifier-accent);color:var(--text-accent);padding:2px 6px;border-radius:8px;font-size:var(--font-ui-smaller);font-weight:500;white-space:nowrap}.task-table-tags-empty{color:var(--text-faint);font-style:italic}.task-table-text-input,.task-table-tags-input{border:none!important;background:transparent!important;outline:none!important;width:100%!important;padding:0!important;font:inherit!important;color:var(--text-normal)!important}.task-table-text-input:focus,.task-table-tags-input:focus{background-color:var(--background-modifier-form-field)!important;border-radius:3px!important;padding:2px 4px!important}.task-count-icon{font-size:16px;display:flex;align-items:center;width:16px;height:16px}.task-table-empty-row{height:80px}.task-table-empty-cell{text-align:center;color:var(--text-muted);font-style:italic;vertical-align:middle}.task-table-loading{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);padding:20px;background-color:var(--background-primary);border:1px solid var(--background-modifier-border);border-radius:8px;color:var(--text-muted);font-size:var(--font-ui-small);z-index:100}.task-table.resizing{user-select:none}.task-table.resizing *{cursor:col-resize!important}.virtual-scroll-spacer{pointer-events:none;visibility:hidden}@media (max-width: 768px){.task-table-container{font-size:var(--font-ui-smaller)}.task-table-wrapper{overflow-x:auto}.task-table{min-width:800px}.task-table-header-cell,.task-table-cell{padding:6px 8px}.task-table-row{height:36px}.task-table-header-row{height:36px}}.theme-dark .task-table-container{border-color:var(--background-modifier-border)}.theme-dark .task-table-row:nth-child(even){background-color:var(--background-primary-alt)}@media (prefers-contrast: high){.task-table-container{border-width:2px}.task-table-header-cell,.task-table-cell{border-width:1px}.task-table-row{border-bottom-width:1px}}@media print{.task-table-container{border:none;overflow:visible;height:auto}.task-table-header{position:static}.task-table-resize-handle{display:none}.task-table-expand-btn{display:none}}.virtual-scroll-spacer-top{pointer-events:none}.virtual-scroll-spacer-top td{padding:0!important;border:none!important;background:transparent!important}.task-table-context-menu{background:var(--background-primary);border:1px solid var(--background-modifier-border);border-radius:4px;box-shadow:0 2px 8px #00000026;z-index:1000;min-width:120px}.task-table-context-menu-item{padding:6px 12px;cursor:pointer;transition:background-color .1s ease}.task-table-context-menu-item:hover{background-color:var(--background-modifier-hover)}.task-table-date-input{cursor:pointer;background:var(--background-primary);border:1px solid var(--background-modifier-border);border-radius:3px;padding:4px 8px;width:100%}.task-table-date-input:hover{border-color:var(--background-modifier-border-hover)}.task-table-date-input:focus{border-color:var(--interactive-accent);outline:none}.task-table-project-input,.task-table-context-input,.task-table-tags-input{background:var(--background-primary);border:1px solid var(--background-modifier-border);border-radius:3px;padding:4px 8px;width:100%}.task-table-project-input:focus,.task-table-context-input:focus,.task-table-tags-input:focus{border-color:var(--interactive-accent);outline:none}.task-table-row.selected{background-color:var(--background-modifier-hover)}.task-table-row:hover{background-color:var(--background-modifier-hover-weak)}@media (max-width: 768px){.task-table{font-size:.9em}th[data-column-id=rowNumber]{max-width:40px!important;min-width:40px!important;width:40px!important}.task-table-tree-container{gap:0!important}.task-table-expand-btn{margin-right:0!important}td[data-column-id=rowNumber]{max-width:40px!important;min-width:40px!important;width:40px!important}.task-table-header-cell,.task-table-cell{padding:6px 4px}}.task-table-header-bar{display:flex;justify-content:space-between;align-items:center;padding:6px 8px;background-color:var(--background-secondary);border-bottom:1px solid var(--background-modifier-border);border-radius:6px 6px 0 0;margin-bottom:0;flex-shrink:0;min-height:40px}.table-header-left{display:flex;align-items:center;gap:12px}.table-header-right{display:flex;align-items:center;gap:8px}.task-count-container{display:flex;align-items:center;gap:8px;padding:6px 12px;background-color:var(--background-primary);border-radius:4px;border:1px solid var(--background-modifier-border)}.task-count-text{font-size:var(--font-ui-small);font-weight:500;color:var(--text-normal)}.table-controls-container{display:flex;align-items:center;gap:8px}.table-control-btn{display:flex;align-items:center;gap:6px;padding:8px 12px;background-color:var(--background-primary);border:1px solid var(--background-modifier-border);border-radius:4px;cursor:pointer;font-size:var(--font-ui-small);color:var(--text-normal);transition:all .2s ease;box-shadow:unset!important}.table-control-btn:hover{background-color:var(--background-modifier-hover)}.table-control-btn:active{background-color:var(--background-modifier-active)}.tree-mode-btn.active{background-color:var(--text-accent);color:var(--text-on-accent);border-color:var(--text-accent)}.tree-mode-icon,.refresh-icon,.column-icon{font-size:14px;display:flex;align-items:center;justify-content:center}.tree-mode-text,.refresh-text,.column-text{font-weight:500}.dropdown-arrow{font-size:10px;transition:transform .2s ease}.column-dropdown{position:relative}.column-dropdown-menu{position:absolute;top:100%;right:0;margin-top:4px;background-color:var(--background-primary);border:1px solid var(--background-modifier-border);border-radius:4px;box-shadow:var(--shadow-l);z-index:1000;min-width:200px;max-height:300px;overflow-y:auto}.column-toggle-item{display:flex;align-items:center;gap:8px;padding:8px 12px;cursor:pointer;transition:background-color .2s ease}.column-toggle-item:hover{background-color:var(--background-modifier-hover)}.column-toggle-checkbox{margin:0;cursor:pointer}.column-toggle-label{flex:1;font-size:var(--font-ui-small);color:var(--text-normal);cursor:pointer;margin:0}@media (max-width: 768px){.task-table-header-bar{flex-direction:column;gap:12px;align-items:stretch}.table-header-left{display:none}.table-header-left,.table-header-right{justify-content:center}.table-controls-container{justify-content:center;flex-wrap:wrap}.table-control-btn{flex:1;min-width:100px;justify-content:center}.column-dropdown-menu{right:auto;left:0;width:100%}}.theme-dark .task-table-header-bar{background-color:var(--background-secondary-alt)}.theme-dark .column-dropdown-menu{background-color:var(--background-primary-alt);border-color:var(--background-modifier-border-hover)}.custom-suggest-dropdown{background-color:var(--background-primary);border:1px solid var(--background-modifier-border);border-radius:4px;box-shadow:var(--shadow-l);z-index:1000;position:absolute;max-height:200px;overflow-y:auto;min-width:150px}.custom-suggest-dropdown .suggestion-item{padding:8px 12px;cursor:pointer;border-bottom:1px solid var(--background-modifier-border);transition:background-color .2s;font-size:var(--font-ui-small);color:var(--text-normal)}.custom-suggest-dropdown .suggestion-item:last-child{border-bottom:none}.custom-suggest-dropdown .suggestion-item:hover,.custom-suggest-dropdown .suggestion-item.selected{background-color:var(--background-modifier-hover)}.custom-suggest-dropdown .suggestion-item.selected{color:var(--text-accent)}.task-table-subtask{border-left:2px solid var(--background-modifier-border-hover)}.task-table-parent .task-table-cell:first-child{font-weight:500}.task-table-subtask-cell{border-left:1px solid var(--background-modifier-border-focus)}.task-table-tree-container{display:flex;align-items:center;gap:6px;width:100%}.task-table-tree-structure{display:flex;align-items:center;gap:2px;flex-shrink:0}.task-table-tree-line{font-family:monospace;font-size:12px;color:var(--text-faint);line-height:1;width:16px;text-align:center}.task-table-tree-connector{color:var(--text-muted)}.task-table-tree-vertical{color:var(--text-faint)}.task-table-subtask-indicator{font-size:10px;color:var(--text-accent);margin-right:6px;margin-left:4px;flex-shrink:0;font-weight:bold}.task-table-top-level-expand{margin-right:6px}.task-table-content-wrapper{flex:1;min-width:0}.task-table-child-indicator{font-size:10px;color:var(--text-muted);margin-left:6px;flex-shrink:0}.task-table-status.clickable-status{cursor:pointer;padding:4px;border-radius:4px;transition:background-color .2s}.task-table-status.clickable-status:hover{background-color:var(--background-modifier-hover)}.task-table-priority-icon.highest{color:var(--text-error);filter:brightness(1.2)}.task-table-priority-icon.lowest{color:var(--text-faint)}.task-table-expand-btn.clickable-icon{opacity:.7;transition:opacity .2s,background-color .2s}.task-table-expand-btn.clickable-icon:hover{opacity:1}.task-table-row-level-1 .task-table-cell:first-child,.task-table-row-level-2 .task-table-cell:first-child,.task-table-row-level-3 .task-table-cell:first-child,.task-table-row-level-4 .task-table-cell:first-child,.task-table-row-level-5 .task-table-cell:first-child{padding-left:12px}.tg-quadrant-component-container{height:100%;display:flex;flex-direction:column;overflow:hidden;background:var(--background-primary);width:100%}.tg-quadrant-header{display:flex;align-items:center;justify-content:space-between;padding:var(--size-4-3) var(--size-4-4);background:var(--background-primary);flex-shrink:0}.tg-quadrant-title{font-size:var(--font-ui-medium);font-weight:var(--font-semibold);color:var(--text-normal);margin:0}.tg-quadrant-controls{display:flex;align-items:center;gap:var(--size-2-3)}.tg-quadrant-sort-select{padding:var(--size-2-2) var(--size-2-3);border:1px solid var(--background-modifier-border);border-radius:var(--radius-s);background:var(--background-primary);color:var(--text-normal);font-size:var(--font-ui-small);cursor:pointer;transition:border-color .2s ease}.tg-quadrant-sort-select:hover{border-color:var(--background-modifier-border-hover)}.tg-quadrant-sort-select:focus{border-color:var(--color-accent);outline:none}.tg-quadrant-toggle-empty{padding:var(--size-2-2);border:1px solid var(--background-modifier-border);border-radius:var(--radius-s);background:var(--background-primary);color:var(--text-muted);cursor:pointer;transition:all .2s ease;width:28px;height:28px;display:flex;align-items:center;justify-content:center}.tg-quadrant-toggle-empty:hover{background:var(--background-modifier-hover);color:var(--text-normal);border-color:var(--background-modifier-border-hover)}.tg-quadrant-filter-container{flex-shrink:0;border-bottom:1px solid var(--background-modifier-border)}.tg-quadrant-grid{display:grid;grid-template-columns:1fr 1fr;grid-template-rows:1fr 1fr;gap:1px;flex:1;background:var(--background-modifier-border);overflow:hidden}.tg-quadrant-column{display:flex;flex-direction:column;background:var(--background-primary);min-height:0;overflow:hidden;position:relative}.tg-quadrant-column--hidden{display:none}.tg-quadrant-column .tg-quadrant-header{padding:var(--size-4-2) var(--size-4-3);background:var(--background-secondary);border-bottom:1px solid var(--background-modifier-border);flex-shrink:0;position:relative;min-height:var(--size-4-12)}.tg-quadrant-title-container{display:flex;align-items:center;gap:var(--size-2-2);margin-bottom:var(--size-2-1)}.tg-quadrant-priority{font-size:var(--font-ui-medium);line-height:1;opacity:.8}.tg-quadrant-column .tg-quadrant-title{font-size:var(--font-ui-small);font-weight:var(--font-semibold);color:var(--text-normal);margin:0}.tg-quadrant-description{font-size:var(--font-ui-smaller);color:var(--text-muted);margin-bottom:var(--size-2-2);line-height:1.3}.tg-quadrant-count{font-size:var(--font-ui-smaller);color:var(--text-faint);background:var(--background-modifier-border);padding:var(--size-2-1) var(--size-2-2);border-radius:var(--radius-s);font-weight:var(--font-medium)}.tg-quadrant-column-content{flex:1;overflow-y:auto;padding:var(--size-2-3);min-height:100px}.tg-quadrant-column-content::-webkit-scrollbar{width:8px}.tg-quadrant-column-content::-webkit-scrollbar-track{background:transparent}.tg-quadrant-column-content::-webkit-scrollbar-thumb{background:var(--background-modifier-border);border-radius:var(--radius-s)}.tg-quadrant-column-content::-webkit-scrollbar-thumb:hover{background:var(--background-modifier-border-hover)}.tg-quadrant-column-content--drop-active{background:var(--background-modifier-hover);border:2px dashed var(--color-accent);border-radius:var(--radius-m)}.quadrant-urgent-important .tg-quadrant-header:before{content:"";position:absolute;top:0;left:0;right:0;height:3px;background:var(--text-error);opacity:.6}.quadrant-not-urgent-important .tg-quadrant-header:before{content:"";position:absolute;top:0;left:0;right:0;height:3px;background:var(--color-accent);opacity:.6}.quadrant-urgent-not-important .tg-quadrant-header:before{content:"";position:absolute;top:0;left:0;right:0;height:3px;background:var(--text-warning);opacity:.6}.quadrant-not-urgent-not-important .tg-quadrant-header:before{content:"";position:absolute;top:0;left:0;right:0;height:3px;background:var(--text-muted);opacity:.4}.tg-quadrant-card{background:var(--background-primary);border:1px solid var(--background-modifier-border);border-radius:var(--radius-s);margin-bottom:var(--size-2-3);padding:var(--size-4-2);cursor:pointer;transition:all .15s ease;position:relative}.tg-quadrant-card:hover{background:var(--background-modifier-hover);border-color:var(--background-modifier-border-hover);transform:translateY(-1px);box-shadow:var(--shadow-s)}.tg-quadrant-card:active{transform:translateY(0)}.tg-quadrant-card:last-child{margin-bottom:0}.tg-quadrant-card-header{display:flex;align-items:flex-start;justify-content:space-between;margin-bottom:var(--size-2-2);gap:var(--size-2-2)}.tg-quadrant-card-checkbox{flex-shrink:0;margin-top:2px}.tg-quadrant-card-actions{flex-shrink:0;opacity:0;transition:opacity .2s ease}.tg-quadrant-card:hover .tg-quadrant-card-actions{opacity:1}.tg-quadrant-card-more-btn{background:none;border:none;padding:var(--size-2-1);border-radius:var(--radius-s);color:var(--text-muted);cursor:pointer;transition:all .2s ease;width:24px;height:24px;display:flex;align-items:center;justify-content:center}.tg-quadrant-card-more-btn:hover{background:var(--background-modifier-hover);color:var(--text-normal)}.tg-quadrant-card-content{margin-bottom:var(--size-2-2)}.tg-quadrant-card-title{font-size:var(--font-ui-small);line-height:1.4;color:var(--text-normal);margin-bottom:var(--size-2-1);word-wrap:break-word;font-weight:var(--font-normal)}.tg-quadrant-card-priority{font-size:var(--font-ui-small);margin-left:var(--size-2-1);opacity:.8}.tg-quadrant-card-tags{display:flex;flex-wrap:wrap;gap:var(--size-2-1);margin-top:var(--size-2-2)}.tg-quadrant-card-tag{background:var(--background-modifier-border);color:var(--text-muted);padding:var(--size-2-1) var(--size-2-2);border-radius:var(--radius-s);font-size:var(--font-ui-smaller);font-weight:var(--font-medium);border:1px solid transparent;transition:all .2s ease}.tg-quadrant-card-tag:hover{background:var(--background-modifier-hover);color:var(--text-normal)}.tg-quadrant-tag--urgent{background:var(--background-modifier-error);color:var(--text-error);border-color:var(--text-error)}.tg-quadrant-tag--important{background:var(--background-modifier-accent);color:var(--text-accent);border-color:var(--color-accent)}.tg-quadrant-card-metadata{display:flex;align-items:center;justify-content:space-between;font-size:var(--font-ui-smaller);color:var(--text-faint);gap:var(--size-2-2)}.tg-quadrant-card-due-date{display:flex;align-items:center;gap:var(--size-2-1);background:var(--background-modifier-border);padding:var(--size-2-1) var(--size-2-2);border-radius:var(--radius-s);font-weight:var(--font-medium)}.tg-quadrant-card-due-date-icon{width:12px;height:12px;opacity:.7}.tg-quadrant-card-due-date--urgent{color:var(--text-warning)}.tg-quadrant-card-due-date--overdue{color:var(--text-error)}.tg-quadrant-card-file-info{display:flex;align-items:center;justify-content:space-between;gap:var(--size-4-2);opacity:.7;transition:opacity .2s ease}.tg-quadrant-card:hover .tg-quadrant-card-file-info{opacity:1}.tg-quadrant-card-file-icon{width:12px;height:12px}.tg-quadrant-card-file-name{font-size:var(--font-ui-smaller);max-width:120px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tg-quadrant-card-line{color:var(--text-faint);font-size:var(--font-ui-smaller);opacity:.6;font-weight:var(--font-medium)}.tg-quadrant-card--priority-highest{border-left:3px solid var(--text-error)}.tg-quadrant-card--priority-high{border-left:3px solid var(--text-warning)}.tg-quadrant-card--priority-medium{border-left:3px solid var(--color-accent)}.tg-quadrant-card--priority-low{border-left:3px solid var(--text-success)}.tg-quadrant-card--priority-lowest{border-left:3px solid var(--text-muted)}.tg-quadrant-card--dragging{box-shadow:var(--shadow-l)}.tg-quadrant-card--chosen{background:var(--background-modifier-hover);border-color:var(--color-accent);box-shadow:var(--shadow-s)}.tg-quadrant-card--drag{box-shadow:var(--shadow-l);z-index:1000;border-color:var(--color-accent)}.tg-quadrant-empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;height:120px;color:var(--text-faint);text-align:center;padding:var(--size-4-4);opacity:.8}.tg-quadrant-empty-icon{width:32px;height:32px;margin-bottom:var(--size-2-3);opacity:.5;color:var(--text-faint)}.tg-quadrant-empty-message{font-size:var(--font-ui-small);line-height:1.4;font-weight:var(--font-medium)}@media (max-width: 768px){.tg-quadrant-grid{grid-template-columns:1fr;grid-template-rows:repeat(4,1fr)}.tg-quadrant-header{padding:var(--size-2-3) var(--size-4-2)}.tg-quadrant-column .tg-quadrant-header{padding:var(--size-2-3) var(--size-4-2)}.tg-quadrant-card{padding:var(--size-2-3)}.tg-quadrant-card-title{font-size:var(--font-ui-smaller)}.tg-quadrant-controls{gap:var(--size-2-2)}}.tg-quadrant-card:focus{outline:2px solid var(--color-accent);outline-offset:2px}.tg-quadrant-card-more-btn:focus{outline:2px solid var(--color-accent);outline-offset:2px}@keyframes cardComplete{0%{transform:scale(1)}50%{transform:scale(1.05)}to{transform:scale(1)}}.tg-quadrant-card--completed{animation:cardComplete .3s ease-in-out}.tg-quadrant-card:hover .tg-quadrant-card-title{color:var(--text-normal)}.tg-quadrant-card:hover .tg-quadrant-card-priority{opacity:1}.tg-quadrant-card-content{position:relative}.tg-quadrant-loading{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:2rem;color:var(--text-muted);min-height:100px}.tg-quadrant-loading-spinner{margin-bottom:1rem}.tg-quadrant-spinner{width:24px;height:24px;color:var(--color-accent)}.tg-quadrant-loading-message{font-size:.9rem;opacity:.7}.tg-quadrant-dragging{cursor:grabbing!important}.tg-quadrant-dragging *{pointer-events:none}.tg-quadrant-card--ghost{opacity:.4;background:var(--background-modifier-border);border:2px dashed var(--color-accent)}.tg-quadrant-card--chosen{box-shadow:0 8px 25px #00000026;transform:scale(1.02);z-index:1000;background:var(--background-primary);border:2px solid var(--color-accent)}.tg-quadrant-card--drag{opacity:.8;box-shadow:0 12px 30px #0003}.tg-quadrant-card--fallback{opacity:.9;background:var(--background-primary);border:2px solid var(--color-accent);border-radius:var(--radius-m);box-shadow:0 8px 25px #00000026}.tg-quadrant-column--drag-target{background:var(--background-modifier-hover);border:2px dashed var(--color-accent);border-radius:var(--radius-m)}.tg-quadrant-column-content--drop-active{background:var(--background-modifier-active-hover);border:2px dashed var(--color-accent);border-radius:var(--radius-s);min-height:60px;position:relative}.tg-quadrant-column-content--drop-active:before{content:"Drop task here";position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);color:var(--color-accent);font-size:.9rem;font-weight:500;opacity:.7;pointer-events:none;z-index:1}.tg-quadrant-update-feedback{position:fixed;top:20px;right:20px;z-index:10000;opacity:0;transform:translate(100%);transition:all .3s ease;pointer-events:none}.tg-quadrant-feedback--show{opacity:1;transform:translate(0)}.tg-quadrant-feedback--hide{opacity:0;transform:translate(100%)}.tg-quadrant-feedback-content{display:flex;align-items:center;gap:.5rem;padding:.75rem 1rem;background:var(--background-primary);border:1px solid var(--background-modifier-border);border-radius:var(--radius-m);box-shadow:0 4px 12px #0000001a;min-width:200px}.tg-quadrant-feedback--error .tg-quadrant-feedback-content{background:var(--background-modifier-error);border-color:var(--text-error);color:var(--text-error)}.tg-quadrant-feedback-icon{font-size:1.2rem;flex-shrink:0}.tg-quadrant-feedback-text{font-size:.9rem;font-weight:500}.tg-quadrant-card{transition:all .2s ease;cursor:grab}.tg-quadrant-card:hover{transform:translateY(-2px);box-shadow:0 4px 12px #0000001a}.tg-quadrant-card:active{cursor:grabbing}.tg-quadrant-empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:2rem 1rem;text-align:center;color:var(--text-muted);min-height:120px;border:2px dashed var(--background-modifier-border);border-radius:var(--radius-m);margin:.5rem 0}.tg-quadrant-empty-icon{margin-bottom:.75rem;opacity:.5}.tg-quadrant-empty-message{font-size:.9rem;line-height:1.4;max-width:200px}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.tg-quadrant-spinner circle{animation:spin 2s linear infinite;transform-origin:center}@media (max-width: 768px){.tg-quadrant-update-feedback{top:10px;right:10px;left:10px;transform:translateY(-100%)}.tg-quadrant-feedback--show{transform:translateY(0)}.tg-quadrant-feedback--hide{transform:translateY(-100%)}.tg-quadrant-feedback-content{min-width:auto;width:100%}}.theme-dark .tg-quadrant-card--chosen{background:var(--background-primary-alt);box-shadow:0 8px 25px #0000004d}.theme-dark .tg-quadrant-card--fallback{background:var(--background-primary-alt);box-shadow:0 8px 25px #0000004d}.theme-dark .tg-quadrant-feedback-content{box-shadow:0 4px 12px #0000004d}@media (prefers-reduced-motion: reduce){.tg-quadrant-card,.tg-quadrant-update-feedback,.tg-quadrant-card--chosen,.tg-quadrant-card--drag{transition:none;animation:none}.tg-quadrant-spinner circle{animation:none}}.tg-quadrant-scroll-container{flex:1;overflow-y:auto;overflow-x:hidden;max-height:70vh;scrollbar-width:thin;scrollbar-color:var(--background-modifier-border) transparent}.tg-quadrant-scroll-container::-webkit-scrollbar{width:6px}.tg-quadrant-scroll-container::-webkit-scrollbar-track{background:transparent}.tg-quadrant-scroll-container::-webkit-scrollbar-thumb{background:var(--background-modifier-border);border-radius:3px}.tg-quadrant-scroll-container::-webkit-scrollbar-thumb:hover{background:var(--background-modifier-border-hover)}.tg-quadrant-load-more{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:1rem;color:var(--text-muted);border-top:1px solid var(--background-modifier-border);margin-top:.5rem}.tg-quadrant-load-more-spinner{margin-bottom:.5rem}.tg-quadrant-load-more-message{font-size:.8rem;opacity:.7}.tg-quadrant-column{display:flex;flex-direction:column;height:100%;min-height:400px;max-height:80vh}.tg-quadrant-column-content{flex:1;display:flex;flex-direction:column;gap:.5rem;padding:.5rem}.tg-quadrant-scroll-container{scroll-behavior:smooth}.tg-quadrant-column.loading-more .tg-quadrant-load-more{opacity:1;pointer-events:none}.tg-quadrant-load-more{min-height:40px;transition:opacity .2s ease}.tg-quadrant-column-content:empty:before{content:"";display:block;min-height:100px}.tg-quadrant-grid{display:grid;grid-template-columns:repeat(2,1fr);height:calc(100vh - 200px);min-height:400px}@media (max-width: 1200px){.tg-quadrant-scroll-container{max-height:60vh}.tg-quadrant-column{max-height:70vh}}@media (max-width: 768px){.tg-quadrant-scroll-container{max-height:50vh}.tg-quadrant-column{max-height:60vh;min-height:300px}.tg-quadrant-grid{grid-template-columns:1fr;height:auto}}.tg-quadrant-column-content{contain:layout style;will-change:contents}.tg-quadrant-card{contain:layout style paint}.tg-quadrant-scroll-container.has-scroll:before{content:"";position:sticky;top:0;height:1px;background:linear-gradient(to bottom,var(--background-primary),transparent);z-index:1}.tg-quadrant-scroll-container.has-scroll:after{content:"";position:sticky;bottom:0;height:1px;background:linear-gradient(to top,var(--background-primary),transparent);z-index:1}.tg-habit-component-container{width:100%;display:flex;flex-direction:column;gap:1rem;padding:1rem;height:100%;overflow-y:auto}.habit-list-container{display:grid;grid-template-columns:repeat(1,minmax(0,1fr));gap:1rem;width:100%}@media screen and (max-width: 480px){.habit-list-container{padding:.5rem;gap:.75rem}}@media screen and (min-width: 768px){.habit-list-container{margin-left:auto;margin-right:auto;max-width:400px;display:flex;flex-direction:column}}@media screen and (min-width: 1024px){.habit-list-container{max-width:500px}}.habit-card-wrapper{width:100%;min-height:fit-content}.habit-card{border:1px solid var(--background-modifier-border);border-radius:var(--radius-m);background-color:var(--background-secondary);color:var(--text-normal);overflow:hidden;display:flex;flex-direction:column;width:100%;height:100%;min-height:fit-content}.habit-card .card-header{display:flex;align-items:center;justify-content:space-between;padding:.5rem 1rem;gap:.5rem}.habit-card .card-title{display:flex;align-items:center;gap:.5rem;font-size:var(--font-ui-large);font-weight:600;flex-grow:1;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.habit-name.habit-name:hover{text-decoration:underline;cursor:pointer}.habit-card .card-content-wrapper{padding:.75rem 1rem;flex-grow:1}.daily-habit-card .habit-checkbox{--checkbox-size: 1.25rem;cursor:pointer;accent-color:var(--interactive-accent)}.daily-habit-card .card-content-wrapper{padding:0 1rem .75rem}.count-habit-card .card-content-wrapper{display:flex;flex-direction:column;gap:.75rem;align-items:center}.count-habit-card .habit-icon-button{--icon-size: 2rem;height:4rem;width:4rem;aspect-ratio:1;padding:0;cursor:pointer;border-radius:var(--radius-s);display:flex;justify-content:center;align-items:center;font-size:1.5rem}.count-habit-card .habit-icon-button{color:var(--icon-color)}.count-habit-card .habit-icon-button:hover{background-color:var(--background-secondary)}.count-habit-card .habit-card-name{font-size:var(--font-ui-large);font-weight:600}.count-habit-card .habit-active-day{font-size:var(--font-ui-small);color:var(--text-muted);font-weight:400}.count-habit-card .habit-info{display:flex;flex-direction:column;align-items:center;text-align:center;flex-grow:1}.count-habit-card .habit-info h3{font-size:var(--font-ui-large);font-weight:600}.count-habit-card .habit-progress-area{width:100%;display:flex;flex-direction:column;align-items:center;gap:.5rem}@media (min-width: 640px){.count-habit-card .card-content-wrapper{flex-direction:row;align-items:center;gap:1rem}.count-habit-card .habit-progress-area{width:auto;min-width:150px;align-items:flex-end}.count-habit-card .habit-heatmap-small{width:100%}}.scheduled-habit-card .card-header{padding-bottom:.5rem}.scheduled-habit-card .card-content-wrapper{display:flex;flex-direction:column;gap:.75rem;align-items:center}.scheduled-habit-card .habit-heatmap-medium{width:100%}.scheduled-habit-card .habit-controls{width:100%;display:flex;flex-direction:column;gap:.5rem;align-items:center}.scheduled-habit-card .habit-event-dropdown{width:auto;margin-bottom:.5rem;width:100%}@media (min-width: 640px){.scheduled-habit-card .card-content-wrapper{flex-direction:row;align-items:flex-start;justify-content:space-between}.scheduled-habit-card .habit-heatmap-medium{width:auto;flex-grow:1;margin-right:1rem}.scheduled-habit-card .habit-controls{width:auto;min-width:150px;align-items:flex-start}}.mapping-habit-card .card-header{padding-bottom:.5rem}.mapping-habit-card .card-content-wrapper{display:flex;flex-direction:column;gap:.75rem;align-items:center;padding-top:0;padding-bottom:1.2rem}.mapping-habit-card .habit-heatmap-medium{width:100%}.mapping-habit-card .habit-controls{width:100%;display:flex;flex-direction:column;align-items:center;gap:.5rem}.mapping-habit-card .habit-mapping-button{display:flex;justify-content:center;align-items:center;font-size:1.75rem;padding:.5rem;width:100%;max-width:100px;height:3.5rem;border:1px solid var(--button-secondary-border-color);background-color:var(--button-secondary-bg);color:var(--text-normal);cursor:pointer;border-radius:var(--radius-s)}.mapping-habit-card .habit-mapping-button:hover{background-color:var(--button-secondary-hover-bg)}.mapping-habit-card .habit-slider-setting{width:100%;max-width:200px}.mapping-habit-card .habit-slider-setting .setting-item-info{display:none}.mapping-habit-card .habit-slider-setting .setting-item{width:100%;padding:0;border:none}.mapping-habit-card .habit-slider-setting .setting-item-control{width:100%}.mapping-habit-card .heatmap-md .heatmap-container-simple{gap:.5rem}@media (min-width: 640px){.mapping-habit-card .card-content-wrapper{flex-direction:row;align-items:center;justify-content:space-between}.mapping-habit-card .habit-heatmap-medium{width:auto;flex-grow:1;margin-right:1rem}.mapping-habit-card .habit-controls{width:auto;min-width:80px;flex-direction:column;align-items:center;gap:.75rem}.mapping-habit-card .habit-mapping-button{width:4rem;height:4rem}.mapping-habit-card .habit-slider-setting{width:100%;max-width:none}}.habit-progress-container{width:100%;height:.75rem;background-color:var(--background-modifier-border);border-radius:var(--radius-l);overflow:hidden;position:relative}.habit-progress-bar{height:100%;background-color:var(--interactive-accent);border-radius:var(--radius-l);transition:width .3s ease-in-out}.habit-progress-container.filled .habit-progress-text{mix-blend-mode:unset}.habit-progress-text{position:absolute;top:0;left:0;width:100%;height:100%;display:flex;justify-content:center;align-items:center;font-size:.6rem;line-height:1;color:var(--text-on-accent);mix-blend-mode:difference;font-weight:500}.tg-heatmap-root{width:100%}.heatmap-sm .heatmap-container-simple{display:grid;grid-template-columns:repeat(3,1fr);gap:3px;overflow-x:auto;padding-bottom:2px}.heatmap-md .heatmap-container-simple{display:grid;grid-template-columns:repeat(6,1fr);gap:3px;overflow-x:auto;padding-bottom:2px;justify-items:center}.heatmap-lg .heatmap-container-simple{display:grid;grid-template-columns:repeat(10,1fr);gap:var(--size-4-2);overflow-x:auto;padding-bottom:2px;justify-items:center}.heatmap-cell{border-radius:var(--radius-s);display:flex;justify-content:center;align-items:center;cursor:pointer;flex-shrink:0;background-color:var( --background-modifier-border );border:1px solid transparent}.heatmap-cell-dot{border-radius:50%}.heatmap-sm .heatmap-cell{width:.75rem;height:.75rem}.habit-heatmap-medium .heatmap-md .heatmap-cell{width:1.4rem;height:1.4rem;font-size:.7rem}.heatmap-md .heatmap-cell{width:1.1rem;height:1.1rem;font-size:.7rem}.heatmap-lg .heatmap-cell{width:1.25rem;height:1.25rem;font-size:.75rem}.heatmap-cell.filled{background-color:var(--interactive-accent);color:var(--text-on-accent)}.heatmap-cell.has-custom-content:has(.pie-dot-container){background:transparent;border:unset}.heatmap-cell.has-custom-content,.heatmap-cell.has-text-content{background-color:var(--background-secondary);border-color:var(--background-modifier-border);color:var(--text-normal)}.heatmap-cell.has-text-content{line-height:1}.pie-dot-container{width:100%;height:100%;display:flex;justify-content:center;align-items:center}.pie-dot-container svg{display:block}.habit-empty-state{text-align:center;padding:2rem 1rem;color:var(--text-muted)}.habit-empty-state h2{font-size:var(--font-ui-large);font-weight:600;margin-bottom:.5rem}.habit-empty-state p{font-size:var(--font-ui-normal);color:var(--text-faint)}.habit-icon{display:inline-block;height:1em;line-height:1;text-align:center;color:var(--text-muted);font-style:italic;margin-right:.25em;--icon-size: 1.5rem}:root{--task-completed-color: #4caf50;--task-doing-color: #80dee5;--task-in-progress-color: #f9d923;--task-abandoned-color: #eb5353;--task-planned-color: #9c27b0;--task-question-color: #2196f3;--task-important-color: #f44336;--task-star-color: #ffc107;--task-quote-color: #607d8b;--task-location-color: #795548;--task-bookmark-color: #ff9800;--task-information-color: #00bcd4;--task-idea-color: #9c27b0;--task-pros-color: #4caf50;--task-cons-color: #f44336;--task-fire-color: #ff5722;--task-key-color: #ffd700;--task-win-color: #66bb6a;--task-up-color: #4caf50;--task-down-color: #f44336;--task-note-color: #9e9e9e;--task-amount-color: #8bc34a;--task-speech-color: #03a9f4;--progress-0-color: #ae431e;--progress-25-color: #e5890a;--progress-50-color: #b4c6a6;--progress-75-color: #6bcb77;--progress-100-color: #4d96ff;--progress-background-color: #f1f1f1}.theme-dark{--task-completed-color: #4caf50;--task-doing-color: #379fa7;--task-in-progress-color: #ffc107;--task-abandoned-color: #f44336;--task-planned-color: #ce93d8;--task-question-color: #42a5f5;--task-important-color: #ef5350;--task-star-color: #ffd54f;--task-quote-color: #90a4ae;--task-location-color: #8d6e63;--task-bookmark-color: #ffb74d;--task-information-color: #26c6da;--task-idea-color: #ce93d8;--task-pros-color: #66bb6a;--task-cons-color: #ef5350;--task-fire-color: #ff7043;--task-key-color: #ffd700;--task-win-color: #81c784;--task-up-color: #66bb6a;--task-down-color: #ef5350;--task-note-color: #bdbdbd;--task-amount-color: #aed581;--task-speech-color: #29b6f6;--progress-0-color: #ae431e;--progress-25-color: #e5890a;--progress-50-color: #b4c6a6;--progress-75-color: #6bcb77;--progress-100-color: #4d96ff;--progress-background-color: #f1f1f1}.task-genius-view-config-modal{width:max(70%,500px)}.task-genius-view-config-modal .setting-item{margin-bottom:15px}.task-genius-view-config-modal .setting-item:not(.setting-item-heading) .setting-item-info{width:120px}.task-genius-view-config-modal .setting-item-control input[type=text],.task-genius-view-config-modal .setting-item-control input[type=number]{width:100%}.task-genius-view-config-modal .setting-item-description{font-size:var(--font-ui-smaller);color:var(--text-muted);margin-top:2px}.view-management-list .setting-item{border-bottom:1px solid var(--background-modifier-border);padding:10px 0;display:flex;align-items:center}.view-management-list .setting-item-info{flex-grow:1;margin-right:10px}.view-management-list .setting-item-control{display:flex;align-items:center;gap:8px}.view-management-list .setting-item-control .button-component{padding:5px;height:auto}.view-management-list .view-order-button,.view-management-list .view-delete-button{margin-left:5px}.view-management-list .setting-item:last-child{border-bottom:none}.view-management-list .setting-item-control .checkbox-container{margin:0}.tg-icon-menu{position:absolute;z-index:100;background-color:var(--background-secondary);border:1px solid var(--background-modifier-border);border-radius:var(--radius-m);box-shadow:var(--shadow-l);padding:8px;max-height:300px;width:250px;display:flex;flex-direction:column;box-sizing:border-box}.tg-icon-menu .tg-menu-search{width:100%;padding:6px 8px;margin-bottom:8px;border:1px solid var(--background-modifier-border);border-radius:var(--radius-s);background-color:var(--background-primary);color:var(--text-normal);box-sizing:border-box;flex-shrink:0}.tg-icon-menu .tg-menu-icons{flex-grow:1;overflow-y:auto;min-height:0;display:grid;grid-template-columns:repeat(auto-fill,minmax(32px,1fr));gap:4px}.tg-icon-menu .clickable-icon{display:flex;justify-content:center;align-items:center;padding:6px;border-radius:var(--radius-s);cursor:pointer;background-color:var(--background-primary);border:1px solid transparent;transition:background-color .1s ease-in-out,border-color .1s ease-in-out}.tg-icon-menu .clickable-icon:hover{background-color:var(--background-modifier-hover);border-color:var(--background-modifier-border-hover)}.tg-icon-menu .clickable-icon svg{width:20px;height:20px;color:var(--text-muted)}.task-status-widget{display:inline-flex;align-items:center;cursor:pointer;font-size:var(--font-ui-medium);font-weight:var(--font-bold)}.task-state.live-preview-mode{padding-inline-start:var(--size-4-2);padding-inline-end:var(--size-2-1)}.task-status-widget .list-bullet:after{background-color:var(--list-marker-color)!important}.task-state[data-task-state=" "]{color:var(--text-accent)}.task-state[data-task-state="/"]{color:var(--task-doing-color)}.task-state[data-task-state=">"]{color:var(--task-in-progress-color)}.task-state[data-task-state=x],.task-state[data-task-state=X]{color:var(--task-completed-color)}.task-state[data-task-state="-"]{color:var(--task-abandoned-color)}.task-state[data-task-state="<"]{color:var(--task-planned-color)}.task-state[data-task-state="?"]{color:var(--task-question-color)}.task-state[data-task-state="!"]{color:var(--task-important-color)}.task-state[data-task-state="*"]{color:var(--task-star-color)}.task-state[data-task-state='"']{color:var(--task-quote-color)}.task-state[data-task-state=l]{color:var(--task-location-color)}.task-state[data-task-state=b]{color:var(--task-bookmark-color)}.task-state[data-task-state=i]{color:var(--task-information-color)}.task-state[data-task-state=I]{color:var(--task-idea-color)}.task-state[data-task-state=p]{color:var(--task-pros-color)}.task-state[data-task-state=c]{color:var(--task-cons-color)}.task-state[data-task-state=f]{color:var(--task-fire-color)}.task-state[data-task-state=k]{color:var(--task-key-color)}.task-state[data-task-state=w]{color:var(--task-win-color)}.task-state[data-task-state=u]{color:var(--task-up-color)}.task-state[data-task-state=d]{color:var(--task-down-color)}.task-state[data-task-state=n]{color:var(--task-note-color)}.task-state[data-task-state=S]{color:var(--task-amount-color)}.task-state[data-task-state="0"],.task-state[data-task-state="1"],.task-state[data-task-state="2"],.task-state[data-task-state="3"],.task-state[data-task-state="4"],.task-state[data-task-state="5"],.task-state[data-task-state="6"],.task-state[data-task-state="7"],.task-state[data-task-state="8"],.task-state[data-task-state="9"]{color:var(--task-speech-color)}.task-fake-bullet{display:inline-block;width:5px;height:5px;border-radius:50%;background-color:var(--text-normal);margin-right:4px;vertical-align:middle}ol>.task-list-item .task-fake-bullet{display:none}ol>.task-list-item .task-state-container{margin-inline-start:0}.onboarding-modal,.onboarding-view{--dialog-width: 800px;--dialog-max-width: 90vw;--dialog-max-height: 90vh;--onboarding-spacing: var(--size-4-4);--onboarding-border-radius: var(--radius-m);--onboarding-transition: all .2s ease-in-out}.onboarding-modal .modal-content,.onboarding-view .modal-content{background-color:var(--modal-background);border-radius:var(--modal-radius);max-width:var(--dialog-max-width);max-height:var(--dialog-max-height);height:90vh;display:flex;flex-direction:column;overflow:auto;position:relative;min-height:100px}.onboarding-view{height:100%;display:flex;flex-direction:column;background-color:var(--background-primary)}.onboarding-view .view-content{height:100%;display:flex;flex-direction:column}.onboarding-modal .onboarding-header,.onboarding-view .onboarding-header{padding:var(--onboarding-spacing) var(--onboarding-spacing) var(--size-4-2) var(--onboarding-spacing);text-align:center}.onboarding-modal .onboarding-subtitle,.onboarding-view .onboarding-subtitle{color:var(--text-muted);font-size:.95em;margin:0}.onboarding-modal .onboarding-content,.onboarding-view .onboarding-content{flex:1;padding:var(--onboarding-spacing);overflow-y:auto;min-height:0}.onboarding-modal .onboarding-footer,.onboarding-view .onboarding-footer{padding:var(--size-4-2) var(--onboarding-spacing) var(--onboarding-spacing) var(--onboarding-spacing);border-top:var(--modal-border-width) solid var(--background-modifier-border);flex-shrink:0}.onboarding-modal .onboarding-buttons,.onboarding-view .onboarding-buttons{display:flex;gap:var(--size-4-2);justify-content:space-between;align-items:center}.onboarding-modal .settings-check-section,.onboarding-view .settings-check-section{margin:var(--onboarding-spacing) 0}.onboarding-modal .changes-summary-list,.onboarding-view .changes-summary-list{list-style:none;padding:0;margin:var(--size-4-2) 0;background:var(--background-secondary);border-radius:var(--onboarding-border-radius);padding:var(--size-4-2)}.onboarding-modal .changes-summary-list li,.onboarding-view .changes-summary-list li{display:flex;align-items:center;gap:var(--size-4-2);padding:var(--size-2-1) 0;color:var(--text-normal);font-size:.9em}.onboarding-modal .change-check,.onboarding-view .change-check{color:var(--color-green);font-size:1.1em;display:flex}.onboarding-modal .change-text,.onboarding-view .change-text{flex:1}.onboarding-modal .onboarding-question,.onboarding-view .onboarding-question{margin:var(--onboarding-spacing) 0;text-align:center}.onboarding-modal .question-options,.onboarding-view .question-options{display:flex;gap:var(--size-4-3);justify-content:center;margin-top:var(--size-4-3)}.onboarding-modal .question-button,.onboarding-view .question-button{padding:var(--size-4-3) var(--size-4-4);border-radius:var(--button-radius);border:none;cursor:pointer;font-size:.9em;font-weight:500;transition:var(--onboarding-transition)}.onboarding-modal .question-options .mod-cta,.onboarding-view .question-options .mod-cta{background:var(--interactive-accent);color:var(--text-on-accent)}.onboarding-modal .question-options .mod-cta:hover,.onboarding-view .question-options .mod-cta:hover{background:var(--interactive-accent-hover)}.onboarding-modal .question-button:not(.mod-cta),.onboarding-view .question-button:not(.mod-cta){background:var(--background-secondary);color:var(--text-normal);border:1px solid var(--background-modifier-border)}.onboarding-modal .question-button:not(.mod-cta):hover,.onboarding-view .question-button:not(.mod-cta):hover{background:var(--background-modifier-hover)}.onboarding-modal .welcome-section,.onboarding-view .welcome-section{display:flex;flex-direction:column;gap:var(--onboarding-spacing)}.onboarding-modal .features-overview,.onboarding-view .features-overview{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:var(--size-4-3);margin:var(--onboarding-spacing) 0}.onboarding-modal .feature-item,.onboarding-view .feature-item{display:flex;gap:var(--size-4-2);padding:var(--size-4-3);background:var(--background-secondary);border-radius:var(--onboarding-border-radius)}.onboarding-modal .feature-icon,.onboarding-view .feature-icon{font-size:1.5em;flex-shrink:0;line-height:1}.onboarding-modal .setup-note,.onboarding-view .setup-note{text-align:center;padding:var(--size-4-3);background:var(--background-secondary);border-radius:var(--onboarding-border-radius)}.onboarding-modal .setup-description,.onboarding-view .setup-description{color:var(--text-muted);font-size:.95em;line-height:1.5;margin:0}.onboarding-modal .user-level-cards,.onboarding-view .user-level-cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(250px,1fr));gap:var(--onboarding-spacing);margin:var(--onboarding-spacing) 0}.onboarding-modal .user-level-card,.onboarding-view .user-level-card{border:1px solid var(--background-modifier-border);border-radius:var(--onboarding-border-radius);padding:var(--onboarding-spacing);cursor:pointer;transition:var(--onboarding-transition);background:var(--background-primary);position:relative;overflow:hidden}.onboarding-modal .user-level-card:hover,.onboarding-modal .user-level-card.card-hover,.onboarding-view .user-level-card.card-hover{border-color:var(--interactive-accent)}.onboarding-modal .user-level-card.selected,.onboarding-view .user-level-card.selected{border-color:var(--interactive-accent);background:var(--background-modifier-hover)}.user-level-card .card-header{display:flex;align-items:center;gap:var(--size-4-2);margin-bottom:var(--size-4-2)}.user-level-card .card-icon{font-size:1.8em;line-height:1;flex-shrink:0}.user-level-card .card-title{margin:0;color:var(--text-normal);font-size:1.2em;font-weight:600}.user-level-card .card-description{color:var(--text-muted);font-size:.9em;line-height:1.4;margin:0 0 var(--size-4-2) 0}.user-level-card .card-features{margin-top:var(--size-4-2)}.user-level-card .card-features ul{margin:0;padding-left:var(--size-4-3);list-style:none}.user-level-card .card-features li{position:relative;color:var(--text-muted);font-size:.85em;line-height:1.4;margin-bottom:var(--size-2-1)}.user-level-card .card-features li:before{content:"\2022";color:var(--interactive-accent);position:absolute;left:calc(-1 * var(--size-4-3));font-weight:bold}.user-level-card .recommendation-badge{position:absolute;top:var(--size-4-2);right:var(--size-4-2);background:var(--interactive-accent);color:var(--text-on-accent);padding:var(--size-2-1) var(--size-4-1);border-radius:var(--radius-s);font-size:.7em;font-weight:600;text-transform:uppercase;letter-spacing:.02em}.onboarding-modal .config-overview,.onboarding-view .config-overview{margin-bottom:var(--onboarding-spacing)}.onboarding-modal .mode-card,.onboarding-view .mode-card{display:flex;align-items:center;gap:var(--size-4-3);padding:var(--size-4-3);background:var(--background-secondary);border-radius:var(--onboarding-border-radius)}.onboarding-modal .mode-icon,.onboarding-view .mode-icon{--icon-size: var(--size-4-4);flex-shrink:0}.onboarding-modal .config-features,.onboarding-modal .config-views,.onboarding-modal .config-settings,.onboarding-view .config-settings{margin-bottom:var(--onboarding-spacing)}.onboarding-modal .enabled-features-list,.onboarding-view .enabled-features-list{list-style:none;padding:0;margin:0;background:var(--background-secondary);border-radius:var(--onboarding-border-radius);padding:var(--size-4-2)}.onboarding-modal .enabled-features-list li,.onboarding-view .enabled-features-list li{display:flex;align-items:center;gap:var(--size-4-2);padding:var(--size-2-1) 0;color:var(--text-normal);font-size:.9em}.onboarding-modal .feature-check,.onboarding-view .feature-check{color:var(--color-green);font-weight:bold}.onboarding-modal .views-grid,.onboarding-view .views-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:var(--size-4-2)}.onboarding-modal .view-item,.onboarding-view .view-item{display:flex;flex-direction:column;align-items:center;padding:var(--size-4-2);background:var(--background-secondary);border-radius:var(--onboarding-border-radius)}.onboarding-modal .view-icon,.onboarding-view .view-icon{font-size:1.2em;margin-bottom:var(--size-2-1)}.onboarding-modal .view-name,.onboarding-view .view-name{font-size:.8em;color:var(--text-muted);text-align:center}.onboarding-modal .settings-summary-list,.onboarding-view .settings-summary-list{list-style:none;padding:0;margin:0;background:var(--background-secondary);border-radius:var(--onboarding-border-radius);padding:var(--size-4-2)}.onboarding-modal .settings-summary-list li,.onboarding-view .settings-summary-list li{display:flex;justify-content:space-between;padding:var(--size-2-1) 0;font-size:.9em;border-bottom:1px solid var(--background-modifier-border)}.onboarding-modal .settings-summary-list li:last-child,.onboarding-view .settings-summary-list li:last-child{border-bottom:none}.onboarding-modal .setting-label,.onboarding-view .setting-label{color:var(--text-normal);font-weight:500}.onboarding-modal .setting-value,.onboarding-view .setting-value{color:var(--text-muted)}.onboarding-modal .config-options,.onboarding-view .config-options{margin-top:var(--onboarding-spacing)}.onboarding-modal .customization-note,.onboarding-view .customization-note{text-align:center;padding:var(--size-4-3);background:var(--background-secondary);border-radius:var(--onboarding-border-radius)}.onboarding-modal .note-text,.onboarding-view .note-text{color:var(--text-muted);font-size:.9em;margin:0;font-style:italic}.onboarding-modal .config-changes-summary,.onboarding-view .config-changes-summary{margin:var(--onboarding-spacing) 0;padding:var(--size-4-3);background:var(--background-secondary);border-radius:var(--onboarding-border-radius)}.onboarding-modal .preserved-views,.onboarding-modal .added-views,.onboarding-modal .updated-views,.onboarding-modal .settings-changes,.onboarding-view .settings-changes{margin:var(--size-4-2) 0;padding:var(--size-4-2);background:var(--background-primary);border-radius:var(--radius-s)}.onboarding-modal .preserved-header,.onboarding-view .preserved-header{display:flex;align-items:center;gap:var(--size-4-1);margin-bottom:var(--size-4-1)}.onboarding-modal .preserved-icon,.onboarding-view .preserved-icon{color:var(--color-green);font-size:1.1em}.onboarding-modal .preserved-text,.onboarding-modal .change-text,.onboarding-view .change-text{color:var(--text-normal);font-size:.9em;font-weight:500}.onboarding-view .updated-views,.onboarding-modal .updated-views{display:flex}.onboarding-modal .change-icon,.onboarding-view .change-icon{color:var(--interactive-accent);font-size:1.1em;margin-right:var(--size-4-1);display:flex}.onboarding-modal .preserved-views-list,.onboarding-modal .settings-changes-list,.onboarding-view .settings-changes-list{list-style:none;padding:0;margin:var(--size-4-1) 0 0 var(--size-4-4)}.onboarding-modal .preserved-views-list li,.onboarding-modal .settings-changes-list li,.onboarding-view .settings-changes-list li{display:flex;align-items:center;padding:var(--size-2-1) 0;color:var(--text-muted);font-size:.85em}.onboarding-modal .safety-note,.onboarding-view .safety-note{margin-top:var(--size-4-3);padding:var(--size-4-2);background:rgba(var(--color-blue-rgb),.1);border-radius:var(--radius-s);display:flex;align-items:center;gap:var(--size-4-1)}.onboarding-modal .safety-icon,.onboarding-view .safety-icon{color:var(--color-blue);font-size:1.1em;display:flex;justify-content:center;align-items:center}.onboarding-modal .safety-text,.onboarding-view .safety-text{color:var(--color-blue);font-size:var(--font-ui-smaller);font-weight:500}.onboarding-modal .task-guide-intro,.onboarding-view .task-guide-intro{margin-bottom:var(--onboarding-spacing)}.onboarding-modal .guide-description,.onboarding-view .guide-description{color:var(--text-muted);font-size:.95em;line-height:1.5;margin:0}.onboarding-modal .task-formats-section,.onboarding-modal .quick-capture-section,.onboarding-modal .practice-section,.onboarding-modal .shortcuts-section,.onboarding-view .shortcuts-section{margin-bottom:var(--onboarding-spacing)}.onboarding-modal .format-example,.onboarding-view .format-example{margin-top:var(--size-4-4);margin-bottom:var(--size-4-4)}.onboarding-modal .format-example code,.onboarding-view .format-example code{background:var(--background-primary);padding:var(--size-2-1) var(--size-4-1);border-radius:var(--radius-s);font-family:var(--font-monospace);font-size:.85em;color:var(--text-accent);border:1px solid var(--background-modifier-border);display:block;margin:var(--size-2-1) 0}.onboarding-modal .format-legend,.onboarding-modal .format-legend small,.onboarding-view .format-legend small{color:var(--text-faint);font-size:.8em;margin-top:var(--size-2-1);display:block}.onboarding-modal .status-markers,.onboarding-modal .metadata-symbols,.onboarding-view .metadata-symbols{margin-top:var(--size-4-2)}.onboarding-modal .status-list,.onboarding-view .status-list li,.onboarding-modal .symbols-list,.onboarding-view .symbols-list{list-style:none;margin:0;background:var(--background-primary);border-radius:var(--onboarding-border-radius)}.onboarding-modal .status-list li,.onboarding-view .status-list li,.onboarding-modal .symbols-list li,.onboarding-view .symbols-list li{display:flex;align-items:center;padding:var(--size-2-1) 0;font-size:.85em;color:var(--text-normal)}.onboarding-modal .status-list code,.onboarding-view .status-list code{background:var(--background-secondary);padding:var(--size-2-1) var(--size-4-1);border-radius:var(--radius-s);font-family:var(--font-monospace);margin-right:var(--size-4-2);min-width:40px;text-align:center}.onboarding-modal .demo-content,.onboarding-view .demo-content{padding:var(--size-4-3);background:var(--background-secondary);border-radius:var(--onboarding-border-radius)}.onboarding-modal .demo-button,.onboarding-view .demo-button{background:var(--interactive-accent);color:var(--text-on-accent);border:none;padding:var(--size-4-2) var(--size-4-4);border-radius:var(--button-radius);cursor:pointer;font-weight:500;transition:var(--onboarding-transition);margin-top:var(--size-4-2)}.onboarding-modal .demo-button:hover,.onboarding-view .demo-button:hover{background:var(--interactive-accent-hover)}.onboarding-modal .practice-feedback,.onboarding-view .practice-feedback{margin-top:var(--size-4-2)}.onboarding-modal .validation-message,.onboarding-view .validation-message{padding:var(--size-4-2);border-radius:var(--onboarding-border-radius);font-size:.9em;margin-bottom:var(--size-2-1)}.onboarding-modal .validation-success,.onboarding-view .validation-success{background:rgba(var(--color-green-rgb),.1);border:1px solid var(--color-green);color:var(--color-green)}.onboarding-modal .validation-error,.onboarding-view .validation-error{background:rgba(var(--color-red-rgb),.1);border:1px solid var(--color-red);color:var(--color-red)}.onboarding-modal .validation-warning,.onboarding-view .validation-warning{background:rgba(var(--color-orange-rgb),.1);border:1px solid var(--color-orange);color:var(--color-orange)}.onboarding-modal .validation-info,.onboarding-view .validation-info{background:rgba(var(--color-blue-rgb),.1);border:1px solid var(--color-blue);color:var(--color-blue)}.onboarding-modal .shortcuts-list,.onboarding-view .shortcuts-list{list-style:none;padding:0;margin:0;background:var(--background-secondary);border-radius:var(--onboarding-border-radius);padding:var(--size-4-2)}.onboarding-modal .shortcuts-list li,.onboarding-view .shortcuts-list li{display:flex;align-items:center;padding:var(--size-2-1) 0;font-size:.9em;color:var(--text-normal)}.onboarding-modal .shortcuts-list code,.onboarding-view .shortcuts-list code{background:var(--background-primary);padding:var(--size-2-1) var(--size-4-2);border-radius:var(--radius-s);font-family:var(--font-monospace);margin-right:var(--size-4-3);min-width:100px;font-size:.8em}.onboarding-modal .completion-success,.onboarding-view .completion-success{text-align:center;margin-bottom:var(--onboarding-spacing)}.onboarding-modal .success-icon,.onboarding-view .success-icon{font-size:3em;margin-bottom:var(--size-4-2)}.onboarding-modal .success-message,.onboarding-view .success-message{color:var(--text-muted);font-size:.95em;margin:0}.onboarding-modal .completion-summary,.onboarding-modal .quick-start-section,.onboarding-modal .next-steps-section,.onboarding-modal .resources-section,.onboarding-modal .feedback-section,.onboarding-view .feedback-section{margin-bottom:var(--onboarding-spacing)}.onboarding-modal .config-summary-card,.onboarding-view .config-summary-card{padding:var(--size-4-3);background:var(--background-secondary);border-radius:var(--onboarding-border-radius)}.onboarding-modal .config-header,.onboarding-view .config-header{display:flex;align-items:center;gap:var(--size-4-2);margin-bottom:var(--size-2-1)}.onboarding-modal .config-icon,.onboarding-view .config-icon{font-size:1.5em}.onboarding-modal .config-name,.onboarding-view .config-name{font-size:1.1em;font-weight:600;color:var(--text-normal)}.onboarding-modal .config-description,.onboarding-view .config-description{color:var(--text-muted);font-size:.9em;margin:0}.onboarding-modal .quick-start-steps,.onboarding-view .quick-start-steps{display:flex;flex-direction:column;gap:var(--size-4-2)}.onboarding-modal .quick-start-step,.onboarding-view .quick-start-step{display:flex;align-items:flex-start;gap:var(--size-4-3);padding:var(--size-4-2);background:var(--background-secondary);border-radius:var(--onboarding-border-radius)}.onboarding-modal .step-number,.onboarding-view .step-number{background:var(--interactive-accent);color:var(--text-on-accent);width:24px;height:24px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:.8em;font-weight:600;flex-shrink:0}.onboarding-modal .step-content,.onboarding-view .step-content{color:var(--text-normal);font-size:.9em;line-height:1.4}.onboarding-modal .next-steps-list,.onboarding-view .next-steps-list{list-style:none;padding:0;margin:0}.onboarding-modal .next-steps-list li,.onboarding-view .next-steps-list li{display:flex;align-items:flex-start;gap:var(--size-4-2);padding:var(--size-4-2);background:var(--background-secondary);border-radius:var(--onboarding-border-radius);margin-bottom:var(--size-2-1)}.onboarding-modal .step-check,.onboarding-view .step-check{color:var(--interactive-accent);font-weight:bold;flex-shrink:0}.onboarding-modal .step-text,.onboarding-view .step-text{color:var(--text-normal);font-size:.9em;line-height:1.4}.onboarding-modal .resources-list,.onboarding-view .resources-list{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:var(--size-4-2)}.onboarding-modal .resource-item,.onboarding-view .resource-item{display:flex;gap:var(--size-4-2);padding:var(--size-4-3);background:var(--background-secondary);border-radius:var(--onboarding-border-radius);transition:var(--onboarding-transition)}.onboarding-modal .resource-clickable,.onboarding-view .resource-clickable{cursor:pointer}.onboarding-modal .resource-clickable:hover,.onboarding-view .resource-clickable:hover{background:var(--background-modifier-hover)}.onboarding-modal .resource-icon,.onboarding-view .resource-icon{font-size:1.5em;flex-shrink:0}.onboarding-modal .feedback-description,.onboarding-view .feedback-description{color:var(--text-muted);font-size:.9em;line-height:1.5;margin:0 0 var(--size-4-2) 0}.onboarding-modal .feedback-buttons,.onboarding-view .feedback-buttons{display:flex;gap:var(--size-4-2);justify-content:center}.onboarding-modal .feedback-button,.onboarding-view .feedback-button{background:var(--background-secondary);border:none;color:var(--text-normal);padding:var(--size-4-2) var(--size-4-4);border-radius:var(--button-radius);cursor:pointer;font-size:.9em;transition:var(--onboarding-transition)}.onboarding-modal .feedback-positive:hover,.onboarding-view .feedback-positive:hover{background:var(--color-green);color:#fff}.onboarding-modal .feedback-negative:hover,.onboarding-view .feedback-negative:hover{background:var(--color-red);color:#fff}.onboarding-modal .feedback-thanks,.onboarding-view .feedback-thanks{text-align:center;padding:var(--size-4-3);background:var(--background-secondary);border-radius:var(--onboarding-border-radius)}.onboarding-modal .feedback-thanks-message,.onboarding-view .feedback-thanks-message{color:var(--text-normal);font-size:.9em;margin:0 0 var(--size-4-2) 0}.onboarding-modal .feedback-thanks a,.onboarding-view .feedback-thanks a{color:var(--interactive-accent);text-decoration:none}.onboarding-modal .feedback-thanks a:hover,.onboarding-view .feedback-thanks a:hover{text-decoration:underline}.onboarding-modal .final-message,.onboarding-view .final-message{text-align:center;padding:var(--size-4-4)}.onboarding-modal .final-message-text,.onboarding-view .final-message-text{color:var(--text-muted);font-size:1em;font-style:italic;margin:0}@media (max-width: 768px){.onboarding-modal,.onboarding-view{--dialog-width: 95vw;--dialog-max-width: 95vw;--dialog-max-height: 95vh}.onboarding-modal .user-level-cards,.onboarding-view .user-level-cards{grid-template-columns:1fr}.onboarding-modal .features-overview,.onboarding-view .features-overview{grid-template-columns:1fr}.onboarding-modal .views-grid,.onboarding-view .views-grid{grid-template-columns:repeat(auto-fit,minmax(100px,1fr))}.onboarding-modal .resources-list,.onboarding-view .resources-list{grid-template-columns:1fr}.onboarding-modal .feedback-buttons,.onboarding-view .feedback-buttons{flex-direction:column}.onboarding-modal .onboarding-buttons,.onboarding-view .onboarding-buttons{flex-wrap:wrap;justify-content:center}}.onboarding-modal .onboarding-content,.onboarding-view .onboarding-content{animation:fadeInUp .3s ease-out}@keyframes fadeInUp{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.onboarding-modal .user-level-card.selected,.onboarding-view .user-level-card.selected{animation:cardSelect .2s ease-out}@keyframes cardSelect{0%{transform:scale(1)}50%{transform:scale(1.02)}to{transform:scale(1)}}div[data-type^=tg-timeline-sidebar-view] .timeline-sidebar-container{display:flex;flex-direction:column;height:100%;width:100%;background-color:var(--background-primary);overflow:hidden;font-family:var(--font-interface);padding:0!important}div[data-type^=tg-timeline-sidebar-view] .timeline-header{display:flex;justify-content:space-between;align-items:center;padding:var(--size-4-3) var(--size-4-4);border-bottom:1px solid var(--background-modifier-border);background:linear-gradient(135deg,var(--background-secondary) 0%,var(--background-modifier-hover) 100%);flex-shrink:0;backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px)}div[data-type^=tg-timeline-sidebar-view] .timeline-title{font-weight:600;font-size:var(--font-ui-medium);color:var(--text-normal);display:flex;align-items:center;gap:var(--size-4-2)}div[data-type^=tg-timeline-sidebar-view] .timeline-controls{display:flex;gap:var(--size-4-2)}div[data-type^=tg-timeline-sidebar-view] .timeline-btn{display:flex;align-items:center;justify-content:center;width:var(--size-4-8);height:var(--size-4-8);border-radius:var(--radius-s);cursor:pointer;color:var(--text-muted);background-color:transparent;transition:all .2s ease}div[data-type^=tg-timeline-sidebar-view] .timeline-btn:hover{color:var(--text-normal);background-color:var(--background-modifier-hover)}div[data-type^=tg-timeline-sidebar-view] .timeline-btn.is-active{color:var(--text-on-accent);background-color:var(--interactive-accent)}div[data-type^=tg-timeline-sidebar-view] .timeline-content{flex:1;overflow-y:auto;padding:var(--size-4-2) 0;position:relative}div[data-type^=tg-timeline-sidebar-view] .timeline-content.focus-mode .timeline-date-group:not(.is-today){opacity:.3;pointer-events:none}div[data-type^=tg-timeline-sidebar-view] .timeline-empty{display:flex;align-items:center;justify-content:center;height:100%;color:var(--text-muted);font-style:italic;text-align:center;padding:var(--size-4-8)}div[data-type^=tg-timeline-sidebar-view] .timeline-date-group{margin-bottom:var(--size-4-2);position:relative;border-radius:var(--radius-m);transition:all .3s ease}div[data-type^=tg-timeline-sidebar-view] .timeline-date-group.is-today{background-color:var(--background-secondary);border-radius:var(--radius-m);margin:0 var(--size-4-2) var(--size-4-2);padding:var(--size-4-2);box-shadow:0 2px 8px #0000001a;border:1px solid var(--interactive-accent)}div[data-type^=tg-timeline-sidebar-view] .timeline-date-header{display:flex;align-items:center;justify-content:space-between;padding:var(--size-4-2) var(--size-4-4);font-weight:600;font-size:var(--font-ui-small);color:var(--text-accent);border-bottom:1px solid var(--background-modifier-border);margin-bottom:var(--size-4-2);position:sticky;top:0;background-color:var(--background-primary);z-index:1}div[data-type^=tg-timeline-sidebar-view] .timeline-date-group.is-today .timeline-date-header{border-radius:var(--radius-s);margin:0 0 var(--size-4-2) 0}div[data-type^=tg-timeline-sidebar-view] .timeline-date-relative{font-size:var(--font-ui-smaller);color:var(--text-muted);font-weight:normal}div[data-type^=tg-timeline-sidebar-view] .timeline-events-list{display:flex;flex-direction:column;gap:var(--size-2-1);padding:0 var(--size-2-3)}div[data-type^=tg-timeline-sidebar-view] .timeline-event{display:flex;align-items:flex-start;gap:var(--size-4-3);padding:var(--size-4-3);border-radius:var(--radius-m);cursor:pointer;position:relative;border:1px solid transparent;margin-bottom:var(--size-4-2)}div[data-type^=tg-timeline-sidebar-view] .timeline-event:hover{background-color:var(--background-modifier-hover);border-color:var(--interactive-accent);box-shadow:0 2px 8px #0000000d;transform:translateY(-1px)}div[data-type^=tg-timeline-sidebar-view] .timeline-event:hover:has(.timeline-event-checkbox:hover){transform:none}div[data-type^=tg-timeline-sidebar-view] .timeline-event.is-completed{opacity:.6}div[data-type^=tg-timeline-sidebar-view] .timeline-event.is-completed .timeline-event-text{text-decoration:line-through;color:var(--text-muted)}div[data-type^=tg-timeline-sidebar-view] .timeline-event-time{font-size:var(--font-ui-smaller);color:var(--text-muted);font-family:var(--font-monospace);min-width:45px;text-align:center;margin-top:2px;flex-shrink:0;background-color:var(--background-modifier-border);border-radius:var(--radius-s);padding:var(--size-4-1) var(--size-4-2);font-weight:500}div[data-type^=tg-timeline-sidebar-view] .timeline-event-content{flex:1;display:flex;align-items:flex-start;gap:var(--size-4-2);min-width:0}div[data-type^=tg-timeline-sidebar-view] .timeline-event-checkbox{display:flex;align-items:center;margin-top:2px}div[data-type^=tg-timeline-sidebar-view] .timeline-event-checkbox input[type=checkbox]{margin:0;cursor:pointer}div[data-type^=tg-timeline-sidebar-view] .timeline-event-text{flex:1;font-size:var(--font-ui-small);line-height:1.4;word-wrap:break-word;color:var(--text-normal);display:flex;align-items:flex-start;gap:var(--size-4-2)}div[data-type^=tg-timeline-sidebar-view] .timeline-event-icon{font-size:var(--font-ui-medium);flex-shrink:0;margin-top:1px}div[data-type^=tg-timeline-sidebar-view] .timeline-event-content-text{flex:1;word-break:break-word}div[data-type^=tg-timeline-sidebar-view] .timeline-event-actions{display:flex;gap:var(--size-4-1);opacity:0;transition:opacity .2s ease}div[data-type^=tg-timeline-sidebar-view] .timeline-event:hover .timeline-event-actions{opacity:1}div[data-type^=tg-timeline-sidebar-view] .timeline-event-action{display:flex;align-items:center;justify-content:center;width:var(--size-4-6);height:var(--size-4-6);border-radius:var(--radius-s);cursor:pointer;color:var(--text-muted);background-color:transparent;transition:all .2s ease}div[data-type^=tg-timeline-sidebar-view] .timeline-event-action:hover{color:var(--text-normal);background-color:var(--background-modifier-border)}div[data-type^=tg-timeline-sidebar-view] .timeline-quick-input{flex-shrink:0;border-top:1px solid var(--background-modifier-border);background-color:var(--background-secondary);padding:var(--size-4-4);display:flex;flex-direction:column;gap:var(--size-4-3);padding-bottom:var(--size-4-12);position:relative;transition:all .3s cubic-bezier(.4,0,.2,1);overflow:hidden}div[data-type^=tg-timeline-sidebar-view] .timeline-quick-input.is-collapsed{padding:0;gap:0;height:auto}div[data-type^=tg-timeline-sidebar-view] .timeline-quick-input.is-collapsed .quick-input-header,div[data-type^=tg-timeline-sidebar-view] .timeline-quick-input.is-collapsed .quick-input-editor,div[data-type^=tg-timeline-sidebar-view] .timeline-quick-input.is-collapsed .quick-input-actions{display:none}div[data-type^=tg-timeline-sidebar-view] .timeline-quick-input.is-collapsing{overflow:hidden}div[data-type^=tg-timeline-sidebar-view] .timeline-quick-input.is-expanding{overflow:hidden}div[data-type^=tg-timeline-sidebar-view] .quick-input-header-collapsed{display:flex;align-items:center;justify-content:space-between;padding:var(--size-4-3) var(--size-4-4);background-color:var(--background-secondary);border-bottom:1px solid var(--background-modifier-border);cursor:pointer;transition:background-color .2s ease}div[data-type^=tg-timeline-sidebar-view] .quick-input-header-collapsed:hover{background-color:var(--background-modifier-hover)}div[data-type^=tg-timeline-sidebar-view] .collapsed-expand-btn{display:flex;align-items:center;justify-content:center;width:var(--size-4-6);height:var(--size-4-6);border-radius:var(--radius-s);color:var(--text-muted);transition:all .2s ease;cursor:pointer}div[data-type^=tg-timeline-sidebar-view] .collapsed-expand-btn:hover{color:var(--text-normal);background-color:var(--background-modifier-border)}div[data-type^=tg-timeline-sidebar-view] .collapsed-title{flex:1;font-weight:600;font-size:var(--font-ui-small);color:var(--text-normal);margin-left:var(--size-4-2)}div[data-type^=tg-timeline-sidebar-view] .collapsed-quick-actions{display:flex;gap:var(--size-4-2)}div[data-type^=tg-timeline-sidebar-view] .collapsed-quick-capture,div[data-type^=tg-timeline-sidebar-view] .collapsed-more-options{display:flex;align-items:center;justify-content:center;width:var(--size-4-7);height:var(--size-4-7);border-radius:var(--radius-s);color:var(--text-muted);cursor:pointer;transition:all .2s ease}div[data-type^=tg-timeline-sidebar-view] .collapsed-quick-capture:hover,div[data-type^=tg-timeline-sidebar-view] .collapsed-more-options:hover{color:var(--text-normal);background-color:var(--background-modifier-border)}div[data-type^=tg-timeline-sidebar-view] .collapsed-quick-capture:hover{color:var(--interactive-accent)}div[data-type^=tg-timeline-sidebar-view] .quick-input-header{display:flex;justify-content:space-between;align-items:flex-start;gap:var(--size-4-2);margin-bottom:var(--size-4-2)}div[data-type^=tg-timeline-sidebar-view] .quick-input-header-left{display:flex;align-items:center;gap:var(--size-4-2)}div[data-type^=tg-timeline-sidebar-view] .quick-input-collapse-btn{display:flex;align-items:center;justify-content:center;width:var(--size-4-6);height:var(--size-4-6);border-radius:var(--radius-s);color:var(--text-muted);cursor:pointer;transition:all .2s ease}div[data-type^=tg-timeline-sidebar-view] .quick-input-collapse-btn:hover{color:var(--text-normal);background-color:var(--background-modifier-border)}div[data-type^=tg-timeline-sidebar-view] .quick-input-collapse-btn svg{transition:transform .2s ease}div[data-type^=tg-timeline-sidebar-view] .timeline-quick-input.is-collapsed .quick-input-collapse-btn svg{transform:rotate(-90deg)}div[data-type^=tg-timeline-sidebar-view] .quick-input-title{font-weight:600;font-size:var(--font-ui-small);color:var(--text-normal)}div[data-type^=tg-timeline-sidebar-view] .quick-input-target-info{font-size:var(--font-ui-smaller);color:var(--text-muted);font-style:italic;padding:var(--size-4-1) var(--size-4-2);background-color:var(--background-modifier-hover);border-radius:var(--radius-s);word-break:break-all}div[data-type^=tg-timeline-sidebar-view] .quick-input-editor{min-height:80px;border:2px solid var(--background-modifier-border);border-radius:var(--radius-m);background-color:var(--background-primary);padding:var(--size-4-3);font-family:var(--font-text);font-size:var(--font-ui-small);resize:vertical;transition:all .3s ease}div[data-type^=tg-timeline-sidebar-view] .quick-input-editor:focus-within{border-color:var(--interactive-accent);box-shadow:0 0 0 2px rgba(var(--interactive-accent-rgb),.2)}div[data-type^=tg-timeline-sidebar-view] .quick-input-editor .cm-editor{background-color:transparent;border:none;outline:none}div[data-type^=tg-timeline-sidebar-view] .quick-input-editor .cm-focused{outline:none}div[data-type^=tg-timeline-sidebar-view] .quick-input-editor .cm-editor.cm-focused{outline:none}div[data-type^=tg-timeline-sidebar-view] .quick-input-actions{display:flex;gap:var(--size-4-2);justify-content:flex-end}div[data-type^=tg-timeline-sidebar-view] .quick-capture-btn,div[data-type^=tg-timeline-sidebar-view] .quick-modal-btn{padding:var(--size-4-3) var(--size-4-6);border-radius:var(--radius-m);font-size:var(--font-ui-small);font-weight:500;cursor:pointer;border:none;transition:all .3s ease;box-shadow:0 2px 4px #0000001a}div[data-type^=tg-timeline-sidebar-view] .quick-capture-btn{background-color:var(--interactive-accent);color:var(--text-on-accent)}div[data-type^=tg-timeline-sidebar-view] .quick-capture-btn:hover{background-color:var(--interactive-accent-hover);transform:translateY(-1px);box-shadow:0 4px 8px #00000026}div[data-type^=tg-timeline-sidebar-view] .quick-modal-btn{background-color:var(--background-modifier-border);color:var(--text-normal)}div[data-type^=tg-timeline-sidebar-view] .quick-modal-btn:hover{background-color:var(--background-modifier-border-hover);transform:translateY(-1px);box-shadow:0 4px 8px #00000026}@media (max-width: 768px){div[data-type^=tg-timeline-sidebar-view] .timeline-header{padding:var(--size-4-2) var(--size-4-3)}div[data-type^=tg-timeline-sidebar-view] .timeline-controls{gap:var(--size-4-1)}div[data-type^=tg-timeline-sidebar-view] .timeline-btn{width:var(--size-4-7);height:var(--size-4-7)}div[data-type^=tg-timeline-sidebar-view] .timeline-events-list{padding:0 var(--size-2-3)}div[data-type^=tg-timeline-sidebar-view] .timeline-event{padding:var(--size-4-2)}div[data-type^=tg-timeline-sidebar-view] .timeline-quick-input{padding:var(--size-4-3)}div[data-type^=tg-timeline-sidebar-view] .timeline-quick-input.is-collapsed{padding:0}div[data-type^=tg-timeline-sidebar-view] .quick-input-editor{min-height:60px}div[data-type^=tg-timeline-sidebar-view] .quick-input-header-collapsed{padding:var(--size-4-2) var(--size-4-3)}div[data-type^=tg-timeline-sidebar-view] .collapsed-quick-capture,div[data-type^=tg-timeline-sidebar-view] .collapsed-more-options{width:var(--size-4-6);height:var(--size-4-6)}}div[data-type^=tg-timeline-sidebar-view] .timeline-content::-webkit-scrollbar{width:6px}div[data-type^=tg-timeline-sidebar-view] .timeline-content::-webkit-scrollbar-track{background-color:var(--background-secondary)}div[data-type^=tg-timeline-sidebar-view] .timeline-content::-webkit-scrollbar-thumb{background-color:var(--background-modifier-border);border-radius:3px}div[data-type^=tg-timeline-sidebar-view] .timeline-content::-webkit-scrollbar-thumb:hover{background-color:var(--background-modifier-border-hover)}@keyframes fadeIn{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}div[data-type^=tg-timeline-sidebar-view] .timeline-content.focus-mode{position:relative}div[data-type^=tg-timeline-sidebar-view] .timeline-content.focus-mode:before{content:"";position:absolute;top:0;left:0;right:0;bottom:0;background:linear-gradient(to bottom,rgba(var(--background-primary-rgb),.9) 0%,rgba(var(--background-primary-rgb),.7) 50%,rgba(var(--background-primary-rgb),.9) 100%);pointer-events:none;z-index:0}div[data-type^=tg-timeline-sidebar-view] .timeline-content.focus-mode .timeline-date-group.is-today{position:relative;z-index:1}div[data-type^=tg-timeline-sidebar-view] .timeline-event-content-text .markdown-block{margin:0;padding:0;font-size:inherit;line-height:inherit}div[data-type^=tg-timeline-sidebar-view] .timeline-event-content-text .markdown-block p{margin:0;padding:0;font-size:inherit;line-height:inherit}div[data-type^=tg-timeline-sidebar-view] .timeline-event-content-text .markdown-block strong,div[data-type^=tg-timeline-sidebar-view] .timeline-event-content-text .markdown-block em,div[data-type^=tg-timeline-sidebar-view] .timeline-event-content-text .markdown-block code{font-size:inherit}div[data-type^=tg-timeline-sidebar-view] .timeline-event-content-text .markdown-block a{color:var(--link-color);text-decoration:none}div[data-type^=tg-timeline-sidebar-view] .timeline-event-content-text .markdown-block a:hover{text-decoration:underline}div[data-type^=tg-timeline-sidebar-view] .timeline-event-content-text .markdown-block ul,div[data-type^=tg-timeline-sidebar-view] .timeline-event-content-text .markdown-block ol{margin:0;padding-left:var(--size-4-4)}div[data-type^=tg-timeline-sidebar-view] .timeline-event-content-text .markdown-block li{margin:0;padding:0}.reward-modal-content{text-align:center}.reward-modal .modal-title{text-align:center}.reward-name{font-size:1.2em;font-weight:bold;margin-bottom:15px}.reward-image-container{margin-bottom:20px;display:flex;justify-content:center;align-items:center}.reward-image{max-width:80%;max-height:300px;border-radius:8px;box-shadow:0 2px 4px #0000001a}.reward-image-error{font-style:italic;color:var(--text-muted)}.reward-spacer{height:20px}.task-genius-reward-modal .setting-item-control{display:flex;justify-content:center;gap:10px}.markdown-source-view.mod-cm6 .cm-gutters.task-gutter{margin-inline-end:0!important;margin-inline-start:var(--file-folding-offset)}.is-mobile .markdown-source-view.mod-cm6 .cm-gutters.task-gutter{margin-inline-start:0!important}.task-details-popover.tg-menu{z-index:20;position:fixed;background-color:var(--background-primary);border:1px solid var(--background-modifier-border);border-radius:var(--radius-s);padding:var(--size-4-3);box-shadow:var(--shadow-l)}.task-gutter{width:26px}.task-gutter-marker{cursor:pointer;font-size:var(--font-smaller);opacity:.1;transition:opacity .2s ease}.task-gutter-marker:hover{opacity:1}.task-popover-content{padding:var(--size-4-3);max-width:300px;max-height:400px;overflow:auto}.task-metadata-editor{display:flex;flex-direction:column;gap:var(--size-4-2);padding:var(--size-2-2);height:100%}.field-container{display:flex;flex-direction:column;margin-bottom:var(--size-2-2)}.field-label{font-size:var(--font-smallest);font-weight:var(--font-bold);margin-bottom:var(--size-2-1);color:var(--text-muted)}.action-buttons{display:flex;justify-content:space-between;margin-top:var(--size-4-2);gap:var(--size-4-2)}.action-button{padding:var(--size-2-2) var(--size-4-2);font-size:var(--font-smallest);border-radius:var(--radius-s);cursor:pointer}.task-gutter-marker.clickable-icon{width:24px;padding:var(--size-2-1);display:flex;justify-content:center;align-items:center}.task-details-popover .tabs-main-container{display:flex;flex-direction:column;width:100%}.task-details-popover .tabs-navigation{display:flex;margin-bottom:var(--size-4-2);gap:var(--size-4-2)}.task-details-popover .tab-button{padding:var(--size-2-2) var(--size-4-2);cursor:pointer;border:none;background:none;font-size:var(--font-ui-small);color:var(--text-muted);margin-bottom:-1px;transition:color .2s ease,border-color .2s ease}.task-details-popover .tab-button:hover{color:var(--text-normal)}.task-details-popover .tab-button.active{color:var(--text-on-accent);font-weight:var(--font-bold);background-color:var(--interactive-accent)}.task-details-popover .tab-pane{display:none;flex-direction:column;gap:var(--size-4-2)}.task-details-popover .tab-pane.active{display:flex}.task-details-popover .details-status-selector,.task-status-editor .details-status-selector{display:flex;flex-direction:row;justify-content:space-between;margin-bottom:var(--size-4-2);margin-top:var(--size-4-2)}.task-details-popover .quick-capture-status-selector,.task-status-editor .quick-capture-status-selector{display:flex;flex-direction:row;justify-content:space-between;gap:var(--size-4-3)}.task-details-popover .quick-capture-status-selector-label,.task-status-editor .quick-capture-status-selector-label{display:none}.modal-content.task-metadata-editor{display:flex;flex-direction:column;gap:var(--size-4-2)}.metadata-full-container{display:flex;flex-direction:column;gap:var(--size-4-2)}.metadata-full-container .dates-container{display:flex;flex-direction:column;gap:var(--size-4-2)}.internal-embed .task-genius-container{max-height:800px}.internal-embed .task-genius-container .task-sidebar{width:44px;min-width:44px;overflow:hidden}.internal-embed .task-genius-container .task-sidebar .sidebar-nav{align-items:center}.internal-embed .task-genius-container .task-sidebar .sidebar-nav-item{padding:8px 10px;justify-content:center;width:var(--size-4-9);flex-shrink:0;transition:width .3s ease-in-out,flex-shrink .3s ease-in-out}.internal-embed .task-genius-container .task-sidebar .nav-item-icon{margin-right:0}.internal-embed .task-genius-container .task-list{max-height:800px}.internal-embed .projects-container{flex:1;height:auto}.internal-embed .forecast-left-column{width:240px}.internal-embed .forecast-left-column .mini-calendar-container .calendar-grid{display:grid;grid-template-columns:repeat(7,1fr);gap:1px;padding:0 5px}.internal-embed .forecast-left-column .mini-calendar-container .calendar-day-header{text-align:center;font-size:.7em;color:var(--text-muted);padding:3px 0;border-bottom:1px solid var(--background-modifier-border);margin-bottom:3px}.internal-embed .forecast-left-column .mini-calendar-container .calendar-day-header.calendar-weekend{color:var(--text-accent)}.internal-embed .forecast-left-column .mini-calendar-container .calendar-day{aspect-ratio:1;border-radius:3px;padding:1px;cursor:pointer;position:relative;display:flex;flex-direction:column;transition:background-color .2s ease}.internal-embed .forecast-left-column .mini-calendar-container .calendar-day:hover{background-color:var(--background-modifier-hover)}.internal-embed .forecast-left-column .mini-calendar-container .calendar-day.selected{background-color:var(--background-modifier-border-hover)}.internal-embed .forecast-left-column .mini-calendar-container .calendar-day.today{background-color:var(--interactive-accent-hover);color:var(--text-on-accent)}.internal-embed .forecast-left-column .mini-calendar-container .calendar-day.past-due{color:var(--text-error)}.internal-embed .forecast-left-column .mini-calendar-container .calendar-day.other-month{opacity:.5}.internal-embed .forecast-left-column .mini-calendar-container .calendar-day-number{text-align:center;font-size:.75em;font-weight:500;padding:1px}.internal-embed .forecast-left-column .mini-calendar-container .calendar-day-count{background-color:var(--background-modifier-border);color:var(--text-normal);border-radius:8px;font-size:.6em;padding:1px 3px;margin:1px auto;text-align:center;width:fit-content}.internal-embed .forecast-left-column .mini-calendar-container .calendar-day-count.has-priority{background-color:var(--text-accent);color:var(--text-on-accent)}.internal-embed .tags-container{height:auto;max-height:100%}.internal-embed .task-genius-container:has(.task-details.visible) .tags-left-column{display:none}.internal-embed .task-genius-container:has(.task-details.visible) .projects-left-column{display:none}.internal-embed .full-calendar-container{height:auto}.internal-embed .tg-kanban-view{height:auto}.bases-view.task-genius-container{border-top:unset}.bases-update-error-notification{position:fixed;top:20px;right:20px;background:var(--background-modifier-error);border:1px solid var(--background-modifier-border);border-radius:6px;padding:12px 16px;max-width:400px;box-shadow:var(--shadow-s);z-index:1000;cursor:pointer;animation:slideInRight .3s ease-out}.bases-update-error-notification:hover{opacity:.8}.bases-update-error-notification .error-icon{font-size:16px;margin-bottom:8px}.bases-update-error-notification .error-message .error-title{font-weight:600;color:var(--text-error);margin-bottom:4px}.bases-update-error-notification .error-message .error-details{font-size:12px;color:var(--text-muted);line-height:1.4}@keyframes slideInRight{0%{transform:translate(100%);opacity:0}to{transform:translate(0);opacity:1}} +.cm-task-progress-bar{display:inline-block;position:relative;margin-left:5px;margin-bottom:1px}.no-progress-bar .cm-task-progress-bar{display:none!important}.HyperMD-header .cm-task-progress-bar{display:inline-block;position:relative;margin-left:5px;margin-bottom:5px}.progress-bar-inline{height:8px;position:relative}.progress-bar-inline-empty{background-color:var(--progress-background-color)}.progress-bar-inline-0{background-color:var(--progress-0-color)}.progress-bar-inline-1{background-color:var(--progress-25-color)}.progress-bar-inline-2{background-color:var(--progress-50-color)}.progress-bar-inline-3{background-color:var(--progress-75-color)}.progress-bar-inline-complete{background-color:var(--progress-100-color)}.progress-completed{background-color:var(--task-completed-color);z-index:3}.progress-in-progress{background-color:var(--task-in-progress-color);z-index:2;position:absolute;top:0;height:100%}.progress-abandoned{background-color:var(--task-abandoned-color);z-index:1;position:absolute;top:0;height:100%}.progress-planned{background-color:var(--task-planned-color);z-index:1;position:absolute;top:0;height:100%}.progress-bar-inline-background{color:#000!important;background-color:var(--progress-background-color);border-radius:10px;flex-direction:row;justify-content:flex-start;align-items:center;width:85px;position:relative;overflow:hidden}.progress-bar-inline-background.hidden{display:none}.cm-task-progress-bar .task-status-indicator{display:inline-block;margin-right:2px}.cm-task-progress-bar .completed-indicator{color:var(--task-completed-color)}.cm-task-progress-bar .in-progress-indicator{color:var(--task-in-progress-color)}.cm-task-progress-bar .abandoned-indicator{color:var(--task-abandoned-color)}.cm-task-progress-bar .planned-indicator{color:var(--task-planned-color)}.cm-task-progress-bar.with-number{display:inline-flex;align-items:center}.HyperMD-header .cm-task-progress-bar.with-number .progress-bar-inline-background,.HyperMD-header .cm-task-progress-bar.with-number .progress-status{margin-bottom:5px}.cm-task-progress-bar.with-number .progress-bar-inline-background{margin-bottom:-2px;width:42px}.cm-task-progress-bar.with-number .progress-status{font-size:13px;margin-left:3px}.theme-dark .progress-completed{background-color:var(--task-completed-color)}.theme-dark .progress-in-progress{background-color:var(--task-in-progress-color)}.theme-dark .progress-abandoned{background-color:var(--task-abandoned-color)}.theme-dark .progress-planned{background-color:var(--task-planned-color)}.task-progress-bar-popover{width:400px}.task-timer-widget{display:block;margin:4px 0;padding:2px 0;font-size:.9em;color:var(--text-muted);line-height:1.4}.task-timer-start,.task-timer-action{cursor:pointer;text-decoration:underline;color:var(--text-accent)}.task-timer-start:hover,.task-timer-action:hover{color:var(--text-accent-hover)}.task-states-container{margin:10px 0;border:1px solid var(--background-modifier-border);border-radius:5px;padding:10px}.task-state-row{margin-bottom:8px}.task-state-row .setting-item{border:none;padding:6px;border-radius:4px}.task-state-row .setting-item-info{margin-right:10px}.task-state-row .setting-item-control{display:flex;align-items:center;justify-content:flex-end;flex-wrap:nowrap}.task-state-row .setting-item-control input[type=text]{margin-right:8px}.task-state-row .extra-setting-button{padding:4px;width:24px;height:24px;border-radius:4px;margin-left:4px;display:flex;align-items:center;justify-content:center}.task-state-row .setting-item-control button{white-space:nowrap}.task-state-container{margin-inline-start:calc(var(--checkbox-size) * -1)}.task-state-container .task-state{padding-inline-start:var(--size-2-1);padding-inline-end:var(--size-2-2);text-decoration:none!important;cursor:pointer}.task-states-container{margin:10px 0;border:1px solid var(--background-modifier-border);border-radius:5px;padding:10px}.task-state-row{margin-bottom:8px}.task-state-row .setting-item{border:none;padding:6px;border-radius:4px}.task-state-row .setting-item-info{margin-right:10px}.task-state-row .setting-item-control{display:flex;align-items:center;justify-content:flex-end;flex-wrap:nowrap}.task-state-row .setting-item-control input[type=text]{margin-right:8px}.task-state-row .extra-setting-button{padding:4px;width:24px;height:24px;border-radius:4px;margin-left:4px;display:flex;align-items:center;justify-content:center}.task-state-row .setting-item-control button{white-space:nowrap}.task-state-container{margin-inline-start:calc(var(--checkbox-size) * -1)}.task-state-container .task-state{padding-inline-start:var(--size-2-1);padding-inline-end:var(--size-2-2);text-decoration:none!important;cursor:pointer}.task-genius-settings .settings-tabs-categorized-container{margin-top:var(--size-4-4);margin-bottom:var(--size-4-4);display:flex;flex-direction:column;gap:var(--size-4-6)}.task-genius-settings .settings-category-section{display:flex;flex-direction:column;gap:var(--size-4-2)}.task-genius-settings .settings-category-header{font-size:var(--font-ui-small);font-weight:var(--font-weight-semibold);color:var(--text-muted);text-transform:uppercase;letter-spacing:.05em;padding:0 var(--size-4-2);border-bottom:1px solid var(--background-modifier-border);padding-bottom:var(--size-4-1)}.task-genius-settings .settings-category-tabs{display:grid;grid-template-columns:repeat(3,minmax(200px,1fr));gap:var(--size-4-2)}@media (max-width: 1200px){.task-genius-settings .settings-category-tabs{grid-template-columns:repeat(2,minmax(200px,1fr))}}@media (max-width: 768px){.task-genius-settings .settings-category-tabs{grid-template-columns:1fr}}.task-genius-settings .settings-tabs-container{display:grid;grid-template-columns:repeat(2,1fr);grid-auto-rows:var(--size-4-18);margin-top:var(--size-4-4);margin-bottom:var(--size-4-4);height:fit-content;gap:var(--size-4-4)}@media (max-width: 768px){.task-genius-settings .settings-tabs-container{grid-template-columns:repeat(1,1fr)}}.task-genius-settings .settings-tab{padding:var(--size-4-3) var(--size-4-4);border-radius:var(--radius-m);cursor:pointer;display:flex;align-items:center;gap:var(--size-4-2);min-height:var(--size-4-12);border:1px solid var(--background-modifier-border);background:var(--background-primary);position:relative;overflow:hidden;transition:all .2s ease}.task-genius-settings .settings-tab:after{content:"";position:absolute;top:10px;right:-80px;width:200px;height:200px;background-color:var(--background-secondary-alt);transform:rotate(-15deg);z-index:0;opacity:.7;transition:all .3s ease;border-radius:var(--radius-m)}.task-genius-settings .settings-tab:hover:after{transform:rotate(-10deg);opacity:.9}.task-genius-settings .settings-tab-active:after{background-color:var(--interactive-accent);opacity:.3}.task-genius-settings .settings-tab-icon,.task-genius-settings .settings-tab span,.task-genius-settings .settings-tab-label{position:relative;z-index:1}.task-genius-settings .settings-category-tabs .settings-tab-icon{display:flex;align-items:center;justify-content:center;width:var(--size-4-4);height:var(--size-4-4);flex-shrink:0}.task-genius-settings .settings-category-tabs .settings-tab-icon svg{width:var(--icon-s);height:var(--icon-s)}.task-genius-settings .settings-category-tabs .settings-tab-label{font-size:var(--font-ui-small);font-weight:var(--font-weight-medium);flex:1;text-align:left}.task-genius-settings .settings-category-tabs .settings-tab:hover{background:var(--background-modifier-hover);border-color:var(--background-modifier-border-hover);transform:translateY(-1px);box-shadow:var(--shadow-m)}.task-genius-settings .settings-category-tabs .settings-tab-active{background:var(--interactive-accent);color:var(--text-on-accent);border-color:var(--interactive-accent);box-shadow:var(--shadow-m);font-weight:var(--font-weight-semibold)}.task-genius-settings .settings-category-tabs .settings-tab-active:hover{background:var(--interactive-accent-hover);border-color:var(--interactive-accent-hover);transform:translateY(-1px)}.task-genius-settings .settings-tab:hover{background-color:var(--background-modifier-hover)}.task-genius-settings .settings-tab-active{background-color:var(--background-modifier-border-hover);font-weight:bold}.task-genius-settings .settings-tab-sections{overflow:hidden}.task-genius-settings .settings-tab-section{display:none}.task-genius-settings .settings-tab-section-active{display:block}.task-genius-settings .settings-tab-section-header{display:flex;align-items:center;justify-content:flex-end;margin-top:var(--size-4-2);margin-bottom:var(--size-4-2)}.task-genius-settings .settings-tab-section-header .header-button{display:flex;align-items:center;justify-content:center;gap:4px;font-size:var(--font-ui-small)}.task-genius-settings .settings-tab-section-header .header-button-icon{--icon-size: 16px;display:flex;align-items:center;justify-content:center}.task-genius-settings .settings-tab[data-tab-id=general]{display:none}.task-genius-settings .settings-tabs-categorized-container{display:flex}.task-genius-settings:has(.settings-tab-section-active:not([data-tab-id="general"])) .settings-tabs-categorized-container{display:none}.task-genius-settings .settings-tabs-container{display:none}.task-genius-settings:has(.settings-tab-active[data-tab-id="general"]) .settings-tabs-container{display:grid}.task-genius-settings-header{display:block}.task-genius-settings:has(.settings-tab-section-active:not([data-tab-id="general"])) .task-genius-settings-header{display:none}.expression-examples{margin-top:8px;border-radius:5px}.expression-example-item{margin-bottom:var(--size-4-3);padding:var(--size-4-2);padding-left:var(--size-4-3);padding-right:var(--size-4-3);border-radius:var(--radius-s);display:flex;flex-direction:column;gap:6px;border:1px solid var(--background-modifier-border)}.expression-example-name{font-weight:bold}.expression-example-code{padding:4px 8px;background-color:var(--background-secondary);border-radius:4px;font-family:var(--font-monospace);font-size:.9em;overflow-wrap:break-word;user-select:text}.expression-example-use{align-self:flex-end;margin-top:4px}.custom-format-textarea{height:200px;width:100%;font-family:var(--font-monospace);resize:vertical}.custom-format-preview-container{margin-bottom:var(--size-4-3);padding:var(--size-4-3);border-radius:var(--radius-s);background-color:var(--background-secondary);display:flex;flex-direction:column}.custom-format-preview-label{font-weight:bold;margin-bottom:var(--size-4-2);color:var(--text-muted)}.custom-format-preview-content{padding:var(--size-4-2);background-color:var(--background-primary);border-radius:var(--radius-s);font-family:var(--font-interface)}.custom-format-placeholder-info{margin-top:var(--size-4-2);margin-bottom:var(--size-4-2);user-select:text}.custom-format-preview-error,.expression-preview-error{color:var(--text-error)}.expression-example-preview{margin-top:var(--size-4-2);padding:var(--size-4-2);background-color:var(--background-primary-alt);border-radius:var(--radius-s);font-size:.9em}.preset-filters-container{margin-top:10px;padding:8px;border-radius:5px;border:1px solid var(--background-modifier-border)}.preset-filter-row{margin-bottom:5px;border-radius:4px;padding-top:var(--size-4-2);padding-left:var(--size-4-2);padding-right:var(--size-4-2);transition:background-color .2s ease}.preset-filter-row:hover{background-color:var(--background-secondary-alt)}.no-presets-message{font-style:italic;color:var(--text-muted);text-align:center;padding:15px}.preset-saved-message{color:var(--text-accent);font-weight:bold;text-align:center;padding:5px;margin-top:5px;animation:fadeIn .3s ease-in-out}.task-filter-save-preset{margin-top:15px;padding:10px;border-radius:5px;background-color:var(--background-secondary-alt)}.tg-modal-button-container{display:flex;justify-content:flex-end;gap:10px;margin-top:20px}.tg-modal-button-container button{padding:6px 12px;border-radius:4px;font-size:14px;font-weight:500;cursor:pointer}.tg-modal-button-container button.mod-warning{background-color:var(--background-modifier-error);color:#fff}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}.modal-workflow-definition{max-width:800px;width:90vw}.modal-stage-definition{max-width:800px;width:90vw}.workflow-container{border:1px solid var(--background-modifier-border);border-radius:5px;padding:15px;max-height:500px;overflow-y:auto;background-color:var(--background-primary);box-shadow:0 1px 4px #0000000d}.workflow-row{margin-bottom:15px;padding:12px;border-radius:6px;background-color:var(--background-secondary-alt);box-shadow:0 1px 3px #00000014;border-left:3px solid var(--interactive-accent)}.workflow-row .setting-item{border:none;padding:0}.workflow-row .setting-item-info{padding:0!important}.workflow-row .setting-item-name{font-size:16px;font-weight:600;color:var(--text-normal)}.workflow-row .setting-item-description{font-size:13px;color:var(--text-muted);margin-top:4px}.workflow-stages-info{margin-top:12px;padding:8px 0 0;border-top:1px solid var(--background-modifier-border)}.workflow-stages-list{list-style-type:none;display:flex;flex-wrap:wrap;gap:var(--size-2-2);padding:0;margin:0}.workflow-stage-item{padding:4px 8px;border-radius:4px;font-size:12px;display:inline-flex;align-items:center;background-color:var(--background-modifier-border)}.workflow-stage-cycle{background-color:var(--task-in-progress-color);color:var(--text-on-accent)}.workflow-stage-terminal{background-color:var(--task-completed-color);color:var(--text-on-accent)}.no-workflows-message{font-style:italic;color:var(--text-muted);text-align:center;padding:15px}.workflow-form{margin-bottom:20px}.workflow-stages-section{margin-top:20px;border-top:1px solid var(--background-modifier-border);padding-top:15px}.workflow-stages-section h2{margin-top:0;margin-bottom:15px;font-size:1.3em;color:var(--text-normal)}.workflow-stages-container{margin-top:15px}.workflow-stages-container .workflow-stages-list{display:block;flex-wrap:unset;gap:unset}.workflow-stages-container .workflow-stage-item{display:block;margin-bottom:10px;padding:0;background-color:transparent}.workflow-buttons{display:flex;justify-content:flex-end;gap:10px;margin-top:20px;padding-top:10px;border-top:1px solid var(--background-modifier-border)}.workflow-save-button,.workflow-cancel-button,.workflow-add-stage-button{padding:6px 12px;border-radius:4px;cursor:pointer}.workflow-save-button.mod-cta{background-color:var(--interactive-accent);color:var(--text-on-accent)}.workflow-cancel-button{background-color:var(--background-modifier-border);color:var(--text-normal)}.workflow-add-stage-button{background-color:var(--interactive-accent);color:var(--text-on-accent);margin-top:10px}.no-stages-message{font-style:italic;color:var(--text-muted);text-align:center;padding:15px}.workflow-stage-header{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;background-color:var(--background-secondary);border-radius:4px;margin-bottom:8px;box-shadow:0 1px 3px #0000001a}.workflow-stage-name{font-weight:600;flex:1;margin-right:10px}.workflow-stage-actions{display:flex;gap:5px}.workflow-stage-edit,.workflow-stage-move-up,.workflow-stage-move-down,.workflow-stage-delete{padding:3px 8px;border-radius:3px;background-color:var(--background-modifier-border);cursor:pointer;font-size:12px;border:none}.workflow-stage-edit:hover,.workflow-stage-move-up:hover,.workflow-stage-move-down:hover{background-color:var(--interactive-accent);color:var(--text-on-accent)}.workflow-stage-type-badge{display:inline-block;padding:2px 6px;margin-left:8px;border-radius:3px;font-size:10px;text-transform:uppercase;font-weight:600}.workflow-stage-type-linear{background-color:var(--background-modifier-border)}.workflow-stage-type-cycle{background-color:var(--task-in-progress-color);color:var(--text-on-accent)}.workflow-stage-type-terminal{background-color:var(--task-completed-color);color:var(--text-on-accent)}.workflow-substages-list{padding:0 0 0 var(--size-4-6);margin-top:var(--size-4-2);margin-bottom:var(--size-4-2);border-left:2px solid var(--background-modifier-border)}.substage-settings-container{width:100%}.stage-type-settings{margin-top:20px;border:1px solid var(--background-modifier-border);border-radius:4px;padding:15px;background-color:var(--background-primary)}.substages-section,.can-proceed-to-section{margin-top:20px;padding-top:15px;border-top:1px solid var(--background-modifier-border)}.substages-container,.can-proceed-to-container{margin-top:15px;padding:10px;border-radius:4px}.substages-list,.can-proceed-list{list-style-type:none;padding:0;margin:0}.substage-name-container{display:flex;gap:10px;align-items:center;flex:1}.substage-name-container input{padding:4px 8px;border-radius:3px;border:1px solid var(--background-modifier-border);background-color:var(--background-primary)}.substage-next-container{display:flex;align-items:center;gap:5px;margin-left:10px}.substage-remove-button,.can-proceed-remove-button{color:var(--text-normal);border-radius:3px;padding:2px 5px;cursor:pointer;border:none}.substage-remove-button:hover,.can-proceed-remove-button:hover{background-color:var(--background-modifier-error);color:var(--text-on-accent)}.add-substage-button,.add-can-proceed-button{background-color:var(--interactive-accent);color:var(--text-on-accent);padding:4px 10px;border-radius:4px;margin-top:10px;cursor:pointer;border:none}.add-can-proceed-container{display:flex;gap:10px;align-items:flex-end}.add-can-proceed-select{flex:1;padding:4px 8px;border-radius:3px;border:1px solid var(--background-modifier-border)}.stage-buttons{display:flex;justify-content:flex-end;gap:10px;margin-top:20px;padding-top:10px;border-top:1px solid var(--background-modifier-border)}.stage-save-button,.stage-cancel-button{padding:6px 12px;border-radius:4px;cursor:pointer;border:none}.stage-save-button.mod-cta{background-color:var(--interactive-accent);color:var(--text-on-accent)}.stage-cancel-button{background-color:var(--background-modifier-border);color:var(--text-normal)}.stage-error-message{color:var(--background-modifier-error);font-weight:bold;text-align:center;margin-top:10px;padding:8px;border-radius:4px}.task-workflow-tag{display:inline-block;padding:2px 5px;border-radius:3px;margin-left:5px;font-size:12px;background-color:var(--background-secondary-alt)}.task-workflow-stage{margin-left:5px;color:var(--text-accent)}.task-workflow-substage{font-size:11px;color:var(--text-muted)}.task-workflow-history{margin-left:20px;font-size:12px;color:var(--text-muted)}.task-workflow-timestamp{color:var(--text-faint)}.setting-item-control span[class^=workflow-stage-name-]{display:inline-block;padding:2px 6px;border-radius:3px;font-size:12px;font-weight:500;margin-right:5px}.setting-item-control .workflow-stage-name-cycle{background-color:var(--task-in-progress-color);color:var(--text-on-accent)}.setting-item-control .workflow-stage-name-terminal{background-color:var(--task-completed-color);color:var(--text-on-accent)}.workflow-stage-item{margin-right:4px}.workflow-stages-container .workflow-stage-header{padding:8px 12px;background-color:var(--background-secondary);border-radius:4px;box-shadow:0 1px 3px #0000001a;margin-bottom:8px}.workflow-stages-container .workflow-stage-type-badge{display:inline-block;padding:2px 6px;margin-left:8px;border-radius:3px;font-size:10px;text-transform:uppercase;font-weight:600}.workflow-substages-list{list-style-type:none;padding:0 0 0 20px;margin:5px 0 10px;border-left:2px solid var(--background-modifier-border)}.workflow-add-stage-button,.stage-save-button.mod-cta,.workflow-save-button.mod-cta{background-color:var(--interactive-accent);color:var(--text-on-accent);padding:6px 15px;border-radius:4px;font-weight:500;border:none;cursor:pointer;box-shadow:0 2px 4px #0000001a;transition:all .2s ease;text-align:center}.workflow-add-stage-button:hover,.stage-save-button.mod-cta:hover,.workflow-save-button.mod-cta:hover{background-color:var(--interactive-accent-hover);box-shadow:0 3px 6px #00000026;transform:translateY(-1px)}.workflow-stage-move-up,.workflow-stage-move-down,.workflow-stage-edit,.workflow-stage-delete{border:none;background-color:var(--background-modifier-border);padding:3px 8px;border-radius:3px;font-size:12px;cursor:pointer;transition:all .2s ease}.workflow-stage-move-up:hover,.workflow-stage-move-down:hover,.workflow-stage-edit:hover{background-color:var(--interactive-accent);color:var(--text-on-accent)}.workflow-stage-delete:hover{background-color:var(--background-modifier-error);color:var(--text-on-accent)}.substage-item{display:flex;justify-content:flex-end;align-items:center;padding:6px 0;margin-bottom:5px;border-radius:4px}.substage-name-container input{background-color:var(--background-primary);border:1px solid var(--background-modifier-border);padding:4px 8px;border-radius:3px;font-size:13px}.substage-name-container input:focus{border-color:var(--interactive-accent);outline:none}.no-stages-message,.no-workflows-message,.no-substages-message,.no-can-proceed-message{font-style:italic;color:var(--text-muted);padding:15px;text-align:center;background-color:var(--background-secondary-alt);border-radius:5px;margin:10px 0}.rewards-levels-container,.rewards-items-container{margin-top:10px;padding:15px;border-radius:5px;border:1px solid var(--background-modifier-border);background-color:var(--background-secondary)}.rewards-level-row .setting-item-info,.rewards-item-row .setting-item-info{display:none}.rewards-item-row.setting-item{border-top:0}.rewards-level-row .setting-item-control,.rewards-item-row .setting-item-control{display:flex;flex-wrap:wrap;gap:10px;align-items:center}.rewards-level-row .setting-item-control input[type=text]{flex:1;min-width:100px}.rewards-item-row .setting-item-control .input-container{flex:1;min-width:150px}.rewards-item-row .setting-item-control textarea{width:100%;min-height:40px;resize:vertical}.rewards-item-row .setting-item-control .dropdown{min-width:120px}.rewards-level-row .setting-item-control button,.rewards-item-row .setting-item-control button{margin-left:auto}.rewards-item-divider{border:none;height:1px;background-color:var(--background-modifier-border);margin-top:15px;margin-bottom:15px}.setting-item.sort-criterion-row .setting-item-info{display:none}.setting-item.sort-criterion-row select.dropdown{flex:1}.view-management-list{margin:10px 0;border:1px solid var(--background-modifier-border);border-radius:5px;padding:10px}.view-edit-button,.view-copy-button,.view-order-button,.view-delete-button{padding:4px;width:24px;height:24px;border-radius:4px;margin-left:4px;display:flex;align-items:center;justify-content:center}.view-copy-button{color:var(--interactive-accent)}.view-copy-button:hover{background-color:var(--interactive-accent);color:var(--text-on-accent)}.view-delete-button{color:var(--text-error)}.view-delete-button:hover{background-color:var(--background-modifier-error);color:var(--text-on-accent)}.view-icon{margin-right:8px;--icon-size: 16px}.copy-mode-info{margin:10px 0;padding:12px;background-color:var(--background-secondary-alt);border-radius:5px;border-left:3px solid var(--interactive-accent)}.copy-mode-info p{margin:4px 0}.tasks-compatibility-warning{display:flex;align-items:flex-start;gap:var(--size-4-3);padding:var(--size-4-4);margin-bottom:var(--size-4-4);background-color:hsl(var(--accent-h),var(--accent-s),var(--accent-l),.5);border:1px solid hsl(var(--accent-h),var(--accent-s),var(--accent-l),.5);border-radius:var(--radius-m);color:var(--text-on-accent)}.tasks-warning-icon{font-size:20px;line-height:1;flex-shrink:0}.tasks-warning-content{flex:1;display:flex;flex-direction:column;gap:var(--size-2-2)}.tasks-warning-title{font-weight:600;font-size:var(--font-ui-medium)}.tasks-warning-text{color:var(--text-on-accent);font-size:var(--font-ui-small);line-height:1.4}.tasks-warning-text a{color:var(--text-on-accent);text-decoration:underline}.tasks-warning-text a:hover{color:var(--text-on-accent)}.task-genius-format-examples{display:flex;flex-direction:column;gap:var(--size-2-3);padding:var(--size-4-3);margin:var(--size-4-3) 0;border-radius:var(--radius-m);background-color:var(--background-secondary-alt);border:1px solid var(--background-modifier-border)}.task-genius-format-examples strong{font-size:var(--font-ui-medium);font-weight:600;color:var(--text-normal);margin-bottom:var(--size-2-1)}.task-genius-format-examples span{font-family:var(--font-monospace);font-size:var(--font-ui-smaller);line-height:1.5;color:var(--text-muted);padding:var(--size-2-1) var(--size-2-3);background-color:var(--background-primary);border-radius:var(--radius-s);border:1px solid var(--background-modifier-border);margin:var(--size-2-1) 0}.task-genius-format-examples span:first-of-type{margin-top:0}.task-genius-format-examples span:last-of-type{margin-bottom:0}.project-path-mappings-container,.project-metadata-mappings-container{margin-top:10px}.project-path-mapping-row,.project-metadata-mapping-row{border:1px solid var(--background-modifier-border);border-radius:6px;margin-bottom:10px;padding:10px}.no-mappings-message{color:var(--text-muted);font-style:italic;text-align:center;padding:20px}.task-project-tg{opacity:.8;font-style:italic;border-left:2px solid var(--color-accent);padding-left:4px}.task-project-tg:before{content:"\1f517";margin-right:2px;font-size:.8em}.project-readonly{opacity:.8}.project-readonly input{background-color:var(--background-modifier-border);cursor:not-allowed}.project-source-indicator{font-size:var(--font-ui-smaller);color:var(--text-muted);font-style:italic;margin-top:4px}.tg-status-icon{display:inline-flex;align-items:center;vertical-align:middle;margin-right:var(--size-2-3);margin-top:calc(-1 * var(--size-2-1))}.tg-icons-container{display:flex;gap:var(--size-2-2);flex-wrap:wrap;align-items:center;justify-content:center}.tg-icons-container .tg-status-icon{margin-right:0;margin-top:0}.global-filter-container{margin-bottom:20px;padding:10px;border:1px solid var(--background-modifier-border);border-radius:6px;background-color:var(--background-secondary)}.beta-test-warning-banner{display:flex;align-items:flex-start;gap:12px;padding:16px;margin-bottom:20px;background-color:var(--background-modifier-warning);border:1px solid var(--color-orange);border-radius:8px}.beta-warning-icon{font-size:20px;line-height:1;flex-shrink:0;margin-top:2px}.beta-warning-content{flex:1;min-width:0}.beta-warning-title{font-weight:600;font-size:14px;color:var(--text-normal);margin-bottom:8px}.beta-warning-text{font-size:13px;line-height:1.4;color:var(--text-muted)}.task-details .panel-toggle-container{left:10px}.task-details{width:300px;flex-shrink:0;border-left:1px solid var(--background-modifier-border);height:100%;overflow-y:auto;display:flex;flex-direction:column;transition:all .3s ease-in-out;position:relative;min-width:250px;max-width:400px;background-color:var(--background-secondary);order:1}.task-genius-container.details-hidden .task-details{width:0;opacity:0;margin-right:-300px;overflow:hidden}.task-genius-container.details-visible .task-details{width:350px;opacity:1;margin-right:0}.is-phone .task-details{position:absolute;right:0;top:0;height:100%;width:100%;max-width:100%;z-index:10;transform:translate(100%)}.is-phone .task-genius-container.details-hidden .task-details{width:100%;margin-right:0;transform:translate(100%)}.is-phone .task-genius-container.details-visible .task-details{width:calc(100% - var(--size-4-12));transform:translate(0)}.is-phone .task-genius-container.details-visible:before{content:"";position:absolute;top:0;left:0;width:100%;height:100%;background-color:var(--background-modifier-cover);opacity:.5;z-index:5;transition:opacity .3s ease-in-out}.is-phone .details-close-btn{width:24px;height:24px;display:flex;align-items:center;justify-content:center}.is-phone .details-header{padding:var(--size-4-4)}.details-empty{display:flex;height:100%;align-items:center;justify-content:center;text-align:center;color:var(--text-muted);padding:20px}.details-header{padding:var(--size-4-4);padding-bottom:var(--size-4-3);padding-top:var(--size-4-3);font-weight:600;border-bottom:1px solid var(--background-modifier-border);display:flex;justify-content:space-between;align-items:center;font-size:1.1em}.details-content{padding:var(--size-4-4);display:flex;flex-direction:column;gap:var(--size-4-2);overflow-y:auto;padding-bottom:max(var(--safe-area-inset-bottom),var(--size-4-8))}.details-name{margin:0 0 8px;padding:0;font-size:1.3em;line-height:1.3}.details-status-container{display:flex;justify-content:space-between;align-items:center}.details-status-label{text-transform:uppercase;font-size:var(--font-ui-small)}.details-status{display:inline-block;padding:4px 8px;border-radius:4px;background-color:var(--color-accent);color:var(--text-on-accent);font-size:var(--font-ui-small)}.details-status-selector{display:flex;justify-content:space-evenly;align-items:center}.menu-item-title:has(.status-option){display:flex;align-items:center;gap:4px}.menu-item:has(.status-option-checkbox) .menu-item-icon{display:none}.menu-item:has(.status-option-icon) .menu-item-icon{display:none}.status-option-icon{display:flex;align-items:center;justify-content:center;margin-right:var(--size-2-2)}.status-option-checkbox{display:flex;align-items:center;justify-content:center}.status-option{display:flex;justify-content:center;text-transform:uppercase}.status-option.current{outline-offset:2px;outline:1px solid hsl(var(--accent-h),var(--accent-s),var(--accent-l),.3);outline-style:dashed}.status-option:not(.current){opacity:.8}.status-option:not(.current):hover{opacity:1}.status-option input.task-list-item-checkbox{margin-inline-end:0}.details-metadata{display:flex;flex-direction:column;gap:var(--size-4-2);margin-top:var(--size-4-2);margin-bottom:var(--size-4-2)}.metadata-field{display:flex;flex-direction:column;gap:2px}.metadata-label{font-size:.8em;color:var(--text-muted)}.metadata-value{word-break:break-word;font-size:.95em}.details-actions{display:flex;align-items:center;justify-content:flex-start;gap:8px;margin-bottom:var(--size-4-4)}.details-edit-btn,.details-toggle-btn{background-color:var(--interactive-normal);border:1px solid var(--background-modifier-border);border-radius:4px;padding:6px 12px;color:var(--text-normal);cursor:pointer;font-size:var(--font-ui-small)}.details-edit-btn:hover,.details-toggle-btn:hover{background-color:var(--interactive-hover)}.details-toggle-btn{background-color:var(--interactive-accent);color:var(--text-on-accent)}.details-edit-form{display:flex;flex-direction:column;gap:12px}.details-form-field{display:flex;flex-direction:column;gap:4px}.details-form-label{font-size:.8em;color:var(--text-muted);font-weight:500}.details-form-input{width:100%}.details-edit-content{font-weight:500}.details-form-input input,.details-form-input select{width:100%;padding:6px 8px;border-radius:4px;border:1px solid var(--background-modifier-border);background-color:var(--background-primary)}.date-input{width:100%;padding:6px 8px;border-radius:4px;border:1px solid var(--background-modifier-border);background-color:var(--background-primary);color:var(--text-normal)}.field-description{font-size:.7em;color:var(--text-muted);margin-top:2px}.details-form-buttons{display:flex;justify-content:space-between;margin-top:16px;gap:8px}.details-form-buttons button{flex:1;justify-content:center}.details-form-error{color:var(--text-error);font-size:.8em;margin-top:8px;padding:8px;background-color:var(--background-modifier-error);border-radius:4px}.details-edit-file-btn{background-color:var(--interactive-normal);border:1px solid var(--background-modifier-border);border-radius:4px;padding:6px 12px;color:var(--text-normal);cursor:pointer;font-size:var(--font-ui-small)}.details-edit-file-btn:hover{background-color:var(--interactive-hover)}@media screen and (max-width: 768px){.task-omnifocus-container{flex-direction:column}.task-sidebar{width:100%;max-width:100%;height:auto;border-right:none;border-bottom:1px solid var(--background-modifier-border)}.task-content{width:100%;flex:1}.task-details{width:100%;max-width:100%;border-left:none}}.project-source-indicator{display:flex;align-items:center;gap:4px;margin-top:4px;padding:4px 8px;border-radius:4px;font-size:.85em;line-height:1.2}.project-source-indicator .indicator-icon{font-size:.9em}.project-source-indicator .indicator-text{color:var(--text-muted)}.project-source-indicator.readonly-indicator{border:1px solid var(--background-modifier-error)}.project-source-indicator.readonly-indicator .indicator-text{color:var(--text-error);font-weight:500}.project-source-indicator.override-indicator{border:1px solid var(--background-modifier-accent)}.project-source-indicator.override-indicator .indicator-text{color:var(--text-accent)}.field-description.readonly-description{color:var(--text-error);font-size:.8em;margin-top:4px;font-style:italic}.field-description.override-description{color:var(--text-accent);font-size:.8em;margin-top:4px;font-style:italic}.project-source-indicator.inline-indicator{position:absolute;top:100%;left:0;right:0;z-index:10;margin-top:2px;padding:2px 6px;font-size:.75em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.project-source-indicator.table-indicator{position:absolute;top:2px;right:2px;padding:1px 3px;font-size:.7em;border-radius:2px;z-index:5}.project-source-indicator.table-indicator .indicator-icon{font-size:.8em}.task-table-cell.readonly-cell{background-color:var(--background-modifier-error-hover);opacity:.8}.project-container.project-readonly{position:relative}.project-container.project-readonly .project-source-indicator{margin-top:8px}.oncompletion-configurator{display:flex;flex-direction:column;gap:12px;padding:12px;border:1px solid var(--background-modifier-border);border-radius:6px;background-color:var(--background-secondary)}.oncompletion-action-type{display:flex;flex-direction:column;gap:6px}.oncompletion-label{font-weight:600;color:var(--text-normal);font-size:.9em}.oncompletion-config{display:flex;flex-direction:column;gap:10px;margin-top:8px;padding-top:8px;border-top:1px solid var(--background-modifier-border-hover)}.oncompletion-field{display:flex;flex-direction:column;gap:4px}.oncompletion-description{font-size:.8em;color:var(--text-muted);font-style:italic;margin-top:2px}.oncompletion-action-type .dropdown{width:100%}.oncompletion-field .text-input{width:100%;padding:6px 8px;border:1px solid var(--background-modifier-border);border-radius:4px;background-color:var(--background-primary);color:var(--text-normal)}.oncompletion-field .text-input:focus{border-color:var(--interactive-accent);outline:none;box-shadow:0 0 0 2px var(--interactive-accent-hover)}.oncompletion-field .checkbox-container{display:flex;align-items:center;gap:8px}.task-id-suggestion{font-weight:600;color:var(--text-accent)}.task-content-preview{font-size:.85em;color:var(--text-muted);margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:300px}.file-name{font-weight:500;color:var(--text-normal)}.file-path{font-size:.8em;color:var(--text-muted);margin-top:2px}.action-type-suggestion{font-weight:600;color:var(--text-accent)}.action-description{font-size:.8em;color:var(--text-muted);margin-top:2px}.oncompletion-configurator.invalid{border-color:var(--text-error);background-color:var(--background-modifier-error)}.oncompletion-configurator.valid{border-color:var(--text-success)}.oncompletion-validation-message{font-size:.8em;margin-top:4px;padding:4px 6px;border-radius:3px}.oncompletion-validation-message.error{color:var(--text-error);background-color:var(--background-modifier-error)}.oncompletion-validation-message.success{color:var(--text-success);background-color:var(--background-modifier-success)}.task-details .oncompletion-configurator{margin-top:8px;border:none;background-color:transparent;padding:0}.task-details .oncompletion-field{margin-bottom:8px}@media (max-width: 768px){.oncompletion-configurator{padding:8px;gap:8px}.oncompletion-config{gap:8px}.task-content-preview{max-width:200px}}.theme-dark .oncompletion-configurator{background-color:var(--background-primary-alt)}.theme-dark .oncompletion-field .text-input{background-color:var(--background-secondary);border-color:var(--background-modifier-border-hover)}@media (prefers-contrast: high){.oncompletion-configurator{border-width:2px}.oncompletion-field .text-input{border-width:2px}.oncompletion-field .text-input:focus{box-shadow:0 0 0 3px var(--interactive-accent-hover)}}.oncompletion-config{transition:all .2s ease-in-out}.oncompletion-field{opacity:1;transform:translateY(0);transition:opacity .2s ease-in-out,transform .2s ease-in-out}.oncompletion-field.entering{opacity:0;transform:translateY(-10px)}.oncompletion-field.exiting{opacity:0;transform:translateY(10px)}.oncompletion-modal{--dialog-width: 600px;--dialog-max-width: 90vw;--dialog-max-height: 80vh}.oncompletion-modal .modal-content{padding:0;max-height:var(--dialog-max-height);overflow-y:auto}.oncompletion-modal-content{padding:20px;max-height:60vh;overflow-y:auto}.oncompletion-modal-buttons{display:flex;justify-content:flex-end;gap:8px;padding:16px 20px;border-top:1px solid var(--background-modifier-border);background-color:var(--background-secondary)}.oncompletion-modal-buttons button{min-width:80px}.inline-oncompletion-button-container{display:inline-flex;align-items:center}.inline-oncompletion-config-button{padding:4px 8px;border:1px solid var(--background-modifier-border);border-radius:4px;background-color:var(--background-primary);color:var(--text-normal);font-family:inherit;font-size:var(--font-ui-small);cursor:pointer;transition:all .15s ease;min-width:100px;text-align:left}.inline-oncompletion-config-button:hover{background-color:var(--background-modifier-hover);border-color:var(--interactive-accent)}.inline-oncompletion-config-button:focus{outline:none;border-color:var(--interactive-accent);box-shadow:0 0 0 2px var(--interactive-accent-hover)}.inline-oncompletion-config-button:active{background-color:var(--background-modifier-active);transform:scale(.98)}@media (max-width: 768px){.oncompletion-modal{--dialog-width: 95vw;--dialog-max-height: 85vh}.oncompletion-modal-content{padding:16px;max-height:65vh}.oncompletion-modal-buttons{padding:12px 16px;flex-direction:column-reverse}.oncompletion-modal-buttons button{width:100%;min-width:unset}}.universal-suggest-item{display:flex;align-items:center;cursor:pointer;border-radius:4px;transition:background-color .1s ease}.universal-suggest-item:hover{background-color:var(--background-modifier-hover)}.universal-suggest-item.is-selected{background-color:var(--background-modifier-active-hover)}.universal-suggest-container{display:flex;flex-direction:row;align-items:center;justify-content:flex-start;overflow:hidden}.universal-suggest-icon{display:flex;align-items:center;justify-content:center;width:20px;height:20px;margin-right:12px;color:var(--text-muted);flex-shrink:0}.universal-suggest-content{flex:1;min-width:0}.universal-suggest-label{font-weight:500;color:var(--text-normal);margin-bottom:2px}.universal-suggest-description{font-size:.85em;color:var(--text-muted);line-height:1.3}.cm-editor .cm-line .universal-suggest-trigger{background-color:var(--background-modifier-accent);color:var(--text-accent);border-radius:2px;padding:1px 2px}.suggestion-container .universal-suggest-item{border-bottom:1px solid var(--background-modifier-border)}.suggestion-container .universal-suggest-item:last-child{border-bottom:none}.theme-dark .universal-suggest-item:hover{background-color:var(--background-modifier-hover)}.theme-dark .universal-suggest-item.is-selected{background-color:var(--background-modifier-active-hover)}@media (prefers-contrast: high){.universal-suggest-item{border:1px solid var(--background-modifier-border);margin-bottom:2px}.universal-suggest-item:hover,.universal-suggest-item.is-selected{border-color:var(--text-accent)}}.confirm-modal-buttons{display:flex;gap:var(--size-4-3);justify-content:flex-end;margin-top:var(--size-4-3)}.habit-edit-dialog{max-width:600px;width:100%}.habit-edit-dialog .modal-content{padding:20px}.habit-edit-dialog .habit-type-selector{margin-bottom:20px}.habit-edit-dialog .habit-type-description{font-weight:600;margin-bottom:10px}.habit-edit-dialog .habit-type-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:10px}@media (max-width: 500px){.habit-edit-dialog .habit-type-grid{grid-template-columns:1fr}}.habit-edit-dialog .habit-type-item{display:flex;padding:12px;border-radius:var(--radius-m);border:1px solid var(--background-modifier-border);background-color:var(--background-secondary);cursor:pointer;transition:all .2s ease}.habit-edit-dialog .habit-type-item:hover{background-color:var(--background-modifier-hover)}.habit-edit-dialog .habit-type-item.selected{border-color:var(--interactive-accent);background-color:var(--interactive-accent-hover)}.habit-edit-dialog .habit-type-icon{display:flex;align-items:center;justify-content:center;width:40px;height:40px;border-radius:50%;background-color:var(--background-primary);margin-right:10px}.habit-edit-dialog .habit-type-icon svg{width:20px;height:20px;color:var(--text-normal)}.habit-edit-dialog .habit-type-text{flex:1;display:flex;flex-direction:column}.habit-edit-dialog .habit-type-name{font-weight:600;margin-bottom:4px}.habit-edit-dialog .habit-type-desc{font-size:.85em;color:var(--text-muted)}.habit-edit-dialog .habit-common-form,.habit-edit-dialog .habit-type-form{margin-bottom:20px}.habit-edit-dialog .habit-icon-preview{display:flex;align-items:center;justify-content:center;width:30px;height:30px;margin-left:10px;background-color:var(--background-primary);border-radius:50%}.habit-edit-dialog .habit-icon-preview svg{width:18px;height:18px}.habit-edit-dialog .habit-mapping-container{border:1px solid var(--background-modifier-border);border-radius:var(--radius-s);padding:10px;margin-bottom:10px;margin-top:5px}.habit-edit-dialog .habit-mapping-row{display:flex;align-items:center;margin-bottom:8px}.habit-edit-dialog .habit-mapping-key{width:80px;margin-right:5px;font-size:.9em}.habit-edit-dialog .habit-mapping-arrow{margin:0 10px;color:var(--text-muted)}.habit-edit-dialog .habit-mapping-value{flex:1;font-size:.9em;margin-right:var(--size-4-4)}.habit-edit-dialog .habit-mapping-delete{background:none;border:none;color:var(--text-error);cursor:pointer;font-size:1.2em;padding:0 8px}.habit-edit-dialog .habit-add-mapping-button{background-color:var(--interactive-accent);color:var(--text-on-accent);border:none;border-radius:var(--radius-s);padding:6px 12px;cursor:pointer;font-size:.9em}.habit-edit-dialog .habit-events-container{border:1px solid var(--background-modifier-border);border-radius:var(--radius-s);padding:10px;margin-bottom:10px;margin-top:5px}.habit-edit-dialog .habit-event-row{display:flex;margin-bottom:8px;gap:5px}.habit-edit-dialog .habit-event-name{width:120px;font-size:.9em}.habit-edit-dialog .habit-event-details{flex:1;font-size:.9em}.habit-edit-dialog .habit-event-property{width:120px;font-size:.9em}.habit-edit-dialog .habit-event-delete{background:none;border:none;color:var(--text-error);cursor:pointer;font-size:1.2em;padding:0 8px}.habit-edit-dialog .habit-add-event-button{background-color:var(--interactive-accent);color:var(--text-on-accent);border:none;border-radius:var(--radius-s);padding:6px 12px;cursor:pointer;font-size:.9em}.habit-edit-dialog .habit-edit-buttons{display:flex;justify-content:flex-end;gap:10px;margin-top:20px}.habit-edit-dialog .habit-cancel-button{background-color:var(--background-modifier-hover);color:var(--text-normal);border:1px solid var(--background-modifier-border);border-radius:var(--radius-s);padding:8px 16px;cursor:pointer}.habit-edit-dialog .habit-save-button{background-color:var(--interactive-accent);color:var(--text-on-accent);border:none;border-radius:var(--radius-s);padding:8px 16px;cursor:pointer}.habit-edit-dialog input[type=text],.habit-edit-dialog input[type=number]{background-color:var(--background-primary);border:1px solid var(--background-modifier-border);border-radius:var(--radius-s);padding:6px;color:var(--text-normal)}.habit-edit-dialog .habit-type-item.selected .habit-type-desc,.habit-edit-dialog .habit-type-item.selected .habit-type-name{color:var(--text-on-accent)}.habit-list-container{padding:12px;width:100%}.habit-settings-container{padding-top:12px;border-top:1px solid var(--background-modifier-border)}.habit-add-button-container{display:flex;justify-content:flex-end;margin-bottom:16px}.habit-add-button{display:flex;align-items:center;gap:6px;padding:6px 12px;background-color:var(--interactive-accent);color:var(--text-on-accent);border-radius:var(--radius-s);cursor:pointer;font-size:14px}.habit-add-button svg{width:16px;height:16px}.habit-empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:200px;text-align:center;padding:20px;border:1px dashed var(--background-modifier-border);border-radius:var(--radius-m);background-color:var(--background-secondary)}.habit-empty-state h2{margin:0 0 10px;font-size:1.2em;color:var(--text-normal)}.habit-empty-state p{margin:0;color:var(--text-muted)}.habit-items-container{display:flex;flex-direction:column;gap:10px}.habit-item{display:flex;align-items:center;padding:12px;border-radius:var(--radius-m);background-color:var(--background-secondary);border:1px solid var(--background-modifier-border);transition:background-color .2s ease;cursor:pointer;height:7.5rem}.habit-item:hover{background-color:var(--background-modifier-hover)}.habit-item-icon{--icon-size: 20px;display:flex;align-items:center;justify-content:center;width:48px;height:48px;border-radius:50%;background-color:var(--background-primary);margin-right:12px}.habit-item-icon svg{color:var(--text-normal)}.habit-item-info{flex:1;min-width:0}.habit-item-name{font-weight:600;margin-bottom:4px;font-size:16px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.habit-item-description{color:var(--text-muted);font-size:12px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-bottom:4px}.habit-item-type{display:inline-block;font-size:11px;padding:2px 6px;border-radius:var(--radius-s);background-color:var(--background-modifier-border);color:var(--text-muted)}.habit-item-actions{display:flex;gap:8px;margin-left:12px}.habit-edit-button,.habit-delete-button{display:flex;align-items:center;justify-content:center;width:32px;height:32px;border-radius:50%;background-color:var(--background-primary);cursor:pointer;padding:0;border:1px solid var(--background-modifier-border)}.habit-edit-button:hover,.habit-delete-button:hover{background-color:var(--background-modifier-hover)}.habit-edit-button svg,.habit-delete-button svg{width:16px;height:16px;color:var(--text-muted)}.habit-delete-button:hover svg{color:var(--text-error)}.habit-delete-modal-buttons{display:flex;justify-content:flex-end;gap:10px;margin-top:20px}.habit-delete-button-confirm{background-color:var(--text-error);color:#fff;border:none;border-radius:var(--radius-s);padding:8px 16px;cursor:pointer}.ics-settings-container{max-width:800px;margin:0 auto}.ics-header-container{margin-bottom:2rem;border-bottom:1px solid var(--background-modifier-border);padding-bottom:1rem}.ics-back-button{background:var(--interactive-accent);color:var(--text-on-accent);border:none;padding:.5rem 1rem;border-radius:6px;cursor:pointer;margin-bottom:1rem;font-size:.9em;transition:all .2s ease}.ics-back-button:hover{background:var(--interactive-accent-hover);transform:translateY(-1px)}.ics-description{color:var(--text-muted);margin-top:.5rem;line-height:1.5}.ics-global-settings{margin-bottom:2rem;padding:1.5rem;border:1px solid var(--background-modifier-border);border-radius:8px;background:var(--background-secondary)}.ics-sources-list{margin-top:1.5rem}.ics-sources-list h3{margin-bottom:1rem;color:var(--text-normal)}.ics-source-item{margin-bottom:1rem;padding:1.5rem;border:1px solid var(--background-modifier-border);border-radius:8px;background:var(--background-primary);transition:all .2s ease}.ics-source-item:hover{border-color:var(--interactive-accent);box-shadow:0 2px 8px #0000001a}.ics-source-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:1rem}.ics-source-title strong{font-size:1.1em;color:var(--text-normal)}.ics-source-status{padding:.3rem .8rem;border-radius:12px;font-size:.75em;font-weight:600;text-transform:uppercase;letter-spacing:.5px}.status-enabled{background:var(--color-green);color:#fff}.status-disabled{background:var(--color-red);color:#fff}.ics-source-details{margin-bottom:1.5rem;font-size:.9em;color:var(--text-muted);line-height:1.4}.ics-source-details div{margin-bottom:.4rem}.ics-source-actions{display:flex;justify-content:space-between;align-items:center;gap:1rem}.primary-actions,.secondary-actions{display:flex;gap:.5rem}.ics-source-actions button{padding:.5rem 1rem;border:1px solid var(--background-modifier-border);border-radius:6px;background:var(--background-secondary);color:var(--text-normal);font-size:.85em;cursor:pointer;transition:all .2s ease;min-width:80px;white-space:nowrap}.ics-source-actions button:hover{background:var(--background-modifier-hover);border-color:var(--interactive-accent);transform:translateY(-1px)}.ics-source-actions button.mod-cta{background:var(--interactive-accent);color:var(--text-on-accent);border-color:var(--interactive-accent)}.ics-source-actions button.mod-cta:hover{background:var(--interactive-accent-hover)}.ics-source-actions button.mod-warning{background:var(--color-red);color:#fff;border-color:var(--color-red)}.ics-source-actions button.mod-warning:hover{background:var(--color-red);opacity:.8}.ics-source-actions button:disabled{opacity:.5;cursor:not-allowed;transform:none}.ics-source-actions button.syncing{color:var(--interactive-accent)}.ics-source-actions button.success{background:var(--color-green);color:#fff;border-color:var(--color-green)}.ics-source-actions button.error{background:var(--color-red);color:#fff;border-color:var(--color-red)}.ics-add-source-container{margin-top:2rem;text-align:center;padding:2rem;border:2px dashed var(--background-modifier-border);border-radius:8px;background:var(--background-secondary);transition:all .2s ease}.ics-add-source-container:hover{border-color:var(--interactive-accent);background:var(--background-modifier-hover)}.ics-add-source-container button{background:var(--interactive-accent);color:var(--text-on-accent);border:none;padding:.8rem 1.5rem;border-radius:6px;font-weight:500;cursor:pointer;transition:all .2s ease;font-size:.95em}.ics-add-source-container button:hover{background:var(--interactive-accent-hover);transform:translateY(-2px)}.ics-test-container{margin-top:1rem;text-align:center;padding:1rem;border:1px solid var(--background-modifier-border);border-radius:8px;background:var(--background-modifier-form-field)}.ics-test-button{background:var(--color-orange);color:#fff;border:none;padding:.6rem 1.2rem;border-radius:6px;font-weight:500;cursor:pointer;transition:all .2s ease;font-size:.9em}.ics-test-button:hover{background:var(--color-orange);opacity:.8;transform:translateY(-1px)}.ics-empty-state{text-align:center;padding:3rem 2rem;color:var(--text-muted);font-style:italic;background:var(--background-secondary);border-radius:8px;border:1px solid var(--background-modifier-border)}.ics-source-modal .modal-content{max-width:600px;max-height:80vh;overflow-y:auto}.auth-field{margin-top:.5rem}.modal-button-container{display:flex;gap:.5rem;justify-content:flex-end;margin-top:1.5rem;padding-top:1rem;border-top:1px solid var(--background-modifier-border)}.modal-button-container button{padding:.5rem 1rem;border-radius:6px;font-size:.9em;min-width:80px}@media (max-width: 768px){.ics-source-header{flex-direction:column;align-items:flex-start;gap:.5rem}.ics-source-actions{flex-direction:column;gap:.5rem}.primary-actions,.secondary-actions{width:100%;justify-content:space-between}.ics-source-actions button{flex:1;min-width:auto}}@media (max-width: 480px){.ics-source-item{padding:1rem}.primary-actions,.secondary-actions{flex-direction:column}.ics-source-actions button{width:100%;margin-bottom:.3rem}.modal-button-container{flex-direction:column}.modal-button-container button{width:100%}}.text-replacements-list{margin:1rem 0}.text-replacements-empty{text-align:center;padding:2rem;color:var(--text-muted);font-style:italic;background:var(--background-secondary);border-radius:6px;border:1px dashed var(--background-modifier-border)}.text-replacement-rule{margin-bottom:1rem;padding:1rem;border:1px solid var(--background-modifier-border);border-radius:6px;background:var(--background-primary);transition:all .2s ease}.text-replacement-rule:hover{border-color:var(--interactive-accent);box-shadow:0 2px 4px #0000001a}.text-replacement-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:.8rem}.text-replacement-header strong{color:var(--text-normal);font-size:1em}.text-replacement-status{padding:.2rem .6rem;border-radius:10px;font-size:.7em;font-weight:600;text-transform:uppercase;letter-spacing:.5px}.text-replacement-status.enabled{background:var(--color-green);color:#fff}.text-replacement-status.disabled{background:var(--color-red);color:#fff}.text-replacement-details{margin-bottom:1rem;font-size:.85em;color:var(--text-muted);line-height:1.4}.text-replacement-details div{margin-bottom:.3rem}.text-replacement-pattern{font-family:var(--font-monospace);background:var(--background-modifier-form-field);padding:.2rem .4rem;border-radius:3px;display:inline-block;margin-left:.5rem}.text-replacement-replacement{font-family:var(--font-monospace);background:var(--background-modifier-form-field);padding:.2rem .4rem;border-radius:3px;display:inline-block;margin-left:.5rem}.text-replacement-actions{display:flex;gap:.5rem;flex-wrap:wrap}.text-replacement-actions button{padding:.4rem .8rem;border:1px solid var(--background-modifier-border);border-radius:4px;background:var(--background-secondary);color:var(--text-normal);font-size:.8em;cursor:pointer;transition:all .2s ease}.text-replacement-actions button:hover{background:var(--background-modifier-hover);border-color:var(--interactive-accent)}.text-replacement-actions button.mod-cta{background:var(--interactive-accent);color:var(--text-on-accent);border-color:var(--interactive-accent)}.text-replacement-actions button.mod-warning{background:var(--color-red);color:#fff;border-color:var(--color-red)}.text-replacement-add{margin-top:1rem;text-align:center}.text-replacement-add button{background:var(--interactive-accent);color:var(--text-on-accent);border:none;padding:.6rem 1.2rem;border-radius:6px;font-weight:500;cursor:pointer;transition:all .2s ease}.text-replacement-add button:hover{background:var(--interactive-accent-hover);transform:translateY(-1px)}.text-replacement-modal .modal-content{max-width:700px;max-height:85vh;overflow-y:auto}.test-output{margin-top:.5rem;padding:.8rem;background:var(--background-modifier-form-field);border-radius:4px;border:1px solid var(--background-modifier-border);font-family:var(--font-monospace);font-size:.9em}.test-result{font-weight:500}.text-replacement-modal ul{margin:.5rem 0;padding-left:1.5rem}.text-replacement-modal li{margin-bottom:.5rem;line-height:1.4}.text-replacement-modal code{background:var(--background-modifier-form-field);padding:.1rem .3rem;border-radius:3px;font-family:var(--font-monospace);font-size:.85em}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.ics-source-actions button.syncing:before{content:"";display:inline-block;margin-right:.3rem;animation:spin 1s linear infinite}.ics-text-replacement-modal,.ics-source-modal{max-width:1000px;max-height:90vh;padding-right:0}.ics-text-replacement-modal .modal-content,.ics-source-modal .modal-content{padding-right:var(--size-4-2)}.task-filter-panel{padding:var(--size-4-4) var(--size-4-4);padding-bottom:var(--size-2-2);padding-left:var(--size-4-8);background-color:var(--background-primary);border-top:1px solid var(--background-modifier-border);display:flex;flex-direction:column;max-height:300px;overflow-y:auto}.task-filter-active{color:var(--color-accent-2);font-weight:bold}.task-filter-panel>.setting-item{border-top:unset}.task-filter-header-container{display:flex;align-items:center;justify-content:flex-end}.task-filter-title{font-size:var(--font-ui-small);color:var(--text-normal)}.task-filter-options{display:flex;flex-direction:column;gap:10px}.task-filter-section{display:flex;flex-direction:column}.task-filter-section h3{font-size:14px;margin:5px 0;color:var(--text-muted)}.task-filter-section:last-child{border-bottom:unset}.task-filter-option{display:flex;align-items:center;gap:6px}.task-filter-option input[type=checkbox]{margin:0}.task-filter-option label{font-size:13px;color:var(--text-normal)}.task-filter-buttons{display:flex;justify-content:flex-end;gap:8px;margin-top:8px;padding-top:8px;border-top:1px solid var(--background-modifier-border)}.task-filter-apply,.task-filter-close{padding:6px 12px;border-radius:4px;font-size:12px;cursor:pointer}.task-filter-apply{background-color:var(--interactive-accent);color:var(--text-on-accent)}.task-filter-reset{background-color:var(--background-modifier-border);color:var(--text-normal)}.task-filter-close{background-color:var(--background-secondary);color:var(--text-normal)}.task-filter-query-input{width:100%;min-width:250px;border-radius:4px;padding:8px 12px;font-family:var(--font-monospace);font-size:14px}.task-filter-query-input:focus{box-shadow:0 0 0 2px var(--interactive-accent);outline:none}.task-filter-section .setting-item-description{margin-top:5px;margin-bottom:10px;font-size:12px;color:var(--text-muted);line-height:1.4}.task-filter-options{max-height:70vh;overflow-y:auto;padding-right:5px}.task-filter-options{margin-bottom:10px;padding-top:var(--size-4-4)}.filter-group-separator{display:flex;align-items:center;justify-content:center;margin:var(--size-2-2) 0;color:var(--text-muted);font-size:var(--font-ui-smaller)}.filter-group-separator:before,.filter-group-separator:after{content:"";flex-grow:1;height:1px;background-color:var( --background-modifier-border );margin:0 var(--size-2-1)}.drag-handle{cursor:grab;display:flex;align-items:center;justify-content:center}.compact-btn{padding:var(--size-2-1) var(--size-2-2);box-shadow:unset!important;border:unset!important;--icon-size: var(--size-4-4);display:flex;justify-content:center;-webkit-app-region:no-drag;display:inline-flex;overflow:hidden;align-items:center;color:var(--text-muted);font-size:var(--font-ui-small);border-radius:var(--button-radius);padding:var(--size-2-2);font-weight:var(--input-font-weight);cursor:var(--cursor);font-family:inherit;gap:var(--size-2-2);min-height:30px}.compact-btn:hover{box-shadow:none;opacity:var(--icon-opacity-hover);background-color:var(--background-modifier-hover);color:var(--text-normal)}.compact-input,.compact-select{font-size:var(--font-ui-smaller);height:var(--input-height);border:1px solid var(--background-modifier-border);box-shadow:none}.compact-select:hover{box-shadow:none}.compact-text{font-size:var(--font-ui-smaller)}.dragging-placeholder{opacity:.5;background-color:var( --background-modifier-hover )}.task-filter-root-container.task-popover-content{padding:var(--size-2-2);max-width:100%;max-height:100%}.task-filter-main-panel{max-width:100%;padding:var(--size-2-2);border-radius:var(--radius-m)}.filter-menu{z-index:50;min-width:600px;background-color:var(--background-primary);border-radius:var(--radius-m);box-shadow:var(--shadow-s);border:1px solid var(--background-modifier-border)}.root-filter-setup-section{display:flex;flex-direction:column;gap:.75rem}.root-condition-section{display:flex;align-items:center;gap:.5rem;padding:.5rem;background-color:var( --background-secondary-alt, var(--background-modifier-hover) );border-radius:var(--radius-m);border:1px solid var(--background-modifier-border)}.root-condition-label{font-weight:500;color:var(--text-normal)}.root-condition-select{width:auto;border:1px solid var(--input-border-color, var(--background-modifier-border))}.root-condition-select:focus{border-color:var(--interactive-accent);box-shadow:0 0 0 1px var(--interactive-accent)}.root-condition-span{color:var(--text-normal)}.filter-groups-container{display:flex;flex-direction:column;gap:var(--size-2-3);max-height:50vh;overflow:auto}.filter-group{padding:var(--size-2-3);border:1px solid var(--background-modifier-border);border-radius:var(--radius-m);background-color:var(--background-primary);display:flex;flex-direction:column;gap:var(--size-4-2)}.filter-group-header{display:flex;align-items:center;justify-content:space-between}.filter-group-header-left{display:flex;align-items:center;gap:.375rem}.filter-group-header-left .drag-handle-container .svg-icon{color:var(--text-faint)}.filter-group-header-left .drag-handle-container:hover .svg-icon{color:var(--text-muted)}.filter-group-header-left .drag-handle-container{padding-right:var(--size-2-1)}.filter-group-header-left>.compact-text,.filter-group-header-left>span.compact-text{font-weight:500;color:var(--text-normal)}.filter-group-header-left .group-condition-select.compact-select{border:1px solid var(--input-border-color, var(--background-modifier-border))}.filter-group-header-left .group-condition-select.compact-select:focus{border-color:var(--interactive-accent);box-shadow:0 0 0 1px var(--interactive-accent)}.filter-group-header-right{display:flex;align-items:center;gap:.25rem}.filter-group-header-right .duplicate-group-btn.compact-icon-btn,.filter-group-header-right .remove-group-btn.compact-icon-btn{border-radius:var(--radius-s)}.filter-group-header-right .duplicate-group-btn.compact-icon-btn .svg-icon{color:var(--text-muted)}.filter-group-header-right .duplicate-group-btn.compact-icon-btn:hover .svg-icon{color:var(--interactive-accent)}.filter-group-header-right .duplicate-group-btn.compact-icon-btn:hover{background-color:var(--background-modifier-hover)}.filter-group-header-right .remove-group-btn.compact-icon-btn .svg-icon{color:var(--text-muted)}.filter-group-header-right .remove-group-btn.compact-icon-btn:hover .svg-icon{color:var(--text-error)}.filter-group-header-right .remove-group-btn.compact-icon-btn:hover{background-color:var( --background-error-hover, var(--background-modifier-error-hover) )}.filters-list{display:flex;flex-direction:column;gap:var(--size-2-2);padding-left:1rem;border-left:2px solid var(--background-modifier-border);margin-left:var(--size-4-2)}.filters-list:empty{display:none}.group-footer{padding-left:.375rem;margin-top:.375rem}.add-filter-btn-icon{display:flex;align-items:center;justify-content:center}.filter-item{display:flex;align-items:center;gap:var(--size-2-2);padding:var(--size-4-2);padding-top:0;padding-bottom:0}.filter-item .filter-conjunction{font-size:var(--font-ui-smaller);font-weight:600;color:var(--text-faint);align-self:center}.filter-item .filter-property-select.compact-select{flex-basis:30%;flex-grow:0;flex-shrink:0;border:1px solid var(--input-border-color, var(--background-modifier-border));box-shadow:none}.filter-item .filter-property-select.compact-select:focus{border-color:var(--interactive-accent);box-shadow:0 0 0 1px var(--interactive-accent)}.filter-item .filter-condition-select.compact-select{width:auto;border:1px solid var(--input-border-color, var(--background-modifier-border));box-shadow:none}.filter-item .filter-condition-select.compact-select:focus{border-color:var(--interactive-accent);box-shadow:0 0 0 1px var(--interactive-accent)}.filter-item .filter-value-input.compact-input{flex-grow:1;border:1px solid var(--input-border-color, var(--background-modifier-border));width:100%}.filter-item .filter-value-input.compact-input:focus{border-color:var(--interactive-accent);box-shadow:0 0 0 1px var(--interactive-accent)}.filter-item .remove-filter-btn.compact-icon-btn .svg-icon{color:var(--text-muted)}.filter-item .remove-filter-btn.compact-icon-btn:hover .svg-icon{color:var(--text-error)}.filter-item .remove-filter-btn.compact-icon-btn:hover{background-color:var( --background-error-hover, var(--background-modifier-error-hover) )}.add-group-section{margin-top:var(--size-2-1);margin-bottom:var(--size-2-1);margin-left:var(--size-2-1);display:flex;justify-content:space-between}.add-filter-group-btn-icon{display:flex;align-items:center;justify-content:center}.filter-config-section{display:flex;gap:var(--size-4-2)}.save-filter-config-btn,.load-filter-config-btn{flex:1}.save-filter-config-btn-icon,.load-filter-config-btn-icon{display:flex;align-items:center;justify-content:center}.save-filter-config-btn:hover{background-color:var(--interactive-accent-hover);color:var(--text-on-accent)}.load-filter-config-btn:hover{background-color:var(--background-modifier-hover)}.filter-config-details{margin-top:var(--size-4-3);padding:var(--size-4-3);border:1px solid var(--background-modifier-border);border-radius:var(--radius-l);background:linear-gradient(135deg,var(--background-secondary) 0%,var(--background-primary-alt) 100%);box-shadow:0 2px 8px #0000001a;transition:all .2s ease-in-out}.filter-config-details:hover{box-shadow:0 4px 12px #00000026;transform:translateY(-1px)}.filter-config-details h3{margin:0 0 var(--size-4-2) 0;font-size:var(--font-ui-medium);font-weight:600;color:var(--text-accent);display:flex;align-items:center;gap:var(--size-2-2)}.filter-config-details p{margin:var(--size-2-2) 0;line-height:1.5;color:var(--text-normal)}.filter-config-meta{font-size:var(--font-ui-smaller);color:var(--text-muted);margin:var(--size-2-1) 0;padding:var(--size-2-1) var(--size-2-2);background-color:var(--background-modifier-form-field);border-radius:var(--radius-s);border-left:3px solid var(--interactive-accent)}.filter-config-summary{margin-top:var(--size-4-3);padding:var(--size-4-2) 0 0 0;border-top:2px solid var(--background-modifier-border)}.filter-config-summary h4{margin:0 0 var(--size-2-3) 0;font-size:var(--font-ui-small);font-weight:600;color:var(--text-normal);display:flex;align-items:center;gap:var(--size-2-1)}.filter-config-summary p{margin:var(--size-2-1) 0;font-size:var(--font-ui-smaller);color:var(--text-muted);padding:var(--size-2-1) var(--size-2-2);background-color:var(--background-primary-alt);border-radius:var(--radius-s)}.filter-config-buttons{margin-top:var(--size-4-3);padding-top:var(--size-4-2)}.filter-config-name-highlight{background-color:var(--text-accent);color:var(--text-on-accent);padding:.125rem .25rem;border-radius:var(--radius-s);font-weight:500}.advanced-filter-container{margin-top:var(--size-4-2);padding:var(--size-4-3);border:1px solid var(--background-modifier-border);border-radius:var(--radius-m);background-color:var(--background-secondary)}.advanced-filter-container .task-filter-root-container{background-color:transparent;border:none;padding:0}.advanced-filter-container .task-filter-main-panel{background-color:transparent;border:none;padding:0}.task-genius-view-config-modal .advanced-filter-container .filter-group{padding:var(--size-4-2);margin-bottom:var(--size-4-2)}.task-genius-view-config-modal .advanced-filter-container .filter-item{padding:var(--size-2-2);gap:var(--size-2-2)}.task-genius-view-config-modal .advanced-filter-container .compact-btn{padding:var(--size-2-1) var(--size-2-2);min-height:26px}.task-genius-view-config-modal .advanced-filter-container .compact-select,.task-genius-view-config-modal .advanced-filter-container .compact-input{font-size:var(--font-ui-smaller);height:28px}.file-filter-rules-container{margin-top:1rem;border:1px solid var(--background-modifier-border);border-radius:6px;padding:1rem;background:var(--background-secondary)}.file-filter-rule{display:flex;align-items:center;gap:1rem;padding:.75rem;margin-bottom:.5rem;border:1px solid var(--background-modifier-border);border-radius:4px;background:var(--background-primary)}.file-filter-rule:last-child{margin-bottom:0}.file-filter-rule-type,.file-filter-rule-path,.file-filter-rule-enabled{display:flex;flex-direction:column;gap:.25rem}.file-filter-rule-type{min-width:80px}.file-filter-rule-path{flex:1}.file-filter-rule-enabled{min-width:60px}.file-filter-rule label{font-size:.8rem;font-weight:500;color:var(--text-muted)}.file-filter-rule input[type=text]{padding:.25rem .5rem;border:1px solid var(--background-modifier-border);border-radius:3px;background:var(--background-primary);color:var(--text-normal);font-size:.9rem}.file-filter-rule input[type=checkbox]{width:16px;height:16px}.file-filter-rule-delete{padding:.25rem;border:none;border-radius:3px;background:var(--interactive-accent);color:var(--text-on-accent);cursor:pointer;display:flex;align-items:center;justify-content:center;min-width:28px;height:28px}.file-filter-add-rule{margin-top:1rem}.file-filter-add-rule .setting-item{border:none;padding:0}.file-filter-add-rule .setting-item-control{gap:.5rem}.file-filter-add-rule+.setting-item{border-top:none}.file-filter-stats{margin-top:1.5rem;padding:1rem;border:1px solid var(--background-modifier-border);border-radius:6px;background:var(--background-secondary)}.file-filter-stat{display:flex;justify-content:space-between;align-items:center;padding:.25rem 0}.file-filter-stat:not(:last-child){border-bottom:1px solid var(--background-modifier-border);margin-bottom:.25rem;padding-bottom:.5rem}.stat-label{font-weight:500;color:var(--text-normal)}.stat-value{font-weight:600;color:var(--interactive-accent)}.file-filter-stat.error{background-color:var(--background-modifier-error);border-left:3px solid var(--text-error)}.file-filter-stat.error .stat-label{color:var(--text-error)}.setting-item .setting-item-control button[aria-label*=refresh]{transition:transform .2s ease}.setting-item .setting-item-control button[aria-label*=refresh]:hover{transform:rotate(90deg)}@keyframes refresh-spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.setting-item .setting-item-control button[disabled] .lucide-refresh-cw{animation:refresh-spin 1s linear infinite}@media (max-width: 768px){.file-filter-rule{flex-direction:column;align-items:stretch;gap:.5rem}.file-filter-rule-type,.file-filter-rule-path,.file-filter-rule-enabled{min-width:auto}.file-filter-rule-delete{align-self:flex-end;margin-top:.5rem}}.theme-dark .file-filter-rule input[type=text]{background:var(--background-primary-alt);border-color:var(--background-modifier-border-hover)}.theme-dark .file-filter-rule input[type=text]:focus{border-color:var(--interactive-accent);box-shadow:0 0 0 2px var(--interactive-accent-hover)}.file-filter-rules-container:empty:before{content:"No filter rules configured. Add rules below to start filtering files and folders.";display:block;text-align:center;color:var(--text-muted);font-style:italic;padding:2rem}.file-filter-preset-container{margin-top:1rem;padding:1rem;border:1px solid var(--background-modifier-border);border-radius:6px;background:var(--background-secondary)}.file-filter-preset-container .setting-item{border:none;padding:.5rem 0}.file-filter-preset-container .setting-item:not(:last-child){border-bottom:1px solid var(--background-modifier-border)}.file-filter-preset-container button{position:relative;transition:all .2s ease}.file-filter-preset-container button:disabled{opacity:.6;cursor:not-allowed;background:var(--background-modifier-border);color:var(--text-muted)}.file-filter-preset-container button:not(:disabled):hover{transform:translateY(-1px);box-shadow:0 2px 8px #0000001a}.file-filter-preset-container button[disabled]{background:var(--color-green);color:var(--text-on-accent);border-color:var(--color-green)}.theme-dark .file-filter-preset-container button[disabled]{background:var(--color-green-rgb);opacity:.8}.cm-workflow-stage-indicator{display:inline-block;margin-left:4px;font-size:12px;cursor:pointer;opacity:.7;transition:opacity .2s ease;user-select:none;align-items:center;vertical-align:middle}.cm-workflow-stage-indicator span{display:inline-flex;justify-content:center;align-items:center}.cm-workflow-stage-indicator:hover{opacity:1}.cm-workflow-stage-indicator[data-stage-type=linear]{color:var(--text-accent)}.cm-workflow-stage-indicator[data-stage-type=cycle]{color:var(--task-in-progress-color)}.cm-workflow-stage-indicator[data-stage-type=terminal]{color:var(--task-completed-color)}.theme-dark .cm-workflow-stage-indicator[data-stage-type=linear]{color:var(--text-accent)}.theme-dark .cm-workflow-stage-indicator[data-stage-type=cycle]{color:var(--task-in-progress-color)}.theme-dark .cm-workflow-stage-indicator[data-stage-type=terminal]{color:var(--task-completed-color)}.date-picker-root-container{display:flex;flex-direction:column;width:100%;min-width:500px;max-width:600px}.date-picker-root-container .date-picker-main-panel{display:flex;gap:var(--size-2-3);padding:var(--size-2-3)}.date-picker-root-container .date-picker-left-panel{flex:1;min-width:200px;border-right:1px solid var(--background-modifier-border)}.date-picker-root-container .date-picker-right-panel{flex:1;min-width:250px}.date-picker-root-container .date-picker-section-title{font-size:var(--font-ui-medium);font-weight:var(--font-bold);margin-bottom:var(--size-4-2);color:var(--text-normal)}.date-picker-root-container .quick-options-container{display:flex;flex-direction:column;gap:var(--size-2-1);max-height:195px;overflow:auto;overflow-x:hidden}.date-picker-root-container .quick-option-item{display:flex;justify-content:space-between;align-items:center;padding:var(--size-2-2) var(--size-4-2);cursor:pointer;transition:background-color .2s ease}.date-picker-root-container .quick-option-item:hover{background-color:var(--background-modifier-hover)}.date-picker-root-container .quick-option-item.selected{background-color:var(--interactive-accent);color:var(--text-on-accent)}.date-picker-root-container .quick-option-item.clear-option{border-top:1px solid var(--background-modifier-border);margin-top:var(--size-2-2);padding-top:var(--size-2-3);color:var(--text-error)}.date-picker-root-container .quick-option-item.clear-option:hover{color:var(--text-on-accent);background-color:var(--background-modifier-error-hover)}.date-picker-root-container .quick-option-label{font-size:var(--font-ui-small);font-weight:var(--font-medium)}.date-picker-root-container .quick-option-date{font-size:var(--font-ui-smaller);color:var(--text-muted);font-family:var(--font-monospace)}.date-picker-root-container .quick-option-item.selected .quick-option-date{color:var(--text-on-accent)}.date-picker-root-container .calendar-container{display:flex;flex-direction:column}.date-picker-root-container .calendar-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:var(--size-4-2);padding:0 var(--size-2-2)}.date-picker-root-container .calendar-nav-btn{display:flex;align-items:center;justify-content:center;width:32px;height:32px;border-radius:var(--radius-s);cursor:pointer;transition:background-color .2s ease}.date-picker-root-container .calendar-nav-btn:hover{background-color:var(--background-modifier-hover)}.date-picker-root-container .calendar-month-year{font-size:var(--font-ui-medium);font-weight:var(--font-bold);color:var(--text-normal)}.date-picker-root-container .calendar-grid{display:grid;grid-template-columns:repeat(7,1fr);gap:1px;background-color:var(--background-modifier-border);border-radius:var(--radius-s);overflow:hidden}.date-picker-root-container .calendar-day-header{background-color:var(--background-secondary);padding:var(--size-2-2);text-align:center;font-size:var(--font-ui-smaller);font-weight:var(--font-bold);color:var(--text-muted)}.date-picker-root-container .calendar-day{background-color:var(--background-primary);padding:var(--size-2-2);text-align:center;font-size:var(--font-ui-small);cursor:pointer;transition:background-color .2s ease;min-height:32px;display:flex;align-items:center;justify-content:center}.date-picker-root-container .calendar-day:hover{background-color:var(--background-modifier-hover)}.date-picker-root-container .calendar-day.other-month{color:var(--text-faint);background-color:var(--background-secondary)}.date-picker-root-container .calendar-day.today{background-color:var(--interactive-accent-hover);color:var(--text-on-accent);font-weight:var(--font-bold)}.date-picker-root-container .calendar-day.selected{background-color:var(--interactive-accent);color:var(--text-on-accent);font-weight:var(--font-bold)}.date-picker-root-container .calendar-day.today.selected{background-color:var(--interactive-accent);box-shadow:inset 0 0 0 2px var(--text-on-accent)}.date-picker-popover.tg-menu{z-index:20;position:fixed;background-color:var(--background-primary);border:1px solid var(--background-modifier-border);border-radius:var(--radius-s);box-shadow:var(--shadow-l);max-height:80vh;overflow:auto}.date-picker-popover.tg-menu .date-picker-popover-content{padding:0}@media (max-width: 768px){.date-picker-root-container .date-picker-main-panel{flex-direction:column;gap:var(--size-4-2)}.date-picker-root-container .date-picker-left-panel{border-right:none;border-bottom:1px solid var(--background-modifier-border);padding-right:0;padding-bottom:var(--size-4-2)}.date-picker-root-container{min-width:300px;max-width:400px}.date-picker-root-container .calendar-day{min-height:40px;font-size:var(--font-ui-medium)}}.date-picker-root-container .date-picker-widget-error{color:var(--text-error);background-color:var(--background-modifier-error);padding:var(--size-2-1) var(--size-2-2);border-radius:var(--radius-s);font-size:var(--font-ui-smaller)}.quick-capture-panel{padding:var(--size-4-2);background-color:var(--background-primary);border-top:1px solid var(--background-modifier-border);display:flex;flex-direction:column;gap:var(--size-4-2)}.quick-capture-modal.minimal{max-width:600px;min-width:500px;max-height:200px}.quick-capture-minimal-editor-container{padding:var(--size-4-2);min-height:50px}.quick-capture-minimal-editor-container .cm-editor{font-size:var(--font-text-size);min-height:40px;border:1px solid var(--background-modifier-border);border-radius:var(--radius-s);padding:var(--size-2-1)}.quick-capture-minimal-editor-container .cm-editor.cm-focused{border-color:var(--interactive-accent);box-shadow:0 0 0 2px var(--interactive-accent-alpha)}.quick-capture-minimal-buttons{display:flex;justify-content:space-between;align-items:center;padding:var(--size-4-2)}.quick-actions-left{display:flex;gap:var(--size-2-1)}.quick-actions-right{display:flex;gap:var(--size-2-1)}.quick-action-button.active{background-color:var(--interactive-accent);color:var(--text-on-accent);border-color:var(--interactive-accent)}.quick-action-save{padding:var(--size-2-1) var(--size-4-2);min-width:80px;height:32px;border-radius:var(--radius-s)}.quick-capture-tag-input{position:absolute;bottom:60px;left:50%;transform:translate(-50%);width:300px;padding:var(--size-2-1);border:1px solid var(--background-modifier-border);border-radius:var(--radius-s);background-color:var(--background-primary);color:var(--text-normal);font-size:var(--font-text-size);z-index:1000}.minimal-quick-capture-suggestion{padding:var(--size-2-1) var(--size-4-2);border-radius:var(--radius-s);cursor:pointer;transition:background-color .2s ease;min-height:40px;display:flex;align-items:center}.minimal-quick-capture-suggestion:hover{background-color:var(--background-modifier-hover)}.minimal-quick-capture-suggestion.is-selected{background-color:var(--interactive-accent);color:var(--text-on-accent)}.minimal-quick-capture-suggestion.is-selected .suggestion-label{color:var(--text-on-accent)}.minimal-quick-capture-suggestion.is-selected .suggestion-description{color:var(--text-on-accent);opacity:.8}.suggestion-icon{font-size:16px;min-width:20px;text-align:center}.suggestion-content{flex:1}.suggestion-label{font-size:var(--font-text-size);font-weight:500;color:var(--text-normal)}.suggestion-description{font-size:var(--font-ui-small);color:var(--text-muted);margin-top:2px}.quick-capture-header-container{display:flex;align-items:center;margin-bottom:var(--size-4-2);gap:var(--size-4-2);font-size:var(--font-ui-medium);font-weight:bold;color:var(--text-normal);padding:var(--size-2-1) var(--size-4-2)}.quick-capture-title{color:var(--text-normal);white-space:nowrap}.quick-capture-target{flex:1;border-radius:var(--radius-s);color:var(--text-accent);font-size:var(--font-text-size);font-weight:normal;min-width:100px;max-width:500px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.quick-capture-target:focus{outline:none}.quick-capture-hint{font-size:12px;color:var(--text-muted);margin-bottom:8px;margin-top:-4px;text-align:right}.quick-capture-editor{min-height:200px;background-color:var(--background-primary)}.quick-capture-file-suggest{max-width:500px}.quick-capture-buttons{display:flex;justify-content:flex-end;gap:8px}.quick-capture-submit,.quick-capture-cancel{padding:6px 12px;border-radius:4px;cursor:pointer}.quick-capture-submit{background-color:var(--interactive-accent);color:var(--text-on-accent)}.quick-capture-cancel{background-color:var(--background-modifier-border);color:var(--text-normal)}.quick-capture-modal .modal-title{display:flex;align-items:center;flex-direction:row;gap:10px;font-size:var(--font-ui-medium);font-weight:bold}.quick-capture-modal-editor{min-height:150px;margin-bottom:20px}.quick-capture-modal-buttons{display:flex;justify-content:flex-end;gap:10px}.quick-capture-modal.full{width:80vw;max-width:900px}.quick-capture-layout{display:flex;height:100%;gap:16px;margin-bottom:16px}.quick-capture-config-panel{flex:1;border-right:1px solid var(--background-modifier-border);padding-right:16px;overflow-y:auto;max-width:40%}.quick-capture-editor-panel{flex:1.5;display:flex;flex-direction:column}.quick-capture-section-title{font-weight:bold;margin-bottom:8px;font-size:var(--font-ui-medium);color:var(--text-normal)}.quick-capture-target-container{margin-bottom:16px}.quick-capture-modal.full .quick-capture-modal-editor{min-height:200px;flex:1;overflow-y:auto;border:1px solid var(--background-modifier-border);border-radius:var(--radius-s);padding:8px;margin-top:8px}@media (max-width: 768px){.quick-capture-modal.full{width:95vw}.quick-capture-layout{flex-direction:column}.quick-capture-config-panel{max-width:100%;border-right:none;border-bottom:1px solid var(--background-modifier-border);padding-right:0;padding-bottom:16px;margin-bottom:16px;max-height:40%}}.quick-capture-config-panel .details-status-selector{display:flex;flex-direction:row;justify-content:space-between;margin-bottom:var(--size-4-2);margin-top:var(--size-4-2)}.quick-capture-config-panel .quick-capture-status-selector{display:flex;flex-direction:row;justify-content:space-between;gap:var(--size-4-3)}.task-list{flex:1;overflow-y:auto;padding:0}.task-item{display:flex;align-items:flex-start;padding:8px 16px;border-bottom:1px solid var(--background-modifier-border);cursor:pointer;gap:var(--size-2-3)}.task-item:hover{background-color:var(--background-secondary-alt)}.task-children-container .task-item:hover{background-color:var(--background-secondary)}.task-item.selected{background-color:var(--background-secondary-alt)}.task-item.task-completed .task-item-content{text-decoration:line-through;color:var(--text-muted)}.task-item .markdown-block.markdown-renderer>p:only-child{padding:0;margin:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.task-checkbox{width:16px;height:16px;display:flex;align-items:center;justify-content:center;color:var(--text-normal);cursor:pointer;flex-shrink:0}.task-item.task-completed .task-checkbox{color:var(--text-on-accent)}.task-item-content{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.task-item-container{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.task-item-metadata{display:flex;align-items:center;gap:var(--size-4-2);margin-top:var(--size-2-2)}.task-item-metadata:empty{display:none}.task-date{font-size:var(--font-ui-small);color:var(--text-faint);white-space:nowrap;background-color:var(--background-modifier-active-hover);padding:var(--size-2-1) var(--size-2-3);border-radius:var(--radius-s);opacity:.8}.task-item:hover .task-date{opacity:1}.task-date:before{display:inline-block;margin-right:var(--size-2-2);font-size:xx-small;display:inline-flex;transform:translateY(-1px)}.tg-kanban-view .task-date:before{transform:translateY(0)}.task-date.task-due-date:before{content:"\1f4c5"}.task-date.task-overdue{color:var(--text-error);font-weight:600}.task-date.task-due-today{color:var(--task-doing-color);font-weight:600}.task-date.task-due-soon{color:var(--text-warning);font-weight:600}.task-date.task-start-date:before{content:"\1f6eb"}.task-date.task-created-date:before{content:"\2795"}.task-date.task-scheduled-date:before{content:"\23f3"}.task-date.task-done-date:before{content:"\2705"}.task-date.task-cancelled-date:before{content:"\274c"}.task-date.task-recurrence:before{content:"\1f501"}.task-date.task-on-completion:before{content:"\1f3c1"}.task-project{font-size:var(--font-ui-small);color:var(--text-on-accent);background-color:var(--color-accent);border-radius:var(--radius-s);padding:var(--size-2-1) var(--size-2-3);white-space:nowrap;opacity:.5}.task-project:has(input){background-color:var(--background-modifier-active-hover);color:var(--text-normal)}.task-item:hover .task-project{opacity:1}.task-project:before{content:"\1f5c2\fe0f";margin-right:var(--size-4-2);display:inline-flex;align-items:center;justify-content:center;font-size:var(--font-ui-small)}.task-project:hover{background-color:var(--background-modifier-active-hover);color:var(--text-accent-hover)}.task-priority{margin-left:8px;font-size:.9em;white-space:nowrap}.task-priority.priority-5{color:var(--text-error);font-weight:600}.task-priority.priority-4{color:var(--text-warning);font-weight:600}.task-priority.priority-3{color:var(--text-warning);font-weight:600}.task-priority.priority-2{color:var(--text-warning)}.task-priority.priority-1{color:var(--text-accent)}.task-oncompletion{display:inline-flex;align-items:center;padding:2px 6px;margin-left:4px;border-radius:3px;font-size:var(--font-ui-small);color:var(--text-muted);white-space:nowrap}.task-oncompletion:hover{color:var(--text-normal)}.task-dependson{display:inline-flex;align-items:center;padding:2px 6px;margin-left:4px;background-color:var(--background-modifier-error);border-radius:3px;font-size:var(--font-ui-small);color:var(--text-error);white-space:nowrap}.task-dependson:hover{background-color:var(--background-modifier-error-hover);color:var(--text-error)}.task-id{display:inline-flex;align-items:center;padding:2px 6px;margin-left:4px;background-color:var(--background-modifier-accent);border-radius:3px;font-size:var(--font-ui-small);color:var(--text-accent);white-space:nowrap}.task-id:hover{background-color:var(--background-modifier-accent-hover);color:var(--text-accent-hover)}.task-tags-container{display:flex;flex-wrap:wrap;gap:var(--size-2-2)}.task-tags-container:empty{display:none}.task-tag{font-size:var(--font-ui-small);color:var(--text-normal);background-color:var(--background-modifier-hover);border-radius:var(--radius-s);padding:var(--size-2-1) var(--size-2-3);white-space:nowrap;opacity:.75}.task-item:hover .task-tag{opacity:1}.task-item-content p:has(img) img{display:block;width:min(50%,200px)}.inline-editor{position:relative;display:inline-block;width:100%}.inline-content-editor{width:100%;min-height:18px;border:none;border-bottom:1px solid var(--interactive-accent);border-radius:0;padding:2px 4px;background-color:transparent;color:var(--text-normal);font-family:inherit;font-size:inherit;line-height:inherit;resize:none;outline:none;transition:border-color .15s ease,background-color .15s ease}.inline-content-editor:focus{border-bottom-color:var(--interactive-accent-hover);background-color:var(--background-primary-alt);box-shadow:0 1px 0 0 var(--interactive-accent-hover)}.inline-embedded-editor-container{width:100%;min-height:18px;border:none;border-radius:0;background-color:transparent}.inline-embedded-editor{width:100%;min-height:18px;background-color:transparent}.inline-embedded-editor .cm-editor{border:none!important;outline:none!important;background-color:transparent!important;border-bottom:1px solid var(--interactive-accent)!important}.inline-embedded-editor .cm-focused{outline:none!important;border-bottom-color:var(--interactive-accent-hover)!important;background-color:var(--background-primary-alt)!important}.inline-embedded-editor .cm-content{padding:2px 4px;min-height:18px;font-family:inherit;font-size:inherit;line-height:inherit}.inline-embedded-editor .cm-line{padding:0}.inline-metadata-editor{display:inline-flex;align-items:center;gap:4px;padding:2px 6px;background-color:var(--background-primary-alt);border:1px solid var(--interactive-accent);border-radius:var(--radius-s);box-shadow:0 1px 3px #0000001a;min-width:120px;max-width:300px;position:relative;z-index:100}.inline-metadata-editor input{border:unset;outline:unset;padding:0;height:var(--line-height);background-color:transparent;background:transparent;border-radius:var(--radius-s)}.inline-metadata-editor input:focus{outline:unset;padding:0;background-color:transparent}.inline-metadata-editor:has(input){outline:unset;border:0;padding:0;background-color:transparent;border-radius:unset}.inline-project-input,.inline-tags-input,.inline-context-input,.inline-date-input,.inline-recurrence-input{flex:1;padding:2px 4px;border:none;border-radius:2px;background-color:transparent;color:var(--text-normal);font-family:inherit;font-size:var(--font-ui-small);outline:none;min-width:80px;transition:background-color .15s ease}.inline-project-input:focus,.inline-tags-input:focus,.inline-context-input:focus,.inline-date-input:focus,.inline-recurrence-input:focus{background-color:var(--background-primary);box-shadow:inset 0 0 0 1px var(--interactive-accent)}.inline-priority-select{padding:2px 4px;border:none;border-radius:2px;background-color:transparent;color:var(--text-normal);font-family:inherit;font-size:var(--font-ui-small);outline:none;cursor:pointer;min-width:80px}.inline-priority-select:focus{background-color:var(--background-primary);box-shadow:inset 0 0 0 1px var(--interactive-accent)}.add-metadata-container{display:inline-flex;align-items:center;margin-left:4px}.task-list .task-item:not(.tree-task-item):hover .add-metadata-btn{opacity:1}.tree-task-item .task-item-container:hover .add-metadata-btn{opacity:1}.add-metadata-btn{display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;border:none;border-radius:2px;background-color:var(--background-secondary);color:var(--text-muted);cursor:pointer;transition:all .15s ease;--icon-size: 10px;opacity:0;padding:0;margin:0}.add-metadata-btn:hover{background-color:var(--background-modifier-hover);color:var(--text-normal);opacity:1}.add-metadata-btn:active{background-color:var(--background-modifier-active);transform:scale(.95)}.add-metadata-btn svg{width:10px;height:10px}.inline-editor *{transition:border-color .15s ease,background-color .15s ease,box-shadow .15s ease}.inline-editor input:focus,.inline-editor textarea:focus,.inline-editor select:focus{outline:none}.task-item-metadata .task-date,.task-item-metadata .task-project,.task-item-metadata .task-tag{cursor:pointer;transition:background-color .15s ease,transform .15s ease;position:relative}.task-item-metadata .task-date:hover,.task-item-metadata .task-project:hover,.task-item-metadata .task-tag:hover{background-color:var(--background-modifier-hover);transform:none}.task-item-metadata .task-date:hover:after,.task-item-metadata .task-project:hover:after,.task-item-metadata .task-tag:hover:after{display:none}.task-item-content{cursor:pointer;transition:background-color .15s ease}.inline-metadata-editor{animation:fadeInScale .15s ease-out}@keyframes fadeInScale{0%{opacity:0;transform:scale(.95)}to{opacity:1;transform:scale(1)}}.inline-editor-placeholder{min-height:1em;display:inline-block}@media (max-width: 768px){.inline-project-input,.inline-tags-input,.inline-context-input,.inline-recurrence-input{min-width:100px;font-size:var(--font-ui-smaller)}.inline-metadata-editor{max-width:250px}}@media (prefers-contrast: high){.inline-content-editor,.inline-embedded-editor .cm-editor{border-bottom-width:2px}.inline-metadata-editor{border-width:2px}}@media (prefers-reduced-motion: reduce){.inline-editor *,.task-item-metadata .task-date,.task-item-metadata .task-project,.task-item-metadata .task-tag,.task-item-content,.add-metadata-btn{transition:none}.inline-metadata-editor{animation:none}}.inline-dependson-input,.inline-id-input{width:100%;min-width:200px;padding:4px 8px;border:1px solid var(--background-modifier-border);border-radius:4px;background-color:var(--background-primary);color:var(--text-normal);font-family:inherit;font-size:var(--font-ui-small);outline:none;transition:border-color .15s ease,box-shadow .15s ease}.inline-dependson-input:focus,.inline-id-input:focus{border-color:var(--interactive-accent);box-shadow:0 0 0 2px var(--interactive-accent-hover)}.inline-dependson-input::placeholder,.inline-id-input::placeholder{color:var(--text-faint)}.tree-task-item{position:relative;display:flex;flex-direction:column;padding:8px 16px;transition:background-color .2s ease}.task-children-container .task-item.tree-task-item{border-bottom:unset;padding-top:var(--size-2-2);padding-bottom:var(--size-2-2);gap:0}.task-item.tree-task-item{gap:0}.tree-task-item:hover{background-color:var(--background-secondary-alt)}.tree-task-item.selected{background-color:var(--background-modifier-active)}.tree-task-item.completed{opacity:.7}.tree-task-item>div:first-of-type{width:100%;display:flex;align-items:flex-start;gap:6px}.task-indent{flex-shrink:0}.task-item.tree-task-item .task-expand-toggle{padding-top:var(--size-2-2)}.task-item .task-checkbox{padding-top:var(--size-2-2)}.task-expand-toggle{cursor:pointer;display:flex;align-items:center;justify-content:center;width:16px;height:16px;flex-shrink:0;color:var(--text-muted)}.task-expand-toggle:hover{color:var(--text-normal)}.task-item.tree-task-item .task-checkbox{cursor:pointer;flex-shrink:0;color:var(--text-muted);width:16px;height:16px;display:flex;align-items:center;justify-content:center}.task-item.tree-task-item .task-checkbox:hover{color:var(--text-accent)}.task-item.tree-task-item .task-checkbox.checked{color:var(--text-accent)}.task-content{flex-grow:1;line-height:1.4}.tree-task-item.completed .task-content{text-decoration:line-through;color:var(--text-muted)}.task-metadata{display:flex;gap:8px;margin-top:4px;font-size:.85em;color:var(--text-muted)}.task-metadata:empty{display:none}.task-due-date.overdue{color:var(--text-error);font-weight:bold}.task-item.tree-task-item .task-project{display:inline-block;padding:1px 6px;border-radius:4px}.task-priority.priority-3{color:var(--text-error)}.task-priority.priority-2{color:var(--text-warning)}.task-priority.priority-1{color:var(--text-accent)}.tree-task-item .task-oncompletion{display:inline-flex;align-items:center;padding:2px 6px;margin-left:4px;background-color:var(--background-modifier-border);border-radius:3px;font-size:var(--font-ui-small);color:var(--text-muted);white-space:nowrap}.tree-task-item .task-oncompletion:hover{color:var(--text-normal)}.tree-task-item .task-dependson{display:inline-flex;align-items:center;padding:2px 6px;margin-left:4px;background-color:var(--background-modifier-error);border-radius:3px;font-size:var(--font-ui-small);color:var(--text-error);white-space:nowrap}.tree-task-item .task-dependson:hover{background-color:var(--background-modifier-error-hover);color:var(--text-error)}.tree-task-item .task-id{display:inline-flex;align-items:center;padding:2px 6px;margin-left:4px;background-color:var(--background-modifier-accent);border-radius:3px;font-size:var(--font-ui-small);color:var(--text-accent);white-space:nowrap}.tree-task-item .task-id:hover{background-color:var(--background-modifier-accent-hover);color:var(--text-accent-hover)}.task-children-container{margin-top:4px;width:100%}.view-toggle-btn{cursor:pointer;display:flex;align-items:center;justify-content:center;width:24px;height:24px;color:var(--text-muted);border-radius:4px}.view-toggle-btn:hover{background-color:var(--background-modifier-hover);color:var(--text-normal)}.task-children-container:empty{display:none!important}.forecast-container{display:flex;flex-direction:column;height:100%;overflow:hidden;flex:1}.forecast-header{display:flex;justify-content:space-between;align-items:center;padding:15px;border-bottom:1px solid var(--background-modifier-border)}.forecast-title-container{display:flex;flex-direction:column}.forecast-title{font-weight:600;font-size:1.2em}.forecast-count{font-size:.8em;color:var(--text-muted);margin-top:4px}.forecast-actions{display:flex;gap:var(--size-4-2);align-items:center;justify-content:center}.forecast-settings{cursor:pointer;opacity:.7;transition:opacity .2s ease;display:flex;align-items:center;justify-content:center}.forecast-settings:hover{opacity:1}.forecast-focus-bar{display:flex;padding:10px 15px;border-bottom:1px solid var(--background-modifier-border);gap:10px;align-items:center}.focus-input{flex:1;padding:6px 12px;border-radius:4px;border:1px solid var(--interactive-accent);background-color:var(--background-primary);color:var(--text-normal)}.unfocus-button{padding:6px 12px;border-radius:4px;background-color:var(--interactive-accent);color:var(--text-on-accent);cursor:pointer;border:none}.unfocus-button:hover{background-color:var(--interactive-accent-hover)}.forecast-content{display:flex;flex:1;overflow:hidden}.forecast-left-column{width:360px;min-width:360px;border-right:1px solid var(--background-modifier-border);display:flex;flex-direction:column;overflow-y:auto;background-color:var(--background-secondary-alt)}.forecast-right-column{flex:1;display:flex;flex-direction:column;background-color:var(--background-primary)}.forecast-task-list{overflow-y:auto}.forecast-calendar-section{padding:10px 0;margin-top:var(--size-4-4);flex-shrink:0;border-top:1px solid var(--background-modifier-border)}.forecast-stats{display:flex}.stat-item{flex:1;display:flex;flex-direction:column;align-items:center;padding:10px;cursor:pointer;transition:background-color .2s ease;position:relative}.stat-item:after{content:"";position:absolute;bottom:0;left:10%;width:80%;height:3px;background-color:transparent;transition:background-color .2s ease}.stat-item:hover{background-color:var(--background-modifier-hover)}.stat-item.active:after{background-color:var(--interactive-accent);animation:color-pulse 1.5s infinite alternate}@keyframes color-pulse{0%{background-color:var(--color-accent-1)!important;opacity:.7}to{background-color:var(--color-accent-2)!important;opacity:1}}.stat-item.tg-past-due:after{background-color:var(--text-error);opacity:.7}.stat-item.tg-today:after{background-color:var(--interactive-accent);opacity:.7}.stat-item.tg-future:after{background-color:var(--text-accent);opacity:.7}.stat-count{font-size:1.5em;font-weight:600}.stat-item.tg-past-due .stat-count{color:var(--text-error)}.stat-label{font-size:.8em;color:var(--text-muted)}.forecast-due-soon-section{display:flex;flex-direction:column;padding-bottom:var(--size-4-3)}.due-soon-header{font-size:.8em;font-weight:600;padding:5px 15px;color:var(--text-muted);text-transform:uppercase;letter-spacing:.05em}.due-soon-item{display:flex;justify-content:space-between;padding:8px 15px;cursor:pointer;border-left:3px solid transparent;transition:background-color .2s ease}.due-soon-item:hover{background-color:var(--background-modifier-hover);border-left-color:var(--interactive-accent)}.due-soon-date{font-size:.9em}.due-soon-count{font-size:.8em;background-color:var(--background-modifier-border);padding:2px 6px;border-radius:10px;color:var(--text-muted)}.due-soon-empty{text-align:center;padding:15px;color:var(--text-muted);font-style:italic;font-size:.9em}.date-section-header{display:flex;align-items:center;padding:8px 15px;cursor:pointer;border-bottom:1px solid var(--background-modifier-border);background-color:var(--background-secondary-alt)}.date-section-header .section-toggle{margin-right:8px;display:flex;align-items:center;justify-content:center}.date-section-header .section-title{flex:1;font-weight:500}.date-section-header .section-count{font-size:.8em;color:var(--text-muted);background-color:var(--background-modifier-border);border-radius:10px;height:var(--size-4-5);width:var(--size-4-5);display:inline-flex;align-items:center;justify-content:center}.task-date-section.overdue .date-section-header{border-left:3px solid var(--text-error)}.task-date-section.overdue .section-title{color:var(--text-error)}.task-date-section.overdue .section-count{background-color:var(--text-error);color:#fff}.section-tasks{display:flex;flex-direction:column}.forecast-empty-state{display:flex;height:100px;align-items:center;justify-content:center;color:var(--text-muted);font-style:italic}.forecast-sidebar-toggle{position:absolute}.is-phone .forecast-header:has(.forecast-sidebar-toggle) .forecast-title-container{padding-left:var(--size-4-10)}.is-phone .forecast-container{position:relative;overflow:hidden}.is-phone .forecast-left-column{position:absolute;left:0;top:0;height:100%;z-index:10;background-color:var(--background-secondary);width:100%;transform:translate(-100%);transition:transform .3s ease-in-out;border-right:1px solid var(--background-modifier-border)}.is-phone .forecast-left-column.is-visible{transform:translate(0)}.is-phone .forecast-sidebar-toggle{display:flex;align-items:center;justify-content:center;margin-right:8px}.is-phone .forecast-sidebar-close{position:absolute;top:10px;right:10px;z-index:15;width:28px;height:28px;display:flex;align-items:center;justify-content:center;border-radius:50%;background-color:var(--background-primary);box-shadow:0 2px 4px #0000001a}.is-phone .task-genius-container:has(.forecast-left-column.is-visible):before{content:"";position:absolute;top:0;left:0;width:100%;height:100%;background-color:var(--background-modifier-cover);opacity:.5;z-index:5;transition:opacity .3s ease-in-out}.task-genius-view .mini-calendar-container{display:flex;flex-direction:column;width:100%;border-bottom:1px solid var(--background-modifier-border);padding-bottom:10px}.task-genius-view .mini-calendar-container .calendar-header{display:flex;justify-content:space-between;align-items:center;padding:8px 15px;margin-bottom:8px}.task-genius-view .mini-calendar-container .calendar-title{font-weight:600;display:flex;gap:5px}.task-genius-view .mini-calendar-container .calendar-month{margin-right:5px}.task-genius-view .mini-calendar-container .calendar-year{color:var(--text-muted)}.task-genius-view .mini-calendar-container .calendar-nav{display:flex;align-items:center;gap:8px}.task-genius-view .mini-calendar-container .calendar-nav-btn{display:flex;align-items:center;justify-content:center;width:24px;height:24px;border-radius:4px;background-color:var(--background-modifier-hover);cursor:pointer;opacity:.7;transition:opacity .2s ease}.task-genius-view .mini-calendar-container .calendar-nav-btn:hover{opacity:1;background-color:var(--background-modifier-border-hover)}.task-genius-view .mini-calendar-container .calendar-today-btn{padding:2px 8px;border-radius:4px;background-color:var(--background-modifier-hover);cursor:pointer;font-size:.8em;transition:background-color .2s ease}.task-genius-view .mini-calendar-container .calendar-today-btn:hover{background-color:var(--background-modifier-border-hover)}.task-genius-view .mini-calendar-container .calendar-grid{display:grid;grid-template-columns:repeat(7,1fr);gap:1px;padding:0 10px}.task-genius-view .mini-calendar-container .calendar-day-header{text-align:center;font-size:.8em;color:var(--text-muted);padding:3px 0;border-bottom:1px solid var(--background-modifier-border);margin-bottom:3px}.task-genius-view .mini-calendar-container .calendar-day-header.calendar-weekend{color:var(--text-accent)}.task-genius-view .mini-calendar-container.hide-weekends .calendar-grid{grid-template-columns:repeat(5,1fr)}.task-genius-view .mini-calendar-container .calendar-day{border-radius:4px;padding:1px;cursor:pointer;position:relative;display:flex;flex-direction:column;transition:background-color .2s ease;height:auto;min-height:var(--size-4-12)}.task-genius-view .mini-calendar-container .calendar-day:hover{background-color:var(--background-modifier-hover)}.task-genius-view .mini-calendar-container .calendar-day.selected{background-color:var(--background-modifier-border-hover)}.task-genius-view .mini-calendar-container .calendar-day.today{background-color:var(--interactive-accent-hover);color:var(--text-on-accent)}.task-genius-view .mini-calendar-container .calendar-day.past-due{color:var(--text-error)}.task-genius-view .mini-calendar-container .calendar-day.other-month{opacity:.5}.task-genius-view .mini-calendar-container .calendar-day-number{text-align:center;font-size:.9em;font-weight:500;padding:1px}.task-genius-view .mini-calendar-container .calendar-day-count{background-color:var(--background-modifier-border);color:var(--text-normal);border-radius:8px;font-size:.7em;padding:1px 4px;margin:1px auto 0;text-align:center;width:fit-content}.task-genius-view .mini-calendar-container .calendar-day-count.has-priority{background-color:var(--text-accent);color:var(--text-on-accent)}@media (max-width: 1400px){.task-genius-container:has(.task-details.visible) .mini-calendar-container .forecast-left-column{display:none}}.tags-container{display:flex;flex-direction:column;height:100%;width:100%;overflow:hidden;flex:1}.task-genius-view:has(.task-details.visible) .tags-left-column{display:none}.tags-content{display:flex;flex-direction:row;flex:1;overflow:hidden}.multi-select-mode .tags-multi-select-btn{color:var(--color-accent)}.tags-left-column{width:max(120px,30%);min-width:min(120px,30%);max-width:400px;display:flex;flex-direction:column;border-right:1px solid var(--background-modifier-border);overflow:hidden}.tags-right-column{flex:1;display:flex;flex-direction:column;overflow:hidden}.tags-sidebar-header{display:flex;justify-content:space-between;align-items:center;padding:var(--size-4-2) var(--size-4-4);border-bottom:1px solid var(--background-modifier-border);height:var(--size-4-10)}.tags-sidebar-title{font-weight:600;font-size:14px}.tags-multi-select-btn{cursor:pointer;color:var(--text-muted);display:flex;align-items:center;justify-content:center}.tags-multi-select-btn:hover{color:var(--text-normal)}.tags-sidebar-list{flex:1;overflow-y:auto;padding:var(--size-4-2);display:flex;flex-direction:column;gap:var(--size-2-1)}.tag-list-item{display:flex;align-items:center;padding:4px 12px;cursor:pointer;position:relative;border-radius:var(--radius-s)}.tag-list-item:hover{background-color:var(--background-modifier-hover)}.tag-list-item.selected{background-color:var(--background-modifier-active)}.tag-indent{flex-shrink:0}.tag-icon{margin-right:var(--size-2-2);color:var(--text-muted);display:flex;--icon-size: var(--size-4-4)}.tag-name{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tag-count{margin-left:8px;font-size:.8em;color:var(--text-muted);background-color:var(--background-modifier-border);border-radius:10px;padding:1px 6px}.tag-children{width:100%}.tags-task-header{display:flex;justify-content:space-between;align-items:center;padding:var(--size-4-2) var(--size-4-4);border-bottom:1px solid var(--background-modifier-border);height:var(--size-4-10)}.tags-task-title{font-weight:600;font-size:16px}.tags-task-count{color:var(--text-muted)}.tags-task-list{flex:1;overflow-y:auto}.tags-empty-state{display:flex;align-items:center;justify-content:center;height:100%;color:var(--text-muted);font-style:italic;padding:16px}.tag-section-header{display:flex;align-items:center;padding:8px 15px;cursor:pointer;border-bottom:1px solid var(--background-modifier-border);background-color:var(--background-secondary-alt)}.tag-section-header .section-toggle{margin-right:8px;display:flex;align-items:center;justify-content:center}.tag-section-header .section-title{flex:1;font-weight:500}.tag-section-header .section-count{font-size:.8em;color:var(--text-muted);background-color:var(--background-modifier-border);padding:2px 6px;border-radius:10px;height:var(--size-4-5);width:var(--size-4-5)}.is-phone .tags-container{position:relative;overflow:hidden}.is-phone .tags-left-column{position:absolute;left:0;top:0;height:100%;z-index:10;background-color:var(--background-secondary);width:100%;transform:translate(-100%);transition:transform .3s ease-in-out;border-right:1px solid var(--background-modifier-border)}.is-phone .tags-left-column.is-visible{transform:translate(0)}.is-phone .tags-sidebar-toggle{display:flex;align-items:center;justify-content:center;margin-right:8px}.is-phone .tags-sidebar-close{--icon-size: var(--size-4-4);position:absolute;top:var(--size-4-2);right:10px;z-index:15;display:flex;align-items:center;justify-content:center}.is-phone .tags-container:has(.tags-left-column.is-visible):before{content:"";position:absolute;top:0;left:0;width:100%;height:100%;background-color:var(--background-modifier-cover);opacity:.5;z-index:5;transition:opacity .3s ease-in-out}.is-phone .tags-sidebar-header:has(.tags-sidebar-close){padding-right:var(--size-4-12)}.projects-container{display:flex;flex-direction:column;height:100%;width:100%;overflow:hidden}.projects-content{display:flex;flex-direction:row;flex:1;overflow:hidden}.projects-left-column{width:max(120px,30%);min-width:min(120px,30%);max-width:300px;display:flex;flex-direction:column;border-right:1px solid var(--background-modifier-border);overflow:hidden}.is-phone .projects-left-column{max-width:100%}.projects-right-column{flex:1;display:flex;flex-direction:column;overflow:hidden}.projects-sidebar-header{display:flex;justify-content:space-between;align-items:center;padding:var(--size-4-2) var(--size-4-4);border-bottom:1px solid var(--background-modifier-border);height:var(--size-4-10)}.projects-sidebar-title{font-weight:600;font-size:14px}.multi-select-mode .projects-multi-select-btn{color:var(--color-accent)}.projects-multi-select-btn{cursor:pointer;color:var(--text-muted);display:flex;align-items:center;justify-content:center}.projects-multi-select-btn:hover{color:var(--text-normal)}.projects-sidebar-list{flex:1;overflow-y:auto;padding:var(--size-4-2)}.project-list-item{display:flex;align-items:center;padding:4px 12px;cursor:pointer;border-radius:var(--radius-s)}.project-list-item:hover{background-color:var(--background-modifier-hover)}.project-list-item.selected{background-color:var(--background-modifier-active)}.project-icon{margin-right:8px;color:var(--text-muted);display:flex;align-items:center;justify-content:center}.project-name{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.project-count{margin-left:8px;font-size:.8em;color:var(--text-muted);background-color:var(--background-modifier-border);border-radius:10px;padding:1px 6px}.projects-task-header{display:flex;justify-content:space-between;align-items:center;padding:var(--size-4-2) var(--size-4-4);border-bottom:1px solid var(--background-modifier-border);height:var(--size-4-10)}.projects-task-title{font-weight:600;font-size:16px}.projects-task-count{color:var(--text-muted)}.projects-task-list{flex:1;overflow-y:auto}.projects-empty-state{display:flex;align-items:center;justify-content:center;height:100%;color:var(--text-muted);font-style:italic;padding:16px}.is-phone .projects-left-column{position:absolute;left:0;top:0;height:100%;z-index:10;background-color:var(--background-secondary);width:100%;transform:translate(-100%);transition:transform .3s ease-in-out;border-right:1px solid var(--background-modifier-border)}.is-phone .projects-left-column.is-visible{transform:translate(0)}.is-phone .projects-sidebar-toggle{display:flex;align-items:center;justify-content:center;margin-right:8px}.is-phone .projects-sidebar-close{--icon-size: var(--size-4-4);position:absolute;top:var(--size-4-2);right:10px;z-index:15;display:flex;align-items:center;justify-content:center}.is-phone .projects-container:has(.projects-left-column.is-visible):before{content:"";position:absolute;top:0;left:0;width:100%;height:100%;background-color:var(--background-modifier-cover);opacity:.5;z-index:5;transition:opacity .3s ease-in-out}.is-phone .projects-container{position:relative;overflow:hidden}.is-phone .projects-sidebar-header:has(.projects-sidebar-close){padding-right:var(--size-4-12)}.review-container{display:flex;flex-direction:column;height:100%;width:100%;overflow:hidden}.review-content{display:flex;flex-direction:row;flex:1;overflow:hidden}.review-left-column{width:250px;min-width:200px;max-width:300px;display:flex;flex-direction:column;border-right:1px solid var(--background-modifier-border);overflow:hidden}.is-phone .review-left-column{max-width:100%}.review-right-column{flex:1;display:flex;flex-direction:column;overflow:hidden}.review-sidebar-header{display:flex;justify-content:space-between;align-items:center;padding:var(--size-4-2) var(--size-4-4);border-bottom:1px solid var(--background-modifier-border);height:var(--size-4-10)}.review-sidebar-title{font-weight:600;font-size:14px}.review-multi-select-btn{cursor:pointer;color:var(--text-muted);display:flex;align-items:center;justify-content:center}.review-multi-select-btn:hover{color:var(--text-normal)}.review-sidebar-list{flex:1;overflow-y:auto;padding:var(--size-4-2)}.review-projects-group-header{font-size:10px;font-weight:600;color:var(--text-faint);text-transform:uppercase;padding:4px 8px;margin-top:12px;letter-spacing:.5px}.review-projects-group-header:first-child{margin-top:4px}.review-project-item{--icon-size: var(--size-4-4);display:flex;align-items:center;padding:4px 8px;cursor:pointer;border-radius:var(--radius-s);margin-bottom:2px}.review-project-item:hover{background-color:var(--background-modifier-hover)}.review-project-item.selected{background-color:var(--background-modifier-active)}.review-project-item.has-review-settings .review-project-icon{color:var(--text-accent)}.review-project-item.has-review-settings .review-project-name{font-weight:500}.review-project-item:not(.has-review-settings) .review-project-icon{color:var(--text-muted)}.review-project-icon{margin-right:8px;display:flex;align-items:center;justify-content:center}.review-project-name{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.review-task-header{display:flex;flex-direction:column;padding:var(--size-4-4);border-bottom:1px solid var(--background-modifier-border)}.is-phone .review-task-header{flex-direction:row;align-items:flex-start}.review-header-content h3{margin:0 0 8px;padding:0}.review-info{display:flex;align-items:center;color:var(--text-muted);font-size:.9em}.review-separator{margin:0 8px}.review-frequency{color:var(--text-accent)}.review-frequency:hover{color:var(--text-normal);text-decoration:underline}.review-last-date{color:var(--text-normal)}.review-no-settings{font-style:italic}.review-filter-info{margin-top:10px;padding:6px 10px;background-color:var(--background-secondary);border-radius:var(--radius-s);font-size:.85em;color:var(--text-muted);border-left:3px solid var(--text-accent)}.review-filter-toggle{cursor:pointer;text-decoration:underline;color:var(--text-accent);margin-left:5px}.review-filter-toggle:hover{color:var(--text-accent-hover)}.review-task-list{flex:1;overflow-y:auto;padding:var(--size-4-2)}.review-empty-state{display:flex;align-items:center;justify-content:center;height:100%;color:var(--text-muted);font-style:italic;padding:16px;text-align:center}.review-button-container{margin-top:12px;display:flex;justify-content:flex-start}.review-complete-btn,.review-configure-btn{padding:6px 12px;border-radius:var(--radius-s);cursor:pointer;font-size:.9em;border:1px solid var(--background-modifier-border);background-color:var(--background-secondary)}.review-complete-btn{color:var(--text-accent)}.review-complete-btn:hover{background-color:var(--background-modifier-hover);color:var(--text-accent)}.review-configure-btn{color:var(--text-muted)}.review-configure-btn:hover{background-color:var(--background-modifier-hover);color:var(--text-normal)}.review-edit-btn{color:var(--text-accent-hover);margin-left:8px}.review-edit-btn:hover{background-color:var(--background-modifier-hover);color:var(--text-accent-hover)}.review-modal-title{margin-top:0;margin-bottom:20px;font-size:1.5em;color:var(--text-normal);border-bottom:1px solid var(--background-modifier-border);padding-bottom:10px}.review-modal-form{margin-bottom:20px}.review-modal-field{margin-bottom:16px}.review-modal-label{display:block;font-weight:600;margin-bottom:4px;color:var(--text-normal)}.review-modal-description{font-size:.9em;color:var(--text-muted);margin-bottom:8px}.review-modal-select{width:100%;border-radius:var(--radius-s);border:1px solid var(--background-modifier-border);background-color:var(--background-primary);color:var(--text-normal);font-size:14px}.review-modal-custom-frequency{margin-top:8px}.review-modal-input{width:100%;padding:8px;border-radius:var(--radius-s);border:1px solid var(--background-modifier-border);background-color:var(--background-primary);color:var(--text-normal);font-size:14px}.review-modal-last-reviewed{padding:8px;font-size:14px;color:var(--text-normal);background-color:var(--background-secondary);border-radius:var(--radius-s)}.review-modal-buttons{display:flex;justify-content:flex-end;margin-top:24px;border-top:1px solid var(--background-modifier-border);padding-top:16px}.review-modal-button{padding:8px 16px;border-radius:var(--radius-s);font-size:14px;cursor:pointer;border:1px solid var(--background-modifier-border)}.review-modal-button-cancel{background-color:var(--background-secondary);color:var(--text-muted);margin-right:8px}.review-modal-button-cancel:hover{background-color:var(--background-modifier-hover);color:var(--text-normal)}.review-modal-button-save{background-color:var(--interactive-accent);color:var(--text-on-accent)}.review-modal-button-save:hover{background-color:var(--interactive-accent-hover)}.is-phone .review-container{position:relative;overflow:hidden}.is-phone .review-left-column{position:absolute;left:0;top:0;height:100%;z-index:10;background-color:var(--background-secondary);width:100%;transform:translate(-100%);transition:transform .3s ease-in-out;border-right:1px solid var(--background-modifier-border)}.is-phone .review-left-column.is-visible{transform:translate(0)}.is-phone .review-sidebar-toggle{display:flex;align-items:center;justify-content:center;margin-right:8px}.is-phone .review-sidebar-close{position:absolute;top:var(--size-2-2);right:10px;z-index:15;display:flex;align-items:center;justify-content:center}.is-phone .review-container:has(.review-left-column.is-visible):before{content:"";position:absolute;top:0;left:0;width:100%;height:100%;background-color:var(--background-modifier-cover);opacity:.5;z-index:5;transition:opacity .3s ease-in-out}.task-sidebar.collapsed{width:48px;overflow:hidden}.panel-toggle-btn{display:flex;align-items:center;justify-content:center;border-radius:4px;cursor:pointer;opacity:.6;transition:opacity .2s ease}.panel-toggle-btn:hover{opacity:1}.task-sidebar.collapsed .sidebar-nav{align-items:center}.sidebar-nav{display:flex;flex-direction:column;padding:20px 0 10px;gap:5px}.sidebar-nav-spacer{height:1px;background-color:var(--background-modifier-border);margin:auto 15px 8px;opacity:.7}.sidebar-nav-item{display:flex;align-items:center;padding:8px 15px;cursor:pointer;border-radius:4px;margin:0 5px;transition:background-color .2s ease}.sidebar-nav-item:hover{background-color:var(--background-modifier-active)}.sidebar-nav-item.is-active{font-weight:600;--background-modifier-hover: var(--interactive-accent);--icon-color: var(--text-on-accent);background-color:var(--interactive-accent);color:var(--text-on-accent)}.nav-item-icon{--icon-size: var(--size-4-4);display:flex;align-items:center;justify-content:center;margin-right:var(--size-4-2)}.nav-item-label{flex:1;font-size:var(--font-ui-medium);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.nav-item-label.hidden{opacity:0;width:0;overflow:hidden;margin:0}.task-sidebar.collapsed .sidebar-nav-item{padding:8px 10px;justify-content:center;width:var(--size-4-9);flex-shrink:0;transition:width .3s ease-in-out,flex-shrink .3s ease-in-out}.task-sidebar.collapsed .nav-item-icon{margin-right:0}.task-content{flex:1;display:flex;flex-direction:column;overflow:hidden;min-width:0;transition:margin .3s ease}.task-sidebar.collapsed .task-content{margin-left:-200px;transition:margin .3s ease}.task-genius-view .project-tree{padding:10px 0;transition:opacity .3s ease}.task-genius-view .tree-root{display:flex;flex-direction:column}.task-genius-view .task-genius-view .tree-item{display:flex;align-items:center;padding:6px 8px;cursor:pointer;transition:background-color .2s ease;border-radius:4px;margin:0 5px}.task-genius-view .tree-item.selected{background-color:var(--background-modifier-border-hover)}.task-genius-view .tree-item-icon{display:flex;align-items:center;justify-content:center;width:20px;height:20px;margin-right:8px;color:var(--text-muted)}.task-genius-view .tree-item-name{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.task-genius-view .tree-item-count{font-size:.8em;color:var(--text-muted);margin-left:5px;background-color:var(--background-modifier-hover);padding:1px 6px;border-radius:10px}.task-genius-view .tree-item-toggle,.task-genius-view .tree-item-indent{width:20px;display:flex;align-items:center;justify-content:center;margin-right:5px}.task-genius-view .tree-item-toggle{cursor:pointer}.content-header{padding:15px;border-bottom:1px solid var(--background-modifier-border);display:flex;align-items:center;flex-shrink:0}.task-count{font-size:.8em;color:var(--text-muted);margin-right:10px}.focus-filter{margin-left:10px}.workspace-leaf-content .task-genius-view{padding:0}.task-genius-container{display:flex;flex-direction:row;height:100%;width:100%;background-color:var(--background-primary);border-top:1px solid var(--background-modifier-border);color:var(--text-normal);position:relative;overflow:hidden}.task-sidebar{display:flex;flex-direction:column;border-right:1px solid var(--background-modifier-border);background-color:var(--background-secondary);overflow-y:auto;width:240px;transition:width .3s ease-in-out;position:relative}.task-content{display:flex;flex-direction:column;flex:1;min-width:300px;height:100%;overflow:hidden}.task-sidebar .sidebar-nav{display:flex;flex-direction:column;padding:8px 0;height:100%}.project-tree{display:flex;flex-direction:column;padding:8px 0;overflow-y:auto}.tree-root{display:flex;flex-direction:column}.task-genius-view .tree-item{display:flex;align-items:center;padding:4px 12px;cursor:pointer;border-radius:4px;margin:2px 8px}.task-genius-view .tree-item:hover{background-color:var(--background-modifier-border-hover)}.task-genius-view .tree-item.selected{background-color:var(--background-modifier-border-hover);color:var(--text-accent)}.task-genius-view .tree-item-toggle{width:16px;height:16px;display:flex;align-items:center;justify-content:center;margin-right:4px}.task-genius-view .tree-item-indent{width:16px;height:16px;margin-right:4px}.task-genius-view .tree-item-icon{margin-right:8px;width:16px;height:16px;display:flex;align-items:center;justify-content:center;color:var(--text-muted)}.task-genius-view .tree-item-name{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.task-genius-view .tree-item-count{font-size:.8em;color:var(--text-muted);background-color:var(--background-modifier-hover);border-radius:10px;padding:2px 6px;min-width:16px;text-align:center}.task-genius-view .tree-item.expanded>.tree-item-children{display:flex}.task-genius-view .tree-item-children{display:none;flex-direction:column;margin-left:16px;width:100%}.task-genius-view .content-header{display:flex;align-items:center;padding:10px 16px;border-bottom:1px solid var(--background-modifier-border);min-height:50px}.task-genius-view .content-title{font-size:1.2em;font-weight:600;margin-right:12px;flex:1}@media screen and (max-width: 768px){.task-genius-view .content-title{display:none}.task-genius-view .task-count{flex:1}.task-genius-view .focus-filter{flex:1}}.task-genius-view .content-filter{display:flex;align-items:center;margin-right:12px}.task-genius-view .filter-input{border:1px solid var(--background-modifier-border);border-radius:4px;padding:4px 8px;width:200px;background-color:var(--background-primary)}.task-genius-view .focus-button{background-color:var(--interactive-normal);border:1px solid var(--background-modifier-border);border-radius:4px;padding:4px 10px;color:var(--text-normal);cursor:pointer}.task-genius-view .focus-button:hover{background-color:var(--interactive-hover)}.task-genius-view .focus-button.focused{background-color:var(--interactive-accent);color:var(--text-on-accent)}.mod-root .task-genius-action-btn{--icon-size: 16px}.mod-left-split .task-genius-action-btn{display:none}.mod-left-split .workspace-tab-header-status-container:has(.task-genius-action-btn){display:none}.mod-right-split .workspace-tab-header-status-container:has(.task-genius-action-btn){display:none}.task-genius-view .task-empty-state{width:100%;height:100%;flex:1;display:flex;align-items:center;justify-content:center}.mod-root .task-genius-tab-header{container-type:inline-size!important}@container (max-width: 120px){.mod-root .task-genius-action-btn {display: none;}}.quick-workflow-modal{max-width:600px;min-height:400px}.workflow-template-section{margin-bottom:20px;padding:15px;border:1px solid var(--background-modifier-border);border-radius:8px}.template-description{margin-top:10px}.template-desc-text{font-style:italic;color:var(--text-muted);margin:0}.workflow-form-section{margin-bottom:20px}.workflow-stages-preview{margin-top:15px}.stages-preview-list{margin-top:10px}.stage-preview-item{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;margin:4px 0;background:var(--background-secondary);border-radius:6px;border:1px solid var(--background-modifier-border)}.stage-info{display:flex;align-items:center;gap:8px}.stage-name{font-weight:500}.stage-type{color:var(--text-muted);font-size:.9em}.stage-actions{display:flex;gap:4px}.no-stages-message{text-align:center;color:var(--text-muted);font-style:italic;padding:20px;border:2px dashed var(--background-modifier-border);border-radius:8px;margin-top:10px}.workflow-modal-buttons{display:flex;justify-content:flex-end;gap:10px;margin-top:20px;padding-top:15px;border-top:1px solid var(--background-modifier-border)}.workflow-progress-indicator{background:var(--background-secondary);border:1px solid var(--background-modifier-border);border-radius:8px;padding:15px;margin:10px 0}.workflow-progress-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:15px}.workflow-name{font-weight:600;font-size:1.1em}.workflow-progress-text{color:var(--text-muted);font-size:.9em}.workflow-progress-bar-container{display:flex;align-items:center;gap:10px;margin-bottom:15px}.workflow-progress-bar{flex:1;height:8px;background:var(--background-modifier-border);border-radius:4px;overflow:hidden}.workflow-progress-fill{height:100%;background:var(--interactive-accent);transition:width .3s ease}.workflow-progress-percentage{font-size:.9em;font-weight:500;min-width:35px;text-align:right}.workflow-stage-list{display:flex;flex-direction:column;gap:8px}.workflow-stage-item{display:flex;align-items:flex-start;gap:12px;padding:10px;border-radius:6px;transition:background-color .2s ease}.workflow-stage-item.completed{background:var(--background-modifier-success)}.workflow-stage-item.current{background:var(--background-modifier-accent);border:1px solid var(--interactive-accent)}.workflow-stage-item.pending{background:var(--background-primary);opacity:.7}.workflow-stage-icon{width:20px;height:20px;display:flex;align-items:center;justify-content:center;margin-top:2px}.workflow-stage-icon.completed-icon{color:var(--text-success)}.workflow-stage-icon.current-icon{color:var(--interactive-accent)}.workflow-stage-icon.pending-icon{color:var(--text-muted)}.workflow-stage-content{flex:1}.workflow-stage-name{font-weight:500;margin-bottom:2px}.workflow-stage-type{font-size:.8em;color:var(--text-muted)}.workflow-stage-number{width:24px;height:24px;border-radius:50%;background:var(--background-modifier-border);display:flex;align-items:center;justify-content:center;font-size:.8em;font-weight:600;margin-top:2px}.workflow-stage-item.completed .workflow-stage-number{background:var(--text-success);color:var(--background-primary)}.workflow-stage-item.current .workflow-stage-number{background:var(--interactive-accent);color:var(--text-on-accent)}.workflow-substage-container{margin-top:8px;padding-left:16px;border-left:2px solid var(--background-modifier-border)}.workflow-substage-item{display:flex;align-items:center;gap:8px;padding:4px 0}.workflow-substage-icon{width:12px;height:12px;color:var(--text-muted)}.workflow-substage-name{font-size:.9em;color:var(--text-muted)}.full-calendar-container{container-type:inline-size;display:flex;flex-direction:column;height:100%;overflow:hidden;flex-grow:1}.full-calendar-container .calendar-header{display:flex;justify-content:space-between;align-items:center;padding:var(--size-2-3) var(--size-4-4);border-bottom:1px solid var(--background-modifier-border);flex-shrink:0;margin-bottom:0}.full-calendar-container .calendar-header button{margin:0 var(--size-4-1)}.full-calendar-container .calendar-nav,.full-calendar-container .calendar-view-switcher{display:flex;gap:var(--size-2-2)}.full-calendar-container .calendar-nav button{border-radius:var(--radius-s);text-transform:uppercase}.full-calendar-container .calendar-view-switcher button{border-radius:var(--radius-s);text-transform:uppercase}.full-calendar-container .calendar-view-switcher button:not(.is-active),.full-calendar-container .calendar-nav button:not(.is-active){box-shadow:var(--shadow-xs);border:1px solid var(--background-modifier-border)}.full-calendar-container .calendar-current-date{font-weight:var(--font-semibold);font-size:var(--font-ui-large);text-align:center;flex-grow:1;max-width:max(120px,40%);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.full-calendar-container .calendar-view-switcher button.is-active{background-color:var(--interactive-accent);color:var(--text-on-accent);border-color:var(--interactive-accent-hover)}.full-calendar-container .calendar-view-container{flex-grow:1;overflow-y:auto;padding:var(--size-4-2);position:relative;display:flex;flex-direction:column}.full-calendar-container .calendar-weekday-header{display:grid;grid-template-columns:repeat(7,1fr);text-align:center;font-size:var(--font-ui-small);color:var(--text-muted);padding:var(--size-4-1) 0;border-bottom:1px solid var(--background-modifier-border);margin-bottom:-1px;background-color:var(--background-secondary)}.full-calendar-container .calendar-weekday{padding:var(--size-4-1)}.full-calendar-container .calendar-view-container.view-month{padding:0}.full-calendar-container .calendar-month-grid{display:grid;grid-template-columns:repeat(7,1fr);grid-auto-rows:minmax(100px,auto);gap:1px;background-color:var(--background-modifier-border);height:100%}.full-calendar-container .calendar-day-cell{background-color:var(--background-primary);padding:var(--size-4-1);position:relative;display:flex;flex-direction:column;min-width:0}.full-calendar-container .calendar-day-cell:hover{background-color:hsl(var(--color-accent-h),var(--color-accent-s),var(--color-accent-l),.8)!important}.full-calendar-container .calendar-day-cell.is-today{background-color:var(--background-secondary-alt)!important;border:1px solid hsl(var(--accent-h),var(--accent-s),var(--accent-l),.5)}.full-calendar-container .calendar-day-cell.is-today .calendar-day-number{color:hsl(var(--accent-h),var(--accent-s),var(--accent-l),1)}.full-calendar-container .calendar-day-header{width:100%;display:flex;flex-direction:row-reverse;justify-content:space-between;align-items:center;gap:var(--size-4-1)}.full-calendar-container .calendar-day-cell:not(.is-today){opacity:.7}.full-calendar-container .calendar-day-cell.is-other-month{background-color:var(--background-secondary);opacity:.7}.full-calendar-container .calendar-day-cell.is-weekend{background-color:var( --background-secondary )}.full-calendar-container .calendar-day-number{font-size:var(--font-ui-small);text-align:center;margin-bottom:var(--size-4-1);flex-shrink:0;align-self:flex-end}.full-calendar-container .calendar-events-container{flex-grow:1;overflow:hidden;position:relative}.full-calendar-container .calendar-event{background-color:var(--interactive-accent);color:var(--text-on-accent);border-radius:var(--radius-s);padding:2px 4px;font-size:var(--font-ui-smaller);margin-bottom:3px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;cursor:pointer;display:block}.full-calendar-container .calendar-event:has(.task-list-item-checkbox){display:flex;flex-direction:row;align-items:center}.full-calendar-container .calendar-event:has(.task-list-item-checkbox).calendar-event-week-allday{display:flex}.full-calendar-container .calendar-event:has(.task-list-item-checkbox).calendar-event-month{display:flex}.full-calendar-container .full-calendar-container .calendar-event:hover{opacity:.8}.full-calendar-container .calendar-event.is-completed{background-color:var( --background-modifier-success-hover );text-decoration:line-through;opacity:.7}.full-calendar-container .calendar-event.calendar-event-month{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:block;width:100%;box-sizing:border-box}.full-calendar-container .calendar-view-container.view-day{display:flex;flex-direction:column;padding:0}.full-calendar-container .calendar-timeline-section{flex-grow:1;border-top:1px solid var(--background-modifier-border);overflow-y:auto;padding:var(--size-4-4)}.full-calendar-container .calendar-timeline-events-container{display:flex;flex-direction:column;gap:var(--size-4-2)}.full-calendar-container .calendar-event-timed{border:1px solid var(--background-modifier-border);overflow:hidden;display:flex;flex-direction:column;width:100%}.full-calendar-container .calendar-event-time{font-size:var(--font-ui-smaller);font-weight:bold;padding:1px 3px;background-color:#0000001a}.full-calendar-container .calendar-event-title{font-size:var(--font-ui-small);padding:2px 3px;flex-grow:1;white-space:normal;word-wrap:break-word;display:flex;align-items:center}.full-calendar-container .calendar-view-container.view-week{display:flex;flex-direction:column;padding:0}.full-calendar-container .calendar-week-header{display:grid;grid-template-columns:repeat(7,1fr);border-bottom:1px solid var(--background-modifier-border);flex-shrink:0;text-align:center;background-color:var(--background-secondary);font-size:var(--font-ui-medium)}.full-calendar-container .calendar-header-cell{padding:var(--size-4-1) 0;border-left:1px solid var(--background-modifier-border-hover)}.full-calendar-container .calendar-header-cell:first-child{border-left:none}.full-calendar-container .calendar-header-cell.is-today .calendar-day-number{background-color:var(--interactive-accent);color:var(--text-on-accent);border-radius:50%;display:inline-block;width:1.5em;height:1.5em;line-height:1.5em;margin:auto;display:flex;align-items:center;justify-content:center}.full-calendar-container .calendar-weekday{font-size:var(--font-ui-small);color:var(--text-muted)}.full-calendar-container .calendar-day-number{font-size:var(--font-ui-medium)}.full-calendar-container .calendar-week-grid-section{flex-grow:1;display:flex;flex-direction:column;overflow-y:auto;border-bottom:1px solid var(--background-modifier-border)}.full-calendar-container .calendar-week-grid{flex-grow:1;display:grid;grid-template-columns:repeat(7,1fr);grid-template-rows:1fr;gap:1px;background-color:var(--background-modifier-border);border-top:1px solid var(--background-modifier-border)}.full-calendar-container .calendar-day-column{background-color:var(--background-primary);padding:var(--size-4-1);border-left:none;display:flex;flex-direction:column;gap:var(--size-4-1);overflow:hidden;min-width:0}.full-calendar-container .calendar-day-column.is-weekend{background-color:var(--background-secondary)}.full-calendar-container .calendar-view-container.hide-weekends .calendar-weekday-header{grid-template-columns:repeat(5,1fr)!important}.full-calendar-container .calendar-view-container.hide-weekends .calendar-month-grid{grid-template-columns:repeat(5,1fr)!important}.full-calendar-container .calendar-view-container.hide-weekends .calendar-week-header{grid-template-columns:repeat(5,1fr)!important}.full-calendar-container .calendar-view-container.hide-weekends .calendar-week-grid{grid-template-columns:repeat(5,1fr)!important}.full-calendar-container .calendar-view-container.hide-weekends .mini-month-grid{grid-template-columns:repeat(5,1fr)!important}.full-calendar-container .calendar-day-events-container{flex-grow:1;display:flex;flex-direction:column;gap:3px}.full-calendar-container .calendar-event.calendar-event-week-allday{display:block;width:100%;position:relative;left:auto;top:auto;height:auto;margin-bottom:3px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.full-calendar-container .calendar-view-container.view-year{padding:var(--size-4-4)}.full-calendar-container .calendar-year-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:var(--size-4-4)}.full-calendar-container .calendar-mini-month{border:1px solid var(--background-modifier-border);border-radius:var(--radius-m);background-color:var(--background-secondary);overflow:hidden}.full-calendar-container .mini-month-header{text-align:center;font-weight:var(--font-semibold);padding:var(--size-4-2);background-color:var(--background-secondary-alt);border-bottom:1px solid var(--background-modifier-border)}.full-calendar-container .mini-month-body{padding:var(--size-4-2)}.full-calendar-container .mini-month-grid{display:grid;grid-template-columns:repeat(7,1fr);gap:2px;text-align:center}.full-calendar-container .mini-weekday-header{display:contents;font-size:var(--font-ui-smaller);color:var(--text-faint);font-weight:bold}.full-calendar-container .mini-weekday{padding-bottom:var(--size-4-1)}.full-calendar-container .mini-day-cell{font-size:var(--font-ui-small);padding:1px;border-radius:var(--radius-s);line-height:1.5em}.full-calendar-container .mini-day-cell.is-other-month{color:var(--text-faint);opacity:.6}.full-calendar-container .mini-day-cell.is-today{font-weight:bold;background-color:var(--interactive-accent-hover);color:var(--text-on-accent)}.full-calendar-container .mini-day-cell.has-events{font-weight:bold}.agenda-day-section{display:flex;width:100%;border:1px solid var(--background-modifier-border);padding-top:var(--size-4-2);padding-bottom:var(--size-4-2);padding-left:var(--size-4-2);padding-right:var(--size-4-2)}.agenda-day-date-column{width:20%;display:flex;flex-direction:column;justify-content:flex-start;align-items:center}.agenda-day-events-column{flex:1}.full-calendar-container input.task-list-item-checkbox{scale:.9}.full-calendar-container .calendar-view-switcher-selector{display:none}.calendar-event-ghost{background-color:var(--background-secondary-alt)!important;border:2px dashed var(--background-modifier-border)!important;opacity:.5!important;box-shadow:none!important}.calendar-event-dragging{opacity:.9!important;box-shadow:var(--shadow-l)!important;transform:rotate(2deg)!important;z-index:1000!important}.calendar-events-container .calendar-event{cursor:grab;transition:transform .2s ease,box-shadow .2s ease}.calendar-events-container .calendar-event:hover{transform:translateY(-1px);box-shadow:var(--shadow-s)}.calendar-events-container .calendar-event:active{cursor:grabbing}.calendar-events-container,.calendar-day-events-container{min-height:20px;border-radius:var(--radius-s);transition:background-color .2s ease}@container (max-width: 600px){.full-calendar-container .calendar-view-switcher button {display: none;} .calendar-nav .prev-button {display: none;} .calendar-nav .next-button {display: none;} .full-calendar-container .calendar-view-switcher-selector {display: block;}}.full-calendar-container .calendar-event-title-container p{padding-inline-start:0;padding-inline-end:0;margin-block-start:0;margin-block-end:0}.full-calendar-container .calendar-event-title-container{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:100%}.full-calendar-container .calendar-event-title p{margin-block-start:0;margin-block-end:0}.calendar-badges-container{display:flex;flex-direction:row;gap:4px;pointer-events:none;z-index:10}.calendar-badge{color:var(--text-muted);display:flex;font-size:10px;padding:var(--size-2-1);border-radius:var(--radius-s);max-width:80px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.calendar-day-cell{position:relative}.tg-kanban-view{display:flex;flex-direction:column;height:100%;width:100%;overflow:hidden}.tg-kanban-filters{border-bottom:1px solid var(--background-modifier-border);flex-shrink:0;display:flex;flex-direction:row-reverse;gap:8px;padding:0 8px}.tg-kanban-controls-container{display:flex;align-items:center;gap:12px;flex-wrap:wrap}.tg-kanban-sort-container{display:flex;align-items:center;gap:4px}.tg-kanban-sort-button{padding:4px 8px;border:1px solid var(--background-modifier-border);border-radius:var(--radius-s);background-color:var(--background-primary);color:var(--text-normal);cursor:pointer;display:flex;align-items:center;gap:4px;font-size:var(--font-ui-small)}.tg-kanban-sort-button:hover{background-color:var(--background-modifier-hover);border-color:var(--background-modifier-border-hover)}.tg-kanban-toggle-container{display:flex;align-items:center;gap:4px}.tg-kanban-toggle-label{display:flex;align-items:center;gap:6px;font-size:var(--font-ui-small);color:var(--text-normal);cursor:pointer}.tg-kanban-toggle-checkbox{margin:0}.tg-kanban-filter-input{flex-grow:1;padding:6px 10px;font-size:var(--font-ui-small);border:1px solid var(--background-modifier-border);border-radius:var(--radius-s);background-color:var(--background-primary);margin-right:10px}.tg-kanban-filter-input:focus{outline:none;border-color:var(--interactive-accent);box-shadow:0 0 0 1px var(--interactive-accent)}.tg-kanban-column-container{display:flex;flex-grow:1;overflow-x:auto;overflow-y:hidden;padding:10px;gap:10px;height:100%;-webkit-overflow-scrolling:touch;overscroll-behavior-x:auto;scroll-snap-type:x proximity;scroll-behavior:smooth}@media (hover: hover) and (pointer: fine){.tg-kanban-column-container{overscroll-behavior-x:none;scroll-snap-type:none}}.tg-kanban-column{flex:0 0 280px;display:flex;flex-direction:column;background-color:var(--background-secondary);border-radius:var(--radius-m);height:100%;max-height:100%;overflow:hidden;border:1px solid var(--background-modifier-border);scroll-snap-align:start}@media (hover: hover) and (pointer: fine){.tg-kanban-column{scroll-snap-align:none}}.tg-kanban-column-header{padding:8px 12px;font-size:var(--font-ui-mediumn);font-weight:600;border-bottom:1px solid var(--background-modifier-border);flex-shrink:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;text-transform:uppercase;display:flex;align-items:center}.tg-kanban-column-content{flex-grow:1;overflow-y:auto;padding:8px;display:flex;flex-direction:column;gap:8px;background-color:var(--background-secondary-alt);-webkit-overflow-scrolling:touch;overscroll-behavior:contain;scroll-behavior:smooth}@media (hover: hover) and (pointer: fine){.tg-kanban-column-content{overscroll-behavior:none}}.tg-kanban-card{background-color:var(--background-primary);border-radius:var(--radius-s);padding:10px 12px;border:1px solid var(--background-modifier-border);font-size:var(--font-ui-small);cursor:grab;transition:box-shadow .2s ease-in-out,background-color .2s ease-in-out;max-width:100%;box-sizing:border-box;white-space:nowrap;text-overflow:ellipsis;touch-action:manipulation;user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none}.tg-kanban-card .tg-kanban-card-content{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:100%}.tg-kanban-card:hover{border-color:var(--background-modifier-border-hover);box-shadow:var(--shadow-m)}.tg-kanban-card.task-completed{background-color:var(--background-secondary);opacity:.7}.tg-kanban-card.task-completed .tg-kanban-card-content{text-decoration:line-through;color:var(--text-muted)}.tg-kanban-card-container{display:flex;align-items:flex-start;margin-bottom:6px}.tg-kanban-card-content p:last-child{margin-bottom:0;margin-block-end:0;margin-block-start:0}.tg-kanban-card-metadata{display:flex;flex-wrap:wrap;gap:4px 8px;font-size:var(--font-ui-small);color:var(--text-muted)}.tg-kanban-card-metadata .task-date,.tg-kanban-card-metadata .task-tags-container,.tg-kanban-card-metadata .task-priority{display:flex;align-items:center;gap:4px;padding:2px 5px;background-color:var(--background-secondary);border-radius:var(--radius-s);margin-inline-start:0;margin-inline-end:0;margin-left:0;margin-right:0}.tg-kanban-card-metadata .task-tag{background-color:var( --background-modifier-accent-hover );color:var(--text-accent);padding:1px 4px;border-radius:var(--radius-s);font-size:calc(var(--font-ui-small) * .9)}.tg-kanban-card-metadata .task-due-date.task-overdue{color:var(--text-error);background-color:var(--background-error)}.tg-kanban-card-metadata .task-due-date.task-due-today{color:var(--text-warning);background-color:var(--background-warning)}.tg-kanban-card-metadata .task-priority.priority-1{color:var(--text-accent)}.tg-kanban-card-metadata .task-priority.priority-2{color:var(--text-warning)}.tg-kanban-card-metadata .task-priority.priority-3{color:var(--text-error);font-weight:bold}.tg-kanban-card-dragging{box-shadow:var(--shadow-l)}.tg-kanban-card-ghost{background-color:var(--background-secondary-alt);border:1px dashed var(--background-modifier-border);box-shadow:none}.tg-kanban-column-content.tg-kanban-drop-target-active{outline:2px dashed var(--background-modifier-accent-hover);outline-offset:-2px}.tg-kanban-column-content.tg-kanban-drop-target-hover{background-color:var(--background-modifier-accent-hover)}.tg-kanban-card--drop-indicator-before{margin-top:10px;border-top:2px dashed var(--interactive-accent);transition:margin-top .1s ease-out,border-top .1s ease-out}.tg-kanban-card--drop-indicator-after{margin-bottom:10px;border-bottom:2px dashed var(--interactive-accent);transition:margin-bottom .1s ease-out,border-bottom .1s ease-out}.tg-kanban-column-content--drop-indicator-empty{border:2px dashed var(--interactive-accent);min-height:50px;box-sizing:border-box;margin-top:5px;margin-bottom:5px}.tg-kanban-card{transition:margin .1s ease-out,padding .1s ease-out,border .1s ease-out,transform .2s ease-out,box-shadow .2s ease-in-out,background-color .2s ease-in-out}.drop-target-active{background-color:#00800033;outline:2px dashed green}.tg-kanban-add-card-container{padding:8px;border-top:1px solid var(--background-modifier-border);flex-shrink:0}.task-genius-add-card-container{padding:8px;margin-top:auto;text-align:center}.tg-kanban-add-card-button{--icon-size: 16px;width:100%;padding:6px 12px;border:none;background-color:transparent;color:var(--text-muted);border-radius:var(--radius-s);cursor:pointer;font-size:var(--font-ui-small);text-align:left;transition:background-color .2s ease-in-out,color .2s ease-in-out}.tg-kanban-add-card-button:hover{background-color:var(--background-modifier-hover);color:var(--text-normal)}.tg-kanban-column-dragging{transform:rotate(5deg);opacity:.8;box-shadow:var(--shadow-xl);z-index:1000}.tg-kanban-column-ghost{background-color:var(--background-modifier-border);border:2px dashed var(--background-modifier-accent);opacity:.5}.tg-kanban-column-header{cursor:grab}.tg-kanban-column-header:active{cursor:grabbing}.filter-component{display:flex;flex-wrap:wrap;align-items:center;gap:var(--size-4-2);padding:var(--size-4-2) var(--size-4-3);background-color:var(--background-primary);min-height:48px;flex:1}.filter-pills-container{display:flex;flex-wrap:wrap;gap:var(--size-4-2);flex:1}.filter-controls{display:flex;align-items:center;gap:var(--size-4-2);margin-left:auto}.filter-pill{display:flex;align-items:center;gap:var(--size-4-1);padding:5px 8px;border:1px solid var(--background-modifier-border);border-radius:var(--radius-m);font-size:var(--font-ui-small);animation:filter-pill-appear .2s ease-out;transition:background-color var(--duration-fast),transform var(--duration-fast)}.filter-pill-remove .clickable-icon:hover{background-color:unset}.filter-pill:hover{background-color:var(--background-tertiary)}.filter-pill-category{font-weight:500;color:var(--text-muted)}.filter-pill-value{color:var(--text-normal)}.filter-pill-remove{display:flex;align-items:center;justify-content:center;width:16px;height:16px;border-radius:50%;background:transparent;border:none;padding:0;margin-left:var(--size-4-1);cursor:pointer;color:var(--text-faint);font-size:14px;line-height:1;transition:background-color var(--duration-fast),color var(--duration-fast)}.filter-pill-remove:hover{background-color:var(--background-modifier-hover);color:var(--text-normal)}.filter-pill-remove-icon{font-size:16px;display:flex;align-items:center;justify-content:center}.filter-add-button,.filter-clear-all-button{display:flex;align-items:center;padding:6px 10px;font-size:var(--font-ui-small);cursor:pointer}.filter-add-button{gap:var(--size-4-1);color:var(--text-muted)}.filter-add-icon{font-weight:var(--font-bold);display:flex;align-items:center;justify-content:center}.filter-dropdown{position:fixed;width:220px;background-color:var(--background-primary);border-radius:var(--radius-m);box-shadow:var(--shadow-l);border:1px solid var(--background-modifier-border);z-index:var(--layer-popover);max-height:400px;display:flex;flex-direction:column;opacity:0;transform:translateY(-8px);transition:opacity var(--duration-normal),transform var(--duration-normal);overflow:hidden}.filter-dropdown-visible{opacity:1;transform:translateY(0)}.filter-dropdown-header{padding:var(--size-4-2);border-bottom:1px solid var(--background-modifier-border)}.filter-dropdown-search{width:100%;padding:var(--size-4-2);border:1px solid var(--background-modifier-border);border-radius:var(--radius-m);background-color:var(--background-secondary);font-size:var(--font-ui-small);outline:none}.filter-dropdown-search:focus{border-color:var(--interactive-accent);box-shadow:0 0 0 2px var(--focus-ring-color)}.filter-dropdown-list{overflow-y:auto;max-height:350px}.filter-dropdown-item{display:flex;align-items:center;padding:var(--size-4-2) var(--size-4-3);cursor:pointer;font-size:var(--font-ui-small);color:var(--text-normal);transition:background-color var(--duration-fast)}.filter-dropdown-item:hover{background-color:var(--background-secondary)}.filter-dropdown-item-label{flex:1}.filter-dropdown-item-arrow{color:var(--text-faint);font-size:18px}.filter-dropdown-item-arrow.back{margin-right:var(--size-4-2);display:flex;align-items:center;justify-content:center}.filter-dropdown-back{color:var(--text-muted)}.filter-dropdown-separator{height:1px;background-color:var(--divider-color);margin:var(--size-4-1) 0}.filter-dropdown-empty{padding:var(--size-4-4);text-align:center;color:var(--text-faint);font-size:var(--font-ui-small)}.filter-dropdown-value-item{padding-left:var(--size-4-4)}.filter-dropdown-category{padding:var(--size-4-2) 0;color:var(--text-muted);font-weight:500}.filter-dropdown-value-preview{padding:var(--size-4-1) var(--size-4-4);cursor:pointer;transition:background-color var(--duration-fast);font-size:var(--font-ui-small);color:var(--text-normal)}.filter-dropdown-value-preview:hover{background-color:var(--background-secondary)}@keyframes filter-pill-appear{0%{opacity:0;transform:scale(.9)}to{opacity:1;transform:scale(1)}}.filter-pill-removing{opacity:0;transform:scale(.9);transition:opacity .15s ease-out,transform .15s ease-out}.gantt-chart-container{width:100%;height:100%;overflow:auto;position:relative;background-color:var(--background-secondary);--gantt-header-height: 50px;--gantt-row-height: 40px;--gantt-bar-height: 20px;--gantt-bar-radius: 3px;--gantt-bg-color: var(--background-secondary);--gantt-grid-color: var(--background-modifier-border);--gantt-row-color: var(--background-secondary);--gantt-bar-color: var(--color-blue);--gantt-milestone-color: var(--color-purple);--gantt-progress-color: var(--color-blue);--gantt-today-color: var(--color-accent)}.gantt-svg{display:block;font-family:var(--font-interface);font-size:var(--font-ui-small);user-select:none}.gantt-header-bg{fill:var(--background-primary);stroke:var(--background-modifier-border);stroke-width:1px}.gantt-header-text{fill:var(--text-muted);font-weight:500}.gantt-grid-bg{fill:transparent;stroke:var(--background-modifier-border);stroke-width:0}.gantt-grid-line-vertical{stroke:var(--background-modifier-border);stroke-width:1px;stroke-dasharray:2,2}.gantt-task-item{cursor:pointer}.gantt-task-bar{fill:var(--interactive-accent);stroke:var(--interactive-accent-hover);stroke-width:1px;transition:fill .1s ease-in-out}.gantt-task-item:hover .gantt-task-bar{fill:var(--interactive-accent-hover)}.gantt-task-milestone{fill:var(--color-orange);stroke:var(--color-orange-border);stroke-width:1px}.gantt-task-label{fill:var(--text-on-accent);font-size:calc(var(--font-ui-small) * .9);pointer-events:none;white-space:pre}.gantt-task-bar.status-completed{fill:var(--color-green);stroke:var(--color-green-border)}.gantt-header{position:sticky;top:0;left:0;right:0;z-index:10;height:var(--gantt-header-height);border-bottom:1px solid var(--gantt-grid-color);user-select:none;background-color:var(--gantt-bg-color);pointer-events:none;width:100%;overflow:hidden}.gantt-header-row{position:relative;height:50%;width:100%}.gantt-header-row.primary{border-bottom:1px solid var(--gantt-grid-color);font-weight:600}.gantt-header-cell{position:absolute;height:100%;display:flex;align-items:center;justify-content:center;text-align:center;font-size:12px;color:var(--text-normal);border-right:1px solid var(--gantt-grid-color);box-sizing:border-box;background-color:var(--gantt-bg-color);pointer-events:auto}.gantt-body{position:relative;overflow:auto;height:100%;padding-top:var(--gantt-header-height);margin-top:calc(var(--gantt-header-height) * -1)}.gantt-grid{position:absolute;top:var(--gantt-header-height);left:0;height:calc(100% - var(--gantt-header-height));min-width:100%}.gantt-grid-column{position:absolute;top:0;height:100%;border-right:1px solid var(--gantt-grid-color);box-sizing:border-box}.gantt-grid-column.today{background-color:var(--gantt-today-color)}.gantt-grid-row{position:absolute;left:0;border-bottom:1px solid var(--gantt-grid-color);box-sizing:border-box;background-color:var(--gantt-row-color)}.gantt-grid-row:nth-child(odd){background-color:var(--gantt-bg-color)}.gantt-bars{position:absolute;top:var(--gantt-header-height);left:0;height:calc(100% - var(--gantt-header-height));min-width:100%;pointer-events:none}.gantt-task-container{position:absolute;box-sizing:border-box;pointer-events:auto;cursor:pointer;transition:transform .1s ease}.gantt-task-container:hover{z-index:10;transform:translateY(-2px)}.gantt-task-bar.milestone{background-color:var(--gantt-milestone-color);width:15px!important;height:15px!important;border-radius:50%;transform:rotate(45deg);top:50%;margin-top:-7.5px;left:50%;margin-left:-7.5px}.gantt-task-progress{position:absolute;top:0;left:0;height:100%;background-color:var(--gantt-progress-color);opacity:.7}.gantt-task-label{position:absolute;left:calc(100% + 8px);top:0;white-space:nowrap;font-size:12px;color:var(--text-normal);line-height:var(--gantt-bar-height)}.gantt-task-container.right-aligned .gantt-task-label{left:auto;right:calc(100% + 8px);text-align:right}@media (max-width: 680px){.gantt-header-cell{font-size:10px}.gantt-task-label{font-size:10px}}.gantt-chart-container{display:flex;flex-direction:column;height:100%;overflow:hidden;position:relative}.gantt-header-container{height:40px;flex-shrink:0;overflow:hidden;position:relative;border-bottom:1px solid var(--background-modifier-border);background-color:var(--background-secondary)}.gantt-header-svg{display:block}.gantt-header-tick-major,.gantt-header-tick-minor,.gantt-header-tick-day,.gantt-header-today-marker{stroke:var(--background-modifier-border);stroke-width:1}.gantt-header-tick-major{stroke-width:1.5}.gantt-header-today-marker{stroke:var(--color-orange);stroke-width:1.5;stroke-dasharray:4,2}.gantt-header-label-major,.gantt-header-label-minor,.gantt-header-label-day{font-size:var(--font-ui-small);fill:var(--text-muted);user-select:none;pointer-events:none}.gantt-header-label-major{font-weight:500;fill:var(--text-normal)}.gantt-scroll-container{flex-grow:1;overflow:auto;position:relative}.gantt-content-wrapper{position:relative;background:var(--background-primary)}.gantt-grid-line-major,.gantt-grid-line-minor{stroke:var(--background-modifier-border-hover);stroke-width:.5}.gantt-grid-line-major{stroke-width:1}.gantt-grid-line-horizontal{stroke:var(--background-modifier-border);stroke-width:1}.gantt-grid-today-marker{stroke:var(--color-orange);stroke-width:1;stroke-dasharray:4,2}.gantt-task-item{cursor:pointer}.gantt-task-bar{fill:var(--color-blue);stroke:var(--color-blue-hover);stroke-width:.5;transition:fill .1s ease}.gantt-task-item:hover .gantt-task-bar{fill:var(--color-accent)}.gantt-task-milestone{fill:var(--color-purple);stroke:var(--color-purple);stroke-width:1;transition:fill .1s ease}.gantt-task-item:hover .gantt-task-milestone{fill:var(--color-accent)}.gantt-task-item.status-done .gantt-task-bar,.gantt-task-item.status-done .gantt-task-milestone{fill:var(--color-green);stroke:var(--color-green);opacity:.7}.gantt-task-item.status-cancelled .gantt-task-bar,.gantt-task-item.status-cancelled .gantt-task-milestone{fill:var(--color-red);stroke:var(--color-red);opacity:.6;text-decoration:line-through}.gantt-task-label-fo{pointer-events:none;overflow:hidden;user-select:none}.gantt-task-label-markdown{color:var(--text-on-accent);font-size:var(--font-ui-smaller);line-height:1.3;padding:0 2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:flex;align-items:center;height:100%}.gantt-task-label-markdown p{margin:0!important}.gantt-milestone-label-container p{margin-block-start:0;margin-block-end:0;margin-inline-start:0;margin-inline-end:0;color:var(--text-normal);font-size:var(--font-ui-smaller);line-height:1.3;padding:0 2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:flex;align-items:center;height:100%}.gantt-task-item.status-done .gantt-task-label-markdown{color:var(--text-on-accent)}.gantt-task-item.status-cancelled .gantt-task-label-markdown{color:var(--text-on-accent);text-decoration:line-through}.gantt-milestone-label{fill:var(--text-normal)}.gantt-filter-area{display:flex;align-items:center;justify-content:flex-end;width:100%;padding-left:var(--size-2-2);padding-right:var(--size-4-2);background-color:var(--background-primary)}.gantt-filter-area .filter-component{flex:1}.gantt-offscreen-indicator{position:absolute;top:calc(50% + 20px);transform:translateY(-50%);width:8px;height:8px;background-color:#80808099;border-radius:50%;z-index:10;pointer-events:none;display:none;transition:opacity .2s ease-in-out;opacity:1}.gantt-offscreen-indicator[style*="display: none"]{opacity:0}.gantt-offscreen-indicator-left{left:5px}.gantt-offscreen-indicator-right{right:5px}.gantt-indicator-container{position:absolute;top:0;bottom:0;width:var(--size-4-3);z-index:10;pointer-events:none;overflow:hidden}.gantt-indicator-container-left{left:0}.gantt-indicator-container-right{right:0}.gantt-single-indicator{position:absolute;left:var(--size-2-1);width:var(--size-4-2);height:var(--size-4-2);border-radius:50%;background-color:var(--text-faint);pointer-events:auto;cursor:default}.gantt-single-indicator:hover{background-color:var(--text-accent)}.gantt-chart-container .gantt-indicator-container{top:calc(var(--header-height, 40px) + var(--filter-height, 0px));bottom:15px}.gantt-chart-container .gantt-indicator-container-right{right:15px}.gantt-task-label p{margin:0;line-height:var(--gantt-bar-height);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.task-property-container{display:flex;flex-direction:column;height:100%;width:100%;overflow:hidden}.task-property-content{display:flex;flex-direction:row;flex:1;overflow:hidden}.task-property-left-column{width:max(120px,30%);min-width:min(120px,30%);max-width:300px;display:flex;flex-direction:column;border-right:1px solid var(--background-modifier-border);overflow:hidden}.is-phone .task-property-left-column{max-width:100%}.task-property-right-column{flex:1;display:flex;flex-direction:column;overflow:hidden}.task-property-sidebar-header{display:flex;justify-content:space-between;align-items:center;padding:var(--size-4-2) var(--size-4-4);border-bottom:1px solid var(--background-modifier-border);height:var(--size-4-10)}.task-property-sidebar-title{font-weight:600;font-size:14px}.multi-select-mode .task-property-multi-select-btn{color:var(--color-accent)}.task-property-multi-select-btn{cursor:pointer;color:var(--text-muted);display:flex;align-items:center;justify-content:center}.task-property-multi-select-btn:hover{color:var(--text-normal)}.task-property-sidebar-list{flex:1;overflow-y:auto;padding:var(--size-4-2)}.task-property-list-item{display:flex;align-items:center;padding:4px 12px;cursor:pointer;border-radius:var(--radius-s)}.task-property-list-item:hover{background-color:var(--background-modifier-hover)}.task-property-list-item.selected{background-color:var(--background-modifier-active)}.task-property-icon{margin-right:8px;color:var(--text-muted);display:flex;align-items:center;justify-content:center}.task-property-name{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.task-property-count{margin-left:8px;font-size:.8em;color:var(--text-muted);background-color:var(--background-modifier-border);border-radius:10px;padding:1px 6px}.task-property-task-header{display:flex;justify-content:space-between;align-items:center;padding:var(--size-4-2) var(--size-4-4);border-bottom:1px solid var(--background-modifier-border);height:var(--size-4-10)}.task-property-task-title{font-weight:600;font-size:16px}.task-property-task-count{color:var(--text-muted)}.task-property-task-list{flex:1;overflow-y:auto}.task-property-empty-state{display:flex;align-items:center;justify-content:center;height:100%;color:var(--text-muted);font-style:italic;padding:16px}.is-phone .task-property-left-column{position:absolute;left:0;top:0;height:100%;z-index:10;background-color:var(--background-secondary);width:100%;transform:translate(-100%);transition:transform .3s ease-in-out;border-right:1px solid var(--background-modifier-border)}.is-phone .task-property-left-column.is-visible{transform:translate(0)}.is-phone .task-property-sidebar-toggle{display:flex;align-items:center;justify-content:center;margin-right:8px}.is-phone .task-property-sidebar-close{--icon-size: var(--size-4-4);position:absolute;top:var(--size-4-2);right:10px;z-index:15;display:flex;align-items:center;justify-content:center}.is-phone .task-property-container:has(.task-property-left-column.is-visible):before{content:"";position:absolute;top:0;left:0;width:100%;height:100%;background-color:var(--background-modifier-cover);opacity:.5;z-index:5;transition:opacity .3s ease-in-out}.is-phone .task-property-container{position:relative;overflow:hidden}.is-phone .task-property-sidebar-header:has(.task-property-sidebar-close){padding-right:var(--size-4-12)}.table-view-adapter{width:100%;display:flex;flex-direction:column;gap:0;height:100%;overflow:hidden}.task-table-container{display:flex;flex-direction:column;height:100%;overflow:hidden;position:relative;background-color:var(--background-primary)}.task-table{width:100%;border-collapse:collapse;table-layout:fixed;font-size:var(--font-ui-small);flex:1;min-height:0;min-width:max-content}.task-table-wrapper{flex:1;overflow:auto;min-height:0;position:relative;overflow-x:auto;overflow-y:auto;scroll-behavior:smooth}.task-table-header{position:sticky;top:0;z-index:10;background-color:var(--background-secondary);border-bottom:2px solid var(--background-modifier-border);min-width:max-content}.task-table-header-row{height:40px}.task-table-header-cell{padding:8px 12px;text-align:left;font-weight:600;color:var(--text-muted);border-right:1px solid var(--background-modifier-border);position:relative;user-select:none;background-color:var(--background-secondary);white-space:nowrap}.task-table-header-cell:last-child{border-right:none}.task-table-header-cell.sortable{cursor:pointer}.task-table-header-cell.sortable:hover{background-color:var(--background-modifier-hover)}.task-table-header-content{display:flex;align-items:center;justify-content:space-between;gap:4px}.task-table-header-title{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.task-table-sort-icon{font-size:12px;opacity:.5;transition:opacity .2s;display:flex;align-items:center;width:16px;height:16px}.task-table-sort-icon.asc,.task-table-sort-icon.desc{opacity:1;color:var(--text-accent)}.task-table-resize-handle{position:absolute;top:0;right:0;width:4px;height:100%;cursor:col-resize;background-color:transparent;transition:background-color .2s}.task-table-resize-handle:hover{background-color:var(--text-accent)}.task-table-body{background-color:var(--background-primary)}.task-table-row{height:40px;border-bottom:1px solid var(--background-modifier-border);transition:background-color .2s}.task-table-row:hover{background-color:var(--background-modifier-hover)}.task-table-row.selected{background-color:var(--background-modifier-active-hover)}.task-table-row:nth-child(even){background-color:var(--background-secondary-alt)}.task-table-row:nth-child(even):hover{background-color:var(--background-modifier-hover)}.task-table-row:nth-child(even).selected{background-color:var(--background-modifier-active-hover)}.task-table-cell{padding:8px 12px;vertical-align:middle;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.task-table-cell:last-child{border-right:none}.task-table-cell.editing{padding:0}.task-table-tree-indent{display:inline-flex;align-items:center;gap:4px}.task-table-cell:has(.task-table-expand-btn){padding-left:0}.task-table-row.task-table-subtask{background-color:var(--background-secondary)}.task-table-expand-btn{cursor:pointer;user-select:none;width:20px;height:20px;padding:0;display:flex;align-items:center;justify-content:center;border-radius:2px;font-size:10px;transition:background-color .2s}.task-table-expand-btn:hover{background-color:var(--background-modifier-hover)}.task-table-row-level-1 .task-table-cell:first-child{padding-left:32px}.task-table-row-level-2 .task-table-cell:first-child{padding-left:52px}.task-table-row-level-3 .task-table-cell:first-child{padding-left:72px}.task-table-row-level-4 .task-table-cell:first-child{padding-left:92px}.task-table-row-level-5 .task-table-cell:first-child{padding-left:112px}.task-table-text{color:var(--text-normal)}.task-table-number{text-align:right;color:var(--text-muted);font-variant-numeric:tabular-nums}.task-table-status{display:flex;align-items:center;gap:6px}.task-table-status-icon{font-size:14px;display:flex;align-items:center;width:16px;height:16px}.task-table-status-text{flex:1;overflow:hidden;text-overflow:ellipsis}.task-table-status.completed .task-table-status-icon{color:var(--text-success)}.task-table-status.in-progress .task-table-status-icon{color:var(--text-warning)}.task-table-status.abandoned .task-table-status-icon{color:var(--text-error)}.task-table-status.planned .task-table-status-icon{color:var(--text-muted)}.task-table-status.not-started .task-table-status-icon{color:var(--text-faint)}.task-table-priority{display:flex;align-items:center;gap:6px}.task-table-priority.clickable-priority{cursor:pointer;padding:4px;border-radius:4px;transition:background-color .2s}.task-table-priority.clickable-priority:hover{background-color:var(--background-modifier-hover)}.task-table-priority-icon{font-size:14px;display:flex;align-items:center;width:16px;height:16px}.task-table-priority-icon.high{color:var(--text-error)}.task-table-priority-icon.medium{color:var(--text-warning)}.task-table-priority-icon.low{color:var(--text-muted)}.task-table-priority-text{flex:1;overflow:hidden;text-overflow:ellipsis}.task-table-priority-empty{color:var(--text-faint);font-style:italic}.task-table-date{display:flex;flex-direction:column;gap:2px;cursor:pointer;transition:background-color .2s;padding:4px;border-radius:4px}.task-table-date:hover{background-color:var(--background-modifier-hover)}.task-table-date-text{font-size:var(--font-ui-small);color:var(--text-normal)}.task-table-date-relative{font-size:var(--font-ui-smaller);font-weight:500}.task-table-date-relative.today{color:var(--text-success)}.task-table-date-relative.tomorrow{color:var(--text-accent)}.task-table-date-relative.yesterday{color:var(--text-muted)}.task-table-date-relative.overdue{color:var(--text-error)}.task-table-date-relative.upcoming{color:var(--text-warning)}.task-table-date-empty{color:var(--text-faint);font-style:italic}.task-table-tags{display:flex;flex-wrap:wrap;gap:4px;align-items:center}.task-table-tag-chip{background-color:var(--background-modifier-accent);color:var(--text-accent);padding:2px 6px;border-radius:8px;font-size:var(--font-ui-smaller);font-weight:500;white-space:nowrap}.task-table-tags-empty{color:var(--text-faint);font-style:italic}.task-table-text-input,.task-table-tags-input{border:none!important;background:transparent!important;outline:none!important;width:100%!important;padding:0!important;font:inherit!important;color:var(--text-normal)!important}.task-table-text-input:focus,.task-table-tags-input:focus{background-color:var(--background-modifier-form-field)!important;border-radius:3px!important;padding:2px 4px!important}.task-count-icon{font-size:16px;display:flex;align-items:center;width:16px;height:16px}.task-table-empty-row{height:80px}.task-table-empty-cell{text-align:center;color:var(--text-muted);font-style:italic;vertical-align:middle}.task-table-loading{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);padding:20px;background-color:var(--background-primary);border:1px solid var(--background-modifier-border);border-radius:8px;color:var(--text-muted);font-size:var(--font-ui-small);z-index:100}.task-table.resizing{user-select:none}.task-table.resizing *{cursor:col-resize!important}.virtual-scroll-spacer{pointer-events:none;visibility:hidden}@media (max-width: 768px){.task-table-container{font-size:var(--font-ui-smaller)}.task-table-wrapper{overflow-x:auto}.task-table{min-width:800px}.task-table-header-cell,.task-table-cell{padding:6px 8px}.task-table-row{height:36px}.task-table-header-row{height:36px}}.theme-dark .task-table-container{border-color:var(--background-modifier-border)}.theme-dark .task-table-row:nth-child(even){background-color:var(--background-primary-alt)}@media (prefers-contrast: high){.task-table-container{border-width:2px}.task-table-header-cell,.task-table-cell{border-width:1px}.task-table-row{border-bottom-width:1px}}@media print{.task-table-container{border:none;overflow:visible;height:auto}.task-table-header{position:static}.task-table-resize-handle{display:none}.task-table-expand-btn{display:none}}.virtual-scroll-spacer-top{pointer-events:none}.virtual-scroll-spacer-top td{padding:0!important;border:none!important;background:transparent!important}.task-table-context-menu{background:var(--background-primary);border:1px solid var(--background-modifier-border);border-radius:4px;box-shadow:0 2px 8px #00000026;z-index:1000;min-width:120px}.task-table-context-menu-item{padding:6px 12px;cursor:pointer;transition:background-color .1s ease}.task-table-context-menu-item:hover{background-color:var(--background-modifier-hover)}.task-table-date-input{cursor:pointer;background:var(--background-primary);border:1px solid var(--background-modifier-border);border-radius:3px;padding:4px 8px;width:100%}.task-table-date-input:hover{border-color:var(--background-modifier-border-hover)}.task-table-date-input:focus{border-color:var(--interactive-accent);outline:none}.task-table-project-input,.task-table-context-input,.task-table-tags-input{background:var(--background-primary);border:1px solid var(--background-modifier-border);border-radius:3px;padding:4px 8px;width:100%}.task-table-project-input:focus,.task-table-context-input:focus,.task-table-tags-input:focus{border-color:var(--interactive-accent);outline:none}.task-table-row.selected{background-color:var(--background-modifier-hover)}.task-table-row:hover{background-color:var(--background-modifier-hover-weak)}@media (max-width: 768px){.task-table{font-size:.9em}th[data-column-id=rowNumber]{max-width:40px!important;min-width:40px!important;width:40px!important}.task-table-tree-container{gap:0!important}.task-table-expand-btn{margin-right:0!important}td[data-column-id=rowNumber]{max-width:40px!important;min-width:40px!important;width:40px!important}.task-table-header-cell,.task-table-cell{padding:6px 4px}}.task-table-header-bar{display:flex;justify-content:space-between;align-items:center;padding:6px 8px;background-color:var(--background-secondary);border-bottom:1px solid var(--background-modifier-border);border-radius:6px 6px 0 0;margin-bottom:0;flex-shrink:0;min-height:40px}.table-header-left{display:flex;align-items:center;gap:12px}.table-header-right{display:flex;align-items:center;gap:8px}.task-count-container{display:flex;align-items:center;gap:8px;padding:6px 12px;background-color:var(--background-primary);border-radius:4px;border:1px solid var(--background-modifier-border)}.task-count-text{font-size:var(--font-ui-small);font-weight:500;color:var(--text-normal)}.table-controls-container{display:flex;align-items:center;gap:8px}.table-control-btn{display:flex;align-items:center;gap:6px;padding:8px 12px;background-color:var(--background-primary);border:1px solid var(--background-modifier-border);border-radius:4px;cursor:pointer;font-size:var(--font-ui-small);color:var(--text-normal);transition:all .2s ease;box-shadow:unset!important}.table-control-btn:hover{background-color:var(--background-modifier-hover)}.table-control-btn:active{background-color:var(--background-modifier-active)}.tree-mode-btn.active{background-color:var(--text-accent);color:var(--text-on-accent);border-color:var(--text-accent)}.tree-mode-icon,.refresh-icon,.column-icon{font-size:14px;display:flex;align-items:center;justify-content:center}.tree-mode-text,.refresh-text,.column-text{font-weight:500}.dropdown-arrow{font-size:10px;transition:transform .2s ease}.column-dropdown{position:relative}.column-dropdown-menu{position:absolute;top:100%;right:0;margin-top:4px;background-color:var(--background-primary);border:1px solid var(--background-modifier-border);border-radius:4px;box-shadow:var(--shadow-l);z-index:1000;min-width:200px;max-height:300px;overflow-y:auto}.column-toggle-item{display:flex;align-items:center;gap:8px;padding:8px 12px;cursor:pointer;transition:background-color .2s ease}.column-toggle-item:hover{background-color:var(--background-modifier-hover)}.column-toggle-checkbox{margin:0;cursor:pointer}.column-toggle-label{flex:1;font-size:var(--font-ui-small);color:var(--text-normal);cursor:pointer;margin:0}@media (max-width: 768px){.task-table-header-bar{flex-direction:column;gap:12px;align-items:stretch}.table-header-left{display:none}.table-header-left,.table-header-right{justify-content:center}.table-controls-container{justify-content:center;flex-wrap:wrap}.table-control-btn{flex:1;min-width:100px;justify-content:center}.column-dropdown-menu{right:auto;left:0;width:100%}}.theme-dark .task-table-header-bar{background-color:var(--background-secondary-alt)}.theme-dark .column-dropdown-menu{background-color:var(--background-primary-alt);border-color:var(--background-modifier-border-hover)}.custom-suggest-dropdown{background-color:var(--background-primary);border:1px solid var(--background-modifier-border);border-radius:4px;box-shadow:var(--shadow-l);z-index:1000;position:absolute;max-height:200px;overflow-y:auto;min-width:150px}.custom-suggest-dropdown .suggestion-item{padding:8px 12px;cursor:pointer;border-bottom:1px solid var(--background-modifier-border);transition:background-color .2s;font-size:var(--font-ui-small);color:var(--text-normal)}.custom-suggest-dropdown .suggestion-item:last-child{border-bottom:none}.custom-suggest-dropdown .suggestion-item:hover,.custom-suggest-dropdown .suggestion-item.selected{background-color:var(--background-modifier-hover)}.custom-suggest-dropdown .suggestion-item.selected{color:var(--text-accent)}.task-table-subtask{border-left:2px solid var(--background-modifier-border-hover)}.task-table-parent .task-table-cell:first-child{font-weight:500}.task-table-subtask-cell{border-left:1px solid var(--background-modifier-border-focus)}.task-table-tree-container{display:flex;align-items:center;gap:6px;width:100%}.task-table-tree-structure{display:flex;align-items:center;gap:2px;flex-shrink:0}.task-table-tree-line{font-family:monospace;font-size:12px;color:var(--text-faint);line-height:1;width:16px;text-align:center}.task-table-tree-connector{color:var(--text-muted)}.task-table-tree-vertical{color:var(--text-faint)}.task-table-subtask-indicator{font-size:10px;color:var(--text-accent);margin-right:6px;margin-left:4px;flex-shrink:0;font-weight:bold}.task-table-top-level-expand{margin-right:6px}.task-table-content-wrapper{flex:1;min-width:0}.task-table-child-indicator{font-size:10px;color:var(--text-muted);margin-left:6px;flex-shrink:0}.task-table-status.clickable-status{cursor:pointer;padding:4px;border-radius:4px;transition:background-color .2s}.task-table-status.clickable-status:hover{background-color:var(--background-modifier-hover)}.task-table-priority-icon.highest{color:var(--text-error);filter:brightness(1.2)}.task-table-priority-icon.lowest{color:var(--text-faint)}.task-table-expand-btn.clickable-icon{opacity:.7;transition:opacity .2s,background-color .2s}.task-table-expand-btn.clickable-icon:hover{opacity:1}.task-table-row-level-1 .task-table-cell:first-child,.task-table-row-level-2 .task-table-cell:first-child,.task-table-row-level-3 .task-table-cell:first-child,.task-table-row-level-4 .task-table-cell:first-child,.task-table-row-level-5 .task-table-cell:first-child{padding-left:12px}.tg-quadrant-component-container{height:100%;display:flex;flex-direction:column;overflow:hidden;background:var(--background-primary);width:100%}.tg-quadrant-header{display:flex;align-items:center;justify-content:space-between;padding:var(--size-4-3) var(--size-4-4);background:var(--background-primary);flex-shrink:0}.tg-quadrant-title{font-size:var(--font-ui-medium);font-weight:var(--font-semibold);color:var(--text-normal);margin:0}.tg-quadrant-controls{display:flex;align-items:center;gap:var(--size-2-3)}.tg-quadrant-sort-select{padding:var(--size-2-2) var(--size-2-3);border:1px solid var(--background-modifier-border);border-radius:var(--radius-s);background:var(--background-primary);color:var(--text-normal);font-size:var(--font-ui-small);cursor:pointer;transition:border-color .2s ease}.tg-quadrant-sort-select:hover{border-color:var(--background-modifier-border-hover)}.tg-quadrant-sort-select:focus{border-color:var(--color-accent);outline:none}.tg-quadrant-toggle-empty{padding:var(--size-2-2);border:1px solid var(--background-modifier-border);border-radius:var(--radius-s);background:var(--background-primary);color:var(--text-muted);cursor:pointer;transition:all .2s ease;width:28px;height:28px;display:flex;align-items:center;justify-content:center}.tg-quadrant-toggle-empty:hover{background:var(--background-modifier-hover);color:var(--text-normal);border-color:var(--background-modifier-border-hover)}.tg-quadrant-filter-container{flex-shrink:0;border-bottom:1px solid var(--background-modifier-border)}.tg-quadrant-grid{display:grid;grid-template-columns:1fr 1fr;grid-template-rows:1fr 1fr;gap:1px;flex:1;background:var(--background-modifier-border);overflow:hidden}.tg-quadrant-column{display:flex;flex-direction:column;background:var(--background-primary);min-height:0;overflow:hidden;position:relative}.tg-quadrant-column--hidden{display:none}.tg-quadrant-column .tg-quadrant-header{padding:var(--size-4-2) var(--size-4-3);background:var(--background-secondary);border-bottom:1px solid var(--background-modifier-border);flex-shrink:0;position:relative;min-height:var(--size-4-12)}.tg-quadrant-title-container{display:flex;align-items:center;gap:var(--size-2-2);margin-bottom:var(--size-2-1)}.tg-quadrant-priority{font-size:var(--font-ui-medium);line-height:1;opacity:.8}.tg-quadrant-column .tg-quadrant-title{font-size:var(--font-ui-small);font-weight:var(--font-semibold);color:var(--text-normal);margin:0}.tg-quadrant-description{font-size:var(--font-ui-smaller);color:var(--text-muted);margin-bottom:var(--size-2-2);line-height:1.3}.tg-quadrant-count{font-size:var(--font-ui-smaller);color:var(--text-faint);background:var(--background-modifier-border);padding:var(--size-2-1) var(--size-2-2);border-radius:var(--radius-s);font-weight:var(--font-medium)}.tg-quadrant-column-content{flex:1;overflow-y:auto;padding:var(--size-2-3);min-height:100px}.tg-quadrant-column-content::-webkit-scrollbar{width:8px}.tg-quadrant-column-content::-webkit-scrollbar-track{background:transparent}.tg-quadrant-column-content::-webkit-scrollbar-thumb{background:var(--background-modifier-border);border-radius:var(--radius-s)}.tg-quadrant-column-content::-webkit-scrollbar-thumb:hover{background:var(--background-modifier-border-hover)}.tg-quadrant-column-content--drop-active{background:var(--background-modifier-hover);border:2px dashed var(--color-accent);border-radius:var(--radius-m)}.quadrant-urgent-important .tg-quadrant-header:before{content:"";position:absolute;top:0;left:0;right:0;height:3px;background:var(--text-error);opacity:.6}.quadrant-not-urgent-important .tg-quadrant-header:before{content:"";position:absolute;top:0;left:0;right:0;height:3px;background:var(--color-accent);opacity:.6}.quadrant-urgent-not-important .tg-quadrant-header:before{content:"";position:absolute;top:0;left:0;right:0;height:3px;background:var(--text-warning);opacity:.6}.quadrant-not-urgent-not-important .tg-quadrant-header:before{content:"";position:absolute;top:0;left:0;right:0;height:3px;background:var(--text-muted);opacity:.4}.tg-quadrant-card{background:var(--background-primary);border:1px solid var(--background-modifier-border);border-radius:var(--radius-s);margin-bottom:var(--size-2-3);padding:var(--size-4-2);cursor:pointer;transition:all .15s ease;position:relative}.tg-quadrant-card:hover{background:var(--background-modifier-hover);border-color:var(--background-modifier-border-hover);transform:translateY(-1px);box-shadow:var(--shadow-s)}.tg-quadrant-card:active{transform:translateY(0)}.tg-quadrant-card:last-child{margin-bottom:0}.tg-quadrant-card-header{display:flex;align-items:flex-start;justify-content:space-between;margin-bottom:var(--size-2-2);gap:var(--size-2-2)}.tg-quadrant-card-checkbox{flex-shrink:0;margin-top:2px}.tg-quadrant-card-actions{flex-shrink:0;opacity:0;transition:opacity .2s ease}.tg-quadrant-card:hover .tg-quadrant-card-actions{opacity:1}.tg-quadrant-card-more-btn{background:none;border:none;padding:var(--size-2-1);border-radius:var(--radius-s);color:var(--text-muted);cursor:pointer;transition:all .2s ease;width:24px;height:24px;display:flex;align-items:center;justify-content:center}.tg-quadrant-card-more-btn:hover{background:var(--background-modifier-hover);color:var(--text-normal)}.tg-quadrant-card-content{margin-bottom:var(--size-2-2)}.tg-quadrant-card-title{font-size:var(--font-ui-small);line-height:1.4;color:var(--text-normal);margin-bottom:var(--size-2-1);word-wrap:break-word;font-weight:var(--font-normal)}.tg-quadrant-card-priority{font-size:var(--font-ui-small);margin-left:var(--size-2-1);opacity:.8}.tg-quadrant-card-tags{display:flex;flex-wrap:wrap;gap:var(--size-2-1);margin-top:var(--size-2-2)}.tg-quadrant-card-tag{background:var(--background-modifier-border);color:var(--text-muted);padding:var(--size-2-1) var(--size-2-2);border-radius:var(--radius-s);font-size:var(--font-ui-smaller);font-weight:var(--font-medium);border:1px solid transparent;transition:all .2s ease}.tg-quadrant-card-tag:hover{background:var(--background-modifier-hover);color:var(--text-normal)}.tg-quadrant-tag--urgent{background:var(--background-modifier-error);color:var(--text-error);border-color:var(--text-error)}.tg-quadrant-tag--important{background:var(--background-modifier-accent);color:var(--text-accent);border-color:var(--color-accent)}.tg-quadrant-card-metadata{display:flex;align-items:center;justify-content:space-between;font-size:var(--font-ui-smaller);color:var(--text-faint);gap:var(--size-2-2)}.tg-quadrant-card-due-date{display:flex;align-items:center;gap:var(--size-2-1);background:var(--background-modifier-border);padding:var(--size-2-1) var(--size-2-2);border-radius:var(--radius-s);font-weight:var(--font-medium)}.tg-quadrant-card-due-date-icon{width:12px;height:12px;opacity:.7}.tg-quadrant-card-due-date--urgent{color:var(--text-warning)}.tg-quadrant-card-due-date--overdue{color:var(--text-error)}.tg-quadrant-card-file-info{display:flex;align-items:center;justify-content:space-between;gap:var(--size-4-2);opacity:.7;transition:opacity .2s ease}.tg-quadrant-card:hover .tg-quadrant-card-file-info{opacity:1}.tg-quadrant-card-file-icon{width:12px;height:12px}.tg-quadrant-card-file-name{font-size:var(--font-ui-smaller);max-width:120px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tg-quadrant-card-line{color:var(--text-faint);font-size:var(--font-ui-smaller);opacity:.6;font-weight:var(--font-medium)}.tg-quadrant-card--priority-highest{border-left:3px solid var(--text-error)}.tg-quadrant-card--priority-high{border-left:3px solid var(--text-warning)}.tg-quadrant-card--priority-medium{border-left:3px solid var(--color-accent)}.tg-quadrant-card--priority-low{border-left:3px solid var(--text-success)}.tg-quadrant-card--priority-lowest{border-left:3px solid var(--text-muted)}.tg-quadrant-card--dragging{box-shadow:var(--shadow-l)}.tg-quadrant-card--chosen{background:var(--background-modifier-hover);border-color:var(--color-accent);box-shadow:var(--shadow-s)}.tg-quadrant-card--drag{box-shadow:var(--shadow-l);z-index:1000;border-color:var(--color-accent)}.tg-quadrant-empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;height:120px;color:var(--text-faint);text-align:center;padding:var(--size-4-4);opacity:.8}.tg-quadrant-empty-icon{width:32px;height:32px;margin-bottom:var(--size-2-3);opacity:.5;color:var(--text-faint)}.tg-quadrant-empty-message{font-size:var(--font-ui-small);line-height:1.4;font-weight:var(--font-medium)}@media (max-width: 768px){.tg-quadrant-grid{grid-template-columns:1fr;grid-template-rows:repeat(4,1fr)}.tg-quadrant-header{padding:var(--size-2-3) var(--size-4-2)}.tg-quadrant-column .tg-quadrant-header{padding:var(--size-2-3) var(--size-4-2)}.tg-quadrant-card{padding:var(--size-2-3)}.tg-quadrant-card-title{font-size:var(--font-ui-smaller)}.tg-quadrant-controls{gap:var(--size-2-2)}}.tg-quadrant-card:focus{outline:2px solid var(--color-accent);outline-offset:2px}.tg-quadrant-card-more-btn:focus{outline:2px solid var(--color-accent);outline-offset:2px}@keyframes cardComplete{0%{transform:scale(1)}50%{transform:scale(1.05)}to{transform:scale(1)}}.tg-quadrant-card--completed{animation:cardComplete .3s ease-in-out}.tg-quadrant-card:hover .tg-quadrant-card-title{color:var(--text-normal)}.tg-quadrant-card:hover .tg-quadrant-card-priority{opacity:1}.tg-quadrant-card-content{position:relative}.tg-quadrant-loading{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:2rem;color:var(--text-muted);min-height:100px}.tg-quadrant-loading-spinner{margin-bottom:1rem}.tg-quadrant-spinner{width:24px;height:24px;color:var(--color-accent)}.tg-quadrant-loading-message{font-size:.9rem;opacity:.7}.tg-quadrant-dragging{cursor:grabbing!important}.tg-quadrant-dragging *{pointer-events:none}.tg-quadrant-card--ghost{opacity:.4;background:var(--background-modifier-border);border:2px dashed var(--color-accent)}.tg-quadrant-card--chosen{box-shadow:0 8px 25px #00000026;transform:scale(1.02);z-index:1000;background:var(--background-primary);border:2px solid var(--color-accent)}.tg-quadrant-card--drag{opacity:.8;box-shadow:0 12px 30px #0003}.tg-quadrant-card--fallback{opacity:.9;background:var(--background-primary);border:2px solid var(--color-accent);border-radius:var(--radius-m);box-shadow:0 8px 25px #00000026}.tg-quadrant-column--drag-target{background:var(--background-modifier-hover);border:2px dashed var(--color-accent);border-radius:var(--radius-m)}.tg-quadrant-column-content--drop-active{background:var(--background-modifier-active-hover);border:2px dashed var(--color-accent);border-radius:var(--radius-s);min-height:60px;position:relative}.tg-quadrant-column-content--drop-active:before{content:"Drop task here";position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);color:var(--color-accent);font-size:.9rem;font-weight:500;opacity:.7;pointer-events:none;z-index:1}.tg-quadrant-update-feedback{position:fixed;top:20px;right:20px;z-index:10000;opacity:0;transform:translate(100%);transition:all .3s ease;pointer-events:none}.tg-quadrant-feedback--show{opacity:1;transform:translate(0)}.tg-quadrant-feedback--hide{opacity:0;transform:translate(100%)}.tg-quadrant-feedback-content{display:flex;align-items:center;gap:.5rem;padding:.75rem 1rem;background:var(--background-primary);border:1px solid var(--background-modifier-border);border-radius:var(--radius-m);box-shadow:0 4px 12px #0000001a;min-width:200px}.tg-quadrant-feedback--error .tg-quadrant-feedback-content{background:var(--background-modifier-error);border-color:var(--text-error);color:var(--text-error)}.tg-quadrant-feedback-icon{font-size:1.2rem;flex-shrink:0}.tg-quadrant-feedback-text{font-size:.9rem;font-weight:500}.tg-quadrant-card{transition:all .2s ease;cursor:grab}.tg-quadrant-card:hover{transform:translateY(-2px);box-shadow:0 4px 12px #0000001a}.tg-quadrant-card:active{cursor:grabbing}.tg-quadrant-empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:2rem 1rem;text-align:center;color:var(--text-muted);min-height:120px;border:2px dashed var(--background-modifier-border);border-radius:var(--radius-m);margin:.5rem 0}.tg-quadrant-empty-icon{margin-bottom:.75rem;opacity:.5}.tg-quadrant-empty-message{font-size:.9rem;line-height:1.4;max-width:200px}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.tg-quadrant-spinner circle{animation:spin 2s linear infinite;transform-origin:center}@media (max-width: 768px){.tg-quadrant-update-feedback{top:10px;right:10px;left:10px;transform:translateY(-100%)}.tg-quadrant-feedback--show{transform:translateY(0)}.tg-quadrant-feedback--hide{transform:translateY(-100%)}.tg-quadrant-feedback-content{min-width:auto;width:100%}}.theme-dark .tg-quadrant-card--chosen{background:var(--background-primary-alt);box-shadow:0 8px 25px #0000004d}.theme-dark .tg-quadrant-card--fallback{background:var(--background-primary-alt);box-shadow:0 8px 25px #0000004d}.theme-dark .tg-quadrant-feedback-content{box-shadow:0 4px 12px #0000004d}@media (prefers-reduced-motion: reduce){.tg-quadrant-card,.tg-quadrant-update-feedback,.tg-quadrant-card--chosen,.tg-quadrant-card--drag{transition:none;animation:none}.tg-quadrant-spinner circle{animation:none}}.tg-quadrant-scroll-container{flex:1;overflow-y:auto;overflow-x:hidden;max-height:70vh;scrollbar-width:thin;scrollbar-color:var(--background-modifier-border) transparent}.tg-quadrant-scroll-container::-webkit-scrollbar{width:6px}.tg-quadrant-scroll-container::-webkit-scrollbar-track{background:transparent}.tg-quadrant-scroll-container::-webkit-scrollbar-thumb{background:var(--background-modifier-border);border-radius:3px}.tg-quadrant-scroll-container::-webkit-scrollbar-thumb:hover{background:var(--background-modifier-border-hover)}.tg-quadrant-load-more{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:1rem;color:var(--text-muted);border-top:1px solid var(--background-modifier-border);margin-top:.5rem}.tg-quadrant-load-more-spinner{margin-bottom:.5rem}.tg-quadrant-load-more-message{font-size:.8rem;opacity:.7}.tg-quadrant-column{display:flex;flex-direction:column;height:100%;min-height:400px;max-height:80vh}.tg-quadrant-column-content{flex:1;display:flex;flex-direction:column;gap:.5rem;padding:.5rem}.tg-quadrant-scroll-container{scroll-behavior:smooth}.tg-quadrant-column.loading-more .tg-quadrant-load-more{opacity:1;pointer-events:none}.tg-quadrant-load-more{min-height:40px;transition:opacity .2s ease}.tg-quadrant-column-content:empty:before{content:"";display:block;min-height:100px}.tg-quadrant-grid{display:grid;grid-template-columns:repeat(2,1fr);height:calc(100vh - 200px);min-height:400px}@media (max-width: 1200px){.tg-quadrant-scroll-container{max-height:60vh}.tg-quadrant-column{max-height:70vh}}@media (max-width: 768px){.tg-quadrant-scroll-container{max-height:50vh}.tg-quadrant-column{max-height:60vh;min-height:300px}.tg-quadrant-grid{grid-template-columns:1fr;height:auto}}.tg-quadrant-column-content{contain:layout style;will-change:contents}.tg-quadrant-card{contain:layout style paint}.tg-quadrant-scroll-container.has-scroll:before{content:"";position:sticky;top:0;height:1px;background:linear-gradient(to bottom,var(--background-primary),transparent);z-index:1}.tg-quadrant-scroll-container.has-scroll:after{content:"";position:sticky;bottom:0;height:1px;background:linear-gradient(to top,var(--background-primary),transparent);z-index:1}.tg-habit-component-container{width:100%;display:flex;flex-direction:column;gap:1rem;padding:1rem;height:100%;overflow-y:auto}.habit-list-container{display:grid;grid-template-columns:repeat(1,minmax(0,1fr));gap:1rem;width:100%}@media screen and (max-width: 480px){.habit-list-container{padding:.5rem;gap:.75rem}}@media screen and (min-width: 768px){.habit-list-container{margin-left:auto;margin-right:auto;max-width:400px;display:flex;flex-direction:column}}@media screen and (min-width: 1024px){.habit-list-container{max-width:500px}}.habit-card-wrapper{width:100%;min-height:fit-content}.habit-card{border:1px solid var(--background-modifier-border);border-radius:var(--radius-m);background-color:var(--background-secondary);color:var(--text-normal);overflow:hidden;display:flex;flex-direction:column;width:100%;height:100%;min-height:fit-content}.habit-card .card-header{display:flex;align-items:center;justify-content:space-between;padding:.5rem 1rem;gap:.5rem}.habit-card .card-title{display:flex;align-items:center;gap:.5rem;font-size:var(--font-ui-large);font-weight:600;flex-grow:1;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.habit-name.habit-name:hover{text-decoration:underline;cursor:pointer}.habit-card .card-content-wrapper{padding:.75rem 1rem;flex-grow:1}.daily-habit-card .habit-checkbox{--checkbox-size: 1.25rem;cursor:pointer;accent-color:var(--interactive-accent)}.daily-habit-card .card-content-wrapper{padding:0 1rem .75rem}.count-habit-card .card-content-wrapper{display:flex;flex-direction:column;gap:.75rem;align-items:center}.count-habit-card .habit-icon-button{--icon-size: 2rem;height:4rem;width:4rem;aspect-ratio:1;padding:0;cursor:pointer;border-radius:var(--radius-s);display:flex;justify-content:center;align-items:center;font-size:1.5rem}.count-habit-card .habit-icon-button{color:var(--icon-color)}.count-habit-card .habit-icon-button:hover{background-color:var(--background-secondary)}.count-habit-card .habit-card-name{font-size:var(--font-ui-large);font-weight:600}.count-habit-card .habit-active-day{font-size:var(--font-ui-small);color:var(--text-muted);font-weight:400}.count-habit-card .habit-info{display:flex;flex-direction:column;align-items:center;text-align:center;flex-grow:1}.count-habit-card .habit-info h3{font-size:var(--font-ui-large);font-weight:600}.count-habit-card .habit-progress-area{width:100%;display:flex;flex-direction:column;align-items:center;gap:.5rem}@media (min-width: 640px){.count-habit-card .card-content-wrapper{flex-direction:row;align-items:center;gap:1rem}.count-habit-card .habit-progress-area{width:auto;min-width:150px;align-items:flex-end}.count-habit-card .habit-heatmap-small{width:100%}}.scheduled-habit-card .card-header{padding-bottom:.5rem}.scheduled-habit-card .card-content-wrapper{display:flex;flex-direction:column;gap:.75rem;align-items:center}.scheduled-habit-card .habit-heatmap-medium{width:100%}.scheduled-habit-card .habit-controls{width:100%;display:flex;flex-direction:column;gap:.5rem;align-items:center}.scheduled-habit-card .habit-event-dropdown{width:auto;margin-bottom:.5rem;width:100%}@media (min-width: 640px){.scheduled-habit-card .card-content-wrapper{flex-direction:row;align-items:flex-start;justify-content:space-between}.scheduled-habit-card .habit-heatmap-medium{width:auto;flex-grow:1;margin-right:1rem}.scheduled-habit-card .habit-controls{width:auto;min-width:150px;align-items:flex-start}}.mapping-habit-card .card-header{padding-bottom:.5rem}.mapping-habit-card .card-content-wrapper{display:flex;flex-direction:column;gap:.75rem;align-items:center;padding-top:0;padding-bottom:1.2rem}.mapping-habit-card .habit-heatmap-medium{width:100%}.mapping-habit-card .habit-controls{width:100%;display:flex;flex-direction:column;align-items:center;gap:.5rem}.mapping-habit-card .habit-mapping-button{display:flex;justify-content:center;align-items:center;font-size:1.75rem;padding:.5rem;width:100%;max-width:100px;height:3.5rem;border:1px solid var(--button-secondary-border-color);background-color:var(--button-secondary-bg);color:var(--text-normal);cursor:pointer;border-radius:var(--radius-s)}.mapping-habit-card .habit-mapping-button:hover{background-color:var(--button-secondary-hover-bg)}.mapping-habit-card .habit-slider-setting{width:100%;max-width:200px}.mapping-habit-card .habit-slider-setting .setting-item-info{display:none}.mapping-habit-card .habit-slider-setting .setting-item{width:100%;padding:0;border:none}.mapping-habit-card .habit-slider-setting .setting-item-control{width:100%}.mapping-habit-card .heatmap-md .heatmap-container-simple{gap:.5rem}@media (min-width: 640px){.mapping-habit-card .card-content-wrapper{flex-direction:row;align-items:center;justify-content:space-between}.mapping-habit-card .habit-heatmap-medium{width:auto;flex-grow:1;margin-right:1rem}.mapping-habit-card .habit-controls{width:auto;min-width:80px;flex-direction:column;align-items:center;gap:.75rem}.mapping-habit-card .habit-mapping-button{width:4rem;height:4rem}.mapping-habit-card .habit-slider-setting{width:100%;max-width:none}}.habit-progress-container{width:100%;height:.75rem;background-color:var(--background-modifier-border);border-radius:var(--radius-l);overflow:hidden;position:relative}.habit-progress-bar{height:100%;background-color:var(--interactive-accent);border-radius:var(--radius-l);transition:width .3s ease-in-out}.habit-progress-container.filled .habit-progress-text{mix-blend-mode:unset}.habit-progress-text{position:absolute;top:0;left:0;width:100%;height:100%;display:flex;justify-content:center;align-items:center;font-size:.6rem;line-height:1;color:var(--text-on-accent);mix-blend-mode:difference;font-weight:500}.tg-heatmap-root{width:100%}.heatmap-sm .heatmap-container-simple{display:grid;grid-template-columns:repeat(3,1fr);gap:3px;overflow-x:auto;padding-bottom:2px}.heatmap-md .heatmap-container-simple{display:grid;grid-template-columns:repeat(6,1fr);gap:3px;overflow-x:auto;padding-bottom:2px;justify-items:center}.heatmap-lg .heatmap-container-simple{display:grid;grid-template-columns:repeat(10,1fr);gap:var(--size-4-2);overflow-x:auto;padding-bottom:2px;justify-items:center}.heatmap-cell{border-radius:var(--radius-s);display:flex;justify-content:center;align-items:center;cursor:pointer;flex-shrink:0;background-color:var( --background-modifier-border );border:1px solid transparent}.heatmap-cell-dot{border-radius:50%}.heatmap-sm .heatmap-cell{width:.75rem;height:.75rem}.habit-heatmap-medium .heatmap-md .heatmap-cell{width:1.4rem;height:1.4rem;font-size:.7rem}.heatmap-md .heatmap-cell{width:1.1rem;height:1.1rem;font-size:.7rem}.heatmap-lg .heatmap-cell{width:1.25rem;height:1.25rem;font-size:.75rem}.heatmap-cell.filled{background-color:var(--interactive-accent);color:var(--text-on-accent)}.heatmap-cell.has-custom-content:has(.pie-dot-container){background:transparent;border:unset}.heatmap-cell.has-custom-content,.heatmap-cell.has-text-content{background-color:var(--background-secondary);border-color:var(--background-modifier-border);color:var(--text-normal)}.heatmap-cell.has-text-content{line-height:1}.pie-dot-container{width:100%;height:100%;display:flex;justify-content:center;align-items:center}.pie-dot-container svg{display:block}.habit-empty-state{text-align:center;padding:2rem 1rem;color:var(--text-muted)}.habit-empty-state h2{font-size:var(--font-ui-large);font-weight:600;margin-bottom:.5rem}.habit-empty-state p{font-size:var(--font-ui-normal);color:var(--text-faint)}.habit-icon{display:inline-block;height:1em;line-height:1;text-align:center;color:var(--text-muted);font-style:italic;margin-right:.25em;--icon-size: 1.5rem}:root{--task-completed-color: #4caf50;--task-doing-color: #80dee5;--task-in-progress-color: #f9d923;--task-abandoned-color: #eb5353;--task-planned-color: #9c27b0;--task-question-color: #2196f3;--task-important-color: #f44336;--task-star-color: #ffc107;--task-quote-color: #607d8b;--task-location-color: #795548;--task-bookmark-color: #ff9800;--task-information-color: #00bcd4;--task-idea-color: #9c27b0;--task-pros-color: #4caf50;--task-cons-color: #f44336;--task-fire-color: #ff5722;--task-key-color: #ffd700;--task-win-color: #66bb6a;--task-up-color: #4caf50;--task-down-color: #f44336;--task-note-color: #9e9e9e;--task-amount-color: #8bc34a;--task-speech-color: #03a9f4;--progress-0-color: #ae431e;--progress-25-color: #e5890a;--progress-50-color: #b4c6a6;--progress-75-color: #6bcb77;--progress-100-color: #4d96ff;--progress-background-color: #f1f1f1}.theme-dark{--task-completed-color: #4caf50;--task-doing-color: #379fa7;--task-in-progress-color: #ffc107;--task-abandoned-color: #f44336;--task-planned-color: #ce93d8;--task-question-color: #42a5f5;--task-important-color: #ef5350;--task-star-color: #ffd54f;--task-quote-color: #90a4ae;--task-location-color: #8d6e63;--task-bookmark-color: #ffb74d;--task-information-color: #26c6da;--task-idea-color: #ce93d8;--task-pros-color: #66bb6a;--task-cons-color: #ef5350;--task-fire-color: #ff7043;--task-key-color: #ffd700;--task-win-color: #81c784;--task-up-color: #66bb6a;--task-down-color: #ef5350;--task-note-color: #bdbdbd;--task-amount-color: #aed581;--task-speech-color: #29b6f6;--progress-0-color: #ae431e;--progress-25-color: #e5890a;--progress-50-color: #b4c6a6;--progress-75-color: #6bcb77;--progress-100-color: #4d96ff;--progress-background-color: #f1f1f1}.task-genius-view-config-modal{width:max(70%,500px)}.task-genius-view-config-modal .setting-item{margin-bottom:15px}.task-genius-view-config-modal .setting-item:not(.setting-item-heading) .setting-item-info{width:120px}.task-genius-view-config-modal .setting-item-control input[type=text],.task-genius-view-config-modal .setting-item-control input[type=number]{width:100%}.task-genius-view-config-modal .setting-item-description{font-size:var(--font-ui-smaller);color:var(--text-muted);margin-top:2px}.view-management-list .setting-item{border-bottom:1px solid var(--background-modifier-border);padding:10px 0;display:flex;align-items:center}.view-management-list .setting-item-info{flex-grow:1;margin-right:10px}.view-management-list .setting-item-control{display:flex;align-items:center;gap:8px}.view-management-list .setting-item-control .button-component{padding:5px;height:auto}.view-management-list .view-order-button,.view-management-list .view-delete-button{margin-left:5px}.view-management-list .setting-item:last-child{border-bottom:none}.view-management-list .setting-item-control .checkbox-container{margin:0}.tg-icon-menu{position:absolute;z-index:100;background-color:var(--background-secondary);border:1px solid var(--background-modifier-border);border-radius:var(--radius-m);box-shadow:var(--shadow-l);padding:8px;max-height:300px;width:250px;display:flex;flex-direction:column;box-sizing:border-box}.tg-icon-menu .tg-menu-search{width:100%;padding:6px 8px;margin-bottom:8px;border:1px solid var(--background-modifier-border);border-radius:var(--radius-s);background-color:var(--background-primary);color:var(--text-normal);box-sizing:border-box;flex-shrink:0}.tg-icon-menu .tg-menu-icons{flex-grow:1;overflow-y:auto;min-height:0;display:grid;grid-template-columns:repeat(auto-fill,minmax(32px,1fr));gap:4px}.tg-icon-menu .clickable-icon{display:flex;justify-content:center;align-items:center;padding:6px;border-radius:var(--radius-s);cursor:pointer;background-color:var(--background-primary);border:1px solid transparent;transition:background-color .1s ease-in-out,border-color .1s ease-in-out}.tg-icon-menu .clickable-icon:hover{background-color:var(--background-modifier-hover);border-color:var(--background-modifier-border-hover)}.tg-icon-menu .clickable-icon svg{width:20px;height:20px;color:var(--text-muted)}.task-status-widget{display:inline-flex;align-items:center;cursor:pointer;font-size:var(--font-ui-medium);font-weight:var(--font-bold)}.task-state.live-preview-mode{padding-inline-start:var(--size-4-2);padding-inline-end:var(--size-2-1)}.task-status-widget .list-bullet:after{background-color:var(--list-marker-color)!important}.task-state[data-task-state=" "]{color:var(--text-accent)}.task-state[data-task-state="/"]{color:var(--task-doing-color)}.task-state[data-task-state=">"]{color:var(--task-in-progress-color)}.task-state[data-task-state=x],.task-state[data-task-state=X]{color:var(--task-completed-color)}.task-state[data-task-state="-"]{color:var(--task-abandoned-color)}.task-state[data-task-state="<"]{color:var(--task-planned-color)}.task-state[data-task-state="?"]{color:var(--task-question-color)}.task-state[data-task-state="!"]{color:var(--task-important-color)}.task-state[data-task-state="*"]{color:var(--task-star-color)}.task-state[data-task-state='"']{color:var(--task-quote-color)}.task-state[data-task-state=l]{color:var(--task-location-color)}.task-state[data-task-state=b]{color:var(--task-bookmark-color)}.task-state[data-task-state=i]{color:var(--task-information-color)}.task-state[data-task-state=I]{color:var(--task-idea-color)}.task-state[data-task-state=p]{color:var(--task-pros-color)}.task-state[data-task-state=c]{color:var(--task-cons-color)}.task-state[data-task-state=f]{color:var(--task-fire-color)}.task-state[data-task-state=k]{color:var(--task-key-color)}.task-state[data-task-state=w]{color:var(--task-win-color)}.task-state[data-task-state=u]{color:var(--task-up-color)}.task-state[data-task-state=d]{color:var(--task-down-color)}.task-state[data-task-state=n]{color:var(--task-note-color)}.task-state[data-task-state=S]{color:var(--task-amount-color)}.task-state[data-task-state="0"],.task-state[data-task-state="1"],.task-state[data-task-state="2"],.task-state[data-task-state="3"],.task-state[data-task-state="4"],.task-state[data-task-state="5"],.task-state[data-task-state="6"],.task-state[data-task-state="7"],.task-state[data-task-state="8"],.task-state[data-task-state="9"]{color:var(--task-speech-color)}.task-fake-bullet{display:inline-block;width:5px;height:5px;border-radius:50%;background-color:var(--text-normal);margin-right:4px;vertical-align:middle}ol>.task-list-item .task-fake-bullet{display:none}ol>.task-list-item .task-state-container{margin-inline-start:0}.onboarding-modal,.onboarding-view{--dialog-width: 800px;--dialog-max-width: 90vw;--dialog-max-height: 90vh;--onboarding-spacing: var(--size-4-4);--onboarding-border-radius: var(--radius-m);--onboarding-transition: all .2s ease-in-out}.onboarding-modal .modal-content,.onboarding-view .modal-content{background-color:var(--modal-background);border-radius:var(--modal-radius);max-width:var(--dialog-max-width);max-height:var(--dialog-max-height);height:90vh;display:flex;flex-direction:column;overflow:auto;position:relative;min-height:100px}.onboarding-view{height:100%;display:flex;flex-direction:column;background-color:var(--background-primary)}.onboarding-view .view-content{height:100%;display:flex;flex-direction:column}.onboarding-modal .onboarding-header,.onboarding-view .onboarding-header{padding:var(--onboarding-spacing) var(--onboarding-spacing) var(--size-4-2) var(--onboarding-spacing);text-align:center}.onboarding-modal .onboarding-subtitle,.onboarding-view .onboarding-subtitle{color:var(--text-muted);font-size:.95em;margin:0}.onboarding-modal .onboarding-content,.onboarding-view .onboarding-content{flex:1;padding:var(--onboarding-spacing);overflow-y:auto;min-height:0}.onboarding-modal .onboarding-footer,.onboarding-view .onboarding-footer{padding:var(--size-4-2) var(--onboarding-spacing) var(--onboarding-spacing) var(--onboarding-spacing);border-top:var(--modal-border-width) solid var(--background-modifier-border);flex-shrink:0}.onboarding-modal .onboarding-buttons,.onboarding-view .onboarding-buttons{display:flex;gap:var(--size-4-2);justify-content:space-between;align-items:center}.onboarding-modal .settings-check-section,.onboarding-view .settings-check-section{margin:var(--onboarding-spacing) 0}.onboarding-modal .changes-summary-list,.onboarding-view .changes-summary-list{list-style:none;padding:0;margin:var(--size-4-2) 0;background:var(--background-secondary);border-radius:var(--onboarding-border-radius);padding:var(--size-4-2)}.onboarding-modal .changes-summary-list li,.onboarding-view .changes-summary-list li{display:flex;align-items:center;gap:var(--size-4-2);padding:var(--size-2-1) 0;color:var(--text-normal);font-size:.9em}.onboarding-modal .change-check,.onboarding-view .change-check{color:var(--color-green);font-size:1.1em;display:flex}.onboarding-modal .change-text,.onboarding-view .change-text{flex:1}.onboarding-modal .onboarding-question,.onboarding-view .onboarding-question{margin:var(--onboarding-spacing) 0;text-align:center}.onboarding-modal .question-options,.onboarding-view .question-options{display:flex;gap:var(--size-4-3);justify-content:center;margin-top:var(--size-4-3)}.onboarding-modal .question-button,.onboarding-view .question-button{padding:var(--size-4-3) var(--size-4-4);border-radius:var(--button-radius);border:none;cursor:pointer;font-size:.9em;font-weight:500;transition:var(--onboarding-transition)}.onboarding-modal .question-options .mod-cta,.onboarding-view .question-options .mod-cta{background:var(--interactive-accent);color:var(--text-on-accent)}.onboarding-modal .question-options .mod-cta:hover,.onboarding-view .question-options .mod-cta:hover{background:var(--interactive-accent-hover)}.onboarding-modal .question-button:not(.mod-cta),.onboarding-view .question-button:not(.mod-cta){background:var(--background-secondary);color:var(--text-normal);border:1px solid var(--background-modifier-border)}.onboarding-modal .question-button:not(.mod-cta):hover,.onboarding-view .question-button:not(.mod-cta):hover{background:var(--background-modifier-hover)}.onboarding-modal .welcome-section,.onboarding-view .welcome-section{display:flex;flex-direction:column;gap:var(--onboarding-spacing)}.onboarding-modal .features-overview,.onboarding-view .features-overview{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:var(--size-4-3);margin:var(--onboarding-spacing) 0}.onboarding-modal .feature-item,.onboarding-view .feature-item{display:flex;gap:var(--size-4-2);padding:var(--size-4-3);background:var(--background-secondary);border-radius:var(--onboarding-border-radius)}.onboarding-modal .feature-icon,.onboarding-view .feature-icon{font-size:1.5em;flex-shrink:0;line-height:1}.onboarding-modal .setup-note,.onboarding-view .setup-note{text-align:center;padding:var(--size-4-3);background:var(--background-secondary);border-radius:var(--onboarding-border-radius)}.onboarding-modal .setup-description,.onboarding-view .setup-description{color:var(--text-muted);font-size:.95em;line-height:1.5;margin:0}.onboarding-modal .user-level-cards,.onboarding-view .user-level-cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(250px,1fr));gap:var(--onboarding-spacing);margin:var(--onboarding-spacing) 0}.onboarding-modal .user-level-card,.onboarding-view .user-level-card{border:1px solid var(--background-modifier-border);border-radius:var(--onboarding-border-radius);padding:var(--onboarding-spacing);cursor:pointer;transition:var(--onboarding-transition);background:var(--background-primary);position:relative;overflow:hidden}.onboarding-modal .user-level-card:hover,.onboarding-modal .user-level-card.card-hover,.onboarding-view .user-level-card.card-hover{border-color:var(--interactive-accent)}.onboarding-modal .user-level-card.selected,.onboarding-view .user-level-card.selected{border-color:var(--interactive-accent);background:var(--background-modifier-hover)}.user-level-card .card-header{display:flex;align-items:center;gap:var(--size-4-2);margin-bottom:var(--size-4-2)}.user-level-card .card-icon{font-size:1.8em;line-height:1;flex-shrink:0}.user-level-card .card-title{margin:0;color:var(--text-normal);font-size:1.2em;font-weight:600}.user-level-card .card-description{color:var(--text-muted);font-size:.9em;line-height:1.4;margin:0 0 var(--size-4-2) 0}.user-level-card .card-features{margin-top:var(--size-4-2)}.user-level-card .card-features ul{margin:0;padding-left:var(--size-4-3);list-style:none}.user-level-card .card-features li{position:relative;color:var(--text-muted);font-size:.85em;line-height:1.4;margin-bottom:var(--size-2-1)}.user-level-card .card-features li:before{content:"\2022";color:var(--interactive-accent);position:absolute;left:calc(-1 * var(--size-4-3));font-weight:bold}.user-level-card .recommendation-badge{position:absolute;top:var(--size-4-2);right:var(--size-4-2);background:var(--interactive-accent);color:var(--text-on-accent);padding:var(--size-2-1) var(--size-4-1);border-radius:var(--radius-s);font-size:.7em;font-weight:600;text-transform:uppercase;letter-spacing:.02em}.onboarding-modal .config-overview,.onboarding-view .config-overview{margin-bottom:var(--onboarding-spacing)}.onboarding-modal .mode-card,.onboarding-view .mode-card{display:flex;align-items:center;gap:var(--size-4-3);padding:var(--size-4-3);background:var(--background-secondary);border-radius:var(--onboarding-border-radius)}.onboarding-modal .mode-icon,.onboarding-view .mode-icon{--icon-size: var(--size-4-4);flex-shrink:0}.onboarding-modal .config-features,.onboarding-modal .config-views,.onboarding-modal .config-settings,.onboarding-view .config-settings{margin-bottom:var(--onboarding-spacing)}.onboarding-modal .enabled-features-list,.onboarding-view .enabled-features-list{list-style:none;padding:0;margin:0;background:var(--background-secondary);border-radius:var(--onboarding-border-radius);padding:var(--size-4-2)}.onboarding-modal .enabled-features-list li,.onboarding-view .enabled-features-list li{display:flex;align-items:center;gap:var(--size-4-2);padding:var(--size-2-1) 0;color:var(--text-normal);font-size:.9em}.onboarding-modal .feature-check,.onboarding-view .feature-check{color:var(--color-green);font-weight:bold}.onboarding-modal .views-grid,.onboarding-view .views-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:var(--size-4-2)}.onboarding-modal .view-item,.onboarding-view .view-item{display:flex;flex-direction:column;align-items:center;padding:var(--size-4-2);background:var(--background-secondary);border-radius:var(--onboarding-border-radius)}.onboarding-modal .view-icon,.onboarding-view .view-icon{font-size:1.2em;margin-bottom:var(--size-2-1)}.onboarding-modal .view-name,.onboarding-view .view-name{font-size:.8em;color:var(--text-muted);text-align:center}.onboarding-modal .settings-summary-list,.onboarding-view .settings-summary-list{list-style:none;padding:0;margin:0;background:var(--background-secondary);border-radius:var(--onboarding-border-radius);padding:var(--size-4-2)}.onboarding-modal .settings-summary-list li,.onboarding-view .settings-summary-list li{display:flex;justify-content:space-between;padding:var(--size-2-1) 0;font-size:.9em;border-bottom:1px solid var(--background-modifier-border)}.onboarding-modal .settings-summary-list li:last-child,.onboarding-view .settings-summary-list li:last-child{border-bottom:none}.onboarding-modal .setting-label,.onboarding-view .setting-label{color:var(--text-normal);font-weight:500}.onboarding-modal .setting-value,.onboarding-view .setting-value{color:var(--text-muted)}.onboarding-modal .config-options,.onboarding-view .config-options{margin-top:var(--onboarding-spacing)}.onboarding-modal .customization-note,.onboarding-view .customization-note{text-align:center;padding:var(--size-4-3);background:var(--background-secondary);border-radius:var(--onboarding-border-radius)}.onboarding-modal .note-text,.onboarding-view .note-text{color:var(--text-muted);font-size:.9em;margin:0;font-style:italic}.onboarding-modal .config-changes-summary,.onboarding-view .config-changes-summary{margin:var(--onboarding-spacing) 0;padding:var(--size-4-3);background:var(--background-secondary);border-radius:var(--onboarding-border-radius)}.onboarding-modal .preserved-views,.onboarding-modal .added-views,.onboarding-modal .updated-views,.onboarding-modal .settings-changes,.onboarding-view .settings-changes{margin:var(--size-4-2) 0;padding:var(--size-4-2);background:var(--background-primary);border-radius:var(--radius-s)}.onboarding-modal .preserved-header,.onboarding-view .preserved-header{display:flex;align-items:center;gap:var(--size-4-1);margin-bottom:var(--size-4-1)}.onboarding-modal .preserved-icon,.onboarding-view .preserved-icon{color:var(--color-green);font-size:1.1em}.onboarding-modal .preserved-text,.onboarding-modal .change-text,.onboarding-view .change-text{color:var(--text-normal);font-size:.9em;font-weight:500}.onboarding-view .updated-views,.onboarding-modal .updated-views{display:flex}.onboarding-modal .change-icon,.onboarding-view .change-icon{color:var(--interactive-accent);font-size:1.1em;margin-right:var(--size-4-1);display:flex}.onboarding-modal .preserved-views-list,.onboarding-modal .settings-changes-list,.onboarding-view .settings-changes-list{list-style:none;padding:0;margin:var(--size-4-1) 0 0 var(--size-4-4)}.onboarding-modal .preserved-views-list li,.onboarding-modal .settings-changes-list li,.onboarding-view .settings-changes-list li{display:flex;align-items:center;padding:var(--size-2-1) 0;color:var(--text-muted);font-size:.85em}.onboarding-modal .safety-note,.onboarding-view .safety-note{margin-top:var(--size-4-3);padding:var(--size-4-2);background:rgba(var(--color-blue-rgb),.1);border-radius:var(--radius-s);display:flex;align-items:center;gap:var(--size-4-1)}.onboarding-modal .safety-icon,.onboarding-view .safety-icon{color:var(--color-blue);font-size:1.1em;display:flex;justify-content:center;align-items:center}.onboarding-modal .safety-text,.onboarding-view .safety-text{color:var(--color-blue);font-size:var(--font-ui-smaller);font-weight:500}.onboarding-modal .task-guide-intro,.onboarding-view .task-guide-intro{margin-bottom:var(--onboarding-spacing)}.onboarding-modal .guide-description,.onboarding-view .guide-description{color:var(--text-muted);font-size:.95em;line-height:1.5;margin:0}.onboarding-modal .task-formats-section,.onboarding-modal .quick-capture-section,.onboarding-modal .practice-section,.onboarding-modal .shortcuts-section,.onboarding-view .shortcuts-section{margin-bottom:var(--onboarding-spacing)}.onboarding-modal .format-example,.onboarding-view .format-example{margin-top:var(--size-4-4);margin-bottom:var(--size-4-4)}.onboarding-modal .format-example code,.onboarding-view .format-example code{background:var(--background-primary);padding:var(--size-2-1) var(--size-4-1);border-radius:var(--radius-s);font-family:var(--font-monospace);font-size:.85em;color:var(--text-accent);border:1px solid var(--background-modifier-border);display:block;margin:var(--size-2-1) 0}.onboarding-modal .format-legend,.onboarding-modal .format-legend small,.onboarding-view .format-legend small{color:var(--text-faint);font-size:.8em;margin-top:var(--size-2-1);display:block}.onboarding-modal .status-markers,.onboarding-modal .metadata-symbols,.onboarding-view .metadata-symbols{margin-top:var(--size-4-2)}.onboarding-modal .status-list,.onboarding-view .status-list li,.onboarding-modal .symbols-list,.onboarding-view .symbols-list{list-style:none;margin:0;background:var(--background-primary);border-radius:var(--onboarding-border-radius)}.onboarding-modal .status-list li,.onboarding-view .status-list li,.onboarding-modal .symbols-list li,.onboarding-view .symbols-list li{display:flex;align-items:center;padding:var(--size-2-1) 0;font-size:.85em;color:var(--text-normal)}.onboarding-modal .status-list code,.onboarding-view .status-list code{background:var(--background-secondary);padding:var(--size-2-1) var(--size-4-1);border-radius:var(--radius-s);font-family:var(--font-monospace);margin-right:var(--size-4-2);min-width:40px;text-align:center}.onboarding-modal .demo-content,.onboarding-view .demo-content{padding:var(--size-4-3);background:var(--background-secondary);border-radius:var(--onboarding-border-radius)}.onboarding-modal .demo-button,.onboarding-view .demo-button{background:var(--interactive-accent);color:var(--text-on-accent);border:none;padding:var(--size-4-2) var(--size-4-4);border-radius:var(--button-radius);cursor:pointer;font-weight:500;transition:var(--onboarding-transition);margin-top:var(--size-4-2)}.onboarding-modal .demo-button:hover,.onboarding-view .demo-button:hover{background:var(--interactive-accent-hover)}.onboarding-modal .practice-feedback,.onboarding-view .practice-feedback{margin-top:var(--size-4-2)}.onboarding-modal .validation-message,.onboarding-view .validation-message{padding:var(--size-4-2);border-radius:var(--onboarding-border-radius);font-size:.9em;margin-bottom:var(--size-2-1)}.onboarding-modal .validation-success,.onboarding-view .validation-success{background:rgba(var(--color-green-rgb),.1);border:1px solid var(--color-green);color:var(--color-green)}.onboarding-modal .validation-error,.onboarding-view .validation-error{background:rgba(var(--color-red-rgb),.1);border:1px solid var(--color-red);color:var(--color-red)}.onboarding-modal .validation-warning,.onboarding-view .validation-warning{background:rgba(var(--color-orange-rgb),.1);border:1px solid var(--color-orange);color:var(--color-orange)}.onboarding-modal .validation-info,.onboarding-view .validation-info{background:rgba(var(--color-blue-rgb),.1);border:1px solid var(--color-blue);color:var(--color-blue)}.onboarding-modal .shortcuts-list,.onboarding-view .shortcuts-list{list-style:none;padding:0;margin:0;background:var(--background-secondary);border-radius:var(--onboarding-border-radius);padding:var(--size-4-2)}.onboarding-modal .shortcuts-list li,.onboarding-view .shortcuts-list li{display:flex;align-items:center;padding:var(--size-2-1) 0;font-size:.9em;color:var(--text-normal)}.onboarding-modal .shortcuts-list code,.onboarding-view .shortcuts-list code{background:var(--background-primary);padding:var(--size-2-1) var(--size-4-2);border-radius:var(--radius-s);font-family:var(--font-monospace);margin-right:var(--size-4-3);min-width:100px;font-size:.8em}.onboarding-modal .completion-success,.onboarding-view .completion-success{text-align:center;margin-bottom:var(--onboarding-spacing)}.onboarding-modal .success-icon,.onboarding-view .success-icon{font-size:3em;margin-bottom:var(--size-4-2)}.onboarding-modal .success-message,.onboarding-view .success-message{color:var(--text-muted);font-size:.95em;margin:0}.onboarding-modal .completion-summary,.onboarding-modal .quick-start-section,.onboarding-modal .next-steps-section,.onboarding-modal .resources-section,.onboarding-modal .feedback-section,.onboarding-view .feedback-section{margin-bottom:var(--onboarding-spacing)}.onboarding-modal .config-summary-card,.onboarding-view .config-summary-card{padding:var(--size-4-3);background:var(--background-secondary);border-radius:var(--onboarding-border-radius)}.onboarding-modal .config-header,.onboarding-view .config-header{display:flex;align-items:center;gap:var(--size-4-2);margin-bottom:var(--size-2-1)}.onboarding-modal .config-icon,.onboarding-view .config-icon{font-size:1.5em}.onboarding-modal .config-name,.onboarding-view .config-name{font-size:1.1em;font-weight:600;color:var(--text-normal)}.onboarding-modal .config-description,.onboarding-view .config-description{color:var(--text-muted);font-size:.9em;margin:0}.onboarding-modal .quick-start-steps,.onboarding-view .quick-start-steps{display:flex;flex-direction:column;gap:var(--size-4-2)}.onboarding-modal .quick-start-step,.onboarding-view .quick-start-step{display:flex;align-items:flex-start;gap:var(--size-4-3);padding:var(--size-4-2);background:var(--background-secondary);border-radius:var(--onboarding-border-radius)}.onboarding-modal .step-number,.onboarding-view .step-number{background:var(--interactive-accent);color:var(--text-on-accent);width:24px;height:24px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:.8em;font-weight:600;flex-shrink:0}.onboarding-modal .step-content,.onboarding-view .step-content{color:var(--text-normal);font-size:.9em;line-height:1.4}.onboarding-modal .next-steps-list,.onboarding-view .next-steps-list{list-style:none;padding:0;margin:0}.onboarding-modal .next-steps-list li,.onboarding-view .next-steps-list li{display:flex;align-items:flex-start;gap:var(--size-4-2);padding:var(--size-4-2);background:var(--background-secondary);border-radius:var(--onboarding-border-radius);margin-bottom:var(--size-2-1)}.onboarding-modal .step-check,.onboarding-view .step-check{color:var(--interactive-accent);font-weight:bold;flex-shrink:0}.onboarding-modal .step-text,.onboarding-view .step-text{color:var(--text-normal);font-size:.9em;line-height:1.4}.onboarding-modal .resources-list,.onboarding-view .resources-list{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:var(--size-4-2)}.onboarding-modal .resource-item,.onboarding-view .resource-item{display:flex;gap:var(--size-4-2);padding:var(--size-4-3);background:var(--background-secondary);border-radius:var(--onboarding-border-radius);transition:var(--onboarding-transition)}.onboarding-modal .resource-clickable,.onboarding-view .resource-clickable{cursor:pointer}.onboarding-modal .resource-clickable:hover,.onboarding-view .resource-clickable:hover{background:var(--background-modifier-hover)}.onboarding-modal .resource-icon,.onboarding-view .resource-icon{font-size:1.5em;flex-shrink:0}.onboarding-modal .feedback-description,.onboarding-view .feedback-description{color:var(--text-muted);font-size:.9em;line-height:1.5;margin:0 0 var(--size-4-2) 0}.onboarding-modal .feedback-buttons,.onboarding-view .feedback-buttons{display:flex;gap:var(--size-4-2);justify-content:center}.onboarding-modal .feedback-button,.onboarding-view .feedback-button{background:var(--background-secondary);border:none;color:var(--text-normal);padding:var(--size-4-2) var(--size-4-4);border-radius:var(--button-radius);cursor:pointer;font-size:.9em;transition:var(--onboarding-transition)}.onboarding-modal .feedback-positive:hover,.onboarding-view .feedback-positive:hover{background:var(--color-green);color:#fff}.onboarding-modal .feedback-negative:hover,.onboarding-view .feedback-negative:hover{background:var(--color-red);color:#fff}.onboarding-modal .feedback-thanks,.onboarding-view .feedback-thanks{text-align:center;padding:var(--size-4-3);background:var(--background-secondary);border-radius:var(--onboarding-border-radius)}.onboarding-modal .feedback-thanks-message,.onboarding-view .feedback-thanks-message{color:var(--text-normal);font-size:.9em;margin:0 0 var(--size-4-2) 0}.onboarding-modal .feedback-thanks a,.onboarding-view .feedback-thanks a{color:var(--interactive-accent);text-decoration:none}.onboarding-modal .feedback-thanks a:hover,.onboarding-view .feedback-thanks a:hover{text-decoration:underline}.onboarding-modal .final-message,.onboarding-view .final-message{text-align:center;padding:var(--size-4-4)}.onboarding-modal .final-message-text,.onboarding-view .final-message-text{color:var(--text-muted);font-size:1em;font-style:italic;margin:0}@media (max-width: 768px){.onboarding-modal,.onboarding-view{--dialog-width: 95vw;--dialog-max-width: 95vw;--dialog-max-height: 95vh}.onboarding-modal .user-level-cards,.onboarding-view .user-level-cards{grid-template-columns:1fr}.onboarding-modal .features-overview,.onboarding-view .features-overview{grid-template-columns:1fr}.onboarding-modal .views-grid,.onboarding-view .views-grid{grid-template-columns:repeat(auto-fit,minmax(100px,1fr))}.onboarding-modal .resources-list,.onboarding-view .resources-list{grid-template-columns:1fr}.onboarding-modal .feedback-buttons,.onboarding-view .feedback-buttons{flex-direction:column}.onboarding-modal .onboarding-buttons,.onboarding-view .onboarding-buttons{flex-wrap:wrap;justify-content:center}}.onboarding-modal .onboarding-content,.onboarding-view .onboarding-content{animation:fadeInUp .3s ease-out}@keyframes fadeInUp{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.onboarding-modal .user-level-card.selected,.onboarding-view .user-level-card.selected{animation:cardSelect .2s ease-out}@keyframes cardSelect{0%{transform:scale(1)}50%{transform:scale(1.02)}to{transform:scale(1)}}div[data-type^=tg-timeline-sidebar-view] .timeline-sidebar-container{display:flex;flex-direction:column;height:100%;width:100%;background-color:var(--background-primary);overflow:hidden;font-family:var(--font-interface);padding:0!important}div[data-type^=tg-timeline-sidebar-view] .timeline-header{display:flex;justify-content:space-between;align-items:center;padding:var(--size-4-3) var(--size-4-4);border-bottom:1px solid var(--background-modifier-border);background:linear-gradient(135deg,var(--background-secondary) 0%,var(--background-modifier-hover) 100%);flex-shrink:0;backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px)}div[data-type^=tg-timeline-sidebar-view] .timeline-title{font-weight:600;font-size:var(--font-ui-medium);color:var(--text-normal);display:flex;align-items:center;gap:var(--size-4-2)}div[data-type^=tg-timeline-sidebar-view] .timeline-controls{display:flex;gap:var(--size-4-2)}div[data-type^=tg-timeline-sidebar-view] .timeline-btn{display:flex;align-items:center;justify-content:center;width:var(--size-4-8);height:var(--size-4-8);border-radius:var(--radius-s);cursor:pointer;color:var(--text-muted);background-color:transparent;transition:all .2s ease}div[data-type^=tg-timeline-sidebar-view] .timeline-btn:hover{color:var(--text-normal);background-color:var(--background-modifier-hover)}div[data-type^=tg-timeline-sidebar-view] .timeline-btn.is-active{color:var(--text-on-accent);background-color:var(--interactive-accent)}div[data-type^=tg-timeline-sidebar-view] .timeline-content{flex:1;overflow-y:auto;padding:var(--size-4-2) 0;position:relative}div[data-type^=tg-timeline-sidebar-view] .timeline-content.focus-mode .timeline-date-group:not(.is-today){opacity:.3;pointer-events:none}div[data-type^=tg-timeline-sidebar-view] .timeline-empty{display:flex;align-items:center;justify-content:center;height:100%;color:var(--text-muted);font-style:italic;text-align:center;padding:var(--size-4-8)}div[data-type^=tg-timeline-sidebar-view] .timeline-date-group{margin-bottom:var(--size-4-2);position:relative;border-radius:var(--radius-m);transition:all .3s ease}div[data-type^=tg-timeline-sidebar-view] .timeline-date-group.is-today{background-color:var(--background-secondary);border-radius:var(--radius-m);margin:0 var(--size-4-2) var(--size-4-2);padding:var(--size-4-2);box-shadow:0 2px 8px #0000001a;border:1px solid var(--interactive-accent)}div[data-type^=tg-timeline-sidebar-view] .timeline-date-header{display:flex;align-items:center;justify-content:space-between;padding:var(--size-4-2) var(--size-4-4);font-weight:600;font-size:var(--font-ui-small);color:var(--text-accent);border-bottom:1px solid var(--background-modifier-border);margin-bottom:var(--size-4-2);position:sticky;top:0;background-color:var(--background-primary);z-index:1}div[data-type^=tg-timeline-sidebar-view] .timeline-date-group.is-today .timeline-date-header{border-radius:var(--radius-s);margin:0 0 var(--size-4-2) 0}div[data-type^=tg-timeline-sidebar-view] .timeline-date-relative{font-size:var(--font-ui-smaller);color:var(--text-muted);font-weight:normal}div[data-type^=tg-timeline-sidebar-view] .timeline-events-list{display:flex;flex-direction:column;gap:var(--size-2-1);padding:0 var(--size-2-3)}div[data-type^=tg-timeline-sidebar-view] .timeline-event{display:flex;align-items:flex-start;gap:var(--size-4-3);padding:var(--size-4-3);border-radius:var(--radius-m);cursor:pointer;position:relative;border:1px solid transparent;margin-bottom:var(--size-4-2)}div[data-type^=tg-timeline-sidebar-view] .timeline-event:hover{background-color:var(--background-modifier-hover);border-color:var(--interactive-accent);box-shadow:0 2px 8px #0000000d;transform:translateY(-1px)}div[data-type^=tg-timeline-sidebar-view] .timeline-event:hover:has(.timeline-event-checkbox:hover){transform:none}div[data-type^=tg-timeline-sidebar-view] .timeline-event.is-completed{opacity:.6}div[data-type^=tg-timeline-sidebar-view] .timeline-event.is-completed .timeline-event-text{text-decoration:line-through;color:var(--text-muted)}div[data-type^=tg-timeline-sidebar-view] .timeline-event-time{font-size:var(--font-ui-smaller);color:var(--text-muted);font-family:var(--font-monospace);min-width:45px;text-align:center;margin-top:2px;flex-shrink:0;background-color:var(--background-modifier-border);border-radius:var(--radius-s);padding:var(--size-4-1) var(--size-4-2);font-weight:500}div[data-type^=tg-timeline-sidebar-view] .timeline-event-content{flex:1;display:flex;align-items:flex-start;gap:var(--size-4-2);min-width:0}div[data-type^=tg-timeline-sidebar-view] .timeline-event-checkbox{display:flex;align-items:center;margin-top:2px}div[data-type^=tg-timeline-sidebar-view] .timeline-event-checkbox input[type=checkbox]{margin:0;cursor:pointer}div[data-type^=tg-timeline-sidebar-view] .timeline-event-text{flex:1;font-size:var(--font-ui-small);line-height:1.4;word-wrap:break-word;color:var(--text-normal);display:flex;align-items:flex-start;gap:var(--size-4-2)}div[data-type^=tg-timeline-sidebar-view] .timeline-event-icon{font-size:var(--font-ui-medium);flex-shrink:0;margin-top:1px}div[data-type^=tg-timeline-sidebar-view] .timeline-event-content-text{flex:1;word-break:break-word}div[data-type^=tg-timeline-sidebar-view] .timeline-event-actions{display:flex;gap:var(--size-4-1);opacity:0;transition:opacity .2s ease}div[data-type^=tg-timeline-sidebar-view] .timeline-event:hover .timeline-event-actions{opacity:1}div[data-type^=tg-timeline-sidebar-view] .timeline-event-action{display:flex;align-items:center;justify-content:center;width:var(--size-4-6);height:var(--size-4-6);border-radius:var(--radius-s);cursor:pointer;color:var(--text-muted);background-color:transparent;transition:all .2s ease}div[data-type^=tg-timeline-sidebar-view] .timeline-event-action:hover{color:var(--text-normal);background-color:var(--background-modifier-border)}div[data-type^=tg-timeline-sidebar-view] .timeline-quick-input{flex-shrink:0;border-top:1px solid var(--background-modifier-border);background-color:var(--background-secondary);padding:var(--size-4-4);display:flex;flex-direction:column;gap:var(--size-4-3);padding-bottom:var(--size-4-12);position:relative;transition:all .3s cubic-bezier(.4,0,.2,1);overflow:hidden}div[data-type^=tg-timeline-sidebar-view] .timeline-quick-input.is-collapsed{padding:0;gap:0;height:auto}div[data-type^=tg-timeline-sidebar-view] .timeline-quick-input.is-collapsed .quick-input-header,div[data-type^=tg-timeline-sidebar-view] .timeline-quick-input.is-collapsed .quick-input-editor,div[data-type^=tg-timeline-sidebar-view] .timeline-quick-input.is-collapsed .quick-input-actions{display:none}div[data-type^=tg-timeline-sidebar-view] .timeline-quick-input.is-collapsing{overflow:hidden}div[data-type^=tg-timeline-sidebar-view] .timeline-quick-input.is-expanding{overflow:hidden}div[data-type^=tg-timeline-sidebar-view] .quick-input-header-collapsed{display:flex;align-items:center;justify-content:space-between;padding:var(--size-4-3) var(--size-4-4);background-color:var(--background-secondary);border-bottom:1px solid var(--background-modifier-border);cursor:pointer;transition:background-color .2s ease}div[data-type^=tg-timeline-sidebar-view] .quick-input-header-collapsed:hover{background-color:var(--background-modifier-hover)}div[data-type^=tg-timeline-sidebar-view] .collapsed-expand-btn{display:flex;align-items:center;justify-content:center;width:var(--size-4-6);height:var(--size-4-6);border-radius:var(--radius-s);color:var(--text-muted);transition:all .2s ease;cursor:pointer}div[data-type^=tg-timeline-sidebar-view] .collapsed-expand-btn:hover{color:var(--text-normal);background-color:var(--background-modifier-border)}div[data-type^=tg-timeline-sidebar-view] .collapsed-title{flex:1;font-weight:600;font-size:var(--font-ui-small);color:var(--text-normal);margin-left:var(--size-4-2)}div[data-type^=tg-timeline-sidebar-view] .collapsed-quick-actions{display:flex;gap:var(--size-4-2)}div[data-type^=tg-timeline-sidebar-view] .collapsed-quick-capture,div[data-type^=tg-timeline-sidebar-view] .collapsed-more-options{display:flex;align-items:center;justify-content:center;width:var(--size-4-7);height:var(--size-4-7);border-radius:var(--radius-s);color:var(--text-muted);cursor:pointer;transition:all .2s ease}div[data-type^=tg-timeline-sidebar-view] .collapsed-quick-capture:hover,div[data-type^=tg-timeline-sidebar-view] .collapsed-more-options:hover{color:var(--text-normal);background-color:var(--background-modifier-border)}div[data-type^=tg-timeline-sidebar-view] .collapsed-quick-capture:hover{color:var(--interactive-accent)}div[data-type^=tg-timeline-sidebar-view] .quick-input-header{display:flex;justify-content:space-between;align-items:flex-start;gap:var(--size-4-2);margin-bottom:var(--size-4-2)}div[data-type^=tg-timeline-sidebar-view] .quick-input-header-left{display:flex;align-items:center;gap:var(--size-4-2)}div[data-type^=tg-timeline-sidebar-view] .quick-input-collapse-btn{display:flex;align-items:center;justify-content:center;width:var(--size-4-6);height:var(--size-4-6);border-radius:var(--radius-s);color:var(--text-muted);cursor:pointer;transition:all .2s ease}div[data-type^=tg-timeline-sidebar-view] .quick-input-collapse-btn:hover{color:var(--text-normal);background-color:var(--background-modifier-border)}div[data-type^=tg-timeline-sidebar-view] .quick-input-collapse-btn svg{transition:transform .2s ease}div[data-type^=tg-timeline-sidebar-view] .timeline-quick-input.is-collapsed .quick-input-collapse-btn svg{transform:rotate(-90deg)}div[data-type^=tg-timeline-sidebar-view] .quick-input-title{font-weight:600;font-size:var(--font-ui-small);color:var(--text-normal)}div[data-type^=tg-timeline-sidebar-view] .quick-input-target-info{font-size:var(--font-ui-smaller);color:var(--text-muted);font-style:italic;padding:var(--size-4-1) var(--size-4-2);background-color:var(--background-modifier-hover);border-radius:var(--radius-s);word-break:break-all}div[data-type^=tg-timeline-sidebar-view] .quick-input-editor{min-height:80px;border:2px solid var(--background-modifier-border);border-radius:var(--radius-m);background-color:var(--background-primary);padding:var(--size-4-3);font-family:var(--font-text);font-size:var(--font-ui-small);resize:vertical;transition:all .3s ease}div[data-type^=tg-timeline-sidebar-view] .quick-input-editor:focus-within{border-color:var(--interactive-accent);box-shadow:0 0 0 2px rgba(var(--interactive-accent-rgb),.2)}div[data-type^=tg-timeline-sidebar-view] .quick-input-editor .cm-editor{background-color:transparent;border:none;outline:none}div[data-type^=tg-timeline-sidebar-view] .quick-input-editor .cm-focused{outline:none}div[data-type^=tg-timeline-sidebar-view] .quick-input-editor .cm-editor.cm-focused{outline:none}div[data-type^=tg-timeline-sidebar-view] .quick-input-actions{display:flex;gap:var(--size-4-2);justify-content:flex-end}div[data-type^=tg-timeline-sidebar-view] .quick-capture-btn,div[data-type^=tg-timeline-sidebar-view] .quick-modal-btn{padding:var(--size-4-3) var(--size-4-6);border-radius:var(--radius-m);font-size:var(--font-ui-small);font-weight:500;cursor:pointer;border:none;transition:all .3s ease;box-shadow:0 2px 4px #0000001a}div[data-type^=tg-timeline-sidebar-view] .quick-capture-btn{background-color:var(--interactive-accent);color:var(--text-on-accent)}div[data-type^=tg-timeline-sidebar-view] .quick-capture-btn:hover{background-color:var(--interactive-accent-hover);transform:translateY(-1px);box-shadow:0 4px 8px #00000026}div[data-type^=tg-timeline-sidebar-view] .quick-modal-btn{background-color:var(--background-modifier-border);color:var(--text-normal)}div[data-type^=tg-timeline-sidebar-view] .quick-modal-btn:hover{background-color:var(--background-modifier-border-hover);transform:translateY(-1px);box-shadow:0 4px 8px #00000026}@media (max-width: 768px){div[data-type^=tg-timeline-sidebar-view] .timeline-header{padding:var(--size-4-2) var(--size-4-3)}div[data-type^=tg-timeline-sidebar-view] .timeline-controls{gap:var(--size-4-1)}div[data-type^=tg-timeline-sidebar-view] .timeline-btn{width:var(--size-4-7);height:var(--size-4-7)}div[data-type^=tg-timeline-sidebar-view] .timeline-events-list{padding:0 var(--size-2-3)}div[data-type^=tg-timeline-sidebar-view] .timeline-event{padding:var(--size-4-2)}div[data-type^=tg-timeline-sidebar-view] .timeline-quick-input{padding:var(--size-4-3)}div[data-type^=tg-timeline-sidebar-view] .timeline-quick-input.is-collapsed{padding:0}div[data-type^=tg-timeline-sidebar-view] .quick-input-editor{min-height:60px}div[data-type^=tg-timeline-sidebar-view] .quick-input-header-collapsed{padding:var(--size-4-2) var(--size-4-3)}div[data-type^=tg-timeline-sidebar-view] .collapsed-quick-capture,div[data-type^=tg-timeline-sidebar-view] .collapsed-more-options{width:var(--size-4-6);height:var(--size-4-6)}}div[data-type^=tg-timeline-sidebar-view] .timeline-content::-webkit-scrollbar{width:6px}div[data-type^=tg-timeline-sidebar-view] .timeline-content::-webkit-scrollbar-track{background-color:var(--background-secondary)}div[data-type^=tg-timeline-sidebar-view] .timeline-content::-webkit-scrollbar-thumb{background-color:var(--background-modifier-border);border-radius:3px}div[data-type^=tg-timeline-sidebar-view] .timeline-content::-webkit-scrollbar-thumb:hover{background-color:var(--background-modifier-border-hover)}@keyframes fadeIn{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}div[data-type^=tg-timeline-sidebar-view] .timeline-content.focus-mode{position:relative}div[data-type^=tg-timeline-sidebar-view] .timeline-content.focus-mode:before{content:"";position:absolute;top:0;left:0;right:0;bottom:0;background:linear-gradient(to bottom,rgba(var(--background-primary-rgb),.9) 0%,rgba(var(--background-primary-rgb),.7) 50%,rgba(var(--background-primary-rgb),.9) 100%);pointer-events:none;z-index:0}div[data-type^=tg-timeline-sidebar-view] .timeline-content.focus-mode .timeline-date-group.is-today{position:relative;z-index:1}div[data-type^=tg-timeline-sidebar-view] .timeline-event-content-text .markdown-block{margin:0;padding:0;font-size:inherit;line-height:inherit}div[data-type^=tg-timeline-sidebar-view] .timeline-event-content-text .markdown-block p{margin:0;padding:0;font-size:inherit;line-height:inherit}div[data-type^=tg-timeline-sidebar-view] .timeline-event-content-text .markdown-block strong,div[data-type^=tg-timeline-sidebar-view] .timeline-event-content-text .markdown-block em,div[data-type^=tg-timeline-sidebar-view] .timeline-event-content-text .markdown-block code{font-size:inherit}div[data-type^=tg-timeline-sidebar-view] .timeline-event-content-text .markdown-block a{color:var(--link-color);text-decoration:none}div[data-type^=tg-timeline-sidebar-view] .timeline-event-content-text .markdown-block a:hover{text-decoration:underline}div[data-type^=tg-timeline-sidebar-view] .timeline-event-content-text .markdown-block ul,div[data-type^=tg-timeline-sidebar-view] .timeline-event-content-text .markdown-block ol{margin:0;padding-left:var(--size-4-4)}div[data-type^=tg-timeline-sidebar-view] .timeline-event-content-text .markdown-block li{margin:0;padding:0}.reward-modal-content{text-align:center}.reward-modal .modal-title{text-align:center}.reward-name{font-size:1.2em;font-weight:bold;margin-bottom:15px}.reward-image-container{margin-bottom:20px;display:flex;justify-content:center;align-items:center}.reward-image{max-width:80%;max-height:300px;border-radius:8px;box-shadow:0 2px 4px #0000001a}.reward-image-error{font-style:italic;color:var(--text-muted)}.reward-spacer{height:20px}.task-genius-reward-modal .setting-item-control{display:flex;justify-content:center;gap:10px}.markdown-source-view.mod-cm6 .cm-gutters.task-gutter{margin-inline-end:0!important;margin-inline-start:var(--file-folding-offset)}.is-mobile .markdown-source-view.mod-cm6 .cm-gutters.task-gutter{margin-inline-start:0!important}.task-details-popover.tg-menu{z-index:20;position:fixed;background-color:var(--background-primary);border:1px solid var(--background-modifier-border);border-radius:var(--radius-s);padding:var(--size-4-3);box-shadow:var(--shadow-l)}.task-gutter{width:26px}.task-gutter-marker{cursor:pointer;font-size:var(--font-smaller);opacity:.1;transition:opacity .2s ease}.task-gutter-marker:hover{opacity:1}.task-popover-content{padding:var(--size-4-3);max-width:300px;max-height:400px;overflow:auto}.task-metadata-editor{display:flex;flex-direction:column;gap:var(--size-4-2);padding:var(--size-2-2);height:100%}.field-container{display:flex;flex-direction:column;margin-bottom:var(--size-2-2)}.field-label{font-size:var(--font-smallest);font-weight:var(--font-bold);margin-bottom:var(--size-2-1);color:var(--text-muted)}.action-buttons{display:flex;justify-content:space-between;margin-top:var(--size-4-2);gap:var(--size-4-2)}.action-button{padding:var(--size-2-2) var(--size-4-2);font-size:var(--font-smallest);border-radius:var(--radius-s);cursor:pointer}.task-gutter-marker.clickable-icon{width:24px;padding:var(--size-2-1);display:flex;justify-content:center;align-items:center}.task-details-popover .tabs-main-container{display:flex;flex-direction:column;width:100%}.task-details-popover .tabs-navigation{display:flex;margin-bottom:var(--size-4-2);gap:var(--size-4-2)}.task-details-popover .tab-button{padding:var(--size-2-2) var(--size-4-2);cursor:pointer;border:none;background:none;font-size:var(--font-ui-small);color:var(--text-muted);margin-bottom:-1px;transition:color .2s ease,border-color .2s ease}.task-details-popover .tab-button:hover{color:var(--text-normal)}.task-details-popover .tab-button.active{color:var(--text-on-accent);font-weight:var(--font-bold);background-color:var(--interactive-accent)}.task-details-popover .tab-pane{display:none;flex-direction:column;gap:var(--size-4-2)}.task-details-popover .tab-pane.active{display:flex}.task-details-popover .details-status-selector,.task-status-editor .details-status-selector{display:flex;flex-direction:row;justify-content:space-between;margin-bottom:var(--size-4-2);margin-top:var(--size-4-2)}.task-details-popover .quick-capture-status-selector,.task-status-editor .quick-capture-status-selector{display:flex;flex-direction:row;justify-content:space-between;gap:var(--size-4-3)}.task-details-popover .quick-capture-status-selector-label,.task-status-editor .quick-capture-status-selector-label{display:none}.modal-content.task-metadata-editor{display:flex;flex-direction:column;gap:var(--size-4-2)}.metadata-full-container{display:flex;flex-direction:column;gap:var(--size-4-2)}.metadata-full-container .dates-container{display:flex;flex-direction:column;gap:var(--size-4-2)}.internal-embed .task-genius-container{max-height:800px}.internal-embed .task-genius-container .task-sidebar{width:44px;min-width:44px;overflow:hidden}.internal-embed .task-genius-container .task-sidebar .sidebar-nav{align-items:center}.internal-embed .task-genius-container .task-sidebar .sidebar-nav-item{padding:8px 10px;justify-content:center;width:var(--size-4-9);flex-shrink:0;transition:width .3s ease-in-out,flex-shrink .3s ease-in-out}.internal-embed .task-genius-container .task-sidebar .nav-item-icon{margin-right:0}.internal-embed .task-genius-container .task-list{max-height:800px}.internal-embed .projects-container{flex:1;height:auto}.internal-embed .forecast-left-column{width:240px}.internal-embed .forecast-left-column .mini-calendar-container .calendar-grid{display:grid;grid-template-columns:repeat(7,1fr);gap:1px;padding:0 5px}.internal-embed .forecast-left-column .mini-calendar-container .calendar-day-header{text-align:center;font-size:.7em;color:var(--text-muted);padding:3px 0;border-bottom:1px solid var(--background-modifier-border);margin-bottom:3px}.internal-embed .forecast-left-column .mini-calendar-container .calendar-day-header.calendar-weekend{color:var(--text-accent)}.internal-embed .forecast-left-column .mini-calendar-container .calendar-day{aspect-ratio:1;border-radius:3px;padding:1px;cursor:pointer;position:relative;display:flex;flex-direction:column;transition:background-color .2s ease}.internal-embed .forecast-left-column .mini-calendar-container .calendar-day:hover{background-color:var(--background-modifier-hover)}.internal-embed .forecast-left-column .mini-calendar-container .calendar-day.selected{background-color:var(--background-modifier-border-hover)}.internal-embed .forecast-left-column .mini-calendar-container .calendar-day.today{background-color:var(--interactive-accent-hover);color:var(--text-on-accent)}.internal-embed .forecast-left-column .mini-calendar-container .calendar-day.past-due{color:var(--text-error)}.internal-embed .forecast-left-column .mini-calendar-container .calendar-day.other-month{opacity:.5}.internal-embed .forecast-left-column .mini-calendar-container .calendar-day-number{text-align:center;font-size:.75em;font-weight:500;padding:1px}.internal-embed .forecast-left-column .mini-calendar-container .calendar-day-count{background-color:var(--background-modifier-border);color:var(--text-normal);border-radius:8px;font-size:.6em;padding:1px 3px;margin:1px auto;text-align:center;width:fit-content}.internal-embed .forecast-left-column .mini-calendar-container .calendar-day-count.has-priority{background-color:var(--text-accent);color:var(--text-on-accent)}.internal-embed .tags-container{height:auto;max-height:100%}.internal-embed .task-genius-container:has(.task-details.visible) .tags-left-column{display:none}.internal-embed .task-genius-container:has(.task-details.visible) .projects-left-column{display:none}.internal-embed .full-calendar-container{height:auto}.internal-embed .tg-kanban-view{height:auto}.bases-view.task-genius-container{border-top:unset}.bases-update-error-notification{position:fixed;top:20px;right:20px;background:var(--background-modifier-error);border:1px solid var(--background-modifier-border);border-radius:6px;padding:12px 16px;max-width:400px;box-shadow:var(--shadow-s);z-index:1000;cursor:pointer;animation:slideInRight .3s ease-out}.bases-update-error-notification:hover{opacity:.8}.bases-update-error-notification .error-icon{font-size:16px;margin-bottom:8px}.bases-update-error-notification .error-message .error-title{font-weight:600;color:var(--text-error);margin-bottom:4px}.bases-update-error-notification .error-message .error-details{font-size:12px;color:var(--text-muted);line-height:1.4}@keyframes slideInRight{0%{transform:translate(100%);opacity:0}to{transform:translate(0);opacity:1}} diff --git a/versions.json b/versions.json index 6ee22c3a..989fb515 100644 --- a/versions.json +++ b/versions.json @@ -100,5 +100,6 @@ "9.1.2": "0.15.2", "9.1.3": "0.15.2", "9.1.4": "0.15.2", - "9.1.5": "0.15.2" + "9.1.5": "0.15.2", + "9.2.0": "0.15.2" } \ No newline at end of file