25 KiB
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.Coreproject. 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 asList<int>, notList<Guid>. - Keep
doneas an ISO-8601 timestamp ornull; it is not a Boolean in the Deck API. Marking a card complete sends the current UTC time, and reopening it sends an explicit JSONnull. - 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-Matchonce 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:
- Forgejo Actions builds, tests, and publishes a versioned, signed
.msixbundlefor each tagged release. - The same release publishes a stable
Decky.appinstallerfile whoseMainBundlepoints to that release asset. - Users install
Decky.appinstalleronce. Windows App Installer checks for a newer package on launch and can also check in the background. - 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
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:
┌──────────────────────────────────────────────────────────────┐
│ 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:
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:
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:
┌────────────────────────────────┐
│ ✓ 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:
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:
Checkmark action:
○ Mark done and change color
○ Change color only
The first option should be the default.
### Right-click menu
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:
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:
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:
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:
https://drive.elijahkuntz.com
It can then test these API path forms:
/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:
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
Services/
├── DeckApiClient.cs
├── AuthenticationService.cs
├── CredentialService.cs
├── SettingsService.cs
├── ColorPresetService.cs
├── BoardSyncService.cs
└── WindowPlacementService.cs
DeckApiClient should expose methods such as:
Task<IReadOnlyList<BoardDto>> GetBoardsAsync();
Task<IReadOnlyList<StackDto>> GetStacksAsync(int boardId);
Task<CardDto> GetCardAsync(int boardId, int stackId, int cardId);
Task<CardDto> CreateCardAsync(CreateCardRequest request);
Task<CardDto> UpdateCardAsync(UpdateCardRequest request);
Task DeleteCardAsync(int boardId, int stackId, int cardId);
Task<CardDto> 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:
{
\  "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:
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:
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:
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:
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<int> OpenBoardTabs { get; set; } = \\\[];
\  public Dictionary<int, BoardViewSettings> Boards { get; set; } = \\\[];
\  public List<ColorPreset> ColorPresets { get; set; } = \\\[];
\  public Guid? ActiveColorPresetId { get; set; }
\  public Guid? CompletedColorPresetId { get; set; }
}
Per-board settings:
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
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:
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"