diff --git a/apple/App/DraftCard.swift b/apple/App/DraftCard.swift index 163452f..6bf5be5 100644 --- a/apple/App/DraftCard.swift +++ b/apple/App/DraftCard.swift @@ -217,7 +217,7 @@ final class DraftCard: NSView, NSTextViewDelegate, NSTextFieldDelegate { private func refresh() { let body = AppFont.native(.body) // A task that does not exist yet has nothing to complete: the mark only says what the card is. - mark.image = .symbol("square", body) + mark.image = .symbol("circle", body) title.font = body titlePlaceholder.font = body titlePlaceholder.isHidden = !title.string.isEmpty @@ -487,7 +487,7 @@ final class DraftCard: NSView, NSTextViewDelegate, NSTextFieldDelegate { subtaskLines.append((mark, field, remove)) } for (index, line) in subtaskLines.enumerated() { - line.mark.image = .symbol("square", font, weight: .light) + line.mark.image = .symbol("circle", font, weight: .light) line.field.font = font if !editing(line.field), line.field.stringValue != draft.subtasks[index] { line.field.stringValue = draft.subtasks[index] } line.remove.image = .symbol("xmark.circle.fill", font) @@ -770,7 +770,7 @@ final class DraftCard: NSView, NSTextViewDelegate, NSTextFieldDelegate { let markSize = mark.image?.size ?? .zero f.mark = NSRect(x: insets.left, y: y + (line - markSize.height) / 2, width: markSize.width, height: markSize.height) // In the list the title keeps the place a row gives it after the bar of the priority. - let titleX = f.mark.maxX + 8 + (footer ? 0 : 3 + 8) + let titleX = f.mark.maxX + (footer ? 8 : TaskTableMetrics.markGap + 3 + TaskTableMetrics.barGap) let parsedWidth = parsed.stops.isEmpty ? 0 : min(parsed.stops.reduce(0) { $0 + $1.intrinsicContentSize.width + 6 }, (right - titleX) / 2) let titleWidth = max(right - titleX - (parsedWidth > 0 ? parsedWidth + 8 : 0), 1) let titleHeight = textHeight(title, width: titleWidth) diff --git a/apple/App/ListsApp.swift b/apple/App/ListsApp.swift index 2046e00..9c07ee1 100644 --- a/apple/App/ListsApp.swift +++ b/apple/App/ListsApp.swift @@ -267,7 +267,7 @@ struct MenuBarView: View { VStack(alignment: .leading, spacing: 6) { ForEach(today.prefix(12), id: \.id) { task in HStack(alignment: .firstTextBaseline, spacing: 8) { - Button { model.toggleDone(task) } label: { Image(systemName: "square") } + Button { model.toggleDone(task) } label: { Image(systemName: "circle") } .buttonStyle(.plain) .accessibilityLabel(L("Complete")) Text(task.title).lineLimit(1) diff --git a/apple/App/TaskCard.swift b/apple/App/TaskCard.swift index 00c5425..b86c70b 100644 --- a/apple/App/TaskCard.swift +++ b/apple/App/TaskCard.swift @@ -629,9 +629,9 @@ final class TaskCard: NSView, NSTextViewDelegate { let headerTop = Self.margin + Self.padding.top + 6 let markSize = mark.fittingSize frames.mark = NSRect(x: x, y: headerTop + (line - markSize.height) / 2, width: markSize.width, height: markSize.height) - x += markSize.width + 8 + x += markSize.width + TaskTableMetrics.markGap frames.bar = NSRect(x: x, y: headerTop + (line - 16) / 2, width: 3, height: 16) - x += 3 + 8 + x += 3 + TaskTableMetrics.barGap if !folder.isHidden { let size = folder.image?.size ?? .zero frames.folder = NSRect(x: x, y: headerTop + (line - size.height) / 2, width: size.width, height: size.height) diff --git a/apple/App/TaskRowCell.swift b/apple/App/TaskRowCell.swift index 90d515e..d0b2bf1 100644 --- a/apple/App/TaskRowCell.swift +++ b/apple/App/TaskRowCell.swift @@ -22,6 +22,11 @@ enum TaskTableMetrics { /// How far a task of an open project stands to the right of the project (R30). static let depthIndent: CGFloat = 22 + /// R72: the space after the mark and after the bar of the priority, shared by the row and the open card, so the + /// title stays close to the mark (#427). + static let markGap: CGFloat = 3 + static let barGap: CGFloat = 4 + /// The field for a new task of an open project. static func newChildRow() -> CGFloat { lineHeight(AppFont.native(.callout)) + 8 + 2 * rowInset } @@ -78,7 +83,7 @@ extension NSImage { } } -/// The square mark of a task or of a subtask: a click completes it or takes it back into work. +/// The round mark of a task or of a subtask (#427): a click completes it or takes it back into work. @MainActor final class TaskMarkButton: NSButton { private var task: TaskItem? @@ -96,7 +101,7 @@ final class TaskMarkButton: NSButton { self.task = task // The mark of a record of a repeat takes its completion back while it can (R128, R133); after that it is a repeat icon. let history = task.isLog && !task.undoable - let symbol = history ? "repeat" : task.wont ? "xmark.square.fill" : task.done != nil ? "checkmark.square.fill" : "square" + let symbol = history ? "repeat" : task.wont ? "xmark.circle.fill" : task.done != nil ? "checkmark.circle.fill" : "circle" // R94: the mark is as quiet as the icons of the fields; R72: a little smaller than the text. image = .symbol(symbol, AppFont.native(.callout), weight: .light) contentTintColor = task.done != nil ? .secondaryLabelColor : .tertiaryLabelColor @@ -254,17 +259,27 @@ final class TaskRowCell: NSTableCellView { let inset: CGFloat = 6 let gap: CGFloat = 8 var x = inset + CGFloat(depth) * TaskTableMetrics.depthIndent - func center(_ view: NSView, width: CGFloat, height viewHeight: CGFloat) { - view.frame = NSRect(x: x, y: ((height - viewHeight) / 2).rounded(), width: width, height: viewHeight) - x += width + gap - } - let markSize = mark.fittingSize - center(mark, width: markSize.width, height: markSize.height) + + // The title and the list under it stand as one block in the middle of the row; the mark, the project icon and + // the date stand at the middle of the title line, so in a row of two lines they go with the title (#427). + let titleHeight = TaskTableMetrics.lineHeight(AppFont.native(.body)) + let originHeight = TaskTableMetrics.lineHeight(AppFont.native(.caption)) let twoLines = !origin.isHidden - center(bar, width: 3, height: twoLines ? 28 : 16) + let block = twoLines ? titleHeight + 2 + originHeight : titleHeight + let top = ((height + block) / 2).rounded() + let titleY = top - titleHeight + func onTitle(_ viewHeight: CGFloat) -> CGFloat { (titleY + (titleHeight - viewHeight) / 2).rounded() } + + let markSize = mark.fittingSize + mark.frame = NSRect(x: x, y: onTitle(markSize.height), width: markSize.width, height: markSize.height) + x += markSize.width + TaskTableMetrics.markGap + let barHeight: CGFloat = twoLines ? 28 : 16 + bar.frame = NSRect(x: x, y: ((height - barHeight) / 2).rounded(), width: 3, height: barHeight) + x += 3 + TaskTableMetrics.barGap if !folder.isHidden { let size = folder.image?.size ?? .zero - center(folder, width: size.width, height: size.height) + folder.frame = NSRect(x: x, y: onTitle(size.height), width: size.width, height: size.height) + x += size.width + gap } // From the right edge: the chevron, then the date. @@ -274,25 +289,20 @@ final class TaskRowCell: NSTableCellView { if !date.isHidden { right -= gap let size = date.fittingSize - date.frame = NSRect(x: right - size.width, y: ((height - size.height) / 2).rounded(), width: size.width, height: size.height) + date.frame = NSRect(x: right - size.width, y: onTitle(size.height), width: size.width, height: size.height) right -= size.width if !dateIcon.isHidden { let icon = dateIcon.image?.size ?? .zero right -= 3 + icon.width - dateIcon.frame = NSRect(x: right, y: ((height - icon.height) / 2).rounded(), width: icon.width, height: icon.height) + dateIcon.frame = NSRect(x: right, y: onTitle(icon.height), width: icon.width, height: icon.height) } } right -= gap // The title and its marks, and under them the list; each is cut at its own width. - let titleHeight = TaskTableMetrics.lineHeight(AppFont.native(.body)) - let originHeight = TaskTableMetrics.lineHeight(AppFont.native(.caption)) - let block = twoLines ? titleHeight + 2 + originHeight : titleHeight - let top = ((height + block) / 2).rounded() let marksWidth = marks.arrangedSubviews.isEmpty ? 0 : marks.fittingSize.width let room = max(right - x, 0) let titleWidth = min(title.fittingSize.width, max(room - (marksWidth > 0 ? marksWidth + gap : 0), 0)) - let titleY = top - titleHeight title.frame = NSRect(x: x, y: titleY, width: titleWidth, height: titleHeight) marks.frame = NSRect(x: x + titleWidth + gap, y: titleY, width: min(marksWidth, max(room - titleWidth - gap, 0)), height: titleHeight) origin.frame = NSRect(x: x, y: titleY - 2 - originHeight, width: room, height: originHeight) diff --git a/docs/screenshots/macos-quick-entry.png b/docs/screenshots/macos-quick-entry.png index 0981803..7eca285 100644 Binary files a/docs/screenshots/macos-quick-entry.png and b/docs/screenshots/macos-quick-entry.png differ diff --git a/docs/screenshots/macos-task.png b/docs/screenshots/macos-task.png index 68b26c0..f3eafe7 100644 Binary files a/docs/screenshots/macos-task.png and b/docs/screenshots/macos-task.png differ diff --git a/docs/screenshots/macos-today.png b/docs/screenshots/macos-today.png index bfc6e3c..8dcbc19 100644 Binary files a/docs/screenshots/macos-today.png and b/docs/screenshots/macos-today.png differ diff --git a/docs/specs/product.md b/docs/specs/product.md index 6d38828..4fb4b24 100644 --- a/docs/specs/product.md +++ b/docs/specs/product.md @@ -86,11 +86,11 @@ Out of scope: shared lists, location reminders, "call/message" actions, duration - R47. Replaced by R93: the "Show Completed in the sidebar" setting became one of the cases of the setting for sidebar items. Neither of them affects the content of the log or the showing of completed tasks inside a list (R2). - R93. macOS: for each built-in view except "Inbox" ("Today", "Upcoming", "All", "Completed", "Won't do", "Trash"), the settings, in the "Sidebar" section, set when it has a row in the sidebar: "Always", "When it holds something" or "Never". Since #255 the row of "Inbox" is governed by the switch of R121, shared by all devices; a "Never" choice for "Inbox" made earlier no longer applies. By default "Today", "Upcoming" and "All" are always shown, "Completed", "Won't do" and "Trash" when they hold something. A Mac on which the former "Show Completed" setting (R47) was off gets "Never" for "Completed" and "Won't do". "Holds something" means the view would show at least one task: for "Completed", "won't do" tasks also count; for "All", open top-level tasks. The setting belongs to the device and does not sync. Lists, tags, projects and filters are not subject to it. When the row of the open view disappears, through the setting or because nothing is left in it, the app goes to "Inbox". The "Go" menu item and the ⌘1–⌘6 key of a view that has no row at the moment do nothing; the item does not turn grey. If "Inbox" itself is hidden (R121), going to "Inbox" when a row disappears stays, and it has no row then. - R68. A completed task does not leave the view at once. It stays in its place with a mark, a struck-through name (on Android without strike-through, R159) and a muted colour until the set time has passed since completion; tapping the mark during that time returns it to work. The time is chosen in the settings: "at once", 5 seconds, 15 seconds or "until the end of the day"; the default is 5 seconds. "Until the end of the day" means until midnight by the device clock: the task stays in view until the day on which it was completed is over. A time in minutes chosen in a previous version (1, 5, 15 minutes or an hour) keeps working and stays in the choice until it is changed. The setting is one for all devices and syncs (S32 in [sync.md](sync.md)). The count runs from the completion time, not from the tap on this device: a task completed on another device stays in view after sync until the end of the same period, and restarting the app does not extend the period. The period is accurate to the second: it is counted from the moment the completion was recorded. A task whose completion was recorded just now but happened long ago (import, a CalDAV calendar read for the first time) is not treated as delayed: the completion date must match the moment of recording to within a minute. The rule applies in "Inbox", in a list, in "Today", "Upcoming", "All", in the view of a tag and in a filter with the "Open" status; counters do not count such a task as open. In a list that shows completed tasks (R2), in a project (R30) and in a filter with the "All" status, the task does not disappear when the time runs out, but moves to the end, to the other completed tasks. The setting also applies to repeating tasks (#287). A repeating task on completion moves on to the next repeat (R17), stays open and at once stands where its new date puts it, including in the same view. In its former place, for the same time, stands the record of the completed repeat, with a mark, a struck-through name and a muted colour; tapping the mark of the record undoes the completion (R128). The snackbar after completion on Android and on the web appears with any setting; for a repeating task it is a message about the move; it always has "Undo" (R131). ⌘Z on macOS undoes a completion with any setting, including the completion of a repeating task. The app removes such a row by itself, without user action, in the second its time runs out; the web interface does so while the page is open and no text is being typed in it. -- R69. A task can be closed with a second outcome, "won't do": it will not be done, but nobody wants to delete it either. The action is the "Won't do" item in the context menu of a row on macOS, in the long-press menu of a row on Android, a button in the card on the web; only an open task has it. The card of a task on macOS and Android does not have it: the row of fields in the card holds only fields. The square mark, swiping and keys stay for completion. Such a task behaves like a completed one. It stays in view and leaves views by the rules of R68, and tapping the mark returns it to work. Its open subtasks get the same outcome with the same moment; those completed earlier stay completed. Undoing the action returns them to work together with the task (R11). A repeating task moves on to the next repeat by R17, and the record of the repeat in the log is marked "won't do". Its mark is a square with a cross instead of a check; the name is struck through and muted, as for a completed task. It is shown in "Completed" next to completed tasks and in the smart view "Won't do", a read-only selection from the same log. Clearing "Completed" (R46) removes both outcomes; "Won't do" has no clearing of its own. On macOS the view opens with ⌘6 and is removed from the sidebar by its setting (R93). Such a task is not part of the project's "done/total" counter (R30) or of the subtask counter in a row, in either number. In a filter (R33) it has its own status, and "Completed" means only completed tasks. Storage and the behaviour of versions that do not know this outcome are in S33 in [sync.md](sync.md); exchange with other CalDAV clients is in C27 in [caldav.md](caldav.md). +- R69. A task can be closed with a second outcome, "won't do": it will not be done, but nobody wants to delete it either. The action is the "Won't do" item in the context menu of a row on macOS, in the long-press menu of a row on Android, a button in the card on the web; only an open task has it. The card of a task on macOS and Android does not have it: the row of fields in the card holds only fields. The mark, swiping and keys stay for completion. Such a task behaves like a completed one. It stays in view and leaves views by the rules of R68, and tapping the mark returns it to work. Its open subtasks get the same outcome with the same moment; those completed earlier stay completed. Undoing the action returns them to work together with the task (R11). A repeating task moves on to the next repeat by R17, and the record of the repeat in the log is marked "won't do". Its mark is a cross instead of a check (a circle on macOS and Android, a square in the web); the name is struck through and muted, as for a completed task. It is shown in "Completed" next to completed tasks and in the smart view "Won't do", a read-only selection from the same log. Clearing "Completed" (R46) removes both outcomes; "Won't do" has no clearing of its own. On macOS the view opens with ⌘6 and is removed from the sidebar by its setting (R93). Such a task is not part of the project's "done/total" counter (R30) or of the subtask counter in a row, in either number. In a filter (R33) it has its own status, and "Completed" means only completed tasks. Storage and the behaviour of versions that do not know this outcome are in S33 in [sync.md](sync.md); exchange with other CalDAV clients is in C27 in [caldav.md](caldav.md). - R22. Search by name and note across all lists. A task is also found by the name of its subtask: the results show the task itself, and under it those of its subtasks whose name contains the search text; a subtask is not a row of its own in the results. - R33. A saved filter is a named view with conditions: date (any, overdue, today, the next N days together with overdue, no date), lists, tags (all at once), priority not lower than a given one, status (open, completed, won't do, all), words in the name or the note. The date of a task of a project is the effective one (R125). All the conditions set must hold. Ready-made presets: "Next 7 days", "Overdue", "High priority", "No date". - R34. Filters sync like the rest of the data. Over CalDAV they are stored as a property of the "Inbox" calendar and are not carried over if the server does not store such properties. -- R72. The row of a task in a list on macOS and Android has the same content on both platforms, on Android with the differences of appearance from R159; it is one line of text, and where lists are mixed, two: the name of the list stands under the title. The mark comes first: square, slightly smaller than the height of the text, centred on the height of the row rather than on the first line of text. After the mark comes the priority as a thin vertical bar the full height of the row's text: red for high, orange for medium, blue for low; a task without priority has no bar, but the space for it remains, so the titles of all tasks start on one vertical line. There are no "!" signs in the row. The project icon (R30) stands before the title when there is one. After the title come icons without text or numbers, only those the task has, in one order: repeat, note (present when the note is not empty), attachments, subtasks. In a view where lists are mixed — "Today", "Upcoming", "All", a tag view, search, a filter, "Completed", "Won't do", the Trash — the name of the task's list stands under the task title in small type, in the colour of that list, without an icon; for a task of a project (R31) it is followed by a dash and the name of the project in a muted colour: "List – Project". "Inbox" is such a list too: its task shows "Inbox" in the colour of the app, because "Inbox" has no colour of its own. In the view of one list, including "Inbox", and in the view of a project, the name is not repeated. The task title and the line under it are each truncated to their own width and do not crowd each other. The due date stands at the right edge; the overdue due date of an open task is red and always includes the date. A due date taken from the project (R125) stands in the same place in a muted colour with the project icon before it. When the day of the due date is already named by a group heading — "Today" in the "Today" view, a day in "Upcoming" — the day is not written: the time remains, and a due date without time leaves the right side empty. The start date, tags, the subtask counter, the number of attachments and the reminder are not shown in the row: they are in the card of the task. Rows are spaced more loosely than before. In the web interface the mark and the priority bar are the same, and under the title, as before, stand the due date, the icons and the name of the list; there too the due date of the project is muted and shown with the project icon. +- R72. The row of a task in a list on macOS and Android has the same content on both platforms, on Android with the differences of appearance from R159; it is one line of text, and where lists are mixed, two: the name of the list stands under the title. The mark comes first, slightly smaller than the height of the text. On macOS it is a circle and stands at the middle of the title line, so in a row of two lines it goes with the title and not with the list name under it; the project icon and the due date stand on the title line too (#427). On Android it is as in R159. After the mark comes the priority as a thin vertical bar the full height of the row's text: red for high, orange for medium, blue for low; a task without priority has no bar, but the space for it remains, so the titles of all tasks start on one vertical line. On macOS the mark, the bar and the title stand close: 3 points from the mark to the bar and 4 from the bar to the title. There are no "!" signs in the row. The project icon (R30) stands before the title when there is one. After the title come icons without text or numbers, only those the task has, in one order: repeat, note (present when the note is not empty), attachments, subtasks. In a view where lists are mixed — "Today", "Upcoming", "All", a tag view, search, a filter, "Completed", "Won't do", the Trash — the name of the task's list stands under the task title in small type, in the colour of that list, without an icon; for a task of a project (R31) it is followed by a dash and the name of the project in a muted colour: "List – Project". "Inbox" is such a list too: its task shows "Inbox" in the colour of the app, because "Inbox" has no colour of its own. In the view of one list, including "Inbox", and in the view of a project, the name is not repeated. The task title and the line under it are each truncated to their own width and do not crowd each other. The due date stands at the right edge; the overdue due date of an open task is red and always includes the date. A due date taken from the project (R125) stands in the same place in a muted colour with the project icon before it. When the day of the due date is already named by a group heading — "Today" in the "Today" view, a day in "Upcoming" — the day is not written: the time remains, and a due date without time leaves the right side empty. The start date, tags, the subtask counter, the number of attachments and the reminder are not shown in the row: they are in the card of the task. Rows are spaced more loosely than before. In the web interface the mark is a square centred on the height of the row, the priority bar is the same, and under the title, as before, stand the due date, the icons and the name of the list; there too the due date of the project is muted and shown with the project icon. - R84. In "Today" on macOS and Android tasks are divided into the groups "Overdue" and "Today" when there are overdue tasks. An overdue task is an open task whose due date is before today. The start date does not put a task in the "Overdue" group: a task without a due date or with a due date today or later whose start has already passed stands in the "Today" group. The "Overdue" group and the red counter of "Today" count the same tasks. - R73. The title of a task in a list row on macOS and Android takes one line. A title that does not fit the width is truncated at the end with an ellipsis, and the height of the row does not depend on the length of the title. The full title is in the title field of the card of the task: a title that does not fit the width wraps there onto the following lines and is visible in full; the height of the field is not limited. A title has no line breaks: Return in the field ends editing, as before, and pasted text of several lines becomes one line. - R78. "Today" on macOS and Android shows the events of the current day from the system calendars: as a block above the tasks, read-only. The setting "Show calendar events in Today" turns this on; by default it is off, belongs to the device and is not synced. The app asks the system for access to the calendars when the setting is turned on, and not before. The system grants access to all calendars at once; which of them to show is chosen in the settings of the app: under the switch stands the list of system calendars with a checkmark at each. The device stores the cleared checkmarks, so a new calendar is shown until it is cleared. When there is no access, there is no block, and the settings say so in a line that opens the system access settings. An event is one line: a bar in the colour of the calendar, the start time, the title. An all-day event and an event that started before today have no time. Events without time come first, then events with time by start time; on a tie, by title. An event that has already ended today stays in the block until the end of the day. The block is muted: a background with rounded corners across the width of the list, grey text smaller than the task title; an event has no mark. An event is not a task: it cannot be completed, changed or moved, and it is not part of the "Today" counter, search or sync. A tap on Android opens the event in the calendar app. A click on macOS opens "Calendar": there is no documented way to open it at the event or at the day, so the app opens as it is. The block is shown also when there are no tasks for today; when there are no events, there is no block. The block is reread when the system calendars change, when the app returns to the screen and when the next day begins. The other views and the web have no events. @@ -142,7 +142,7 @@ Out of scope: shared lists, location reminders, "call/message" actions, duration - R52. Return at the end of a non-empty list item starts the next item with the same indent and marker: in a numbered list the number is one higher, in a task list the checkbox is empty. Return in the middle of an item moves the rest of the line into a new item. Return on an item without text removes its marker and leaves an empty line. In a quote Return continues `> `, and on an empty line of a quote it removes `>`. On macOS this is done by the key that starts a new line according to R39. Outside a list and a quote, and inside a code block, Return only starts a new line. - R71. A link in a note opens with a plain click on macOS and a tap on Android, both in a note without focus and while it is being typed, including in the block with the cursor. The system opens the address: the browser or the mail app. The cursor does not move, and the text of the note does not change. A click or tap outside a link places the cursor, as in an ordinary field. The text of a link is edited by placing the cursor next to it and moving to the spot with the arrow keys. On macOS the pointer over a link is a hand; a drag started on a link selects text and does not open the link; a Shift-click extends the selection. On Android a long press on a link selects the word, as in the rest of the text; a scroll started on a link does not open it. - R81. Android: while text is typed in the note of a task card, the line with the cursor stays visible above the keyboard. When the note is taller than the space above the keyboard, the card scrolls after the cursor: while typing, when the cursor is moved with a tap and when the keyboard appears. -- R82. macOS: the look of the card of a new task. The title is set in the same size and weight as the text of the note, and the square before it is muted. Under the title is the note with the placeholder "Notes" (the same as in the card of an open task), and under it a row of fields: what is set stands on the left as a pill with its value (start, due date, repeat, priority, tags), what is not set stands on the right as an icon without a label, thin and light (the tertiary text colour); the "#" and attachment icons are always on the right. While the picker of a field is open, its pill or icon stays in place, and it moves to the other side when the picker closes. In the quick entry window the card ends with a bar: on the left the list or project the task goes to (R129), on the right the buttons "Cancel" (the same as Esc) and "Save" (the same as Return in the title; unavailable while there is no title). The buttons of the bar are capsules with bold white text: "Save" in the accent colour, "Cancel" grey. The quick entry window has no system frame: it is drawn as a card with corners rounder than those of system windows (a radius of 16 points), and it is dragged by its background. Under the note of an empty card it keeps room for three lines, and with a longer note the window grows; the margin above the title is larger than at the sides. The main window and the menu bar window have no bar (in the main window the card is an expanded row of the list, R37), the list stands as a pill in the row of fields on the left, and the note takes as many lines as it has. A note taken from the clipboard (R58), until it is touched, is shown as a block with a background and the label "From the clipboard", no longer than three lines, with a button that removes it; a click on the block or moving into the note from the keyboard expands it into an ordinary note in full. +- R82. macOS: the look of the card of a new task. The title is set in the same size and weight as the text of the note, and the circle before it is muted. Under the title is the note with the placeholder "Notes" (the same as in the card of an open task), and under it a row of fields: what is set stands on the left as a pill with its value (start, due date, repeat, priority, tags), what is not set stands on the right as an icon without a label, thin and light (the tertiary text colour); the "#" and attachment icons are always on the right. While the picker of a field is open, its pill or icon stays in place, and it moves to the other side when the picker closes. In the quick entry window the card ends with a bar: on the left the list or project the task goes to (R129), on the right the buttons "Cancel" (the same as Esc) and "Save" (the same as Return in the title; unavailable while there is no title). The buttons of the bar are capsules with bold white text: "Save" in the accent colour, "Cancel" grey. The quick entry window has no system frame: it is drawn as a card with corners rounder than those of system windows (a radius of 16 points), and it is dragged by its background. Under the note of an empty card it keeps room for three lines, and with a longer note the window grows; the margin above the title is larger than at the sides. The main window and the menu bar window have no bar (in the main window the card is an expanded row of the list, R37), the list stands as a pill in the row of fields on the left, and the note takes as many lines as it has. A note taken from the clipboard (R58), until it is touched, is shown as a block with a background and the label "From the clipboard", no longer than three lines, with a button that removes it; a click on the block or moving into the note from the keyboard expands it into an ordinary note in full. - R94. macOS: the look of the card of an open task. The title row has no selection background, there is no divider under the title, and there is no chevron on the right. The completion mark in all rows of the list is the size of the text, thin and muted (the tertiary text colour), and it does not carry the colour of the list. The text of the note is set in the ordinary text colour; only the placeholder "Notes" stays grey; the same holds in the card of a new task and in the quick entry window. The row of fields is arranged as in the card of a new task (R82): what is set stands on the left as a pill with its value (start, due date, repeat, reminder, priority, tags); what is not set stands on the right as a thin light icon without a label; the icons of "#", subtasks (while subtasks are not shown) and attachments are always on the right. There are no empty "Start" and "Due" pills and no "+" button. While the picker of a field is open, its pill or icon stays in place. The card is a rectangle with a corner radius of 14 points, without a visible outline, with a soft wide shadow and larger margins than before; the card of a new task opened with ⌘N has the same shape. - R96. Android: the look of the card of an open task. The card stays a bottom sheet. The mark before the title is the same as in a row of the list (R159). The row of fields under the note is arranged as on macOS (R94): what is set stands on the left as a pill with an icon and its value (start, due date, repeat, reminder, priority, tags); what is not set stands on the right as a light icon without a label; the icons of tag, subtasks (while subtasks are not shown) and attachments are always on the right. An overdue due date of an open task is red in its pill. There are no empty "Start" and "Due" pills and no "+" button. The pill of a tag removes the tag when pressed; the other pills and icons open the picker of their value. There is no line before the subtasks. Subtasks are separated by thin lines, and the mark of a subtask is a small circle (R159); for a completed subtask it is filled, and the text is grey, not struck through. - R99. Android: the card of an open task stretches to the full screen when its handle is dragged up. Only the height changes: the contents of the card and the look of the fields stay as before (R96), and contents that do not fit scroll. Dragging the handle down returns the card to the height of its contents and does not close it; a card at the height of its contents closes at once when dragged down. On a stretched card the handle stands below the status bar. There is no button for this; for TalkBack the handle is the action "Expand" or "Collapse". The next task opened opens as a card at the height of its contents. @@ -251,7 +251,7 @@ Principles by which decisions about screens are made: 6. **On macOS the keyboard covers everything:** ⌘N — new task, arrows — selection, Enter — expand and edit the title, Esc — close the card, Space or ⌘Enter — complete, ⌘T — due today, ⌘1…⌘6 — views, ⌘F — search, ⌘Z — undo, ⌃⌥Space — quick entry from any app (the shortcut is changed in the settings). 7. **Colour carries meaning.** Colour belongs to a list; red marks only an overdue due date. Priority is shown by a sign at the mark, not by filling the row. There are two colours not from a list, and neither stands in a task row: the icons of the built-in views (R79), in fixed colours that tell the views apart, none of them red, and the calendar bar of an event in "Today" (R78), in a muted block above the tasks. -macOS: the sidebar (views, lists, tags) and the task list. There is no third column. The mark in a task row stands at the middle of the row height (R72), in an open card at the middle of the first line of the title: there the middle of the mark matches the middle of a capital letter at any size and weight of the text (R35). +macOS: the sidebar (views, lists, tags) and the task list. There is no third column. The mark in a task row stands at the middle of the title line (R72), in an open card at the middle of the first line of the title: there the middle of the mark matches the middle of a capital letter at any size and weight of the text (R35). Android: one screen with the task list, a slide-out panel with the lists, and a floating "New task" button that opens the card of a new task as a bottom sheet (R57); the card is compact, with field icons, and expands to full screen (R64). ## Verification scenarios @@ -284,7 +284,7 @@ Scenarios of the core are checked by tests in `core/tests/`; test names match th - R127: the list "Work" is open, with "due today" for new tasks → "New project" with the title "Vacation" → the sidebar has the project "Vacation" 0/0, it is in the list "Work", without a due date or priority, and its view is open. In "Today" → "New project" → the project is in the default list (R40). An empty title does not create a project. - R69, R33: a filter with the state "completed" shows only the completed task, with the state "won't do" only such a task, with the state "all" both and the open ones. - R69, R46: a completed task and a "won't do" task closed a month ago → clear "Completed" → both logs are empty. -- R72: the list has task "a" with the note "buy", a repeat, an attachment, a subtask and the tag "home", and task "b" without the other fields → "a" has four signs after the title without text or numbers, and the text "buy" and "#home" are not in the row; "b" has nothing after the title, both rows are of the same height. Erase the note of "a" → three signs. In "Today" a task due today → the right side is empty; due today at 17:00 → "17:00" on the right; due yesterday → "Yesterday" in red on the right. The same task due today in its own list → "Today" on the right. The list "Home" has the task "windows" and the project "renovation" with the task "tiles"; Inbox has the task "bread" and the project "vacation" with the task "tickets", all due today → in "Today" under the title: "windows" has "Home" in the list colour, "tiles" has "Home – renovation", "bread" has "Inbox", "tickets" has "Inbox – vacation"; the mark stands at the middle of the two lines. In the list "Home" for "windows" and in Inbox for "bread" there is no list name, the row takes one line of text. Give "windows" high priority and "bread" low → after the mark "windows" has a red bar, "bread" a blue one, there are no "!" signs; the titles "windows", "bread" and of a task without priority start on the same vertical line. +- R72: the list has task "a" with the note "buy", a repeat, an attachment, a subtask and the tag "home", and task "b" without the other fields → "a" has four signs after the title without text or numbers, and the text "buy" and "#home" are not in the row; "b" has nothing after the title, both rows are of the same height. Erase the note of "a" → three signs. In "Today" a task due today → the right side is empty; due today at 17:00 → "17:00" on the right; due yesterday → "Yesterday" in red on the right. The same task due today in its own list → "Today" on the right. The list "Home" has the task "windows" and the project "renovation" with the task "tiles"; Inbox has the task "bread" and the project "vacation" with the task "tickets", all due today → in "Today" under the title: "windows" has "Home" in the list colour, "tiles" has "Home – renovation", "bread" has "Inbox", "tickets" has "Inbox – vacation"; on macOS the round mark stands at the middle of the title line, level with "windows", not between the two lines, and the title starts 10 points after the mark. In the list "Home" for "windows" and in Inbox for "bread" there is no list name, the row takes one line of text. Give "windows" high priority and "bread" low → after the mark "windows" has a red bar, "bread" a blue one, there are no "!" signs; the titles "windows", "bread" and of a task without priority start on the same vertical line. - R84: task "a" with a start date of yesterday and no due date, task "b" due yesterday → in "Today" "b" stands under "Overdue", "a" under "Today", the "Today" count is red. Complete "b" → there are no group headings, "a" is in place, the count is not red. - R103: macOS, a new install, task "a" due yesterday and task "b" due today → the Dock icon shows "2". In the settings, section "Notifications", "Count on the icon" → "Overdue tasks" → "1"; complete "a" → no number; "Do not show" → no number; "Today" → "1". Quit and open the app → the choice is the same. The app is hidden, a task due today is added through the quick entry window → the number has grown by one. - R36: a new install, a task due tomorrow at 10:00 → one notification is scheduled, at 10:00. The device has the saved choice "15 minutes before", the app is updated → the choice is "At the due time", the notification is at 10:00; choose "15 minutes before" again, restart → the choice is "15 minutes before", the notification is at 9:45. Saved choices "15 minutes before" and "an hour before", update → the choice is as before. Saved "off", update → "off". A task due tomorrow, notifications scheduled today; the device clock is moved to tomorrow, 08:59 → at 9:00 the notification "Due: today", not "Due: tomorrow". @@ -584,6 +584,8 @@ State as of 2026-10-05, version 0.1.0. **The list, cards and sidebar of macOS on AppKit** (R37, R82, R114, R117, R130, #307, #308) — the decisions of the author of the project from 2026-10-16 are recorded in the plan of #308: the task list is a flat `NSTableView` with its own rows, the card of an open task and the card of a new task are AppKit views, the same card of a new task sits in the quick entry window and in the menu bar window; the sidebar is its own table, and the selection in it does not fade when the keyboard is with the tasks (as in Things 3). The app has no event interceptors (`addLocalMonitorForEvents`). A new task goes first or right under the selected one (R130). Checked by the debug script in a hidden instance with a separate data folder: the heights of rows by R122 (26/30/40, in "Today" 41/45/55) and of the sidebar (32 and 19 points) are the same as in the former SwiftUI list; selection, keys, menus of rows and subtasks, the card with popovers and Esc in two steps (R62), files, subtasks, tasks of a project, drag data and reordering, a drop on the sidebar, the card of a new task under the selected one, the quick entry window (Esc discards, Return and ⌘Return save, height 176 points). The author checked on a visible window: the flicker of #307 is gone both when a card opens and when a task is created; dragging onto the sidebar and reordering with the mouse work. Not checked: the menu bar window, the shadow of the card and the height animation by eye, ⌘V with files in the card. +**The mark of a macOS row on the title line** (R72, #427) — the decisions of the author of the project from 2026-10-26 are recorded in #427: in a row of two lines the mark centred on the row stood between the title and the list name and read as a separate element; now the mark is a circle again, stands at the middle of the title line together with the project icon and the due date, and the title is 10 points after it (3 to the bar, the bar, 4 after it), the same in the open card. The icons of the views "Completed" and "Won't do" and the checkboxes of a note stay square. Checked on 2026-10-26 by the author of the project on the screen, in a debug build over a copy of the data folder: "Inbox" and "Today". Not checked: the larger text sizes, the dark appearance, a task of an open project, the card of a new task and the menu bar window. + **Clicks, selection and drags of macOS the system way** (R117, R123, R62, #424) — the decisions of the author of the project from 2026-10-26 are recorded in #424: what the task table did on its own is left to AppKit where no product decision needs it. The selection stays in the model, because the subtasks of an open card are selected with the rows and are not rows of the table, but behaves as the selection of a system table: a row is selected when the button goes down, a plain click on one of several selected rows waits for the button to go up, so a drag of an unselected row now selects it; Option with an arrow goes to the first or the last row; typing selects a task by the start of its title; the double click is the double action of the table; the menu of a row goes through the table, which outlines the row; several selected tasks are reordered by one drag; the events block and the clipboard note react to a click only when it ends over them; the sidebar draws its selection the system way while the keyboard is elsewhere, in place of the always-emphasized selection of #308; Esc in a popover of a card closes the card as well, except in the quick entry window. The keys Space, ⌫ and Return of the list stay as they are (as in Things and Mail). Checked in a hidden instance of the debug build with a separate data folder: typing "га" selected "Гамма" and "b" selected "beta"; typing "bu", ⌫, "y", Space, "m" selected "buy milk" and left it open and in place, and Space and ⌫ a second and a half later completed and deleted it (three runs); two selected tasks dropped before a third stood together in the order of the view, and one undo returned them; the order of `Reorder.moves` is covered by `ReorderTests`. Not checked, since they need the mouse on a visible window: the selection on press, the double click, the menu and the outline of the row with a right click and ⌃click (also in a subtask line), dragging several tasks between rows, the events block and the clipboard note, the look of the sidebar, Esc in a popover. **Undo of any action on a task** (R131, R11, R68, R109, R117, R118, R128, #311) — the decisions of the author of the project from 2026-10-17 are recorded in #311: the history is in the core, the stack in memory, on Android a snackbar after each action and an "Undo" item in the top bar menu, in the web interface Ctrl/Cmd+Z outside input fields, each login session has its own history, a task changed after the action is skipped, and the other tasks of the action are restored. The core is checked by the tests `core/tests/undo.rs` (`r131_*`) and `r131_undo_leaves_a_task_changed_on_another_device_and_reaches_it` in `core/tests/sync.rs`, the web by `r131_each_session_takes_back_its_own_actions` and the earlier tests of batch undo, moved to `undo`. The web client was checked 2026-10-17 in headless Chromium: the snackbar "Created"/"Task completed" with "Undo" with the R68 setting "5 seconds", Ctrl+Z on the page undoes, in an input field it does not. macOS: the hidden debug script — ⌘Z (`undo`) removes the due date and priority, then moves the created task to the trash; the steps of the script fall into one `UndoManager` group (#230), so separate ⌘Z presses from the keyboard are not checked. Android: built, the unit tests `SelectionTest` are green, not run on a device. Not decided in #311 and chosen during implementation: there is no snackbar after editing the title and the note, the history depth is 100 steps.