Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
43bf4fd
fix(windows): restore quick-add draft when parsing or saving fails
turinglambdaai Oct 20, 2026
a9043d0
fix(windows): require an explicit target list for every quick-add task
turinglambdaai Oct 20, 2026
4c3d840
fix(windows): route quick add to selected list and align task checkbo…
turinglambdaai Oct 20, 2026
1f25649
test(windows): give new-list action a stable accessibility ID
turinglambdaai Oct 20, 2026
b159866
test(windows): expose stable automation selector for list-name prompt
turinglambdaai Oct 20, 2026
b0a044f
test(windows): expose active view title for list-routing regression
turinglambdaai Oct 20, 2026
4801779
test(windows): reproduce wrong-list Enter save and plain-row checkbox…
turinglambdaai Oct 20, 2026
7ed7b5d
docs: require correct list ownership and aligned plain task rows on e…
turinglambdaai Oct 20, 2026
93734e1
feat(windows): declare task move-to-list operation
turinglambdaai Oct 20, 2026
61f9e56
feat(windows): allow moving existing tasks between lists from context…
turinglambdaai Oct 20, 2026
2aa3ae2
docs: require explicit recovery of legacy misfiled Windows tasks
turinglambdaai Oct 20, 2026
73307d4
fix(windows): avoid showing All tasks under restored list heading
turinglambdaai Oct 20, 2026
108aec1
test(windows): find visible list text instead of inaccessible Grid IDs
turinglambdaai Oct 20, 2026
42e4924
fix(windows): expose list names through accessible TextBlock peers
turinglambdaai Oct 20, 2026
d8d6a33
fix(windows): expose unified task lookup for search actions
turinglambdaai Oct 20, 2026
ba1d5ec
fix(windows): allow editing searched tasks and show owning list
turinglambdaai Oct 20, 2026
ede8a04
fix(windows): refresh task search after edits
turinglambdaai Oct 20, 2026
f2837ed
fix(windows): keep search results actionable and fresh
turinglambdaai Oct 20, 2026
fd2144c
test(windows): expose task search automation selector
turinglambdaai Oct 20, 2026
c6932cd
test(windows): verify global search displays task ownership
turinglambdaai Oct 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions docs/UX-BENCHMARKS.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,15 @@ Scheduled(有日期的)。
https://digitoolbook.com/en/blog/things3-minimalist-task-management-gtd
- 2Do 官方功能页:https://www.2doapp.com/page-section/main-features
- 2Do Focus Lists:https://www.2doapp.com/docs/ios/focus-lists

### Windows task ownership recovery

- ✅ Task rows expose a right-click **Move to List / 移动到列表** submenu for
other lists. It calls `update_task` with the target list ID, preserving
title, due date/time, completion state and notes.
- This is the **explicit** recovery path for tasks already saved into the
wrong list by older Windows versions. Do not auto-migrate existing rows:
the original intended list cannot be inferred reliably.
- Manual acceptance: move a task from 工作 to 生活, confirm it disappears from
工作, appears in 生活 and keeps all other fields; then restart and verify
ownership persists. Exercise both languages and all smart-list views.
110 changes: 109 additions & 1 deletion scripts/e2e/e2e-windows.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,110 @@ try {
return $false
})

# Regression: a task entered in 生活 must stay in 生活, not the
# database's first/default list 工作. These are the shipped default
# list names, from racket/taskly/model.rkt. The disposable acceptance
# profile guarantees default-list id 1 and the new 生活 list id 2.
$createList = Wait-Until 'create-list control' { Find-ById $window 'create-list' }
$createList.GetCurrentPattern(
[System.Windows.Automation.InvokePattern]::Pattern).Invoke()
$nameInput = Wait-Until 'new-list input' { Find-ById $window 'prompt-input' }
$nameInput.GetCurrentPattern(
[System.Windows.Automation.ValuePattern]::Pattern).SetValue('生活')
$confirm = Wait-Until 'new-list confirmation' { Find-ByName $window 'OK' }
$confirm.GetCurrentPattern(
[System.Windows.Automation.InvokePattern]::Pattern).Invoke()

function Select-TaskList {
param([int]$ListId, [string]$Name)
# WinUI's inner Grid is not a ControlView automation peer (verified
# against the failing packaged Windows screenshot). Resolve the
# displayed TextBlock instead, then select its ListViewItem parent.
$label = Wait-Until ("visible list: {0}" -f $Name) {
$found = Find-ById $window ("list-name-{0}" -f $ListId)
if ($null -eq $found) { $found = Find-ByName $window $Name }
return $found
}
$item = $label
$walker = [System.Windows.Automation.TreeWalker]::ControlViewWalker
while ($null -ne $item -and
$item.Current.ControlType -ne [System.Windows.Automation.ControlType]::ListItem) {
$item = $walker.GetParent($item)
}
if ($null -eq $item) { throw ("no selectable list item for {0}" -f $Name) }
$item.GetCurrentPattern(
[System.Windows.Automation.SelectionItemPattern]::Pattern).Select()
}

$paneTitle = Wait-Until 'active view label' {
Find-ById $window 'current-view-title'
}
Select-TaskList 2 '生活'
[void](Wait-Until '生活 view selected' {
if ($paneTitle.Current.Name -eq '生活') { return $true }
return $false
})
$plainTask = 'Life-only task without due date or notes'
$quickAdd.GetCurrentPattern(
[System.Windows.Automation.ValuePattern]::Pattern).SetValue($plainTask)
$quickAdd.SetFocus()
# Exercise the actual Enter key, not only the plus-button click.
[System.Windows.Forms.SendKeys]::SendWait('{ENTER}')

