# Implementation plan: Nextcloud Deck desktop client ## Architecture review (July 19, 2026) The overall direction remains appropriate: a C# WinUI 3 client, an API-first core, and MSIX packaging are a good fit for a small Windows-only Deck client. The following decisions refine the original plan before implementation: - Target **.NET 10 LTS** and a stable Windows App SDK release. Pin exact package versions in the repository and update them deliberately. - Keep the Nextcloud API and domain code in a UI-independent `Decky.Core` project. This makes the compatibility spike and most tests runnable without installing or launching the desktop UI. - Treat board, stack, and card identifiers as `int`, matching the Deck REST API. Open board tabs are therefore stored as `List`, not `List`. - Keep `done` as an ISO-8601 timestamp or `null`; it is not a Boolean in the Deck API. Marking a card complete sends the current UTC time, and reopening it sends an explicit JSON `null`. - Normalize card and board colors to six-digit RGB values without a leading `#` at the API boundary; add `#` only for UI display. - Discover the Deck API base path once during connection and persist the working path. Prefer the documented `/index.php/apps/deck/api/v1.0/` path, then try the rewrite-friendly `/apps/deck/api/v1.0/` path. - Preserve the last successfully loaded board snapshot for disconnected viewing, but never store the app password in that snapshot or in settings. - Use ETags/`If-None-Match` once basic synchronization works; the Deck API propagates child changes into parent ETags and this avoids re-downloading unchanged boards every 30 seconds. - Store credentials through Windows Credential Locker behind an interface so the API core remains testable. Never pass credentials on command lines, including the API spike. ### Program update strategy Do **not** update an installed application by pulling its Git repository. A Git pull requires developer tools, exposes the source tree to local changes, and bypasses Windows package identity, signing, rollback, and file-integrity rules. Use this release flow instead: 1. Forgejo Actions builds, tests, and publishes a versioned, signed `.msixbundle` for each tagged release. 2. The same release publishes a stable `Decky.appinstaller` file whose `MainBundle` points to that release asset. 3. Users install `Decky.appinstaller` once. Windows App Installer checks for a newer package on launch and can also check in the background. 4. Package identity and publisher certificate remain constant; the four-part MSIX version increases for every release. For a private Forgejo instance, the release files must be reachable by Windows App Installer over HTTPS without an interactive Forgejo login. If private release assets cannot be exposed safely, publish signed artifacts to a small static HTTPS location (which may still be deployed by Forgejo Actions). Keep updates optional and non-blocking for this personal utility. A portable unpackaged build can be a later secondary distribution, but it would need a separate updater. I would build this as a \*\*C# WinUI 3 application using MVVM\*\*, distributed through an \*\*MSIX installer\*\*. WinUI 3 provides a modern native Windows interface, its list controls support drag-based reordering, and MSIX gives you a normal installed application rather than a folder of source files. An unpackaged self-contained executable could be added later, but MSIX is the cleaner primary distribution format. (\[Microsoft Learn]\[1]) \## 1. Defined scope for version 1 The first version should support: \* Connecting to one Nextcloud server \* Viewing every accessible Deck board \* Opening multiple boards in tabs \* Switching each board between horizontal and vertical layouts \* Creating, editing, deleting, and completing cards \* Dragging cards to reorder them within a list \* Dragging cards between lists \* Displaying and changing card colors \* Creating reusable local color presets \* Automatically applying configurable active and completed colors \* Periodic and manual synchronization \* Remembering window position, monitor, size, and layout choices It should not initially include attachments, comments, labels, sharing controls, board creation, or offline editing. Those would add a disproportionate amount of complexity for a desktop task list. \## 2. Technology stack ```text Language: C# UI framework: WinUI 3 / XAML Runtime: .NET 10 LTS Architecture: MVVM API client: HttpClient JSON: System.Text.Json Local settings: JSON files in local app data Credentials: Windows PasswordVault or Credential Manager Distribution: MSIX installer Testing: xUnit ``` Use `CommunityToolkit.Mvvm` for observable properties, commands, and dependency injection. It removes considerable MVVM boilerplate without changing the basic application architecture. No local database is necessary. The source of truth remains Nextcloud, while a few small JSON files hold interface settings and color presets. \## 3. Main interface \### Application shell The main window should contain: ```text ┌──────────────────────────────────────────────────────────────┐ │ Board tabs Refresh Layout Always on top Settings│ ├───────────────┬──────────────────────────────────────────────┤ │ Board list │ │ │ │ Selected board │ │ Homework │ │ │ Misc. To Do │ │ │ │ │ └───────────────┴──────────────────────────────────────────────┘ ``` The board sidebar should be collapsible, particularly for a narrow vertical monitor. Selecting a board opens it in the current tab. An option in its context menu can open it in a new tab. Each tab maintains its own scroll position and layout orientation. \### Horizontal board layout This resembles the standard Deck interface: ```text Saturday Sunday Monday ──────── ────── ────── Card Card Card Card Card Card \ Card ``` The stacks are arranged side by side inside a horizontal `ScrollViewer`. Every stack contains a vertical card `ListView`. \### Vertical board layout This is intended for the vertical monitor: ```text SATURDAY Card Card SUNDAY Card Card Card MONDAY Card Card ``` Each stack occupies the available width and the stacks are arranged from top to bottom. The orientation should be saved separately for each board. \## 4. Card design Each card should display: \* Card title \* Card background color \* Optional due date \* Checkmark button \* Context-menu button, visible on hover \* A subtle drag handle, or allow dragging from the whole card A compact card could look like: ```text ┌────────────────────────────────┐ │ ✓ Pharm Med Assignment 2 ⋮ │ └────────────────────────────────┘ ``` Text color should be calculated from the card background so light cards use dark text and dark cards use light text. \### Checkmark behavior The checkmark should perform two related actions: 1\. Set the card’s actual Deck `done` value. 2\. Apply the configured completed color preset. When unchecked: 1\. Clear the `done` value. 2\. Apply the configured active color preset. This means your typical configuration could be: ```text Active card preset: Red Completed card preset: Green ``` This is preferable to treating completion as only a visual color change because current Deck card updates support a `done` property as well as the card `color` property. (\[GitHub]\[2]) An optional setting can control this behavior: ```text Checkmark action: ○ Mark done and change color ○ Change color only ``` The first option should be the default. \### Right-click menu ```text Mark as done / Mark as not done Apply color preset > ────────────────────────────── Edit card Move to list > ────────────────────────────── Delete card Open in Nextcloud ``` “Apply color preset” opens a submenu containing every saved preset plus a custom color option. \## 5. Card editor Use a modal dialog or right-side panel containing: ```text Title Description Due date Color \ \\\[Red] \\\[Green] \\\[Orange] \\\[Blue] \\\[Custom...] \ \\\[Save current color as preset] Completed checkbox Cancel Save ``` Creating and editing should use the same component. When a custom color is selected, show: \* Visual color picker \* Hex value \* “Save as preset” checkbox \* Preset name field Saving a preset stores it locally. It does not create a Deck label or modify anything else on Nextcloud. \## 6. Color preset settings The settings page should have a dedicated section: ```text Color presets Name Color Actions Needs attention #D99080 Edit Delete Completed #7FBC9F Edit Delete Optional #D6B36A Edit Delete \\\[Add preset] Active-card preset: Needs attention Completed-card preset: Completed ``` Recommended local model: ```csharp public sealed class ColorPreset { \ public Guid Id { get; init; } \ public string Name { get; set; } = ""; \ public string HexColor { get; set; } = ""; } ``` Color presets should be global by default. Individual boards can optionally override the active and completed preset selections. \## 7. Nextcloud API integration Deck’s documented API supports loading boards and stacks, creating cards, updating cards, deleting cards, and changing card order. The current Deck controller also accepts `color` during create and update, and accepts `done` during update. (\[Nextcloud Deck]\[3]) \### API operations | Operation | Request | | --------------------- | --------------------------------------------------------------- | | List boards | `GET /boards` | | Load stacks and cards | `GET /boards/{boardId}/stacks` | | Get full card | `GET /boards/{boardId}/stacks/{stackId}/cards/{cardId}` | | Create card | `POST /boards/{boardId}/stacks/{stackId}/cards` | | Update card | `PUT /boards/{boardId}/stacks/{stackId}/cards/{cardId}` | | Delete card | `DELETE /boards/{boardId}/stacks/{stackId}/cards/{cardId}` | | Reorder or move | `PUT /boards/{boardId}/stacks/{stackId}/cards/{cardId}/reorder` | The client should accept only the server root from the user: ```text https://drive.elijahkuntz.com ``` It can then test these API path forms: ```text /apps/deck/api/v1.0 /index.php/apps/deck/api/v1.0 ``` The second is the formally documented form, while installations using rewritten URLs commonly omit `index.php`. (\[Nextcloud Deck]\[3]) \### Authentication The first-run connection screen should request: \* Nextcloud server address \* Username \* App password Use Basic authentication with the app password and include: ```http OCS-APIRequest: true Accept: application/json Content-Type: application/json ``` App passwords are preferable because they can be revoked independently and do not expose the primary account password. Nextcloud supports Basic authentication using a username and password or app token. (\[Nextcloud]\[4]) The app password must be stored in Windows credential storage, never in `settings.json`. \## 8. API client structure ```text Services/ ├── DeckApiClient.cs ├── AuthenticationService.cs ├── CredentialService.cs ├── SettingsService.cs ├── ColorPresetService.cs ├── BoardSyncService.cs └── WindowPlacementService.cs ``` `DeckApiClient` should expose methods such as: ```csharp Task> GetBoardsAsync(); Task> GetStacksAsync(int boardId); Task GetCardAsync(int boardId, int stackId, int cardId); Task CreateCardAsync(CreateCardRequest request); Task UpdateCardAsync(UpdateCardRequest request); Task DeleteCardAsync(int boardId, int stackId, int cardId); Task ReorderCardAsync(ReorderCardRequest request); ``` Use one long-lived `HttpClient`, not a new instance for each request. \## 9. Important card-update behavior Deck’s card update is effectively a complete update rather than a simple patch. The current controller expects required values such as `title`, `type`, and `owner`, with other fields passed alongside them. The desktop application should retain the complete server card model and construct an update request from the latest copy rather than creating a minimal payload. (\[GitHub]\[2]) This is especially important for colors. Deck 1.18.2 has a currently reported bug where updating a card without including `color` can clear the existing color. Every card update from this client should therefore resend the existing color, even when only changing the title or completion status. (\[GitHub]\[5]) The update request should include at least: ```json { \ "title": "Pharm Med Assignment 2", \ "type": "plain", \ "owner": "Elijah", \ "description": "", \ "order": 2, \ "duedate": null, \ "startdate": null, \ "archived": false, \ "done": null, \ "color": "D99080" } ``` The DTO should distinguish between: \* A field not being sent \* A field explicitly being sent as `null` That distinction matters for clearing `done`, due dates, and other nullable fields. \## 10. Drag-and-drop implementation WinUI `ListView` supports drag reordering within a list. Moving between separate stack lists requires handling the drag source and destination manually. (\[Microsoft Learn]\[6]) \### Drag data When dragging starts, attach: ```csharp public sealed record CardDragData( \ int BoardId, \ int CardId, \ int SourceStackId, \ int SourceIndex); ``` \### Drop process 1\. Determine the destination stack. 2\. Determine the destination card index. 3\. Move the card locally for immediate feedback. 4\. Lock further card mutations for that board. 5\. Call the reorder endpoint with the destination stack ID and index. 6\. Verify the returned `stackId` and `order`. 7\. Keep the change on success. 8\. Roll back the local change on failure. 9\. Reload the affected stacks from Nextcloud. 10\. Release the board mutation lock. Use a per-board `SemaphoreSlim` so rapid drag operations cannot overlap and corrupt the local ordering. \### Cross-stack compatibility check A reported Deck 1.16.4 API problem caused the source stack ID in the URL to override the destination stack ID in the request body, preventing cards from moving between stacks through the reorder endpoint. (\[GitHub]\[7]) Because Deck versions vary, the application should verify the response from every cross-stack move: ```text Requested destination stack: 12 Returned card stack: 12 -> success Requested destination stack: 12 Returned card stack: 9 -> rollback and compatibility warning ``` Do not assume an HTTP 200 response means the card actually moved. The application can show: ```text Your installed Deck version did not accept a cross-list move. The card has been restored to its original position. ``` This needs to be tested against your actual server before the rest of drag-and-drop development proceeds. \## 11. Synchronization approach Keep synchronization intentionally simple. \### Initial loading 1\. Load the list of boards. 2\. Restore previously open board tabs. 3\. Load only those boards. 4\. Lazy-load other boards when selected. \### Refresh behavior \* Automatic refresh every 30 seconds by default \* Manual refresh button \* Pause background refresh while a card is being edited \* Refresh the affected board after every mutation \* Show “Last updated” in a tooltip or status area \### Optimistic changes Use optimistic updates for: \* Reordering \* Completion toggles \* Color changes Use confirmed updates for: \* Creating cards \* Editing card details \* Deleting cards For deletion, show a confirmation dialog. Do not automatically retry a failed delete. \### Offline behavior Version 1 should be read-only when disconnected: \* Keep the most recently loaded cards on screen \* Display an offline banner \* Disable create, edit, delete, and dragging \* Retry when the user refreshes or the polling timer runs An offline mutation queue is unnecessary for this application. \## 12. Local settings Suggested settings model: ```csharp public sealed class AppSettings { \ public string ServerUrl { get; set; } = ""; \ public string Username { get; set; } = ""; \ public int RefreshIntervalSeconds { get; set; } = 30; \ public bool AlwaysOnTop { get; set; } \ public bool StartWithWindows { get; set; } \ public bool StartMinimized { get; set; } \ public List OpenBoardTabs { get; set; } = \\\[]; \ public Dictionary Boards { get; set; } = \\\[]; \ public List ColorPresets { get; set; } = \\\[]; \ public Guid? ActiveColorPresetId { get; set; } \ public Guid? CompletedColorPresetId { get; set; } } ``` Per-board settings: ```csharp public sealed class BoardViewSettings { \ public BoardOrientation Orientation { get; set; } \ public Guid? ActiveColorPresetOverride { get; set; } \ public Guid? CompletedColorPresetOverride { get; set; } } ``` Also persist: \* Window size \* Window coordinates \* Monitor identifier \* Sidebar width and collapsed state \* Last selected board \* Open tabs Validate the saved monitor position at launch so the window is not placed offscreen after a monitor is disconnected. \## 13. Suggested project structure ```text DeckDesktop/ ├── App.xaml ├── App.xaml.cs ├── Models/ │ ├── Board.cs │ ├── Stack.cs │ ├── Card.cs │ ├── ColorPreset.cs │ └── AppSettings.cs ├── Api/ │ ├── DeckApiClient.cs │ ├── Dtos/ │ └── Requests/ ├── Services/ │ ├── BoardSyncService.cs │ ├── CredentialService.cs │ ├── SettingsService.cs │ ├── ColorPresetService.cs │ └── WindowPlacementService.cs ├── ViewModels/ │ ├── MainViewModel.cs │ ├── BoardViewModel.cs │ ├── StackViewModel.cs │ ├── CardViewModel.cs │ ├── CardEditorViewModel.cs │ └── SettingsViewModel.cs ├── Views/ │ ├── MainWindow.xaml │ ├── ConnectionPage.xaml │ ├── BoardView.xaml │ ├── CardEditorDialog.xaml │ └── SettingsPage.xaml ├── Controls/ │ ├── DeckCardControl.xaml │ ├── HorizontalBoardControl.xaml │ └── VerticalBoardControl.xaml └── Tests/ \ ├── DeckApiClientTests.cs \ ├── CardUpdateRequestTests.cs \ ├── DragOrderTests.cs \ └── ColorPresetTests.cs ``` \## 14. Development phases \### Phase 0: API compatibility spike Before building the interface, create a console test client and a disposable Deck test board. Verify: \* Authentication \* Board retrieval \* Card color in responses \* Creating a card with a color \* Editing without losing color \* Setting and clearing `done` \* Reordering within one stack \* Moving between stacks \* Deleting a card This phase is important because the published API documentation does not yet fully describe newer fields such as card color, while current source code does. Card colors were added to Deck in version 1.18.0. (\[GitHub]\[8]) \### Phase 1: Read-only application Build: \* Connection screen \* Credential storage \* Board sidebar \* Board tabs \* Horizontal layout \* Vertical layout \* Card color rendering \* Manual refresh \* Window placement persistence Acceptance criteria: \* The program starts as an installed application. \* It reconnects without requesting credentials again. \* It displays the same lists, cards, order, and colors as Nextcloud. \* Layout orientation persists per board. \### Phase 2: Card CRUD Build: \* New-card dialog \* Edit-card dialog \* Delete confirmation \* Color picker \* Automatic board refresh \* Error notifications Acceptance criteria: \* New cards appear in both the desktop client and web Deck. \* Edited titles, descriptions, dates, colors, and completion status persist. \* Unrelated card properties are never lost. \* Delete removes the correct card. \### Phase 3: Drag and drop Build: \* Same-stack reordering \* Cross-stack movement \* Drop indicators \* Optimistic movement \* Response verification \* Rollback on failure \* Per-board mutation locking Acceptance criteria: \* Cards retain the correct order after reloading. \* Cross-stack moves are verified against the server response. \* Failed operations restore the previous UI state. \### Phase 4: Color presets and completion workflow Build: \* Preset management page \* Custom preset creation from the editor \* Preset context menu \* Active and completed preset selection \* Checkmark completion button \* Board-specific overrides Acceptance criteria: \* Clicking the checkmark changes the card to the completed preset. \* Unchecking applies the active preset. \* The actual Deck `done` state is synchronized when enabled. \* Presets remain available after restarting. \### Phase 5: Desktop polish and release Build: \* Light and dark themes \* Always-on-top option \* Launch at Windows startup \* Keyboard shortcuts \* Loading placeholders \* Offline status banner \* Logging \* Application icon \* MSIX packaging Suggested keyboard controls: ```text Ctrl+N New card Ctrl+R Refresh board Ctrl+Tab Next board tab Ctrl+Shift+Tab Previous board tab Ctrl+, Settings Enter Edit selected card Delete Delete selected card Space Toggle completion ``` \## 15. Final acceptance checklist The application is complete when: \* It installs through one MSIX package. \* Credentials are not stored as plaintext. \* Every accessible Deck board can be opened. \* Multiple boards can remain open in tabs. \* Horizontal and vertical layouts work and persist independently. \* Cards can be created, edited, deleted, reordered, and moved. \* Colors remain intact during every type of update. \* Color presets can be added, edited, removed, and applied. \* The checkmark reliably changes completion state and color. \* Network or API failures do not silently discard or misplace cards. \* The application restores itself to the correct monitor and position. \* A test suite covers request serialization, color preservation, completion toggling, and drag-order calculations. This keeps the project small enough to remain a simple personal utility while leaving clear extension points for labels, due-date notifications, attachments, and comments later. \[1]: https://learn.microsoft.com/en-us/windows/apps/get-started/winui-get-started-overview?utm\_source=chatgpt.com "Get started with WinUI - Windows apps | Microsoft Learn" \[2]: https://github.com/nextcloud/deck/blob/main/lib/Controller/CardApiController.php?utm\_source=chatgpt.com "deck/lib/Controller/CardApiController.php at main" \[3]: https://deck.readthedocs.io/en/latest/API/?utm\_source=chatgpt.com "REST API - Nextcloud Deck" \[4]: https://docs.nextcloud.com/server/latest/developer\_manual/client\_apis/OCS/ocs-api-overview.html?utm\_source=chatgpt.com "OCS APIs overview — Nextcloud latest Developer Manual latest documentation" \[5]: https://github.com/nextcloud/deck/issues/8131?utm\_source=chatgpt.com "Card update API clears the existing color when ..." \[6]: https://learn.microsoft.com/sr-cyrl-rs/windows/apps/develop/data/drag-and-drop?view=gestures-1.0\&utm\_source=chatgpt.com "Drag and drop - Windows apps | Microsoft Learn" \[7]: https://github.com/nextcloud/deck/issues/7933 "Deck API: CardApiController reorder() ignores request body stackId due to route parameter conflict · Issue #7933 · nextcloud/deck · GitHub" \[8]: https://github.com/nextcloud/deck/releases "Releases · nextcloud/deck · GitHub"