← All repositories

listless

A simple list app for Apple platforms

git clone https://code.inqk.net/listless.git


commit 09301221000ff103601c578b10b7c1c3088e9d64
parent 1c46eb5e2e6ae720bcd912cb8494baccb4b2e21b
Author: Michael Camilleri <[email protected]>
Date:   Wed, 25 Mar 2026 16:33:15 +0900

Use 'item' instead of 'task' in codebase

Diffstat

MAGENTS.md | 60++++++++++++++++++++++++++++++------------------------------
MListless.xcodeproj/project.pbxproj | 280++++++++++++++++++++++++++++++++++++++++----------------------------------------
RListless/Extensions/TaskListView+Logic.swift -> Listless/Extensions/ItemListView+Logic.swift | 378++++++++++++++++++++++++++++++++++++++++----------------------------------------
RListless/Extensions/TaskListView+SyncUI.swift -> Listless/Extensions/ItemListView+SyncUI.swift | 2+-
MListless/Helpers/AccentColor.swift | 18+++++++++---------
RListless/Helpers/TaskListTypes.swift -> Listless/Helpers/ItemListTypes.swift | 84++++++++++++++++++++++++++++++++++++++++----------------------------------------
RListless/Helpers/TaskListViewProtocol.swift -> Listless/Helpers/ItemListViewProtocol.swift | 10+++++-----
RListless/Models/TaskItem.swift -> Listless/Models/ItemEntity.swift | 20++++++++++----------
AListless/Models/ItemStore.swift | 201+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
DListless/Models/TaskStore.swift | 201-------------------------------------------------------------------------------
MListless/Sync/CloudKitErrorClassifier.swift | 2+-
MListless/Sync/PersistenceController.swift | 10+++++-----
RListlessMac/Extensions/TaskListView+Toolbar.swift -> ListlessMac/Extensions/ItemListView+Toolbar.swift | 12++++++------
MListlessMac/Helpers/AppColors.swift | 2+-
MListlessMac/Helpers/AppCommands.swift | 40++++++++++++++++++++--------------------
MListlessMac/Helpers/ClickableTextField.swift | 26+++++++++++++-------------
RListlessMac/Helpers/TaskRowDragGesture.swift -> ListlessMac/Helpers/ItemRowDragGesture.swift | 18+++++++++---------
MListlessMac/ListlessMacApp.swift | 58+++++++++++++++++++++++++++++-----------------------------
RListlessMac/Views/TaskListView.swift -> ListlessMac/Views/ItemListView.swift | 226++++++++++++++++++++++++++++++++++++++++----------------------------------------
RListlessMac/Views/TaskRowView.swift -> ListlessMac/Views/ItemRowView.swift | 100++++++++++++++++++++++++++++++++++++++++----------------------------------------
MListlessWatch/ListlessWatchApp.swift | 4++--
RListlessWatch/Views/TaskListView.swift -> ListlessWatch/Views/ItemListView.swift | 42+++++++++++++++++++++---------------------
RListlessWatch/Views/TaskRowView.swift -> ListlessWatch/Views/ItemRowView.swift | 24++++++++++++------------
RListlessiOS/Extensions/TaskListView+Drag.swift -> ListlessiOS/Extensions/ItemListView+Drag.swift | 10+++++-----
RListlessiOS/Extensions/TaskListView+NavigationHeader.swift -> ListlessiOS/Extensions/ItemListView+NavigationHeader.swift | 4++--
RListlessiOS/Extensions/TaskListView+PullGestures.swift -> ListlessiOS/Extensions/ItemListView+PullGestures.swift | 30+++++++++++++++---------------
RListlessiOS/Extensions/TaskListView+PullToClear.swift -> ListlessiOS/Extensions/ItemListView+PullToClear.swift | 4++--
RListlessiOS/Extensions/TaskListView+PullToCreate.swift -> ListlessiOS/Extensions/ItemListView+PullToCreate.swift | 16++++++++--------
RListlessiOS/Extensions/TaskListView+Toolbar.swift -> ListlessiOS/Extensions/ItemListView+Toolbar.swift | 4++--
RListlessiOS/Extensions/TaskListView+Undo.swift -> ListlessiOS/Extensions/ItemListView+Undo.swift | 20++++++++++----------
MListlessiOS/Helpers/AppColors.swift | 4++--
MListlessiOS/Helpers/AppCommands.swift | 16++++++++--------
RListlessiOS/Helpers/TaskCardModifier.swift -> ListlessiOS/Helpers/ItemCardModifier.swift | 12++++++------
RListlessiOS/Helpers/TaskRowDragGesture.swift -> ListlessiOS/Helpers/ItemRowDragGesture.swift | 12++++++------
RListlessiOS/Helpers/TaskRowMetrics.swift -> ListlessiOS/Helpers/ItemRowMetrics.swift | 4++--
RListlessiOS/Helpers/TaskRowSwipeGesture.swift -> ListlessiOS/Helpers/ItemRowSwipeGesture.swift | 8++++----
MListlessiOS/Helpers/KeyCommandBridge.swift | 12++++++------
MListlessiOS/Helpers/TappableTextField.swift | 22+++++++++++-----------
MListlessiOS/ListlessiOSApp.swift | 8++++----
MListlessiOS/Views/DraftRowView.swift | 14+++++++-------
RListlessiOS/Views/TaskListView.swift -> ListlessiOS/Views/ItemListView.swift | 192++++++++++++++++++++++++++++++++++++++++----------------------------------------
RListlessiOS/Views/TaskRowView.swift -> ListlessiOS/Views/ItemRowView.swift | 154+++++++++++++++++++++++++++++++++++++++---------------------------------------
MListlessiOS/Views/PullToClear.swift | 4++--
MListlessiOS/Views/PullToCreate.swift | 20++++++++++----------
MListlessiOS/Views/SettingsView.swift | 6+++---
MTests/Support/TestHelpers.swift | 24++++++++++++------------
MTests/UI/ListlessMacUITests.swift | 254++++++++++++++++++++++++++++++++++++++++----------------------------------------
MTests/UI/ListlessiOSUITests.swift | 86++++++++++++++++++++++++++++++++++++++++----------------------------------------
ATests/Unit/ItemStoreCompletionTests.swift | 189+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ATests/Unit/ItemStoreEdgeCaseTests.swift | 285+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ATests/Unit/ItemStoreOrderingTests.swift | 190+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ATests/Unit/ItemStoreTests.swift | 208+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
DTests/Unit/TaskStoreCompletionTests.swift | 189-------------------------------------------------------------------------------
DTests/Unit/TaskStoreEdgeCaseTests.swift | 285-------------------------------------------------------------------------------
DTests/Unit/TaskStoreOrderingTests.swift | 190-------------------------------------------------------------------------------
DTests/Unit/TaskStoreTests.swift | 208-------------------------------------------------------------------------------

56 files changed, 2251 insertions(+), 2251 deletions(-)

diff --git a/AGENTS.md b/AGENTS.md

@@ -5,15 +5,15 @@ Listless is a to-do list app for Apple platforms. It is intended to run on iPhon
 ## Project Structure & Module Organization
 - `Listless.xcodeproj` coordinates three app targets: "Listless iOS" (iPhone/iPad), "Listless macOS" (native Mac), and "Listless watchOS" (Apple Watch), all sharing code from the `Listless/` directory.
 - `project.yml` defines the Xcode project structure for XcodeGen; run `xcodegen generate` to regenerate the project after modifying it.
-- `Listless/Models` owns `TaskItem` (NSManagedObject), `TaskStore` (plain `final class` wrapping Core Data operations), and Core Data model definitions; keep CloudKit configuration inside `Listless/Sync`.
-- `Listless/Extensions` holds extensions on shared types; `TaskListView+Logic.swift` and `TaskListView+SyncUI.swift` are extensions on `TaskListViewProtocol` (not the concrete struct) so SourceKit can resolve them unambiguously across both targets.
-- `Listless/Helpers` holds shared non-view supporting code (`AccentColor`, `KeyboardNavigationModifier`, `TaskListTypes`, `TaskListViewProtocol`). `AccentColor.swift` defines `ColorTheme` (cases: `pilbara`, `collaroy`) with HSB gradient stops and a cached color interpolation function (`cachedTaskColor`); both platforms read the selected theme from `@AppStorage("colorThemeRaw")`. `TaskListTypes.swift` defines the `FocusField` and `DragState` enums as top-level types (shared by both platform `TaskListView` structs) and the `FocusStateData` struct that manages selection state — including `inactiveSelections` for discontinuous selections created by Cmd+Click toggle on macOS. `TaskListViewProtocol.swift` defines the `@MainActor TaskListViewProtocol` that both structs conform to, declaring the shared property contract (`tasks`, `store`, `syncMonitor`, `managedObjectContext`, `focusedField`, `fState`, `dragState`, `draftPlacement`, `draftTitle`, `didStartDrag()`, `clearDraftTaskUI()`).
+- `Listless/Models` owns `ItemEntity` (NSManagedObject), `ItemStore` (plain `final class` wrapping Core Data operations), and Core Data model definitions; keep CloudKit configuration inside `Listless/Sync`.
+- `Listless/Extensions` holds extensions on shared types; `ItemListView+Logic.swift` and `ItemListView+SyncUI.swift` are extensions on `ItemListViewProtocol` (not the concrete struct) so SourceKit can resolve them unambiguously across both targets.
+- `Listless/Helpers` holds shared non-view supporting code (`AccentColor`, `KeyboardNavigationModifier`, `ItemListTypes`, `ItemListViewProtocol`). `AccentColor.swift` defines `ColorTheme` (cases: `pilbara`, `collaroy`) with HSB gradient stops and a cached color interpolation function (`cachedItemColor`); both platforms read the selected theme from `@AppStorage("colorThemeRaw")`. `ItemListTypes.swift` defines the `FocusField` and `DragState` enums as top-level types (shared by both platform `ItemListView` structs) and the `FocusStateData` struct that manages selection state — including `inactiveSelections` for discontinuous selections created by Cmd+Click toggle on macOS. `ItemListViewProtocol.swift` defines the `@MainActor ItemListViewProtocol` that both structs conform to, declaring the shared property contract (`items`, `store`, `syncMonitor`, `managedObjectContext`, `focusedField`, `fState`, `dragState`, `draftPlacement`, `draftTitle`, `didStartDrag()`, `clearDraftItemUI()`).
 - `ListlessiOS/` contains the iOS app entry point, organised into three subdirectories:
-  - `Views/` — iOS-specific view components (`TaskListView`, `TaskRowView`, `DraftRowView`, `PullToCreate`, `PullToClear`, `UndoToast`, `SettingsView`, `SyncDiagnosticsView`, `AboutView`).
-  - `Helpers/` — gesture recognizers, UIKit representables, color definitions, and platform-shim view modifiers (`TappableTextField`, `TaskRowSwipeGesture`, `TaskRowDragGesture`, `AppColors`, `HoverCursorModifier`, etc.).
-  - `Extensions/` — platform-specific extensions on shared types (`TaskListView+NavigationHeader`, `TaskListView+Toolbar`, `TaskListView+PullToCreate`, `TaskListView+PullToClear`, `TaskListView+PullGestures`, `TaskListView+Drag`, `TaskListView+Undo`).
-- `ListlessMac/` contains the macOS app entry point with the same three-subdirectory structure as `ListlessiOS/` (`Views/` includes `TaskListView`).
-- `ListlessWatch/` contains the watchOS app entry point and a simplified `Views/` subdirectory (`TaskListView`, `TaskRowView`). The watchOS target selectively includes only `Listless/Models`, `Listless/Sync`, and `Listless/Helpers/AccentColor.swift` — it does not use `TaskListViewProtocol` or the shared extensions. The watch app is read-only (no creating, editing, reordering, or deleting) and supports toggling task completion only.
+  - `Views/` — iOS-specific view components (`ItemListView`, `ItemRowView`, `DraftRowView`, `PullToCreate`, `PullToClear`, `UndoToast`, `SettingsView`, `SyncDiagnosticsView`, `AboutView`).
+  - `Helpers/` — gesture recognizers, UIKit representables, color definitions, and platform-shim view modifiers (`TappableTextField`, `ItemRowSwipeGesture`, `ItemRowDragGesture`, `AppColors`, `HoverCursorModifier`, etc.).
+  - `Extensions/` — platform-specific extensions on shared types (`ItemListView+NavigationHeader`, `ItemListView+Toolbar`, `ItemListView+PullToCreate`, `ItemListView+PullToClear`, `ItemListView+PullGestures`, `ItemListView+Drag`, `ItemListView+Undo`).
+- `ListlessMac/` contains the macOS app entry point with the same three-subdirectory structure as `ListlessiOS/` (`Views/` includes `ItemListView`).
+- `ListlessWatch/` contains the watchOS app entry point and a simplified `Views/` subdirectory (`ItemListView`, `ItemRowView`). The watchOS target selectively includes only `Listless/Models`, `Listless/Sync`, and `Listless/Helpers/AccentColor.swift` — it does not use `ItemListViewProtocol` or the shared extensions. The watch app is read-only (no creating, editing, reordering, or deleting) and supports toggling item completion only.
 - `Tests/Unit` covers ordering, editing, and persistence; `Tests/Support` holds shared test helpers and fixtures.
 
 ## Build, Test, and Development Commands
@@ -52,16 +52,16 @@ Listless is a to-do list app for Apple platforms. It is intended to run on iPhon
 
 ## Coding Style & Naming Conventions
 - Use SwiftUI + Observation (`@Observable`), indent four spaces, and prefer trailing commas in builders.
-- Models are nouns (`TaskItem`), views end with `View`, and services end with `Service`; keep async methods verb-first (`syncTasks()`).
-- Centralize state in `@Observable TaskStore` that wraps Core Data operations; mutations flow through intent methods like `complete(taskID:)`.
-- `TaskStore.createTask(title:atBeginning:)` accepts an `atBeginning` flag (default `false`); when `true` assigns `minSortOrder - 1000` to prepend the task before all existing active tasks. `createTask` does **not** save — callers must call `store.save()` explicitly when they want to persist. This keeps empty placeholder tasks (from background tap or Return) in-memory only until the user types, avoiding iCloud sync of transient objects.
-- Completed tasks display below active ones; never reorder or edit them in-place.
+- Models are nouns (`ItemEntity`), views end with `View`, and services end with `Service`; keep async methods verb-first (`syncItems()`).
+- Centralize state in `@Observable ItemStore` that wraps Core Data operations; mutations flow through intent methods like `complete(itemID:)`.
+- `ItemStore.createItem(title:atBeginning:)` accepts an `atBeginning` flag (default `false`); when `true` assigns `minSortOrder - 1000` to prepend the item before all existing active items. `createItem` does **not** save — callers must call `store.save()` explicitly when they want to persist. This keeps empty placeholder items (from background tap or Return) in-memory only until the user types, avoiding iCloud sync of transient objects.
+- Completed items display below active ones; never reorder or edit them in-place.
 - For selection state in ForEach contexts, use computed Bool values + callbacks rather than passing @Binding to children (avoids SwiftUI update issues).
-- The codebase uses "task" internally (e.g. `TaskItem`, `TaskStore`, `deleteTask`) but user-facing text (labels, toast messages, menu items) should use "item" instead.
+- Both code and user-facing text use "item" terminology. The Core Data entity name remains `TaskItem` (via `@objc(TaskItem)`) to avoid CloudKit migration, but the Swift class is `ItemEntity`.
 
 ## Sync & Data Guidelines
 - Core Data with `NSPersistentCloudKitContainer` handles persistence and iCloud sync; configured in `PersistenceController` within `Listless/Sync`.
-- `TaskItem` is an `NSManagedObject` subclass with auto-updating `updatedAt` timestamp via `willSave()`.
+- `ItemEntity` is an `NSManagedObject` subclass (Core Data entity name: `TaskItem`, kept via `@objc(TaskItem)` to preserve CloudKit compatibility) with auto-updating `updatedAt` timestamp via `willSave()`.
 - CloudKit container identifier is `iCloud.net.inqk.listless`; entitlements are defined in `project.yml` and generated by XcodeGen.
 - Keep CloudKit configuration and Core Data setup inside `Listless/Sync`; exposing raw Core Data contexts elsewhere is discouraged.
 - `KeyValueSyncBridge` (`Listless/Sync/KeyValueSyncBridge.swift`) bridges `@AppStorage` keys to `NSUbiquitousKeyValueStore` for iCloud sync of user preferences. Currently syncs `"headingText"` (the customisable list title). On startup it pulls cloud values into `UserDefaults`; bidirectional observation keeps the two stores in sync with an `isSyncing` flag to prevent feedback loops. To sync additional preferences, add the key to the `keys` set in `ListlessiOSApp.init()`.
@@ -72,8 +72,8 @@ Listless is a to-do list app for Apple platforms. It is intended to run on iPhon
 - Organize tests into `@Suite` groupings with descriptive names.
 - Use natural function names that describe what's being tested (no `test` prefix required).
 - Leverage parameterized tests with `@Test(arguments: [...])` for testing multiple scenarios.
-- Maintain ≥80% coverage for `TaskStore` and Core Data operations, especially reordering edge cases and merge conflict resolution.
-- UI tests must verify keyboard entry flows: pressing Return creates a task then focuses the new empty row; tapping whitespace also starts entry.
+- Maintain ≥80% coverage for `ItemStore` and Core Data operations, especially reordering edge cases and merge conflict resolution.
+- UI tests must verify keyboard entry flows: pressing Return creates an item then focuses the new empty row; tapping whitespace also starts entry.
 - Use `PersistenceController(inMemory: true)` for isolated test environments with Core Data.
 
 ## macOS Implementation: SwiftUI vs AppKit
@@ -82,14 +82,14 @@ Listless is a to-do list app for Apple platforms. It is intended to run on iPhon
 
 ## Menu Customisation (both platforms)
 - **Do not use SwiftUI's `Commands` API** — broken on macOS 15 (dividers don't render, `CommandGroup(replacing:)` causes Window menu jitter) and on iPadOS (arrow key glyphs render as "?", `@FocusedValue` propagation fails when a UIKit view is first responder).
-- **Both platforms use native menu APIs** with a shared coordinator pattern: action closures + enabled-state booleans updated by `TaskListView.updateWindowCoordinator()` (macOS) / `TaskListView.updateMenuCoordinator()` (iOS).
+- **Both platforms use native menu APIs** with a shared coordinator pattern: action closures + enabled-state booleans updated by `ItemListView.updateWindowCoordinator()` (macOS) / `ItemListView.updateMenuCoordinator()` (iOS).
 - **Adding a new key binding** requires four touch points per platform: (1) coordinator — add action closure + enabled flag, (2) selector/action protocol — add the `@objc` method, (3) menu definition — register the key command in the right menu, (4) `updateWindowCoordinator()`/`updateMenuCoordinator()` — wire up the action and enabled state.
 
 ### macOS
 - **Menus are fully AppKit-owned** in `AppDelegate.installMainMenu()` (`ListlessMac/ListlessMacApp.swift`), assigned directly via `NSApp.mainMenu`.
 - **No runtime menu patching**: do not use `NSMenu.didAddItemNotification`/tag-guard patch logic for command setup in the current architecture.
 - **`WindowCoordinator`** (`ListlessMac/Helpers/AppCommands.swift`) bridges SwiftUI state to AppKit. One instance per window, stored in `AppDelegate`'s `NSMapTable<NSWindow, WindowCoordinator>`. Handles menu actions/enabled state (surfaced via `NSMenuItemValidation.validateMenuItem`) and focus gating (see macOS focus gating below).
-- **Command shortcuts are canonical in AppKit menus** (e.g. New Item, Move Up/Down, Mark Completed, Delete). Avoid duplicating those command shortcuts in `TaskListView.keyboardNavigation(...)`.
+- **Command shortcuts are canonical in AppKit menus** (e.g. New Item, Move Up/Down, Mark Completed, Delete). Avoid duplicating those command shortcuts in `ItemListView.keyboardNavigation(...)`.
 - **Selector style**: prefer typed `#selector(...)` where available; use `MenuSelectors` constants for string-based selectors that lack typed Swift symbols.
 - **Window menu**: keep explicit baseline items in `installMainMenu()` and let `NSApp.windowsMenu` provide system-managed dynamic window list behavior.
 - **Theme menu**: `installMainMenu()` builds a View > Theme submenu from `ColorTheme.displayOrder`; selection is persisted via `UserDefaults` key `"colorThemeRaw"` and handled by `handleThemeSelection(_:)` on `WindowCoordinator`.
@@ -97,29 +97,29 @@ Listless is a to-do list app for Apple platforms. It is intended to run on iPhon
 ### iOS (iPad)
 - **Menus use UIKit's `buildMenu(with:)`** in `IOSAppDelegate` (`ListlessiOS/ListlessiOSApp.swift`), inserting `UIKeyCommand` items into standard `.file` and `.edit` menus so the iPad keyboard shortcut overlay groups them correctly.
 - **`IOSMenuCoordinator`** (`ListlessiOS/Helpers/AppCommands.swift`) bridges SwiftUI state to UIKit. `IOSMenuActions` protocol declares `@objc` action selectors; `KeyCaptureView` (the first responder in `KeyCommandBridge`) conforms and dispatches to the coordinator.
-- **Conditional availability**: `KeyCaptureView.canPerformAction(_:withSender:)` checks coordinator enabled flags; commands are greyed out in the overlay when conditions aren't met (e.g. Move Up disabled when selected task is first).
+- **Conditional availability**: `KeyCaptureView.canPerformAction(_:withSender:)` checks coordinator enabled flags; commands are greyed out in the overlay when conditions aren't met (e.g. Move Up disabled when selected item is first).
 - **Plain (unmodified) key commands** (Up, Down, Space, Return, Delete) are still handled by `KeyCommandBridge`'s `keyCommands` property with `wantsPriorityOverSystemBehavior = true`. Command-modified shortcuts go through `buildMenu` → responder chain → `KeyCaptureView` action methods.
 
 ## iOS Implementation Notes
-- **Platform-specific inits**: iOS and macOS `TaskRowView` have diverged (iOS takes `isDragging: Binding<Bool>`). When adding new parameters, update both `TaskRowView` inits and both `TaskListView` bodies. The watchOS `TaskRowView` is independent and much simpler (tap-to-toggle only).
+- **Platform-specific inits**: iOS and macOS `ItemRowView` have diverged (iOS takes `isDragging: Binding<Bool>`). When adding new parameters, update both `ItemRowView` inits and both `ItemListView` bodies. The watchOS `ItemRowView` is independent and much simpler (tap-to-toggle only).
 - **Appearance override**: Use `UIWindow.overrideUserInterfaceStyle` in `ListlessiOSApp`, not `.preferredColorScheme()` (which doesn't properly revert to nil in sheets).
 - **Focus guard for sheets**: The `onChange(of: focusedFieldBinding)` handler skips "reclaim focus to `.scrollView`" logic when a sheet is presented, preventing focus theft from sheet TextFields.
 - **App icon in About screen**: Use `Image("AboutIcon")` from `Media.xcassets/AboutIcon.imageset` — `.appiconset` images can't be loaded via `Image()` in SwiftUI.
-- **iOS color system**: `ListlessiOS/Helpers/AppColors.swift` defines `Color.outerBackground` and `Color.taskCard`. Adjust these two values to shift the palette.
-- **Pull-to-create/clear**: Scroll gesture handling is in `.pullGestures()` (`TaskListView+PullGestures.swift`); visual indicators are in `TaskListView+PullToCreate.swift` and `TaskListView+PullToClear.swift`. The macOS `body` omits all of these. The pull-to-create indicator and the draft prepend row are separate siblings in the VStack (not a ZStack overlay) — the indicator collapses to zero height when the draft row appears. `revealPhantomRow()` directly sets up draft placement and focus without calling `createNewTaskAtTop()`. Row spacing uses explicit `.padding(.bottom, rowGap)` on each row rather than VStack `spacing` (which is set to 0).
+- **iOS color system**: `ListlessiOS/Helpers/AppColors.swift` defines `Color.outerBackground` and `Color.itemCard`. Adjust these two values to shift the palette.
+- **Pull-to-create/clear**: Scroll gesture handling is in `.pullGestures()` (`ItemListView+PullGestures.swift`); visual indicators are in `ItemListView+PullToCreate.swift` and `ItemListView+PullToClear.swift`. The macOS `body` omits all of these. The pull-to-create indicator and the draft prepend row are separate siblings in the VStack (not a ZStack overlay) — the indicator collapses to zero height when the draft row appears. `revealPhantomRow()` directly sets up draft placement and focus without calling `createNewItemAtTop()`. Row spacing uses explicit `.padding(.bottom, rowGap)` on each row rather than VStack `spacing` (which is set to 0).
 - **Haptics**: Gated behind `@AppStorage("hapticsEnabled")`. Pull-to-create/clear thresholds and drag-start use `.light` impact weight. Draft creation uses `.sensoryFeedback` on a `draftCount` trigger.
 - **Liquid Glass**: The iOS settings button uses `.buttonStyle(.glass)` gated behind `if #available(iOS 26.0, *)`, falling back to `.buttonStyle(.plain)` on older versions.
-- **Selection on iOS/iPadOS is intentionally limited**: iOS supports single-task cursor navigation via arrow keys (for marking complete/incomplete) but does not support multi-select, Select All (Cmd+A), or Cmd+Click toggling. Full selection semantics (range select, multi-select, select all) are macOS-only. Do not add selection features to the iOS target.
+- **Selection on iOS/iPadOS is intentionally limited**: iOS supports single-item cursor navigation via arrow keys (for marking complete/incomplete) but does not support multi-select, Select All (Cmd+A), or Cmd+Click toggling. Full selection semantics (range select, multi-select, select all) are macOS-only. Do not add selection features to the iOS target.
 
 ## SwiftUI Implementation Notes
-- **TaskListView architecture**: Declared separately per platform (`ListlessiOS/Views/TaskListView.swift`, `ListlessMac/Views/TaskListView.swift`, `ListlessWatch/Views/TaskListView.swift`). iOS and macOS conform to `TaskListViewProtocol`; watchOS is standalone (does not use the protocol or shared extensions). State is grouped by concern: `fState` (focus), `iState` (interaction), `pState` (pull gestures, iOS only), `isDragging` (iOS only, separate `@State` to avoid dirtying `iState`), and `layoutStorage` (non-reactive `LayoutStorage` class holding `rowFrames` and `contentBottomY` — a plain class under `@State` so SwiftUI holds a stable reference without tracking field mutations). macOS uses `tState` (task/view-local) instead of `pState`/`isDragging`/`layoutStorage`. Shared logic lives in `TaskListView+Logic.swift` as an extension on `TaskListViewProtocol`. Because `private` is file-scoped, stored properties accessed from extensions must be `internal`. Platform-specific extensions that would return `EmptyView()` on the other platform are simply omitted.
-- **Selection pattern**: Parent owns `@State var selectedTaskID`; children receive `isSelected: Bool` + `onSelect: () -> Void` callback (avoids SwiftUI ForEach update issues with @Binding). macOS supports Cmd+Click to toggle individual items in/out of the selection, creating discontinuous selections tracked via `FocusStateData.inactiveSelections`. Shift+Arrow after Cmd+Click preserves inactive items and merges them when the active range becomes adjacent. Read modifiers via `NSApp.currentEvent?.modifierFlags` (not the `NSEvent.modifierFlags` class property) so CGEvent-based UI tests can set modifier flags on synthesised mouse events.
-- **Focus management**: Single `@FocusState` enum (`FocusField` in `TaskListTypes.swift`) with `.task(UUID)` and `.scrollView` cases. Never use multiple @FocusState variables for related focus. Keyboard handlers return `.ignored` when wrong focus state.
-  - **iOS focus cleanup**: On iOS, always dismiss focus by setting `focusedField = nil`, never directly to `.scrollView`. The `onChange(of: focusedFieldBinding)` handler intercepts `nil` to run cleanup (e.g. `deleteIfEmpty` for empty tasks) before redirecting to `.scrollView`. Skipping `nil` desyncs SwiftUI focus state from UIKit first responder and can cause crashes.
-  - **Pending focus**: Set both `pendingFocus` and `focusedField` in `createNewTask()`. Do NOT resolve in `.onAppear` (race conditions). Clear `pendingFocus` in `startEditing()`. Guard `deleteIfEmpty()` against `pendingFocus` matches.
-  - **macOS focus gating via `WindowCoordinator.allowedFocusTarget`**: When Return/Escape ends editing via `makeFirstResponder(nil)`, SwiftUI processes the `@FocusState` change asynchronously. During reconciliation it can traverse the view hierarchy and make the first `NSTextField` become first responder, overriding the `.scrollView` assignment. The per-window `allowedFocusTarget` property on `WindowCoordinator` prevents this: `doCommandBy` sets it to `.scrollView` before `makeFirstResponder(nil)`, and `ClickableNSTextField.acceptsFirstResponder` checks it — rejecting focus unless the field matches the allowed target. Each `ClickableNSTextField` carries a `taskID` for this comparison. The gate is cleared in `onChange(of: focusedFieldBinding)` once `newValue` is non-nil (reconciliation complete). When `pendingFocus` is set (e.g. revealing a draft row), the gate is updated to allow that specific target, and `ClickableNSTextField.viewDidMoveToWindow()` claims focus when the view enters the window hierarchy (handles the case where the NSView doesn't exist yet when focus is requested).
+- **ItemListView architecture**: Declared separately per platform (`ListlessiOS/Views/ItemListView.swift`, `ListlessMac/Views/ItemListView.swift`, `ListlessWatch/Views/ItemListView.swift`). iOS and macOS conform to `ItemListViewProtocol`; watchOS is standalone (does not use the protocol or shared extensions). State is grouped by concern: `fState` (focus), `iState` (interaction), `pState` (pull gestures, iOS only), `isDragging` (iOS only, separate `@State` to avoid dirtying `iState`), and `layoutStorage` (non-reactive `LayoutStorage` class holding `rowFrames` and `contentBottomY` — a plain class under `@State` so SwiftUI holds a stable reference without tracking field mutations). macOS uses `tState` (item/view-local) instead of `pState`/`isDragging`/`layoutStorage`. Shared logic lives in `ItemListView+Logic.swift` as an extension on `ItemListViewProtocol`. Because `private` is file-scoped, stored properties accessed from extensions must be `internal`. Platform-specific extensions that would return `EmptyView()` on the other platform are simply omitted.
+- **Selection pattern**: Parent owns `@State var selectedItemID`; children receive `isSelected: Bool` + `onSelect: () -> Void` callback (avoids SwiftUI ForEach update issues with @Binding). macOS supports Cmd+Click to toggle individual items in/out of the selection, creating discontinuous selections tracked via `FocusStateData.inactiveSelections`. Shift+Arrow after Cmd+Click preserves inactive items and merges them when the active range becomes adjacent. Read modifiers via `NSApp.currentEvent?.modifierFlags` (not the `NSEvent.modifierFlags` class property) so CGEvent-based UI tests can set modifier flags on synthesised mouse events.
+- **Focus management**: Single `@FocusState` enum (`FocusField` in `ItemListTypes.swift`) with `.item(UUID)` and `.scrollView` cases. Never use multiple @FocusState variables for related focus. Keyboard handlers return `.ignored` when wrong focus state.
+  - **iOS focus cleanup**: On iOS, always dismiss focus by setting `focusedField = nil`, never directly to `.scrollView`. The `onChange(of: focusedFieldBinding)` handler intercepts `nil` to run cleanup (e.g. `deleteIfEmpty` for empty items) before redirecting to `.scrollView`. Skipping `nil` desyncs SwiftUI focus state from UIKit first responder and can cause crashes.
+  - **Pending focus**: Set both `pendingFocus` and `focusedField` in `createNewItem()`. Do NOT resolve in `.onAppear` (race conditions). Clear `pendingFocus` in `startEditing()`. Guard `deleteIfEmpty()` against `pendingFocus` matches.
+  - **macOS focus gating via `WindowCoordinator.allowedFocusTarget`**: When Return/Escape ends editing via `makeFirstResponder(nil)`, SwiftUI processes the `@FocusState` change asynchronously. During reconciliation it can traverse the view hierarchy and make the first `NSTextField` become first responder, overriding the `.scrollView` assignment. The per-window `allowedFocusTarget` property on `WindowCoordinator` prevents this: `doCommandBy` sets it to `.scrollView` before `makeFirstResponder(nil)`, and `ClickableNSTextField.acceptsFirstResponder` checks it — rejecting focus unless the field matches the allowed target. Each `ClickableNSTextField` carries an `itemID` for this comparison. The gate is cleared in `onChange(of: focusedFieldBinding)` once `newValue` is non-nil (reconciliation complete). When `pendingFocus` is set (e.g. revealing a draft row), the gate is updated to allow that specific target, and `ClickableNSTextField.viewDidMoveToWindow()` claims focus when the view enters the window hierarchy (handles the case where the NSView doesn't exist yet when focus is requested).
 - **Text editing**: iOS uses `TappableTextField` (UIViewRepresentable wrapping `UITextView`); macOS uses `ClickableTextField` (NSViewRepresentable wrapping `NSTextField`). Both use delegate/coordinator patterns to bridge to SwiftUI. Key gotcha: `onEditingChanged` callbacks from UIKit may arrive during a SwiftUI update pass — defer via `DispatchQueue.main.async`. On iOS, `TappableTextField` defers `isDragging`-driven changes to `isEditable`/`isSelectable` via the coordinator's `setDragging(_:)` method (called from `Task { @MainActor in }` in `updateUIView`) — setting these properties synchronously in `updateUIView` causes UITextView to call `invalidateIntrinsicContentSize()`, creating a layout-to-state backward edge that triggers AttributeGraph cycle warnings.
-- **Drag-and-drop**: Both platforms maintain `visualOrder` during drag and commit via `store.moveTask()` on release. macOS uses `onDrag` + `dropDestination`; iOS uses `UIGestureRecognizerRepresentable` (`TaskRowDragGesture`) with a long-press recognizer. The drag gesture is gated so it won't activate on a focused (editing) row — prevents the drag from stealing touches from the text cursor/loupe. The gesture's delegate uses `shouldBeRequiredToFailBy` to make UITextView gestures wait for the drag to fail, preventing the loupe from appearing during drag-reorder. Swipe gestures (`TaskRowSwipeGesture`) are also disabled on the focused row via an `isEditing` parameter, so horizontal swiping doesn't interfere with text selection.
+- **Drag-and-drop**: Both platforms maintain `visualOrder` during drag and commit via `store.moveItem()` on release. macOS uses `onDrag` + `dropDestination`; iOS uses `UIGestureRecognizerRepresentable` (`ItemRowDragGesture`) with a long-press recognizer. The drag gesture is gated so it won't activate on a focused (editing) row — prevents the drag from stealing touches from the text cursor/loupe. The gesture's delegate uses `shouldBeRequiredToFailBy` to make UITextView gestures wait for the drag to fail, preventing the loupe from appearing during drag-reorder. Swipe gestures (`ItemRowSwipeGesture`) are also disabled on the focused row via an `isEditing` parameter, so horizontal swiping doesn't interfere with text selection.
 - **Keyboard navigation**: macOS uses dictionary-based keybindings in `KeyboardNavigationModifier.swift`. iOS uses `KeyCommandBridge` (UIViewRepresentable) because iPadOS `@FocusState` silently fails with hardware keyboards — the iOS `body` does **not** use `.focusable()`, `.focused(equals: .scrollView)`, or `.keyboardNavigation()`.
 - Avoid `Spacer` inside `ScrollView` (causes unwanted scrollbar).
 - **Escape hatches** (`GeometryReader`, `PreferenceKey`, `UIViewRepresentable`/`NSViewRepresentable`): only after exhausting SwiftUI-native alternatives (`.frame`, `.overlay`, `.background`, `alignmentGuide`).

diff --git a/Listless.xcodeproj/project.pbxproj b/Listless.xcodeproj/project.pbxproj

@@ -7,104 +7,104 @@
 	objects = {
 
 /* Begin PBXBuildFile section */
-		03BC4562430EC105221AB43B /* TaskRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92D1AF0DE30657AAD86482CA /* TaskRowView.swift */; };
 		060436CDDB388BC04C51581A /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3E995954787F0A14CCFF348 /* AboutView.swift */; };
+		066E095075895DE73F217816 /* ItemStoreCompletionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 114A89FD89C8EFB5771B7242 /* ItemStoreCompletionTests.swift */; };
 		072594B24D88AD6D1DF7AFE5 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C611E04943F1D82D6F975592 /* SettingsView.swift */; };
-		07E2BB2FF9E75A922C3756AB /* TaskListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56DEECB5DC31DE5F34FB55A3 /* TaskListView.swift */; };
-		0ACA67F6578EFF181EE5C9A7 /* TaskItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBB8A3BEB346267B30B4675F /* TaskItem.swift */; };
 		0F12D56A528FCBF8A67864CB /* TappableTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ECE0E961F87BA32FA87BF90 /* TappableTextField.swift */; };
 		0F2E6817C315B947033DA2BE /* DraftRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67CB2A88C7F19F8305EBED43 /* DraftRowView.swift */; };
 		11AA75BE98CFBE44AEAB7100 /* Media.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9404C09EE1A4D91DFF338464 /* Media.xcassets */; };
+		12384AB44B4578E19EF8B0B7 /* ItemStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47892E3231D64596A2A00105 /* ItemStoreTests.swift */; };
 		12E43D0CD9124037022E3C38 /* KeyCommandBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8B5E429B253183F887C5FD6 /* KeyCommandBridge.swift */; };
-		1477B460E3DEFA3FDB7DA65B /* TaskListTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F9754B32B2FD5AF8552BC85 /* TaskListTypes.swift */; };
+		12FC79D42A110E2CDC753CCE /* ItemEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 138DCA35ED82A745E4745175 /* ItemEntity.swift */; };
 		172F2DD978988E207610055F /* KeyboardNavigationModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4669414A460FC0758D5B49A8 /* KeyboardNavigationModifier.swift */; };
-		182D9FB61E3A3650D7D83D2A /* TaskRowSwipeGesture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 067D666DF0AF1C53404ECF7C /* TaskRowSwipeGesture.swift */; };
+		194CAF0FBC308BE96CE8AA7B /* ItemRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3048ACA1CAF1284F99E1400E /* ItemRowView.swift */; };
 		19699EC4FF57EF0D636B65E3 /* KeyValueSyncBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAE264D30C7692457B92E518 /* KeyValueSyncBridge.swift */; };
 		1A66A0454558B207AF9265D4 /* UndoToast.swift in Sources */ = {isa = PBXBuildFile; fileRef = 658295C1386BFF48CE3C2419 /* UndoToast.swift */; };
 		1AA328A921EF8A7FDD03119A /* TestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75B048B19C5219862BBED2E7 /* TestHelpers.swift */; };
+		1E31935122C5E97907B30C70 /* ItemStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47892E3231D64596A2A00105 /* ItemStoreTests.swift */; };
+		20D2A3E4694AE35AE6CF4AAD /* ItemListView+PullGestures.swift in Sources */ = {isa = PBXBuildFile; fileRef = E85BFCBD4DCB35CC1C8F9401 /* ItemListView+PullGestures.swift */; };
+		2323CE015C01C6354C557F90 /* ItemListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37F80F7BB632A04A687890F0 /* ItemListView.swift */; };
 		239F975836FD432A5FF04036 /* ListlessiOSUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88C0D6F2667BD14F29CB84E5 /* ListlessiOSUITests.swift */; };
 		264BD64C1DD30376E8BDAF79 /* KeyValueSyncBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAE264D30C7692457B92E518 /* KeyValueSyncBridge.swift */; };
-		269B93D5543770B464DFB37A /* TaskStoreOrderingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D3A2DDCE24E54ABCCFBBD4C /* TaskStoreOrderingTests.swift */; };
-		26F609518DE1055DF09B0159 /* TaskListView+NavigationHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 712ADAD0FE66175DAA9A6D50 /* TaskListView+NavigationHeader.swift */; };
-		2C53717A0EAFA39240615C9A /* TaskRowDragGesture.swift in Sources */ = {isa = PBXBuildFile; fileRef = D57C3CC81C4380EFAE4DB910 /* TaskRowDragGesture.swift */; };
+		2921A4A343D43664F954B588 /* ItemListTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848DBC251E2D2EB7BD089768 /* ItemListTypes.swift */; };
 		2CCB5FE0084742D018E52A3D /* KeyValueSyncBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAE264D30C7692457B92E518 /* KeyValueSyncBridge.swift */; };
-		322EE74CBED492693B92AD59 /* TaskListView+Toolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A440253D045A0896D94ADD0 /* TaskListView+Toolbar.swift */; };
-		354687FCBBCC2432340BD5EB /* TaskCardModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2143744C421C67E53A2674CF /* TaskCardModifier.swift */; };
-		365FDEE6823D7A114F3FB12A /* TaskListViewProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2C018476BD91B73870244B9 /* TaskListViewProtocol.swift */; };
+		2F5309630692E89276CC3149 /* ItemListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20A09A6C1C2251E96E1B5D96 /* ItemListView.swift */; };
+		3383645AE13E9C3AAECFBD0B /* ItemListView+PullToClear.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23060125DF03EE84F3AED8CB /* ItemListView+PullToClear.swift */; };
 		37AEF10712B3325BF9BC72E4 /* BackgroundClickMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = F15BF645DEDE8D9E94DB508B /* BackgroundClickMonitor.swift */; };
-		3ABE52A15C2059D8D5570528 /* TaskStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC3DEE364304587D280C5672 /* TaskStore.swift */; };
-		3D1F551A03B97ECF4E3DC8B0 /* TaskRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E06485DBE35B60868E14202A /* TaskRowView.swift */; };
 		3EDB6A9A30B4226C15E7F44D /* AppCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46C46FC97E6DB6FF81AC5C22 /* AppCommands.swift */; };
+		4263E6472020564AA702D117 /* ItemCardModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CF4BADA48F30C5F471EA910 /* ItemCardModifier.swift */; };
+		47D9272442A5F15B324D3DAC /* ItemStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B8EC97702E7B218A03B7898 /* ItemStore.swift */; };
 		4A0682E85B50DF42ECF83B48 /* Listless watchOS.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = C6812E535A24C599C28F9278 /* Listless watchOS.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
 		4DD2030E321567BD25661760 /* SyncDiagnosticsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9288507CE6023425D1DE724 /* SyncDiagnosticsView.swift */; };
-		4E5A0A02121E02124F80E320 /* TaskListView+SyncUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A7BD42B1E3C71333FA24893 /* TaskListView+SyncUI.swift */; };
-		5035EC4C7518A5FF9AD454CA /* TaskRowDragGesture.swift in Sources */ = {isa = PBXBuildFile; fileRef = F416DD868A4C044F0D64F8D0 /* TaskRowDragGesture.swift */; };
 		53700EA974FE4AD771FE89EC /* CloudKitSyncMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E3E82F6093EEFC94A41FED9 /* CloudKitSyncMonitor.swift */; };
 		543C8A0C8A9E2F77B2C0060F /* AccentColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17DD7EDA74DAAFA27C84CA08 /* AccentColor.swift */; };
-		54FCE72978F20AA64855B521 /* TaskStoreEdgeCaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B2BBE01D99CDA278BCB9F49 /* TaskStoreEdgeCaseTests.swift */; };
 		568635BB34CD7EBE24E66A15 /* SyncDiagnosticsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF3374CE58E7D9378C6997D2 /* SyncDiagnosticsView.swift */; };
+		5716CA28163DE0EAAA875AEE /* ItemListView+PullToCreate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78232E9D9F77FA3630F9D089 /* ItemListView+PullToCreate.swift */; };
 		5761B201BF46FCA9C5C98CEF /* PlatformScrollIndicatorsModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 466F9B0E407DF1F5B4789531 /* PlatformScrollIndicatorsModifier.swift */; };
-		57E864F71C9C84B63A28E14F /* TaskStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC3DEE364304587D280C5672 /* TaskStore.swift */; };
 		5B60B409CE4BA668DB30A65D /* Listless.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = C093494053E6C348F245D4EC /* Listless.xcdatamodeld */; };
 		5D3EE9526DA269EE9EE3AB52 /* AppColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1E998119283F784B9ADEE28 /* AppColors.swift */; };
-		5DB1231FEF24A9E4BC1E56B0 /* TaskListView+PullGestures.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2D9CDDA8913CD116FB4DA74 /* TaskListView+PullGestures.swift */; };
 		5E6BE0BA881F6CAEF455D9ED /* ListlessMacUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EADAA53B8BCBC80AEFF191EF /* ListlessMacUITests.swift */; };
 		614FCCA450EC0BFFD8B40640 /* ListlessMacApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DA467DF2E59BDBE6EEF6A7D /* ListlessMacApp.swift */; };
-		642151C8EEA34DAD76C49FA1 /* TaskListView+SyncUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A7BD42B1E3C71333FA24893 /* TaskListView+SyncUI.swift */; };
+		620D9398218A88B7E4C2331C /* ItemStoreEdgeCaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52FCE9FC4EFD26C9016389B7 /* ItemStoreEdgeCaseTests.swift */; };
+		63C08E89303BD17601271D2C /* ItemListView+SyncUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = E209A0014EE95F2BC300CE42 /* ItemListView+SyncUI.swift */; };
 		65E97DE8C190E9E9B71EC356 /* ListlessWatchApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BE7F4637B4F4C1FF4BE160B /* ListlessWatchApp.swift */; };
-		684A80B692237B74EDF3C7A8 /* TaskItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBB8A3BEB346267B30B4675F /* TaskItem.swift */; };
 		6FE9D247153209BD4CFD9E34 /* Listless.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = C093494053E6C348F245D4EC /* Listless.xcdatamodeld */; };
 		763363F6F3C7D2D3C9A63977 /* PlatformScrollIndicatorsModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA5D58EC1FBAA96E83A79445 /* PlatformScrollIndicatorsModifier.swift */; };
-		77970A2323A598E830D95301 /* TaskListView+Toolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB43816B8E7F083A2AD07F28 /* TaskListView+Toolbar.swift */; };
 		77FE96F070B1F7FE31A9CE51 /* PlatformTextFieldWidthModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2174C43654733E9D4023157 /* PlatformTextFieldWidthModifier.swift */; };
 		785721EB774EAC6BBA26C038 /* PullToClear.swift in Sources */ = {isa = PBXBuildFile; fileRef = 567DBAC2A39FA2760D006AAB /* PullToClear.swift */; };
+		790843E40F28B4E186F88F16 /* ItemStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B8EC97702E7B218A03B7898 /* ItemStore.swift */; };
 		7BB45276D4EB96B8425D2EBD /* AppColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4DB90B3400C191460F4F4BD /* AppColors.swift */; };
-		7E366008FF9335A77FE1636D /* TaskRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 199CC2F58DD7CBA3F2229366 /* TaskRowView.swift */; };
-		80BCA654FE694D8A77D9BF93 /* TaskListView+PullToCreate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AD8D4C29E09FEE78AE5AB79 /* TaskListView+PullToCreate.swift */; };
-		82E75475E13FD847DDDC69D8 /* TaskListView+Drag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FA75CA18C6270D68A755AEB /* TaskListView+Drag.swift */; };
-		879608140FA8D7A32078C3CF /* TaskListView+Undo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AACB5E40BEBA75E7FE8B930 /* TaskListView+Undo.swift */; };
+		84B31DA3CF21D57742C9217A /* ItemStoreEdgeCaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52FCE9FC4EFD26C9016389B7 /* ItemStoreEdgeCaseTests.swift */; };
+		851F46417FE40D6BC765BC70 /* ItemListView+Logic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82F2C024B6C1F2F1FD7A25B0 /* ItemListView+Logic.swift */; };
 		882695E7AE463C0F39ACFF3C /* HoverCursorModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F5D8B5866362D422A2A331C /* HoverCursorModifier.swift */; };
 		889DCB2BB3C01DDA281EA81A /* CloudKitSyncMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E3E82F6093EEFC94A41FED9 /* CloudKitSyncMonitor.swift */; };
-		8A8D164CAA7B95B4C7435A7C /* TaskListView+PullToClear.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A6D74EDDD7E3FC150064FB5 /* TaskListView+PullToClear.swift */; };
-		8FA8398315B67F12EA25FA35 /* TaskStoreCompletionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E88DDD119EEEECCD45F36D2 /* TaskStoreCompletionTests.swift */; };
-		91EDF52C7C5C0B35E9D8B51E /* TaskStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC3DEE364304587D280C5672 /* TaskStore.swift */; };
-		93275BD83342D6CE94272E6A /* TaskListTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F9754B32B2FD5AF8552BC85 /* TaskListTypes.swift */; };
+		89C4109374BD64464B0018B7 /* ItemListView+NavigationHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93CC030045617DBC90812D79 /* ItemListView+NavigationHeader.swift */; };
+		8FB18395E5436F6C91A0F077 /* ItemRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7CB071709951F52C7742A3 /* ItemRowView.swift */; };
+		9082E96001188E516B7F903B /* ItemListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3116A37F1353BF6E18308DD2 /* ItemListView.swift */; };
+		90BC899E66B98517A91F2627 /* ItemListView+Drag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D4ED76F5996A4308D2BC7C8 /* ItemListView+Drag.swift */; };
+		930AE396D982D7C46E498311 /* ItemEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 138DCA35ED82A745E4745175 /* ItemEntity.swift */; };
 		96617677059FABDBB80D642B /* Listless.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = C093494053E6C348F245D4EC /* Listless.xcdatamodeld */; };
-		99977BFA37FBAAA49AF6B71E /* TaskStoreEdgeCaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B2BBE01D99CDA278BCB9F49 /* TaskStoreEdgeCaseTests.swift */; };
 		99D17075DA3F00F52A18BB4D /* AccentColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17DD7EDA74DAAFA27C84CA08 /* AccentColor.swift */; };
+		9E75E4AEEF577E0096E22DBA /* ItemListView+Toolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03343E3D48A0EF2B146528E2 /* ItemListView+Toolbar.swift */; };
 		A0AA8FD4C542E9AEB2437BC2 /* PersistenceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14858BDFD1FD5119F1F24A6 /* PersistenceController.swift */; };
 		A10BF9D0C850105E4FA1A2AD /* CloudKitErrorClassifierTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C3B9FF63645B35E09CF1B1 /* CloudKitErrorClassifierTests.swift */; };
 		A119D0130DB77E30FBCB5436 /* PersistenceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14858BDFD1FD5119F1F24A6 /* PersistenceController.swift */; };
+		A34F23F6314067EFB35FB209 /* ItemListViewProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = F31EDB2C34C8B255000A2525 /* ItemListViewProtocol.swift */; };
 		A8D5A7B0DFBEC87501FD0526 /* Media.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9404C09EE1A4D91DFF338464 /* Media.xcassets */; };
+		A92F8503815056D3183CD7AE /* ItemListView+SyncUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = E209A0014EE95F2BC300CE42 /* ItemListView+SyncUI.swift */; };
+		B0F8B09E2AB38C8C4AF74C10 /* ItemRowSwipeGesture.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCCE85438FE23E43095B2C25 /* ItemRowSwipeGesture.swift */; };
 		B7CFDCA5EA48EDE1C768FA21 /* AppCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C79ABB39A40D3E1828716C7 /* AppCommands.swift */; };
 		BA6953E0EFE6F8255F05A3FD /* AccentColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17DD7EDA74DAAFA27C84CA08 /* AccentColor.swift */; };
-		C169823665158AA347A63990 /* TaskListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E51B8129962E5CC78ECDDC2B /* TaskListView.swift */; };
-		C1FE091454864C4BBBBEB077 /* TaskItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBB8A3BEB346267B30B4675F /* TaskItem.swift */; };
+		BB16A28C4DA1695B722B45B2 /* ItemEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 138DCA35ED82A745E4745175 /* ItemEntity.swift */; };
+		BDA8D53342F745B27B72B242 /* ItemRowDragGesture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03A80C91DBDA44C879958098 /* ItemRowDragGesture.swift */; };
+		C7C69D883B45F1B4CE979AF7 /* ItemListViewProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = F31EDB2C34C8B255000A2525 /* ItemListViewProtocol.swift */; };
+		C89D4C17F91AF91F18B6EF4E /* ItemStoreCompletionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 114A89FD89C8EFB5771B7242 /* ItemStoreCompletionTests.swift */; };
 		CA439FD953EA59A9664E0D74 /* CloudKitErrorClassifierTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C3B9FF63645B35E09CF1B1 /* CloudKitErrorClassifierTests.swift */; };
 		CAB42FAA253E2B347AB0594B /* CloudKitErrorClassifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68A677CC11ACE0BF743AFCE5 /* CloudKitErrorClassifier.swift */; };
-		CAD142ED738A83371DFF8F5B /* TaskListView+Logic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72B4668483D05A6ECA142B89 /* TaskListView+Logic.swift */; };
-		CC99A96BBC089C423F582E4F /* TaskListViewProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2C018476BD91B73870244B9 /* TaskListViewProtocol.swift */; };
-		CDE538A0B3CA14B1CB421ED8 /* TaskStoreOrderingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D3A2DDCE24E54ABCCFBBD4C /* TaskStoreOrderingTests.swift */; };
+		D24133A6C0105FE8E4528EF2 /* ItemStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B8EC97702E7B218A03B7898 /* ItemStore.swift */; };
+		D9325B2D23FF2CD644A1A7E3 /* ItemRowMetrics.swift in Sources */ = {isa = PBXBuildFile; fileRef = E71FBC3E19D32F527B5FE9E6 /* ItemRowMetrics.swift */; };
+		DAEA21531CFEA94D335FFC6E /* ItemStoreOrderingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D1DF1C029FA3C6B12A58E7F /* ItemStoreOrderingTests.swift */; };
+		DB0CBB6C3EE56406AF86FDE3 /* ItemRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0DF205300C6B51A53B256D6 /* ItemRowView.swift */; };
 		DB5FF6C1AA57D4C9BDDD50FD /* ClickableTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = D640E7D21735C62A30A26DA4 /* ClickableTextField.swift */; };
+		DBFE5DADCAAF26CF77245410 /* ItemListTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848DBC251E2D2EB7BD089768 /* ItemListTypes.swift */; };
+		DC103B1BFDC5940F63DD48ED /* ItemRowDragGesture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05AB46824DCDD04903EA4C82 /* ItemRowDragGesture.swift */; };
 		DC73A39A269AB495BCE1AC48 /* PersistenceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14858BDFD1FD5119F1F24A6 /* PersistenceController.swift */; };
 		DEE187A790A4058FE4AFDB2E /* PlatformTextFieldWidthModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8448C5778F75F52719114AF /* PlatformTextFieldWidthModifier.swift */; };
+		E0BDC0FCAB43CEE0C9AC5279 /* ItemListView+Undo.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB4B98BD127A56CE4669DCD5 /* ItemListView+Undo.swift */; };
 		E12C1304464FC7799856B2BA /* TestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75B048B19C5219862BBED2E7 /* TestHelpers.swift */; };
-		E14F8177232BBC75FEEE1E2C /* TaskStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 967F7ECEB3915CEDCE584872 /* TaskStoreTests.swift */; };
 		E2FC6CF95A2C59CA147172EE /* FPSOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA6BD768FB1788D0BC58589F /* FPSOverlay.swift */; };
 		E429067963379F99DD184FED /* CloudKitSyncMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E3E82F6093EEFC94A41FED9 /* CloudKitSyncMonitor.swift */; };
 		E47136CA7428927395D8C7C7 /* PullToCreate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF7D84B54AE70036D205CA4 /* PullToCreate.swift */; };
 		E4BD761E34CBB84CE80F7F49 /* CloudKitErrorClassifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68A677CC11ACE0BF743AFCE5 /* CloudKitErrorClassifier.swift */; };
-		E5878BAA0EA66A94440E2B0F /* TaskListView+Logic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72B4668483D05A6ECA142B89 /* TaskListView+Logic.swift */; };
 		E60F81C7B930AACE67746759 /* HoverCursorModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7588879D0FA1C2A8BCEF14F /* HoverCursorModifier.swift */; };
+		E627F653B5A692B42A75FFBA /* ItemListView+Toolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 869254E16B52F36616416DB4 /* ItemListView+Toolbar.swift */; };
 		EA5E6FC7D61E235B70A139FA /* Media.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9404C09EE1A4D91DFF338464 /* Media.xcassets */; };
-		ECD5E7EA05AE1C00B38C939E /* TaskStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 967F7ECEB3915CEDCE584872 /* TaskStoreTests.swift */; };
 		F046B5A7F4C0BD7AAF46A69C /* CloudKitErrorClassifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68A677CC11ACE0BF743AFCE5 /* CloudKitErrorClassifier.swift */; };
 		F0B2B806BD84A4F2FDF8E038 /* ListlessiOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC245331D715EA85887C0BA0 /* ListlessiOSApp.swift */; };
+		F2845953BA77F171FDA2A59F /* ItemStoreOrderingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D1DF1C029FA3C6B12A58E7F /* ItemStoreOrderingTests.swift */; };
 		F3DD1F167E4107456473B6B2 /* Credits.html in Resources */ = {isa = PBXBuildFile; fileRef = 3DD31E8962F7EEC22EFC0CA9 /* Credits.html */; };
-		F50E8B8F73F64D8E641DC74C /* TaskStoreCompletionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E88DDD119EEEECCD45F36D2 /* TaskStoreCompletionTests.swift */; };
+		F56B03B24F33B31394D91512 /* ItemListView+Logic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82F2C024B6C1F2F1FD7A25B0 /* ItemListView+Logic.swift */; };
 		F6587B84ECC6BFE92A5FB493 /* KeyboardNavigationModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4669414A460FC0758D5B49A8 /* KeyboardNavigationModifier.swift */; };
-		FDD09FECEED48EC9598538F4 /* TaskListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 632DA39B24C4CF1528A1A24D /* TaskListView.swift */; };
-		FF58A3AB2BCFFA42BF2F6413 /* TaskRowMetrics.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2EF927F09D67532F44BB80D /* TaskRowMetrics.swift */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXContainerItemProxy section */
@@ -161,91 +161,91 @@
 
 /* Begin PBXFileReference section */
 		01E141436176F83594E2F26B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
-		067D666DF0AF1C53404ECF7C /* TaskRowSwipeGesture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskRowSwipeGesture.swift; sourceTree = "<group>"; };
+		03343E3D48A0EF2B146528E2 /* ItemListView+Toolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ItemListView+Toolbar.swift"; sourceTree = "<group>"; };
+		03A80C91DBDA44C879958098 /* ItemRowDragGesture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemRowDragGesture.swift; sourceTree = "<group>"; };
+		05AB46824DCDD04903EA4C82 /* ItemRowDragGesture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemRowDragGesture.swift; sourceTree = "<group>"; };
 		0B750F1634E250256AF3FEB6 /* Listless iOS UI Tests.xctest */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = "Listless iOS UI Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
+		114A89FD89C8EFB5771B7242 /* ItemStoreCompletionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemStoreCompletionTests.swift; sourceTree = "<group>"; };
 		126108860D7878DDC3BECC4B /* Listless iOS.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = "Listless iOS.app"; sourceTree = BUILT_PRODUCTS_DIR; };
+		138DCA35ED82A745E4745175 /* ItemEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemEntity.swift; sourceTree = "<group>"; };
 		17DD7EDA74DAAFA27C84CA08 /* AccentColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccentColor.swift; sourceTree = "<group>"; };
-		199CC2F58DD7CBA3F2229366 /* TaskRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskRowView.swift; sourceTree = "<group>"; };
 		1DA467DF2E59BDBE6EEF6A7D /* ListlessMacApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListlessMacApp.swift; sourceTree = "<group>"; };
-		2143744C421C67E53A2674CF /* TaskCardModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskCardModifier.swift; sourceTree = "<group>"; };
-		2A440253D045A0896D94ADD0 /* TaskListView+Toolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TaskListView+Toolbar.swift"; sourceTree = "<group>"; };
-		2D3A2DDCE24E54ABCCFBBD4C /* TaskStoreOrderingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskStoreOrderingTests.swift; sourceTree = "<group>"; };
+		20A09A6C1C2251E96E1B5D96 /* ItemListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemListView.swift; sourceTree = "<group>"; };
+		23060125DF03EE84F3AED8CB /* ItemListView+PullToClear.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ItemListView+PullToClear.swift"; sourceTree = "<group>"; };
+		2B8EC97702E7B218A03B7898 /* ItemStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemStore.swift; sourceTree = "<group>"; };
+		3048ACA1CAF1284F99E1400E /* ItemRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemRowView.swift; sourceTree = "<group>"; };
+		3116A37F1353BF6E18308DD2 /* ItemListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemListView.swift; sourceTree = "<group>"; };
 		3313FEDB101EECA4B344EEF4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
+		37F80F7BB632A04A687890F0 /* ItemListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemListView.swift; sourceTree = "<group>"; };
 		3DD31E8962F7EEC22EFC0CA9 /* Credits.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = Credits.html; sourceTree = "<group>"; };
-		3FA75CA18C6270D68A755AEB /* TaskListView+Drag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TaskListView+Drag.swift"; sourceTree = "<group>"; };
 		4669414A460FC0758D5B49A8 /* KeyboardNavigationModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardNavigationModifier.swift; sourceTree = "<group>"; };
 		466F9B0E407DF1F5B4789531 /* PlatformScrollIndicatorsModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlatformScrollIndicatorsModifier.swift; sourceTree = "<group>"; };
 		46C46FC97E6DB6FF81AC5C22 /* AppCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppCommands.swift; sourceTree = "<group>"; };
+		47892E3231D64596A2A00105 /* ItemStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemStoreTests.swift; sourceTree = "<group>"; };
 		4C79ABB39A40D3E1828716C7 /* AppCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppCommands.swift; sourceTree = "<group>"; };
+		4C7CB071709951F52C7742A3 /* ItemRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemRowView.swift; sourceTree = "<group>"; };
+		4D4ED76F5996A4308D2BC7C8 /* ItemListView+Drag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ItemListView+Drag.swift"; sourceTree = "<group>"; };
 		4FC64B9F9370041BEDBD1E14 /* .gitkeep */ = {isa = PBXFileReference; path = .gitkeep; sourceTree = "<group>"; };
 		51C3B9FF63645B35E09CF1B1 /* CloudKitErrorClassifierTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudKitErrorClassifierTests.swift; sourceTree = "<group>"; };
+		52FCE9FC4EFD26C9016389B7 /* ItemStoreEdgeCaseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemStoreEdgeCaseTests.swift; sourceTree = "<group>"; };
 		567DBAC2A39FA2760D006AAB /* PullToClear.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PullToClear.swift; sourceTree = "<group>"; };
-		56DEECB5DC31DE5F34FB55A3 /* TaskListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskListView.swift; sourceTree = "<group>"; };
-		5A6D74EDDD7E3FC150064FB5 /* TaskListView+PullToClear.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TaskListView+PullToClear.swift"; sourceTree = "<group>"; };
 		5B0E22B8F7B2B7283CAF749E /* Listless macOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Listless macOS.app"; sourceTree = BUILT_PRODUCTS_DIR; };
-		5E88DDD119EEEECCD45F36D2 /* TaskStoreCompletionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskStoreCompletionTests.swift; sourceTree = "<group>"; };
-		632DA39B24C4CF1528A1A24D /* TaskListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskListView.swift; sourceTree = "<group>"; };
 		658295C1386BFF48CE3C2419 /* UndoToast.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UndoToast.swift; sourceTree = "<group>"; };
 		67CB2A88C7F19F8305EBED43 /* DraftRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DraftRowView.swift; sourceTree = "<group>"; };
 		68A677CC11ACE0BF743AFCE5 /* CloudKitErrorClassifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudKitErrorClassifier.swift; sourceTree = "<group>"; };
 		6BE7F4637B4F4C1FF4BE160B /* ListlessWatchApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListlessWatchApp.swift; sourceTree = "<group>"; };
+		6CF4BADA48F30C5F471EA910 /* ItemCardModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemCardModifier.swift; sourceTree = "<group>"; };
+		6D1DF1C029FA3C6B12A58E7F /* ItemStoreOrderingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemStoreOrderingTests.swift; sourceTree = "<group>"; };
 		6E3E82F6093EEFC94A41FED9 /* CloudKitSyncMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudKitSyncMonitor.swift; sourceTree = "<group>"; };
 		6ECE0E961F87BA32FA87BF90 /* TappableTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TappableTextField.swift; sourceTree = "<group>"; };
-		6F9754B32B2FD5AF8552BC85 /* TaskListTypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskListTypes.swift; sourceTree = "<group>"; };
-		712ADAD0FE66175DAA9A6D50 /* TaskListView+NavigationHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TaskListView+NavigationHeader.swift"; sourceTree = "<group>"; };
-		72B4668483D05A6ECA142B89 /* TaskListView+Logic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TaskListView+Logic.swift"; sourceTree = "<group>"; };
 		72BAC76C5B5ED291048705CF /* Listless.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Listless.entitlements; sourceTree = "<group>"; };
 		74255E6B6C40899E9B17D927 /* .gitkeep */ = {isa = PBXFileReference; path = .gitkeep; sourceTree = "<group>"; };
 		75B048B19C5219862BBED2E7 /* TestHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestHelpers.swift; sourceTree = "<group>"; };
-		7A7BD42B1E3C71333FA24893 /* TaskListView+SyncUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TaskListView+SyncUI.swift"; sourceTree = "<group>"; };
+		78232E9D9F77FA3630F9D089 /* ItemListView+PullToCreate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ItemListView+PullToCreate.swift"; sourceTree = "<group>"; };
 		7C73E9D4C42CCABBF0F33543 /* Listless.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Listless.entitlements; sourceTree = "<group>"; };
+		82F2C024B6C1F2F1FD7A25B0 /* ItemListView+Logic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ItemListView+Logic.swift"; sourceTree = "<group>"; };
+		848DBC251E2D2EB7BD089768 /* ItemListTypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemListTypes.swift; sourceTree = "<group>"; };
+		869254E16B52F36616416DB4 /* ItemListView+Toolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ItemListView+Toolbar.swift"; sourceTree = "<group>"; };
 		88C0D6F2667BD14F29CB84E5 /* ListlessiOSUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListlessiOSUITests.swift; sourceTree = "<group>"; };
-		8AACB5E40BEBA75E7FE8B930 /* TaskListView+Undo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TaskListView+Undo.swift"; sourceTree = "<group>"; };
 		9262207DAC21619BD9EDEE15 /* Listless.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Listless.entitlements; sourceTree = "<group>"; };
-		92D1AF0DE30657AAD86482CA /* TaskRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskRowView.swift; sourceTree = "<group>"; };
+		93CC030045617DBC90812D79 /* ItemListView+NavigationHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ItemListView+NavigationHeader.swift"; sourceTree = "<group>"; };
 		9404C09EE1A4D91DFF338464 /* Media.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Media.xcassets; sourceTree = "<group>"; };
 		944BAE054AAC1B9C4FC954F9 /* .gitkeep */ = {isa = PBXFileReference; path = .gitkeep; sourceTree = "<group>"; };
-		967F7ECEB3915CEDCE584872 /* TaskStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskStoreTests.swift; sourceTree = "<group>"; };
-		9AD8D4C29E09FEE78AE5AB79 /* TaskListView+PullToCreate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TaskListView+PullToCreate.swift"; sourceTree = "<group>"; };
-		9B2BBE01D99CDA278BCB9F49 /* TaskStoreEdgeCaseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskStoreEdgeCaseTests.swift; sourceTree = "<group>"; };
 		9F5D8B5866362D422A2A331C /* HoverCursorModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HoverCursorModifier.swift; sourceTree = "<group>"; };
 		9FBEB58DD41817F09B0EB9F0 /* Listless.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Listless.xcdatamodel; sourceTree = "<group>"; };
-		A2EF927F09D67532F44BB80D /* TaskRowMetrics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskRowMetrics.swift; sourceTree = "<group>"; };
 		AC245331D715EA85887C0BA0 /* ListlessiOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListlessiOSApp.swift; sourceTree = "<group>"; };
 		B7588879D0FA1C2A8BCEF14F /* HoverCursorModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HoverCursorModifier.swift; sourceTree = "<group>"; };
 		B88DC6E36FA41DCB6CEB9647 /* Listless macOS Unit Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Listless macOS Unit Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
 		BAE264D30C7692457B92E518 /* KeyValueSyncBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyValueSyncBridge.swift; sourceTree = "<group>"; };
 		BC845482926A73B0BF820328 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
 		BFF7D84B54AE70036D205CA4 /* PullToCreate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PullToCreate.swift; sourceTree = "<group>"; };
+		C0DF205300C6B51A53B256D6 /* ItemRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemRowView.swift; sourceTree = "<group>"; };
 		C14858BDFD1FD5119F1F24A6 /* PersistenceController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersistenceController.swift; sourceTree = "<group>"; };
 		C4DB90B3400C191460F4F4BD /* AppColors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppColors.swift; sourceTree = "<group>"; };
 		C611E04943F1D82D6F975592 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
 		C6812E535A24C599C28F9278 /* Listless watchOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Listless watchOS.app"; sourceTree = BUILT_PRODUCTS_DIR; };
 		C71466C5CD1A5BA984352F8D /* Listless iOS Unit Tests.xctest */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = "Listless iOS Unit Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
 		C9B14DC786A336008AAB78EE /* .gitkeep */ = {isa = PBXFileReference; path = .gitkeep; sourceTree = "<group>"; };
-		CB43816B8E7F083A2AD07F28 /* TaskListView+Toolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TaskListView+Toolbar.swift"; sourceTree = "<group>"; };
 		CCB5F87A520B1CD47F2F71D0 /* Listless macOS UI Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Listless macOS UI Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
 		CF3374CE58E7D9378C6997D2 /* SyncDiagnosticsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncDiagnosticsView.swift; sourceTree = "<group>"; };
-		D2C018476BD91B73870244B9 /* TaskListViewProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskListViewProtocol.swift; sourceTree = "<group>"; };
-		D2D9CDDA8913CD116FB4DA74 /* TaskListView+PullGestures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TaskListView+PullGestures.swift"; sourceTree = "<group>"; };
 		D3E995954787F0A14CCFF348 /* AboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutView.swift; sourceTree = "<group>"; };
 		D43D37CE25806380C0B13466 /* BuildNumber.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = BuildNumber.xcconfig; sourceTree = "<group>"; };
-		D57C3CC81C4380EFAE4DB910 /* TaskRowDragGesture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskRowDragGesture.swift; sourceTree = "<group>"; };
 		D640E7D21735C62A30A26DA4 /* ClickableTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClickableTextField.swift; sourceTree = "<group>"; };
 		DA6BD768FB1788D0BC58589F /* FPSOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FPSOverlay.swift; sourceTree = "<group>"; };
-		DC3DEE364304587D280C5672 /* TaskStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskStore.swift; sourceTree = "<group>"; };
-		E06485DBE35B60868E14202A /* TaskRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskRowView.swift; sourceTree = "<group>"; };
+		DB4B98BD127A56CE4669DCD5 /* ItemListView+Undo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ItemListView+Undo.swift"; sourceTree = "<group>"; };
+		DCCE85438FE23E43095B2C25 /* ItemRowSwipeGesture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemRowSwipeGesture.swift; sourceTree = "<group>"; };
+		E209A0014EE95F2BC300CE42 /* ItemListView+SyncUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ItemListView+SyncUI.swift"; sourceTree = "<group>"; };
 		E2174C43654733E9D4023157 /* PlatformTextFieldWidthModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlatformTextFieldWidthModifier.swift; sourceTree = "<group>"; };
-		E51B8129962E5CC78ECDDC2B /* TaskListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskListView.swift; sourceTree = "<group>"; };
+		E71FBC3E19D32F527B5FE9E6 /* ItemRowMetrics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemRowMetrics.swift; sourceTree = "<group>"; };
 		E8448C5778F75F52719114AF /* PlatformTextFieldWidthModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlatformTextFieldWidthModifier.swift; sourceTree = "<group>"; };
+		E85BFCBD4DCB35CC1C8F9401 /* ItemListView+PullGestures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ItemListView+PullGestures.swift"; sourceTree = "<group>"; };
 		E8B5E429B253183F887C5FD6 /* KeyCommandBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyCommandBridge.swift; sourceTree = "<group>"; };
 		E9288507CE6023425D1DE724 /* SyncDiagnosticsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncDiagnosticsView.swift; sourceTree = "<group>"; };
 		EADAA53B8BCBC80AEFF191EF /* ListlessMacUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListlessMacUITests.swift; sourceTree = "<group>"; };
 		F15BF645DEDE8D9E94DB508B /* BackgroundClickMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundClickMonitor.swift; sourceTree = "<group>"; };
 		F1E998119283F784B9ADEE28 /* AppColors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppColors.swift; sourceTree = "<group>"; };
-		F416DD868A4C044F0D64F8D0 /* TaskRowDragGesture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskRowDragGesture.swift; sourceTree = "<group>"; };
+		F31EDB2C34C8B255000A2525 /* ItemListViewProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemListViewProtocol.swift; sourceTree = "<group>"; };
 		FA5D58EC1FBAA96E83A79445 /* PlatformScrollIndicatorsModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlatformScrollIndicatorsModifier.swift; sourceTree = "<group>"; };
-		FBB8A3BEB346267B30B4675F /* TaskItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskItem.swift; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 
 /* Begin PBXGroup section */
@@ -256,14 +256,14 @@
 				46C46FC97E6DB6FF81AC5C22 /* AppCommands.swift */,
 				DA6BD768FB1788D0BC58589F /* FPSOverlay.swift */,
 				B7588879D0FA1C2A8BCEF14F /* HoverCursorModifier.swift */,
+				6CF4BADA48F30C5F471EA910 /* ItemCardModifier.swift */,
+				05AB46824DCDD04903EA4C82 /* ItemRowDragGesture.swift */,
+				E71FBC3E19D32F527B5FE9E6 /* ItemRowMetrics.swift */,
+				DCCE85438FE23E43095B2C25 /* ItemRowSwipeGesture.swift */,
 				E8B5E429B253183F887C5FD6 /* KeyCommandBridge.swift */,
 				FA5D58EC1FBAA96E83A79445 /* PlatformScrollIndicatorsModifier.swift */,
 				E2174C43654733E9D4023157 /* PlatformTextFieldWidthModifier.swift */,
 				6ECE0E961F87BA32FA87BF90 /* TappableTextField.swift */,
-				2143744C421C67E53A2674CF /* TaskCardModifier.swift */,
-				D57C3CC81C4380EFAE4DB910 /* TaskRowDragGesture.swift */,
-				A2EF927F09D67532F44BB80D /* TaskRowMetrics.swift */,
-				067D666DF0AF1C53404ECF7C /* TaskRowSwipeGesture.swift */,
 			);
 			path = Helpers;
 			sourceTree = "<group>";
@@ -311,9 +311,9 @@
 				F15BF645DEDE8D9E94DB508B /* BackgroundClickMonitor.swift */,
 				D640E7D21735C62A30A26DA4 /* ClickableTextField.swift */,
 				9F5D8B5866362D422A2A331C /* HoverCursorModifier.swift */,
+				03A80C91DBDA44C879958098 /* ItemRowDragGesture.swift */,
 				466F9B0E407DF1F5B4789531 /* PlatformScrollIndicatorsModifier.swift */,
 				E8448C5778F75F52719114AF /* PlatformTextFieldWidthModifier.swift */,
-				F416DD868A4C044F0D64F8D0 /* TaskRowDragGesture.swift */,
 			);
 			path = Helpers;
 			sourceTree = "<group>";
@@ -342,12 +342,12 @@
 			children = (
 				D3E995954787F0A14CCFF348 /* AboutView.swift */,
 				67CB2A88C7F19F8305EBED43 /* DraftRowView.swift */,
+				3116A37F1353BF6E18308DD2 /* ItemListView.swift */,
+				C0DF205300C6B51A53B256D6 /* ItemRowView.swift */,
 				567DBAC2A39FA2760D006AAB /* PullToClear.swift */,
 				BFF7D84B54AE70036D205CA4 /* PullToCreate.swift */,
 				C611E04943F1D82D6F975592 /* SettingsView.swift */,
 				E9288507CE6023425D1DE724 /* SyncDiagnosticsView.swift */,
-				E51B8129962E5CC78ECDDC2B /* TaskListView.swift */,
-				199CC2F58DD7CBA3F2229366 /* TaskRowView.swift */,
 				658295C1386BFF48CE3C2419 /* UndoToast.swift */,
 			);
 			path = Views;
@@ -357,9 +357,9 @@
 			isa = PBXGroup;
 			children = (
 				17DD7EDA74DAAFA27C84CA08 /* AccentColor.swift */,
+				848DBC251E2D2EB7BD089768 /* ItemListTypes.swift */,
+				F31EDB2C34C8B255000A2525 /* ItemListViewProtocol.swift */,
 				4669414A460FC0758D5B49A8 /* KeyboardNavigationModifier.swift */,
-				6F9754B32B2FD5AF8552BC85 /* TaskListTypes.swift */,
-				D2C018476BD91B73870244B9 /* TaskListViewProtocol.swift */,
 			);
 			path = Helpers;
 			sourceTree = "<group>";
@@ -367,13 +367,13 @@
 		8629C1C94770B3B0D08B580D /* Extensions */ = {
 			isa = PBXGroup;
 			children = (
-				3FA75CA18C6270D68A755AEB /* TaskListView+Drag.swift */,
-				712ADAD0FE66175DAA9A6D50 /* TaskListView+NavigationHeader.swift */,
-				D2D9CDDA8913CD116FB4DA74 /* TaskListView+PullGestures.swift */,
-				5A6D74EDDD7E3FC150064FB5 /* TaskListView+PullToClear.swift */,
-				9AD8D4C29E09FEE78AE5AB79 /* TaskListView+PullToCreate.swift */,
-				CB43816B8E7F083A2AD07F28 /* TaskListView+Toolbar.swift */,
-				8AACB5E40BEBA75E7FE8B930 /* TaskListView+Undo.swift */,
+				4D4ED76F5996A4308D2BC7C8 /* ItemListView+Drag.swift */,
+				93CC030045617DBC90812D79 /* ItemListView+NavigationHeader.swift */,
+				E85BFCBD4DCB35CC1C8F9401 /* ItemListView+PullGestures.swift */,
+				23060125DF03EE84F3AED8CB /* ItemListView+PullToClear.swift */,
+				78232E9D9F77FA3630F9D089 /* ItemListView+PullToCreate.swift */,
+				869254E16B52F36616416DB4 /* ItemListView+Toolbar.swift */,
+				DB4B98BD127A56CE4669DCD5 /* ItemListView+Undo.swift */,
 			);
 			path = Extensions;
 			sourceTree = "<group>";
@@ -382,9 +382,9 @@
 			isa = PBXGroup;
 			children = (
 				74255E6B6C40899E9B17D927 /* .gitkeep */,
+				138DCA35ED82A745E4745175 /* ItemEntity.swift */,
+				2B8EC97702E7B218A03B7898 /* ItemStore.swift */,
 				C093494053E6C348F245D4EC /* Listless.xcdatamodeld */,
-				FBB8A3BEB346267B30B4675F /* TaskItem.swift */,
-				DC3DEE364304587D280C5672 /* TaskStore.swift */,
 			);
 			path = Models;
 			sourceTree = "<group>";
@@ -405,8 +405,8 @@
 		A1A9B54C4CBA03BEE12B34A9 /* Extensions */ = {
 			isa = PBXGroup;
 			children = (
-				72B4668483D05A6ECA142B89 /* TaskListView+Logic.swift */,
-				7A7BD42B1E3C71333FA24893 /* TaskListView+SyncUI.swift */,
+				82F2C024B6C1F2F1FD7A25B0 /* ItemListView+Logic.swift */,
+				E209A0014EE95F2BC300CE42 /* ItemListView+SyncUI.swift */,
 			);
 			path = Extensions;
 			sourceTree = "<group>";
@@ -414,9 +414,9 @@
 		D12ECC901ABED96B86CC85B5 /* Views */ = {
 			isa = PBXGroup;
 			children = (
+				37F80F7BB632A04A687890F0 /* ItemListView.swift */,
+				3048ACA1CAF1284F99E1400E /* ItemRowView.swift */,
 				CF3374CE58E7D9378C6997D2 /* SyncDiagnosticsView.swift */,
-				632DA39B24C4CF1528A1A24D /* TaskListView.swift */,
-				E06485DBE35B60868E14202A /* TaskRowView.swift */,
 			);
 			path = Views;
 			sourceTree = "<group>";
@@ -476,8 +476,8 @@
 		F0EB58775713E4C83A8D477A /* Views */ = {
 			isa = PBXGroup;
 			children = (
-				56DEECB5DC31DE5F34FB55A3 /* TaskListView.swift */,
-				92D1AF0DE30657AAD86482CA /* TaskRowView.swift */,
+				20A09A6C1C2251E96E1B5D96 /* ItemListView.swift */,
+				4C7CB071709951F52C7742A3 /* ItemRowView.swift */,
 			);
 			path = Views;
 			sourceTree = "<group>";
@@ -487,10 +487,10 @@
 			children = (
 				C9B14DC786A336008AAB78EE /* .gitkeep */,
 				51C3B9FF63645B35E09CF1B1 /* CloudKitErrorClassifierTests.swift */,
-				5E88DDD119EEEECCD45F36D2 /* TaskStoreCompletionTests.swift */,
-				9B2BBE01D99CDA278BCB9F49 /* TaskStoreEdgeCaseTests.swift */,
-				2D3A2DDCE24E54ABCCFBBD4C /* TaskStoreOrderingTests.swift */,
-				967F7ECEB3915CEDCE584872 /* TaskStoreTests.swift */,
+				114A89FD89C8EFB5771B7242 /* ItemStoreCompletionTests.swift */,
+				52FCE9FC4EFD26C9016389B7 /* ItemStoreEdgeCaseTests.swift */,
+				6D1DF1C029FA3C6B12A58E7F /* ItemStoreOrderingTests.swift */,
+				47892E3231D64596A2A00105 /* ItemStoreTests.swift */,
 			);
 			name = Unit;
 			path = Tests/Unit;
@@ -499,7 +499,7 @@
 		F7279CE4B2501F9F5111A3D8 /* Extensions */ = {
 			isa = PBXGroup;
 			children = (
-				2A440253D045A0896D94ADD0 /* TaskListView+Toolbar.swift */,
+				03343E3D48A0EF2B146528E2 /* ItemListView+Toolbar.swift */,
 			);
 			path = Extensions;
 			sourceTree = "<group>";
@@ -735,10 +735,10 @@
 			buildActionMask = 2147483647;
 			files = (
 				A10BF9D0C850105E4FA1A2AD /* CloudKitErrorClassifierTests.swift in Sources */,
-				8FA8398315B67F12EA25FA35 /* TaskStoreCompletionTests.swift in Sources */,
-				54FCE72978F20AA64855B521 /* TaskStoreEdgeCaseTests.swift in Sources */,
-				CDE538A0B3CA14B1CB421ED8 /* TaskStoreOrderingTests.swift in Sources */,
-				E14F8177232BBC75FEEE1E2C /* TaskStoreTests.swift in Sources */,
+				066E095075895DE73F217816 /* ItemStoreCompletionTests.swift in Sources */,
+				84B31DA3CF21D57742C9217A /* ItemStoreEdgeCaseTests.swift in Sources */,
+				F2845953BA77F171FDA2A59F /* ItemStoreOrderingTests.swift in Sources */,
+				12384AB44B4578E19EF8B0B7 /* ItemStoreTests.swift in Sources */,
 				E12C1304464FC7799856B2BA /* TestHelpers.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
@@ -763,6 +763,16 @@
 				E4BD761E34CBB84CE80F7F49 /* CloudKitErrorClassifier.swift in Sources */,
 				E429067963379F99DD184FED /* CloudKitSyncMonitor.swift in Sources */,
 				882695E7AE463C0F39ACFF3C /* HoverCursorModifier.swift in Sources */,
+				BB16A28C4DA1695B722B45B2 /* ItemEntity.swift in Sources */,
+				2921A4A343D43664F954B588 /* ItemListTypes.swift in Sources */,
+				851F46417FE40D6BC765BC70 /* ItemListView+Logic.swift in Sources */,
+				A92F8503815056D3183CD7AE /* ItemListView+SyncUI.swift in Sources */,
+				9E75E4AEEF577E0096E22DBA /* ItemListView+Toolbar.swift in Sources */,
+				2323CE015C01C6354C557F90 /* ItemListView.swift in Sources */,
+				A34F23F6314067EFB35FB209 /* ItemListViewProtocol.swift in Sources */,
+				BDA8D53342F745B27B72B242 /* ItemRowDragGesture.swift in Sources */,
+				194CAF0FBC308BE96CE8AA7B /* ItemRowView.swift in Sources */,
+				D24133A6C0105FE8E4528EF2 /* ItemStore.swift in Sources */,
 				2CCB5FE0084742D018E52A3D /* KeyValueSyncBridge.swift in Sources */,
 				172F2DD978988E207610055F /* KeyboardNavigationModifier.swift in Sources */,
 				96617677059FABDBB80D642B /* Listless.xcdatamodeld in Sources */,
@@ -771,16 +781,6 @@
 				5761B201BF46FCA9C5C98CEF /* PlatformScrollIndicatorsModifier.swift in Sources */,
 				DEE187A790A4058FE4AFDB2E /* PlatformTextFieldWidthModifier.swift in Sources */,
 				568635BB34CD7EBE24E66A15 /* SyncDiagnosticsView.swift in Sources */,
-				C1FE091454864C4BBBBEB077 /* TaskItem.swift in Sources */,
-				93275BD83342D6CE94272E6A /* TaskListTypes.swift in Sources */,
-				E5878BAA0EA66A94440E2B0F /* TaskListView+Logic.swift in Sources */,
-				642151C8EEA34DAD76C49FA1 /* TaskListView+SyncUI.swift in Sources */,
-				322EE74CBED492693B92AD59 /* TaskListView+Toolbar.swift in Sources */,
-				FDD09FECEED48EC9598538F4 /* TaskListView.swift in Sources */,
-				365FDEE6823D7A114F3FB12A /* TaskListViewProtocol.swift in Sources */,
-				5035EC4C7518A5FF9AD454CA /* TaskRowDragGesture.swift in Sources */,
-				3D1F551A03B97ECF4E3DC8B0 /* TaskRowView.swift in Sources */,
-				3ABE52A15C2059D8D5570528 /* TaskStore.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -791,14 +791,14 @@
 				543C8A0C8A9E2F77B2C0060F /* AccentColor.swift in Sources */,
 				F046B5A7F4C0BD7AAF46A69C /* CloudKitErrorClassifier.swift in Sources */,
 				53700EA974FE4AD771FE89EC /* CloudKitSyncMonitor.swift in Sources */,
+				12FC79D42A110E2CDC753CCE /* ItemEntity.swift in Sources */,
+				2F5309630692E89276CC3149 /* ItemListView.swift in Sources */,
+				8FB18395E5436F6C91A0F077 /* ItemRowView.swift in Sources */,
+				47D9272442A5F15B324D3DAC /* ItemStore.swift in Sources */,
 				264BD64C1DD30376E8BDAF79 /* KeyValueSyncBridge.swift in Sources */,
 				6FE9D247153209BD4CFD9E34 /* Listless.xcdatamodeld in Sources */,
 				65E97DE8C190E9E9B71EC356 /* ListlessWatchApp.swift in Sources */,
 				A119D0130DB77E30FBCB5436 /* PersistenceController.swift in Sources */,
-				684A80B692237B74EDF3C7A8 /* TaskItem.swift in Sources */,
-				07E2BB2FF9E75A922C3756AB /* TaskListView.swift in Sources */,
-				03BC4562430EC105221AB43B /* TaskRowView.swift in Sources */,
-				57E864F71C9C84B63A28E14F /* TaskStore.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -815,6 +815,25 @@
 				0F2E6817C315B947033DA2BE /* DraftRowView.swift in Sources */,
 				E2FC6CF95A2C59CA147172EE /* FPSOverlay.swift in Sources */,
 				E60F81C7B930AACE67746759 /* HoverCursorModifier.swift in Sources */,
+				4263E6472020564AA702D117 /* ItemCardModifier.swift in Sources */,
+				930AE396D982D7C46E498311 /* ItemEntity.swift in Sources */,
+				DBFE5DADCAAF26CF77245410 /* ItemListTypes.swift in Sources */,
+				90BC899E66B98517A91F2627 /* ItemListView+Drag.swift in Sources */,
+				F56B03B24F33B31394D91512 /* ItemListView+Logic.swift in Sources */,
+				89C4109374BD64464B0018B7 /* ItemListView+NavigationHeader.swift in Sources */,
+				20D2A3E4694AE35AE6CF4AAD /* ItemListView+PullGestures.swift in Sources */,
+				3383645AE13E9C3AAECFBD0B /* ItemListView+PullToClear.swift in Sources */,
+				5716CA28163DE0EAAA875AEE /* ItemListView+PullToCreate.swift in Sources */,
+				63C08E89303BD17601271D2C /* ItemListView+SyncUI.swift in Sources */,
+				E627F653B5A692B42A75FFBA /* ItemListView+Toolbar.swift in Sources */,
+				E0BDC0FCAB43CEE0C9AC5279 /* ItemListView+Undo.swift in Sources */,
+				9082E96001188E516B7F903B /* ItemListView.swift in Sources */,
+				C7C69D883B45F1B4CE979AF7 /* ItemListViewProtocol.swift in Sources */,
+				DC103B1BFDC5940F63DD48ED /* ItemRowDragGesture.swift in Sources */,
+				D9325B2D23FF2CD644A1A7E3 /* ItemRowMetrics.swift in Sources */,
+				B0F8B09E2AB38C8C4AF74C10 /* ItemRowSwipeGesture.swift in Sources */,
+				DB0CBB6C3EE56406AF86FDE3 /* ItemRowView.swift in Sources */,
+				790843E40F28B4E186F88F16 /* ItemStore.swift in Sources */,
 				12E43D0CD9124037022E3C38 /* KeyCommandBridge.swift in Sources */,
 				19699EC4FF57EF0D636B65E3 /* KeyValueSyncBridge.swift in Sources */,
 				F6587B84ECC6BFE92A5FB493 /* KeyboardNavigationModifier.swift in Sources */,
@@ -828,25 +847,6 @@
 				072594B24D88AD6D1DF7AFE5 /* SettingsView.swift in Sources */,
 				4DD2030E321567BD25661760 /* SyncDiagnosticsView.swift in Sources */,
 				0F12D56A528FCBF8A67864CB /* TappableTextField.swift in Sources */,
-				354687FCBBCC2432340BD5EB /* TaskCardModifier.swift in Sources */,
-				0ACA67F6578EFF181EE5C9A7 /* TaskItem.swift in Sources */,
-				1477B460E3DEFA3FDB7DA65B /* TaskListTypes.swift in Sources */,
-				82E75475E13FD847DDDC69D8 /* TaskListView+Drag.swift in Sources */,
-				CAD142ED738A83371DFF8F5B /* TaskListView+Logic.swift in Sources */,
-				26F609518DE1055DF09B0159 /* TaskListView+NavigationHeader.swift in Sources */,
-				5DB1231FEF24A9E4BC1E56B0 /* TaskListView+PullGestures.swift in Sources */,
-				8A8D164CAA7B95B4C7435A7C /* TaskListView+PullToClear.swift in Sources */,
-				80BCA654FE694D8A77D9BF93 /* TaskListView+PullToCreate.swift in Sources */,
-				4E5A0A02121E02124F80E320 /* TaskListView+SyncUI.swift in Sources */,
-				77970A2323A598E830D95301 /* TaskListView+Toolbar.swift in Sources */,
-				879608140FA8D7A32078C3CF /* TaskListView+Undo.swift in Sources */,
-				C169823665158AA347A63990 /* TaskListView.swift in Sources */,
-				CC99A96BBC089C423F582E4F /* TaskListViewProtocol.swift in Sources */,
-				2C53717A0EAFA39240615C9A /* TaskRowDragGesture.swift in Sources */,
-				FF58A3AB2BCFFA42BF2F6413 /* TaskRowMetrics.swift in Sources */,
-				182D9FB61E3A3650D7D83D2A /* TaskRowSwipeGesture.swift in Sources */,
-				7E366008FF9335A77FE1636D /* TaskRowView.swift in Sources */,
-				91EDF52C7C5C0B35E9D8B51E /* TaskStore.swift in Sources */,
 				1A66A0454558B207AF9265D4 /* UndoToast.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
@@ -864,10 +864,10 @@
 			buildActionMask = 2147483647;
 			files = (
 				CA439FD953EA59A9664E0D74 /* CloudKitErrorClassifierTests.swift in Sources */,
-				F50E8B8F73F64D8E641DC74C /* TaskStoreCompletionTests.swift in Sources */,
-				99977BFA37FBAAA49AF6B71E /* TaskStoreEdgeCaseTests.swift in Sources */,
-				269B93D5543770B464DFB37A /* TaskStoreOrderingTests.swift in Sources */,
-				ECD5E7EA05AE1C00B38C939E /* TaskStoreTests.swift in Sources */,
+				C89D4C17F91AF91F18B6EF4E /* ItemStoreCompletionTests.swift in Sources */,
+				620D9398218A88B7E4C2331C /* ItemStoreEdgeCaseTests.swift in Sources */,
+				DAEA21531CFEA94D335FFC6E /* ItemStoreOrderingTests.swift in Sources */,
+				1E31935122C5E97907B30C70 /* ItemStoreTests.swift in Sources */,
 				1AA328A921EF8A7FDD03119A /* TestHelpers.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;

diff --git a/Listless/Extensions/TaskListView+Logic.swift b/Listless/Extensions/ItemListView+Logic.swift

@@ -1,41 +1,41 @@
 import SwiftUI
 
-extension TaskListViewProtocol {
+extension ItemListViewProtocol {
 
     // MARK: - Computed Properties
 
-    var activeTasks: [TaskItem] {
-        Array(tasks.filter { !$0.isDeleted && !$0.isCompleted })
+    var activeItems: [ItemEntity] {
+        Array(items.filter { !$0.isDeleted && !$0.isCompleted })
             .sorted { $0.sortOrder < $1.sortOrder }
     }
 
-    var displayActiveTasks: [TaskItem] {
+    var displayActiveItems: [ItemEntity] {
         guard let visualOrder else {
-            return activeTasks
+            return activeItems
         }
 
         return visualOrder.compactMap { id in
-            activeTasks.first(where: { $0.id == id })
+            activeItems.first(where: { $0.id == id })
         }
     }
 
-    var completedTasks: [TaskItem] {
-        Array(tasks.filter { !$0.isDeleted && $0.isCompleted })
+    var completedItems: [ItemEntity] {
+        Array(items.filter { !$0.isDeleted && $0.isCompleted })
             .sorted { $0.completedOrder > $1.completedOrder }
     }
 
-    var allTasksInDisplayOrder: [TaskItem] {
-        displayActiveTasks + completedTasks
+    var allItemsInDisplayOrder: [ItemEntity] {
+        displayActiveItems + completedItems
     }
 
-    var editingTaskID: UUID? {
-        if case .task(let id) = focusedField {
+    var editingItemID: UUID? {
+        if case .item(let id) = focusedField {
             return id
         }
         return nil
     }
 
-    var draggedTaskID: UUID? {
+    var draggedItemID: UUID? {
         if case .dragging(let id, _) = dragState {
             return id
         }
@@ -53,12 +53,12 @@ extension TaskListViewProtocol {
         syncMonitor.ingest(error: error)
     }
 
-    private func isLastActiveTask(_ taskID: UUID) -> Bool {
-        guard let lastTask = activeTasks.last else { return false }
-        return lastTask.id == taskID
+    private func isLastActiveItem(_ itemID: UUID) -> Bool {
+        guard let lastItem = activeItems.last else { return false }
+        return lastItem.id == itemID
     }
 
-    func draftID(for placement: DraftTaskPlacement) -> UUID {
+    func draftID(for placement: DraftItemPlacement) -> UUID {
         switch placement {
         case .prepend:
             draftPrependRowID
@@ -67,8 +67,8 @@ extension TaskListViewProtocol {
         }
     }
 
-    func draftPlacement(for taskID: UUID) -> DraftTaskPlacement? {
-        switch taskID {
+    func draftPlacement(for itemID: UUID) -> DraftItemPlacement? {
+        switch itemID {
         case draftPrependRowID:
             .prepend
         case draftAppendRowID:
@@ -78,134 +78,134 @@ extension TaskListViewProtocol {
         }
     }
 
-    // MARK: - Task Creation
+    // MARK: - Item Creation
 
-    func createNewTaskAtTop() -> UUID {
-        revealDraftTask(at: .prepend)
+    func createNewItemAtTop() -> UUID {
+        revealDraftItem(at: .prepend)
         return draftPrependRowID
     }
 
-    func createNewTask() {
-        revealDraftTask(at: .append)
+    func createNewItem() {
+        revealDraftItem(at: .append)
     }
 
-    func revealDraftTask(at placement: DraftTaskPlacement) {
+    func revealDraftItem(at placement: DraftItemPlacement) {
         if draftPlacement != placement, draftPlacement != nil {
-            commitDraftTask()
+            commitDraftItem()
         }
 
         clearDragState()
-        let taskID = draftID(for: placement)
+        let itemID = draftID(for: placement)
         draftTitle = ""
         draftPlacement = placement
-        fState.pendingFocus = .task(taskID)
-        focusedField = .task(taskID)
-        fState.selectedTaskID = taskID
+        fState.pendingFocus = .item(itemID)
+        focusedField = .item(itemID)
+        fState.selectedItemID = itemID
     }
 
-    func beginDraftTaskEditing(_ placement: DraftTaskPlacement) {
+    func beginDraftItemEditing(_ placement: DraftItemPlacement) {
         guard draftPlacement == placement else { return }
-        let taskID = draftID(for: placement)
-        fState.selectedTaskID = taskID
-        if case .task(let id) = fState.pendingFocus, id == taskID {
+        let itemID = draftID(for: placement)
+        fState.selectedItemID = itemID
+        if case .item(let id) = fState.pendingFocus, id == itemID {
             fState.pendingFocus = nil
         }
     }
 
-    func commitDraftTask(shouldCreateNewTask: Bool = false) {
+    func commitDraftItem(shouldCreateNewItem: Bool = false) {
         guard let placement = draftPlacement else { return }
-        let taskID = draftID(for: placement)
+        let itemID = draftID(for: placement)
         let title = draftTitle.trimmingCharacters(in: .whitespacesAndNewlines)
 
-        // Clear fState.pendingFocus before clearDraftTaskUI so that the iOS
+        // Clear fState.pendingFocus before clearDraftItemUI so that the iOS
         // onChange(of: focusedFieldBinding) nil-redirect doesn't re-focus
         // the draft row via a stale fState.pendingFocus value.
-        if case .task(let id) = fState.pendingFocus, id == taskID {
+        if case .item(let id) = fState.pendingFocus, id == itemID {
             fState.pendingFocus = nil
         }
 
-        clearDraftTaskUI(at: placement, hasTitle: !title.isEmpty)
+        clearDraftItemUI(at: placement, hasTitle: !title.isEmpty)
 
-        if fState.selectedTaskID == taskID {
-            fState.selectedTaskID = nil
+        if fState.selectedItemID == itemID {
+            fState.selectedItemID = nil
         }
 
         guard !title.isEmpty else { return }
 
         do {
-            let task = switch placement {
+            let item = switch placement {
             case .prepend:
-                try store.createTask(title: title, atBeginning: true)
+                try store.createItem(title: title, atBeginning: true)
             case .append:
-                try store.createTask(title: title)
+                try store.createItem(title: title)
             }
             try store.save()
             if placement == .append {
-                fState.selectedTaskID = task.id
+                fState.selectedItemID = item.id
             }
         } catch {
             presentStoreError(error)
         }
 
-        if shouldCreateNewTask, placement == .append {
-            revealDraftTask(at: .append)
+        if shouldCreateNewItem, placement == .append {
+            revealDraftItem(at: .append)
         }
     }
 
-    func createTask(title: String, afterTaskID: UUID) {
+    func createItem(title: String, afterItemID: UUID) {
         clearDragState()
         do {
-            let sortOrder = try sortOrderAfter(taskID: afterTaskID)
-            let newTask = try store.createTask(title: title, sortOrder: sortOrder)
+            let sortOrder = try sortOrderAfter(itemID: afterItemID)
+            let newItem = try store.createItem(title: title, sortOrder: sortOrder)
             try store.save()
-            fState.selectedTaskID = newTask.id
+            fState.selectedItemID = newItem.id
             focusedField = .scrollView
         } catch {
             presentStoreError(error)
         }
     }
 
-    private func sortOrderAfter(taskID: UUID) throws -> Int64? {
-        guard let afterIndex = activeTasks.firstIndex(where: { $0.id == taskID }) else {
+    private func sortOrderAfter(itemID: UUID) throws -> Int64? {
+        guard let afterIndex = activeItems.firstIndex(where: { $0.id == itemID }) else {
             return nil
         }
-        let afterTask = activeTasks[afterIndex]
-        if afterIndex + 1 < activeTasks.count {
-            let nextTask = activeTasks[afterIndex + 1]
-            let midpoint = (afterTask.sortOrder + nextTask.sortOrder) / 2
-            if midpoint == afterTask.sortOrder {
+        let afterItem = activeItems[afterIndex]
+        if afterIndex + 1 < activeItems.count {
+            let nextItem = activeItems[afterIndex + 1]
+            let midpoint = (afterItem.sortOrder + nextItem.sortOrder) / 2
+            if midpoint == afterItem.sortOrder {
                 // Consecutive sort orders leave no room; re-normalise with 1000-unit gaps
-                // then recompute. Core Data's identity map ensures afterTask/nextTask reflect
+                // then recompute. Core Data's identity map ensures afterItem/nextItem reflect
                 // the updated values immediately after normalisation.
                 try store.normalizeSortOrders()
-                return (afterTask.sortOrder + nextTask.sortOrder) / 2
+                return (afterItem.sortOrder + nextItem.sortOrder) / 2
             }
             return midpoint
         } else {
-            return afterTask.sortOrder + 1000
+            return afterItem.sortOrder + 1000
         }
     }
 
     // MARK: - Interaction Handlers
 
     func handleBackgroundTap() {
-        let isTaskFocused = if case .task = focusedField { true } else { false }
+        let isItemFocused = if case .item = focusedField { true } else { false }
 
-        if isTaskFocused || fState.selectedTaskID != nil {
+        if isItemFocused || fState.selectedItemID != nil {
             fState.pendingFocus = nil
             if draftPlacement != nil {
-                commitDraftTask()
+                commitDraftItem()
             }
-            fState.selectedTaskID = nil
+            fState.selectedItemID = nil
             focusedField = nil
         } else {
-            revealDraftTask(at: .append)
+            revealDraftItem(at: .append)
         }
     }
 
     func handleFocusChange(from oldValue: FocusField?, to newValue: FocusField?) {
-        let oldID = taskID(from: oldValue)
-        let newID = taskID(from: newValue)
+        let oldID = itemID(from: oldValue)
+        let newID = itemID(from: newValue)
 
         guard oldID != newID, let oldID else {
             return
@@ -215,102 +215,102 @@ extension TaskListViewProtocol {
             return
         }
 
-        deleteIfEmpty(taskID: oldID)
+        deleteIfEmpty(itemID: oldID)
     }
 
-    private func taskID(from field: FocusField?) -> UUID? {
-        guard case .task(let id) = field else { return nil }
+    private func itemID(from field: FocusField?) -> UUID? {
+        guard case .item(let id) = field else { return nil }
         return id
     }
 
-    private func deleteIfEmpty(taskID: UUID) {
-        if case .task(let pendingTaskID) = fState.pendingFocus, pendingTaskID == taskID {
+    private func deleteIfEmpty(itemID: UUID) {
+        if case .item(let pendingItemID) = fState.pendingFocus, pendingItemID == itemID {
             return
         }
 
-        guard let task = tasks.first(where: { $0.id == taskID }) else {
+        guard let item = items.first(where: { $0.id == itemID }) else {
             return
         }
-        let trimmedTitle = task.title.trimmingCharacters(in: .whitespacesAndNewlines)
+        let trimmedTitle = item.title.trimmingCharacters(in: .whitespacesAndNewlines)
         guard trimmedTitle.isEmpty else { return }
 
-        managedObjectContext.undoManager?.removeAllActions(withTarget: task)
+        managedObjectContext.undoManager?.removeAllActions(withTarget: item)
         managedObjectContext.undoManager?.disableUndoRegistration()
-        deleteTask(task)
+        deleteItem(item)
         managedObjectContext.undoManager?.enableUndoRegistration()
     }
 
-    func updateTitle(_ task: TaskItem, _ title: String) {
-        guard task.title != title else { return }
+    func updateTitle(_ item: ItemEntity, _ title: String) {
+        guard item.title != title else { return }
         do {
-            try store.updateWithoutSaving(taskID: task.id, title: title)
+            try store.updateWithoutSaving(itemID: item.id, title: title)
         } catch {
             presentStoreError(error)
         }
     }
 
-    func toggleCompletion(_ task: TaskItem) {
+    func toggleCompletion(_ item: ItemEntity) {
         do {
-            if task.isCompleted {
-                try store.uncomplete(taskID: task.id)
+            if item.isCompleted {
+                try store.uncomplete(itemID: item.id)
             } else {
-                try store.complete(taskID: task.id)
+                try store.complete(itemID: item.id)
             }
         } catch {
             presentStoreError(error)
         }
     }
 
-    func handleSwipeComplete(_ taskID: UUID) {
-        guard let task = tasks.first(where: { $0.id == taskID }) else { return }
-        toggleCompletion(task)
+    func handleSwipeComplete(_ itemID: UUID) {
+        guard let item = items.first(where: { $0.id == itemID }) else { return }
+        toggleCompletion(item)
     }
 
-    func handleSwipeDelete(_ taskID: UUID) {
-        guard let task = tasks.first(where: { $0.id == taskID }) else { return }
-        deleteTask(task)
+    func handleSwipeDelete(_ itemID: UUID) {
+        guard let item = items.first(where: { $0.id == itemID }) else { return }
+        deleteItem(item)
     }
 
-    func selectTask(
-        _ taskID: UUID,
+    func selectItem(
+        _ itemID: UUID,
         extendSelection: Bool = false,
         toggleSelection: Bool = false
     ) {
         if toggleSelection {
             fState.toggleSelection(
-                taskID: taskID,
-                displayOrder: allTasksInDisplayOrder.map(\.id)
+                itemID: itemID,
+                displayOrder: allItemsInDisplayOrder.map(\.id)
             )
-        } else if extendSelection && fState.selectedTaskID != nil {
-            if fState.anchorTaskID == nil {
-                fState.anchorTaskID = fState.cursorTaskID
+        } else if extendSelection && fState.selectedItemID != nil {
+            if fState.anchorItemID == nil {
+                fState.anchorItemID = fState.cursorItemID
             }
             fState.extendSelection(
-                to: taskID,
-                displayOrder: allTasksInDisplayOrder.map(\.id)
+                to: itemID,
+                displayOrder: allItemsInDisplayOrder.map(\.id)
             )
         } else {
-            fState.selectedTaskID = taskID
+            fState.selectedItemID = itemID
         }
     }
 
-    func deleteTask(_ task: TaskItem) {
-        guard !task.isDeleted else { return }
-        let taskID = task.id
+    func deleteItem(_ item: ItemEntity) {
+        guard !item.isDeleted else { return }
+        let itemID = item.id
         do {
-            try store.delete(taskID: taskID)
-            if fState.selectedTaskID == taskID {
-                fState.selectedTaskID = nil
+            try store.delete(itemID: itemID)
+            if fState.selectedItemID == itemID {
+                fState.selectedItemID = nil
             }
         } catch {
             presentStoreError(error)
         }
     }
 
-    func clearCompletedTasks() {
-        for task in completedTasks.reversed() {
+    func clearCompletedItems() {
+        for item in completedItems.reversed() {
             do {
-                try store.delete(taskID: task.id)
+                try store.delete(itemID: item.id)
             } catch {
                 presentStoreError(error)
             }
@@ -324,18 +324,18 @@ extension TaskListViewProtocol {
             return .ignored
         }
 
-        guard let currentID = fState.selectedTaskID else {
-            fState.selectedTaskID = activeTasks.last?.id
+        guard let currentID = fState.selectedItemID else {
+            fState.selectedItemID = activeItems.last?.id
             return .handled
         }
 
-        let displayOrder = allTasksInDisplayOrder
+        let displayOrder = allItemsInDisplayOrder
         guard let currentIndex = displayOrder.firstIndex(where: { $0.id == currentID }) else {
             return .handled
         }
 
         if currentIndex > 0 {
-            fState.selectedTaskID = displayOrder[currentIndex - 1].id
+            fState.selectedItemID = displayOrder[currentIndex - 1].id
         }
         return .handled
     }
@@ -345,18 +345,18 @@ extension TaskListViewProtocol {
             return .ignored
         }
 
-        guard let currentID = fState.selectedTaskID else {
-            fState.selectedTaskID = activeTasks.first?.id ?? completedTasks.first?.id
+        guard let currentID = fState.selectedItemID else {
+            fState.selectedItemID = activeItems.first?.id ?? completedItems.first?.id
             return .handled
         }
 
-        let displayOrder = allTasksInDisplayOrder
+        let displayOrder = allItemsInDisplayOrder
         guard let currentIndex = displayOrder.firstIndex(where: { $0.id == currentID }) else {
             return .handled
         }
 
         if currentIndex < displayOrder.count - 1 {
-            fState.selectedTaskID = displayOrder[currentIndex + 1].id
+            fState.selectedItemID = displayOrder[currentIndex + 1].id
         }
         return .handled
     }
@@ -366,11 +366,11 @@ extension TaskListViewProtocol {
             return .ignored
         }
 
-        let displayOrder = allTasksInDisplayOrder.map(\.id)
+        let displayOrder = allItemsInDisplayOrder.map(\.id)
 
         // If nothing is selected yet, start single-select at the bottom.
-        guard let cursorID = fState.cursorTaskID else {
-            fState.selectedTaskID = activeTasks.last?.id
+        guard let cursorID = fState.cursorItemID else {
+            fState.selectedItemID = activeItems.last?.id
             return .handled
         }
 
@@ -383,7 +383,7 @@ extension TaskListViewProtocol {
         let targetID = displayOrder[cursorIndex - 1]
         // On the first extend, the anchor is wherever the cursor is.
         if !fState.hasMultipleSelection {
-            fState.anchorTaskID = cursorID
+            fState.anchorItemID = cursorID
         }
         fState.extendSelection(to: targetID, displayOrder: displayOrder)
         return .handled
@@ -394,10 +394,10 @@ extension TaskListViewProtocol {
             return .ignored
         }
 
-        let displayOrder = allTasksInDisplayOrder.map(\.id)
+        let displayOrder = allItemsInDisplayOrder.map(\.id)
 
-        guard let cursorID = fState.cursorTaskID else {
-            fState.selectedTaskID = activeTasks.first?.id ?? completedTasks.first?.id
+        guard let cursorID = fState.cursorItemID else {
+            fState.selectedItemID = activeItems.first?.id ?? completedItems.first?.id
             return .handled
         }
 
@@ -409,116 +409,116 @@ extension TaskListViewProtocol {
 
         let targetID = displayOrder[cursorIndex + 1]
         if !fState.hasMultipleSelection {
-            fState.anchorTaskID = cursorID
+            fState.anchorItemID = cursorID
         }
         fState.extendSelection(to: targetID, displayOrder: displayOrder)
         return .handled
     }
 
-    func toggleSelectedTask() -> KeyPress.Result {
+    func toggleSelectedItem() -> KeyPress.Result {
         guard focusedField == .scrollView else { return .ignored }
-        let ids = fState.selectedTaskIDs
+        let ids = fState.selectedItemIDs
         guard !ids.isEmpty else { return .handled }
-        let tasksToToggle = allTasksInDisplayOrder.filter { ids.contains($0.id) }
-        guard !tasksToToggle.isEmpty else { return .handled }
-        let hasActive = tasksToToggle.contains { !$0.isCompleted }
-        let hasCompleted = tasksToToggle.contains { $0.isCompleted }
+        let itemsToToggle = allItemsInDisplayOrder.filter { ids.contains($0.id) }
+        guard !itemsToToggle.isEmpty else { return .handled }
+        let hasActive = itemsToToggle.contains { !$0.isCompleted }
+        let hasCompleted = itemsToToggle.contains { $0.isCompleted }
         guard !(hasActive && hasCompleted) else { return .handled }
-        for task in tasksToToggle {
-            toggleCompletion(task)
+        for item in itemsToToggle {
+            toggleCompletion(item)
         }
         return .handled
     }
 
-    func focusSelectedTask() -> KeyPress.Result {
+    func focusSelectedItem() -> KeyPress.Result {
         guard focusedField == .scrollView else { return .ignored }
         guard !fState.hasMultipleSelection else { return .handled }
-        guard let currentID = fState.selectedTaskID else { return .handled }
-        guard let task = allTasksInDisplayOrder.first(where: { $0.id == currentID }) else {
+        guard let currentID = fState.selectedItemID else { return .handled }
+        guard let item = allItemsInDisplayOrder.first(where: { $0.id == currentID }) else {
             return .handled
         }
-        guard !task.isCompleted else { return .handled }
+        guard !item.isCompleted else { return .handled }
         startEditing(currentID)
         return .handled
     }
 
-    func deleteSelectedTask() -> KeyPress.Result {
+    func deleteSelectedItem() -> KeyPress.Result {
         guard focusedField == .scrollView else {
             return .ignored
         }
-        let ids = fState.selectedTaskIDs
+        let ids = fState.selectedItemIDs
         guard !ids.isEmpty else { return .handled }
-        let displayOrder = allTasksInDisplayOrder
-        let tasksToDelete = displayOrder.filter { ids.contains($0.id) }
-        guard !tasksToDelete.isEmpty else { return .handled }
+        let displayOrder = allItemsInDisplayOrder
+        let itemsToDelete = displayOrder.filter { ids.contains($0.id) }
+        guard !itemsToDelete.isEmpty else { return .handled }
 
-        // Find the next task after the last selected one to move selection to.
+        // Find the next item after the last selected one to move selection to.
         let lastSelectedIndex = displayOrder.lastIndex(where: { ids.contains($0.id) })
-        let nextTask = lastSelectedIndex.flatMap { idx in
+        let nextItem = lastSelectedIndex.flatMap { idx in
             displayOrder.dropFirst(idx + 1).first(where: { !ids.contains($0.id) })
         }
 
-        fState.selectedTaskID = nil
-        for task in tasksToDelete {
-            deleteTask(task)
+        fState.selectedItemID = nil
+        for item in itemsToDelete {
+            deleteItem(item)
         }
-        if let nextTask {
-            fState.selectedTaskID = nextTask.id
+        if let nextItem {
+            fState.selectedItemID = nextItem.id
         }
         return .handled
     }
 
-    func moveSelectedTaskUp() {
+    func moveSelectedItemUp() {
         guard focusedField == .scrollView else { return }
-        guard let currentID = fState.selectedTaskID else { return }
-        guard let currentIndex = activeTasks.firstIndex(where: { $0.id == currentID }) else { return }
+        guard let currentID = fState.selectedItemID else { return }
+        guard let currentIndex = activeItems.firstIndex(where: { $0.id == currentID }) else { return }
         guard currentIndex > 0 else { return }
 
         do {
-            try store.moveTask(taskID: currentID, toIndex: currentIndex - 1)
+            try store.moveItem(itemID: currentID, toIndex: currentIndex - 1)
         } catch {
             presentStoreError(error)
         }
     }
 
-    func moveSelectedTaskDown() {
+    func moveSelectedItemDown() {
         guard focusedField == .scrollView else { return }
-        guard let currentID = fState.selectedTaskID else { return }
-        guard let currentIndex = activeTasks.firstIndex(where: { $0.id == currentID }) else { return }
-        guard currentIndex < activeTasks.count - 1 else { return }
+        guard let currentID = fState.selectedItemID else { return }
+        guard let currentIndex = activeItems.firstIndex(where: { $0.id == currentID }) else { return }
+        guard currentIndex < activeItems.count - 1 else { return }
 
         do {
-            try store.moveTask(taskID: currentID, toIndex: currentIndex + 1)
+            try store.moveItem(itemID: currentID, toIndex: currentIndex + 1)
         } catch {
             presentStoreError(error)
         }
     }
 
-    func markSelectedTaskCompleted() {
+    func markSelectedItemCompleted() {
         guard focusedField == .scrollView else { return }
-        let ids = fState.selectedTaskIDs
+        let ids = fState.selectedItemIDs
         guard !ids.isEmpty else { return }
-        let tasksToToggle = allTasksInDisplayOrder.filter { ids.contains($0.id) }
-        for task in tasksToToggle {
-            toggleCompletion(task)
+        let itemsToToggle = allItemsInDisplayOrder.filter { ids.contains($0.id) }
+        for item in itemsToToggle {
+            toggleCompletion(item)
         }
     }
 
     // MARK: - Focus Management
 
-    func focusTextField(_ taskID: UUID) {
-        focusedField = .task(taskID)
+    func focusTextField(_ itemID: UUID) {
+        focusedField = .item(itemID)
     }
 
-    func startEditing(_ taskID: UUID) {
-        fState.selectedTaskID = taskID
-        focusedField = .task(taskID)
+    func startEditing(_ itemID: UUID) {
+        fState.selectedItemID = itemID
+        focusedField = .item(itemID)
         fState.pendingFocus = nil
     }
 
-    func endEditing(_ taskID: UUID, shouldCreateNewTask: Bool) {
-        if draftPlacement(for: taskID) != nil {
-            commitDraftTask(shouldCreateNewTask: shouldCreateNewTask)
+    func endEditing(_ itemID: UUID, shouldCreateNewItem: Bool) {
+        if draftPlacement(for: itemID) != nil {
+            commitDraftItem(shouldCreateNewItem: shouldCreateNewItem)
             return
         }
 
@@ -528,39 +528,39 @@ extension TaskListViewProtocol {
             presentStoreError(error)
         }
 
-        let wasLastActiveTask = isLastActiveTask(taskID)
-        let willBeDeleted = shouldDeleteIfEmpty(taskID: taskID)
+        let wasLastActiveItem = isLastActiveItem(itemID)
+        let willBeDeleted = shouldDeleteIfEmpty(itemID: itemID)
 
         if willBeDeleted {
-            fState.selectedTaskID = nil
-            deleteIfEmpty(taskID: taskID)
-        } else if wasLastActiveTask && shouldCreateNewTask {
-            revealDraftTask(at: .append)
-        } else if shouldCreateNewTask {
+            fState.selectedItemID = nil
+            deleteIfEmpty(itemID: itemID)
+        } else if wasLastActiveItem && shouldCreateNewItem {
+            revealDraftItem(at: .append)
+        } else if shouldCreateNewItem {
             focusedField = .scrollView
         }
     }
 
-    private func shouldDeleteIfEmpty(taskID: UUID) -> Bool {
-        guard let task = tasks.first(where: { $0.id == taskID }) else {
+    private func shouldDeleteIfEmpty(itemID: UUID) -> Bool {
+        guard let item = items.first(where: { $0.id == itemID }) else {
             return false
         }
-        let trimmedTitle = task.title.trimmingCharacters(in: .whitespacesAndNewlines)
+        let trimmedTitle = item.title.trimmingCharacters(in: .whitespacesAndNewlines)
         return trimmedTitle.isEmpty
     }
 
     // MARK: - Drag and Drop
 
-    func startDrag(taskID: UUID) {
+    func startDrag(itemID: UUID) {
         guard case .idle = dragState else { return }
         withAnimation(.easeOut(duration: 0.15)) {
-            dragState = .dragging(id: taskID, order: activeTasks.map(\.id))
+            dragState = .dragging(id: itemID, order: activeItems.map(\.id))
         }
         didStartDrag()
     }
 
     func updateVisualOrder(insertBefore targetID: UUID) {
-        guard let draggedID = draggedTaskID,
+        guard let draggedID = draggedItemID,
             let order = visualOrder
         else { return }
 
@@ -577,7 +577,7 @@ extension TaskListViewProtocol {
     }
 
     func updateVisualOrder(insertAfter targetID: UUID) {
-        guard let draggedID = draggedTaskID,
+        guard let draggedID = draggedItemID,
             let order = visualOrder
         else { return }
 
@@ -594,7 +594,7 @@ extension TaskListViewProtocol {
     }
 
     func updateVisualOrderSmart(relativeTo targetID: UUID) {
-        guard let draggedID = draggedTaskID,
+        guard let draggedID = draggedItemID,
             let order = visualOrder
         else { return }
 
@@ -610,7 +610,7 @@ extension TaskListViewProtocol {
     }
 
     func updateVisualOrder(insertAtEnd: Bool) {
-        guard let draggedID = draggedTaskID,
+        guard let draggedID = draggedItemID,
             let order = visualOrder
         else { return }
 
@@ -625,7 +625,7 @@ extension TaskListViewProtocol {
     }
 
     func commitCurrentDrag() -> Bool {
-        guard let droppedUUID = draggedTaskID,
+        guard let droppedUUID = draggedItemID,
             let order = visualOrder,
             let finalIndex = order.firstIndex(of: droppedUUID)
         else {
@@ -634,7 +634,7 @@ extension TaskListViewProtocol {
         }
 
         do {
-            try store.moveTask(taskID: droppedUUID, toIndex: finalIndex)
+            try store.moveItem(itemID: droppedUUID, toIndex: finalIndex)
             clearDragState()
         } catch {
             withAnimation(.spring(response: 0.3, dampingFraction: 0.8)) {

diff --git a/Listless/Extensions/TaskListView+SyncUI.swift b/Listless/Extensions/ItemListView+SyncUI.swift

@@ -5,7 +5,7 @@ import UIKit
 import AppKit
 #endif
 
-extension TaskListViewProtocol {
+extension ItemListViewProtocol {
     @ViewBuilder
     var syncErrorBanner: some View {
         if let message = syncMonitor.transientErrorMessage {

diff --git a/Listless/Helpers/AccentColor.swift b/Listless/Helpers/AccentColor.swift

@@ -41,18 +41,18 @@ enum ColorTheme: Int, CaseIterable, Identifiable {
     }
 }
 
-private struct TaskAccentColorKey: Hashable {
+private struct ItemAccentColorKey: Hashable {
     let index: Int
     let total: Int
     let theme: ColorTheme
 }
 
 @MainActor
-private enum TaskAccentColorCache {
-    static var colors: [TaskAccentColorKey: Color] = [:]
+private enum ItemAccentColorCache {
+    static var colors: [ItemAccentColorKey: Color] = [:]
 }
 
-func taskColor(forIndex index: Int, total: Int, theme: ColorTheme = .pilbara) -> Color {
+func itemColor(forIndex index: Int, total: Int, theme: ColorTheme = .pilbara) -> Color {
     let top = theme.top
     guard total > 1 else { return Color(hue: top.h, saturation: top.s, brightness: top.b) }
 
@@ -68,14 +68,14 @@ func taskColor(forIndex index: Int, total: Int, theme: ColorTheme = .pilbara) ->
 }
 
 @MainActor
-func cachedTaskColor(forIndex index: Int, total: Int, theme: ColorTheme = .pilbara) -> Color {
-    let key = TaskAccentColorKey(index: index, total: total, theme: theme)
-    if let cached = TaskAccentColorCache.colors[key] {
+func cachedItemColor(forIndex index: Int, total: Int, theme: ColorTheme = .pilbara) -> Color {
+    let key = ItemAccentColorKey(index: index, total: total, theme: theme)
+    if let cached = ItemAccentColorCache.colors[key] {
         return cached
     }
 
-    let computed = taskColor(forIndex: index, total: total, theme: theme)
-    TaskAccentColorCache.colors[key] = computed
+    let computed = itemColor(forIndex: index, total: total, theme: theme)
+    ItemAccentColorCache.colors[key] = computed
     return computed
 }
 

diff --git a/Listless/Helpers/TaskListTypes.swift b/Listless/Helpers/ItemListTypes.swift

@@ -1,7 +1,7 @@
 import Foundation
 
 enum FocusField: Hashable {
-    case task(UUID)
+    case item(UUID)
     case scrollView
 }
 
@@ -10,7 +10,7 @@ enum DragState: Equatable {
     case dragging(id: UUID, order: [UUID])
 }
 
-enum DraftTaskPlacement: Equatable {
+enum DraftItemPlacement: Equatable {
     case prepend
     case append
 }
@@ -21,16 +21,16 @@ struct FocusStateData {
 
     // MARK: - Selection
 
-    /// The full set of selected task IDs (supports multi-select on macOS).
-    private(set) var selectedTaskIDs: Set<UUID> = []
+    /// The full set of selected item IDs (supports multi-select on macOS).
+    private(set) var selectedItemIDs: Set<UUID> = []
 
     /// The start of a Shift+Arrow range selection. Stays fixed while the
     /// cursor moves via repeated Shift+Arrow presses.
-    var anchorTaskID: UUID?
+    var anchorItemID: UUID?
 
     /// The current cursor position. During single-select this equals the
     /// anchor. During Shift+Arrow it tracks the moving end of the range.
-    private(set) var cursorTaskID: UUID?
+    private(set) var cursorItemID: UUID?
 
     /// Selected items outside the active anchor–cursor range, preserved
     /// across Shift+Arrow operations after a Cmd+Click toggle.
@@ -40,31 +40,31 @@ struct FocusStateData {
     /// position plain Arrow keys navigate from); setting resets to a
     /// single-element (or empty) selection, keeping all existing call
     /// sites working without modification.
-    var selectedTaskID: UUID? {
-        get { cursorTaskID }
+    var selectedItemID: UUID? {
+        get { cursorItemID }
         set {
-            anchorTaskID = newValue
-            cursorTaskID = newValue
-            selectedTaskIDs = newValue.map { Set([$0]) } ?? []
+            anchorItemID = newValue
+            cursorItemID = newValue
+            selectedItemIDs = newValue.map { Set([$0]) } ?? []
             inactiveSelections = []
         }
     }
 
-    func isTaskSelected(_ id: UUID) -> Bool {
-        selectedTaskIDs.contains(id)
+    func isItemSelected(_ id: UUID) -> Bool {
+        selectedItemIDs.contains(id)
     }
 
     var hasMultipleSelection: Bool {
-        selectedTaskIDs.count > 1
+        selectedItemIDs.count > 1
     }
 
-    /// Select all tasks in display order, anchoring at the first and
+    /// Select all items in display order, anchoring at the first and
     /// placing the cursor at the last.
     mutating func selectAll(displayOrder: [UUID]) {
         guard !displayOrder.isEmpty else { return }
-        anchorTaskID = displayOrder.first
-        cursorTaskID = displayOrder.last
-        selectedTaskIDs = Set(displayOrder)
+        anchorItemID = displayOrder.first
+        cursorItemID = displayOrder.last
+        selectedItemIDs = Set(displayOrder)
         inactiveSelections = []
     }
 
@@ -74,25 +74,25 @@ struct FocusStateData {
     /// Shift+Arrow contracts from the far end. When adding, cursor
     /// resets to anchor so the active range stays small and other
     /// selections are preserved as inactive.
-    mutating func toggleSelection(taskID: UUID, displayOrder: [UUID]) {
-        guard let toggledIndex = displayOrder.firstIndex(of: taskID) else { return }
+    mutating func toggleSelection(itemID: UUID, displayOrder: [UUID]) {
+        guard let toggledIndex = displayOrder.firstIndex(of: itemID) else { return }
 
-        let wasSelected = selectedTaskIDs.contains(taskID)
+        let wasSelected = selectedItemIDs.contains(itemID)
         if wasSelected {
-            selectedTaskIDs.remove(taskID)
+            selectedItemIDs.remove(itemID)
         } else {
-            selectedTaskIDs.insert(taskID)
+            selectedItemIDs.insert(itemID)
         }
 
-        guard !selectedTaskIDs.isEmpty else {
-            anchorTaskID = nil
-            cursorTaskID = nil
+        guard !selectedItemIDs.isEmpty else {
+            anchorItemID = nil
+            cursorItemID = nil
             inactiveSelections = []
             return
         }
 
         // Anchor = item below the toggled item (or self if at bottom).
-        anchorTaskID =
+        anchorItemID =
             toggledIndex + 1 < displayOrder.count
             ? displayOrder[toggledIndex + 1]
             : displayOrder[toggledIndex]
@@ -100,24 +100,24 @@ struct FocusStateData {
         if wasSelected {
             // Deselecting: cursor stays so Shift+Arrow contracts from
             // the far end of the remaining selection.
-            if cursorTaskID == nil {
-                cursorTaskID = anchorTaskID
+            if cursorItemID == nil {
+                cursorItemID = anchorItemID
             }
         } else {
             // Adding: cursor resets to anchor so the active range is
             // small, preserving other selections as inactive.
-            cursorTaskID = anchorTaskID
+            cursorItemID = anchorItemID
         }
 
         recomputeInactiveSelections(displayOrder: displayOrder)
     }
 
     /// Extend or contract the selection from the anchor to `targetID`,
-    /// selecting all tasks between them in `displayOrder`. Inactive
+    /// selecting all items between them in `displayOrder`. Inactive
     /// selections are preserved and merged when they become adjacent
     /// to the active range.
     mutating func extendSelection(to targetID: UUID, displayOrder: [UUID]) {
-        guard let anchorID = anchorTaskID,
+        guard let anchorID = anchorItemID,
             let anchorIndex = displayOrder.firstIndex(of: anchorID),
             let targetIndex = displayOrder.firstIndex(of: targetID)
         else {
@@ -126,17 +126,17 @@ struct FocusStateData {
         let lo = min(anchorIndex, targetIndex)
         let hi = max(anchorIndex, targetIndex)
         let activeRange = Set(displayOrder[lo...hi])
-        selectedTaskIDs = inactiveSelections.union(activeRange)
-        cursorTaskID = targetID
+        selectedItemIDs = inactiveSelections.union(activeRange)
+        cursorItemID = targetID
         mergeAdjacentInactiveSelections(displayOrder: displayOrder)
     }
 
     // MARK: - Private Helpers
 
-    /// Partition `selectedTaskIDs` into those inside vs outside the
+    /// Partition `selectedItemIDs` into those inside vs outside the
     /// anchor–cursor range.
     private mutating func recomputeInactiveSelections(displayOrder: [UUID]) {
-        guard let anchorID = anchorTaskID, let cursorID = cursorTaskID,
+        guard let anchorID = anchorItemID, let cursorID = cursorItemID,
             let anchorIndex = displayOrder.firstIndex(of: anchorID),
             let cursorIndex = displayOrder.firstIndex(of: cursorID)
         else {
@@ -146,7 +146,7 @@ struct FocusStateData {
         let lo = min(anchorIndex, cursorIndex)
         let hi = max(anchorIndex, cursorIndex)
         let activeRange = Set(displayOrder[lo...hi])
-        inactiveSelections = selectedTaskIDs.subtracting(activeRange)
+        inactiveSelections = selectedItemIDs.subtracting(activeRange)
     }
 
     /// When the active range becomes adjacent to inactive selections,
@@ -155,9 +155,9 @@ struct FocusStateData {
     /// from the anchor).
     private mutating func mergeAdjacentInactiveSelections(displayOrder: [UUID]) {
         guard !inactiveSelections.isEmpty,
-            let anchorID = anchorTaskID,
+            let anchorID = anchorItemID,
             let anchorIndex = displayOrder.firstIndex(of: anchorID),
-            let cursorID = cursorTaskID,
+            let cursorID = cursorItemID,
             let cursorIndex = displayOrder.firstIndex(of: cursorID)
         else {
             return
@@ -186,12 +186,12 @@ struct FocusStateData {
         inactiveSelections.subtract(mergedIDs)
 
         if cursorIndex <= anchorIndex {
-            cursorTaskID = displayOrder[lo]
+            cursorItemID = displayOrder[lo]
         } else {
-            cursorTaskID = displayOrder[hi]
+            cursorItemID = displayOrder[hi]
         }
 
-        selectedTaskIDs = inactiveSelections.union(Set(displayOrder[lo...hi]))
+        selectedItemIDs = inactiveSelections.union(Set(displayOrder[lo...hi]))
     }
 }
 

diff --git a/Listless/Helpers/TaskListViewProtocol.swift b/Listless/Helpers/ItemListViewProtocol.swift

@@ -2,16 +2,16 @@ import CoreData
 import SwiftUI
 
 @MainActor
-protocol TaskListViewProtocol {
-    var tasks: FetchedResults<TaskItem> { get }
-    var store: TaskStore { get }
+protocol ItemListViewProtocol {
+    var items: FetchedResults<ItemEntity> { get }
+    var store: ItemStore { get }
     var syncMonitor: CloudKitSyncMonitor { get }
     var managedObjectContext: NSManagedObjectContext { get }
     var focusedField: FocusField? { get nonmutating set }
     var fState: FocusStateData { get nonmutating set }
     var dragState: DragState { get nonmutating set }
-    var draftPlacement: DraftTaskPlacement? { get nonmutating set }
+    var draftPlacement: DraftItemPlacement? { get nonmutating set }
     var draftTitle: String { get nonmutating set }
     func didStartDrag()
-    func clearDraftTaskUI(at placement: DraftTaskPlacement, hasTitle: Bool)
+    func clearDraftItemUI(at placement: DraftItemPlacement, hasTitle: Bool)
 }

diff --git a/Listless/Models/TaskItem.swift b/Listless/Models/ItemEntity.swift

@@ -2,18 +2,18 @@ import CoreData
 import Foundation
 
 @objc(TaskItem)
-public class TaskItem: NSManagedObject, Identifiable {
+public class ItemEntity: NSManagedObject, Identifiable {
     private enum Keys {
-        private static func key<Value>(_ keyPath: KeyPath<TaskItem, Value>) -> String {
+        private static func key<Value>(_ keyPath: KeyPath<ItemEntity, Value>) -> String {
             NSExpression(forKeyPath: keyPath).keyPath
         }
 
-        static let id = key(\TaskItem.id as KeyPath<TaskItem, UUID>)
-        static let title = key(\TaskItem.title)
-        static let createdAt = key(\TaskItem.createdAt)
-        static let updatedAt = key(\TaskItem.updatedAt)
-        static let sortOrder = key(\TaskItem.sortOrder)
-        static let completedOrder = key(\TaskItem.completedOrder)
+        static let id = key(\ItemEntity.id as KeyPath<ItemEntity, UUID>)
+        static let title = key(\ItemEntity.title)
+        static let createdAt = key(\ItemEntity.createdAt)
+        static let updatedAt = key(\ItemEntity.updatedAt)
+        static let sortOrder = key(\ItemEntity.sortOrder)
+        static let completedOrder = key(\ItemEntity.completedOrder)
     }
 
     @NSManaged public var id: UUID
@@ -25,8 +25,8 @@ public class TaskItem: NSManagedObject, Identifiable {
 
     public var isCompleted: Bool { completedOrder > 0 }
 
-    @nonobjc public class func fetchRequest() -> NSFetchRequest<TaskItem> {
-        return NSFetchRequest<TaskItem>(entityName: "TaskItem")
+    @nonobjc public class func fetchRequest() -> NSFetchRequest<ItemEntity> {
+        return NSFetchRequest<ItemEntity>(entityName: "TaskItem")
     }
 
     public override func awakeFromInsert() {

diff --git a/Listless/Models/ItemStore.swift b/Listless/Models/ItemStore.swift

@@ -0,0 +1,201 @@
+import CoreData
+import Foundation
+
+enum ItemStoreError: LocalizedError {
+    case fetchFailed(Error)
+    case saveFailed(Error)
+
+    var errorDescription: String? {
+        switch self {
+        case .fetchFailed(let error):
+            return "Failed to fetch items: \(error.localizedDescription)"
+        case .saveFailed(let error):
+            return "Failed to save changes: \(error.localizedDescription)"
+        }
+    }
+}
+
+@MainActor
+final class ItemStore {
+    private let persistenceController: PersistenceController
+    private var context: NSManagedObjectContext {
+        persistenceController.viewContext
+    }
+
+    init(persistenceController: PersistenceController = .shared) {
+        self.persistenceController = persistenceController
+    }
+
+    func fetchItems() throws -> [ItemEntity] {
+        do {
+            let activeRequest = ItemEntity.fetchRequest()
+            activeRequest.predicate = NSPredicate(format: "completedOrder == 0")
+            activeRequest.sortDescriptors = [
+                NSSortDescriptor(keyPath: \ItemEntity.sortOrder, ascending: true)
+            ]
+
+            let completedRequest = ItemEntity.fetchRequest()
+            completedRequest.predicate = NSPredicate(format: "completedOrder > 0")
+            completedRequest.sortDescriptors = [
+                NSSortDescriptor(keyPath: \ItemEntity.completedOrder, ascending: false)
+            ]
+
+            let activeItems = try context.fetch(activeRequest)
+            let completedItems = try context.fetch(completedRequest)
+
+            return activeItems + completedItems
+        } catch {
+            throw ItemStoreError.fetchFailed(error)
+        }
+    }
+
+    func createItem(title: String = "", atBeginning: Bool = false, sortOrder: Int64? = nil) throws -> ItemEntity {
+        // Compute sort order before inserting the new object so we don't need
+        // processPendingChanges() and the new item can't appear in our own query.
+        let resolvedSortOrder: Int64
+        if let sortOrder {
+            resolvedSortOrder = sortOrder
+        } else if atBeginning {
+            let minOrder = try minActiveSortOrder() ?? 0
+            resolvedSortOrder = minOrder - 1000
+        } else {
+            let maxOrder = try maxActiveSortOrder() ?? -1000
+            resolvedSortOrder = maxOrder + 1000
+        }
+
+        let item = ItemEntity(context: context)
+        item.title = title
+        item.sortOrder = resolvedSortOrder
+
+        return item
+    }
+
+    private func minActiveSortOrder() throws -> Int64? {
+        let request = ItemEntity.fetchRequest()
+        request.predicate = NSPredicate(format: "completedOrder == 0")
+        request.sortDescriptors = [NSSortDescriptor(keyPath: \ItemEntity.sortOrder, ascending: true)]
+        request.fetchLimit = 1
+        do {
+            return try context.fetch(request).first?.sortOrder
+        } catch {
+            throw ItemStoreError.fetchFailed(error)
+        }
+    }
+
+    private func maxActiveSortOrder() throws -> Int64? {
+        let request = ItemEntity.fetchRequest()
+        request.predicate = NSPredicate(format: "completedOrder == 0")
+        request.sortDescriptors = [NSSortDescriptor(keyPath: \ItemEntity.sortOrder, ascending: false)]
+        request.fetchLimit = 1
+        do {
+            return try context.fetch(request).first?.sortOrder
+        } catch {
+            throw ItemStoreError.fetchFailed(error)
+        }
+    }
+
+    func complete(itemID: UUID) throws {
+        guard let item = try findItem(id: itemID) else { return }
+
+        let request = ItemEntity.fetchRequest()
+        request.predicate = NSPredicate(format: "completedOrder > 0")
+        request.sortDescriptors = [
+            NSSortDescriptor(keyPath: \ItemEntity.completedOrder, ascending: false)
+        ]
+        request.fetchLimit = 1
+        let maxOrder = (try context.fetch(request).first)?.completedOrder ?? 0
+
+        item.completedOrder = maxOrder + 1
+        try save()
+    }
+
+    func uncomplete(itemID: UUID) throws {
+        guard let item = try findItem(id: itemID) else { return }
+        let restoredSortOrder = item.sortOrder
+        let activeItems = try fetchItems().filter { !$0.isCompleted && $0.id != item.id }
+        let hasSortOrderConflict = activeItems.contains { $0.sortOrder == restoredSortOrder }
+
+        if hasSortOrderConflict {
+            let maxSortOrder = activeItems.map(\.sortOrder).max() ?? -1000
+            item.sortOrder = maxSortOrder + 1000
+        }
+
+        item.completedOrder = 0
+        try save()
+    }
+
+    func update(itemID: UUID, title: String) throws {
+        guard let item = try findItem(id: itemID) else { return }
+        item.title = title
+        try save()
+    }
+
+    func updateWithoutSaving(itemID: UUID, title: String) throws {
+        guard let item = try findItem(id: itemID) else { return }
+        item.title = title
+        // Don't save - will be saved when editing ends
+    }
+
+    func delete(itemID: UUID) throws {
+        guard let item = try findItem(id: itemID) else { return }
+        context.delete(item)
+        try save()
+    }
+
+    func deleteMultiple(itemIDs: [UUID]) throws {
+        for itemID in itemIDs {
+            guard let item = try findItem(id: itemID) else { continue }
+            context.delete(item)
+        }
+        try save()
+    }
+
+    func normalizeSortOrders() throws {
+        let activeItems = try fetchItems().filter { !$0.isCompleted }
+            .sorted { $0.sortOrder < $1.sortOrder }
+
+        for (index, item) in activeItems.enumerated() {
+            item.sortOrder = Int64(index) * 1000
+        }
+
+        try save()
+    }
+
+    func moveItem(itemID: UUID, toIndex: Int) throws {
+        let activeItems = try fetchItems().filter { !$0.isCompleted }
+            .sorted { $0.sortOrder < $1.sortOrder }
+
+        guard let currentIndex = activeItems.firstIndex(where: { $0.id == itemID }) else { return }
+        guard currentIndex != toIndex else { return }
+
+        var reordered = activeItems
+        let item = reordered.remove(at: currentIndex)
+
+        // Clamp toIndex to valid range [0, reordered.count] after removal
+        let insertIndex = max(0, min(toIndex, reordered.count))
+        reordered.insert(item, at: insertIndex)
+
+        // Reassign sortOrder with gaps of 1000
+        for (index, item) in reordered.enumerated() {
+            item.sortOrder = Int64(index) * 1000
+        }
+
+        try save()
+    }
+
+    private func findItem(id: UUID) throws -> ItemEntity? {
+        let request = ItemEntity.fetchRequest()
+        request.predicate = NSPredicate(format: "id == %@", id as CVarArg)
+        request.fetchLimit = 1
+
+        do {
+            return try context.fetch(request).first
+        } catch {
+            throw ItemStoreError.fetchFailed(error)
+        }
+    }
+
+    func save() throws {
+        try persistenceController.save()
+    }
+}

diff --git a/Listless/Models/TaskStore.swift b/Listless/Models/TaskStore.swift

@@ -1,201 +0,0 @@
-import CoreData
-import Foundation
-
-enum TaskStoreError: LocalizedError {
-    case fetchFailed(Error)
-    case saveFailed(Error)
-
-    var errorDescription: String? {
-        switch self {
-        case .fetchFailed(let error):
-            return "Failed to fetch tasks: \(error.localizedDescription)"
-        case .saveFailed(let error):
-            return "Failed to save changes: \(error.localizedDescription)"
-        }
-    }
-}
-
-@MainActor
-final class TaskStore {
-    private let persistenceController: PersistenceController
-    private var context: NSManagedObjectContext {
-        persistenceController.viewContext
-    }
-
-    init(persistenceController: PersistenceController = .shared) {
-        self.persistenceController = persistenceController
-    }
-
-    func fetchTasks() throws -> [TaskItem] {
-        do {
-            let activeRequest = TaskItem.fetchRequest()
-            activeRequest.predicate = NSPredicate(format: "completedOrder == 0")
-            activeRequest.sortDescriptors = [
-                NSSortDescriptor(keyPath: \TaskItem.sortOrder, ascending: true)
-            ]
-
-            let completedRequest = TaskItem.fetchRequest()
-            completedRequest.predicate = NSPredicate(format: "completedOrder > 0")
-            completedRequest.sortDescriptors = [
-                NSSortDescriptor(keyPath: \TaskItem.completedOrder, ascending: false)
-            ]
-
-            let activeTasks = try context.fetch(activeRequest)
-            let completedTasks = try context.fetch(completedRequest)
-
-            return activeTasks + completedTasks
-        } catch {
-            throw TaskStoreError.fetchFailed(error)
-        }
-    }
-
-    func createTask(title: String = "", atBeginning: Bool = false, sortOrder: Int64? = nil) throws -> TaskItem {
-        // Compute sort order before inserting the new object so we don't need
-        // processPendingChanges() and the new task can't appear in our own query.
-        let resolvedSortOrder: Int64
-        if let sortOrder {
-            resolvedSortOrder = sortOrder
-        } else if atBeginning {
-            let minOrder = try minActiveSortOrder() ?? 0
-            resolvedSortOrder = minOrder - 1000
-        } else {
-            let maxOrder = try maxActiveSortOrder() ?? -1000
-            resolvedSortOrder = maxOrder + 1000
-        }
-
-        let task = TaskItem(context: context)
-        task.title = title
-        task.sortOrder = resolvedSortOrder
-
-        return task
-    }
-
-    private func minActiveSortOrder() throws -> Int64? {
-        let request = TaskItem.fetchRequest()
-        request.predicate = NSPredicate(format: "completedOrder == 0")
-        request.sortDescriptors = [NSSortDescriptor(keyPath: \TaskItem.sortOrder, ascending: true)]
-        request.fetchLimit = 1
-        do {
-            return try context.fetch(request).first?.sortOrder
-        } catch {
-            throw TaskStoreError.fetchFailed(error)
-        }
-    }
-
-    private func maxActiveSortOrder() throws -> Int64? {
-        let request = TaskItem.fetchRequest()
-        request.predicate = NSPredicate(format: "completedOrder == 0")
-        request.sortDescriptors = [NSSortDescriptor(keyPath: \TaskItem.sortOrder, ascending: false)]
-        request.fetchLimit = 1
-        do {
-            return try context.fetch(request).first?.sortOrder
-        } catch {
-            throw TaskStoreError.fetchFailed(error)
-        }
-    }
-
-    func complete(taskID: UUID) throws {
-        guard let task = try findTask(id: taskID) else { return }
-
-        let request = TaskItem.fetchRequest()
-        request.predicate = NSPredicate(format: "completedOrder > 0")
-        request.sortDescriptors = [
-            NSSortDescriptor(keyPath: \TaskItem.completedOrder, ascending: false)
-        ]
-        request.fetchLimit = 1
-        let maxOrder = (try context.fetch(request).first)?.completedOrder ?? 0
-
-        task.completedOrder = maxOrder + 1
-        try save()
-    }
-
-    func uncomplete(taskID: UUID) throws {
-        guard let task = try findTask(id: taskID) else { return }
-        let restoredSortOrder = task.sortOrder
-        let activeTasks = try fetchTasks().filter { !$0.isCompleted && $0.id != task.id }
-        let hasSortOrderConflict = activeTasks.contains { $0.sortOrder == restoredSortOrder }
-
-        if hasSortOrderConflict {
-            let maxSortOrder = activeTasks.map(\.sortOrder).max() ?? -1000
-            task.sortOrder = maxSortOrder + 1000
-        }
-
-        task.completedOrder = 0
-        try save()
-    }
-
-    func update(taskID: UUID, title: String) throws {
-        guard let task = try findTask(id: taskID) else { return }
-        task.title = title
-        try save()
-    }
-
-    func updateWithoutSaving(taskID: UUID, title: String) throws {
-        guard let task = try findTask(id: taskID) else { return }
-        task.title = title
-        // Don't save - will be saved when editing ends
-    }
-
-    func delete(taskID: UUID) throws {
-        guard let task = try findTask(id: taskID) else { return }
-        context.delete(task)
-        try save()
-    }
-
-    func deleteMultiple(taskIDs: [UUID]) throws {
-        for taskID in taskIDs {
-            guard let task = try findTask(id: taskID) else { continue }
-            context.delete(task)
-        }
-        try save()
-    }
-
-    func normalizeSortOrders() throws {
-        let activeTasks = try fetchTasks().filter { !$0.isCompleted }
-            .sorted { $0.sortOrder < $1.sortOrder }
-
-        for (index, task) in activeTasks.enumerated() {
-            task.sortOrder = Int64(index) * 1000
-        }
-
-        try save()
-    }
-
-    func moveTask(taskID: UUID, toIndex: Int) throws {
-        let activeTasks = try fetchTasks().filter { !$0.isCompleted }
-            .sorted { $0.sortOrder < $1.sortOrder }
-
-        guard let currentIndex = activeTasks.firstIndex(where: { $0.id == taskID }) else { return }
-        guard currentIndex != toIndex else { return }
-
-        var reordered = activeTasks
-        let task = reordered.remove(at: currentIndex)
-
-        // Clamp toIndex to valid range [0, reordered.count] after removal
-        let insertIndex = max(0, min(toIndex, reordered.count))
-        reordered.insert(task, at: insertIndex)
-
-        // Reassign sortOrder with gaps of 1000
-        for (index, task) in reordered.enumerated() {
-            task.sortOrder = Int64(index) * 1000
-        }
-
-        try save()
-    }
-
-    private func findTask(id: UUID) throws -> TaskItem? {
-        let request = TaskItem.fetchRequest()
-        request.predicate = NSPredicate(format: "id == %@", id as CVarArg)
-        request.fetchLimit = 1
-
-        do {
-            return try context.fetch(request).first
-        } catch {
-            throw TaskStoreError.fetchFailed(error)
-        }
-    }
-
-    func save() throws {
-        try persistenceController.save()
-    }
-}

diff --git a/Listless/Sync/CloudKitErrorClassifier.swift b/Listless/Sync/CloudKitErrorClassifier.swift

@@ -44,7 +44,7 @@ enum CloudKitErrorClassifier {
     }
 
     private static func unwrap(_ error: Error) -> Error {
-        if let storeError = error as? TaskStoreError {
+        if let storeError = error as? ItemStoreError {
             switch storeError {
             case .fetchFailed(let wrappedError), .saveFailed(let wrappedError):
                 return wrappedError

diff --git a/Listless/Sync/PersistenceController.swift b/Listless/Sync/PersistenceController.swift

@@ -14,7 +14,7 @@ private final class UpdatedAtMergePolicy: NSMergePolicy {
         for item in list {
             guard
                 let conflict = item as? NSMergeConflict,
-                let task = conflict.sourceObject as? TaskItem,
+                let item = conflict.sourceObject as? ItemEntity,
                 let objectSnapshot = conflict.objectSnapshot,
                 let persistedSnapshot = conflict.persistedSnapshot,
                 let storeUpdatedAt = persistedSnapshot["updatedAt"] as? Date
@@ -23,7 +23,7 @@ private final class UpdatedAtMergePolicy: NSMergePolicy {
                 continue
             }
 
-            let localUpdatedAt = (objectSnapshot["updatedAt"] as? Date) ?? task.updatedAt
+            let localUpdatedAt = (objectSnapshot["updatedAt"] as? Date) ?? item.updatedAt
 
             // Keep local in-memory values if they are newer or equal.
             guard storeUpdatedAt > localUpdatedAt else { continue }
@@ -33,9 +33,9 @@ private final class UpdatedAtMergePolicy: NSMergePolicy {
             // see these keys in changedValues() and overwrite updatedAt with Date().
             for (key, value) in persistedSnapshot {
                 if value is NSNull {
-                    task.setPrimitiveValue(nil, forKey: key)
+                    item.setPrimitiveValue(nil, forKey: key)
                 } else {
-                    task.setPrimitiveValue(value, forKey: key)
+                    item.setPrimitiveValue(value, forKey: key)
                 }
             }
         }
@@ -116,7 +116,7 @@ final class PersistenceController {
         do {
             try context.save()
         } catch {
-            throw TaskStoreError.saveFailed(error)
+            throw ItemStoreError.saveFailed(error)
         }
     }
 }

diff --git a/ListlessMac/Extensions/TaskListView+Toolbar.swift b/ListlessMac/Extensions/ItemListView+Toolbar.swift

@@ -1,6 +1,6 @@
 import SwiftUI
 
-extension TaskListView {
+extension ItemListView {
     @ToolbarContentBuilder
     var platformToolbar: some ToolbarContent {
         ToolbarItem(placement: .automatic) {
@@ -24,14 +24,14 @@ extension TaskListView {
                 }
 
                 Button {
-                    createNewTask()
+                    createNewItem()
                 } label: {
                     Label("New Item", systemImage: "plus")
                 }
                 .help("Create a new item")
 
                 Button {
-                    _ = deleteSelectedTask()
+                    _ = deleteSelectedItem()
                 } label: {
                     Label("Delete", systemImage: "trash")
                 }
@@ -41,12 +41,12 @@ extension TaskListView {
                 Divider()
 
                 Button {
-                    clearCompletedTasks()
+                    clearCompletedItems()
                 } label: {
                     Label("Clear Completed", systemImage: "tray")
                 }
-                .disabled(completedTasks.isEmpty)
-                .help("Clear all completed tasks")
+                .disabled(completedItems.isEmpty)
+                .help("Clear all completed items")
             }
         }
     }

diff --git a/ListlessMac/Helpers/AppColors.swift b/ListlessMac/Helpers/AppColors.swift

@@ -1,7 +1,7 @@
 import SwiftUI
 
 extension Color {
-    /// Canvas behind task rows: warm gray in light mode, default window background in dark mode.
+    /// Canvas behind item rows: warm gray in light mode, default window background in dark mode.
     static let outerBackground = Color(nsColor: NSColor(name: nil) { appearance in
         appearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua
             ? .windowBackgroundColor

diff --git a/ListlessMac/Helpers/AppCommands.swift b/ListlessMac/Helpers/AppCommands.swift

@@ -5,29 +5,29 @@ import Foundation
 @MainActor
 final class WindowCoordinator {
 
-    // Actions — set by TaskListView on each relevant state change.
-    var newTask: (() -> Void)?
+    // Actions — set by ItemListView on each relevant state change.
+    var newItem: (() -> Void)?
     var newWindow: (() -> Void)?
-    var copySelectedTask: (() -> Void)?
-    var cutSelectedTask: (() -> Void)?
-    var pasteAfterSelectedTask: (() -> Void)?
-    var deleteSelectedTask: (() -> Void)?
-    var moveSelectedTaskUp: (() -> Void)?
-    var moveSelectedTaskDown: (() -> Void)?
-    var markSelectedTaskCompleted: (() -> Void)?
-    var selectAllTasks: (() -> Void)?
-    var clearCompletedTasks: (() -> Void)?
+    var copySelectedItem: (() -> Void)?
+    var cutSelectedItem: (() -> Void)?
+    var pasteAfterSelectedItem: (() -> Void)?
+    var deleteSelectedItem: (() -> Void)?
+    var moveSelectedItemUp: (() -> Void)?
+    var moveSelectedItemDown: (() -> Void)?
+    var markSelectedItemCompleted: (() -> Void)?
+    var selectAllItems: (() -> Void)?
+    var clearCompletedItems: (() -> Void)?
 
     // Enabled state — read by AppDelegate in menuWillOpen and validateMenuItem.
-    var canSelectAllTasks = false
-    var canCopySelectedTask = false
-    var canCutSelectedTask = false
-    var canPasteAfterSelectedTask = false
-    var canDeleteSelectedTask = false
-    var canMoveSelectedTaskUp = false
-    var canMoveSelectedTaskDown = false
-    var canMarkSelectedTaskCompleted = false
-    var canClearCompletedTasks = false
+    var canSelectAllItems = false
+    var canCopySelectedItem = false
+    var canCutSelectedItem = false
+    var canPasteAfterSelectedItem = false
+    var canDeleteSelectedItem = false
+    var canMoveSelectedItemUp = false
+    var canMoveSelectedItemDown = false
+    var canMarkSelectedItemCompleted = false
+    var canClearCompletedItems = false
 
     // Dynamic titles — read by AppDelegate in validateMenuItem.
     var markCompletedTitle: String = "Mark as Complete"

diff --git a/ListlessMac/Helpers/ClickableTextField.swift b/ListlessMac/Helpers/ClickableTextField.swift

@@ -5,9 +5,9 @@ import SwiftUI
 class ClickableNSTextField: NSTextField {
     var onBecomeFirstResponder: (() -> Void)?
 
-    /// The task ID this text field represents, used by the per-window
+    /// The item ID this text field represents, used by the per-window
     /// `WindowCoordinator.allowedFocusTarget` check.
-    var taskID: UUID?
+    var itemID: UUID?
 
     override var acceptsFirstResponder: Bool {
         // Always allow if this field is already editing.
@@ -25,7 +25,7 @@ class ClickableNSTextField: NSTextField {
         {
             if let allowed = coordinator.allowedFocusTarget {
                 // A specific target is set — only that field may accept.
-                guard let taskID, case .task(let allowedID) = allowed, allowedID == taskID else {
+                guard let itemID, case .item(let allowedID) = allowed, allowedID == itemID else {
                     return false
                 }
             }
@@ -37,11 +37,11 @@ class ClickableNSTextField: NSTextField {
     override func viewDidMoveToWindow() {
         super.viewDidMoveToWindow()
         guard let window,
-            let taskID,
+            let itemID,
             let delegate = NSApp.delegate as? AppDelegate,
             let coordinator = delegate.coordinator(for: window)
         else { return }
-        if case .task(let allowedID) = coordinator.allowedFocusTarget, allowedID == taskID {
+        if case .item(let allowedID) = coordinator.allowedFocusTarget, allowedID == itemID {
             coordinator.allowedFocusTarget = nil
             window.makeFirstResponder(self)
         }
@@ -63,13 +63,13 @@ class ClickableNSTextField: NSTextField {
 struct ClickableTextField: NSViewRepresentable {
     @Binding var text: String
     let isCompleted: Bool
-    let onEditingChanged: (Bool, _ shouldCreateNewTask: Bool) -> Void
-    var taskID: UUID? = nil
+    let onEditingChanged: (Bool, _ shouldCreateNewItem: Bool) -> Void
+    var itemID: UUID? = nil
     var onContentChange: ((String) -> Void)? = nil
 
     func makeNSView(context: Context) -> ClickableNSTextField {
         let textField = ClickableNSTextField()
-        textField.taskID = taskID
+        textField.itemID = itemID
         textField.delegate = context.coordinator
         textField.isBordered = false
         textField.drawsBackground = false
@@ -171,13 +171,13 @@ struct ClickableTextField: NSViewRepresentable {
     @MainActor
     final class Coordinator: NSObject, NSTextFieldDelegate {
         @Binding var text: String
-        let onEditingChanged: (Bool, _ shouldCreateNewTask: Bool) -> Void
+        let onEditingChanged: (Bool, _ shouldCreateNewItem: Bool) -> Void
         let onContentChange: ((String) -> Void)?
         var editEndReason: EditEndReason = .focusLost
 
         init(
             text: Binding<String>,
-            onEditingChanged: @escaping (Bool, _ shouldCreateNewTask: Bool) -> Void,
+            onEditingChanged: @escaping (Bool, _ shouldCreateNewItem: Bool) -> Void,
             onContentChange: ((String) -> Void)? = nil
         ) {
             _text = text
@@ -215,9 +215,9 @@ struct ClickableTextField: NSViewRepresentable {
 
         func controlTextDidEndEditing(_ obj: Notification) {
             hasNotifiedEditingStarted = false
-            let shouldCreateNewTask = editEndReason == .returnKey
+            let shouldCreateNewItem = editEndReason == .returnKey
             editEndReason = .focusLost  // Reset for next time
-            onEditingChanged(false, shouldCreateNewTask)
+            onEditingChanged(false, shouldCreateNewItem)
         }
 
         func controlTextDidChange(_ obj: Notification) {
@@ -237,7 +237,7 @@ struct ClickableTextField: NSViewRepresentable {
             if commandSelector == #selector(NSResponder.insertNewline(_:)) {
                 // Return key pressed — set the per-window allowed focus
                 // target to .scrollView so no text field can steal focus
-                // during reconciliation. Cleared in TaskListView's outer
+                // during reconciliation. Cleared in ItemListView's outer
                 // onChange(of: focusedFieldBinding).
                 editEndReason = .returnKey
                 setAllowedFocusTarget(for: control.window, target: .scrollView)

diff --git a/ListlessMac/Helpers/TaskRowDragGesture.swift b/ListlessMac/Helpers/ItemRowDragGesture.swift

@@ -3,17 +3,17 @@ import SwiftUI
 import UniformTypeIdentifiers
 
 extension View {
-    func taskDragGesture(
+    func itemDragGesture(
         isActive: Bool,
-        taskID: UUID,
+        itemID: UUID,
         onDragStart: @escaping () -> Void,
         onLift: @escaping () -> Void = {},
         onLiftEnd: @escaping () -> Void = {}
     ) -> some View {
         self.modifier(
-            TaskRowDragGesture(
+            ItemRowDragGesture(
                 isActive: isActive,
-                taskID: taskID,
+                itemID: itemID,
                 onDragStart: onDragStart,
                 onLift: onLift,
                 onLiftEnd: onLiftEnd
@@ -21,9 +21,9 @@ extension View {
     }
 }
 
-struct TaskRowDragGesture: ViewModifier {
+struct ItemRowDragGesture: ViewModifier {
     let isActive: Bool
-    let taskID: UUID
+    let itemID: UUID
     let onDragStart: () -> Void
     let onLift: () -> Void
     let onLiftEnd: () -> Void
@@ -61,7 +61,7 @@ struct TaskRowDragGesture: ViewModifier {
             NSEvent.addLocalMonitorForEvents(matching: .leftMouseDragged) { event in
                 if isLifted && !dragSource.isActive {
                     onDragStart()
-                    dragSource.beginDrag(taskID: taskID, event: event)
+                    dragSource.beginDrag(itemID: itemID, event: event)
                 }
                 return event
             }!
@@ -113,9 +113,9 @@ class DragSourceManager: NSObject, NSDraggingSource {
     var isActive = false
     var onDragEnd: (() -> Void)?
 
-    func beginDrag(taskID: UUID, event: NSEvent) {
+    func beginDrag(itemID: UUID, event: NSEvent) {
         guard let sourceView, !isActive else { return }
-        let item = NSDraggingItem(pasteboardWriter: taskID.uuidString as NSString)
+        let item = NSDraggingItem(pasteboardWriter: itemID.uuidString as NSString)
         let image = NSImage(size: NSSize(width: 1, height: 1))
         item.setDraggingFrame(sourceView.bounds, contents: image)
         isActive = true

diff --git a/ListlessMac/ListlessMacApp.swift b/ListlessMac/ListlessMacApp.swift

@@ -76,52 +76,52 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSMenuItemValidation {
         }
         guard let coord = keyWindowCoordinator else { return false }
         switch menuItem.action {
-        case #selector(selectAll(_:)):         return coord.canSelectAllTasks
-        case #selector(cut(_:)):              return coord.canCutSelectedTask
-        case #selector(copy(_:)):             return coord.canCopySelectedTask
-        case #selector(paste(_:)):            return coord.canPasteAfterSelectedTask
-        case #selector(handleDeleteTask):     return coord.canDeleteSelectedTask
-        case #selector(handleMoveUp):         return coord.canMoveSelectedTaskUp
-        case #selector(handleMoveDown):       return coord.canMoveSelectedTaskDown
+        case #selector(selectAll(_:)):         return coord.canSelectAllItems
+        case #selector(cut(_:)):              return coord.canCutSelectedItem
+        case #selector(copy(_:)):             return coord.canCopySelectedItem
+        case #selector(paste(_:)):            return coord.canPasteAfterSelectedItem
+        case #selector(handleDeleteItem):     return coord.canDeleteSelectedItem
+        case #selector(handleMoveUp):         return coord.canMoveSelectedItemUp
+        case #selector(handleMoveDown):       return coord.canMoveSelectedItemDown
         case #selector(handleMarkCompleted):
             menuItem.title = coord.markCompletedTitle
-            return coord.canMarkSelectedTaskCompleted
-        case #selector(handleClearCompleted): return coord.canClearCompletedTasks
+            return coord.canMarkSelectedItemCompleted
+        case #selector(handleClearCompleted): return coord.canClearCompletedItems
         default: return true
         }
     }
 
     // MARK: - Actions
 
-    @objc private func handleNewTask() {
+    @objc private func handleNewItem() {
         if NSApp.windows.filter({ $0.isVisible }).isEmpty {
             openNewWindow()
             Task { @MainActor in
-                keyWindowCoordinator?.newTask?()
+                keyWindowCoordinator?.newItem?()
             }
         } else {
-            keyWindowCoordinator?.newTask?()
+            keyWindowCoordinator?.newItem?()
         }
     }
 
     @objc func selectAll(_ sender: Any?) {
-        keyWindowCoordinator?.selectAllTasks?()
+        keyWindowCoordinator?.selectAllItems?()
     }
 
     @objc func cut(_ sender: Any?) {
-        keyWindowCoordinator?.cutSelectedTask?()
+        keyWindowCoordinator?.cutSelectedItem?()
     }
 
     @objc func copy(_ sender: Any?) {
-        keyWindowCoordinator?.copySelectedTask?()
+        keyWindowCoordinator?.copySelectedItem?()
     }
 
     @objc func paste(_ sender: Any?) {
-        keyWindowCoordinator?.pasteAfterSelectedTask?()
+        keyWindowCoordinator?.pasteAfterSelectedItem?()
     }
 
-    @objc private func handleDeleteTask() {
-        keyWindowCoordinator?.deleteSelectedTask?()
+    @objc private func handleDeleteItem() {
+        keyWindowCoordinator?.deleteSelectedItem?()
     }
 
     @objc private func handleNewWindow() {
@@ -129,19 +129,19 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSMenuItemValidation {
     }
 
     @objc private func handleMoveUp() {
-        keyWindowCoordinator?.moveSelectedTaskUp?()
+        keyWindowCoordinator?.moveSelectedItemUp?()
     }
 
     @objc private func handleMoveDown() {
-        keyWindowCoordinator?.moveSelectedTaskDown?()
+        keyWindowCoordinator?.moveSelectedItemDown?()
     }
 
     @objc private func handleMarkCompleted() {
-        keyWindowCoordinator?.markSelectedTaskCompleted?()
+        keyWindowCoordinator?.markSelectedItemCompleted?()
     }
 
     @objc private func handleClearCompleted() {
-        keyWindowCoordinator?.clearCompletedTasks?()
+        keyWindowCoordinator?.clearCompletedItems?()
     }
 
     @objc func handleShowSyncDiagnostics() {
@@ -172,8 +172,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSMenuItemValidation {
     private func openNewWindow() {
         let defaultContentSize = NSSize(width: 400, height: 350)
         let windowCoordinator = WindowCoordinator()
-        let rootView = TaskListView(
-            store: TaskStore(persistenceController: persistenceController),
+        let rootView = ItemListView(
+            store: ItemStore(persistenceController: persistenceController),
             syncMonitor: persistenceController.syncMonitor,
             windowCoordinator: windowCoordinator
         )
@@ -294,10 +294,10 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSMenuItemValidation {
         mainMenu.addItem(appMenuItem)
 
         let fileMenu = NSMenu(title: "File")
-        let newTaskItem = NSMenuItem(title: "New Item", action: #selector(handleNewTask), keyEquivalent: "n")
-        newTaskItem.keyEquivalentModifierMask = [.command]
-        newTaskItem.target = self
-        fileMenu.addItem(newTaskItem)
+        let newItemEntity = NSMenuItem(title: "New Item", action: #selector(handleNewItem), keyEquivalent: "n")
+        newItemEntity.keyEquivalentModifierMask = [.command]
+        newItemEntity.target = self
+        fileMenu.addItem(newItemEntity)
         fileMenu.addItem(NSMenuItem.separator())
 
         let newWindowItem = NSMenuItem(title: "New Window", action: #selector(handleNewWindow), keyEquivalent: "n")
@@ -326,7 +326,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSMenuItemValidation {
         editMenu.addItem(withTitle: "Copy", action: #selector(NSText.copy(_:)), keyEquivalent: "c")
         editMenu.addItem(withTitle: "Paste", action: #selector(NSText.paste(_:)), keyEquivalent: "v")
         editMenu.addItem(withTitle: "Select All", action: #selector(NSText.selectAll(_:)), keyEquivalent: "a")
-        let deleteItem = NSMenuItem(title: "Delete", action: #selector(handleDeleteTask), keyEquivalent: "\u{08}")
+        let deleteItem = NSMenuItem(title: "Delete", action: #selector(handleDeleteItem), keyEquivalent: "\u{08}")
         deleteItem.keyEquivalentModifierMask = []
         deleteItem.target = self
         editMenu.addItem(deleteItem)

diff --git a/ListlessMac/Views/TaskListView.swift b/ListlessMac/Views/ItemListView.swift

@@ -1,11 +1,11 @@
 import SwiftUI
 import UniformTypeIdentifiers
 
-struct TaskListView: View, TaskListViewProtocol {
+struct ItemListView: View, ItemListViewProtocol {
     struct InteractionStateData {
         var dragState: DragState = .idle
-        var liftedTaskID: UUID?
-        var draftPlacement: DraftTaskPlacement?
+        var liftedItemID: UUID?
+        var draftPlacement: DraftItemPlacement?
         var draftTitle: String = ""
     }
 
@@ -15,14 +15,14 @@ struct TaskListView: View, TaskListViewProtocol {
     @Environment(\.undoManager) var undoManager
     @Environment(\.managedObjectContext) var managedObjectContext
 
-    let store: TaskStore
+    let store: ItemStore
     let windowCoordinator: WindowCoordinator
     @ObservedObject var syncMonitor: CloudKitSyncMonitor
     @FetchRequest(
         sortDescriptors: [],
         animation: .default
     )
-    var tasks: FetchedResults<TaskItem>
+    var items: FetchedResults<ItemEntity>
     @FocusState private var focusedFieldBinding: FocusField?
     @State var fState = FocusStateData()
     @State private var iState = InteractionStateData()
@@ -40,7 +40,7 @@ struct TaskListView: View, TaskListViewProtocol {
         nonmutating set { iState.dragState = newValue }
     }
 
-    var draftPlacement: DraftTaskPlacement? {
+    var draftPlacement: DraftItemPlacement? {
         get { iState.draftPlacement }
         nonmutating set { iState.draftPlacement = newValue }
     }
@@ -51,19 +51,19 @@ struct TaskListView: View, TaskListViewProtocol {
     }
 
     var vStackSpacing: CGFloat { 0 }
-    var isCompletelyEmpty: Bool { activeTasks.isEmpty && completedTasks.isEmpty }
+    var isCompletelyEmpty: Bool { activeItems.isEmpty && completedItems.isEmpty }
     var selectedIndex: Int? {
-        guard let currentID = fState.selectedTaskID else { return nil }
-        return activeTasks.firstIndex(where: { $0.id == currentID })
+        guard let currentID = fState.selectedItemID else { return nil }
+        return activeItems.firstIndex(where: { $0.id == currentID })
     }
 
     var canDeleteSelectionFromList: Bool {
-        !fState.selectedTaskIDs.isEmpty && focusedField == .scrollView
+        !fState.selectedItemIDs.isEmpty && focusedField == .scrollView
     }
 
     var canMarkSelectionCompleted: Bool {
         guard focusedField == .scrollView else { return false }
-        let selected = allTasksInDisplayOrder.filter { fState.isTaskSelected($0.id) }
+        let selected = allItemsInDisplayOrder.filter { fState.isItemSelected($0.id) }
         guard !selected.isEmpty else { return false }
         let hasActive = selected.contains { !$0.isCompleted }
         let hasCompleted = selected.contains { $0.isCompleted }
@@ -72,10 +72,10 @@ struct TaskListView: View, TaskListViewProtocol {
 
     var markCompletedMenuTitle: String {
         if fState.hasMultipleSelection {
-            let hasCompleted = completedTasks.contains(where: { fState.isTaskSelected($0.id) })
+            let hasCompleted = completedItems.contains(where: { fState.isItemSelected($0.id) })
             return hasCompleted ? "Mark as Incomplete" : "Mark as Complete"
         }
-        return completedTasks.contains(where: { $0.id == fState.selectedTaskID })
+        return completedItems.contains(where: { $0.id == fState.selectedItemID })
             ? "Mark as Incomplete" : "Mark as Complete"
     }
 
@@ -90,89 +90,89 @@ struct TaskListView: View, TaskListViewProtocol {
         guard focusedField == .scrollView else { return false }
         guard !fState.hasMultipleSelection else { return false }
         guard let index = selectedIndex else { return false }
-        return index < activeTasks.count - 1
+        return index < activeItems.count - 1
     }
 
     struct MenuState: Equatable {
-        let selectedTaskIDs: Set<UUID>
+        let selectedItemIDs: Set<UUID>
         let isScrollViewFocused: Bool
-        let activeTaskCount: Int
-        let completedTaskCount: Int
+        let activeItemCount: Int
+        let completedItemCount: Int
         let selectedIndex: Int?
     }
 
     var windowCoordinatorTrigger: MenuState {
         MenuState(
-            selectedTaskIDs: fState.selectedTaskIDs,
+            selectedItemIDs: fState.selectedItemIDs,
             isScrollViewFocused: focusedField == .scrollView,
-            activeTaskCount: activeTasks.count,
-            completedTaskCount: completedTasks.count,
+            activeItemCount: activeItems.count,
+            completedItemCount: completedItems.count,
             selectedIndex: selectedIndex
         )
     }
 
     func updateWindowCoordinator() {
         let coord = windowCoordinator
-        coord.newTask = { createNewTask() }
-        coord.copySelectedTask = {
-            guard let taskID = fState.selectedTaskID,
-                  let task = tasks.first(where: { $0.id == taskID }) else { return }
+        coord.newItem = { createNewItem() }
+        coord.copySelectedItem = {
+            guard let itemID = fState.selectedItemID,
+                  let item = items.first(where: { $0.id == itemID }) else { return }
             let pasteboard = NSPasteboard.general
             pasteboard.clearContents()
-            pasteboard.setString(task.title, forType: .string)
+            pasteboard.setString(item.title, forType: .string)
         }
-        coord.cutSelectedTask = {
-            guard let taskID = fState.selectedTaskID,
-                  let task = tasks.first(where: { $0.id == taskID }) else { return }
+        coord.cutSelectedItem = {
+            guard let itemID = fState.selectedItemID,
+                  let item = items.first(where: { $0.id == itemID }) else { return }
             let pasteboard = NSPasteboard.general
             pasteboard.clearContents()
-            pasteboard.setString(task.title, forType: .string)
-            deleteTask(task)
+            pasteboard.setString(item.title, forType: .string)
+            deleteItem(item)
         }
-        coord.pasteAfterSelectedTask = {
-            guard let taskID = fState.selectedTaskID,
+        coord.pasteAfterSelectedItem = {
+            guard let itemID = fState.selectedItemID,
                   let string = NSPasteboard.general.string(forType: .string) else { return }
-            createTask(title: string, afterTaskID: taskID)
+            createItem(title: string, afterItemID: itemID)
         }
-        coord.deleteSelectedTask = { _ = deleteSelectedTask() }
-        coord.moveSelectedTaskUp = { moveSelectedTaskUp() }
-        coord.moveSelectedTaskDown = { moveSelectedTaskDown() }
-        coord.markSelectedTaskCompleted = { markSelectedTaskCompleted() }
-        coord.selectAllTasks = {
-            fState.selectAll(displayOrder: allTasksInDisplayOrder.map(\.id))
+        coord.deleteSelectedItem = { _ = deleteSelectedItem() }
+        coord.moveSelectedItemUp = { moveSelectedItemUp() }
+        coord.moveSelectedItemDown = { moveSelectedItemDown() }
+        coord.markSelectedItemCompleted = { markSelectedItemCompleted() }
+        coord.selectAllItems = {
+            fState.selectAll(displayOrder: allItemsInDisplayOrder.map(\.id))
         }
-        coord.clearCompletedTasks = { clearCompletedTasks() }
+        coord.clearCompletedItems = { clearCompletedItems() }
         let inNavMode = focusedField == .scrollView
-        let singleSelect = !fState.selectedTaskIDs.isEmpty && !fState.hasMultipleSelection
-        coord.canSelectAllTasks = inNavMode && !allTasksInDisplayOrder.isEmpty
-        coord.canCopySelectedTask = singleSelect && inNavMode
-        coord.canCutSelectedTask = singleSelect && inNavMode
-        coord.canPasteAfterSelectedTask = selectedIndex != nil && singleSelect && inNavMode
-        coord.canDeleteSelectedTask = canDeleteSelectionFromList
-        coord.canMoveSelectedTaskUp = canMoveSelectionUp
-        coord.canMoveSelectedTaskDown = canMoveSelectionDown
-        coord.canMarkSelectedTaskCompleted = canMarkSelectionCompleted
+        let singleSelect = !fState.selectedItemIDs.isEmpty && !fState.hasMultipleSelection
+        coord.canSelectAllItems = inNavMode && !allItemsInDisplayOrder.isEmpty
+        coord.canCopySelectedItem = singleSelect && inNavMode
+        coord.canCutSelectedItem = singleSelect && inNavMode
+        coord.canPasteAfterSelectedItem = selectedIndex != nil && singleSelect && inNavMode
+        coord.canDeleteSelectedItem = canDeleteSelectionFromList
+        coord.canMoveSelectedItemUp = canMoveSelectionUp
+        coord.canMoveSelectedItemDown = canMoveSelectionDown
+        coord.canMarkSelectedItemCompleted = canMarkSelectionCompleted
         coord.markCompletedTitle = markCompletedMenuTitle
-        coord.canClearCompletedTasks = !completedTasks.isEmpty
+        coord.canClearCompletedItems = !completedItems.isEmpty
     }
 
-    init(store: TaskStore, syncMonitor: CloudKitSyncMonitor, windowCoordinator: WindowCoordinator) {
+    init(store: ItemStore, syncMonitor: CloudKitSyncMonitor, windowCoordinator: WindowCoordinator) {
         self.store = store
         self.syncMonitor = syncMonitor
         self.windowCoordinator = windowCoordinator
     }
 
-    func isRowLifted(_ taskID: UUID) -> Bool {
-        iState.liftedTaskID == taskID || draggedTaskID == taskID
+    func isRowLifted(_ itemID: UUID) -> Bool {
+        iState.liftedItemID == itemID || draggedItemID == itemID
     }
 
-    func clearDraftTaskUI(at placement: DraftTaskPlacement, hasTitle _: Bool) {
+    func clearDraftItemUI(at placement: DraftItemPlacement, hasTitle _: Bool) {
         if draftPlacement == placement {
             draftPlacement = nil
         }
         draftTitle = ""
-        if fState.selectedTaskID == draftID(for: placement) {
-            fState.selectedTaskID = nil
+        if fState.selectedItemID == draftID(for: placement) {
+            fState.selectedItemID = nil
         }
         // Resign AppKit first responder explicitly — SwiftUI's @FocusState
         // and AppKit's responder chain are parallel systems, so setting
@@ -187,52 +187,52 @@ struct TaskListView: View, TaskListViewProtocol {
         ScrollView {
           ScrollViewReader { scrollProxy in
             VStack(alignment: .leading, spacing: vStackSpacing) {
-                ForEach(Array(displayActiveTasks.enumerated()), id: \.element.id) { index, task in
-                    let taskID = task.id
-                    TaskRowView(
-                        task: task,
-                        taskID: taskID,
+                ForEach(Array(displayActiveItems.enumerated()), id: \.element.id) { index, item in
+                    let itemID = item.id
+                    ItemRowView(
+                        item: item,
+                        itemID: itemID,
                         index: index,
-                        totalTasks: displayActiveTasks.count,
-                        isSelected: fState.isTaskSelected(taskID),
+                        totalItems: displayActiveItems.count,
+                        isSelected: fState.isItemSelected(itemID),
                         focusedField: $focusedFieldBinding,
                         onToggle: { toggleCompletion($0) },
                         onTitleChange: { updateTitle($0, $1) },
-                        onDelete: { deleteTask($0) },
+                        onDelete: { deleteItem($0) },
                         onSelect: {
                             let modifiers = NSApp.currentEvent?.modifierFlags ?? []
-                            selectTask(
+                            selectItem(
                                 $0,
                                 extendSelection: modifiers.contains(.shift),
                                 toggleSelection: modifiers.contains(.command)
                             )
                         },
                         onStartEdit: { startEditing($0) },
-                        onEndEdit: { endEditing($0, shouldCreateNewTask: $1) },
-                        onPaste: { createTask(title: $0, afterTaskID: taskID) }
+                        onEndEdit: { endEditing($0, shouldCreateNewItem: $1) },
+                        onPaste: { createItem(title: $0, afterItemID: itemID) }
                     )
-                    .taskDragGesture(
-                        isActive: !task.isCompleted,
-                        taskID: task.id,
+                    .itemDragGesture(
+                        isActive: !item.isCompleted,
+                        itemID: item.id,
                         onDragStart: {
-                            iState.liftedTaskID = nil
-                            startDrag(taskID: task.id)
+                            iState.liftedItemID = nil
+                            startDrag(itemID: item.id)
                         },
-                        onLift: { iState.liftedTaskID = task.id },
+                        onLift: { iState.liftedItemID = item.id },
                         onLiftEnd: {
-                            if iState.liftedTaskID == task.id { iState.liftedTaskID = nil }
-                            if draggedTaskID == task.id { clearDragState() }
+                            if iState.liftedItemID == item.id { iState.liftedItemID = nil }
+                            if draggedItemID == item.id { clearDragState() }
                         }
                     )
-                    .scaleEffect(isRowLifted(taskID) ? 1.03 : 1.0)
+                    .scaleEffect(isRowLifted(itemID) ? 1.03 : 1.0)
                     .shadow(
-                        color: isRowLifted(taskID) ? .black.opacity(0.2) : .clear,
+                        color: isRowLifted(itemID) ? .black.opacity(0.2) : .clear,
                         radius: 8, y: 3
                     )
-                    .zIndex(isRowLifted(taskID) ? 1 : 0)
-                    .animation(.spring(response: 0.3, dampingFraction: 0.8), value: isRowLifted(taskID))
+                    .zIndex(isRowLifted(itemID) ? 1 : 0)
+                    .animation(.spring(response: 0.3, dampingFraction: 0.8), value: isRowLifted(itemID))
                     .overlay {
-                        if draggedTaskID != nil && draggedTaskID != taskID {
+                        if draggedItemID != nil && draggedItemID != itemID {
                             VStack(spacing: 0) {
                                 // Top 1/6 - insert BEFORE
                                 Color.clear
@@ -240,8 +240,8 @@ struct TaskListView: View, TaskListViewProtocol {
                                     .layoutPriority(1)
                                     .onDrop(
                                         of: [UTType.text],
-                                        delegate: TaskReorderDropDelegate(
-                                            onTargeted: { updateVisualOrder(insertBefore: taskID) },
+                                        delegate: ItemReorderDropDelegate(
+                                            onTargeted: { updateVisualOrder(insertBefore: itemID) },
                                             onPerform: { commitCurrentDrag() }
                                         )
                                     )
@@ -252,8 +252,8 @@ struct TaskListView: View, TaskListViewProtocol {
                                     .layoutPriority(4)
                                     .onDrop(
                                         of: [UTType.text],
-                                        delegate: TaskReorderDropDelegate(
-                                            onTargeted: { updateVisualOrderSmart(relativeTo: taskID) },
+                                        delegate: ItemReorderDropDelegate(
+                                            onTargeted: { updateVisualOrderSmart(relativeTo: itemID) },
                                             onPerform: { commitCurrentDrag() }
                                         )
                                     )
@@ -264,8 +264,8 @@ struct TaskListView: View, TaskListViewProtocol {
                                     .layoutPriority(1)
                                     .onDrop(
                                         of: [UTType.text],
-                                        delegate: TaskReorderDropDelegate(
-                                            onTargeted: { updateVisualOrder(insertAfter: taskID) },
+                                        delegate: ItemReorderDropDelegate(
+                                            onTargeted: { updateVisualOrder(insertAfter: itemID) },
                                             onPerform: { commitCurrentDrag() }
                                         )
                                     )
@@ -275,12 +275,12 @@ struct TaskListView: View, TaskListViewProtocol {
                 }
 
                 if draftPlacement == .append {
-                    let total = max(1, displayActiveTasks.count + 1)
-                    let index = displayActiveTasks.count
-                    let accentColor = cachedTaskColor(
+                    let total = max(1, displayActiveItems.count + 1)
+                    let index = displayActiveItems.count
+                    let accentColor = cachedItemColor(
                         forIndex: index, total: total, theme: colorTheme
                     )
-                    let isSelected = fState.isTaskSelected(draftAppendRowID)
+                    let isSelected = fState.isItemSelected(draftAppendRowID)
                     HStack(alignment: .firstTextBaseline, spacing: 12) {
                         Image(systemName: "circle")
                             .foregroundStyle(.primary)
@@ -296,20 +296,20 @@ struct TaskListView: View, TaskListViewProtocol {
                                 set: { iState.draftTitle = $0 }
                             ),
                             isCompleted: false,
-                            onEditingChanged: { editing, shouldCreateNewTask in
+                            onEditingChanged: { editing, shouldCreateNewItem in
                                 if editing {
-                                    beginDraftTaskEditing(.append)
+                                    beginDraftItemEditing(.append)
                                 } else {
-                                    commitDraftTask(
-                                        shouldCreateNewTask: shouldCreateNewTask
+                                    commitDraftItem(
+                                        shouldCreateNewItem: shouldCreateNewItem
                                     )
                                 }
                             },
-                            taskID: draftAppendRowID
+                            itemID: draftAppendRowID
                         )
                         .focused(
                             $focusedFieldBinding,
-                            equals: .task(draftAppendRowID)
+                            equals: .item(draftAppendRowID)
                         )
                         .frame(maxWidth: .infinity, alignment: .leading)
                     }
@@ -340,18 +340,18 @@ struct TaskListView: View, TaskListViewProtocol {
                     .id(draftAppendRowID)
                 }
 
-                ForEach(completedTasks) { task in
-                    let taskID = task.id
-                    TaskRowView(
-                        task: task,
-                        taskID: taskID,
-                        isSelected: fState.isTaskSelected(taskID),
+                ForEach(completedItems) { item in
+                    let itemID = item.id
+                    ItemRowView(
+                        item: item,
+                        itemID: itemID,
+                        isSelected: fState.isItemSelected(itemID),
                         focusedField: $focusedFieldBinding,
                         onToggle: { toggleCompletion($0) },
                         onTitleChange: { updateTitle($0, $1) },
-                        onDelete: { deleteTask($0) },
+                        onDelete: { deleteItem($0) },
                         onSelect: {
-                            selectTask(
+                            selectItem(
                                 $0,
                                 extendSelection: NSEvent.modifierFlags.contains(.shift)
                             )
@@ -362,14 +362,14 @@ struct TaskListView: View, TaskListViewProtocol {
             .frame(maxWidth: .infinity, alignment: .topLeading)
             .onDrop(
                 of: [UTType.text],
-                delegate: TaskReorderDropDelegate(
+                delegate: ItemReorderDropDelegate(
                     onTargeted: {},
                     onPerform: { commitCurrentDrag() }
                 )
             )
             .onChange(of: focusedFieldBinding) { _, newValue in
-                if case .task(let id) = (newValue ?? fState.focusedField),
-                    draggedTaskID == nil,
+                if case .item(let id) = (newValue ?? fState.focusedField),
+                    draggedItemID == nil,
                     id != draftPrependRowID
                 {
                     withAnimation {
@@ -377,8 +377,8 @@ struct TaskListView: View, TaskListViewProtocol {
                     }
                 }
             }
-            .onChange(of: fState.selectedTaskID) { _, newID in
-                if let newID, draggedTaskID == nil {
+            .onChange(of: fState.selectedItemID) { _, newID in
+                if let newID, draggedItemID == nil {
                     guard newID != draftPrependRowID else { return }
                     withAnimation {
                         scrollProxy.scrollTo(newID)
@@ -389,7 +389,7 @@ struct TaskListView: View, TaskListViewProtocol {
         }
         .onDrop(
             of: [UTType.text],
-            delegate: TaskReorderDropDelegate(
+            delegate: ItemReorderDropDelegate(
                 onTargeted: {},
                 onPerform: { commitCurrentDrag() }
             )
@@ -412,13 +412,13 @@ struct TaskListView: View, TaskListViewProtocol {
         .focusable()
         .focused($focusedFieldBinding, equals: .scrollView)
         .focusEffectDisabled()
-        .accessibilityIdentifier("task-list-scrollview")
+        .accessibilityIdentifier("item-list-scrollview")
         .keyboardNavigation([
             ShortcutKey(key: .upArrow): navigateUp,
             ShortcutKey(key: .downArrow): navigateDown,
             ShortcutKey(key: .upArrow, modifiers: .shift): navigateUpExtend,
             ShortcutKey(key: .downArrow, modifiers: .shift): navigateDownExtend,
-            ShortcutKey(key: .return): focusSelectedTask,
+            ShortcutKey(key: .return): focusSelectedItem,
         ])
         .onAppear {
             if focusedFieldBinding == nil {
@@ -466,7 +466,7 @@ struct TaskListView: View, TaskListViewProtocol {
     }
 }
 
-private struct TaskReorderDropDelegate: DropDelegate {
+private struct ItemReorderDropDelegate: DropDelegate {
     let onTargeted: () -> Void
     let onPerform: () -> Bool
 

diff --git a/ListlessMac/Views/TaskRowView.swift b/ListlessMac/Views/ItemRowView.swift

@@ -1,17 +1,17 @@
 import SwiftUI
 
-struct TaskRowView: View {
-    let task: TaskItem
-    let taskID: UUID
+struct ItemRowView: View {
+    let item: ItemEntity
+    let itemID: UUID
     let index: Int
-    let totalTasks: Int
+    let totalItems: Int
     let isSelected: Bool
-    let onToggle: (TaskItem) -> Void
-    let onTitleChange: (TaskItem, String) -> Void
-    let onDelete: (TaskItem) -> Void
+    let onToggle: (ItemEntity) -> Void
+    let onTitleChange: (ItemEntity, String) -> Void
+    let onDelete: (ItemEntity) -> Void
     let onSelect: (UUID) -> Void
     let onStartEdit: (UUID) -> Void
-    let onEndEdit: (UUID, _ shouldCreateNewTask: Bool) -> Void
+    let onEndEdit: (UUID, _ shouldCreateNewItem: Bool) -> Void
     let onPaste: (String) -> Void
     @FocusState.Binding var focusedField: FocusField?
 
@@ -32,29 +32,29 @@ struct TaskRowView: View {
 
     @MainActor
     private func computeAccentColor() -> Color {
-        guard !task.isCompleted else { return .clear }
-        return cachedTaskColor(forIndex: index, total: totalTasks, theme: colorTheme)
+        guard !item.isCompleted else { return .clear }
+        return cachedItemColor(forIndex: index, total: totalItems, theme: colorTheme)
     }
 
     init(
-        task: TaskItem,
-        taskID: UUID,
+        item: ItemEntity,
+        itemID: UUID,
         index: Int = 0,
-        totalTasks: Int = 1,
+        totalItems: Int = 1,
         isSelected: Bool,
         focusedField: FocusState<FocusField?>.Binding,
-        onToggle: @escaping (TaskItem) -> Void,
-        onTitleChange: @escaping (TaskItem, String) -> Void,
-        onDelete: @escaping (TaskItem) -> Void,
+        onToggle: @escaping (ItemEntity) -> Void,
+        onTitleChange: @escaping (ItemEntity, String) -> Void,
+        onDelete: @escaping (ItemEntity) -> Void,
         onSelect: @escaping (UUID) -> Void,
         onStartEdit: @escaping (UUID) -> Void = { _ in },
-        onEndEdit: @escaping (UUID, _ shouldCreateNewTask: Bool) -> Void = { _, _ in },
+        onEndEdit: @escaping (UUID, _ shouldCreateNewItem: Bool) -> Void = { _, _ in },
         onPaste: @escaping (String) -> Void = { _ in }
     ) {
-        self.task = task
-        self.taskID = taskID
+        self.item = item
+        self.itemID = itemID
         self.index = index
-        self.totalTasks = totalTasks
+        self.totalItems = totalItems
         self.isSelected = isSelected
         self.onToggle = onToggle
         self.onTitleChange = onTitleChange
@@ -69,10 +69,10 @@ struct TaskRowView: View {
     var body: some View {
         HStack(alignment: .firstTextBaseline, spacing: 12) {
             Button {
-                onToggle(task)
+                onToggle(item)
             } label: {
-                Image(systemName: task.isCompleted ? "checkmark.circle.fill" : "circle")
-                    .foregroundStyle(task.isCompleted ? .secondary : .primary)
+                Image(systemName: item.isCompleted ? "checkmark.circle.fill" : "circle")
+                    .foregroundStyle(item.isCompleted ? .secondary : .primary)
                     .font(.system(size: 17))
                     .fontWeight(.thin)
             }
@@ -80,30 +80,30 @@ struct TaskRowView: View {
             .alignmentGuide(.firstTextBaseline) { d in
                 d[VerticalAlignment.center] + 5
             }
-            .accessibilityIdentifier("task-checkbox")
-            .accessibilityValue(task.isCompleted ? "checkmark.circle.fill" : "circle")
+            .accessibilityIdentifier("item-checkbox")
+            .accessibilityValue(item.isCompleted ? "checkmark.circle.fill" : "circle")
 
             ClickableTextField(
                 text: $editingTitle,
-                isCompleted: task.isCompleted,
-                onEditingChanged: { editing, shouldCreateNewTask in
+                isCompleted: item.isCompleted,
+                onEditingChanged: { editing, shouldCreateNewItem in
                     isCurrentlyEditing = editing
                     if editing {
-                        onStartEdit(taskID)
+                        onStartEdit(itemID)
                     } else {
-                        onEndEdit(taskID, shouldCreateNewTask)
+                        onEndEdit(itemID, shouldCreateNewItem)
                     }
                 },
-                taskID: taskID,
+                itemID: itemID,
                 onContentChange: { newTitle in
-                    guard !task.isCompleted else { return }
-                    onTitleChange(task, newTitle)
+                    guard !item.isCompleted else { return }
+                    onTitleChange(item, newTitle)
                 }
             )
-            .focused($focusedField, equals: .task(taskID))
+            .focused($focusedField, equals: .item(itemID))
             .frame(maxWidth: .infinity, alignment: .leading)
             .accessibilityIdentifier(
-                isCurrentlyEditing ? "task-textfield" : "task-text-\(taskID.uuidString)")
+                isCurrentlyEditing ? "item-textfield" : "item-text-\(itemID.uuidString)")
         }
         .padding(.top, 4)
         .padding(.vertical, 8)
@@ -111,7 +111,7 @@ struct TaskRowView: View {
         .frame(maxWidth: .infinity, alignment: .leading)
         .contentShape(Rectangle())
         .onTapGesture {
-            onSelect(taskID)
+            onSelect(itemID)
         }
         .background(selectionBackground)
         .overlay(alignment: .leading) {
@@ -123,8 +123,8 @@ struct TaskRowView: View {
         }
         .overlay(alignment: .bottom) {
             // Hairline border between rows, inset to align with text
-            // Only show for active (non-completed) tasks
-            if !task.isCompleted {
+            // Only show for active (non-completed) items
+            if !item.isCompleted {
                 Rectangle()
                     .fill(.separator)
                     .frame(height: 0.5)
@@ -132,14 +132,14 @@ struct TaskRowView: View {
             }
         }
         .overlay {
-            if isSelected && !task.isCompleted {
+            if isSelected && !item.isCompleted {
                 RoundedRectangle(cornerRadius: 6, style: .continuous)
                     .strokeBorder(cachedAccentColor.opacity(0.40), lineWidth: 2)
             }
         }
         .contextMenu {
-            Button(task.isCompleted ? "Mark as Incomplete" : "Mark as Complete") {
-                onToggle(task)
+            Button(item.isCompleted ? "Mark as Incomplete" : "Mark as Complete") {
+                onToggle(item)
             }
             Divider()
             Button("Cut") {
@@ -151,14 +151,14 @@ struct TaskRowView: View {
             Button("Paste") {
                 pasteFromPasteboard()
             }
-            .disabled(task.isCompleted)
+            .disabled(item.isCompleted)
             Divider()
             Button("Delete", role: .destructive) {
-                onDelete(task)
+                onDelete(item)
             }
         }
-        .onChange(of: task.title) { _, newValue in
-            // Keep editingTitle in sync with task.title when not editing
+        .onChange(of: item.title) { _, newValue in
+            // Keep editingTitle in sync with item.title when not editing
             if !isCurrentlyEditing {
                 editingTitle = newValue
             }
@@ -169,12 +169,12 @@ struct TaskRowView: View {
         .onChange(of: index) { _, _ in
             cachedAccentColor = computeAccentColor()
         }
-        .onChange(of: totalTasks) { _, _ in
+        .onChange(of: totalItems) { _, _ in
             cachedAccentColor = computeAccentColor()
         }
         .onAppear {
             // Initialize editingTitle and cache accent color (computed once)
-            editingTitle = task.title
+            editingTitle = item.title
             cachedAccentColor = computeAccentColor()
         }
     }
@@ -182,7 +182,7 @@ struct TaskRowView: View {
     @ViewBuilder
     private var selectionBackground: some View {
         if isSelected {
-            if task.isCompleted {
+            if item.isCompleted {
                 Color(nsColor: .controlBackgroundColor)
             } else {
                 RoundedRectangle(cornerRadius: 6, style: .continuous)
@@ -197,14 +197,14 @@ struct TaskRowView: View {
 
     private func cutToPasteboard() {
         copyToPasteboard()
-        onDelete(task)
+        onDelete(item)
     }
 
     private func copyToPasteboard() {
-        guard !task.title.isEmpty else { return }
+        guard !item.title.isEmpty else { return }
         let pasteboard = NSPasteboard.general
         pasteboard.clearContents()
-        pasteboard.setString(task.title, forType: .string)
+        pasteboard.setString(item.title, forType: .string)
     }
 
     private func pasteFromPasteboard() {

diff --git a/ListlessWatch/ListlessWatchApp.swift b/ListlessWatch/ListlessWatchApp.swift

@@ -11,8 +11,8 @@ struct ListlessWatchApp: App {
 
     var body: some Scene {
         WindowGroup {
-            TaskListView(
-                store: TaskStore(persistenceController: persistenceController),
+            ItemListView(
+                store: ItemStore(persistenceController: persistenceController),
                 syncMonitor: persistenceController.syncMonitor
             )
             .environment(\.managedObjectContext, persistenceController.viewContext)

diff --git a/ListlessWatch/Views/TaskListView.swift b/ListlessWatch/Views/ItemListView.swift

@@ -1,28 +1,28 @@
 import CoreData
 import SwiftUI
 
-struct TaskListView: View {
-    let store: TaskStore
+struct ItemListView: View {
+    let store: ItemStore
     let syncMonitor: CloudKitSyncMonitor
 
     @AppStorage("headingText") private var headingText = "Items"
 
     @FetchRequest(
         sortDescriptors: [
-            SortDescriptor(\TaskItem.sortOrder, order: .forward),
+            SortDescriptor(\ItemEntity.sortOrder, order: .forward),
         ],
         animation: .default
     )
-    private var tasks: FetchedResults<TaskItem>
+    private var items: FetchedResults<ItemEntity>
 
     var body: some View {
-        let activeTasks = tasks.filter { !$0.isCompleted }
-        let completedTasks = tasks.filter { $0.isCompleted }
+        let activeItems = items.filter { !$0.isCompleted }
+        let completedItems = items.filter { $0.isCompleted }
             .sorted { $0.completedOrder > $1.completedOrder }
 
         NavigationStack {
             Group {
-                if tasks.isEmpty {
+                if items.isEmpty {
                     ContentUnavailableView(
                         "No Items",
                         systemImage: "checklist",
@@ -30,23 +30,23 @@ struct TaskListView: View {
                     )
                 } else {
                     List {
-                        ForEach(Array(activeTasks.enumerated()), id: \.element.id) { index, task in
-                            TaskRowView(
-                                task: task,
+                        ForEach(Array(activeItems.enumerated()), id: \.element.id) { index, item in
+                            ItemRowView(
+                                item: item,
                                 index: index,
-                                totalActive: activeTasks.count,
-                                onToggle: { toggleTask($0) }
+                                totalActive: activeItems.count,
+                                onToggle: { toggleItem($0) }
                             )
                         }
 
-                        if !completedTasks.isEmpty {
+                        if !completedItems.isEmpty {
                             Section("Completed") {
-                                ForEach(completedTasks) { task in
-                                    TaskRowView(
-                                        task: task,
+                                ForEach(completedItems) { item in
+                                    ItemRowView(
+                                        item: item,
                                         index: 0,
                                         totalActive: 0,
-                                        onToggle: { toggleTask($0) }
+                                        onToggle: { toggleItem($0) }
                                     )
                                 }
                             }
@@ -58,12 +58,12 @@ struct TaskListView: View {
         }
     }
 
-    private func toggleTask(_ task: TaskItem) {
+    private func toggleItem(_ item: ItemEntity) {
         do {
-            if task.isCompleted {
-                try store.uncomplete(taskID: task.id)
+            if item.isCompleted {
+                try store.uncomplete(itemID: item.id)
             } else {
-                try store.complete(taskID: task.id)
+                try store.complete(itemID: item.id)
             }
         } catch {
             // Sync monitor handles error reporting

diff --git a/ListlessWatch/Views/TaskRowView.swift b/ListlessWatch/Views/ItemRowView.swift

@@ -1,39 +1,39 @@
 import SwiftUI
 
-struct TaskRowView: View {
-    let task: TaskItem
+struct ItemRowView: View {
+    let item: ItemEntity
     let index: Int
     let totalActive: Int
-    let onToggle: (TaskItem) -> Void
+    let onToggle: (ItemEntity) -> Void
 
     var body: some View {
         Button {
-            onToggle(task)
+            onToggle(item)
         } label: {
             HStack(spacing: 8) {
-                Image(systemName: task.isCompleted ? "checkmark.circle.fill" : "circle")
+                Image(systemName: item.isCompleted ? "checkmark.circle.fill" : "circle")
                     .foregroundColor(
-                        task.isCompleted
+                        item.isCompleted
                             ? .secondary
-                            : cachedTaskColor(forIndex: index, total: totalActive)
+                            : cachedItemColor(forIndex: index, total: totalActive)
                     )
                     .font(.system(size: 17))
 
-                Text(task.title)
-                    .strikethrough(task.isCompleted)
-                    .foregroundStyle(task.isCompleted ? .secondary : .primary)
+                Text(item.title)
+                    .strikethrough(item.isCompleted)
+                    .foregroundStyle(item.isCompleted ? .secondary : .primary)
                     .lineLimit(3)
             }
             .padding(.vertical, 4)
         }
         .listRowBackground(
-            task.isCompleted
+            item.isCompleted
                 ? AnyView(Color(white: 0.15)
                     .clipShape(RoundedRectangle(cornerRadius: 10, style: .continuous)))
                 : AnyView(
                     ZStack(alignment: .top) {
                         Color(white: 0.15)
-                        cachedTaskColor(forIndex: index, total: totalActive)
+                        cachedItemColor(forIndex: index, total: totalActive)
                             .frame(height: 3)
                     }
                     .clipShape(RoundedRectangle(cornerRadius: 10, style: .continuous))

diff --git a/ListlessiOS/Extensions/TaskListView+Drag.swift b/ListlessiOS/Extensions/ItemListView+Drag.swift

@@ -1,8 +1,8 @@
 import SwiftUI
 
-extension TaskListView {
-    func handleIOSDragChanged(taskID: UUID, point: CGPoint) {
-        guard let draggedID = draggedTaskID,
+extension ItemListView {
+    func handleIOSDragChanged(itemID: UUID, point: CGPoint) {
+        guard let draggedID = draggedItemID,
               var order = visualOrder,
               let currentIndex = order.firstIndex(of: draggedID) else { return }
 
@@ -30,7 +30,7 @@ extension TaskListView {
     }
 
     func commitIOSDrag() {
-        guard let draggedID = draggedTaskID,
+        guard let draggedID = draggedItemID,
               let order = visualOrder,
               let finalIndex = order.firstIndex(of: draggedID) else {
             clearDragState()
@@ -38,7 +38,7 @@ extension TaskListView {
             return
         }
         do {
-            try store.moveTask(taskID: draggedID, toIndex: finalIndex)
+            try store.moveItem(itemID: draggedID, toIndex: finalIndex)
             clearDragState()
             isDragging = false
         } catch {

diff --git a/ListlessiOS/Extensions/TaskListView+NavigationHeader.swift b/ListlessiOS/Extensions/ItemListView+NavigationHeader.swift

@@ -1,6 +1,6 @@
 import SwiftUI
 
-extension TaskListView {
+extension ItemListView {
     @ViewBuilder
     private var settingsButton: some View {
         Button {
@@ -41,7 +41,7 @@ extension TaskListView {
         .padding(.bottom, 8)
         .contentShape(Rectangle())
         .onTapGesture {
-            fState.selectedTaskID = nil
+            fState.selectedItemID = nil
             focusedField = nil
         }
     }

diff --git a/ListlessiOS/Extensions/TaskListView+PullGestures.swift b/ListlessiOS/Extensions/ItemListView+PullGestures.swift

@@ -1,10 +1,10 @@
 import SwiftUI
 
-extension TaskListView {
+extension ItemListView {
     struct PullToCreateState {
         enum Action {
             case none
-            case createTask
+            case createItem
             case collapseIndicator
         }
 
@@ -53,7 +53,7 @@ extension TaskListView {
 
             if pullOffset >= pullThreshold || isFlick {
                 isInsertionPending = true
-                return .createTask
+                return .createItem
             }
 
             isInsertionPending = false
@@ -63,18 +63,18 @@ extension TaskListView {
 }
 
 private struct PullGesturesModifier: ViewModifier {
-    @Binding var pullToCreate: TaskListView.PullToCreateState
+    @Binding var pullToCreate: ItemListView.PullToCreateState
     @Binding var pullUpOffset: CGFloat
 
     @AppStorage("hapticsEnabled") private var hapticsEnabled = true
     @State private var isScrollInteracting = false
 
     let isDraftOpen: Bool
-    let hasCompletedTasks: Bool
+    let hasCompletedItems: Bool
     let pullCreateThreshold: CGFloat
     let flickThreshold: CGFloat
     let pullClearThreshold: CGFloat
-    let onCreateTaskAtTop: () -> UUID
+    let onCreateItemAtTop: () -> UUID
     let onClearCompleted: () -> Void
 
     func body(content: Content) -> some View {
@@ -92,7 +92,7 @@ private struct PullGesturesModifier: ViewModifier {
                 )
                 return max(0, geo.contentOffset.y - maxOffset)
             } action: { _, bottomOverscroll in
-                guard hasCompletedTasks, isScrollInteracting else { return }
+                guard hasCompletedItems, isScrollInteracting else { return }
                 pullUpOffset = bottomOverscroll
             }
             .onScrollPhaseChange { oldPhase, newPhase in
@@ -130,11 +130,11 @@ private struct PullGesturesModifier: ViewModifier {
         guard oldPhase == .interacting, newPhase != .interacting else { return }
 
         switch action {
-        case .createTask:
+        case .createItem:
             var transaction = Transaction(animation: nil)
             transaction.disablesAnimations = true
             withTransaction(transaction) {
-                _ = onCreateTaskAtTop()
+                _ = onCreateItemAtTop()
             }
         case .collapseIndicator:
             withAnimation(.spring(response: 0.22, dampingFraction: 1.0)) {
@@ -146,7 +146,7 @@ private struct PullGesturesModifier: ViewModifier {
     }
 
     private func handlePullToClearRelease() {
-        guard hasCompletedTasks, pullUpOffset >= pullClearThreshold else {
+        guard hasCompletedItems, pullUpOffset >= pullClearThreshold else {
             pullUpOffset = 0
             return
         }
@@ -157,14 +157,14 @@ private struct PullGesturesModifier: ViewModifier {
 
 extension View {
     func pullGestures(
-        pullToCreate: Binding<TaskListView.PullToCreateState>,
+        pullToCreate: Binding<ItemListView.PullToCreateState>,
         pullUpOffset: Binding<CGFloat>,
         isDraftOpen: Bool,
-        hasCompletedTasks: Bool,
+        hasCompletedItems: Bool,
         pullCreateThreshold: CGFloat,
         flickThreshold: CGFloat,
         pullClearThreshold: CGFloat,
-        onCreateTaskAtTop: @escaping () -> UUID,
+        onCreateItemAtTop: @escaping () -> UUID,
         onClearCompleted: @escaping () -> Void
     ) -> some View {
         modifier(
@@ -172,11 +172,11 @@ extension View {
                 pullToCreate: pullToCreate,
                 pullUpOffset: pullUpOffset,
                 isDraftOpen: isDraftOpen,
-                hasCompletedTasks: hasCompletedTasks,
+                hasCompletedItems: hasCompletedItems,
                 pullCreateThreshold: pullCreateThreshold,
                 flickThreshold: flickThreshold,
                 pullClearThreshold: pullClearThreshold,
-                onCreateTaskAtTop: onCreateTaskAtTop,
+                onCreateItemAtTop: onCreateItemAtTop,
                 onClearCompleted: onClearCompleted
             )
         )

diff --git a/ListlessiOS/Extensions/TaskListView+PullToClear.swift b/ListlessiOS/Extensions/ItemListView+PullToClear.swift

@@ -1,8 +1,8 @@
 import SwiftUI
 
-extension TaskListView {
+extension ItemListView {
     @ViewBuilder var pullToClearIndicatorRow: some View {
-        if pState.pullUpOffset > 0 && !completedTasks.isEmpty {
+        if pState.pullUpOffset > 0 && !completedItems.isEmpty {
             PullToClearIndicator(pullOffset: pState.pullUpOffset)
         }
     }

diff --git a/ListlessiOS/Extensions/TaskListView+PullToCreate.swift b/ListlessiOS/Extensions/ItemListView+PullToCreate.swift

@@ -1,26 +1,26 @@
 import SwiftUI
 
-extension TaskListView {
+extension ItemListView {
 
     // MARK: - Pull-to-Create Draft Helpers
 
     func revealPhantomRow() -> UUID {
-        let taskID = draftPrependRowID
+        let itemID = draftPrependRowID
 
         if draftPlacement != .prepend, draftPlacement != nil {
-            commitDraftTask()
+            commitDraftItem()
         }
         clearDragState()
         draftTitle = ""
         draftPlacement = .prepend
-        fState.selectedTaskID = taskID
-        fState.pendingFocus = .task(taskID)
-        focusedField = .task(taskID)
+        fState.selectedItemID = itemID
+        fState.pendingFocus = .item(itemID)
+        focusedField = .item(itemID)
 
-        return taskID
+        return itemID
     }
 
     func commitPhantomRow() {
-        commitDraftTask()
+        commitDraftItem()
     }
 }

diff --git a/ListlessiOS/Extensions/TaskListView+Toolbar.swift b/ListlessiOS/Extensions/ItemListView+Toolbar.swift

@@ -1,9 +1,9 @@
 import SwiftUI
 
-extension TaskListView {
+extension ItemListView {
     @ToolbarContentBuilder
     var platformToolbar: some ToolbarContent {
-        // No toolbar on iOS - users interact with tasks directly
+        // No toolbar on iOS - users interact with items directly
         // (tap to toggle, swipe to delete, tap background to create)
         ToolbarItemGroup {}
     }

diff --git a/ListlessiOS/Extensions/TaskListView+Undo.swift b/ListlessiOS/Extensions/ItemListView+Undo.swift

@@ -1,33 +1,33 @@
 import SwiftUI
 
-extension TaskListView {
+extension ItemListView {
 
-    func deleteTaskWithUndo(_ task: TaskItem) {
-        deleteTask(task)
+    func deleteItemWithUndo(_ item: ItemEntity) {
+        deleteItem(item)
         showUndoToast(message: "Item deleted")
     }
 
-    func deleteSelectedTaskWithUndo() -> KeyPress.Result {
+    func deleteSelectedItemWithUndo() -> KeyPress.Result {
         guard focusedField == .scrollView else {
             return .ignored
         }
-        guard let currentID = fState.selectedTaskID else {
+        guard let currentID = fState.selectedItemID else {
             return .handled
         }
-        guard let task = allTasksInDisplayOrder.first(where: { $0.id == currentID }) else {
+        guard let item = allItemsInDisplayOrder.first(where: { $0.id == currentID }) else {
             return .handled
         }
-        deleteTaskWithUndo(task)
+        deleteItemWithUndo(item)
         return .handled
     }
 
-    func clearCompletedTasksWithUndo() {
-        let ids = completedTasks.map(\.id)
+    func clearCompletedItemsWithUndo() {
+        let ids = completedItems.map(\.id)
         guard !ids.isEmpty else { return }
         let count = ids.count
         managedObjectContext.undoManager?.beginUndoGrouping()
         do {
-            try store.deleteMultiple(taskIDs: ids)
+            try store.deleteMultiple(itemIDs: ids)
         } catch {
             presentStoreError(error)
             managedObjectContext.undoManager?.endUndoGrouping()

diff --git a/ListlessiOS/Helpers/AppColors.swift b/ListlessiOS/Helpers/AppColors.swift

@@ -2,7 +2,7 @@ import SwiftUI
 import UIKit
 
 extension Color {
-    /// Canvas behind task cards: warm gray in light mode, black in dark mode.
+    /// Canvas behind item cards: warm gray in light mode, black in dark mode.
     static let outerBackground = Color(uiColor: UIColor { traits in
         traits.userInterfaceStyle == .dark
             ? .black
@@ -10,7 +10,7 @@ extension Color {
     })
 
     /// Card surface: warm tint in light mode, elevated dark gray in dark mode.
-    static let taskCard = Color(uiColor: UIColor { traits in
+    static let itemCard = Color(uiColor: UIColor { traits in
         traits.userInterfaceStyle == .dark
             ? UIColor.secondarySystemBackground
             : .white

diff --git a/ListlessiOS/Helpers/AppCommands.swift b/ListlessiOS/Helpers/AppCommands.swift

@@ -3,7 +3,7 @@ import UIKit
 // MARK: - Menu Coordinator
 
 /// Bridges SwiftUI view state to UIKit menu items, mirroring the macOS
-/// `MenuCoordinator` pattern. `TaskListView.updateMenuCoordinator()` keeps
+/// `MenuCoordinator` pattern. `ItemListView.updateMenuCoordinator()` keeps
 /// actions and enabled flags current; `KeyCaptureView` dispatches actions
 /// and validates commands via the responder chain.
 @MainActor
@@ -11,9 +11,9 @@ final class IOSMenuCoordinator {
     static let shared = IOSMenuCoordinator()
     private init() {}
 
-    // Actions — set by TaskListView on each relevant state change.
-    var newTask: (() -> Void)?
-    var deleteTask: (() -> Void)?
+    // Actions — set by ItemListView on each relevant state change.
+    var newItem: (() -> Void)?
+    var deleteItem: (() -> Void)?
     var moveUp: (() -> Void)?
     var moveDown: (() -> Void)?
     var markCompleted: (() -> Void)?
@@ -33,8 +33,8 @@ final class IOSMenuCoordinator {
 /// Selectors for menu item actions routed through the responder chain.
 /// `KeyCaptureView` (first responder) implements these as `@objc` methods.
 enum IOSMenuSelectors {
-    static let newTask = #selector(IOSMenuActions.handleNewTask)
-    static let deleteTask = #selector(IOSMenuActions.handleDeleteTask)
+    static let newItem = #selector(IOSMenuActions.handleNewItem)
+    static let deleteItem = #selector(IOSMenuActions.handleDeleteItem)
     static let moveUp = #selector(IOSMenuActions.handleMoveUp)
     static let moveDown = #selector(IOSMenuActions.handleMoveDown)
     static let markCompleted = #selector(IOSMenuActions.handleMarkCompleted)
@@ -43,8 +43,8 @@ enum IOSMenuSelectors {
 /// Protocol declaring the `@objc` action methods so selectors can be
 /// referenced at compile time. `KeyCaptureView` conforms to this.
 @MainActor @objc protocol IOSMenuActions {
-    func handleNewTask()
-    func handleDeleteTask()
+    func handleNewItem()
+    func handleDeleteItem()
     func handleMoveUp()
     func handleMoveDown()
     func handleMarkCompleted()

diff --git a/ListlessiOS/Helpers/TaskCardModifier.swift b/ListlessiOS/Helpers/ItemCardModifier.swift

@@ -1,13 +1,13 @@
 import SwiftUI
 
-struct TaskCardModifier: ViewModifier {
+struct ItemCardModifier: ViewModifier {
     var accentColor: Color
     var isSelected: Bool
 
     static let shape = UnevenRoundedRectangle(
         topLeadingRadius: 0, bottomLeadingRadius: 0,
-        bottomTrailingRadius: TaskRowMetrics.trailingCornerRadius,
-        topTrailingRadius: TaskRowMetrics.trailingCornerRadius
+        bottomTrailingRadius: ItemRowMetrics.trailingCornerRadius,
+        topTrailingRadius: ItemRowMetrics.trailingCornerRadius
     )
 
     func body(content: Content) -> some View {
@@ -16,7 +16,7 @@ struct TaskCardModifier: ViewModifier {
             .overlay(alignment: .leading) {
                 Rectangle()
                     .fill(accentColor)
-                    .frame(width: TaskRowMetrics.accentBarWidth)
+                    .frame(width: ItemRowMetrics.accentBarWidth)
             }
             .overlay(
                 isSelected
@@ -28,7 +28,7 @@ struct TaskCardModifier: ViewModifier {
 }
 
 extension View {
-    func taskCard(accentColor: Color, isSelected: Bool) -> some View {
-        modifier(TaskCardModifier(accentColor: accentColor, isSelected: isSelected))
+    func itemCard(accentColor: Color, isSelected: Bool) -> some View {
+        modifier(ItemCardModifier(accentColor: accentColor, isSelected: isSelected))
     }
 }

diff --git a/ListlessiOS/Helpers/TaskRowDragGesture.swift b/ListlessiOS/Helpers/ItemRowDragGesture.swift

@@ -1,17 +1,17 @@
 import SwiftUI
 
 extension View {
-    func taskDragGesture(
+    func itemDragGesture(
         isActive: Bool,
-        taskID: UUID,
+        itemID: UUID,
         onDragStart: @escaping (CGFloat) -> Void,
         onDragChanged: @escaping (CGPoint) -> Void,
         onDragEnded: @escaping () -> Void
     ) -> some View {
         self.modifier(
-            TaskRowDragGesture(
+            ItemRowDragGesture(
                 isActive: isActive,
-                taskID: taskID,
+                itemID: itemID,
                 onDragStart: onDragStart,
                 onDragChanged: onDragChanged,
                 onDragEnded: onDragEnded
@@ -19,9 +19,9 @@ extension View {
     }
 }
 
-struct TaskRowDragGesture: ViewModifier {
+struct ItemRowDragGesture: ViewModifier {
     let isActive: Bool
-    let taskID: UUID
+    let itemID: UUID
     let onDragStart: (CGFloat) -> Void
     let onDragChanged: (CGPoint) -> Void
     let onDragEnded: () -> Void

diff --git a/ListlessiOS/Helpers/TaskRowMetrics.swift b/ListlessiOS/Helpers/ItemRowMetrics.swift

@@ -2,8 +2,8 @@ import CoreGraphics
 import SwiftUI
 import UIKit
 
-enum TaskRowMetrics {
-    /// Base task-title font size (18pt), scaled by Dynamic Type.
+enum ItemRowMetrics {
+    /// Base item-title font size (18pt), scaled by Dynamic Type.
     static let bodyUIK: UIFont = UIFontMetrics(forTextStyle: .body)
         .scaledFont(for: .systemFont(ofSize: 18))
     /// SwiftUI equivalent for use in pure SwiftUI views (e.g. PullToCreate).

diff --git a/ListlessiOS/Helpers/TaskRowSwipeGesture.swift b/ListlessiOS/Helpers/ItemRowSwipeGesture.swift

@@ -1,19 +1,19 @@
 import SwiftUI
 
 extension View {
-    func taskSwipeGesture(
+    func itemSwipeGesture(
         isDragging: Binding<Bool>,
         isEditing: Bool,
         isSwiping: Binding<Bool>,
         swipeOffset: Binding<CGFloat>,
-        swipeDirection: Binding<TaskRowSwipeGesture.SwipeDirection>,
+        swipeDirection: Binding<ItemRowSwipeGesture.SwipeDirection>,
         isTriggered: Binding<Bool>,
         completeColor: Color = .green,
         onComplete: @escaping () -> Void,
         onDelete: @escaping () -> Void
     ) -> some View {
         self.modifier(
-            TaskRowSwipeGesture(
+            ItemRowSwipeGesture(
                 isDragging: isDragging,
                 isEditing: isEditing,
                 isSwiping: isSwiping,
@@ -27,7 +27,7 @@ extension View {
     }
 }
 
-struct TaskRowSwipeGesture: ViewModifier {
+struct ItemRowSwipeGesture: ViewModifier {
     @Binding var isDragging: Bool
     let isEditing: Bool
     @Binding var isSwiping: Bool

diff --git a/ListlessiOS/Helpers/KeyCommandBridge.swift b/ListlessiOS/Helpers/KeyCommandBridge.swift

@@ -96,12 +96,12 @@ struct KeyCommandBridge: UIViewRepresentable {
 
         // MARK: - Menu item actions (from buildMenu via responder chain)
 
-        @objc func handleNewTask() {
-            IOSMenuCoordinator.shared.newTask?()
+        @objc func handleNewItem() {
+            IOSMenuCoordinator.shared.newItem?()
         }
 
-        @objc func handleDeleteTask() {
-            IOSMenuCoordinator.shared.deleteTask?()
+        @objc func handleDeleteItem() {
+            IOSMenuCoordinator.shared.deleteItem?()
         }
 
         @objc func handleMoveUp() {
@@ -120,9 +120,9 @@ struct KeyCommandBridge: UIViewRepresentable {
 
         override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
             switch action {
-            case IOSMenuSelectors.newTask:
+            case IOSMenuSelectors.newItem:
                 return isActive
-            case IOSMenuSelectors.deleteTask:
+            case IOSMenuSelectors.deleteItem:
                 return isActive && IOSMenuCoordinator.shared.canDelete
             case IOSMenuSelectors.moveUp:
                 return isActive && IOSMenuCoordinator.shared.canMoveUp

diff --git a/ListlessiOS/Helpers/TappableTextField.swift b/ListlessiOS/Helpers/TappableTextField.swift

@@ -3,12 +3,12 @@ import UIKit
 
 /// UITextView that's always present, manages its own editing state, and expands
 /// vertically to fit its content. Mirrors the interface of ClickableTextField (macOS)
-/// so TaskListView can drive both platforms through the same focusedField binding.
+/// so ItemListView can drive both platforms through the same focusedField binding.
 struct TappableTextField: UIViewRepresentable {
     @Binding var text: String
     let isCompleted: Bool
     let isDragging: Bool
-    let onEditingChanged: (Bool, _ shouldCreateNewTask: Bool) -> Void
+    let onEditingChanged: (Bool, _ shouldCreateNewItem: Bool) -> Void
     var returnKeyType: UIReturnKeyType = .done
     var onContentChange: ((String) -> Void)? = nil
     var uiAccessibilityIdentifier: String? = nil
@@ -18,7 +18,7 @@ struct TappableTextField: UIViewRepresentable {
         let textView = UITextView()
         textView.accessibilityIdentifier = uiAccessibilityIdentifier
         textView.delegate = context.coordinator
-        textView.font = TaskRowMetrics.bodyUIK
+        textView.font = ItemRowMetrics.bodyUIK
         textView.backgroundColor = .clear
         textView.textContainerInset = .zero
         textView.textContainer.lineFragmentPadding = 0
@@ -29,7 +29,7 @@ struct TappableTextField: UIViewRepresentable {
 
         let placeholder = UILabel()
         placeholder.text = "Enter text"
-        placeholder.font = TaskRowMetrics.bodyUIK
+        placeholder.font = ItemRowMetrics.bodyUIK
         placeholder.textColor = .placeholderText
         placeholder.tag = 100
         placeholder.translatesAutoresizingMaskIntoConstraints = false
@@ -96,7 +96,7 @@ struct TappableTextField: UIViewRepresentable {
 
     private func applyStyle(to textView: UITextView, text: String, isCompleted: Bool) {
         var attributes: [NSAttributedString.Key: Any] = [
-            .font: TaskRowMetrics.bodyUIK,
+            .font: ItemRowMetrics.bodyUIK,
             .foregroundColor: isCompleted ? UIColor.secondaryLabel : UIColor.label,
         ]
         if isCompleted {
@@ -108,7 +108,7 @@ struct TappableTextField: UIViewRepresentable {
 
     final class Coordinator: NSObject, UITextViewDelegate {
         @Binding var text: String
-        let onEditingChanged: (Bool, _ shouldCreateNewTask: Bool) -> Void
+        let onEditingChanged: (Bool, _ shouldCreateNewItem: Bool) -> Void
         let onContentChange: ((String) -> Void)?
         var returnKeyPressed: Bool = false
         weak var textView: UITextView?
@@ -117,7 +117,7 @@ struct TappableTextField: UIViewRepresentable {
 
         init(
             text: Binding<String>,
-            onEditingChanged: @escaping (Bool, _ shouldCreateNewTask: Bool) -> Void,
+            onEditingChanged: @escaping (Bool, _ shouldCreateNewItem: Bool) -> Void,
             onContentChange: ((String) -> Void)? = nil
         ) {
             _text = text
@@ -173,19 +173,19 @@ struct TappableTextField: UIViewRepresentable {
             replacementText text: String
         ) -> Bool {
             guard text == "\n" else { return true }
-            // Intercept Return: trigger new-task creation without inserting a newline.
+            // Intercept Return: trigger new-item creation without inserting a newline.
             returnKeyPressed = true
             onEditingChanged(false, true)
             if textView.returnKeyType == .done {
-                // Non-last task (or empty title): resign immediately so SwiftUI's
+                // Non-last item (or empty title): resign immediately so SwiftUI's
                 // focus binding update reliably clears the field on iPad, where the
                 // deferred focusedFieldBinding = .scrollView alone doesn't resign
                 // the UITextView through the hardware-keyboard focus system.
                 textView.resignFirstResponder()
             }
-            // Return false: for .next (last active task with text), the text view
+            // Return false: for .next (last active item with text), the text view
             // stays first responder so SwiftUI can transfer focus atomically to the
-            // newly created task's text field in the same render pass.
+            // newly created item's text field in the same render pass.
             return false
         }
     }

diff --git a/ListlessiOS/ListlessiOSApp.swift b/ListlessiOS/ListlessiOSApp.swift

@@ -12,7 +12,7 @@ class IOSAppDelegate: UIResponder, UIApplicationDelegate {
         // File menu — New Item (⌘N)
         let newItem = UIKeyCommand(
             title: "New Item",
-            action: IOSMenuSelectors.newTask,
+            action: IOSMenuSelectors.newItem,
             input: "n",
             modifierFlags: .command
         )
@@ -37,7 +37,7 @@ class IOSAppDelegate: UIResponder, UIApplicationDelegate {
         )
         let delete = UIKeyCommand(
             title: "Delete",
-            action: IOSMenuSelectors.deleteTask,
+            action: IOSMenuSelectors.deleteItem,
             input: "\u{8}",
             modifierFlags: .command
         )
@@ -73,8 +73,8 @@ struct ListlessiOSApp: App {
 
     var body: some Scene {
         WindowGroup {
-            TaskListView(
-                store: TaskStore(persistenceController: persistenceController),
+            ItemListView(
+                store: ItemStore(persistenceController: persistenceController),
                 syncMonitor: persistenceController.syncMonitor
             )
                 .safeAreaInset(edge: .top) {

diff --git a/ListlessiOS/Views/DraftRowView.swift b/ListlessiOS/Views/DraftRowView.swift

@@ -11,7 +11,7 @@ struct DraftRowView: View {
     var focusedField: FocusState<FocusField?>.Binding
 
     var body: some View {
-        HStack(alignment: .center, spacing: TaskRowMetrics.contentSpacing) {
+        HStack(alignment: .center, spacing: ItemRowMetrics.contentSpacing) {
             Image(systemName: "circle")
                 .frame(width: 22, height: 22)
                 .foregroundStyle(Color.secondary)
@@ -25,17 +25,17 @@ struct DraftRowView: View {
                 returnKeyType: returnKeyType,
                 uiAccessibilityIdentifier: accessibilityIdentifier
             )
-            .focused(focusedField, equals: .task(draftID))
+            .focused(focusedField, equals: .item(draftID))
             .frame(maxWidth: .infinity, alignment: .leading)
         }
-        .padding(.vertical, TaskRowMetrics.contentVerticalPadding)
-        .padding(.trailing, TaskRowMetrics.contentHorizontalPadding)
-        .padding(.leading, TaskRowMetrics.activeLeadingPadding)
+        .padding(.vertical, ItemRowMetrics.contentVerticalPadding)
+        .padding(.trailing, ItemRowMetrics.contentHorizontalPadding)
+        .padding(.leading, ItemRowMetrics.activeLeadingPadding)
         .frame(maxWidth: .infinity, alignment: .leading)
         .contentShape(Rectangle())
         .background {
-            Color.taskCard.overlay(accentColor.opacity(0.15))
+            Color.itemCard.overlay(accentColor.opacity(0.15))
         }
-        .taskCard(accentColor: accentColor, isSelected: isSelected)
+        .itemCard(accentColor: accentColor, isSelected: isSelected)
     }
 }

diff --git a/ListlessiOS/Views/TaskListView.swift b/ListlessiOS/Views/ItemListView.swift

@@ -1,7 +1,7 @@
 import SwiftUI
 import UIKit
 
-struct TaskListView: View, TaskListViewProtocol {
+struct ItemListView: View, ItemListViewProtocol {
     class LayoutStorage {
         var draggedRowWidth: CGFloat = 0
         var draggedRowFrame: CGRect = .zero
@@ -13,10 +13,10 @@ struct TaskListView: View, TaskListViewProtocol {
         var draftCount: Int = 0
         var isShowingSyncDiagnostics = false
         var isShowingSettings = false
-        var clearingTaskIDs: Set<UUID> = []
+        var clearingItemIDs: Set<UUID> = []
         var undoToast: UndoToastData? = nil
         var isSwiping: Bool = false
-        var draftPlacement: DraftTaskPlacement?
+        var draftPlacement: DraftItemPlacement?
         var draftTitle: String = ""
         var fetchWorkaround: Int = 0
     }
@@ -36,13 +36,13 @@ struct TaskListView: View, TaskListViewProtocol {
     @Environment(\.undoManager) var undoManager
     @Environment(\.managedObjectContext) var managedObjectContext
 
-    let store: TaskStore
+    let store: ItemStore
     @ObservedObject var syncMonitor: CloudKitSyncMonitor
     @FetchRequest(
         sortDescriptors: [],
         animation: .default
     )
-    var tasks: FetchedResults<TaskItem>
+    var items: FetchedResults<ItemEntity>
     @FocusState private var focusedFieldBinding: FocusField?
     @State var fState = FocusStateData()
     @State var iState = InteractionStateData()
@@ -63,7 +63,7 @@ struct TaskListView: View, TaskListViewProtocol {
         nonmutating set { iState.dragState = newValue }
     }
 
-    var draftPlacement: DraftTaskPlacement? {
+    var draftPlacement: DraftItemPlacement? {
         get { iState.draftPlacement }
         nonmutating set {
             if newValue != nil, iState.draftPlacement == nil {
@@ -119,8 +119,8 @@ struct TaskListView: View, TaskListViewProtocol {
     }
 
     private var selectedIndex: Int? {
-        guard let currentID = fState.selectedTaskID else { return nil }
-        return activeTasks.firstIndex(where: { $0.id == currentID })
+        guard let currentID = fState.selectedItemID else { return nil }
+        return activeItems.firstIndex(where: { $0.id == currentID })
     }
 
     private var canMoveSelectionUp: Bool {
@@ -132,40 +132,40 @@ struct TaskListView: View, TaskListViewProtocol {
     private var canMoveSelectionDown: Bool {
         guard focusedField == .scrollView else { return false }
         guard let index = selectedIndex else { return false }
-        return index < activeTasks.count - 1
+        return index < activeItems.count - 1
     }
 
     private struct MenuState: Equatable {
-        let selectedTaskID: UUID?
+        let selectedItemID: UUID?
         let isScrollViewFocused: Bool
-        let activeTaskCount: Int
-        let completedTaskCount: Int
+        let activeItemCount: Int
+        let completedItemCount: Int
         let selectedIndex: Int?
     }
 
     private var menuCoordinatorTrigger: MenuState {
         MenuState(
-            selectedTaskID: fState.selectedTaskID,
+            selectedItemID: fState.selectedItemID,
             isScrollViewFocused: focusedField == .scrollView,
-            activeTaskCount: activeTasks.count,
-            completedTaskCount: completedTasks.count,
+            activeItemCount: activeItems.count,
+            completedItemCount: completedItems.count,
             selectedIndex: selectedIndex
         )
     }
 
     func updateMenuCoordinator() {
         let coord = IOSMenuCoordinator.shared
-        coord.newTask = { createNewTask() }
-        coord.deleteTask = { _ = deleteSelectedTaskWithUndo() }
-        coord.moveUp = { moveSelectedTaskUp() }
-        coord.moveDown = { moveSelectedTaskDown() }
-        coord.markCompleted = { markSelectedTaskCompleted() }
+        coord.newItem = { createNewItem() }
+        coord.deleteItem = { _ = deleteSelectedItemWithUndo() }
+        coord.moveUp = { moveSelectedItemUp() }
+        coord.moveDown = { moveSelectedItemDown() }
+        coord.markCompleted = { markSelectedItemCompleted() }
         let inNavMode = focusedField == .scrollView
-        coord.canDelete = fState.selectedTaskID != nil && inNavMode
+        coord.canDelete = fState.selectedItemID != nil && inNavMode
         coord.canMoveUp = canMoveSelectionUp
         coord.canMoveDown = canMoveSelectionDown
-        coord.canMarkCompleted = fState.selectedTaskID != nil && inNavMode
-        coord.markCompletedTitle = completedTasks.contains(where: { $0.id == fState.selectedTaskID })
+        coord.canMarkCompleted = fState.selectedItemID != nil && inNavMode
+        coord.markCompletedTitle = completedItems.contains(where: { $0.id == fState.selectedItemID })
             ? "Mark as Incomplete" : "Mark as Complete"
     }
 
@@ -173,21 +173,21 @@ struct TaskListView: View, TaskListViewProtocol {
     var rowGap: CGFloat { 12 }
     var pullCreateThreshold: CGFloat { 70 }
     var flickThreshold: CGFloat { 500 }
-    var isCompletelyEmpty: Bool { activeTasks.isEmpty && completedTasks.isEmpty }
+    var isCompletelyEmpty: Bool { activeItems.isEmpty && completedItems.isEmpty }
 
-    init(store: TaskStore, syncMonitor: CloudKitSyncMonitor) {
+    init(store: ItemStore, syncMonitor: CloudKitSyncMonitor) {
         self.store = store
         self.syncMonitor = syncMonitor
     }
 
-    func clearDraftTaskUI(at placement: DraftTaskPlacement, hasTitle: Bool) {
+    func clearDraftItemUI(at placement: DraftItemPlacement, hasTitle: Bool) {
         let clear: () -> Void = {
             if draftPlacement == placement {
                 draftPlacement = nil
             }
             draftTitle = ""
-            if fState.selectedTaskID == draftID(for: placement) {
-                fState.selectedTaskID = nil
+            if fState.selectedItemID == draftID(for: placement) {
+                fState.selectedItemID = nil
             }
 
             guard placement == .prepend else { return }
@@ -264,22 +264,22 @@ struct TaskListView: View, TaskListViewProtocol {
         .opacity(opacity)
     }
 
-    /// The draft row content styled to match a task row. Controlled by the
+    /// The draft row content styled to match a item row. Controlled by the
     /// ZStack in ``pullToCreateIndicatorRow`` rather than its own visibility.
     @ViewBuilder private var draftPrependRow: some View {
         DraftRowView(
-            accentColor: taskColor(
-                forIndex: 0, total: max(1, displayActiveTasks.count + 1), theme: colorTheme
+            accentColor: itemColor(
+                forIndex: 0, total: max(1, displayActiveItems.count + 1), theme: colorTheme
             ),
-            isSelected: fState.selectedTaskID == draftPrependRowID,
+            isSelected: fState.selectedItemID == draftPrependRowID,
             draftID: draftPrependRowID,
             title: draftTitleBinding,
             onEditingChanged: { editing, _ in
                 DispatchQueue.main.async {
                     if editing {
-                        beginDraftTaskEditing(.prepend)
+                        beginDraftItemEditing(.prepend)
                     } else {
-                        commitDraftTask()
+                        commitDraftItem()
                     }
                 }
             },
@@ -292,21 +292,21 @@ struct TaskListView: View, TaskListViewProtocol {
     @ViewBuilder private var draftAppendRow: some View {
         if isAppendDraftVisible {
             DraftRowView(
-                accentColor: taskColor(
-                    forIndex: displayActiveTasks.count,
-                    total: max(1, displayActiveTasks.count + 1),
+                accentColor: itemColor(
+                    forIndex: displayActiveItems.count,
+                    total: max(1, displayActiveItems.count + 1),
                     theme: colorTheme
                 ),
-                isSelected: fState.selectedTaskID == draftAppendRowID,
+                isSelected: fState.selectedItemID == draftAppendRowID,
                 draftID: draftAppendRowID,
                 title: draftTitleBinding,
-                onEditingChanged: { editing, shouldCreateNewTask in
+                onEditingChanged: { editing, shouldCreateNewItem in
                     DispatchQueue.main.async {
                         if editing {
-                            beginDraftTaskEditing(.append)
+                            beginDraftItemEditing(.append)
                         } else {
-                            commitDraftTask(
-                                shouldCreateNewTask: shouldCreateNewTask
+                            commitDraftItem(
+                                shouldCreateNewItem: shouldCreateNewItem
                             )
                         }
                     }
@@ -322,53 +322,53 @@ struct TaskListView: View, TaskListViewProtocol {
         }
     }
 
-    @ViewBuilder private var taskRows: some View {
+    @ViewBuilder private var itemRows: some View {
         let _ = iState.fetchWorkaround
         let draftOffset = isPrependDraftVisible ? 1 : 0
         let draftTotal = draftPlacement != nil ? 1 : 0
-        ForEach(Array(displayActiveTasks.enumerated()), id: \.element.id) { index, task in
-            let taskID = task.id
-            TaskRowView(
-                task: task,
-                taskID: taskID,
+        ForEach(Array(displayActiveItems.enumerated()), id: \.element.id) { index, item in
+            let itemID = item.id
+            ItemRowView(
+                item: item,
+                itemID: itemID,
                 index: index + draftOffset,
-                totalTasks: displayActiveTasks.count + draftTotal,
-                isSelected: fState.selectedTaskID == taskID,
+                totalItems: displayActiveItems.count + draftTotal,
+                isSelected: fState.selectedItemID == itemID,
                 isDragging: isDraggingStateBinding,
                 isSwiping: $iState.isSwiping,
-                isLastActiveTask: index == displayActiveTasks.count - 1,
+                isLastActiveItem: index == displayActiveItems.count - 1,
                 focusedField: $focusedFieldBinding,
                 onToggle: { toggleCompletion($0); withAnimation { iState.fetchWorkaround &+= 1 } },
                 onTitleChange: { updateTitle($0, $1) },
-                onDelete: { deleteTaskWithUndo($0) },
-                onSelect: { selectTask($0) },
+                onDelete: { deleteItemWithUndo($0) },
+                onSelect: { selectItem($0) },
                 onStartEdit: { startEditing($0) },
                 onEndEdit: {
-                    if fState.selectedTaskID == $0 {
-                        fState.selectedTaskID = nil
+                    if fState.selectedItemID == $0 {
+                        fState.selectedItemID = nil
                     }
-                    endEditing($0, shouldCreateNewTask: $1)
+                    endEditing($0, shouldCreateNewItem: $1)
                 }
             )
-            .scaleEffect(draggedTaskID == taskID ? dragScaleEffect() : 1.0)
+            .scaleEffect(draggedItemID == itemID ? dragScaleEffect() : 1.0)
             .shadow(
-                color: draggedTaskID == taskID ? .black.opacity(0.3) : .clear,
+                color: draggedItemID == itemID ? .black.opacity(0.3) : .clear,
                 radius: 12, y: 4
             )
-            .taskDragGesture(
-                isActive: !task.isCompleted && focusedFieldBinding != .task(taskID),
-                taskID: taskID,
+            .itemDragGesture(
+                isActive: !item.isCompleted && focusedFieldBinding != .item(itemID),
+                itemID: itemID,
                 onDragStart: { width in
                     layoutStorage.draggedRowWidth = width
-                    startDrag(taskID: taskID)
+                    startDrag(itemID: itemID)
                 },
                 onDragChanged: { point in
-                    handleIOSDragChanged(taskID: taskID, point: point)
+                    handleIOSDragChanged(itemID: itemID, point: point)
                 },
                 onDragEnded: { commitIOSDrag() }
             )
             .background {
-                if draggedTaskID == taskID {
+                if draggedItemID == itemID {
                     Color.clear
                         .onGeometryChange(for: CGRect.self) { proxy in
                             proxy.frame(in: .global)
@@ -378,35 +378,35 @@ struct TaskListView: View, TaskListViewProtocol {
                 }
             }
             .padding(.bottom, rowGap)
-            .zIndex(draggedTaskID == taskID ? 2 : 1)
-            .id(taskID)
+            .zIndex(draggedItemID == itemID ? 2 : 1)
+            .id(itemID)
         }
 
         draftAppendRow
 
-        ForEach(completedTasks) { task in
-            let taskID = task.id
-            let isBeingCleared = iState.clearingTaskIDs.contains(taskID)
-            TaskRowView(
-                task: task,
-                taskID: taskID,
-                isSelected: fState.selectedTaskID == taskID,
+        ForEach(completedItems) { item in
+            let itemID = item.id
+            let isBeingCleared = iState.clearingItemIDs.contains(itemID)
+            ItemRowView(
+                item: item,
+                itemID: itemID,
+                isSelected: fState.selectedItemID == itemID,
                 isSwiping: $iState.isSwiping,
                 focusedField: $focusedFieldBinding,
                 onToggle: { toggleCompletion($0); withAnimation { iState.fetchWorkaround &+= 1 } },
                 onTitleChange: { updateTitle($0, $1) },
-                onDelete: { deleteTaskWithUndo($0) },
-                onSelect: { selectTask($0) }
+                onDelete: { deleteItemWithUndo($0) },
+                onSelect: { selectItem($0) }
             )
             .opacity(isBeingCleared ? 0 : 1)
             .offset(y: isBeingCleared ? 40 : 0)
             .padding(.bottom, rowGap)
-            .id(taskID)
+            .id(itemID)
         }
     }
 
     var body: some View {
-        taskScrollView
+        itemScrollView
             .overlay(alignment: .topLeading) {
                 if showFPSOverlay {
                     FPSOverlay()
@@ -421,17 +421,17 @@ struct TaskListView: View, TaskListViewProtocol {
                     handleBackgroundTap()
                 }
             )
-            .accessibilityIdentifier("task-list-scrollview")
+            .accessibilityIdentifier("item-list-scrollview")
             .background {
-                let isEditing = if case .task = focusedFieldBinding { true } else { false }
+                let isEditing = if case .item = focusedFieldBinding { true } else { false }
                 let isShowingSheet = iState.isShowingSettings || iState.isShowingSyncDiagnostics
                 KeyCommandBridge(
                     isActive: !isEditing && !isShowingSheet,
                     onUp: { _ = navigateUp() },
                     onDown: { _ = navigateDown() },
-                    onSpace: { _ = toggleSelectedTask() },
-                    onReturn: { _ = focusSelectedTask() },
-                    onDelete: { _ = deleteSelectedTaskWithUndo() }
+                    onSpace: { _ = toggleSelectedItem() },
+                    onReturn: { _ = focusSelectedItem() },
+                    onDelete: { _ = deleteSelectedItemWithUndo() }
                 )
             }
             .onAppear {
@@ -483,7 +483,7 @@ struct TaskListView: View, TaskListViewProtocol {
             }
     }
 
-    private var taskScrollView: some View {
+    private var itemScrollView: some View {
         ZStack(alignment: .top) {
             ScrollView {
               ScrollViewReader { scrollProxy in
@@ -495,7 +495,7 @@ struct TaskListView: View, TaskListViewProtocol {
                         draftPrependRow
                             .padding(.bottom, rowGap)
                     }
-                    taskRows
+                    itemRows
                 }
                 .frame(maxWidth: .infinity, alignment: .topLeading)
                 .onGeometryChange(for: CGFloat.self) {
@@ -523,8 +523,8 @@ struct TaskListView: View, TaskListViewProtocol {
                         }
                     }
 
-                    if case .task(let id) = (newValue ?? fState.focusedField),
-                        draggedTaskID == nil,
+                    if case .item(let id) = (newValue ?? fState.focusedField),
+                        draggedItemID == nil,
                         id != draftPrependRowID
                     {
                         withAnimation {
@@ -532,8 +532,8 @@ struct TaskListView: View, TaskListViewProtocol {
                         }
                     }
                 }
-                .onChange(of: fState.selectedTaskID) { _, newID in
-                    if let newID, draggedTaskID == nil {
+                .onChange(of: fState.selectedItemID) { _, newID in
+                    if let newID, draggedItemID == nil {
                         guard newID != draftPrependRowID else { return }
                         withAnimation {
                             scrollProxy.scrollTo(newID)
@@ -542,7 +542,7 @@ struct TaskListView: View, TaskListViewProtocol {
                 }
               }
             }
-            .scrollDisabled(draggedTaskID != nil || iState.isSwiping)
+            .scrollDisabled(draggedItemID != nil || iState.isSwiping)
             .scrollBounceBehavior(.always)
             .contentMargins(.bottom, 20)
             .background {
@@ -551,7 +551,7 @@ struct TaskListView: View, TaskListViewProtocol {
             .overlay {
                 if isCompletelyEmpty && draftPlacement == nil {
                     Text("Pull down to create")
-                        .font(TaskRowMetrics.hintSUI)
+                        .font(ItemRowMetrics.hintSUI)
                         .foregroundStyle(.secondary)
                         .padding(.top, 24)
                         .allowsHitTesting(false)
@@ -564,18 +564,18 @@ struct TaskListView: View, TaskListViewProtocol {
                 pullToCreate: pullToCreateStateBinding,
                 pullUpOffset: pullUpOffsetStateBinding,
                 isDraftOpen: draftPlacement != nil,
-                hasCompletedTasks: !completedTasks.isEmpty,
+                hasCompletedItems: !completedItems.isEmpty,
                 pullCreateThreshold: pullCreateThreshold,
                 flickThreshold: flickThreshold,
                 pullClearThreshold: pullClearThreshold,
-                onCreateTaskAtTop: { revealPhantomRow() },
+                onCreateItemAtTop: { revealPhantomRow() },
                 onClearCompleted: {
-                    let ids = Set(completedTasks.map(\.id))
+                    let ids = Set(completedItems.map(\.id))
                     withAnimation(.easeIn(duration: 0.35)) {
-                        iState.clearingTaskIDs = ids
+                        iState.clearingItemIDs = ids
                     } completion: {
-                        iState.clearingTaskIDs = []
-                        clearCompletedTasksWithUndo()
+                        iState.clearingItemIDs = []
+                        clearCompletedItemsWithUndo()
                     }
                 }
             )

diff --git a/ListlessiOS/Views/TaskRowView.swift b/ListlessiOS/Views/ItemRowView.swift

@@ -1,26 +1,26 @@
 import SwiftUI
 
-struct TaskRowView: View {
-    let task: TaskItem
-    let taskID: UUID
+struct ItemRowView: View {
+    let item: ItemEntity
+    let itemID: UUID
     let index: Int
-    let totalTasks: Int
+    let totalItems: Int
     let isSelected: Bool
     @Binding var isDragging: Bool
     @Binding var isSwiping: Bool
-    let onToggle: (TaskItem) -> Void
-    let onTitleChange: (TaskItem, String) -> Void
-    let onDelete: (TaskItem) -> Void
+    let onToggle: (ItemEntity) -> Void
+    let onTitleChange: (ItemEntity, String) -> Void
+    let onDelete: (ItemEntity) -> Void
     let onSelect: (UUID) -> Void
-    let isLastActiveTask: Bool
+    let isLastActiveItem: Bool
     let onStartEdit: (UUID) -> Void
-    let onEndEdit: (UUID, _ shouldCreateNewTask: Bool) -> Void
+    let onEndEdit: (UUID, _ shouldCreateNewItem: Bool) -> Void
     @FocusState.Binding var focusedField: FocusField?
 
     @AppStorage("colorTheme") private var colorThemeRaw = 0
     private var colorTheme: ColorTheme { ColorTheme(rawValue: colorThemeRaw) ?? .pilbara }
     @State private var swipeOffset: CGFloat = 0
-    @State private var swipeDirection: TaskRowSwipeGesture.SwipeDirection = .none
+    @State private var swipeDirection: ItemRowSwipeGesture.SwipeDirection = .none
     @State private var isSwipeTriggered: Bool = false
     @State private var editingTitle: String = ""
     @State private var isCurrentlyEditing: Bool = false
@@ -28,30 +28,30 @@ struct TaskRowView: View {
     @State private var cachedAccentColor: Color = .clear
 
     init(
-        task: TaskItem,
-        taskID: UUID,
+        item: ItemEntity,
+        itemID: UUID,
         index: Int = 0,
-        totalTasks: Int = 1,
+        totalItems: Int = 1,
         isSelected: Bool,
         isDragging: Binding<Bool> = .constant(false),
         isSwiping: Binding<Bool> = .constant(false),
-        isLastActiveTask: Bool = false,
+        isLastActiveItem: Bool = false,
         focusedField: FocusState<FocusField?>.Binding,
-        onToggle: @escaping (TaskItem) -> Void,
-        onTitleChange: @escaping (TaskItem, String) -> Void,
-        onDelete: @escaping (TaskItem) -> Void,
+        onToggle: @escaping (ItemEntity) -> Void,
+        onTitleChange: @escaping (ItemEntity, String) -> Void,
+        onDelete: @escaping (ItemEntity) -> Void,
         onSelect: @escaping (UUID) -> Void,
         onStartEdit: @escaping (UUID) -> Void = { _ in },
-        onEndEdit: @escaping (UUID, _ shouldCreateNewTask: Bool) -> Void = { _, _ in }
+        onEndEdit: @escaping (UUID, _ shouldCreateNewItem: Bool) -> Void = { _, _ in }
     ) {
-        self.task = task
-        self.taskID = taskID
+        self.item = item
+        self.itemID = itemID
         self.index = index
-        self.totalTasks = totalTasks
+        self.totalItems = totalItems
         self.isSelected = isSelected
         _isDragging = isDragging
         _isSwiping = isSwiping
-        self.isLastActiveTask = isLastActiveTask
+        self.isLastActiveItem = isLastActiveItem
         self.onToggle = onToggle
         self.onTitleChange = onTitleChange
         self.onDelete = onDelete
@@ -62,14 +62,14 @@ struct TaskRowView: View {
     }
 
     var body: some View {
-        HStack(alignment: .center, spacing: TaskRowMetrics.contentSpacing) {
+        HStack(alignment: .center, spacing: ItemRowMetrics.contentSpacing) {
             Button {
-                onToggle(task)
+                onToggle(item)
             } label: {
                 // When a right-swipe is past the threshold, preview the toggled state
                 let previewCompleted = isSwipeTriggered && swipeDirection == .right
-                    ? !task.isCompleted
-                    : task.isCompleted
+                    ? !item.isCompleted
+                    : item.isCompleted
                 Image(systemName: previewCompleted ? "checkmark.circle.fill" : "circle")
                     .contentTransition(.identity)
                     .frame(width: 22, height: 22)
@@ -77,53 +77,53 @@ struct TaskRowView: View {
                     .font(.system(size: 17))
             }
             .buttonStyle(.borderless)
-            .accessibilityIdentifier("task-checkbox")
-            .accessibilityValue(task.isCompleted ? "checkmark.circle.fill" : "circle")
+            .accessibilityIdentifier("item-checkbox")
+            .accessibilityValue(item.isCompleted ? "checkmark.circle.fill" : "circle")
 
-            if !task.isCompleted && (isSelected || isEditing) {
+            if !item.isCompleted && (isSelected || isEditing) {
                 TappableTextField(
                     text: $editingTitle,
-                    isCompleted: task.isCompleted,
+                    isCompleted: item.isCompleted,
                     isDragging: isDragging,
-                    onEditingChanged: { editing, shouldCreateNewTask in
+                    onEditingChanged: { editing, shouldCreateNewItem in
                         DispatchQueue.main.async {
                             isCurrentlyEditing = editing
-                            if editing { onStartEdit(taskID) }
+                            if editing { onStartEdit(itemID) }
                             else {
                                 tapPoint = nil
-                                onEndEdit(taskID, shouldCreateNewTask)
+                                onEndEdit(itemID, shouldCreateNewItem)
                             }
                         }
                     },
-                    returnKeyType: isLastActiveTask && !editingTitle.isEmpty ? .next : .done,
+                    returnKeyType: isLastActiveItem && !editingTitle.isEmpty ? .next : .done,
                     onContentChange: { newTitle in
-                        guard !task.isCompleted else { return }
-                        onTitleChange(task, newTitle)
+                        guard !item.isCompleted else { return }
+                        onTitleChange(item, newTitle)
                     },
-                    uiAccessibilityIdentifier: "task-text-\(taskID.uuidString)",
+                    uiAccessibilityIdentifier: "item-text-\(itemID.uuidString)",
                     initialCursorPoint: tapPoint
                 )
-                .focused($focusedField, equals: .task(taskID))
+                .focused($focusedField, equals: .item(itemID))
                 .frame(maxWidth: .infinity, alignment: .leading)
-            } else if !task.isCompleted {
-                taskProxy
+            } else if !item.isCompleted {
+                itemProxy
                     .frame(maxWidth: .infinity, alignment: .leading)
                     .contentShape(Rectangle())
                     .gesture(SpatialTapGesture().onEnded { value in
                         tapPoint = value.location
-                        onSelect(taskID)
-                        focusedField = .task(taskID)
+                        onSelect(itemID)
+                        focusedField = .item(itemID)
                     })
             } else {
-                taskProxy
+                itemProxy
                     .frame(maxWidth: .infinity, alignment: .leading)
             }
         }
-        .padding(.vertical, TaskRowMetrics.contentVerticalPadding)
-        .padding(.trailing, TaskRowMetrics.contentHorizontalPadding)
+        .padding(.vertical, ItemRowMetrics.contentVerticalPadding)
+        .padding(.trailing, ItemRowMetrics.contentHorizontalPadding)
         .padding(
             .leading,
-            task.isCompleted ? TaskRowMetrics.completedLeadingPadding : TaskRowMetrics.activeLeadingPadding
+            item.isCompleted ? ItemRowMetrics.completedLeadingPadding : ItemRowMetrics.activeLeadingPadding
         )
         .frame(maxWidth: .infinity, alignment: .leading)
         .contentShape(Rectangle())
@@ -132,34 +132,34 @@ struct TaskRowView: View {
             // gesture for its own hit area, so circle button taps don't also fire here.
             // If tapping a completed row while another row is being edited, preserve
             // the current focus/selection.
-            if task.isCompleted,
+            if item.isCompleted,
                let field = focusedField,
-               case .task(let id) = field,
-               id != taskID
+               case .item(let id) = field,
+               id != itemID
             {
                 return
             }
-            if task.isCompleted {
-                withAnimation { onToggle(task) }
+            if item.isCompleted {
+                withAnimation { onToggle(item) }
             } else {
                 tapPoint = nil
-                onSelect(taskID)
-                focusedField = .task(taskID)
+                onSelect(itemID)
+                focusedField = .item(itemID)
             }
         }
         .background(cardBackground)
         .overlay(alignment: .leading) {
-            if !task.isCompleted {
+            if !item.isCompleted {
                 Rectangle()
                     .fill(cachedAccentColor)
-                    .frame(width: TaskRowMetrics.accentBarWidth)
+                    .frame(width: ItemRowMetrics.accentBarWidth)
             }
         }
         .onAppear {
-            editingTitle = task.title
+            editingTitle = item.title
             cachedAccentColor = computeAccentColor()
         }
-        .onChange(of: task.title) { _, newValue in
+        .onChange(of: item.title) { _, newValue in
             if !isCurrentlyEditing {
                 editingTitle = newValue
             }
@@ -167,22 +167,22 @@ struct TaskRowView: View {
         .onChange(of: index) { _, _ in
             cachedAccentColor = computeAccentColor()
         }
-        .onChange(of: totalTasks) { _, _ in
+        .onChange(of: totalItems) { _, _ in
             cachedAccentColor = computeAccentColor()
         }
         .onChange(of: colorThemeRaw) { _, _ in
             cachedAccentColor = computeAccentColor()
         }
-        .taskSwipeGesture(
+        .itemSwipeGesture(
             isDragging: $isDragging,
-            isEditing: focusedField == .task(taskID),
+            isEditing: focusedField == .item(itemID),
             isSwiping: $isSwiping,
             swipeOffset: $swipeOffset,
             swipeDirection: $swipeDirection,
             isTriggered: $isSwipeTriggered,
             completeColor: cachedAccentColor,
-            onComplete: { onToggle(task) },
-            onDelete: { onDelete(task) }
+            onComplete: { onToggle(item) },
+            onDelete: { onDelete(item) }
         )
         .onChange(of: isDragging) { _, newValue in
             if newValue {
@@ -191,49 +191,49 @@ struct TaskRowView: View {
                 isSwipeTriggered = false
             }
         }
-        .clipShape(TaskCardModifier.shape)
+        .clipShape(ItemCardModifier.shape)
         .overlay(
-            isSelected && !task.isCompleted
-                ? TaskCardModifier.shape
+            isSelected && !item.isCompleted
+                ? ItemCardModifier.shape
                     .strokeBorder(cachedAccentColor.opacity(0.40), lineWidth: 2)
                 : nil
         )
     }
 
     private var isEditing: Bool {
-        focusedField == .task(taskID)
+        focusedField == .item(itemID)
     }
 
     @ViewBuilder
-    private var taskProxy: some View {
-        if task.isCompleted {
+    private var itemProxy: some View {
+        if item.isCompleted {
             Text(editingTitle)
-                .font(TaskRowMetrics.bodySUI)
+                .font(ItemRowMetrics.bodySUI)
                 .foregroundStyle(.secondary)
                 .strikethrough(true, color: .secondary)
-                .accessibilityIdentifier("task-text-\(taskID.uuidString)")
+                .accessibilityIdentifier("item-text-\(itemID.uuidString)")
         } else {
             Text(editingTitle)
-                .font(TaskRowMetrics.bodySUI)
+                .font(ItemRowMetrics.bodySUI)
                 .foregroundStyle(.primary)
-                .accessibilityIdentifier("task-text-\(taskID.uuidString)")
+                .accessibilityIdentifier("item-text-\(itemID.uuidString)")
         }
     }
 
     @MainActor
     private func computeAccentColor() -> Color {
-        guard !task.isCompleted else { return .clear }
-        return cachedTaskColor(forIndex: index, total: totalTasks, theme: colorTheme)
+        guard !item.isCompleted else { return .clear }
+        return cachedItemColor(forIndex: index, total: totalItems, theme: colorTheme)
     }
 
     @ViewBuilder
     private var cardBackground: some View {
-        if task.isCompleted {
+        if item.isCompleted {
             isSelected ? Color.completedSelected : Color.clear
         } else if isSelected {
-            Color.taskCard.overlay(cachedAccentColor.opacity(0.15))
+            Color.itemCard.overlay(cachedAccentColor.opacity(0.15))
         } else {
-            Color.taskCard
+            Color.itemCard
         }
     }
 }

diff --git a/ListlessiOS/Views/PullToClear.swift b/ListlessiOS/Views/PullToClear.swift

@@ -1,6 +1,6 @@
 import SwiftUI
 
-/// Pull distance at which the indicator signals readiness and completed task clearing triggers.
+/// Pull distance at which the indicator signals readiness and completed item clearing triggers.
 let pullClearThreshold: CGFloat = 50
 
 struct PullToClearIndicator: View {
@@ -31,7 +31,7 @@ struct PullToClearIndicator: View {
                     .offset(y: isReady ? -textSlideDistance : 0)
             }
             .foregroundStyle(.secondary)
-            .font(TaskRowMetrics.hintSUI)
+            .font(ItemRowMetrics.hintSUI)
             .frame(height: textSlideDistance, alignment: .topLeading)
             .clipped()
             .animation(.easeInOut(duration: 0.15), value: isReady)

diff --git a/ListlessiOS/Views/PullToCreate.swift b/ListlessiOS/Views/PullToCreate.swift

@@ -12,7 +12,7 @@ struct PullToCreateIndicator: View {
     private let textSlideDistance: CGFloat = 22
 
     var body: some View {
-        HStack(alignment: .center, spacing: TaskRowMetrics.contentSpacing) {
+        HStack(alignment: .center, spacing: ItemRowMetrics.contentSpacing) {
             Image(systemName: "circle")
                 .frame(width: 22, height: 22)
                 .foregroundStyle(Color.secondary)
@@ -24,29 +24,29 @@ struct PullToCreateIndicator: View {
                     .offset(y: isReady ? textSlideDistance : 0)
             }
             .foregroundStyle(.secondary)
-            .font(TaskRowMetrics.bodySUI)
+            .font(ItemRowMetrics.bodySUI)
             .frame(height: textSlideDistance, alignment: .topLeading)
             .clipped()
             .animation(.easeInOut(duration: 0.18), value: isReady)
             Spacer()
         }
-        .padding(.vertical, TaskRowMetrics.contentVerticalPadding)
-        .padding(.trailing, TaskRowMetrics.contentHorizontalPadding)
-        .padding(.leading, TaskRowMetrics.activeLeadingPadding)
+        .padding(.vertical, ItemRowMetrics.contentVerticalPadding)
+        .padding(.trailing, ItemRowMetrics.contentHorizontalPadding)
+        .padding(.leading, ItemRowMetrics.activeLeadingPadding)
         .frame(maxWidth: .infinity, alignment: .leading)
         .contentShape(Rectangle())
-        .background(Color.taskCard)
+        .background(Color.itemCard)
         .clipShape(
             UnevenRoundedRectangle(
                 topLeadingRadius: 0, bottomLeadingRadius: 0,
-                bottomTrailingRadius: TaskRowMetrics.trailingCornerRadius,
-                topTrailingRadius: TaskRowMetrics.trailingCornerRadius
+                bottomTrailingRadius: ItemRowMetrics.trailingCornerRadius,
+                topTrailingRadius: ItemRowMetrics.trailingCornerRadius
             )
         )
         .overlay(alignment: .leading) {
             Rectangle()
-                .fill(taskColor(forIndex: 0, total: 1, theme: colorTheme))
-                .frame(width: TaskRowMetrics.accentBarWidth)
+                .fill(itemColor(forIndex: 0, total: 1, theme: colorTheme))
+                .frame(width: ItemRowMetrics.accentBarWidth)
         }
         .frame(height: Self.indicatorHeight, alignment: .top)
         .allowsHitTesting(false)

diff --git a/ListlessiOS/Views/SettingsView.swift b/ListlessiOS/Views/SettingsView.swift

@@ -32,9 +32,9 @@ struct SettingsView: View {
                                 Spacer()
                                 LinearGradient(
                                     colors: [
-                                        taskColor(forIndex: 0, total: 5, theme: theme),
-                                        taskColor(forIndex: 2, total: 5, theme: theme),
-                                        taskColor(forIndex: 4, total: 5, theme: theme),
+                                        itemColor(forIndex: 0, total: 5, theme: theme),
+                                        itemColor(forIndex: 2, total: 5, theme: theme),
+                                        itemColor(forIndex: 4, total: 5, theme: theme),
                                     ],
                                     startPoint: .leading,
                                     endPoint: .trailing

diff --git a/Tests/Support/TestHelpers.swift b/Tests/Support/TestHelpers.swift

@@ -7,31 +7,31 @@ import Testing
 @testable import Listless_iOS
 #endif
 
-/// Creates a fresh TaskStore with in-memory persistence for isolated testing.
+/// Creates a fresh ItemStore with in-memory persistence for isolated testing.
 @MainActor
-func makeTestStore() -> TaskStore {
+func makeTestStore() -> ItemStore {
     let controller = PersistenceController(inMemory: true)
-    return TaskStore(persistenceController: controller)
+    return ItemStore(persistenceController: controller)
 }
 
-/// Creates a TaskStore pre-populated with test tasks.
+/// Creates a ItemStore pre-populated with test items.
 /// - Parameters:
-///   - count: Number of tasks to create (default: 3)
-///   - titles: Optional array of titles; if nil, generates "Task 1", "Task 2", etc.
-/// - Returns: Tuple of (store, array of created task IDs)
+///   - count: Number of items to create (default: 3)
+///   - titles: Optional array of titles; if nil, generates "Item 1", "Item 2", etc.
+/// - Returns: Tuple of (store, array of created item IDs)
 @MainActor
-func makeTestStoreWithTasks(count: Int = 3, titles: [String]? = nil) throws -> (TaskStore, [UUID]) {
+func makeTestStoreWithItems(count: Int = 3, titles: [String]? = nil) throws -> (ItemStore, [UUID]) {
     let store = makeTestStore()
-    var taskIDs: [UUID] = []
+    var itemIDs: [UUID] = []
 
     for i in 0..<count {
         let title = titles?[safe: i] ?? "Task \(i + 1)"
-        let task = try store.createTask(title: title)
+        let item = try store.createItem(title: title)
         try store.save()
-        taskIDs.append(task.id)
+        itemIDs.append(item.id)
     }
 
-    return (store, taskIDs)
+    return (store, itemIDs)
 }
 
 /// Safe array subscript that returns nil instead of crashing on out-of-bounds access.

diff --git a/Tests/UI/ListlessMacUITests.swift b/Tests/UI/ListlessMacUITests.swift

@@ -27,16 +27,16 @@ final class ListlessMacUITests: XCTestCase {
         app.textFields["draft-row-append"]
     }
 
-    /// Returns the text field for a committed task with the given title.
-    func taskText(_ title: String) -> XCUIElement {
+    /// Returns the text field for a committed item with the given title.
+    func itemText(_ title: String) -> XCUIElement {
         app.textFields.matching(
-            NSPredicate(format: "identifier BEGINSWITH 'task-text-' AND value == %@", title)
+            NSPredicate(format: "identifier BEGINSWITH 'item-text-' AND value == %@", title)
         ).firstMatch
     }
 
-    /// Creates a task by typing into the draft field and pressing Return.
+    /// Creates a item by typing into the draft field and pressing Return.
     /// If no draft field exists yet, presses Cmd+N to create one.
-    func createTask(_ title: String) {
+    func createItem(_ title: String) {
         let textField = draftTextField
         if !textField.exists {
             app.typeKey("n", modifierFlags: .command)
@@ -51,26 +51,26 @@ final class ListlessMacUITests: XCTestCase {
     }
 
     /// Returns the Nth checkbox button (0-indexed).
-    func taskCheckbox(at index: Int) -> XCUIElement {
-        app.buttons.matching(identifier: "task-checkbox").element(boundBy: index)
+    func itemCheckbox(at index: Int) -> XCUIElement {
+        app.buttons.matching(identifier: "item-checkbox").element(boundBy: index)
     }
 
-    /// Enters navigation mode by pressing Escape, then navigates to the task at
+    /// Enters navigation mode by pressing Escape, then navigates to the item at
     /// the given position (0-indexed from the top) using arrow keys.
-    func navigateToTask(at index: Int) {
+    func navigateToItem(at index: Int) {
         app.typeKey(.escape, modifierFlags: [])
         for _ in 0...index {
             app.typeKey(.downArrow, modifierFlags: [])
         }
     }
 
-    /// Performs a Command+Click on the row containing the given task title.
+    /// Performs a Command+Click on the row containing the given item title.
     /// Uses CGEvent mouse events with `.maskCommand` so the app sees the
     /// modifier via `NSApp.currentEvent?.modifierFlags`. Clicks in the
     /// row's left padding area (before the checkbox) so the tap gesture
     /// fires rather than the text field or checkbox.
     func cmdClickRow(withText title: String) {
-        let textField = taskText(title)
+        let textField = itemText(title)
         XCTAssertTrue(textField.waitForExistence(timeout: 2))
         // Offset to the left of the text field, into the row's 16pt left padding.
         let coord = textField.coordinate(withNormalizedOffset: CGVector(dx: 0, dy: 0.5))
@@ -105,13 +105,13 @@ final class ListlessMacUITests: XCTestCase {
         )
     }
 
-    func testEmptyStateDisappearsAfterCreatingTask() {
-        createTask("First item")
+    func testEmptyStateDisappearsAfterCreatingItem() {
+        createItem("First item")
         app.typeKey(.escape, modifierFlags: [])
-        XCTAssertFalse(emptyStateLabel.exists, "Empty state should disappear after creating a task")
+        XCTAssertFalse(emptyStateLabel.exists, "Empty state should disappear after creating a item")
     }
 
-    // MARK: - Task Creation
+    // MARK: - Item Creation
 
     func testCmdNFocusesDraftField() {
         app.typeKey("n", modifierFlags: .command)
@@ -124,61 +124,61 @@ final class ListlessMacUITests: XCTestCase {
         textField.typeText("Focused item")
         textField.typeKey(.return, modifierFlags: [])
         XCTAssertTrue(
-            taskText("Focused item").waitForExistence(timeout: 2),
-            "Typing without clicking should commit the task if draft field has focus"
+            itemText("Focused item").waitForExistence(timeout: 2),
+            "Typing without clicking should commit the item if draft field has focus"
         )
     }
 
-    func testCreateTaskViaMenuShortcut() {
-        createTask("Buy groceries")
+    func testCreateItemViaMenuShortcut() {
+        createItem("Buy groceries")
         XCTAssertTrue(
-            taskText("Buy groceries").waitForExistence(timeout: 2),
-            "Task should appear with the typed title"
+            itemText("Buy groceries").waitForExistence(timeout: 2),
+            "Item should appear with the typed title"
         )
     }
 
-    func testReturnChainsNewTask() {
-        createTask("First item")
+    func testReturnChainsNewItem() {
+        createItem("First item")
         XCTAssertTrue(
             draftTextField.waitForExistence(timeout: 2),
             "New draft text field should appear after Return"
         )
     }
 
-    func testCreateMultipleTasks() {
-        createTask("Alpha")
-        createTask("Bravo")
-        createTask("Charlie")
+    func testCreateMultipleItems() {
+        createItem("Alpha")
+        createItem("Bravo")
+        createItem("Charlie")
         app.typeKey(.escape, modifierFlags: [])
 
-        XCTAssertTrue(taskText("Alpha").waitForExistence(timeout: 2))
-        XCTAssertTrue(taskText("Bravo").exists)
-        XCTAssertTrue(taskText("Charlie").exists)
+        XCTAssertTrue(itemText("Alpha").waitForExistence(timeout: 2))
+        XCTAssertTrue(itemText("Bravo").exists)
+        XCTAssertTrue(itemText("Charlie").exists)
     }
 
-    func testEmptyTaskDeletedOnCommit() {
+    func testEmptyItemDeletedOnCommit() {
         app.typeKey("n", modifierFlags: .command)
         XCTAssertTrue(draftTextField.waitForExistence(timeout: 2))
         app.typeKey(.escape, modifierFlags: [])
         XCTAssertTrue(
             emptyStateLabel.waitForExistence(timeout: 2),
-            "Empty state should reappear when empty task is discarded"
+            "Empty state should reappear when empty item is discarded"
         )
     }
 
-    // MARK: - Task Completion
+    // MARK: - Item Completion
 
-    func testCompleteTaskViaCheckbox() {
-        createTask("Finish report")
+    func testCompleteItemViaCheckbox() {
+        createItem("Finish report")
         app.typeKey(.escape, modifierFlags: [])
 
-        let checkbox = taskCheckbox(at: 0)
+        let checkbox = itemCheckbox(at: 0)
         XCTAssertTrue(checkbox.waitForExistence(timeout: 2))
         XCTAssertEqual(checkbox.value as? String, "circle")
         checkbox.click()
 
         let completed = app.buttons.matching(
-            NSPredicate(format: "identifier == 'task-checkbox' AND value == 'checkmark.circle.fill'")
+            NSPredicate(format: "identifier == 'item-checkbox' AND value == 'checkmark.circle.fill'")
         ).firstMatch
         XCTAssertTrue(
             completed.waitForExistence(timeout: 3),
@@ -186,20 +186,20 @@ final class ListlessMacUITests: XCTestCase {
         )
     }
 
-    func testUncompleteTask() {
-        createTask("Finish report")
+    func testUncompleteItem() {
+        createItem("Finish report")
         app.typeKey(.escape, modifierFlags: [])
 
-        taskCheckbox(at: 0).click()
+        itemCheckbox(at: 0).click()
 
         let completed = app.buttons.matching(
-            NSPredicate(format: "identifier == 'task-checkbox' AND value == 'checkmark.circle.fill'")
+            NSPredicate(format: "identifier == 'item-checkbox' AND value == 'checkmark.circle.fill'")
         ).firstMatch
         XCTAssertTrue(completed.waitForExistence(timeout: 3))
         completed.click()
 
         let uncompleted = app.buttons.matching(
-            NSPredicate(format: "identifier == 'task-checkbox' AND value == 'circle'")
+            NSPredicate(format: "identifier == 'item-checkbox' AND value == 'circle'")
         ).firstMatch
         XCTAssertTrue(
             uncompleted.waitForExistence(timeout: 3),
@@ -207,38 +207,38 @@ final class ListlessMacUITests: XCTestCase {
         )
     }
 
-    // MARK: - Task Deletion
+    // MARK: - Item Deletion
 
-    func testDeleteTaskViaBackspace() {
-        createTask("Delete me")
-        navigateToTask(at: 0)
+    func testDeleteItemViaBackspace() {
+        createItem("Delete me")
+        navigateToItem(at: 0)
         app.typeKey(.delete, modifierFlags: [])
         XCTAssertTrue(
             emptyStateLabel.waitForExistence(timeout: 2),
-            "Empty state should reappear after deleting the only task"
+            "Empty state should reappear after deleting the only item"
         )
     }
 
     func testArrowKeyNavigationThenDelete() {
-        createTask("Keep me")
-        createTask("Delete me")
-        navigateToTask(at: 1)
+        createItem("Keep me")
+        createItem("Delete me")
+        navigateToItem(at: 1)
         app.typeKey(.delete, modifierFlags: [])
 
-        XCTAssertTrue(taskText("Keep me").waitForExistence(timeout: 2), "First task should remain")
-        XCTAssertFalse(taskText("Delete me").exists, "Second task should be deleted")
+        XCTAssertTrue(itemText("Keep me").waitForExistence(timeout: 2), "First item should remain")
+        XCTAssertFalse(itemText("Delete me").exists, "Second item should be deleted")
     }
 
     // MARK: - Reordering
 
-    func testMoveTaskUp() {
-        createTask("Alpha")
-        createTask("Bravo")
-        navigateToTask(at: 1)
+    func testMoveItemUp() {
+        createItem("Alpha")
+        createItem("Bravo")
+        navigateToItem(at: 1)
         app.typeKey(.upArrow, modifierFlags: .command)
 
-        let bravo = taskText("Bravo")
-        let alpha = taskText("Alpha")
+        let bravo = itemText("Bravo")
+        let alpha = itemText("Alpha")
         XCTAssertTrue(bravo.waitForExistence(timeout: 2))
         XCTAssertTrue(alpha.exists)
         XCTAssertLessThan(
@@ -247,14 +247,14 @@ final class ListlessMacUITests: XCTestCase {
         )
     }
 
-    func testMoveTaskDown() {
-        createTask("Alpha")
-        createTask("Bravo")
-        navigateToTask(at: 0)
+    func testMoveItemDown() {
+        createItem("Alpha")
+        createItem("Bravo")
+        navigateToItem(at: 0)
         app.typeKey(.downArrow, modifierFlags: .command)
 
-        let alpha = taskText("Alpha")
-        let bravo = taskText("Bravo")
+        let alpha = itemText("Alpha")
+        let bravo = itemText("Bravo")
         XCTAssertTrue(alpha.waitForExistence(timeout: 2))
         XCTAssertTrue(bravo.exists)
         XCTAssertGreaterThan(
@@ -266,50 +266,50 @@ final class ListlessMacUITests: XCTestCase {
     // MARK: - Select All
 
     func testSelectAllThenDelete() {
-        createTask("Alpha")
-        createTask("Bravo")
-        createTask("Charlie")
-        navigateToTask(at: 0)
+        createItem("Alpha")
+        createItem("Bravo")
+        createItem("Charlie")
+        navigateToItem(at: 0)
         app.typeKey("a", modifierFlags: .command)
         app.typeKey(.delete, modifierFlags: [])
         XCTAssertTrue(
             emptyStateLabel.waitForExistence(timeout: 2),
-            "All tasks should be deleted after Select All + Delete"
+            "All items should be deleted after Select All + Delete"
         )
     }
 
-    func testSelectAllIncludesCompletedTasks() {
-        createTask("Active item")
-        createTask("Done item")
+    func testSelectAllIncludesCompletedItems() {
+        createItem("Active item")
+        createItem("Done item")
         app.typeKey(.escape, modifierFlags: [])
 
-        // Complete the second task
-        taskCheckbox(at: 1).click()
+        // Complete the second item
+        itemCheckbox(at: 1).click()
         let completed = app.buttons.matching(
-            NSPredicate(format: "identifier == 'task-checkbox' AND value == 'checkmark.circle.fill'")
+            NSPredicate(format: "identifier == 'item-checkbox' AND value == 'checkmark.circle.fill'")
         ).firstMatch
         XCTAssertTrue(completed.waitForExistence(timeout: 3))
 
-        // Navigate to first task, then select all and delete
-        navigateToTask(at: 0)
+        // Navigate to first item, then select all and delete
+        navigateToItem(at: 0)
         app.typeKey("a", modifierFlags: .command)
         app.typeKey(.delete, modifierFlags: [])
         XCTAssertTrue(
             emptyStateLabel.waitForExistence(timeout: 2),
-            "Both active and completed tasks should be deleted after Select All + Delete"
+            "Both active and completed items should be deleted after Select All + Delete"
         )
     }
 
     // MARK: - Clear Completed
 
     func testClearCompleted() {
-        createTask("Done task")
+        createItem("Done item")
         app.typeKey(.escape, modifierFlags: [])
 
-        taskCheckbox(at: 0).click()
+        itemCheckbox(at: 0).click()
 
         let completed = app.buttons.matching(
-            NSPredicate(format: "identifier == 'task-checkbox' AND value == 'checkmark.circle.fill'")
+            NSPredicate(format: "identifier == 'item-checkbox' AND value == 'checkmark.circle.fill'")
         ).firstMatch
         XCTAssertTrue(completed.waitForExistence(timeout: 3))
 
@@ -318,32 +318,32 @@ final class ListlessMacUITests: XCTestCase {
 
         XCTAssertTrue(
             emptyStateLabel.waitForExistence(timeout: 3),
-            "Empty state should reappear after clearing the only completed task"
+            "Empty state should reappear after clearing the only completed item"
         )
     }
 
     // MARK: - Shift+Arrow Selection
 
     func testShiftDownExtendsSelection() {
-        createTask("Alpha")
-        createTask("Bravo")
-        createTask("Charlie")
-        navigateToTask(at: 0)
+        createItem("Alpha")
+        createItem("Bravo")
+        createItem("Charlie")
+        navigateToItem(at: 0)
         app.typeKey(.downArrow, modifierFlags: .shift)
         app.typeKey(.downArrow, modifierFlags: .shift)
         // All three should be selected; delete removes them all.
         app.typeKey(.delete, modifierFlags: [])
         XCTAssertTrue(
             emptyStateLabel.waitForExistence(timeout: 2),
-            "All three tasks should be deleted after Shift+Down range select"
+            "All three items should be deleted after Shift+Down range select"
         )
     }
 
     func testShiftUpContractsSelection() {
-        createTask("Alpha")
-        createTask("Bravo")
-        createTask("Charlie")
-        navigateToTask(at: 0)
+        createItem("Alpha")
+        createItem("Bravo")
+        createItem("Charlie")
+        navigateToItem(at: 0)
         // Extend down to select Alpha, Bravo, Charlie
         app.typeKey(.downArrow, modifierFlags: .shift)
         app.typeKey(.downArrow, modifierFlags: .shift)
@@ -352,16 +352,16 @@ final class ListlessMacUITests: XCTestCase {
         app.typeKey(.upArrow, modifierFlags: .shift)
         // Only Alpha should be selected now.
         app.typeKey(.delete, modifierFlags: [])
-        XCTAssertTrue(taskText("Bravo").waitForExistence(timeout: 2), "Bravo should remain")
-        XCTAssertTrue(taskText("Charlie").exists, "Charlie should remain")
-        XCTAssertFalse(taskText("Alpha").exists, "Alpha should be deleted")
+        XCTAssertTrue(itemText("Bravo").waitForExistence(timeout: 2), "Bravo should remain")
+        XCTAssertTrue(itemText("Charlie").exists, "Charlie should remain")
+        XCTAssertFalse(itemText("Alpha").exists, "Alpha should be deleted")
     }
 
     func testSelectAllThenShiftDown() {
-        createTask("Alpha")
-        createTask("Bravo")
-        createTask("Charlie")
-        navigateToTask(at: 0)
+        createItem("Alpha")
+        createItem("Bravo")
+        createItem("Charlie")
+        navigateToItem(at: 0)
         // Select all via Cmd+A
         app.typeKey("a", modifierFlags: .command)
         // Shift+Down should be a no-op (cursor already at last item).
@@ -370,17 +370,17 @@ final class ListlessMacUITests: XCTestCase {
         app.typeKey(.delete, modifierFlags: [])
         XCTAssertTrue(
             emptyStateLabel.waitForExistence(timeout: 2),
-            "All tasks should still be selected after Shift+Down at end"
+            "All items should still be selected after Shift+Down at end"
         )
     }
 
     // MARK: - Cmd+Click Selection
 
     func testCmdClickDeselectsFromRange() {
-        createTask("Alpha")
-        createTask("Bravo")
-        createTask("Charlie")
-        navigateToTask(at: 0)
+        createItem("Alpha")
+        createItem("Bravo")
+        createItem("Charlie")
+        navigateToItem(at: 0)
         // Extend selection to all three
         app.typeKey(.downArrow, modifierFlags: .shift)
         app.typeKey(.downArrow, modifierFlags: .shift)
@@ -389,36 +389,36 @@ final class ListlessMacUITests: XCTestCase {
         app.typeKey(.delete, modifierFlags: [])
 
         XCTAssertTrue(
-            taskText("Bravo").waitForExistence(timeout: 2),
+            itemText("Bravo").waitForExistence(timeout: 2),
             "Bravo should remain (was deselected by Cmd+Click)"
         )
-        XCTAssertFalse(taskText("Alpha").exists, "Alpha should be deleted")
-        XCTAssertFalse(taskText("Charlie").exists, "Charlie should be deleted")
+        XCTAssertFalse(itemText("Alpha").exists, "Alpha should be deleted")
+        XCTAssertFalse(itemText("Charlie").exists, "Charlie should be deleted")
     }
 
     func testCmdClickAddsToSelection() {
-        createTask("Alpha")
-        createTask("Bravo")
-        createTask("Charlie")
-        navigateToTask(at: 0)
+        createItem("Alpha")
+        createItem("Bravo")
+        createItem("Charlie")
+        navigateToItem(at: 0)
         // Cmd+Click Charlie to add it to selection
         cmdClickRow(withText: "Charlie")
         app.typeKey(.delete, modifierFlags: [])
 
         XCTAssertTrue(
-            taskText("Bravo").waitForExistence(timeout: 2),
+            itemText("Bravo").waitForExistence(timeout: 2),
             "Bravo should remain (was not selected)"
         )
-        XCTAssertFalse(taskText("Alpha").exists, "Alpha should be deleted")
-        XCTAssertFalse(taskText("Charlie").exists, "Charlie should be deleted")
+        XCTAssertFalse(itemText("Alpha").exists, "Alpha should be deleted")
+        XCTAssertFalse(itemText("Charlie").exists, "Charlie should be deleted")
     }
 
     func testShiftUpAfterCmdClickDeselect() {
-        createTask("Delta")
-        createTask("Echo")
-        createTask("Foxtrot")
-        createTask("Golf")
-        navigateToTask(at: 0)
+        createItem("Delta")
+        createItem("Echo")
+        createItem("Foxtrot")
+        createItem("Golf")
+        navigateToItem(at: 0)
         // Select Delta through Golf
         app.typeKey(.downArrow, modifierFlags: .shift)
         app.typeKey(.downArrow, modifierFlags: .shift)
@@ -429,17 +429,17 @@ final class ListlessMacUITests: XCTestCase {
         app.typeKey(.upArrow, modifierFlags: .shift)
         app.typeKey(.delete, modifierFlags: [])
 
-        XCTAssertTrue(taskText("Echo").waitForExistence(timeout: 2), "Echo should remain")
-        XCTAssertTrue(taskText("Golf").exists, "Golf should remain")
-        XCTAssertFalse(taskText("Delta").exists, "Delta should be deleted")
-        XCTAssertFalse(taskText("Foxtrot").exists, "Foxtrot should be deleted")
+        XCTAssertTrue(itemText("Echo").waitForExistence(timeout: 2), "Echo should remain")
+        XCTAssertTrue(itemText("Golf").exists, "Golf should remain")
+        XCTAssertFalse(itemText("Delta").exists, "Delta should be deleted")
+        XCTAssertFalse(itemText("Foxtrot").exists, "Foxtrot should be deleted")
     }
 
     func testSelectAllAfterCmdClick() {
-        createTask("Alpha")
-        createTask("Bravo")
-        createTask("Charlie")
-        navigateToTask(at: 0)
+        createItem("Alpha")
+        createItem("Bravo")
+        createItem("Charlie")
+        navigateToItem(at: 0)
         app.typeKey(.downArrow, modifierFlags: .shift)
         app.typeKey(.downArrow, modifierFlags: .shift)
         // Cmd+Click Bravo to create discontinuous selection {Alpha, Charlie}
@@ -450,7 +450,7 @@ final class ListlessMacUITests: XCTestCase {
 
         XCTAssertTrue(
             emptyStateLabel.waitForExistence(timeout: 2),
-            "All tasks should be deleted after Select All"
+            "All items should be deleted after Select All"
         )
     }
 }

diff --git a/Tests/UI/ListlessiOSUITests.swift b/Tests/UI/ListlessiOSUITests.swift

@@ -21,9 +21,9 @@ final class ListlessiOSUITests: XCTestCase {
         app.staticTexts["Pull down to create"]
     }
 
-    /// The main scroll view area; tapping empty space here creates a draft task.
-    var taskListScrollView: XCUIElement {
-        app.scrollViews["task-list-scrollview"]
+    /// The main scroll view area; tapping empty space here creates a draft item.
+    var itemListScrollView: XCUIElement {
+        app.scrollViews["item-list-scrollview"]
     }
 
     /// The draft row text view that appears after tapping empty space.
@@ -32,19 +32,19 @@ final class ListlessiOSUITests: XCTestCase {
         app.textViews.matching(identifier: "draft-row-append").firstMatch
     }
 
-    /// Returns the static text for a committed task with the given title.
-    func taskText(_ title: String) -> XCUIElement {
+    /// Returns the static text for a committed item with the given title.
+    func itemText(_ title: String) -> XCUIElement {
         app.staticTexts.matching(
-            NSPredicate(format: "identifier BEGINSWITH 'task-text-' AND label == %@", title)
+            NSPredicate(format: "identifier BEGINSWITH 'item-text-' AND label == %@", title)
         ).firstMatch
     }
 
-    /// Creates a task by tapping empty space to reveal the draft field,
+    /// Creates a item by tapping empty space to reveal the draft field,
     /// typing a title, and pressing Return.
-    func createTask(_ title: String) {
+    func createItem(_ title: String) {
         let textView = draftTextField
         if !textView.exists {
-            taskListScrollView.tap()
+            itemListScrollView.tap()
             if !textView.waitForExistence(timeout: 2) {
                 XCTFail("Draft text view should appear after tapping empty space")
                 return
@@ -55,11 +55,11 @@ final class ListlessiOSUITests: XCTestCase {
     }
 
     /// Returns the Nth checkbox button (0-indexed).
-    func taskCheckbox(at index: Int) -> XCUIElement {
-        app.buttons.matching(identifier: "task-checkbox").element(boundBy: index)
+    func itemCheckbox(at index: Int) -> XCUIElement {
+        app.buttons.matching(identifier: "item-checkbox").element(boundBy: index)
     }
 
-    /// Exits editing mode. After createTask, the new draft text view is
+    /// Exits editing mode. After createItem, the new draft text view is
     /// focused. Dismiss it by tapping the scroll view background (which
     /// calls handleBackgroundTap to commit/dismiss the empty draft).
     func exitEditingMode() {
@@ -68,7 +68,7 @@ final class ListlessiOSUITests: XCTestCase {
             draft.typeText("\n")
         }
         // Tap background to deselect
-        taskListScrollView.tap()
+        itemListScrollView.tap()
     }
 
     // MARK: - Empty State
@@ -80,65 +80,65 @@ final class ListlessiOSUITests: XCTestCase {
         )
     }
 
-    func testEmptyStateDisappearsAfterCreatingTask() {
-        createTask("First item")
+    func testEmptyStateDisappearsAfterCreatingItem() {
+        createItem("First item")
         exitEditingMode()
-        XCTAssertFalse(emptyStateLabel.exists, "Empty state should disappear after creating a task")
+        XCTAssertFalse(emptyStateLabel.exists, "Empty state should disappear after creating a item")
     }
 
-    // MARK: - Task Creation
+    // MARK: - Item Creation
 
-    func testCreateTaskViaTap() {
-        createTask("Buy groceries")
+    func testCreateItemViaTap() {
+        createItem("Buy groceries")
         exitEditingMode()
         XCTAssertTrue(
-            taskText("Buy groceries").waitForExistence(timeout: 2),
-            "Task should appear with the typed title"
+            itemText("Buy groceries").waitForExistence(timeout: 2),
+            "Item should appear with the typed title"
         )
     }
 
-    func testReturnChainsNewTask() {
-        createTask("First item")
+    func testReturnChainsNewItem() {
+        createItem("First item")
         XCTAssertTrue(
             draftTextField.waitForExistence(timeout: 2),
             "New draft text view should appear after Return"
         )
     }
 
-    func testCreateMultipleTasks() {
-        createTask("Alpha")
-        createTask("Bravo")
-        createTask("Charlie")
+    func testCreateMultipleItems() {
+        createItem("Alpha")
+        createItem("Bravo")
+        createItem("Charlie")
         exitEditingMode()
 
-        XCTAssertTrue(taskText("Alpha").waitForExistence(timeout: 2))
-        XCTAssertTrue(taskText("Bravo").exists)
-        XCTAssertTrue(taskText("Charlie").exists)
+        XCTAssertTrue(itemText("Alpha").waitForExistence(timeout: 2))
+        XCTAssertTrue(itemText("Bravo").exists)
+        XCTAssertTrue(itemText("Charlie").exists)
     }
 
-    func testEmptyTaskDeletedOnCommit() {
-        taskListScrollView.tap()
+    func testEmptyItemDeletedOnCommit() {
+        itemListScrollView.tap()
         XCTAssertTrue(draftTextField.waitForExistence(timeout: 2))
         draftTextField.typeText("\n")
         XCTAssertTrue(
             emptyStateLabel.waitForExistence(timeout: 2),
-            "Empty state should reappear when empty task is discarded"
+            "Empty state should reappear when empty item is discarded"
         )
     }
 
-    // MARK: - Task Completion
+    // MARK: - Item Completion
 
-    func testCompleteTaskViaCheckbox() {
-        createTask("Finish report")
+    func testCompleteItemViaCheckbox() {
+        createItem("Finish report")
         exitEditingMode()
 
-        let checkbox = taskCheckbox(at: 0)
+        let checkbox = itemCheckbox(at: 0)
         XCTAssertTrue(checkbox.waitForExistence(timeout: 2))
         XCTAssertEqual(checkbox.value as? String, "circle")
         checkbox.tap()
 
         let completed = app.buttons.matching(
-            NSPredicate(format: "identifier == 'task-checkbox' AND value == 'checkmark.circle.fill'")
+            NSPredicate(format: "identifier == 'item-checkbox' AND value == 'checkmark.circle.fill'")
         ).firstMatch
         XCTAssertTrue(
             completed.waitForExistence(timeout: 3),
@@ -146,20 +146,20 @@ final class ListlessiOSUITests: XCTestCase {
         )
     }
 
-    func testUncompleteTask() {
-        createTask("Finish report")
+    func testUncompleteItem() {
+        createItem("Finish report")
         exitEditingMode()
 
-        taskCheckbox(at: 0).tap()
+        itemCheckbox(at: 0).tap()
 
         let completed = app.buttons.matching(
-            NSPredicate(format: "identifier == 'task-checkbox' AND value == 'checkmark.circle.fill'")
+            NSPredicate(format: "identifier == 'item-checkbox' AND value == 'checkmark.circle.fill'")
         ).firstMatch
         XCTAssertTrue(completed.waitForExistence(timeout: 3))
         completed.tap()
 
         let uncompleted = app.buttons.matching(
-            NSPredicate(format: "identifier == 'task-checkbox' AND value == 'circle'")
+            NSPredicate(format: "identifier == 'item-checkbox' AND value == 'circle'")
         ).firstMatch
         XCTAssertTrue(
             uncompleted.waitForExistence(timeout: 3),

diff --git a/Tests/Unit/ItemStoreCompletionTests.swift b/Tests/Unit/ItemStoreCompletionTests.swift

@@ -0,0 +1,189 @@
+import Foundation
+import Testing
+
+#if os(macOS)
+@testable import Listless
+#else
+@testable import Listless_iOS
+#endif
+
+@Suite("ItemStore Completion Behavior", .serialized)
+@MainActor
+struct ItemStoreCompletionTests {
+
+    // MARK: - Basic Completion Tests
+
+    @Test("Complete item")
+    func completeItem() async throws {
+        let store = makeTestStore()
+        let item = try store.createItem(title: "Task to complete")
+
+        try store.complete(itemID: item.id)
+
+        let items = try store.fetchItems()
+        #expect(items.first?.isCompleted == true)
+    }
+
+    @Test("Uncomplete item")
+    func uncompleteItem() async throws {
+        let store = makeTestStore()
+        let item = try store.createItem(title: "Item")
+        try store.complete(itemID: item.id)
+
+        try store.uncomplete(itemID: item.id)
+
+        let items = try store.fetchItems()
+        #expect(items.first?.isCompleted == false)
+    }
+
+    @Test("Complete with invalid ID does nothing")
+    func completeWithInvalidIDDoesNothing() async throws {
+        let store = makeTestStore()
+        let item = try store.createItem(title: "Item")
+        let invalidID = UUID()
+
+        try store.complete(itemID: invalidID)
+
+        let items = try store.fetchItems()
+        #expect(items.first?.isCompleted == false)
+    }
+
+    @Test("Uncomplete with invalid ID does nothing")
+    func uncompleteWithInvalidIDDoesNothing() async throws {
+        let store = makeTestStore()
+        let item = try store.createItem(title: "Item")
+        try store.complete(itemID: item.id)
+        let invalidID = UUID()
+
+        try store.uncomplete(itemID: invalidID)
+
+        let items = try store.fetchItems()
+        #expect(items.first?.isCompleted == true)
+    }
+
+    // MARK: - Timestamp Tests
+
+    @Test("Completing item updates timestamp")
+    func completingItemUpdatesTimestamp() async throws {
+        let store = makeTestStore()
+        let item = try store.createItem(title: "Item")
+        let originalUpdatedAt = item.updatedAt
+
+        // Small delay to ensure timestamp difference
+        try await Task.sleep(nanoseconds: 10_000_000) // 10ms
+
+        try store.complete(itemID: item.id)
+
+        let items = try store.fetchItems()
+        let updatedItem = items.first
+        #expect(updatedItem?.updatedAt ?? Date() > originalUpdatedAt)
+    }
+
+    // MARK: - Sorting Tests
+
+    @Test("Active items appear before completed items")
+    func activeItemsAppearBeforeCompletedItems() async throws {
+        let store = makeTestStore()
+        let item1 = try store.createItem(title: "Item 1")
+        let item2 = try store.createItem(title: "Item 2")
+        let item3 = try store.createItem(title: "Item 3")
+
+        try store.complete(itemID: item2.id)
+
+        let items = try store.fetchItems()
+        #expect(items[0].id == item1.id)
+        #expect(items[1].id == item3.id)
+        #expect(items[2].id == item2.id)
+    }
+
+    @Test("Completed items sorted by completedOrder")
+    func completedItemsSortedByCompletedOrder() async throws {
+        let store = makeTestStore()
+        let item1 = try store.createItem(title: "Item 1")
+        let item2 = try store.createItem(title: "Item 2")
+        let item3 = try store.createItem(title: "Item 3")
+
+        // Complete in specific order
+        try store.complete(itemID: item2.id)
+        try store.complete(itemID: item1.id)
+        try store.complete(itemID: item3.id)
+
+        let items = try store.fetchItems()
+        // All completed, should be sorted by completedOrder (most recently completed first)
+        #expect(items[0].id == item3.id)
+        #expect(items[1].id == item1.id)
+        #expect(items[2].id == item2.id)
+    }
+
+    @Test("Toggle completion multiple times")
+    func toggleCompletionMultipleTimes() async throws {
+        let store = makeTestStore()
+        let item = try store.createItem(title: "Item")
+
+        try store.complete(itemID: item.id)
+        var items = try store.fetchItems()
+        #expect(items.first?.isCompleted == true)
+
+        try store.uncomplete(itemID: item.id)
+        items = try store.fetchItems()
+        #expect(items.first?.isCompleted == false)
+
+        try store.complete(itemID: item.id)
+        items = try store.fetchItems()
+        #expect(items.first?.isCompleted == true)
+    }
+
+    @Test("Complete all items")
+    func completeAllItems() async throws {
+        let (store, itemIDs) = try makeTestStoreWithItems(count: 5)
+
+        for id in itemIDs {
+            try store.complete(itemID: id)
+        }
+
+        let items = try store.fetchItems()
+        #expect(items.allSatisfy { $0.isCompleted })
+        #expect(items.count == 5)
+    }
+
+    @Test("Uncomplete restores previous sortOrder when no active conflict")
+    func uncompleteRestoresPreviousSortOrderWhenNoConflict() async throws {
+        let (store, itemIDs) = try makeTestStoreWithItems(count: 3)
+        let itemToRestoreID = itemIDs[1]
+
+        let originalSortOrder = try store.fetchItems().first { $0.id == itemToRestoreID }?.sortOrder
+        #expect(originalSortOrder != nil)
+
+        try store.complete(itemID: itemToRestoreID)
+        try store.uncomplete(itemID: itemToRestoreID)
+
+        let activeItems = try store.fetchItems().filter { !$0.isCompleted }
+        let restoredItem = activeItems.first { $0.id == itemToRestoreID }
+
+        #expect(restoredItem != nil)
+        #expect(restoredItem?.sortOrder == originalSortOrder)
+        #expect(activeItems.count == 3)
+    }
+
+    @Test("Uncomplete appends item when restored sortOrder conflicts with active item")
+    func uncompleteAppendsWhenRestoredSortOrderConflicts() async throws {
+        let store = makeTestStore()
+        let activeItem = try store.createItem(title: "Active item")
+        let completedItem = try store.createItem(title: "Completed item")
+
+        try store.complete(itemID: completedItem.id)
+        try store.moveItem(itemID: activeItem.id, toIndex: 0)
+        completedItem.sortOrder = activeItem.sortOrder
+        try store.save()
+
+        try store.uncomplete(itemID: completedItem.id)
+
+        let activeItems = try store.fetchItems().filter { !$0.isCompleted }
+            .sorted { $0.sortOrder < $1.sortOrder }
+        let lastActiveItem = activeItems.last
+
+        #expect(activeItems.count == 2)
+        #expect(lastActiveItem?.id == completedItem.id)
+        #expect(lastActiveItem?.sortOrder ?? 0 > activeItem.sortOrder)
+    }
+}

diff --git a/Tests/Unit/ItemStoreEdgeCaseTests.swift b/Tests/Unit/ItemStoreEdgeCaseTests.swift

@@ -0,0 +1,285 @@
+import Foundation
+import CoreData
+import Testing
+
+#if os(macOS)
+@testable import Listless
+#else
+@testable import Listless_iOS
+#endif
+
+@Suite("ItemStore Edge Cases", .serialized)
+@MainActor
+struct ItemStoreEdgeCaseTests {
+
+    // MARK: - Title Edge Cases
+
+    @Test("Task with empty title")
+    func itemWithEmptyTitle() async throws {
+        let store = makeTestStore()
+
+        let item = try store.createItem(title: "")
+
+        let items = try store.fetchItems()
+        #expect(items.first?.title == "")
+    }
+
+    @Test("Task with very long title")
+    func itemWithVeryLongTitle() async throws {
+        let store = makeTestStore()
+        let longTitle = String(repeating: "A", count: 10_000)
+
+        let item = try store.createItem(title: longTitle)
+
+        let items = try store.fetchItems()
+        #expect(items.first?.title.count == 10_000)
+    }
+
+    @Test("Task with special characters")
+    func itemWithSpecialCharacters() async throws {
+        let store = makeTestStore()
+        let specialTitle = "Test 🎉 with émojis & spëcial çharacters! @#$%^&*()"
+
+        let item = try store.createItem(title: specialTitle)
+
+        let items = try store.fetchItems()
+        #expect(items.first?.title == specialTitle)
+    }
+
+    @Test("Task with newlines and tabs")
+    func itemWithNewlinesAndTabs() async throws {
+        let store = makeTestStore()
+        let multilineTitle = "Line 1\nLine 2\tTabbed"
+
+        let item = try store.createItem(title: multilineTitle)
+
+        let items = try store.fetchItems()
+        #expect(items.first?.title == multilineTitle)
+    }
+
+    // MARK: - Large Data Sets
+
+    @Test("Create many items")
+    func createManyItems() async throws {
+        let store = makeTestStore()
+        let count = 100
+
+        for i in 0..<count {
+            _ = try store.createItem(title: "Task \(i)")
+        }
+
+        let items = try store.fetchItems()
+        #expect(items.count == count)
+    }
+
+    @Test("Delete all items from large set")
+    func deleteAllItemsFromLargeSet() async throws {
+        let store = makeTestStore()
+        var itemIDs: [UUID] = []
+
+        for i in 0..<50 {
+            let item = try store.createItem(title: "Task \(i)")
+            itemIDs.append(item.id)
+        }
+
+        for id in itemIDs {
+            try store.delete(itemID: id)
+        }
+
+        let items = try store.fetchItems()
+        #expect(items.isEmpty)
+    }
+
+    // MARK: - State Transitions
+
+    @Test("Create item after completing all items")
+    func createItemAfterCompletingAllItems() async throws {
+        let (store, itemIDs) = try makeTestStoreWithItems(count: 3)
+
+        for id in itemIDs {
+            try store.complete(itemID: id)
+        }
+
+        let newItem = try store.createItem(title: "New item")
+
+        let items = try store.fetchItems()
+        let activeItems = items.filter { !$0.isCompleted }
+        #expect(activeItems.count == 1)
+        #expect(activeItems[0].id == newItem.id)
+    }
+
+    @Test("Rapid updates to same item")
+    func rapidUpdatesToSameItem() async throws {
+        let store = makeTestStore()
+        let item = try store.createItem(title: "Original")
+
+        for i in 0..<10 {
+            try store.update(itemID: item.id, title: "Update \(i)")
+        }
+
+        let items = try store.fetchItems()
+        #expect(items.first?.title == "Update 9")
+    }
+
+    // MARK: - Store State Tests
+
+    @Test("Store with only completed items")
+    func storeWithOnlyCompletedItems() async throws {
+        let (store, itemIDs) = try makeTestStoreWithItems(count: 5)
+
+        for id in itemIDs {
+            try store.complete(itemID: id)
+        }
+
+        let items = try store.fetchItems()
+        #expect(items.allSatisfy { $0.isCompleted })
+        #expect(items.count == 5)
+    }
+
+    @Test("SortOrder after completing all items")
+    func sortOrderAfterCompletingAllItems() async throws {
+        let (store, itemIDs) = try makeTestStoreWithItems(count: 3)
+
+        for id in itemIDs {
+            try store.complete(itemID: id)
+        }
+
+        let newItem1 = try store.createItem(title: "New 1")
+        let newItem2 = try store.createItem(title: "New 2")
+
+        let activeItems = try store.fetchItems().filter { !$0.isCompleted }
+        #expect(activeItems[0].id == newItem1.id)
+        #expect(activeItems[1].id == newItem2.id)
+        #expect(activeItems[1].sortOrder > activeItems[0].sortOrder)
+    }
+
+    @Test("Uncompleting item moves it back to active")
+    func uncompletingItemMovesItBackToActive() async throws {
+        let (store, itemIDs) = try makeTestStoreWithItems(count: 3)
+        try store.complete(itemID: itemIDs[1])
+
+        try store.uncomplete(itemID: itemIDs[1])
+
+        let items = try store.fetchItems()
+        let activeItems = items.filter { !$0.isCompleted }
+        #expect(activeItems.count == 3)
+        #expect(activeItems.contains { $0.id == itemIDs[1] })
+    }
+
+    @Test("Uncomplete legacy sortOrder zero conflict appends to end")
+    func uncompleteLegacyZeroConflictAppendsToEnd() async throws {
+        let store = makeTestStore()
+        let activeItem = try store.createItem(title: "Active")
+        let completedItem = try store.createItem(title: "Completed")
+
+        activeItem.sortOrder = 0
+        try store.complete(itemID: completedItem.id)
+        completedItem.sortOrder = 0
+        try store.save()
+
+        try store.uncomplete(itemID: completedItem.id)
+
+        let activeItems = try store.fetchItems().filter { !$0.isCompleted }
+            .sorted { $0.sortOrder < $1.sortOrder }
+        #expect(activeItems.count == 2)
+        #expect(activeItems[0].id == activeItem.id)
+        #expect(activeItems[1].id == completedItem.id)
+        #expect(activeItems[1].sortOrder > activeItems[0].sortOrder)
+    }
+
+    @Test("Merge policy prefers store when store updatedAt is newer")
+    func mergePolicyPrefersStoreWhenStoreIsNewer() async throws {
+        let controller = PersistenceController(inMemory: true)
+        let viewContext = controller.viewContext
+        viewContext.automaticallyMergesChangesFromParent = false
+
+        let item = ItemEntity(context: viewContext)
+        item.title = "Original"
+        item.updatedAt = Date(timeIntervalSince1970: 100)
+        try viewContext.save()
+
+        let itemID = item.id
+
+        let backgroundContext = controller.container.newBackgroundContext()
+        try await backgroundContext.perform {
+            let request = ItemEntity.fetchRequest()
+            request.predicate = NSPredicate(format: "id == %@", itemID as CVarArg)
+            request.fetchLimit = 1
+
+            guard let remote = try backgroundContext.fetch(request).first else {
+                Issue.record("Expected item to exist in background context")
+                return
+            }
+
+            remote.title = "Remote newer"
+            remote.updatedAt = Date(timeIntervalSince1970: 200)
+            try backgroundContext.save()
+        }
+
+        item.updatedAt = Date(timeIntervalSince1970: 150)
+        try viewContext.save()
+
+        let verifyContext = controller.container.newBackgroundContext()
+        try await verifyContext.perform {
+            let request = ItemEntity.fetchRequest()
+            request.predicate = NSPredicate(format: "id == %@", itemID as CVarArg)
+            request.fetchLimit = 1
+
+            guard let resolved = try verifyContext.fetch(request).first else {
+                Issue.record("Expected item to exist in verify context")
+                return
+            }
+
+            #expect(resolved.title == "Remote newer")
+            #expect(resolved.updatedAt == Date(timeIntervalSince1970: 200))
+        }
+    }
+
+    @Test("Merge policy prefers local when local updatedAt is newer")
+    func mergePolicyPrefersLocalWhenLocalIsNewer() async throws {
+        let controller = PersistenceController(inMemory: true)
+        let viewContext = controller.viewContext
+        viewContext.automaticallyMergesChangesFromParent = false
+
+        let item = ItemEntity(context: viewContext)
+        item.title = "Original"
+        item.updatedAt = Date(timeIntervalSince1970: 100)
+        try viewContext.save()
+
+        let itemID = item.id
+
+        let backgroundContext = controller.container.newBackgroundContext()
+        try await backgroundContext.perform {
+            let request = ItemEntity.fetchRequest()
+            request.predicate = NSPredicate(format: "id == %@", itemID as CVarArg)
+            request.fetchLimit = 1
+
+            guard let remote = try backgroundContext.fetch(request).first else {
+                Issue.record("Expected item to exist in background context")
+                return
+            }
+
+            remote.title = "Remote older"
+            remote.updatedAt = Date(timeIntervalSince1970: 200)
+            try backgroundContext.save()
+        }
+
+        item.updatedAt = Date(timeIntervalSince1970: 300)
+        try viewContext.save()
+
+        let verifyContext = controller.container.newBackgroundContext()
+        try await verifyContext.perform {
+            let request = ItemEntity.fetchRequest()
+            request.predicate = NSPredicate(format: "id == %@", itemID as CVarArg)
+            request.fetchLimit = 1
+
+            guard let resolved = try verifyContext.fetch(request).first else {
+                Issue.record("Expected item to exist in verify context")
+                return
+            }
+
+            #expect(resolved.title == "Original")
+            #expect(resolved.updatedAt == Date(timeIntervalSince1970: 300))
+        }
+    }
+}

diff --git a/Tests/Unit/ItemStoreOrderingTests.swift b/Tests/Unit/ItemStoreOrderingTests.swift

@@ -0,0 +1,190 @@
+import Foundation
+import Testing
+
+#if os(macOS)
+@testable import Listless
+#else
+@testable import Listless_iOS
+#endif
+
+@Suite("ItemStore Task Reordering", .serialized)
+@MainActor
+struct ItemStoreOrderingTests {
+
+    // MARK: - Initial State Tests
+
+    @Test("Initial sortOrder has 1000-point gaps")
+    func initialSortOrderHasThousandPointGaps() async throws {
+        let store = makeTestStore()
+
+        let item1 = try store.createItem(title: "Item 1")
+        let item2 = try store.createItem(title: "Item 2")
+        let item3 = try store.createItem(title: "Item 3")
+
+        let items = try store.fetchItems()
+
+        // All items are active, so they should be the first 3
+        #expect(items.count == 3)
+
+        // Verify items are in ascending order
+        #expect(items[0].sortOrder < items[1].sortOrder)
+        #expect(items[1].sortOrder < items[2].sortOrder)
+
+        // Verify 1000-point gaps between items
+        #expect(items[1].sortOrder - items[0].sortOrder == 1000)
+        #expect(items[2].sortOrder - items[1].sortOrder == 1000)
+    }
+
+    // MARK: - Move Tests (Parameterized)
+
+    @Test("Move item to different positions", arguments: [
+        (from: 0, to: 2),
+        (from: 2, to: 0),
+        (from: 0, to: 1),
+        (from: 1, to: 0),
+        (from: 1, to: 2),
+        (from: 2, to: 1),
+    ])
+    func moveItemToDifferentPositions(from: Int, to: Int) async throws {
+        let (store, itemIDs) = try makeTestStoreWithItems(count: 3)
+        let itemToMove = itemIDs[from]
+
+        try store.moveItem(itemID: itemToMove, toIndex: to)
+
+        let items = try store.fetchItems().filter { !$0.isCompleted }
+        #expect(items[to].id == itemToMove)
+    }
+
+    // MARK: - Order Preservation Tests
+
+    @Test("Moving maintains 1000-point gaps")
+    func movingMaintainsThousandPointGaps() async throws {
+        let (store, itemIDs) = try makeTestStoreWithItems(count: 4)
+
+        try store.moveItem(itemID: itemIDs[0], toIndex: 2)
+
+        let items = try store.fetchItems().filter { !$0.isCompleted }
+        #expect(items[0].sortOrder == 0)
+        #expect(items[1].sortOrder == 1000)
+        #expect(items[2].sortOrder == 2000)
+        #expect(items[3].sortOrder == 3000)
+    }
+
+    @Test("Move item to same index does nothing")
+    func moveItemToSameIndexDoesNothing() async throws {
+        let (store, itemIDs) = try makeTestStoreWithItems(count: 3)
+        let originalItems = try store.fetchItems().filter { !$0.isCompleted }
+
+        try store.moveItem(itemID: itemIDs[1], toIndex: 1)
+
+        let items = try store.fetchItems().filter { !$0.isCompleted }
+        #expect(items[0].id == originalItems[0].id)
+        #expect(items[1].id == originalItems[1].id)
+        #expect(items[2].id == originalItems[2].id)
+    }
+
+    // MARK: - Invalid Input Tests
+
+    @Test("Move with invalid ID does nothing")
+    func moveWithInvalidIDDoesNothing() async throws {
+        let (store, itemIDs) = try makeTestStoreWithItems(count: 3)
+        let originalItems = try store.fetchItems().filter { !$0.isCompleted }
+        let invalidID = UUID()
+
+        try store.moveItem(itemID: invalidID, toIndex: 0)
+
+        let items = try store.fetchItems().filter { !$0.isCompleted }
+        #expect(items[0].id == originalItems[0].id)
+        #expect(items[1].id == originalItems[1].id)
+        #expect(items[2].id == originalItems[2].id)
+    }
+
+    @Test("Move to negative index clamps to 0")
+    func moveToNegativeIndexClampsToZero() async throws {
+        let (store, itemIDs) = try makeTestStoreWithItems(count: 3)
+
+        try store.moveItem(itemID: itemIDs[2], toIndex: -5)
+
+        let items = try store.fetchItems().filter { !$0.isCompleted }
+        #expect(items[0].id == itemIDs[2])
+    }
+
+    @Test("Move to out-of-bounds index clamps to end")
+    func moveToOutOfBoundsIndexClampsToEnd() async throws {
+        let (store, itemIDs) = try makeTestStoreWithItems(count: 3)
+
+        try store.moveItem(itemID: itemIDs[0], toIndex: 999)
+
+        let items = try store.fetchItems().filter { !$0.isCompleted }
+        #expect(items[2].id == itemIDs[0])
+    }
+
+    // MARK: - Completed Task Tests
+
+    @Test("Moving only affects active items")
+    func movingOnlyAffectsActiveItems() async throws {
+        let (store, itemIDs) = try makeTestStoreWithItems(count: 4)
+        try store.complete(itemID: itemIDs[3])
+
+        try store.moveItem(itemID: itemIDs[0], toIndex: 2)
+
+        let allItems = try store.fetchItems()
+        let activeItems = allItems.filter { !$0.isCompleted }
+        let completedItems = allItems.filter { $0.isCompleted }
+
+        #expect(activeItems.count == 3)
+        #expect(completedItems.count == 1)
+        #expect(completedItems[0].id == itemIDs[3])
+    }
+
+    @Test("Moving completed item does nothing")
+    func movingCompletedItemDoesNothing() async throws {
+        let (store, itemIDs) = try makeTestStoreWithItems(count: 3)
+        try store.complete(itemID: itemIDs[0])
+        let originalItems = try store.fetchItems()
+
+        try store.moveItem(itemID: itemIDs[0], toIndex: 1)
+
+        let items = try store.fetchItems()
+        #expect(items[0].id == originalItems[0].id)
+        #expect(items[1].id == originalItems[1].id)
+        #expect(items[2].id == originalItems[2].id)
+    }
+
+    // MARK: - Edge Cases
+
+    @Test("Move single item does nothing")
+    func moveSingleItemDoesNothing() async throws {
+        let store = makeTestStore()
+        let item = try store.createItem(title: "Only item")
+
+        try store.moveItem(itemID: item.id, toIndex: 0)
+
+        let items = try store.fetchItems()
+        #expect(items.count == 1)
+        #expect(items[0].id == item.id)
+    }
+
+    @Test("Move in empty store does nothing")
+    func moveInEmptyStoreDoesNothing() async throws {
+        let store = makeTestStore()
+        let randomID = UUID()
+
+        try store.moveItem(itemID: randomID, toIndex: 0)
+
+        let items = try store.fetchItems()
+        #expect(items.isEmpty)
+    }
+
+    @Test("Multiple moves maintain order")
+    func multipleMoveMaintainOrder() async throws {
+        let (store, itemIDs) = try makeTestStoreWithItems(count: 4)
+
+        try store.moveItem(itemID: itemIDs[0], toIndex: 3)
+        try store.moveItem(itemID: itemIDs[2], toIndex: 0)
+
+        let items = try store.fetchItems().filter { !$0.isCompleted }
+        #expect(items[0].id == itemIDs[2])
+        #expect(items[3].id == itemIDs[0])
+    }
+}

diff --git a/Tests/Unit/ItemStoreTests.swift b/Tests/Unit/ItemStoreTests.swift

@@ -0,0 +1,208 @@
+import Foundation
+import Testing
+
+#if os(macOS)
+@testable import Listless
+#else
+@testable import Listless_iOS
+#endif
+
+@Suite("ItemStore CRUD Operations", .serialized)
+@MainActor
+struct ItemStoreTests {
+
+    // MARK: - Creation Tests
+
+    @Test("Create item with empty title")
+    func createItemWithEmptyTitle() async throws {
+        let store = makeTestStore()
+
+        let item = try store.createItem()
+
+        #expect(item.title == "")
+        #expect(item.id != UUID(uuidString: "00000000-0000-0000-0000-000000000000")!)
+        #expect(item.isCompleted == false)
+        #expect(item.createdAt.timeIntervalSinceNow > -1.0)
+    }
+
+    @Test("Create item with title")
+    func createItemWithTitle() async throws {
+        let store = makeTestStore()
+
+        let item = try store.createItem(title: "Buy groceries")
+
+        #expect(item.title == "Buy groceries")
+        #expect(item.id != UUID(uuidString: "00000000-0000-0000-0000-000000000000")!)
+    }
+
+    @Test("Create multiple items with unique IDs")
+    func createMultipleItemsWithUniqueIDs() async throws {
+        let store = makeTestStore()
+
+        let item1 = try store.createItem(title: "Item 1")
+        let item2 = try store.createItem(title: "Item 2")
+        let item3 = try store.createItem(title: "Item 3")
+
+        #expect(item1.id != item2.id)
+        #expect(item2.id != item3.id)
+        #expect(item1.id != item3.id)
+    }
+
+    @Test("Created item has timestamps")
+    func createdItemHasTimestamps() async throws {
+        let store = makeTestStore()
+
+        let beforeCreate = Date()
+        let item = try store.createItem(title: "Test")
+        let afterCreate = Date()
+
+        #expect(item.createdAt >= beforeCreate)
+        #expect(item.createdAt <= afterCreate)
+        #expect(item.updatedAt >= beforeCreate)
+        #expect(item.updatedAt <= afterCreate)
+    }
+
+    @Test("Create item at beginning prepends to active items")
+    func createItemAtBeginningPrepends() async throws {
+        let store = makeTestStore()
+
+        let first = try store.createItem(title: "First")
+        let second = try store.createItem(title: "Second")
+        let prepended = try store.createItem(title: "Prepended", atBeginning: true)
+
+        let items = try store.fetchItems().filter { !$0.isCompleted }
+
+        #expect(items.map(\.title) == ["Prepended", "First", "Second"])
+        #expect(prepended.sortOrder < first.sortOrder)
+        #expect(first.sortOrder < second.sortOrder)
+    }
+
+    // MARK: - Fetch Tests
+
+    @Test("Fetch items from empty store")
+    func fetchItemsFromEmptyStore() async throws {
+        let store = makeTestStore()
+
+        let items = try store.fetchItems()
+
+        #expect(items.isEmpty)
+    }
+
+    @Test("Fetch items returns created items")
+    func fetchItemsReturnsCreatedItems() async throws {
+        let store = makeTestStore()
+        _ = try store.createItem(title: "Item 1")
+        _ = try store.createItem(title: "Item 2")
+
+        let items = try store.fetchItems()
+
+        #expect(items.count == 2)
+        #expect(items[0].title == "Item 1")
+        #expect(items[1].title == "Item 2")
+    }
+
+    // MARK: - Update Tests
+
+    @Test("Update item title")
+    func updateItemTitle() async throws {
+        let store = makeTestStore()
+        let item = try store.createItem(title: "Original")
+
+        try store.update(itemID: item.id, title: "Updated")
+
+        let items = try store.fetchItems()
+        #expect(items.first?.title == "Updated")
+    }
+
+    @Test("Update item title without saving")
+    func updateItemTitleWithoutSaving() async throws {
+        let store = makeTestStore()
+        let item = try store.createItem(title: "Original")
+
+        try store.updateWithoutSaving(itemID: item.id, title: "Updated")
+
+        let items = try store.fetchItems()
+        #expect(items.first?.title == "Updated")
+    }
+
+    @Test("Update with invalid ID does nothing")
+    func updateWithInvalidIDDoesNothing() async throws {
+        let store = makeTestStore()
+        _ = try store.createItem(title: "Item 1")
+        let invalidID = UUID()
+
+        try store.update(itemID: invalidID, title: "Should not exist")
+
+        let items = try store.fetchItems()
+        #expect(items.count == 1)
+        #expect(items.first?.title == "Item 1")
+    }
+
+    // MARK: - Delete Tests
+
+    @Test("Delete item")
+    func deleteItem() async throws {
+        let store = makeTestStore()
+        let item1 = try store.createItem(title: "Item 1")
+        let item2 = try store.createItem(title: "Item 2")
+
+        try store.delete(itemID: item1.id)
+
+        let items = try store.fetchItems()
+        #expect(items.count == 1)
+        #expect(items.first?.id == item2.id)
+    }
+
+    @Test("Delete all items")
+    func deleteAllItems() async throws {
+        let store = makeTestStore()
+        let item1 = try store.createItem(title: "Item 1")
+        let item2 = try store.createItem(title: "Item 2")
+
+        try store.delete(itemID: item1.id)
+        try store.delete(itemID: item2.id)
+
+        let items = try store.fetchItems()
+        #expect(items.isEmpty)
+    }
+
+    @Test("Delete with invalid ID does nothing")
+    func deleteWithInvalidIDDoesNothing() async throws {
+        let store = makeTestStore()
+        _ = try store.createItem(title: "Item 1")
+        let invalidID = UUID()
+
+        try store.delete(itemID: invalidID)
+
+        let items = try store.fetchItems()
+        #expect(items.count == 1)
+    }
+
+    // MARK: - Edge Cases
+
+    @Test("Task IDs persist across fetches")
+    func itemIDsPersistAcrossFetches() async throws {
+        let store = makeTestStore()
+        let item = try store.createItem(title: "Test")
+        let originalID = item.id
+
+        let fetchedItems = try store.fetchItems()
+        let fetchedID = fetchedItems.first?.id
+
+        #expect(fetchedID == originalID)
+    }
+
+    @Test("Create item increments sortOrder")
+    func createItemIncrementsSortOrder() async throws {
+        let store = makeTestStore()
+
+        let item1 = try store.createItem(title: "Item 1")
+        let item2 = try store.createItem(title: "Item 2")
+        let item3 = try store.createItem(title: "Item 3")
+
+        #expect(item2.sortOrder > item1.sortOrder)
+        #expect(item3.sortOrder > item2.sortOrder)
+        #expect(item2.sortOrder - item1.sortOrder == 1000)
+        #expect(item3.sortOrder - item2.sortOrder == 1000)
+    }
+}

diff --git a/Tests/Unit/TaskStoreCompletionTests.swift b/Tests/Unit/TaskStoreCompletionTests.swift

@@ -1,189 +0,0 @@
-import Foundation
-import Testing
-
-#if os(macOS)
-@testable import Listless
-#else
-@testable import Listless_iOS
-#endif
-
-@Suite("TaskStore Completion Behavior", .serialized)
-@MainActor
-struct TaskStoreCompletionTests {
-
-    // MARK: - Basic Completion Tests
-
-    @Test("Complete task")
-    func completeTask() async throws {
-        let store = makeTestStore()
-        let task = try store.createTask(title: "Task to complete")
-
-        try store.complete(taskID: task.id)
-
-        let tasks = try store.fetchTasks()
-        #expect(tasks.first?.isCompleted == true)
-    }
-
-    @Test("Uncomplete task")
-    func uncompleteTask() async throws {
-        let store = makeTestStore()
-        let task = try store.createTask(title: "Task")
-        try store.complete(taskID: task.id)
-
-        try store.uncomplete(taskID: task.id)
-
-        let tasks = try store.fetchTasks()
-        #expect(tasks.first?.isCompleted == false)
-    }
-
-    @Test("Complete with invalid ID does nothing")
-    func completeWithInvalidIDDoesNothing() async throws {
-        let store = makeTestStore()
-        let task = try store.createTask(title: "Task")
-        let invalidID = UUID()
-
-        try store.complete(taskID: invalidID)
-
-        let tasks = try store.fetchTasks()
-        #expect(tasks.first?.isCompleted == false)
-    }
-
-    @Test("Uncomplete with invalid ID does nothing")
-    func uncompleteWithInvalidIDDoesNothing() async throws {
-        let store = makeTestStore()
-        let task = try store.createTask(title: "Task")
-        try store.complete(taskID: task.id)
-        let invalidID = UUID()
-
-        try store.uncomplete(taskID: invalidID)
-
-        let tasks = try store.fetchTasks()
-        #expect(tasks.first?.isCompleted == true)
-    }
-
-    // MARK: - Timestamp Tests
-
-    @Test("Completing task updates timestamp")
-    func completingTaskUpdatesTimestamp() async throws {
-        let store = makeTestStore()
-        let task = try store.createTask(title: "Task")
-        let originalUpdatedAt = task.updatedAt
-
-        // Small delay to ensure timestamp difference
-        try await Task.sleep(nanoseconds: 10_000_000) // 10ms
-
-        try store.complete(taskID: task.id)
-
-        let tasks = try store.fetchTasks()
-        let updatedTask = tasks.first
-        #expect(updatedTask?.updatedAt ?? Date() > originalUpdatedAt)
-    }
-
-    // MARK: - Sorting Tests
-
-    @Test("Active tasks appear before completed tasks")
-    func activeTasksAppearBeforeCompletedTasks() async throws {
-        let store = makeTestStore()
-        let task1 = try store.createTask(title: "Task 1")
-        let task2 = try store.createTask(title: "Task 2")
-        let task3 = try store.createTask(title: "Task 3")
-
-        try store.complete(taskID: task2.id)
-
-        let tasks = try store.fetchTasks()
-        #expect(tasks[0].id == task1.id)
-        #expect(tasks[1].id == task3.id)
-        #expect(tasks[2].id == task2.id)
-    }
-
-    @Test("Completed tasks sorted by completedOrder")
-    func completedTasksSortedByCompletedOrder() async throws {
-        let store = makeTestStore()
-        let task1 = try store.createTask(title: "Task 1")
-        let task2 = try store.createTask(title: "Task 2")
-        let task3 = try store.createTask(title: "Task 3")
-
-        // Complete in specific order
-        try store.complete(taskID: task2.id)
-        try store.complete(taskID: task1.id)
-        try store.complete(taskID: task3.id)
-
-        let tasks = try store.fetchTasks()
-        // All completed, should be sorted by completedOrder (most recently completed first)
-        #expect(tasks[0].id == task3.id)
-        #expect(tasks[1].id == task1.id)
-        #expect(tasks[2].id == task2.id)
-    }
-
-    @Test("Toggle completion multiple times")
-    func toggleCompletionMultipleTimes() async throws {
-        let store = makeTestStore()
-        let task = try store.createTask(title: "Task")
-
-        try store.complete(taskID: task.id)
-        var tasks = try store.fetchTasks()
-        #expect(tasks.first?.isCompleted == true)
-
-        try store.uncomplete(taskID: task.id)
-        tasks = try store.fetchTasks()
-        #expect(tasks.first?.isCompleted == false)
-
-        try store.complete(taskID: task.id)
-        tasks = try store.fetchTasks()
-        #expect(tasks.first?.isCompleted == true)
-    }
-
-    @Test("Complete all tasks")
-    func completeAllTasks() async throws {
-        let (store, taskIDs) = try makeTestStoreWithTasks(count: 5)
-
-        for id in taskIDs {
-            try store.complete(taskID: id)
-        }
-
-        let tasks = try store.fetchTasks()
-        #expect(tasks.allSatisfy { $0.isCompleted })
-        #expect(tasks.count == 5)
-    }
-
-    @Test("Uncomplete restores previous sortOrder when no active conflict")
-    func uncompleteRestoresPreviousSortOrderWhenNoConflict() async throws {
-        let (store, taskIDs) = try makeTestStoreWithTasks(count: 3)
-        let taskToRestoreID = taskIDs[1]
-
-        let originalSortOrder = try store.fetchTasks().first { $0.id == taskToRestoreID }?.sortOrder
-        #expect(originalSortOrder != nil)
-
-        try store.complete(taskID: taskToRestoreID)
-        try store.uncomplete(taskID: taskToRestoreID)
-
-        let activeTasks = try store.fetchTasks().filter { !$0.isCompleted }
-        let restoredTask = activeTasks.first { $0.id == taskToRestoreID }
-
-        #expect(restoredTask != nil)
-        #expect(restoredTask?.sortOrder == originalSortOrder)
-        #expect(activeTasks.count == 3)
-    }
-
-    @Test("Uncomplete appends task when restored sortOrder conflicts with active task")
-    func uncompleteAppendsWhenRestoredSortOrderConflicts() async throws {
-        let store = makeTestStore()
-        let activeTask = try store.createTask(title: "Active task")
-        let completedTask = try store.createTask(title: "Completed task")
-
-        try store.complete(taskID: completedTask.id)
-        try store.moveTask(taskID: activeTask.id, toIndex: 0)
-        completedTask.sortOrder = activeTask.sortOrder
-        try store.save()
-
-        try store.uncomplete(taskID: completedTask.id)
-
-        let activeTasks = try store.fetchTasks().filter { !$0.isCompleted }
-            .sorted { $0.sortOrder < $1.sortOrder }
-        let lastActiveTask = activeTasks.last
-
-        #expect(activeTasks.count == 2)
-        #expect(lastActiveTask?.id == completedTask.id)
-        #expect(lastActiveTask?.sortOrder ?? 0 > activeTask.sortOrder)
-    }
-}

diff --git a/Tests/Unit/TaskStoreEdgeCaseTests.swift b/Tests/Unit/TaskStoreEdgeCaseTests.swift

@@ -1,285 +0,0 @@
-import Foundation
-import CoreData
-import Testing
-
-#if os(macOS)
-@testable import Listless
-#else
-@testable import Listless_iOS
-#endif
-
-@Suite("TaskStore Edge Cases", .serialized)
-@MainActor
-struct TaskStoreEdgeCaseTests {
-
-    // MARK: - Title Edge Cases
-
-    @Test("Task with empty title")
-    func taskWithEmptyTitle() async throws {
-        let store = makeTestStore()
-
-        let task = try store.createTask(title: "")
-
-        let tasks = try store.fetchTasks()
-        #expect(tasks.first?.title == "")
-    }
-
-    @Test("Task with very long title")
-    func taskWithVeryLongTitle() async throws {
-        let store = makeTestStore()
-        let longTitle = String(repeating: "A", count: 10_000)
-
-        let task = try store.createTask(title: longTitle)
-
-        let tasks = try store.fetchTasks()
-        #expect(tasks.first?.title.count == 10_000)
-    }
-
-    @Test("Task with special characters")
-    func taskWithSpecialCharacters() async throws {
-        let store = makeTestStore()
-        let specialTitle = "Test 🎉 with émojis & spëcial çharacters! @#$%^&*()"
-
-        let task = try store.createTask(title: specialTitle)
-
-        let tasks = try store.fetchTasks()
-        #expect(tasks.first?.title == specialTitle)
-    }
-
-    @Test("Task with newlines and tabs")
-    func taskWithNewlinesAndTabs() async throws {
-        let store = makeTestStore()
-        let multilineTitle = "Line 1\nLine 2\tTabbed"
-
-        let task = try store.createTask(title: multilineTitle)
-
-        let tasks = try store.fetchTasks()
-        #expect(tasks.first?.title == multilineTitle)
-    }
-
-    // MARK: - Large Data Sets
-
-    @Test("Create many tasks")
-    func createManyTasks() async throws {
-        let store = makeTestStore()
-        let count = 100
-
-        for i in 0..<count {
-            _ = try store.createTask(title: "Task \(i)")
-        }
-
-        let tasks = try store.fetchTasks()
-        #expect(tasks.count == count)
-    }
-
-    @Test("Delete all tasks from large set")
-    func deleteAllTasksFromLargeSet() async throws {
-        let store = makeTestStore()
-        var taskIDs: [UUID] = []
-
-        for i in 0..<50 {
-            let task = try store.createTask(title: "Task \(i)")
-            taskIDs.append(task.id)
-        }
-
-        for id in taskIDs {
-            try store.delete(taskID: id)
-        }
-
-        let tasks = try store.fetchTasks()
-        #expect(tasks.isEmpty)
-    }
-
-    // MARK: - State Transitions
-
-    @Test("Create task after completing all tasks")
-    func createTaskAfterCompletingAllTasks() async throws {
-        let (store, taskIDs) = try makeTestStoreWithTasks(count: 3)
-
-        for id in taskIDs {
-            try store.complete(taskID: id)
-        }
-
-        let newTask = try store.createTask(title: "New task")
-
-        let tasks = try store.fetchTasks()
-        let activeTasks = tasks.filter { !$0.isCompleted }
-        #expect(activeTasks.count == 1)
-        #expect(activeTasks[0].id == newTask.id)
-    }
-
-    @Test("Rapid updates to same task")
-    func rapidUpdatesToSameTask() async throws {
-        let store = makeTestStore()
-        let task = try store.createTask(title: "Original")
-
-        for i in 0..<10 {
-            try store.update(taskID: task.id, title: "Update \(i)")
-        }
-
-        let tasks = try store.fetchTasks()
-        #expect(tasks.first?.title == "Update 9")
-    }
-
-    // MARK: - Store State Tests
-
-    @Test("Store with only completed tasks")
-    func storeWithOnlyCompletedTasks() async throws {
-        let (store, taskIDs) = try makeTestStoreWithTasks(count: 5)
-
-        for id in taskIDs {
-            try store.complete(taskID: id)
-        }
-
-        let tasks = try store.fetchTasks()
-        #expect(tasks.allSatisfy { $0.isCompleted })
-        #expect(tasks.count == 5)
-    }
-
-    @Test("SortOrder after completing all tasks")
-    func sortOrderAfterCompletingAllTasks() async throws {
-        let (store, taskIDs) = try makeTestStoreWithTasks(count: 3)
-
-        for id in taskIDs {
-            try store.complete(taskID: id)
-        }
-
-        let newTask1 = try store.createTask(title: "New 1")
-        let newTask2 = try store.createTask(title: "New 2")
-
-        let activeTasks = try store.fetchTasks().filter { !$0.isCompleted }
-        #expect(activeTasks[0].id == newTask1.id)
-        #expect(activeTasks[1].id == newTask2.id)
-        #expect(activeTasks[1].sortOrder > activeTasks[0].sortOrder)
-    }
-
-    @Test("Uncompleting task moves it back to active")
-    func uncompletingTaskMovesItBackToActive() async throws {
-        let (store, taskIDs) = try makeTestStoreWithTasks(count: 3)
-        try store.complete(taskID: taskIDs[1])
-
-        try store.uncomplete(taskID: taskIDs[1])
-
-        let tasks = try store.fetchTasks()
-        let activeTasks = tasks.filter { !$0.isCompleted }
-        #expect(activeTasks.count == 3)
-        #expect(activeTasks.contains { $0.id == taskIDs[1] })
-    }
-
-    @Test("Uncomplete legacy sortOrder zero conflict appends to end")
-    func uncompleteLegacyZeroConflictAppendsToEnd() async throws {
-        let store = makeTestStore()
-        let activeTask = try store.createTask(title: "Active")
-        let completedTask = try store.createTask(title: "Completed")
-
-        activeTask.sortOrder = 0
-        try store.complete(taskID: completedTask.id)
-        completedTask.sortOrder = 0
-        try store.save()
-
-        try store.uncomplete(taskID: completedTask.id)
-
-        let activeTasks = try store.fetchTasks().filter { !$0.isCompleted }
-            .sorted { $0.sortOrder < $1.sortOrder }
-        #expect(activeTasks.count == 2)
-        #expect(activeTasks[0].id == activeTask.id)
-        #expect(activeTasks[1].id == completedTask.id)
-        #expect(activeTasks[1].sortOrder > activeTasks[0].sortOrder)
-    }
-
-    @Test("Merge policy prefers store when store updatedAt is newer")
-    func mergePolicyPrefersStoreWhenStoreIsNewer() async throws {
-        let controller = PersistenceController(inMemory: true)
-        let viewContext = controller.viewContext
-        viewContext.automaticallyMergesChangesFromParent = false
-
-        let task = TaskItem(context: viewContext)
-        task.title = "Original"
-        task.updatedAt = Date(timeIntervalSince1970: 100)
-        try viewContext.save()
-
-        let taskID = task.id
-
-        let backgroundContext = controller.container.newBackgroundContext()
-        try await backgroundContext.perform {
-            let request = TaskItem.fetchRequest()
-            request.predicate = NSPredicate(format: "id == %@", taskID as CVarArg)
-            request.fetchLimit = 1
-
-            guard let remote = try backgroundContext.fetch(request).first else {
-                Issue.record("Expected task to exist in background context")
-                return
-            }
-
-            remote.title = "Remote newer"
-            remote.updatedAt = Date(timeIntervalSince1970: 200)
-            try backgroundContext.save()
-        }
-
-        task.updatedAt = Date(timeIntervalSince1970: 150)
-        try viewContext.save()
-
-        let verifyContext = controller.container.newBackgroundContext()
-        try await verifyContext.perform {
-            let request = TaskItem.fetchRequest()
-            request.predicate = NSPredicate(format: "id == %@", taskID as CVarArg)
-            request.fetchLimit = 1
-
-            guard let resolved = try verifyContext.fetch(request).first else {
-                Issue.record("Expected task to exist in verify context")
-                return
-            }
-
-            #expect(resolved.title == "Remote newer")
-            #expect(resolved.updatedAt == Date(timeIntervalSince1970: 200))
-        }
-    }
-
-    @Test("Merge policy prefers local when local updatedAt is newer")
-    func mergePolicyPrefersLocalWhenLocalIsNewer() async throws {
-        let controller = PersistenceController(inMemory: true)
-        let viewContext = controller.viewContext
-        viewContext.automaticallyMergesChangesFromParent = false
-
-        let task = TaskItem(context: viewContext)
-        task.title = "Original"
-        task.updatedAt = Date(timeIntervalSince1970: 100)
-        try viewContext.save()
-
-        let taskID = task.id
-
-        let backgroundContext = controller.container.newBackgroundContext()
-        try await backgroundContext.perform {
-            let request = TaskItem.fetchRequest()
-            request.predicate = NSPredicate(format: "id == %@", taskID as CVarArg)
-            request.fetchLimit = 1
-
-            guard let remote = try backgroundContext.fetch(request).first else {
-                Issue.record("Expected task to exist in background context")
-                return
-            }
-
-            remote.title = "Remote older"
-            remote.updatedAt = Date(timeIntervalSince1970: 200)
-            try backgroundContext.save()
-        }
-
-        task.updatedAt = Date(timeIntervalSince1970: 300)
-        try viewContext.save()
-
-        let verifyContext = controller.container.newBackgroundContext()
-        try await verifyContext.perform {
-            let request = TaskItem.fetchRequest()
-            request.predicate = NSPredicate(format: "id == %@", taskID as CVarArg)
-            request.fetchLimit = 1
-
-            guard let resolved = try verifyContext.fetch(request).first else {
-                Issue.record("Expected task to exist in verify context")
-                return
-            }
-
-            #expect(resolved.title == "Original")
-            #expect(resolved.updatedAt == Date(timeIntervalSince1970: 300))
-        }
-    }
-}

diff --git a/Tests/Unit/TaskStoreOrderingTests.swift b/Tests/Unit/TaskStoreOrderingTests.swift

@@ -1,190 +0,0 @@
-import Foundation
-import Testing
-
-#if os(macOS)
-@testable import Listless
-#else
-@testable import Listless_iOS
-#endif
-
-@Suite("TaskStore Task Reordering", .serialized)
-@MainActor
-struct TaskStoreOrderingTests {
-
-    // MARK: - Initial State Tests
-
-    @Test("Initial sortOrder has 1000-point gaps")
-    func initialSortOrderHasThousandPointGaps() async throws {
-        let store = makeTestStore()
-
-        let task1 = try store.createTask(title: "Task 1")
-        let task2 = try store.createTask(title: "Task 2")
-        let task3 = try store.createTask(title: "Task 3")
-
-        let tasks = try store.fetchTasks()
-
-        // All tasks are active, so they should be the first 3
-        #expect(tasks.count == 3)
-
-        // Verify tasks are in ascending order
-        #expect(tasks[0].sortOrder < tasks[1].sortOrder)
-        #expect(tasks[1].sortOrder < tasks[2].sortOrder)
-
-        // Verify 1000-point gaps between tasks
-        #expect(tasks[1].sortOrder - tasks[0].sortOrder == 1000)
-        #expect(tasks[2].sortOrder - tasks[1].sortOrder == 1000)
-    }
-
-    // MARK: - Move Tests (Parameterized)
-
-    @Test("Move task to different positions", arguments: [
-        (from: 0, to: 2),
-        (from: 2, to: 0),
-        (from: 0, to: 1),
-        (from: 1, to: 0),
-        (from: 1, to: 2),
-        (from: 2, to: 1),
-    ])
-    func moveTaskToDifferentPositions(from: Int, to: Int) async throws {
-        let (store, taskIDs) = try makeTestStoreWithTasks(count: 3)
-        let taskToMove = taskIDs[from]
-
-        try store.moveTask(taskID: taskToMove, toIndex: to)
-
-        let tasks = try store.fetchTasks().filter { !$0.isCompleted }
-        #expect(tasks[to].id == taskToMove)
-    }
-
-    // MARK: - Order Preservation Tests
-
-    @Test("Moving maintains 1000-point gaps")
-    func movingMaintainsThousandPointGaps() async throws {
-        let (store, taskIDs) = try makeTestStoreWithTasks(count: 4)
-
-        try store.moveTask(taskID: taskIDs[0], toIndex: 2)
-
-        let tasks = try store.fetchTasks().filter { !$0.isCompleted }
-        #expect(tasks[0].sortOrder == 0)
-        #expect(tasks[1].sortOrder == 1000)
-        #expect(tasks[2].sortOrder == 2000)
-        #expect(tasks[3].sortOrder == 3000)
-    }
-
-    @Test("Move task to same index does nothing")
-    func moveTaskToSameIndexDoesNothing() async throws {
-        let (store, taskIDs) = try makeTestStoreWithTasks(count: 3)
-        let originalTasks = try store.fetchTasks().filter { !$0.isCompleted }
-
-        try store.moveTask(taskID: taskIDs[1], toIndex: 1)
-
-        let tasks = try store.fetchTasks().filter { !$0.isCompleted }
-        #expect(tasks[0].id == originalTasks[0].id)
-        #expect(tasks[1].id == originalTasks[1].id)
-        #expect(tasks[2].id == originalTasks[2].id)
-    }
-
-    // MARK: - Invalid Input Tests
-
-    @Test("Move with invalid ID does nothing")
-    func moveWithInvalidIDDoesNothing() async throws {
-        let (store, taskIDs) = try makeTestStoreWithTasks(count: 3)
-        let originalTasks = try store.fetchTasks().filter { !$0.isCompleted }
-        let invalidID = UUID()
-
-        try store.moveTask(taskID: invalidID, toIndex: 0)
-
-        let tasks = try store.fetchTasks().filter { !$0.isCompleted }
-        #expect(tasks[0].id == originalTasks[0].id)
-        #expect(tasks[1].id == originalTasks[1].id)
-        #expect(tasks[2].id == originalTasks[2].id)
-    }
-
-    @Test("Move to negative index clamps to 0")
-    func moveToNegativeIndexClampsToZero() async throws {
-        let (store, taskIDs) = try makeTestStoreWithTasks(count: 3)
-
-        try store.moveTask(taskID: taskIDs[2], toIndex: -5)
-
-        let tasks = try store.fetchTasks().filter { !$0.isCompleted }
-        #expect(tasks[0].id == taskIDs[2])
-    }
-
-    @Test("Move to out-of-bounds index clamps to end")
-    func moveToOutOfBoundsIndexClampsToEnd() async throws {
-        let (store, taskIDs) = try makeTestStoreWithTasks(count: 3)
-
-        try store.moveTask(taskID: taskIDs[0], toIndex: 999)
-
-        let tasks = try store.fetchTasks().filter { !$0.isCompleted }
-        #expect(tasks[2].id == taskIDs[0])
-    }
-
-    // MARK: - Completed Task Tests
-
-    @Test("Moving only affects active tasks")
-    func movingOnlyAffectsActiveTasks() async throws {
-        let (store, taskIDs) = try makeTestStoreWithTasks(count: 4)
-        try store.complete(taskID: taskIDs[3])
-
-        try store.moveTask(taskID: taskIDs[0], toIndex: 2)
-
-        let allTasks = try store.fetchTasks()
-        let activeTasks = allTasks.filter { !$0.isCompleted }
-        let completedTasks = allTasks.filter { $0.isCompleted }
-
-        #expect(activeTasks.count == 3)
-        #expect(completedTasks.count == 1)
-        #expect(completedTasks[0].id == taskIDs[3])
-    }
-
-    @Test("Moving completed task does nothing")
-    func movingCompletedTaskDoesNothing() async throws {
-        let (store, taskIDs) = try makeTestStoreWithTasks(count: 3)
-        try store.complete(taskID: taskIDs[0])
-        let originalTasks = try store.fetchTasks()
-
-        try store.moveTask(taskID: taskIDs[0], toIndex: 1)
-
-        let tasks = try store.fetchTasks()
-        #expect(tasks[0].id == originalTasks[0].id)
-        #expect(tasks[1].id == originalTasks[1].id)
-        #expect(tasks[2].id == originalTasks[2].id)
-    }
-
-    // MARK: - Edge Cases
-
-    @Test("Move single task does nothing")
-    func moveSingleTaskDoesNothing() async throws {
-        let store = makeTestStore()
-        let task = try store.createTask(title: "Only task")
-
-        try store.moveTask(taskID: task.id, toIndex: 0)
-
-        let tasks = try store.fetchTasks()
-        #expect(tasks.count == 1)
-        #expect(tasks[0].id == task.id)
-    }
-
-    @Test("Move in empty store does nothing")
-    func moveInEmptyStoreDoesNothing() async throws {
-        let store = makeTestStore()
-        let randomID = UUID()
-
-        try store.moveTask(taskID: randomID, toIndex: 0)
-
-        let tasks = try store.fetchTasks()
-        #expect(tasks.isEmpty)
-    }
-
-    @Test("Multiple moves maintain order")
-    func multipleMoveMaintainOrder() async throws {
-        let (store, taskIDs) = try makeTestStoreWithTasks(count: 4)
-
-        try store.moveTask(taskID: taskIDs[0], toIndex: 3)
-        try store.moveTask(taskID: taskIDs[2], toIndex: 0)
-
-        let tasks = try store.fetchTasks().filter { !$0.isCompleted }
-        #expect(tasks[0].id == taskIDs[2])
-        #expect(tasks[3].id == taskIDs[0])
-    }
-}

diff --git a/Tests/Unit/TaskStoreTests.swift b/Tests/Unit/TaskStoreTests.swift

@@ -1,208 +0,0 @@
-import Foundation
-import Testing
-
-#if os(macOS)
-@testable import Listless
-#else
-@testable import Listless_iOS
-#endif
-
-@Suite("TaskStore CRUD Operations", .serialized)
-@MainActor
-struct TaskStoreTests {
-
-    // MARK: - Creation Tests
-
-    @Test("Create task with empty title")
-    func createTaskWithEmptyTitle() async throws {
-        let store = makeTestStore()
-
-        let task = try store.createTask()
-
-        #expect(task.title == "")
-        #expect(task.id != UUID(uuidString: "00000000-0000-0000-0000-000000000000")!)
-        #expect(task.isCompleted == false)
-        #expect(task.createdAt.timeIntervalSinceNow > -1.0)
-    }
-
-    @Test("Create task with title")
-    func createTaskWithTitle() async throws {
-        let store = makeTestStore()
-
-        let task = try store.createTask(title: "Buy groceries")
-
-        #expect(task.title == "Buy groceries")
-        #expect(task.id != UUID(uuidString: "00000000-0000-0000-0000-000000000000")!)
-    }
-
-    @Test("Create multiple tasks with unique IDs")
-    func createMultipleTasksWithUniqueIDs() async throws {
-        let store = makeTestStore()
-
-        let task1 = try store.createTask(title: "Task 1")
-        let task2 = try store.createTask(title: "Task 2")
-        let task3 = try store.createTask(title: "Task 3")
-
-        #expect(task1.id != task2.id)
-        #expect(task2.id != task3.id)
-        #expect(task1.id != task3.id)
-    }
-
-    @Test("Created task has timestamps")
-    func createdTaskHasTimestamps() async throws {
-        let store = makeTestStore()
-
-        let beforeCreate = Date()
-        let task = try store.createTask(title: "Test")
-        let afterCreate = Date()
-
-        #expect(task.createdAt >= beforeCreate)
-        #expect(task.createdAt <= afterCreate)
-        #expect(task.updatedAt >= beforeCreate)
-        #expect(task.updatedAt <= afterCreate)
-    }
-
-    @Test("Create task at beginning prepends to active tasks")
-    func createTaskAtBeginningPrepends() async throws {
-        let store = makeTestStore()
-
-        let first = try store.createTask(title: "First")
-        let second = try store.createTask(title: "Second")
-        let prepended = try store.createTask(title: "Prepended", atBeginning: true)
-
-        let tasks = try store.fetchTasks().filter { !$0.isCompleted }
-
-        #expect(tasks.map(\.title) == ["Prepended", "First", "Second"])
-        #expect(prepended.sortOrder < first.sortOrder)
-        #expect(first.sortOrder < second.sortOrder)
-    }
-
-    // MARK: - Fetch Tests
-
-    @Test("Fetch tasks from empty store")
-    func fetchTasksFromEmptyStore() async throws {
-        let store = makeTestStore()
-
-        let tasks = try store.fetchTasks()
-
-        #expect(tasks.isEmpty)
-    }
-
-    @Test("Fetch tasks returns created tasks")
-    func fetchTasksReturnsCreatedTasks() async throws {
-        let store = makeTestStore()
-        _ = try store.createTask(title: "Task 1")
-        _ = try store.createTask(title: "Task 2")
-
-        let tasks = try store.fetchTasks()
-
-        #expect(tasks.count == 2)
-        #expect(tasks[0].title == "Task 1")
-        #expect(tasks[1].title == "Task 2")
-    }
-
-    // MARK: - Update Tests
-
-    @Test("Update task title")
-    func updateTaskTitle() async throws {
-        let store = makeTestStore()
-        let task = try store.createTask(title: "Original")
-
-        try store.update(taskID: task.id, title: "Updated")
-
-        let tasks = try store.fetchTasks()
-        #expect(tasks.first?.title == "Updated")
-    }
-
-    @Test("Update task title without saving")
-    func updateTaskTitleWithoutSaving() async throws {
-        let store = makeTestStore()
-        let task = try store.createTask(title: "Original")
-
-        try store.updateWithoutSaving(taskID: task.id, title: "Updated")
-
-        let tasks = try store.fetchTasks()
-        #expect(tasks.first?.title == "Updated")
-    }
-
-    @Test("Update with invalid ID does nothing")
-    func updateWithInvalidIDDoesNothing() async throws {
-        let store = makeTestStore()
-        _ = try store.createTask(title: "Task 1")
-        let invalidID = UUID()
-
-        try store.update(taskID: invalidID, title: "Should not exist")
-
-        let tasks = try store.fetchTasks()
-        #expect(tasks.count == 1)
-        #expect(tasks.first?.title == "Task 1")
-    }
-
-    // MARK: - Delete Tests
-
-    @Test("Delete task")
-    func deleteTask() async throws {
-        let store = makeTestStore()
-        let task1 = try store.createTask(title: "Task 1")
-        let task2 = try store.createTask(title: "Task 2")
-
-        try store.delete(taskID: task1.id)
-
-        let tasks = try store.fetchTasks()
-        #expect(tasks.count == 1)
-        #expect(tasks.first?.id == task2.id)
-    }
-
-    @Test("Delete all tasks")
-    func deleteAllTasks() async throws {
-        let store = makeTestStore()
-        let task1 = try store.createTask(title: "Task 1")
-        let task2 = try store.createTask(title: "Task 2")
-
-        try store.delete(taskID: task1.id)
-        try store.delete(taskID: task2.id)
-
-        let tasks = try store.fetchTasks()
-        #expect(tasks.isEmpty)
-    }
-
-    @Test("Delete with invalid ID does nothing")
-    func deleteWithInvalidIDDoesNothing() async throws {
-        let store = makeTestStore()
-        _ = try store.createTask(title: "Task 1")
-        let invalidID = UUID()
-
-        try store.delete(taskID: invalidID)
-
-        let tasks = try store.fetchTasks()
-        #expect(tasks.count == 1)
-    }
-
-    // MARK: - Edge Cases
-
-    @Test("Task IDs persist across fetches")
-    func taskIDsPersistAcrossFetches() async throws {
-        let store = makeTestStore()
-        let task = try store.createTask(title: "Test")
-        let originalID = task.id
-
-        let fetchedTasks = try store.fetchTasks()
-        let fetchedID = fetchedTasks.first?.id
-
-        #expect(fetchedID == originalID)
-    }
-
-    @Test("Create task increments sortOrder")
-    func createTaskIncrementsSortOrder() async throws {
-        let store = makeTestStore()
-
-        let task1 = try store.createTask(title: "Task 1")
-        let task2 = try store.createTask(title: "Task 2")
-        let task3 = try store.createTask(title: "Task 3")
-
-        #expect(task2.sortOrder > task1.sortOrder)
-        #expect(task3.sortOrder > task2.sortOrder)
-        #expect(task2.sortOrder - task1.sortOrder == 1000)
-        #expect(task3.sortOrder - task2.sortOrder == 1000)
-    }
-}