$titleText = Wait-Until 'task saved under 生活' {
Find-ByName $window $plainTask
}
# Layout assertions use the text and native checkbox peers: a Grid
# itself is not reliably exposed in WinUI UIA's ControlView.
$plainCheck = Wait-Until 'new task checkbox' {
Find-ById $window 'task-complete-2'
}
# Both elements share the row's first Grid row; the title must
# visually align with the native checkbox for a metadata-free task.
$checkBoxBounds = $plainCheck.Current.BoundingRectangle
$titleBounds = $titleText.Current.BoundingRectangle
$centerDiff = [Math]::Abs(
($checkBoxBounds.Top + $checkBoxBounds.Height / 2) -
($titleBounds.Top + $titleBounds.Height / 2))
$limit = [Math]::Max(8.0, $checkBoxBounds.Height / 4)
if ($centerDiff -gt $limit) {
throw ("plain task title and checkbox are misaligned by {0:N1}px" -f $centerDiff)
}

Select-TaskList 1 '工作'
[void](Wait-Until '工作 view selected' {
if ($paneTitle.Current.Name -eq '工作') { return $true }
return $false
})
[void](Wait-Until '生活 task absent from 工作' {
if ($null -eq (Find-ByName $window $plainTask)) { return $true }
return $false
})
Select-TaskList 2 '生活'
[void](Wait-Until '生活 task remains in 生活' {
if ($paneTitle.Current.Name -eq '生活' -and
$null -ne (Find-ByName $window $plainTask)) { return $true }
return $false
})

# Search is global, even while 生活 is selected. A result must
# display its owning list, never appear as an unlabeled local row.
$searchBox = Wait-Until 'search box' { Find-ById $window 'task-search' }
$searchBox.GetCurrentPattern(
[System.Windows.Automation.ValuePattern]::Pattern).SetValue($plainTask)
$owner = Wait-Until 'search result list owner' {
Find-ById $window 'task-owner-2'
}
if ($owner.Current.Name -ne '生活') {
throw ("search result claims wrong owner: {0}" -f $owner.Current.Name)
}
$searchBox.GetCurrentPattern(
[System.Windows.Automation.ValuePattern]::Pattern).SetValue('')
[void](Wait-Until 'search cleared' {
if ($null -eq (Find-ById $window 'task-owner-2')) { return $true }
return $false
})

$nodes = $window.FindAll(
[System.Windows.Automation.TreeScope]::Descendants,
[System.Windows.Automation.Condition]::TrueCondition)
Expand All @@ -127,12 +231,16 @@ try {
[ordered]@{
platform = 'windows'
artifact = $executable.FullName
actions = @('set:quick-add', 'invoke:quick-add-submit', 'toggle:task-complete-1')
actions = @('set:quick-add', 'invoke:quick-add-submit', 'toggle:task-complete-1', 'create-list:生活', 'select-list:2', 'enter:quick-add', 'select-list:1', 'select-list:2')
assertions = @(
'packaged WinUI window activated',
'stable UI Automation identifiers exposed',
'RPC-created task appeared',
'changed Event removed the completed task from the active view',
'new task submitted by Enter is owned by 生活, not 工作',
'task checkbox and plain-text title are vertically aligned',
'view switches preserve task list ownership',
'global search shows the real owning list for every task',
'native window close stopped the process'
)
} | ConvertTo-Json -Depth 5 |
Expand Down
11 changes: 11 additions & 0 deletions shared/spec/PRODUCT-SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,13 @@ README's honest gaps).
text first runs through ExtractTimeCommand (trailing `@10am`, `+1d`…),
target list = selected list else first list (none → error
`taskCreateListFirst`); new task lands at top; counts refresh.
The host captures the **concrete list ID at submission**, before any
asynchronous parsing or persistence. Switching lists while the request
is running must never change the destination. No list / invalid selected
list must keep the unsent text rather than silently using another list.
The Windows packaged acceptance must create a second list, submit a
task via Enter, verify that it remains in the selected list, and verify
that it never appears in the first/default list.
- Empty states: not connected → 📂 + `taskListEmptyHint`; connected and
empty → ✓ + `taskListEmpty`.
- Row order per view (SQL in DATA-FORMAT/CLI-SPEC §list): All & list views
Expand All @@ -64,6 +71,10 @@ README's honest gaps).

- 20px circular checkbox: incomplete = hollow ring; complete = ring +
check in accent; completed text gets strikethrough + tertiary color.
The checkbox and task title share the **same first-row vertical center**
on every platform. Due date, time and notes (only when actually present)
sit beneath the title, not beside or vertically above the checkbox.
A task with no metadata renders as one clean centered checkbox/title row.
- Meta line under the text when present: the localized due date
(today/tomorrow/yesterday/`Aug 31`/`8月31日`, plus the time when set) in
its semantic color (overdue incomplete `#FF3B30`, due today accent,
Expand Down
3 changes: 3 additions & 0 deletions windows/MainWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@
Foreground="{ThemeResource TasklySecondaryTextBrush}" />
<Button
x:Name="NewListButton"
AutomationProperties.AutomationId="create-list"
Grid.Column="1"
Padding="6,2"
Background="Transparent"
Expand Down Expand Up @@ -303,6 +304,7 @@
Margin="10,0,12,0">
<TextBlock
x:Name="PaneTitle"
AutomationProperties.AutomationId="current-view-title"
FontSize="22"
FontWeight="SemiBold"
TextTrimming="CharacterEllipsis" />
Expand All @@ -315,6 +317,7 @@
<StackPanel Grid.Column="2" Orientation="Horizontal" Spacing="12" VerticalAlignment="Center">
<TextBox
x:Name="SearchBox"
AutomationProperties.AutomationId="task-search"
Width="200"
Height="28"
CornerRadius="10"
Expand Down
Loading
Loading